feat: show timed Today breaks on lock screen (Closes #1050)
This commit is contained in:
1 parent
af86034339
commit
ed15392097
4 files changed
+112
-12
No files matched your search
@@ -421,7 +421,7 @@ self.addEventListener('sync', event => {
|
||||
if (event.tag === 'stackchain-issue-outbox-v1') event.waitUntil(flushAndNotify());
|
||||
});
|
||||
|
||||
async function updateTodayLockScreen(active, running, rawActionToken = '') {
|
||||
async function updateTodayLockScreen(active, running, rawActionToken = '', rawBreakDeadline = 0) {
|
||||
const tag = 'stackchain-today-session';
|
||||
if (!active) {
|
||||
const notifications = await self.registration.getNotifications({ tag });
|
||||
@@ -429,12 +429,24 @@ async function updateTodayLockScreen(active, running, rawActionToken = '') {
|
||||
return;
|
||||
}
|
||||
const actionToken = /^[A-Za-z0-9_-]{16,128}$/.test(rawActionToken) ? rawActionToken : '';
|
||||
await self.registration.showNotification(running ? 'Today session running' : 'Today session paused', {
|
||||
body: running ? 'Your active Today timer is running.' : 'Your active Today timer is paused.',
|
||||
const now = Date.now();
|
||||
const breakDeadline = Number(rawBreakDeadline);
|
||||
const onBreak = !running && actionToken && Number.isSafeInteger(breakDeadline) &&
|
||||
breakDeadline > now && breakDeadline <= now + 120 * 60 * 1000;
|
||||
const title = onBreak ? 'On a Today break' :
|
||||
running ? 'Today session running' : 'Today session paused';
|
||||
const body = onBreak ? 'Return at ' + new Date(breakDeadline).toLocaleTimeString([], {
|
||||
hour:'numeric', minute:'2-digit',
|
||||
}) : running ? 'Your active Today timer is running.' : 'Your active Today timer is paused.';
|
||||
await self.registration.showNotification(title, {
|
||||
body,
|
||||
tag,
|
||||
renotify:false,
|
||||
silent:true,
|
||||
actions: [
|
||||
actions: onBreak ? [
|
||||
{ action:'resume-today', title:'Resume now' },
|
||||
{ action:'open-today', title:'Open Today' },
|
||||
] : [
|
||||
{ action:running ? 'pause-today' : 'resume-today', title:running ? 'Pause' : 'Resume' },
|
||||
...(actionToken ? [{ action:'finish-today', title:'Finish current' }] : []),
|
||||
],
|
||||
@@ -464,7 +476,8 @@ self.addEventListener('message', event => {
|
||||
event.waitUntil(updateTodayLockScreen(
|
||||
event.data.active === true,
|
||||
event.data.running === true,
|
||||
String(event.data.actionToken || '')
|
||||
String(event.data.actionToken || ''),
|
||||
Number(event.data.breakDeadlineAt || 0)
|
||||
));
|
||||
}
|
||||
});
|
||||
@@ -561,19 +574,20 @@ async function openCanonicalIssueUrl(rawUrl) {
|
||||
|
||||
async function applyTodayTimerAction(action, actionToken = '') {
|
||||
if (!['pause', 'resume', 'complete'].includes(action)) return;
|
||||
if (action === 'complete' && !/^[A-Za-z0-9_-]{16,128}$/.test(actionToken)) return;
|
||||
if (actionToken && !/^[A-Za-z0-9_-]{16,128}$/.test(actionToken)) return;
|
||||
if (action === 'complete' && !actionToken) return;
|
||||
const route = '#/my-work/today';
|
||||
const windows = await self.clients.matchAll({ type:'window', includeUncontrolled:true });
|
||||
const client = windows.find(candidate => candidate.url.startsWith(self.location.origin + BASE));
|
||||
if (client) {
|
||||
client.postMessage?.({
|
||||
type:'stackchain-today-timer-action', action,
|
||||
...(action === 'complete' ? { actionToken } : {}),
|
||||
...(actionToken ? { actionToken } : {}),
|
||||
});
|
||||
return client.focus?.();
|
||||
}
|
||||
const query = '?today_timer_action=' + action +
|
||||
(action === 'complete' ? '&today_action_token=' + encodeURIComponent(actionToken) : '');
|
||||
(actionToken ? '&today_action_token=' + encodeURIComponent(actionToken) : '');
|
||||
const target = new URL(BASE + query + route, self.location.origin).href;
|
||||
return self.clients.openWindow(target);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ function createTodayLockScreen({
|
||||
onAction = () => {},
|
||||
}) {
|
||||
let activeIdentity = '';
|
||||
let activeActionIdentity = '';
|
||||
let activeBreakDeadline = 0;
|
||||
const preferenceKey = () => {
|
||||
const login = String(getLogin?.() || '').trim().toLowerCase();
|
||||
return login ? 'stackchain.today-lock-screen.v1.' + encodeURIComponent(login) : '';
|
||||
@@ -82,13 +84,14 @@ function createTodayLockScreen({
|
||||
};
|
||||
const consumeAction = async (action, token = '') => {
|
||||
if (!enabled() || !['pause', 'resume', 'complete'].includes(action)) return false;
|
||||
if (action !== 'complete') {
|
||||
const requiresToken = action === 'complete' || (action === 'resume' && activeBreakDeadline > 0);
|
||||
if (!requiresToken) {
|
||||
onAction(action, null);
|
||||
return true;
|
||||
}
|
||||
const pending = readAction();
|
||||
if (!pending || !token || !activeIdentity || pending.token !== token ||
|
||||
await fingerprint(token, activeIdentity) !== pending.fingerprint) return false;
|
||||
await fingerprint(token, activeActionIdentity) !== pending.fingerprint) return false;
|
||||
if (!clearAction()) return false;
|
||||
onAction(action, activeIdentity);
|
||||
return true;
|
||||
@@ -128,12 +131,17 @@ function createTodayLockScreen({
|
||||
if (!enabled() || NotificationRef?.permission !== 'granted') return false;
|
||||
const visible = Boolean(active && snapshot?.identity);
|
||||
activeIdentity = visible ? snapshot.identity : '';
|
||||
const pending = visible ? await actionFor(activeIdentity) : null;
|
||||
const rawBreakDeadline = Number(snapshot?.break_deadline_at);
|
||||
activeBreakDeadline = visible && Number.isSafeInteger(rawBreakDeadline) && rawBreakDeadline > 0 ?
|
||||
rawBreakDeadline : 0;
|
||||
activeActionIdentity = activeIdentity + (activeBreakDeadline ? '\0break:' + activeBreakDeadline : '');
|
||||
const pending = visible ? await actionFor(activeActionIdentity) : null;
|
||||
if (!visible) clearAction();
|
||||
await post({
|
||||
type:'stackchain-today-lock-screen',
|
||||
active:visible,
|
||||
running:visible && Boolean(snapshot.running),
|
||||
...(activeBreakDeadline ? { breakDeadlineAt:activeBreakDeadline } : {}),
|
||||
...(pending?.token ? { actionToken:pending.token } : {}),
|
||||
});
|
||||
if (visible && !pending) setStatus('Finish current is unavailable because its one-time action could not be saved.');
|
||||
|
||||
Reference in new issue
Block a user