aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/app.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-24 14:33:38 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-24 14:33:38 +0200
commit317f09328ed8bf20148b707470c9b0fe82e59575 (patch)
treeba8daf5dcf050d44b7b0e766babbfda8fadac59f /packages/meshbay-hub/src/meshbay_hub/static/app.js
parentb6e2dcea65124673da047f9b3c92bc5e25980d63 (diff)
parent0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a (diff)
downloadmeshbay-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.js70
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>