// The contacts, calendar and messages sections 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 copies go is the page's destination section; these // only turn each backup on or off. import { html, useState, useEffect, useCallback } from './vendor/htm-preact.js'; import { t } from './i18n.js'; import { KINDS, subscribe, runDocSync, failureText } from './phone-sync.js'; // What each kind says in its own words; everything else is shared. const TEXT = { contacts: { intro: 'contactsync.intro', permission: 'contactsync.permission_needed', }, calendar: { intro: 'calendarsync.intro', permission: 'calendarsync.permission_needed', }, messages: { intro: 'messagesync.intro', permission: 'messagesync.permission_needed', // Android keeps text messages behind "restricted settings" for an // application installed outside a store: said, with the way to lift it. restricted: 'messagesync.restricted_hint', }, }; function stateLine(st, status) { switch (st.phase) { case 'connecting': return t('photosync.state_connecting'); case 'sending': return t('phonesync.state_sending'); 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 DocSyncSection({ kind, destination }) { const { sync, heading } = KINDS[kind]; 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(kind, (st) => { setRun(st); if (st.phase !== 'sending' && st.phase !== 'connecting') refresh(); }), [kind, refresh]); if (!status) return null; const running = run.phase === 'sending' || run.phase === 'connecting'; const turnOn = async () => { setBusy(true); setError(''); try { let st = status; if (st.permission !== 'granted') st = await sync.permit(); if (st.permission !== 'granted') { setStatus(st); return; } setStatus(await sync.configure({})); runDocSync(kind, { force: true }); } catch (e) { setError(e.message); } finally { setBusy(false); } }; const turnOff = async () => { setError(''); try { setStatus(await sync.configure(null)); } catch (e) { setError(e.message); } }; const backUpNow = async () => { setError(''); try { await runDocSync(kind, { force: true }); } catch (e) { setError(e.message); } }; return html`

${t(heading)}

${!status.on && html`

${t(TEXT[kind].intro)}

${!destination && html`

${t('androidsync.dest_needed')}

`} ${destination && status.permission !== 'granted' && html`

${t(TEXT[kind].permission)}

${TEXT[kind].restricted && html`

${t(TEXT[kind].restricted)}

`}`} ${destination && html` `} `} ${status.on && html` ${destination && html`
${t('phonesync.summary', { group: destination.groupName, dir: status.dir })}
`} ${!destination && html`

${t('androidsync.dest_needed')}

`}

${t('phonesync.copies_sent', { n: status.sent })} ${' · '} ${status.lastCompleted ? t('photosync.last_run', { when: new Date(status.lastCompleted).toLocaleString() }) : t('photosync.never')}

${status.lastSent && html`

${t('phonesync.last_file', { name: status.lastSent })}

`} ${status.failure ? html`

${failureText(status.failure)}

` : stateLine(run, status) && html`

${stateLine(run, status)}

`}
`} ${error && html`

${error}

`}
`; }