Scanyonero/static/js/components/document-queue.js
David Vogel 853a1bb58d Rework into FTP scanning server
- Rename to Scanyonero
- Add FTP server that ingests TIFF, PNG, JPEG or PDF files
- Add web interface to check and modify ingested files
- Rework how ocrmypdf is invoked

Basics are working, but the program is not in a usable state.
2025-05-14 12:08:38 +02:00

170 lines
5.1 KiB
JavaScript

import { API } from '../api.js';
import { DocumentQueueEntry } from './document-queue-entry.js'
/** @typedef {{selectedIDs: number[], allIDs: number[]}} DocumentQueueEventChangeSelectionDetails */
/** @typedef {CustomEvent<DocumentQueueEventChangeSelectionDetails>} 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<DocumentQueueEventChangeSelectionDetails>} */
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);