// mod-table.jsx - BranchPicker + ModTable components // Loaded after sortof-shared.jsx; defines globals that sortof-app.jsx references. const { useState, useMemo } = React; function BranchPicker({ wsid, branches, selected, userTouched, onToggle }) { const radio = isRadioMode(branches); const inputType = radio ? 'radio' : 'checkbox'; const activeSet = new Set(D.SORTED_ORDER || []); const effective = userTouched ? selected : defaultSelectionForBranches(branches, activeSet); return (
workshop {wsid} · {branches.length} mod_ids · {radio ? 'pick one' : 'multi-select'}
{branches.map(b => { const checked = effective.includes(b.modId); return ( ); })}
); } function ModTable({ defaultOpen = false, branchSelections, onToggleBranch, expandedWsids, onToggleExpansion }) { const [open, setOpen] = useState(defaultOpen); const groupedByWsid = useMemo(() => { const g = {}; for (const m of (D.MOD_DB || [])) { const w = m.wsid || ''; if (!w) continue; (g[w] = g[w] || []).push(m); } return g; }, [D.MOD_DB]); const rowSpecs = useMemo(() => { const specs = []; const seenWsid = new Set(); for (const modId of (D.SORTED_ORDER || [])) { const m = (D.MOD_DB || []).find(x => x.modId === modId); if (!m || !m.wsid) continue; if (seenWsid.has(m.wsid)) continue; seenWsid.add(m.wsid); const branches = groupedByWsid[m.wsid] || [m]; const isMulti = branches.length >= 2; specs.push({ wsid: m.wsid, primary: m, branches, isMulti }); } for (const w of Object.keys(groupedByWsid)) { if (!seenWsid.has(w)) { const branches = groupedByWsid[w]; specs.push({ wsid: w, primary: branches[0], branches, isMulti: branches.length >= 2 }); } } return specs; }, [D.SORTED_ORDER, D.MOD_DB, groupedByWsid]); return (
setOpen(!open)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen(!open); } }}> mod details · {(D.SORTED_ORDER || []).length} mods why everything ended up where it did {open ? '▾' : '▸'}
{open && ( {rowSpecs.map((spec, i) => { const idx = String(i + 1).padStart(2, '0'); if (!spec.isMulti) { const m = spec.primary; const rowState = rowStateForMod(m.modId); const stateGlyph = { resolved: '✓', warning: '⚠', error: '✗' }[rowState] || '·'; return ( ); } const selected = branchSelections[spec.wsid] || []; const N = spec.branches.length; const X = selected.length; const userTouched = (spec.wsid in branchSelections); const affordance = userTouched ? `✓ ${X} of ${N}` : `▾ ${N} branches`; const firstSelected = spec.branches.find(b => selected.includes(b.modId)) || spec.branches[0]; const showAsZero = userTouched && X === 0; const display = showAsZero ? null : firstSelected; const expanded = expandedWsids.has(spec.wsid); const rowState = display ? rowStateForMod(display.modId) : 'resolved'; const stateGlyph = { resolved: '✓', warning: '⚠', error: '✗' }[rowState] || '·'; return ( {expanded && ( )} ); })}
# mod id workshop id category dependencies load
{idx} {m.modId} {spec.wsid} {m.cat} {m.deps && m.deps.length ? m.deps.join(', ') : '-'} {m.pos === 'first' && first} {m.pos === 'last' && last} {!m.pos && -}
{idx} {spec.wsid} {display ? {display.cat} : '-'} {display && display.deps && display.deps.length ? display.deps.join(', ') : '-'} {display && display.pos === 'first' && first} {display && display.pos === 'last' && last} {(!display || !display.pos) && -}
)}
); } Object.assign(window, { ModTable, BranchPicker, rowStateForMod });