stackchain-dashboard/frontend/mobile-task-dock.js
timmy d34c082da2
All checks were successful
CI / lint (pull_request) Successful in 1m37s
CI / build-release (pull_request) Successful in 7s
CI / release-candidate (pull_request) Has been skipped
fix: make mobile queue summary truthful (Closes #743)
2026-08-13 13:53:04 +00:00

134 lines
5.0 KiB
JavaScript

(function (root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory;
else root.createMobileTaskDock = factory;
})(typeof self !== 'undefined' ? self : this, function createMobileTaskDock(options) {
const nav = options.nav;
const buttons = options.buttons;
const overlays = options.overlays || [];
let launcher = null;
let wasHidden = false;
function select(name) {
if (!buttons[name]) return false;
Object.entries(buttons).forEach(([key, button]) => {
if (key === name) button.setAttribute('aria-current', 'page');
else button.removeAttribute('aria-current');
});
return true;
}
function refreshVisibility() {
const hidden = overlays.some(overlay => overlay.open || overlay.classList.contains('open'));
nav.hidden = hidden;
if (options.sessionHud) {
if (hidden) options.sessionHud.setAttribute('data-overlay-hidden', 'true');
else options.sessionHud.removeAttribute('data-overlay-hidden');
}
if (wasHidden && !hidden && launcher) launcher.focus();
wasHidden = hidden;
}
function openQueues() {
if (!options.queueSheet || options.queueSheet.open) return;
options.queueSheet.showModal();
}
function closeQueues() {
options.queueSheet?.open && options.queueSheet.close();
}
function start() {
Object.entries(buttons).forEach(([name, button]) => {
button.addEventListener('click', event => {
launcher = event.currentTarget;
select(name);
name === 'queues' && options.queueSheet ? openQueues() : options.actions[name]();
});
});
if (options.queueSheet) {
options.queueClose?.addEventListener('click', closeQueues);
options.queueSheet.addEventListener('cancel', event => {
event.preventDefault();
closeQueues();
});
options.queueSheet.addEventListener('close', () => buttons.queues?.focus());
Object.entries(options.queueRows || {}).forEach(([name, row]) => {
row.addEventListener('click', () => {
closeQueues();
options.onSelectQueue?.(name, row);
});
});
}
options.observe(refreshVisibility, overlays);
refreshVisibility();
}
function updateWork(mode) {
const labels = {
continue: ['Continue', 'Continue Today'],
resume: ['Resume', 'Resume Today'],
start: ['Start', 'Start Today'],
plan: ['Plan', 'Plan Today'],
find: ['Find', 'Find work'],
work: ['Work', 'Work'],
};
const workMode = labels[mode] ? mode : 'work';
if (options.workLabel) options.workLabel.textContent = labels[workMode][0];
if (buttons.work) {
buttons.work.setAttribute('aria-label', labels[workMode][1]);
}
}
function updateAttention(count) {
const total = Math.max(0, Number(count) || 0);
const visible = total > 0;
if (options.attentionBadge) {
options.attentionBadge.textContent = String(total);
options.attentionBadge.hidden = !visible;
}
if (buttons.attention) {
buttons.attention.hidden = !visible;
buttons.attention.setAttribute('aria-label', 'Attention, ' + total + ' items');
}
if (visible) nav.setAttribute('data-attention', 'true');
else nav.removeAttribute('data-attention');
}
function updateQueues(counts) {
const names = 'today agenda attention update later draft'.split(' ');
const normalized = Object.fromEntries(names.map(name => [name, Math.max(0, Number(counts?.[name]) || 0)]));
const actionableNames = ['today', 'attention', 'update', 'later', 'draft'];
const active = actionableNames.reduce((total, name) => total + (normalized[name] > 0 ? 1 : 0), 0);
Object.entries(options.queueCounts || {}).forEach(([name, element]) => {
element.textContent = String(normalized[name] || 0);
});
options.queueRows?.update?.setAttribute(
'aria-label', 'Updates, ' + normalized.update + ' unread conversations'
);
if (options.queueBadge) {
options.queueBadge.textContent = active + ' active';
options.queueBadge.hidden = active === 0;
}
if (options.deadlineBadge) {
options.deadlineBadge.textContent = normalized.agenda + ' due';
options.deadlineBadge.hidden = normalized.agenda === 0;
}
if (options.queueRows?.agenda) {
if (normalized.agenda > 0) options.queueRows.agenda.setAttribute('data-deadlines', 'true');
else options.queueRows.agenda.removeAttribute('data-deadlines');
}
const label = active === 0 && normalized.agenda === 0
? 'Queues: no active queues; no upcoming deadlines'
: 'Queues: Today ' + normalized.today
+ ', Agenda ' + normalized.agenda + ' due'
+ ', Attention ' + normalized.attention
+ ', Updates ' + normalized.update
+ ', Later ' + normalized.later
+ ', Drafts ' + normalized.draft
+ '; ' + active + ' active ' + (active === 1 ? 'queue' : 'queues');
buttons.queues?.setAttribute('aria-label', label);
}
return {start, select, refreshVisibility, updateAttention, updateQueues, updateWork};
});