import { API } from '../api.js'; import { DocumentQueueEntry } from './document-queue-entry.js' /** @typedef {{selectedIDs: number[], allIDs: number[]}} DocumentQueueEventChangeSelectionDetails */ /** @typedef {CustomEvent} DocumentQueueEventChangeSelection */ // TODO: Use LitElement, and use repeat directive, which also keeps the DOM state when shuffling elements around export class DocumentQueue extends HTMLElement { /** @type {API|undefined} */ #api; /** @param {API} api */ set api(api) { this.#api = api; this.#api.addEventListener("queuedeleteat", /** @param {import("model").APIEvents["queuedeleteat"]} event */(event) => { this.queueDeleteAt(event.detail.indexA, event.detail.indexB); }); this.#api.addEventListener("queueinsertat", /** @param {import("model").APIEvents["queueinsertat"]} event */(event) => { this.queueInsertAt(event.detail.index, event.detail.documents); }); this.#api.addEventListener("queueshiftat", /** @param {import("model").APIEvents["queueshiftat"]} event */(event) => { this.queueShiftAt(event.detail.index, event.detail.offset); }); this.#api.addEventListener("queuereplace", /** @param {import("model").APIEvents["queuereplace"]} event */(event) => { this.queueReplace(event.detail.documents); }); } connectedCallback() { this.style.display = "flex"; this.style.gap = "8px"; this.style.flexDirection = "column"; this.style.padding = "8px"; } selectionInfo() { const children = Array.from(this.children); const result = {}; result.selectedIDs = children.filter(/** @param {DocumentQueueEntry} value */ value => { return value.selected; }).map(/** @param {DocumentQueueEntry} value */(value) => { return value.queueEntry.id; }); result.allIDs = children.map(/** @param {DocumentQueueEntry} value */ value => { return value.queueEntry.id; }); return result; } updateSelection() { /** @type {CustomEventInit} */ const eventData = { detail: this.selectionInfo() }; this.dispatchEvent(new CustomEvent("changeselection", eventData)); } /** * * @param {boolean} state */ selectAll(state) { const children = Array.from(this.children); children.forEach(/** @param {DocumentQueueEntry} child */ child => { child.selected = state; }); } /** * Deletes a range of documents. * @param {number} indexA // Start index. * @param {number} indexB // End index. (Not included in the range). */ queueDeleteAt(indexA, indexB) { // Store positions. Array.from(this.children).forEach(/** @param {DocumentQueueEntry} child */ child => { child.prepareFLIP(); }); if (this.hasChildNodes()) { const children = this.children; for (let i = indexA; i < indexB; i++) { this.removeChild(children[i]); } } // Start FLIP animation. Array.from(this.children).forEach(/** @param {DocumentQueueEntry} child */ child => { child.doFLIP(); }); this.updateSelection(); } /** * Inserts a range of documents at the given index. * @param {number} index * @param {import('model').APIQueueEntry[]} documents */ queueInsertAt(index, documents) { // Store positions. Array.from(this.children).forEach(/** @param {DocumentQueueEntry} child */ child => { child.prepareFLIP(); }); documents.forEach(document => { if (this.hasChildNodes() || this.children.length === index) { const newChild = this.appendChild(new DocumentQueueEntry()); newChild.api = this.#api; newChild.queueEntry = document; newChild.addEventListener("changeselection", e => this.updateSelection()); } else { const newChild = this.insertBefore(new DocumentQueueEntry(), this.childNodes[index]); newChild.api = this.#api; newChild.queueEntry = document; newChild.addEventListener("changeselection", e => this.updateSelection()); } index++; }); // Start FLIP animation. Array.from(this.children).forEach(/** @param {DocumentQueueEntry} child */ child => { child.doFLIP(); }); this.updateSelection(); } /** * Replaces all documents currently in the list/queue. * @param {import('model').APIQueueEntry[]} documents */ queueReplace(documents) { this.innerHTML = ""; documents.forEach(document => { const newChild = this.appendChild(new DocumentQueueEntry()); newChild.api = this.#api; newChild.queueEntry = document; newChild.addEventListener("changeselection", e => this.updateSelection()); }); this.updateSelection(); } /** * Shifts a single document entry by the given offset. * @param {number} index * @param {number} offset */ queueShiftAt(index, offset) { // Store positions. Array.from(this.children).forEach(/** @param {DocumentQueueEntry} child */ child => { child.prepareFLIP(); }); const child = this.children[index]; child.remove(); const newIndex = index + offset; if (!this.hasChildNodes() || this.children.length === newIndex) { this.appendChild(child); } else { this.insertBefore(child, this.children[newIndex]); } // Start FLIP animation. Array.from(this.children).forEach(/** @param {DocumentQueueEntry} child */ child => { child.doFLIP(); }); } } customElements.define("document-queue", DocumentQueue);