139 lines
6.1 KiB
JavaScript
139 lines
6.1 KiB
JavaScript
(function (root, factory) {
|
|
const createSearchBatchPlan = factory();
|
|
if (typeof module === 'object' && module.exports) module.exports = createSearchBatchPlan;
|
|
if (root) {
|
|
root.createSearchBatchPlan = createSearchBatchPlan;
|
|
root.mountSearchBatchPlanning = createSearchBatchPlan.mount;
|
|
}
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
function createSearchBatchPlan({
|
|
limit = 50,
|
|
resolve = item => Promise.resolve(item),
|
|
claim = item => Promise.resolve(item),
|
|
onChange = () => {},
|
|
} = {}) {
|
|
const maximum = Number.isInteger(limit) && limit > 0 ? limit : 50;
|
|
const selected = new Map();
|
|
let active = false;
|
|
|
|
const identity = item => [item?.kind || '', item?.repository || '', item?.number || ''].join(':');
|
|
const eligible = item => item?.kind === 'issue' && item?.state === 'open' &&
|
|
Boolean(item.repository) && Number.isInteger(item.number);
|
|
function snapshot() {
|
|
return { active, count:selected.size, items:Array.from(selected.values()) };
|
|
}
|
|
function changed() {
|
|
const state = snapshot();
|
|
onChange(state);
|
|
return state;
|
|
}
|
|
function start() {
|
|
active = true;
|
|
selected.clear();
|
|
return changed();
|
|
}
|
|
function cancel() {
|
|
active = false;
|
|
selected.clear();
|
|
return changed();
|
|
}
|
|
function toggle(item) {
|
|
if (!active) return 'inactive';
|
|
if (!eligible(item)) return 'ineligible';
|
|
const key = identity(item);
|
|
if (selected.has(key)) {
|
|
selected.delete(key);
|
|
changed();
|
|
return 'removed';
|
|
}
|
|
if (selected.size >= maximum) return 'limit';
|
|
selected.set(key, { ...item });
|
|
changed();
|
|
return 'selected';
|
|
}
|
|
async function prepare(item) {
|
|
const detail = await resolve(item);
|
|
if (detail?.assigned_to_me) return detail;
|
|
if (detail?.claimable) return claim(detail);
|
|
throw new Error('This issue is no longer available to assign.');
|
|
}
|
|
return { identity, eligible, start, cancel, toggle, snapshot, prepare, limit:maximum };
|
|
}
|
|
|
|
createSearchBatchPlan.mount = function mountSearchBatchPlanning(
|
|
document, batchFactory, todayWork, getOwner, fetchJson, previewPath, queueToday,
|
|
acceptIssue, lookup, render, escapeHtml, escapeAttribute
|
|
) {
|
|
const get = selector => document.querySelector(selector);
|
|
let processor;
|
|
const plan = createSearchBatchPlan({
|
|
resolve:item => fetchJson(previewPath(item), {headers:{Accept:'application/json'}}),
|
|
claim:detail => fetchJson(
|
|
'api/v1/repos/' + detail.repository.split('/').map(encodeURIComponent).join('/') +
|
|
'/issues/' + encodeURIComponent(detail.number) + '/claim',
|
|
{method:'PATCH', headers:{Accept:'application/json'}}
|
|
),
|
|
onChange:state => {
|
|
get('#search-batch-actions').hidden = !state.active;
|
|
get('#select-search-results').hidden = state.active;
|
|
get('#queue-selected-search-results').disabled = state.count === 0;
|
|
get('#search-selection-status').textContent = state.count ?
|
|
state.count + ' issue' + (state.count === 1 ? '' : 's') + ' selected.' : 'No issues selected.';
|
|
render();
|
|
},
|
|
});
|
|
processor = batchFactory({
|
|
capacity:() => Math.max(0, todayWork.limit - todayWork.read().length),
|
|
owner:getOwner,
|
|
journalName:'search-today-batch',
|
|
autoMount:false,
|
|
claim:item => plan.prepare(item),
|
|
queue:confirmed => queueToday(acceptIssue(confirmed)),
|
|
onProgress:progress => {
|
|
get('#queue-selected-search-results').disabled = progress.status === 'running';
|
|
if (progress.status === 'running') {
|
|
get('#search-selection-status').textContent = 'Planning ' + progress.processed + ' of ' + progress.selected + '…';
|
|
} else if (progress.status === 'full') {
|
|
get('#search-selection-status').textContent = 'Today has ' + progress.available + ' remaining slot' +
|
|
(progress.available === 1 ? '.' : 's.');
|
|
} else if (progress.status === 'complete') {
|
|
get('#cmd-search-action-status').textContent = progress.failed.length ?
|
|
progress.queued.length + ' queued · ' + progress.failed.length + ' need retry.' :
|
|
progress.queued.length + ' added to Today.';
|
|
get('#resume-search-batch').hidden = processor.pending() === 0;
|
|
if (!progress.failed.length) plan.cancel();
|
|
}
|
|
},
|
|
});
|
|
get('#select-search-results').addEventListener('click', () => plan.start());
|
|
get('#cancel-search-selection').addEventListener('click', () => plan.cancel());
|
|
get('#queue-selected-search-results').addEventListener('click', () => processor.run(plan.snapshot().items));
|
|
get('#resume-search-batch').addEventListener('click', () => processor.resume());
|
|
get('#open-palette').addEventListener('click', () => restore());
|
|
get('#cmd-results').addEventListener('change', event => {
|
|
const index = event.target?.dataset?.searchSelect;
|
|
if (index === undefined) return;
|
|
const result = lookup(Number(index));
|
|
if (result) plan.toggle(result);
|
|
});
|
|
function restore() {
|
|
const pending = processor.pending();
|
|
get('#resume-search-batch').hidden = pending === 0;
|
|
get('#resume-search-batch').textContent = 'Resume ' + pending + ' interrupted';
|
|
}
|
|
function resultHtml(result, index) {
|
|
const allowed = result.kind === 'issue' && result.state === 'open';
|
|
const selected = plan.snapshot().items.some(item => plan.identity(item) === plan.identity(result));
|
|
return '<label class="cmd-select-result"' + (allowed ? '' : ' aria-disabled="true"') + '>' +
|
|
'<input type="checkbox" data-search-select="' + index + '" ' + (selected ? 'checked ' : '') +
|
|
(allowed ? '' : 'disabled ') + 'aria-label="Select ' + escapeAttribute(result.title) + '" />' +
|
|
'<span>' + escapeHtml(result.title) + '</span><span class="cmd-meta">' +
|
|
escapeHtml(result.repository) + ' #' + escapeHtml(result.number) + ' · ' +
|
|
(allowed ? 'Open issue' : 'Not eligible') + '</span></label>';
|
|
}
|
|
return { plan, processor, restore, resultHtml };
|
|
};
|
|
|
|
return createSearchBatchPlan;
|
|
});
|