132 lines
5.6 KiB
JavaScript
132 lines
5.6 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) module.exports = factory;
|
|
else root.createMobileDeviceSetup = factory;
|
|
})(typeof self !== 'undefined' ? self : this, function createMobileDeviceSetup(options) {
|
|
const promptDismissKey = 'stackchain.device-setup-prompt-dismissed-until';
|
|
const promptDismissMs = 7 * 24 * 60 * 60 * 1000;
|
|
const steps = [
|
|
['install', options.installButton, options.installStatus, options.install],
|
|
['offline', options.offlineButton, options.offlineStatus, options.enableOffline],
|
|
['push', options.pushButton, options.pushStatus, options.enablePush],
|
|
['deadline', options.deadlineButton, options.deadlineStatus, options.enableDeadline],
|
|
];
|
|
let trigger = options.launcher;
|
|
|
|
function readinessCounts(readiness) {
|
|
const available = steps.filter(([name]) => readiness[name].state !== 'unavailable').length;
|
|
const complete = steps.filter(([name]) => readiness[name].state === 'complete').length;
|
|
return {available, complete};
|
|
}
|
|
|
|
function renderPrompt(readiness) {
|
|
if (!options.promptCard) return;
|
|
const {available, complete} = readinessCounts(readiness);
|
|
let dismissed = false;
|
|
try {
|
|
dismissed = Number(options.promptStorage?.getItem(promptDismissKey) || 0) > (options.now?.() ?? Date.now());
|
|
} catch (_error) {}
|
|
options.promptSummary.textContent = `${complete} of ${available} steps complete`;
|
|
options.promptCard.hidden = dismissed || available === 0 || complete === available;
|
|
}
|
|
|
|
async function render() {
|
|
const readiness = await options.getReadiness();
|
|
let available = 0;
|
|
let complete = 0;
|
|
steps.forEach(([name, button, status]) => {
|
|
const step = readiness[name];
|
|
status.textContent = step.detail;
|
|
button.hidden = step.state === 'complete' || step.state === 'unavailable';
|
|
button.disabled = step.state === 'pending';
|
|
if (step.state !== 'unavailable') available += 1;
|
|
if (step.state === 'complete') complete += 1;
|
|
});
|
|
options.readyStatus.textContent = complete === available
|
|
? 'This device is ready.'
|
|
: `${complete} of ${available} available steps ready.`;
|
|
renderPrompt(readiness);
|
|
return readiness;
|
|
}
|
|
|
|
async function open(event) {
|
|
trigger = event?.currentTarget || options.launcher;
|
|
await render();
|
|
options.sheet.hidden = false;
|
|
options.closeButton.focus();
|
|
}
|
|
|
|
function close() {
|
|
options.sheet.hidden = true;
|
|
trigger?.focus?.();
|
|
}
|
|
|
|
async function start() {
|
|
options.launcher.addEventListener('click', open);
|
|
options.promptLauncher?.addEventListener('click', open);
|
|
options.promptDismiss?.addEventListener('click', () => {
|
|
try {
|
|
options.promptStorage?.setItem(promptDismissKey, String((options.now?.() ?? Date.now()) + promptDismissMs));
|
|
} catch (_error) {}
|
|
options.promptCard.hidden = true;
|
|
});
|
|
options.closeButton.addEventListener('click', close);
|
|
options.sheet.addEventListener('click', event => {
|
|
if (event.target === options.sheet) close();
|
|
});
|
|
options.escapeTarget.addEventListener('keydown', event => {
|
|
if (event.key === 'Escape' && !options.sheet.hidden) close();
|
|
});
|
|
steps.forEach(([_name, button, _status, action]) => {
|
|
button.addEventListener('click', async () => {
|
|
button.disabled = true;
|
|
try { await action(); }
|
|
finally { await render(); }
|
|
});
|
|
});
|
|
renderPrompt(await options.getReadiness());
|
|
}
|
|
|
|
return {start, open, close, render};
|
|
});
|
|
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports.mount = mountMobileDeviceSetup;
|
|
} else {
|
|
self.createMobileDeviceSetup.mount = mountMobileDeviceSetup;
|
|
}
|
|
|
|
function mountMobileDeviceSetup(options) {
|
|
const qs = selector => options.document.querySelector(selector);
|
|
return createMobileDeviceSetup({
|
|
launcher:qs('#open-device-setup'), closeButton:qs('#close-device-setup'),
|
|
sheet:qs('#device-setup-sheet'), installButton:qs('#device-setup-install'),
|
|
offlineButton:qs('#device-setup-offline'), pushButton:qs('#device-setup-push'),
|
|
deadlineButton:qs('#device-setup-deadline'),
|
|
installStatus:qs('#device-setup-install-status'), offlineStatus:qs('#device-setup-offline-status'),
|
|
pushStatus:qs('#device-setup-push-status'), deadlineStatus:qs('#device-setup-deadline-status'),
|
|
readyStatus:qs('#device-setup-ready-status'),
|
|
promptCard:qs('#device-readiness-card'), promptSummary:qs('#device-readiness-summary'),
|
|
promptLauncher:qs('#finish-device-setup'), promptDismiss:qs('#dismiss-device-readiness'),
|
|
promptStorage:options.promptStorage,
|
|
escapeTarget:options.document,
|
|
getReadiness:() => ({
|
|
install:options.installApp.state(),
|
|
offline:!options.offlineAvailable()
|
|
? {state:'unavailable', detail:'Offline saving is unavailable in this browser.'}
|
|
: options.offlineEnabled()
|
|
? {state:'complete', detail:qs('#offline-work-status').textContent || 'Offline work is saved.'}
|
|
: {state:'incomplete', detail:'Private My Work and Today data are not saved offline.'},
|
|
push:qs('#push-updates').disabled
|
|
? {state:'unavailable', detail:qs('#push-update-status').textContent || 'Update notifications are unavailable.'}
|
|
: qs('#push-updates').checked
|
|
? {state:'complete', detail:'New update notifications are enabled.'}
|
|
: {state:'incomplete', detail:qs('#push-update-status').textContent || 'New update notifications are off.'},
|
|
deadline:options.deadlineReadiness(),
|
|
}),
|
|
install:() => options.installApp.install(),
|
|
enableOffline:options.enableOffline,
|
|
enablePush:options.enablePush,
|
|
enableDeadline:options.enableDeadline,
|
|
});
|
|
}
|