154 lines
5.1 KiB
JavaScript
154 lines
5.1 KiB
JavaScript
function createFindWork({ fetchJson, onItems, onPagination, onStatus, onSelection = () => {} }) {
|
|
let available = [];
|
|
let pagination = { page: 1, total: 0, has_more: false };
|
|
let loadRequest = null;
|
|
let loadGeneration = 0;
|
|
let query = '';
|
|
let claimRequest = null;
|
|
const previewed = new Set();
|
|
let selecting = false;
|
|
const selected = new Map();
|
|
|
|
function itemKey(item) {
|
|
return String(item?.repository || '') + '#' + String(item?.number || '');
|
|
}
|
|
|
|
function emitSelection() {
|
|
const state = { active: selecting, count: selected.size, ids: Array.from(selected.keys()) };
|
|
onSelection(state);
|
|
return state;
|
|
}
|
|
|
|
function apply(result, append) {
|
|
const incoming = Array.isArray(result?.items) ? result.items : [];
|
|
if (append) {
|
|
const byId = new Map(available.map(item => [item.id, item]));
|
|
incoming.forEach(item => {
|
|
if (item && !byId.has(item.id)) byId.set(item.id, { ...item });
|
|
});
|
|
available = Array.from(byId.values());
|
|
} else {
|
|
available = incoming.map(item => ({ ...item }));
|
|
}
|
|
const availableKeys = new Set(available.map(itemKey));
|
|
previewed.forEach(key => {
|
|
if (!availableKeys.has(key)) previewed.delete(key);
|
|
});
|
|
pagination = {
|
|
page: Number(result?.page) || 1,
|
|
total: Number(result?.total) || available.length,
|
|
has_more: result?.has_more === true,
|
|
};
|
|
onItems(available.slice());
|
|
onPagination({ ...pagination });
|
|
}
|
|
|
|
function loadPage(page, append, requestedQuery = query) {
|
|
if (loadRequest && requestedQuery === query && append) return loadRequest;
|
|
const generation = ++loadGeneration;
|
|
const queryPart = requestedQuery ? '&q=' + encodeURIComponent(requestedQuery) : '';
|
|
const request = fetchJson('api/v1/available-issues?page=' + page + queryPart, {
|
|
headers: { Accept: 'application/json' },
|
|
}).then(result => {
|
|
if (generation !== loadGeneration) return result;
|
|
apply(result, append);
|
|
if (result?.refresh_failed === true) {
|
|
onStatus('Showing saved available work. Catalog refresh failed; retrying shortly.');
|
|
} else if (result?.revalidating === true) {
|
|
onStatus('Showing saved available work while the catalog refreshes…');
|
|
}
|
|
return result;
|
|
}).finally(() => { if (loadRequest === request) loadRequest = null; });
|
|
loadRequest = request;
|
|
return request;
|
|
}
|
|
|
|
return {
|
|
reset(result) {
|
|
apply(result, false);
|
|
},
|
|
load() {
|
|
return loadPage(1, false);
|
|
},
|
|
loadMore() {
|
|
if (!pagination.has_more) return Promise.resolve(false);
|
|
return loadPage(pagination.page + 1, true);
|
|
},
|
|
search(value) {
|
|
query = String(value || '').trim();
|
|
return loadPage(1, false, query);
|
|
},
|
|
query() {
|
|
return query;
|
|
},
|
|
items() {
|
|
return available.slice();
|
|
},
|
|
startSelection() {
|
|
selecting = true;
|
|
selected.clear();
|
|
return emitSelection();
|
|
},
|
|
cancelSelection() {
|
|
selecting = false;
|
|
selected.clear();
|
|
return emitSelection();
|
|
},
|
|
toggleSelection(item) {
|
|
if (!selecting || !item) return emitSelection();
|
|
const key = itemKey(item);
|
|
if (selected.has(key)) selected.delete(key);
|
|
else selected.set(key, item);
|
|
return emitSelection();
|
|
},
|
|
isSelected(item) {
|
|
return selected.has(itemKey(item));
|
|
},
|
|
selectedItems() {
|
|
return Array.from(selected.values());
|
|
},
|
|
selection() {
|
|
return { active: selecting, count: selected.size, ids: Array.from(selected.keys()) };
|
|
},
|
|
togglePreview(item) {
|
|
const key = itemKey(item);
|
|
if (previewed.has(key)) previewed.delete(key);
|
|
else previewed.add(key);
|
|
return previewed.has(key);
|
|
},
|
|
isPreviewed(item) {
|
|
return previewed.has(itemKey(item));
|
|
},
|
|
claim(item) {
|
|
if (claimRequest) return claimRequest;
|
|
const key = item.repository + '#' + item.number;
|
|
onStatus('Assigning ' + key + '…');
|
|
const path = 'api/v1/repos/' + item.repository.split('/').map(encodeURIComponent).join('/') +
|
|
'/issues/' + encodeURIComponent(item.number) + '/claim';
|
|
claimRequest = fetchJson(path, {
|
|
method: 'PATCH',
|
|
headers: { Accept: 'application/json' },
|
|
}).then(confirmed => {
|
|
if (!Array.isArray(confirmed?.assignees) || !confirmed.assignees.length) {
|
|
throw new Error('Issue assignment was not confirmed.');
|
|
}
|
|
available = available.filter(candidate =>
|
|
candidate.repository !== item.repository || candidate.number !== item.number
|
|
);
|
|
pagination.total = Math.max(available.length, pagination.total - 1);
|
|
pagination.has_more = available.length < pagination.total;
|
|
previewed.delete(itemKey(item));
|
|
selected.delete(itemKey(item));
|
|
emitSelection();
|
|
onItems(available.slice());
|
|
onPagination({ ...pagination });
|
|
onStatus('Assigned ' + key + ' to you.');
|
|
return confirmed;
|
|
}).finally(() => { claimRequest = null; });
|
|
return claimRequest;
|
|
},
|
|
};
|
|
}
|
|
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = createFindWork;
|