From 2f2a9a6542d2194e50ffba6f382e4fdffd481838 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 10 Oct 2026 14:21:46 +0200 Subject: feat(android): one backup destination, a group the account owns alone Chosen once at the top of Android Sync for every kind; photos and contacts go into /-photos and -contacts. Owner and sole member are checked at set-up and before every run. Co-Authored-By: Claude Opus 5.5 --- .../src/meshbay_hub/static/photo-sync-settings.js | 97 ++++++---------------- 1 file changed, 27 insertions(+), 70 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/photo-sync-settings.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/photo-sync-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/photo-sync-settings.js index b5e9a30..7809efa 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/photo-sync-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/photo-sync-settings.js @@ -1,14 +1,14 @@ // The "Photo backup" section of the Android Sync page, on the Android // application only (docs/MESHBAY_DESIGN.md §9.12). Loaded on demand: no other -// client has it. +// client has it. Where the photos go is the page's destination section; +// this one chooses which. import { html, useState, useEffect, useCallback } from './vendor/htm-preact.js'; import { t } from './i18n.js'; import * as platform from './platform.js'; -import { hubFetch } from './hub-client.js'; import { formatSize } from './file-utils.js'; import { ask } from './ask.js'; -import { subscribe, runPhotoSync, probeGroup } from './photo-sync.js'; +import { subscribe, runPhotoSync } from './photo-sync.js'; function stateLine(st, status) { switch (st.phase) { @@ -30,10 +30,11 @@ const FAILURES = { no_such_directory: 'photosync.fail_no_folder', root_unavailable: 'photosync.fail_unavailable', not_a_member: 'photosync.fail_not_member', + not_private: 'photosync.fail_not_private', permission: 'photosync.fail_permission', }; -export function PhotoSyncSection({ user, groups }) { +export function PhotoSyncSection({ destination }) { const [status, setStatus] = useState(null); const [run, setRun] = useState({ phase: 'idle' }); const [editing, setEditing] = useState(false); @@ -51,6 +52,16 @@ export function PhotoSyncSection({ user, groups }) { if (!status) return null; const cfg = status.config; + if (!destination) { + return html` +
+

${t('photosync.heading')}

+

${t('photosync.intro')}

+

${t('androidsync.dest_needed')}

+
+ `; + } + const backUpNow = async () => { setError(''); try { @@ -82,11 +93,9 @@ export function PhotoSyncSection({ user, groups }) { ${cfg && !editing && html`
- ${t('photosync.summary', { group: cfg.groupName, owner: cfg.owner, folder: cfg.folder })} + ${t('phonesync.summary', { group: destination.groupName, dir: status.dir })}
- ${cfg.account !== user.username && html` -

${t('photosync.other_account', { account: cfg.account })}

`}

${t('photosync.sent_total', { n: status.sent })} ${' · '} @@ -107,7 +116,7 @@ export function PhotoSyncSection({ user, groups }) { `} ${editing && html` - <${Setup} user=${user} groups=${groups} status=${status} + <${Setup} status=${status} onPermission=${setStatus} onDone=${(next) => { setEditing(false); if (next) { setStatus(next); runPhotoSync(); } }} />`} ${error && html`

${error}

`} @@ -115,12 +124,8 @@ export function PhotoSyncSection({ user, groups }) { `; } -function Setup({ user, groups, status, onPermission, onDone }) { - const cfg = status.config && status.config.account === user.username ? status.config : null; - const [groupId, setGroupId] = useState(cfg ? cfg.groupId : ''); - const [folders, setFolders] = useState(null); // null while unknown - const [probe, setProbe] = useState(''); // '', 'checking', 'offline' - const [folder, setFolder] = useState(cfg ? cfg.folder : ''); +function Setup({ status, onPermission, onDone }) { + const cfg = status.config; const [albums, setAlbums] = useState(null); const [chosen, setChosen] = useState(() => new Set(cfg ? cfg.albums : [])); const [existing, setExisting] = useState(cfg ? cfg.includeExisting : true); @@ -138,20 +143,6 @@ function Setup({ user, groups, status, onPermission, onDone }) { }).catch((e) => setError(e.message)); }, [permitted]); - useEffect(() => { - setFolders(null); - if (!groupId) return; - let gone = false; - setProbe('checking'); - probeGroup(groupId).then((r) => { - if (gone) return; - setProbe(''); - setFolders(r.folders); - setFolder((f) => (r.folders.includes(f) ? f : (r.folders[0] || ''))); - }).catch(() => { if (!gone) setProbe('offline'); }); - return () => { gone = true; }; - }, [groupId]); - const allow = async () => { setError(''); try { onPermission(await platform.photoSync.permit()); } catch (e) { setError(e.message); } @@ -164,30 +155,16 @@ function Setup({ user, groups, status, onPermission, onDone }) { }); const start = async () => { - const group = groups.find((g) => g.id === groupId); - if (!group || !folder || !chosen.size) return; + if (!chosen.size) return; setBusy(true); setError(''); try { - const settings = { - account: user.username, groupId, groupName: group.name, - owner: group.owner_username || '', folder, albums: [...chosen], includeExisting: existing, - }; - // Asked when the photos would go somewhere new, or from somewhere new in - // time — never for a change of albums alone, which the person just made. - const same = cfg && cfg.groupId === groupId && cfg.folder === folder - && cfg.includeExisting === existing; - if (!same) { - const [est, members] = await Promise.all([ - platform.photoSync.estimate(settings), - hubFetch(`/v1/groups/${groupId}/members`, { token: user.token }) - .then((r) => (r.members || []).length).catch(() => null), - ]); - const params = { - group: group.name, owner: group.owner_username || '', folder, - members: members == null ? '?' : members, - count: est.count, size: formatSize(est.bytes), - }; + const settings = { albums: [...chosen], includeExisting: existing }; + // Asked when the photos would come from somewhere new in time; never + // for a change of albums alone, which the person just made. + if (!cfg || cfg.includeExisting !== existing) { + const est = await platform.photoSync.estimate(settings); + const params = { count: est.count, size: formatSize(est.bytes), dir: status.dir }; const yes = await ask(t(existing && est.count > 0 ? 'photosync.confirm' : 'photosync.confirm_new', params)); if (!yes) { setBusy(false); return; } @@ -210,26 +187,6 @@ function Setup({ user, groups, status, onPermission, onDone }) { `} ${permitted && html` -
- ${t('photosync.group')} - -
- ${probe === 'checking' && html`

${t('photosync.checking_group')}

`} - ${probe === 'offline' && html`

${t('photosync.group_offline')}

`} - ${folders && !folders.length && html`

${t('photosync.no_folder')}

`} - ${folders && folders.length > 0 && html` -
- ${t('photosync.folder')} - -
- `}

${t('photosync.albums')}

${(albums || []).map((a) => html`
+ disabled=${busy || !chosen.size}>${t('photosync.start')}
`} -- cgit v1.2.3