← back to davejharmon__murderhouse

Function bodies 456 total

All specs Real LLM only Function bodies
drawText function · javascript · L84-L91 (8 LOC)
client/src/components/TinyScreen.jsx
function drawText(ctx, text, x, baselineY, font) {
  for (let i = 0; i < text.length; i++) {
    const code = text.charCodeAt(i)
    if (code >= 32 && code <= 126) {
      drawChar(ctx, code, x + i * font.width, baselineY, font)
    }
  }
}
drawIcon function · javascript · L97-L123 (27 LOC)
client/src/components/TinyScreen.jsx
function drawIcon(ctx, iconId, x, y, color = ICON_COLOR) {
  const bytes = Icons[iconId]
  if (!bytes) return

  ctx.fillStyle = color
  for (let row = 0; row < ICON_SIZE; row++) {
    const b0 = bytes[row * 3]
    const b1 = bytes[row * 3 + 1]
    const b2 = bytes[row * 3 + 2]
    for (let bit = 0; bit < ICON_SIZE; bit++) {
      let byteIdx, bitIdx
      if (bit < 8) {
        byteIdx = b0
        bitIdx = bit
      } else if (bit < 16) {
        byteIdx = b1
        bitIdx = bit - 8
      } else {
        byteIdx = b2
        bitIdx = bit - 16
      }
      if (byteIdx & (1 << bitIdx)) {
        ctx.fillRect(x + bit, y + row, 1, 1)
      }
    }
  }
}
drawSelectionBar function · javascript · L128-L132 (5 LOC)
client/src/components/TinyScreen.jsx
function drawSelectionBar(ctx, activeIndex, color) {
  if (activeIndex < 0 || activeIndex > 2) return
  ctx.fillStyle = color
  ctx.fillRect(BAR_X, SLOT_Y[activeIndex], BAR_W, ICON_SLOT_H)
}
operatorWordWrap function · javascript · L154-L174 (21 LOC)
client/src/components/TinyScreen.jsx
function operatorWordWrap(sentence, previewWord) {
  const combined = [sentence.trim(), previewWord].filter(Boolean).join(' ')
  if (!combined) return ['', '', '']

  const words = combined.split(/\s+/)
  const rows = ['', '', '']
  let row = 0

  for (const w of words) {
    if (row >= 3) break
    const test = rows[row] ? rows[row] + ' ' + w : w
    if (test.length <= OP_MAX_CHARS) {
      rows[row] = test
    } else if (row < 2) {
      row++
      rows[row] = w
    }
  }

  return rows
}
renderOperatorDisplay function · javascript · L180-L236 (57 LOC)
client/src/components/TinyScreen.jsx
function renderOperatorDisplay(ctx, display, color, previewVisible = true) {
  const sentence    = display.line1?.left || ''
  const previewWord = display.line2?.text || ''

  const rows = operatorWordWrap(sentence, previewWord)

  // Locate which row ends with the preview word (it's always the last word)
  let previewRow = -1
  let previewX   = MARGIN_X
  if (previewWord) {
    for (let r = 2; r >= 0; r--) {
      if (rows[r] && rows[r].endsWith(previewWord)) {
        previewRow = r
        const prefixLen = rows[r].length - previewWord.length
        previewX = MARGIN_X + prefixLen * FONT_6x10.width
        break
      }
    }
  }

  ctx.fillStyle = color

  for (let r = 0; r < 3; r++) {
    if (!rows[r]) continue

    if (r === previewRow && previewWord) {
      // Draw committed prefix on this row
      const prefixLen = rows[r].length - previewWord.length
      drawText(ctx, rows[r].substring(0, prefixLen), MARGIN_X, OPERATOR_LINE_Y[r], FONT_6x10)
      // Blink: only draw previe
renderDisplay function · javascript · L243-L328 (86 LOC)
client/src/components/TinyScreen.jsx
function renderDisplay(ctx, display, color, line2Color = null, iconColors = null) {
  // Operator sentence mode has completely different layout
  if (display.line2?.style === 'operator') {
    renderOperatorDisplay(ctx, display, color)
    return
  }
  const { line1, line2, line3, icons } = display
  const isLocked = line2.style === 'locked'

  // Effective line 2 color: explicit override → locked bright → base color
  const l2Color = line2Color !== null ? line2Color
    : isLocked ? COLOR_BRIGHT
    : color

  // === ICON COLUMN ===
  if (icons && icons.length === 3) {
    for (let i = 0; i < 3; i++) {
      const icon = icons[i]
      if (icon && icon.id) {
        const slotColor = (iconColors && iconColors[i] != null) ? iconColors[i] : ICON_COLOR
        drawIcon(ctx, icon.id, ICON_COL_X, ICON_Y[i], slotColor)
      }
    }
    // Draw selection bar next to the active icon slot (only if 2+ icons visible)
    const visibleCount = icons.filter(ic => ic && ic.id && ic.id !== 'empty').
TinyScreen function · javascript · L334-L526 (193 LOC)
client/src/components/TinyScreen.jsx
export default function TinyScreen({ display, compact = false }) {
  const canvasRef = useRef(null)
  const animRef = useRef(null)
  const criticalSeenRef = useRef(new Set()) // keys of critical content already flashed
  const prevIconsRef = useRef(null)   // previous icon state for change detection
  const iconBlinkRef = useRef({})     // { slotIndex: startTimestamp } for active blinks

  const renderFrame = useCallback((canvas, style, time) => {
    if (!canvas || !display) return
    const ctx = canvas.getContext('2d')

    // Clear to OLED background
    ctx.fillStyle = COLOR_BG
    ctx.fillRect(0, 0, W, H)

    // Determine draw color based on style
    let color = COLOR_NORMAL
    if (style === 'abstained') {
      color = COLOR_DIM
    } else if (style === 'waiting' && time !== undefined) {
      // Pulse between dim and normal over 2s
      const t = (Math.sin(time / 1000 * Math.PI) + 1) / 2 // 0->1->0 over 2s
      const r = Math.round(0x80 + (0xff - 0x80) * t)
      const g =
Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
TutorialSlidesModal function · javascript · L7-L85 (79 LOC)
client/src/components/TutorialSlidesModal.jsx
export default function TutorialSlidesModal({
  isOpen,
  onClose,
  players,
  onPushCompSlide,
  onPushRoleTipSlide,
  onPushItemTipSlide,
}) {
  const [expanded, setExpanded] = useState(false);

  // Roles currently in the game (pre-assigned in lobby, or active role in game)
  const gameRoles = [...new Set(
    (players || [])
      .map(p => p.preAssignedRole || p.role)
      .filter(Boolean)
  )];

  const rolesToShow = expanded ? AVAILABLE_ROLES : gameRoles;

  return (
    <Modal isOpen={isOpen} onClose={onClose} title="TUTORIAL SLIDES">
      <div className={styles.section}>
        <div className={styles.sectionHeader}>
          <span className={styles.sectionLabel}>ROLES</span>
          {!expanded && gameRoles.length > 0 && (
            <span className={styles.sectionHint}>in game</span>
          )}
          <button
            className={styles.expandBtn}
            onClick={() => setExpanded(e => !e)}
          >
            {expanded ? '▲ in game' : '▼ all roles'}
  
GameProvider function · javascript · L13-L260 (248 LOC)
client/src/context/GameContext.jsx
export function GameProvider({ children }) {
  const [connected, setConnected] = useState(false);
  const [gameState, setGameState] = useState(null);
  const [playerState, setPlayerState] = useState(null);
  const [slideQueue, setSlideQueue] = useState({ queue: [], currentIndex: -1, current: null });
  const [currentSlide, setCurrentSlide] = useState(null);
  const [log, setLog] = useState([]);
  const [eventPrompt, setEventPrompt] = useState(null);
  const [eventResult, setEventResult] = useState(null);
  const [notifications, setNotifications] = useState([]);
  const [eventTimers, setEventTimers] = useState({});
  const [gamePresets, setGamePresets] = useState([]);
  const [presetSettings, setPresetSettings] = useState(null);
  const [hostSettings, setHostSettings] = useState(null);
  const [operatorState, setOperatorState] = useState({ words: [], ready: false });
  const [scores, setScores] = useState({});
  const [calibrationState, setCalibrationState] = useState(null);

  const ws
useGame function · javascript · L262-L268 (7 LOC)
client/src/context/GameContext.jsx
export function useGame() {
  const context = useContext(GameContext);
  if (!context) {
    throw new Error('useGame must be used within a GameProvider');
  }
  return context;
}
DebugGrid function · javascript · L13-L199 (187 LOC)
client/src/pages/DebugGrid.jsx
export default function DebugGrid() {
  const [playerStates, setPlayerStates] = useState({});
  const [gameStates, setGameStates] = useState({});
  const [eventPrompts, setEventPrompts] = useState({});
  const [eventResults, setEventResults] = useState({});
  const [connections, setConnections] = useState({});
  const wsRefs = useRef({});
  const gameStatesRef = useRef({});

  // Set page title
  useEffect(() => {
    document.title = 'Debug - MURDERHOUSE';
  }, []);

  // Connect all 9 players
  useEffect(() => {
    const newConnections = {};

    for (let i = 1; i <= PLAYER_COUNT; i++) {
      const playerId = String(i);
      const ws = new WebSocket(WS_URL);

      ws.onopen = () => {
        console.log(`[Player ${playerId}] Connected`);
        ws.send(
          JSON.stringify({
            type: ClientMsg.JOIN,
            payload: { playerId },
          })
        );
        newConnections[playerId] = true;
        setConnections({ ...newConnections });
      };

      ws.on
Landing function · javascript · L7-L82 (76 LOC)
client/src/pages/Landing.jsx
export default function Landing() {
  const { connected, gameState } = useGame();
  const players = gameState?.players || [];

  return (
    <div className={styles.container}>
      <div className={styles.content}>
        <header className={styles.header}>
          <h1>{getStr('landing', 'title')}</h1>
          <p className={styles.tagline}>{getStr('landing', 'tagline')}</p>
        </header>

        <section className={styles.links}>
          <div className={styles.linkGroup}>
            <h2>Join as Player</h2>
            <div className={styles.playerGrid}>
              {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => {
                const taken = players.some((p) => p.id === String(num));
                return (
                  <Link
                    key={num}
                    to={`/player/${num}`}
                    className={`${styles.playerLink} ${
                      taken ? styles.taken : ''
                    }`}
                  >
                    <span c
buildDisplay function · javascript · L27-L58 (32 LOC)
client/src/pages/Operator.jsx
function buildDisplay(wordIndex, words, ready, cleared) {
  const entry = FLAT_WORDS[wordIndex] || FLAT_WORDS[0];

  if (cleared) {
    return {
      line1: { left: '', right: '' },
      line2: { text: 'SENT!', style: 'critical' },
      line3: { text: '', left: '', right: '', center: '' },
      icons: [
        { id: 'empty', state: 'empty' },
        { id: 'empty', state: 'empty' },
        { id: 'empty', state: 'empty' },
      ],
      idleScrollIndex: 0,
    };
  }

  const previewWord = ready ? '' : entry.word;
  const catIconId   = ready ? 'empty' : entry.icon;

  return {
    line1: { left: words.join(' '), right: '' },
    line2: { text: previewWord, style: 'operator' },
    line3: { text: '', left: '', right: '', center: '' },
    icons: [
      { id: catIconId, state: 'normal' },
      { id: 'empty',   state: 'empty' },
      { id: ready ? 'op_tick' : 'empty', state: ready ? 'normal' : 'empty' },
    ],
    idleScrollIndex: 0,
  };
}
Operator function · javascript · L62-L277 (216 LOC)
client/src/pages/Operator.jsx
export default function Operator() {
  const wsRef    = useRef(null);
  const readyRef = useRef(false);  // mutable ref for stale-closure-safe access in onmessage
  const yesTimerRef    = useRef(null);  // pointer long press timers
  const noTimerRef     = useRef(null);
  const keyYesTimerRef = useRef(null);  // keyboard long press timers
  const keyNoTimerRef  = useRef(null);
  const [connected, setConnected] = useState(false);

  // Server-sync state
  const [words, setWords] = useState([]);
  const [ready, setReady] = useState(false);
  const [cleared, setCleared] = useState(false);

  // Local dial state
  const [wordIndex, setWordIndex] = useState(0);

  const send = useCallback((type, payload = {}) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify({ type, payload }));
    }
  }, []);

  // WebSocket connection
  useEffect(() => {
    const connect = () => {
      const ws = new WebSocket(WS_URL);
      wsRef.current = ws;

      
handleDialUp function · javascript · L127-L130 (4 LOC)
client/src/pages/Operator.jsx
  function handleDialUp() {
    if (ready || cleared) return;
    setWordIndex(i => (i - 1 + FLAT_WORDS.length) % FLAT_WORDS.length);
  }
Repobility (the analyzer behind this table) · https://repobility.com
handleDialDown function · javascript · L132-L135 (4 LOC)
client/src/pages/Operator.jsx
  function handleDialDown() {
    if (ready || cleared) return;
    setWordIndex(i => (i + 1) % FLAT_WORDS.length);
  }
handleYes function · javascript · L137-L140 (4 LOC)
client/src/pages/Operator.jsx
  function handleYes() {
    if (ready || cleared) return;
    send(ClientMsg.OPERATOR_ADD, { word: entry.word });
  }
handleNo function · javascript · L142-L155 (14 LOC)
client/src/pages/Operator.jsx
  function handleNo() {
    if (cleared) return;
    if (ready) {
      send(ClientMsg.OPERATOR_UNREADY);
      return;
    }
    if (words.length > 0) {
      // Jump dial to the position of the word being deleted
      const lastWord = words[words.length - 1];
      const idx = FLAT_WORDS.findIndex(w => w.word === lastWord);
      if (idx !== -1) setWordIndex(idx);
      send(ClientMsg.OPERATOR_DELETE);
    }
  }
handleLongYes function · javascript · L157-L160 (4 LOC)
client/src/pages/Operator.jsx
  function handleLongYes() {
    if (ready || cleared) return;
    if (words.length > 0) send(ClientMsg.OPERATOR_READY);
  }
handleLongNo function · javascript · L162-L165 (4 LOC)
client/src/pages/Operator.jsx
  function handleLongNo() {
    if (cleared) return;
    if (words.length > 0 || ready) send(ClientMsg.OPERATOR_CLEAR);
  }
handleYesPointerDown function · javascript · L168-L171 (4 LOC)
client/src/pages/Operator.jsx
  function handleYesPointerDown() {
    if (!yesEnabled) return;
    yesTimerRef.current = setTimeout(() => { yesTimerRef.current = null; handleLongYes(); }, LONG_PRESS_MS);
  }
handleYesPointerUp function · javascript · L172-L174 (3 LOC)
client/src/pages/Operator.jsx
  function handleYesPointerUp() {
    if (yesTimerRef.current) { clearTimeout(yesTimerRef.current); yesTimerRef.current = null; handleYes(); }
  }
handleYesPointerLeave function · javascript · L175-L177 (3 LOC)
client/src/pages/Operator.jsx
  function handleYesPointerLeave() {
    clearTimeout(yesTimerRef.current); yesTimerRef.current = null;
  }
All rows scored by the Repobility analyzer (https://repobility.com)
handleNoPointerDown function · javascript · L180-L183 (4 LOC)
client/src/pages/Operator.jsx
  function handleNoPointerDown() {
    if (!noEnabled) return;
    noTimerRef.current = setTimeout(() => { noTimerRef.current = null; handleLongNo(); }, LONG_PRESS_MS);
  }
handleNoPointerUp function · javascript · L184-L186 (3 LOC)
client/src/pages/Operator.jsx
  function handleNoPointerUp() {
    if (noTimerRef.current) { clearTimeout(noTimerRef.current); noTimerRef.current = null; handleNo(); }
  }
handleNoPointerLeave function · javascript · L187-L189 (3 LOC)
client/src/pages/Operator.jsx
  function handleNoPointerLeave() {
    clearTimeout(noTimerRef.current); noTimerRef.current = null;
  }
Player function · javascript · L10-L178 (169 LOC)
client/src/pages/Player.jsx
export default function Player() {
  const { id } = useParams();
  const playerId = id;

  const {
    connected,
    gameState,
    playerState,
    eventPrompt,
    eventResult,
    notifications,
    send,
    joinAsPlayer,
    rejoinAsPlayer,
  } = useGame();

  // Set page title
  useEffect(() => {
    document.title = `Player ${playerId} - MURDERHOUSE`;
  }, [playerId]);

  // Join game on mount
  useEffect(() => {
    if (connected && playerId) {
      // JOIN handles both new players and reconnection
      joinAsPlayer(playerId);
    }
  }, [connected, playerId, joinAsPlayer]);

  // Auto-rejoin after game reset
  useEffect(() => {
    // If we're connected but have no playerState, and game is in LOBBY, rejoin
    if (connected && playerId && !playerState && gameState?.phase === GamePhase.LOBBY) {
      console.log('[Player] Detected reset, rejoining...');
      joinAsPlayer(playerId);
    }
  }, [connected, playerId, playerState, gameState?.phase, joinAsPlayer]);

  // Handle 
Screen function · javascript · L26-L153 (128 LOC)
client/src/pages/Screen.jsx
export default function Screen() {
  const {
    connected,
    gameState,
    currentSlide,
    slideQueue,
    eventTimers,
    connectAsScreen,
  } = useGame()

  const effectiveSlide = currentSlide || slideQueue?.current

  useEffect(() => {
    console.log('[Screen] State update:', {
      connected,
      phase: gameState?.phase,
      playerCount: gameState?.players?.length,
      currentSlide: currentSlide ? { type: currentSlide.type, id: currentSlide.id } : null,
      slideQueueCurrent: slideQueue?.current ? { type: slideQueue.current.type, id: slideQueue.current.id } : null,
      effectiveSlide: effectiveSlide ? { type: effectiveSlide.type, id: effectiveSlide.id } : null,
      slideQueueLen: slideQueue?.queue?.length,
    })
  }, [connected, gameState, currentSlide, slideQueue, effectiveSlide])

  useEffect(() => {
    document.title = 'Screen - MURDERHOUSE'
  }, [])

  useEffect(() => {
    if (connected) connectAsScreen()
  }, [connected, connectAsScreen])

  const rende
loadOverrides function · javascript · L34-L41 (8 LOC)
client/src/pages/SlideEditor.jsx
function loadOverrides() {
  try {
    const raw = localStorage.getItem(LS_KEY)
    return raw ? JSON.parse(raw) : {}
  } catch {
    return {}
  }
}
mergeStrings function · javascript · L43-L49 (7 LOC)
client/src/pages/SlideEditor.jsx
function mergeStrings(overrides) {
  const merged = {}
  for (const group of Object.keys(SLIDE_STRINGS)) {
    merged[group] = { ...SLIDE_STRINGS[group], ...(overrides[group] || {}) }
  }
  return merged
}
renderSlideComponent function · javascript · L51-L88 (38 LOC)
client/src/pages/SlideEditor.jsx
function renderSlideComponent(slideType, variantKey, slide, gameState, strings) {
  const players = MOCK_PLAYERS

  if (slideType === 'fallback') {
    return <FallbackSlide gameState={gameState ?? MOCK_GAME_STATE_LOBBY} strings={strings.fallback} />
  }

  switch (slideType) {
    case SlideType.TITLE:
      return <TitleSlide slide={slide} players={players} />
    case SlideType.PLAYER_REVEAL:
      return <PlayerRevealSlide slide={slide} players={players} />
    case SlideType.VOTE_TALLY:
      return <VoteTallySlide slide={slide} players={players} />
    case SlideType.GALLERY:
      return <GallerySlide slide={slide} players={players} gameState={MOCK_GAME_STATE_DAY} strings={strings.gallery} />
    case SlideType.COUNTDOWN:
      return <CountdownSlide slide={slide} />
    case SlideType.DEATH:
      return <DeathSlide slide={slide} players={players} strings={strings.death} />
    case SlideType.VICTORY:
      return <VictorySlide slide={slide} />
    case SlideType.COMPOSITION:
 
Repobility · open methodology · https://repobility.com/research/
useAutoScale function · javascript · L91-L103 (13 LOC)
client/src/pages/SlideEditor.jsx
function useAutoScale(wrapperRef, deps) {
  useLayoutEffect(() => {
    const wrapper = wrapperRef.current
    const slide = wrapper?.firstElementChild
    if (!slide) return
    slide.style.transform = ''
    const availableH = wrapper.clientHeight
    const naturalH = slide.offsetHeight
    if (naturalH > availableH) {
      slide.style.transform = `scale(${availableH / naturalH})`
    }
  }, deps) // eslint-disable-line react-hooks/exhaustive-deps
}
SlideEditor function · javascript · L105-L229 (125 LOC)
client/src/pages/SlideEditor.jsx
export default function SlideEditor() {
  const [selectedType, setSelectedType]     = useState(SLIDE_EDITOR_LIST[0].type)
  const [selectedVariant, setSelectedVariant] = useState(0)
  const [overrides, setOverrides]           = useState(loadOverrides)
  const [copyFeedback, setCopyFeedback]     = useState(false)

  const strings = mergeStrings(overrides)

  const entryDef = SLIDE_EDITOR_LIST.find(e => e.type === selectedType) || SLIDE_EDITOR_LIST[0]
  const variant  = entryDef.variants[selectedVariant] || entryDef.variants[0]
  const slide    = MOCK_SLIDES[variant.key]
  const gameState = variant.gameState ?? (selectedType === 'fallback' ? MOCK_GAME_STATE_LOBBY : MOCK_GAME_STATE_DAY)

  const wrapperRef = useRef(null)
  useAutoScale(wrapperRef, [selectedType, selectedVariant, strings])

  // Which string groups to show
  const stringGroupKeys = SLIDE_STRING_KEYS[selectedType] || []

  function handleStringChange(group, key, value) {
    setOverrides(prev => {
      const next = { ...pr
handleStringChange function · javascript · L124-L130 (7 LOC)
client/src/pages/SlideEditor.jsx
  function handleStringChange(group, key, value) {
    setOverrides(prev => {
      const next = { ...prev, [group]: { ...(prev[group] || {}), [key]: value } }
      localStorage.setItem(LS_KEY, JSON.stringify(next))
      return next
    })
  }
handleReset function · javascript · L132-L135 (4 LOC)
client/src/pages/SlideEditor.jsx
  function handleReset() {
    localStorage.removeItem(LS_KEY)
    setOverrides({})
  }
handleCopyJson function · javascript · L137-L142 (6 LOC)
client/src/pages/SlideEditor.jsx
  function handleCopyJson() {
    navigator.clipboard.writeText(JSON.stringify(strings, null, 2)).then(() => {
      setCopyFeedback(true)
      setTimeout(() => setCopyFeedback(false), 2000)
    })
  }
handleSelectType function · javascript · L145-L148 (4 LOC)
client/src/pages/SlideEditor.jsx
  function handleSelectType(type) {
    setSelectedType(type)
    setSelectedVariant(0)
  }
loadOverrides function · javascript · L14-L21 (8 LOC)
client/src/pages/StringSheets.jsx
function loadOverrides() {
  try {
    const raw = localStorage.getItem(LS_KEY)
    return raw ? JSON.parse(raw) : {}
  } catch {
    return {}
  }
}
saveOverrides function · javascript · L23-L25 (3 LOC)
client/src/pages/StringSheets.jsx
function saveOverrides(overrides) {
  localStorage.setItem(LS_KEY, JSON.stringify(overrides))
}
Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
loadTagOverrides function · javascript · L27-L34 (8 LOC)
client/src/pages/StringSheets.jsx
function loadTagOverrides() {
  try {
    const raw = localStorage.getItem(LS_TAGS_KEY)
    return raw ? JSON.parse(raw) : {}
  } catch {
    return {}
  }
}
saveTagOverrides function · javascript · L36-L38 (3 LOC)
client/src/pages/StringSheets.jsx
function saveTagOverrides(tagOverrides) {
  localStorage.setItem(LS_TAGS_KEY, JSON.stringify(tagOverrides))
}
loadTokenExamples function · javascript · L40-L47 (8 LOC)
client/src/pages/StringSheets.jsx
function loadTokenExamples() {
  try {
    const raw = localStorage.getItem(LS_TOKEN_KEY)
    return raw ? JSON.parse(raw) : {}
  } catch {
    return {}
  }
}
saveTokenExamples function · javascript · L49-L51 (3 LOC)
client/src/pages/StringSheets.jsx
function saveTokenExamples(examples) {
  localStorage.setItem(LS_TOKEN_KEY, JSON.stringify(examples))
}
renderWithTokens function · javascript · L53-L55 (3 LOC)
client/src/pages/StringSheets.jsx
function renderWithTokens(template, tokenValues) {
  return template.replace(/\{(\w+)\}/g, (_, key) => tokenValues[`{${key}}`] ?? `{${key}}`)
}
escapeCsvField function · javascript · L59-L65 (7 LOC)
client/src/pages/StringSheets.jsx
function escapeCsvField(val) {
  const s = String(val ?? '')
  if (s.includes(',') || s.includes('"') || s.includes('\n')) {
    return `"${s.replace(/"/g, '""')}"`
  }
  return s
}
buildCsv function · javascript · L67-L81 (15 LOC)
client/src/pages/StringSheets.jsx
function buildCsv(catalog, overrides) {
  const rows = [CSV_COLUMNS.map(escapeCsvField).join(',')]
  for (const entry of catalog) {
    const value = overrides[`${entry.cat}.${entry.key}`] ?? entry.default
    rows.push([
      entry.cat,
      entry.key,
      entry.default,
      value,
      (entry.tokens || []).join(' '),
      entry.desc || '',
    ].map(escapeCsvField).join(','))
  }
  return rows.join('\n')
}
parseCsv function · javascript · L83-L115 (33 LOC)
client/src/pages/StringSheets.jsx
function parseCsv(text) {
  // Returns array of objects keyed by first-row headers
  const lines = text.trim().split(/\r?\n/)
  if (lines.length < 2) return []

  const parseRow = (line) => {
    const fields = []
    let current = ''
    let inQuotes = false
    for (let i = 0; i < line.length; i++) {
      const ch = line[i]
      if (inQuotes) {
        if (ch === '"' && line[i + 1] === '"') { current += '"'; i++ }
        else if (ch === '"') { inQuotes = false }
        else { current += ch }
      } else {
        if (ch === '"') { inQuotes = true }
        else if (ch === ',') { fields.push(current); current = '' }
        else { current += ch }
      }
    }
    fields.push(current)
    return fields
  }

  const headers = parseRow(lines[0])
  return lines.slice(1).map(line => {
    const fields = parseRow(line)
    const obj = {}
    headers.forEach((h, i) => { obj[h.trim()] = fields[i] ?? '' })
    return obj
  })
}
Repobility (the analyzer behind this table) · https://repobility.com
downloadFile function · javascript · L117-L125 (9 LOC)
client/src/pages/StringSheets.jsx
function downloadFile(content, filename, mime) {
  const blob = new Blob([content], { type: mime })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  a.click()
  URL.revokeObjectURL(url)
}
buildJsFile function · javascript · L127-L147 (21 LOC)
client/src/pages/StringSheets.jsx
function buildJsFile(catalog, overrides, tagOverrides) {
  const lines = [
    '// client/src/strings/gameStrings.js',
    '// Generated by String Sheets — paste this file to update string defaults.',
    '',
    'export const STRING_CATALOG = [',
  ]
  for (const entry of catalog) {
    const value = overrides[`${entry.cat}.${entry.key}`] ?? entry.default
    const tokens = entry.tokens ? `, tokens: ${JSON.stringify(entry.tokens)}` : ''
    const effectiveTags = getEffectiveTags(entry, tagOverrides)
    const tags = effectiveTags.length ? `, tags: ${JSON.stringify(effectiveTags)}` : ''
    lines.push(
      `  { cat: ${JSON.stringify(entry.cat)}, key: ${JSON.stringify(entry.key)}, default: ${JSON.stringify(value)}${tokens}${tags}, desc: ${JSON.stringify(entry.desc || '')} },`
    )
  }
  const catsJs = STRING_CATEGORIES.map(c => `  { id: ${JSON.stringify(c.id)}, label: ${JSON.stringify(c.label)} }`).join(',\n')
  lines.push(']', '', `export const STRING_CATEGORIES = [\n${catsJs},\n]`,
getEffectiveTags function · javascript · L150-L153 (4 LOC)
client/src/pages/StringSheets.jsx
function getEffectiveTags(entry, tagOverrides) {
  const id = `${entry.cat}.${entry.key}`
  return tagOverrides[id] ?? entry.tags ?? []
}
‹ prevpage 2 / 10next ›