diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js new file mode 100644 index 0000000..2c1ebc0 --- /dev/null +++ b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js @@ -0,0 +1,120 @@ +// 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', + }, +}; + +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); + } 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` + <div class="settings-section"> + <h3 class="settings-heading">${t(heading)}</h3> + ${!status.on && html` + <p class="settings-hint">${t(TEXT[kind].intro)}</p> + ${!destination && html`<p class="settings-hint">${t('androidsync.dest_needed')}</p>`} + ${destination && status.permission !== 'granted' && html` + <p class="settings-hint">${t(TEXT[kind].permission)}</p>`} + ${destination && html` + <button class="admin-btn" disabled=${busy} onClick=${turnOn}>${t('phonesync.turn_on')}</button>`} + `} + ${status.on && html` + ${destination && html` + <div class="settings-row"> + <span class="settings-label settings-name"> + ${t('phonesync.summary', { group: destination.groupName, dir: status.dir })} + </span> + </div>`} + ${!destination && html`<p class="settings-hint">${t('androidsync.dest_needed')}</p>`} + <p class="settings-hint"> + ${t('phonesync.copies_sent', { n: status.sent })} + ${' · '} + ${status.lastCompleted + ? t('photosync.last_run', { when: new Date(status.lastCompleted).toLocaleString() }) + : t('photosync.never')} + </p> + ${status.lastSent && html` + <p class="settings-hint settings-name">${t('phonesync.last_file', { name: status.lastSent })}</p>`} + ${status.failure + ? html`<p class="error-msg">${failureText(status.failure)}</p>` + : stateLine(run, status) && html`<p class="settings-hint">${stateLine(run, status)}</p>`} + <div class="settings-row" style="gap:8px; justify-content:flex-start; flex-wrap:wrap"> + <button class="admin-btn" disabled=${running || !destination} + onClick=${backUpNow}>${t('photosync.now')}</button> + <button class="btn-secondary" onClick=${turnOff}>${t('photosync.turn_off')}</button> + </div> + `} + ${error && html`<p class="error-msg">${error}</p>`} + </div> + `; +} |