145 lines
5.7 KiB
JavaScript
145 lines
5.7 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) module.exports = factory;
|
|
else root.createMobileQueueLauncher = factory;
|
|
})(typeof self !== 'undefined' ? self : this, function createMobileQueueLauncher(options) {
|
|
const emptyMessages = {
|
|
attention: 'No items need attention.',
|
|
update: 'No unread updates are ready to open.',
|
|
later: 'No deferred work is ready to open.',
|
|
draft: 'No drafts are ready to open.',
|
|
};
|
|
const labels = {
|
|
delivery: 'Recover Delivery', gate: 'Review Human Gates', attention: 'Start Attention',
|
|
today: 'Continue Today', update: 'Resume Updates', agenda: 'Open Agenda',
|
|
following: 'Review Following', authored: 'Open My PRs', filed: 'Review Filed',
|
|
later: 'Start Later', draft: 'Open Drafts',
|
|
};
|
|
const criticalQueueNames = ['delivery', 'gate'];
|
|
const defaultRoutineOrder = [
|
|
'attention', 'today', 'update', 'agenda', 'following', 'authored', 'filed', 'later', 'draft',
|
|
];
|
|
const onlineOnlyQueues = new Set(['delivery', 'gate']);
|
|
const allQueues = [
|
|
'today', 'tomorrow', 'week', 'agenda', 'delivery', 'gate', 'attention',
|
|
'update', 'following', 'filed', 'authored', 'later', 'draft', 'find', 'recaps',
|
|
];
|
|
|
|
function routineOrder() {
|
|
const proposed = options.getRoutineOrder ? options.getRoutineOrder() : defaultRoutineOrder;
|
|
if (!Array.isArray(proposed) || proposed.length !== defaultRoutineOrder.length ||
|
|
new Set(proposed).size !== defaultRoutineOrder.length ||
|
|
proposed.some(name => !defaultRoutineOrder.includes(name))) return defaultRoutineOrder.slice();
|
|
return proposed.slice();
|
|
}
|
|
|
|
function activeQueueNames() {
|
|
return criticalQueueNames.concat(routineOrder());
|
|
}
|
|
|
|
function recommend() {
|
|
const counts = options.getCounts ? options.getCounts() : {};
|
|
const online = options.isOnline ? options.isOnline() : true;
|
|
const match = activeQueueNames().map(name => [name, labels[name]]).find(([name]) =>
|
|
Number(counts[name]) > 0 && (online || !onlineOnlyQueues.has(name))
|
|
);
|
|
if (!match) return {name: 'find', count: 0, label: 'Find Work'};
|
|
const [name, label] = match;
|
|
const count = Math.max(0, Number(counts[name]) || 0);
|
|
return {name, count, label: label + ' (' + count + ')'};
|
|
}
|
|
|
|
function adaptiveRecommendation() {
|
|
const preparation = options.getPreparation ? options.getPreparation() : null;
|
|
if (preparation && (preparation.active || Number(preparation.total) > 0)) {
|
|
const prefix = preparation.active ? 'Resume preparation · ' : 'Start day · ';
|
|
return {name: 'prepare', count: Math.max(0, Number(preparation.total) || 0), label: prefix + preparation.label};
|
|
}
|
|
return recommend();
|
|
}
|
|
|
|
function presentation() {
|
|
const counts = options.getCounts ? options.getCounts() : {};
|
|
const names = activeQueueNames();
|
|
const active = names
|
|
.map(name => ({name, count: Math.max(0, Number(counts[name]) || 0)}))
|
|
.filter(item => item.count > 0);
|
|
names.forEach(name => {
|
|
if (counts[name + 'Unavailable'] && !active.some(item => item.name === name)) {
|
|
active.push({name, unavailable: true});
|
|
}
|
|
});
|
|
return {
|
|
nextUp: adaptiveRecommendation(),
|
|
active,
|
|
planning: ['today', 'tomorrow', 'week'],
|
|
all: allQueues.slice(),
|
|
};
|
|
}
|
|
|
|
function renderPresentation() {
|
|
const view = presentation();
|
|
const planning = new Set(view.planning);
|
|
const active = view.active.filter(item => !planning.has(item.name));
|
|
const activeNames = new Set(active.map(item => item.name));
|
|
if (options.nextAction) {
|
|
options.nextAction.textContent = view.nextUp.label;
|
|
options.nextAction.dataset.queue = view.nextUp.name;
|
|
const prefix = view.nextUp.name === 'prepare' ? 'Start or continue: ' : 'Next up: ';
|
|
options.nextAction.setAttribute('aria-label', prefix + view.nextUp.label);
|
|
}
|
|
active.forEach(item => {
|
|
const row = options.rows?.[item.name];
|
|
if (!row) return;
|
|
if (item.unavailable) row.setAttribute('data-unavailable', 'true');
|
|
else row.removeAttribute('data-unavailable');
|
|
options.activeList?.append(row);
|
|
});
|
|
view.planning.forEach(name => {
|
|
const row = options.rows?.[name];
|
|
if (row) options.planningList?.append(row);
|
|
});
|
|
view.all.forEach(name => {
|
|
if (planning.has(name) || activeNames.has(name)) return;
|
|
const row = options.rows?.[name];
|
|
if (!row) return;
|
|
row.removeAttribute('data-unavailable');
|
|
options.allList?.append(row);
|
|
});
|
|
if (options.activeSection) options.activeSection.hidden = active.length === 0;
|
|
return view;
|
|
}
|
|
|
|
function open(name) {
|
|
if (name === 'delivery' && options.openDelivery) return options.openDelivery();
|
|
if (name === 'gate' && options.openHumanGates) return options.openHumanGates();
|
|
if (name === 'today') return options.openToday();
|
|
if (name === 'agenda') return options.openAgenda();
|
|
if (name === 'update' && options.openUpdates) return options.openUpdates();
|
|
if (name === 'following' && options.openFollowing) return options.openFollowing();
|
|
if (name === 'filed' && options.openFiled) return options.openFiled();
|
|
options.selectFilter(name);
|
|
const action = options.firstAction(name);
|
|
if (!action) {
|
|
options.announce(emptyMessages[name] || 'No work is ready to open.');
|
|
return 'empty';
|
|
}
|
|
action.click();
|
|
return 'opened';
|
|
}
|
|
|
|
function continueWork() {
|
|
const next = adaptiveRecommendation();
|
|
if (next.name === 'prepare') {
|
|
options.openPreparation();
|
|
return 'prepare';
|
|
}
|
|
if (next.name === 'find') {
|
|
options.openFindWork();
|
|
return 'find';
|
|
}
|
|
return open(next.name);
|
|
}
|
|
|
|
return { open, recommend, adaptiveRecommendation, presentation, renderPresentation, continueWork };
|
|
});
|