// The Android Sync page: what this phone sends to its groups, on the Android // application only (docs/MESHBAY_DESIGN.md §9.12, §9.13). Loaded on demand, // like the sections it holds: no other client has it. // // Where everything goes is chosen once, at the top, and every section below // sends into its own folder there. import { html, useState, useEffect } from './vendor/htm-preact.js'; import { t } from './i18n.js'; import { ask } from './ask.js'; import * as platform from './platform.js'; import { PhotoSyncSection } from './photo-sync-settings.js'; import { DocSyncSection } from './phone-sync-settings.js'; import { DriveSyncSection } from './drive-sync-settings.js'; import { WhatsAppSyncSection } from './whatsapp-sync-settings.js'; import { runDriveSync, runWhatsAppSync } from './drive-sync.js'; import { runPhotoSync } from './photo-sync.js'; import { runDocSync, KINDS } from './phone-sync.js'; import { destination, privateGroups, probeGroup, photosTabShows, } from './sync-destination.js'; /** The folder each kind this phone has goes into, named as the phone names it. */ function kindDirs(folder, account) { const suffixes = [ ...(platform.photoSync.available ? ['photos'] : []), ...Object.values(KINDS).filter((k) => k.sync.available).map((k) => k.suffix), ...(platform.driveSync.available ? ['drive'] : []), ...(platform.whatsappSync.available ? ['whatsapp'] : []), ]; return suffixes.map((s) => `${folder}/${account}-${s}`); } export function AndroidSyncPage({ user, groups }) { const [dest, setDest] = useState(undefined); // undefined while asked, null when none useEffect(() => { destination.get().then(setDest).catch(() => setDest(null)); }, []); if (dest === undefined) { return html`

${t('androidsync.title')}

`; } const mine = dest && dest.account === user.username ? dest : null; const place = mine ? `${mine.groupId}\n${mine.folder}` : 'none'; const moved = (next) => { setDest(next); // Each kind that is on starts again in the new place, now: forced, as a // run asked for by a change, not left to the 15-minute retry spacing. const now = { force: true }; if (platform.photoSync.available) runPhotoSync(now); for (const kind of Object.keys(KINDS)) if (KINDS[kind].sync.available) runDocSync(kind, now); if (platform.driveSync.available) runDriveSync(now); if (platform.whatsappSync.available) runWhatsAppSync(now); }; return html`

${t('androidsync.title')}

<${DestinationSection} user=${user} groups=${groups || []} dest=${dest} onChange=${moved} /> ${platform.photoSync.available && html` <${PhotoSyncSection} key=${'photos' + place} destination=${mine} />`} ${Object.keys(KINDS).filter((k) => KINDS[k].sync.available).map((kind) => html` <${DocSyncSection} key=${kind + place} kind=${kind} destination=${mine} />`)} ${platform.driveSync.available && html` <${DriveSyncSection} key=${'drive' + place} destination=${mine} />`} ${platform.whatsappSync.available && html` <${WhatsAppSyncSection} key=${'whatsapp' + place} destination=${mine} />`}
`; } function DestinationSection({ user, groups, dest, onChange }) { const [editing, setEditing] = useState(false); const mine = dest && dest.account === user.username ? dest : null; return html`

${t('androidsync.dest_heading')}

${!editing && mine && html`
${t('androidsync.dest_summary', { group: mine.groupName, folder: mine.folder })}

${t('androidsync.dest_dirs', { dirs: kindDirs(mine.folder, user.username).join(', ') })}

`} ${!editing && !mine && html`

${dest ? t('photosync.other_account', { account: dest.account }) : t('androidsync.dest_intro')}

`} ${editing && html` <${DestinationSetup} user=${user} groups=${groups} current=${mine} onDone=${(next) => { setEditing(false); if (next) onChange(next); }} />`}
`; } function DestinationSetup({ user, groups, current, onDone }) { const [candidates, setCandidates] = useState(null); // null while the hub is asked const [groupId, setGroupId] = useState(current ? current.groupId : ''); const [found, setFound] = useState(null); // { folders, photoDirectories } const [probe, setProbe] = useState(''); // '', 'checking', 'offline' const [folder, setFolder] = useState(current ? current.folder : ''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); // Only groups this account owns and is alone in: what is sent there is // readable by every member, and none of it is the group's. useEffect(() => { let gone = false; privateGroups(groups).then((list) => { if (gone) return; setCandidates(list); setGroupId((g) => (list.some((x) => x.id === g) ? g : '')); }); return () => { gone = true; }; }, [groups]); useEffect(() => { setFound(null); if (!groupId) return undefined; let gone = false; setProbe('checking'); probeGroup(groupId).then((r) => { if (gone) return; setProbe(''); setFound(r); setFolder((f) => (r.folders.includes(f) ? f : (r.folders[0] || ''))); }).catch(() => { if (!gone) setProbe('offline'); }); return () => { gone = true; }; }, [groupId]); const photosDir = folder ? `${folder}/${user.username}-photos` : ''; const hiddenFromPhotos = platform.photoSync.available && found && folder && !photosTabShows(photosDir, found.photoDirectories); const save = async () => { const group = (candidates || []).find((g) => g.id === groupId); if (!group || !folder) return; setBusy(true); setError(''); try { // Moving is a fresh start for every kind: said before it happens. if (current && (current.groupId !== groupId || current.folder !== folder)) { const yes = await ask(t('androidsync.dest_change_confirm', { group: group.name, folder })); if (!yes) { setBusy(false); return; } } onDone(await destination.set({ account: user.username, groupId, groupName: group.name, folder, })); } catch (e) { setError(e.message); setBusy(false); } }; return html`
${candidates === null && html`

${t('phonesync.checking_groups')}

`} ${candidates && !candidates.length && html`

${t('phonesync.no_private_group')}

`} ${candidates && candidates.length > 0 && html`
${t('photosync.group')}
`} ${probe === 'checking' && html`

${t('photosync.checking_group')}

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

${t('photosync.group_offline')}

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

${t('phonesync.no_folder')}

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

${t('androidsync.dest_dirs', { dirs: kindDirs(folder, user.username).join(', ') })}

`} ${hiddenFromPhotos && html`

${t('androidsync.dest_not_in_photos', { dir: photosDir })}

`} `}
${error && html`

${error}

`}
`; }