diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-24 14:33:38 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-24 14:33:38 +0200 |
| commit | 317f09328ed8bf20148b707470c9b0fe82e59575 (patch) | |
| tree | ba8daf5dcf050d44b7b0e766babbfda8fadac59f /packages/meshbay-hub/src/meshbay_hub/static/app.js | |
| parent | b6e2dcea65124673da047f9b3c92bc5e25980d63 (diff) | |
| parent | 0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a (diff) | |
| download | meshbay-317f09328ed8bf20148b707470c9b0fe82e59575.tar.gz | |
Merge branch 'docs/mediacenter-videos-app': Videos group app
Poster grid / flat list browsing, TMDB metadata enrichment, thumbnail
generation and caching, season-specific overviews, manual match
correction, and the create-group wizard's app-selection step.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmyXtc6dAADsH23ydXQpY
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 70 |
1 files changed, 58 insertions, 12 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 044457b..f9bcd43 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -16,6 +16,7 @@ import { refreshAccessToken, } from './hub-client.js'; import { GroupPage } from './group-page.js'; +import { APPS } from './apps.js'; // ── Constants ──────────────────────────────────────────────────────────────── @@ -838,6 +839,15 @@ function CreateGroupWizard({ token, username, onCreated }) { const [joinPolicy, setJoinPolicy] = useState('invite'); const [roots, setRoots] = useState([]); const [uploadIdx, setUploadIdx] = useState(0); + // Every registered app, on by default — narrowing this down here means + // members never briefly see one the operator meant to leave off, the way + // toggling it afterward from Settings would. + const [enabledApps, setEnabledApps] = useState(() => APPS.map(a => a.key)); + const toggleWizardApp = useCallback((key) => { + setEnabledApps(prev => prev.includes(key) + ? prev.filter(k => k !== key) + : [...prev, key]); + }, []); // Step 2 progress const [setupSteps, setSetupSteps] = useState([]); @@ -914,8 +924,13 @@ function CreateGroupWizard({ token, username, onCreated }) { const steps = [ { label: t('wizard.step_create_hub'), status: 'pending' }, { label: t('wizard.step_attach'), status: 'pending' }, - { label: t('wizard.step_index'), status: 'pending' }, ]; + // Only a step at all when it does something — the common case (every + // app left on, the default) has nothing to set and no reason to show a + // step for it. + if (enabledApps.length < APPS.length) + steps.push({ label: t('wizard.step_apps'), status: 'pending' }); + steps.push({ label: t('wizard.step_index'), status: 'pending' }); if (roots.length > 1) steps.push({ label: t('wizard.step_add_roots'), status: 'pending' }); steps.push({ label: t('wizard.step_gek'), status: 'pending' }); @@ -979,11 +994,24 @@ function CreateGroupWizard({ token, username, onCreated }) { update('done'); advance(); - // 3. Wait for the node's own initial scan of this group to finish — + // 3. Narrow the enabled apps down, if the operator unchecked any — + // before the scan below, so a member who joins while it is still + // running never briefly sees an app meant to be off. Same + // not-hosted-yet race as steps 4+ below: the group is attached, but + // may not have reached groups_ctx yet. + if (enabledApps.length < APPS.length) { + update('running'); + await withRetry(() => platform.node.call( + 'PUT', `/api/groups/${gid}/apps`, { apps: enabledApps })); + update('done'); + advance(); + } + + // 4. Wait for the node's own initial scan of this group to finish — // the group is not usable for anything below (extra roots, GEK) until // this finishes, so nobody lands on a page that looks broken, or hits // a "not configured" error from racing ahead of it. Can take tens of - // minutes on a slow disk (see the StarWars benchmark) — the node + // minutes on a slow disk with a large library — the node // keeps scanning on its own either way (test_hot_reload_survives_ // client_close.py); this step is only about not lying about it. update('running'); @@ -991,7 +1019,7 @@ function CreateGroupWizard({ token, username, onCreated }) { update('done'); advance(); - // 4. Add extra roots (if >1) + // 5. Add extra roots (if >1) if (roots.length > 1) { update('running'); for (let i = 0; i < roots.length; i++) { @@ -1006,13 +1034,13 @@ function CreateGroupWizard({ token, username, onCreated }) { advance(); } - // 5. GEK init + // 6. GEK init update('running'); await withRetry(() => platform.node.call('POST', `/api/groups/${gid}/gek`)); update('done'); advance(); - // 6. Generate pairing code + // 7. Generate pairing code update('running'); const pairResult = await platform.node.call('POST', '/api/operator/pair'); if (pairResult && pairResult.code) { @@ -1030,7 +1058,7 @@ function CreateGroupWizard({ token, username, onCreated }) { update('error'); setSetupError(platform.bridgeMessage(err)); } - }, [name, description, joinPolicy, roots, uploadIdx, token, onCreated]); + }, [name, description, joinPolicy, roots, uploadIdx, enabledApps, token, onCreated]); // Step 0: Node detection if (step === 0) { @@ -1064,7 +1092,7 @@ function CreateGroupWizard({ token, username, onCreated }) { // Step 1: Group details + directories if (step === 1) { - const canProceed = name.trim() && roots.length > 0; + const canProceed = name.trim() && roots.length > 0 && enabledApps.length > 0; return html`<div class="page-content"> <h2>${t('wizard.title')}</h2> ${error && html`<div class="error-msg" style="margin-bottom:16px">${error}</div>`} @@ -1111,6 +1139,25 @@ function CreateGroupWizard({ token, username, onCreated }) { </div> <div class="settings-section"> + <h3 class="settings-heading">${t('members.apps_title')}</h3> + <p style="font-size:0.85em;color:var(--text-dim);margin-bottom:8px"> + ${t('members.apps_hint')}</p> + <ul class="apps-toggle-list"> + ${APPS.map(a => html` + <li key=${a.key} class="settings-row"> + <label class="settings-label"> + <input type="checkbox" checked=${enabledApps.includes(a.key)} + onChange=${() => toggleWizardApp(a.key)} /> + ${' '}${t(a.labelKey)} + </label> + </li> + `)} + </ul> + ${enabledApps.length === 0 && html` + <p class="error-msg">${t('members.apps_need_one')}</p>`} + </div> + + <div class="settings-section"> <h3 class="settings-heading">${t('wizard.directories')}</h3> <p style="font-size:0.85em;color:var(--text-dim);margin-bottom:8px"> ${t('wizard.directories_hint')}</p> @@ -1160,8 +1207,8 @@ function CreateGroupWizard({ token, username, onCreated }) { ${setupSteps.map((s, i) => html` <div class="wizard-step wizard-step-${s.status}" key=${i}> <span class="wizard-step-icon"> - ${s.status === 'done' ? '✓' : - s.status === 'running' ? '●' : + ${s.status === 'running' ? html`<span class="spinner"></span>` : + s.status === 'done' ? '✓' : s.status === 'error' ? '✗' : '○'} </span> <span>${s.label}</span> @@ -1731,8 +1778,7 @@ function SettingsPage({ user, theme, onThemeChange, groups, onPrefsChange }) { <span class="settings-label">${t('settings.default_tab')}</span> <select class="settings-select" value=${defaultTab} onChange=${changeDefaultTab}> - <option value="chat">${t('group.tab_chat')}</option> - <option value="files">${t('group.tab_files')}</option> + ${APPS.map((a) => html`<option key=${a.key} value=${a.key}>${t(a.labelKey)}</option>`)} <option value="settings">${t('group.tab_settings')}</option> </select> </div> |