// The files section of the Android Sync page, on the Android application only // (docs/MESHBAY_DESIGN.md §9.13). Loaded on demand: no other client has it. // Where the files go is the page's destination section; this one chooses the // folders, through the system's own picker. import { html, useState, useEffect, useCallback } from './vendor/htm-preact.js'; import { t } from './i18n.js'; import { ask } from './ask.js'; import * as platform from './platform.js'; import { subscribe, runDriveSync } from './drive-sync.js'; import { failureText } from './phone-sync.js'; // What the phone says when it refuses a folder, in the person's words. const REFUSED = { media_folder: 'drivesync.refused_media', whole_storage: 'drivesync.refused_root', }; function refusal(message) { const code = /Refused: (\w+)/.exec(message || ''); return code && REFUSED[code[1]] ? t(REFUSED[code[1]]) : message; } function stateLine(st, status) { switch (st.phase) { case 'waiting_wifi': return t('photosync.state_waiting_wifi'); case 'connecting': return t('photosync.state_connecting'); case 'sending': return t('photosync.state_sending', { done: st.done, total: st.total }); case 'offline': return t('photosync.state_offline'); case 'interrupted': return t('photosync.state_interrupted'); case 'up_to_date': return t('photosync.state_up_to_date'); default: return status && status.lastCompleted ? t('photosync.state_up_to_date') : ''; } } export function DriveSyncSection({ destination }) { const sync = platform.driveSync; const [status, setStatus] = useState(null); const [run, setRun] = useState({ phase: 'idle' }); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const refresh = useCallback(() => sync.status().then(setStatus).catch(() => {}), [sync]); useEffect(() => { refresh(); }, [refresh]); useEffect(() => subscribe((st) => { setRun(st); if (st.phase !== 'sending' && st.phase !== 'connecting') refresh(); }), [refresh]); if (!status) return null; const folders = status.folders || []; const running = run.phase === 'sending' || run.phase === 'connecting'; const act = async (fn) => { setBusy(true); setError(''); try { const next = await fn(); if (next) setStatus(next); } catch (e) { setError(refusal(e.message)); } finally { setBusy(false); } }; const backUpNow = () => act(async () => { if (!status.unmetered && !await ask(t('drivesync.metered_confirm'))) return null; await runDriveSync({ force: true, metered: !status.unmetered }); return null; }); return html`
${t('drivesync.intro')}
`} ${!destination && html`${t('androidsync.dest_needed')}
`} ${destination && html`${t('drivesync.folders')}
${!folders.length && html`${t('drivesync.no_folders')}
`} ${folders.map((f) => html`${t('drivesync.folders_hint')}
`} ${destination && !status.on && html`${t('drivesync.sent_total', { n: status.sent })} ${' · '} ${status.lastCompleted ? t('photosync.last_run', { when: new Date(status.lastCompleted).toLocaleString() }) : t('photosync.never')}
${status.failure ? html`${failureText(status.failure)}
` : stateLine(run, status) && html`${stateLine(run, status)}
`}${error}
`}