From 181938ea32cf2c11d10d56018df22586adff7860 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 11 Jan 2026 17:07:14 +0000
Subject: [PATCH] =CRUD.js cleanup, DataStore.js filtering optimization
---
assets/js/concise/Queue.js | 155 ++++++++++++++++++++++++++++++++++-----------------
1 files changed, 102 insertions(+), 53 deletions(-)
diff --git a/assets/js/concise/Queue.js b/assets/js/concise/Queue.js
index 478be92..db25262 100644
--- a/assets/js/concise/Queue.js
+++ b/assets/js/concise/Queue.js
@@ -152,7 +152,7 @@
window.addEventListener('beforeunload', this.unloadHandler);
}
handleOnline() {
- this.updatePanel();
+ this.updatePanel('synced');
if (this.getQueueByStatus(this.pendingStatuses).length > 0) {
this.processQueue();
}
@@ -174,6 +174,7 @@
const refresh = window.targetCheck(e, this.selectors.refresh.button);
if (refresh) {
this.store.clearCache();
+ this.store.clearFilters();
this.store.fetch();
return;
}
@@ -304,6 +305,7 @@
title: 'Operation',
status: 'queued',
timestamp: Date.now(),
+ created_at: new Date().toISOString(),
retries: 0,
user: this.user,
... operation
@@ -441,8 +443,7 @@
this.setProcessing(false);
this.stopActivityTracking();
- // this.toggleQueue(this.maybeStartPolling());
-
+ this.toggleQueue(this.maybeStartPolling());
}
async processOperation(operation) {
@@ -545,13 +546,37 @@
});
}
+ sortOperations(ops) {
+ const statusPriority = {
+ 'processing': 0,
+ 'uploading': 1,
+ 'pending': 2,
+ 'queued': 3,
+ 'localProcessing': 4,
+ 'failed': 5,
+ 'completed': 6,
+ 'failed_permanent': 7
+ };
+
+ return ops.sort((a, b) => {
+ // First by status priority
+ const priorityDiff = (statusPriority[a.status] ?? 99) - (statusPriority[b.status] ?? 99);
+ if (priorityDiff !== 0) return priorityDiff;
+
+ // Then by updated_at (most recent first)
+ const aTime = a.updated_at ?? a.timestamp ?? 0;
+ const bTime = b.updated_at ?? b.timestamp ?? 0;
+ return new Date(bTime) - new Date(aTime);
+ });
+ }
+
getAllQueue() {
let ops = [... new Set([
...Array.from(this.store.data.values()),
... Array.from(this.queue.values())
])];
//Sort operations by operation updated_at
- return this.sortByDate(ops);
+ return this.sortOperations(ops);
}
getQueueByStatus(status) {
@@ -563,7 +588,7 @@
...Array.from(this.store.filterByIndex({status: status})),
...Array.from(this.queue.values()).filter(op => status.includes(op.status))
])];
- return this.sortByDate(ops);
+ return this.sortOperations(ops);
}
@@ -589,32 +614,53 @@
POLLING
****************************************************************************/
maybeStartPolling() {
- const incomplete = this.getQueueByStatus(this.pendingStatuses);
+ const incomplete = this.getQueueByStatus([...this.pendingStatuses, ...this.workingStatuses]);
if (incomplete.length > 0) {
this.startPolling();
return true;
}
+ this.updatePanel('synced');
return false;
}
startPolling() {
if (this.isPolling) return;
this.isPolling = true;
this.updatePanel('pending');
+ this.runPollCycle();
+ }
- this.pollTimer = setInterval(async () => {
- try {
- this.store.clearCache();
- await this.store.fetch();
- if (!this.maybeStartPolling()) {
- this.stopPolling();
- this.updatePanel('synced');
- }
- } catch (error) {
- console.error('Polling error:', error);
+ async runPollCycle() {
+ if (!this.isPolling) return;
+
+ try {
+ this.store.clearCache();
+ await this.store.fetch();
+ if (!this.maybeStartPolling()) {
+ this.stopPolling();
+ this.updatePanel('synced');
+ return;
}
- },
- 5000);
- this.startCountdown();
+ } catch (error) {
+ console.error('Polling error:', error);
+ }
+
+ // Schedule next poll with countdown
+ this.startCountdown(5, () => this.runPollCycle());
+ }
+
+ startCountdown(count, onComplete) {
+ this.ui.refresh.countdown.classList.add('counting');
+ this.ui.refresh.countdown.textContent = count;
+
+ this.countdownTimer = setInterval(() => {
+ count--;
+ if (count > 0) {
+ this.ui.refresh.countdown.textContent = count;
+ } else {
+ this.stopCountdown();
+ if (onComplete) onComplete();
+ }
+ }, 1000);
}
stopPolling() {
@@ -626,24 +672,14 @@
}
this.stopCountdown();
}
- startCountdown(count = 5) {
- if (!this.isPolling) return;
- this.ui.refresh.countdown.textContent = count;
- this.countdownTimer = setInterval(async() => {
- count--;
- if (count >= 0) {
- this.ui.refresh.countdown.textContent = count;
- }else {
- this.ui.refresh.countdown.textContent = '';
- this.stopCountdown();
- }
- },1000);
- }
+
stopCountdown() {
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
}
+ this.ui.refresh.countdown.classList.remove('counting');
+ this.ui.refresh.countdown.textContent = '';
}
/****************************************************************************
UI
@@ -661,6 +697,12 @@
this.ui.actions.retry.disabled = operations.filter(op => op.status === 'failed').length === 0;
this.ui.actions.clear.disabled = operations.filter(op => op.status === 'completed').length ===0;
+ const activeCount = operations.filter(op =>
+ [...this.pendingStatuses, ...this.workingStatuses].includes(op.status)
+ ).length;
+ this.ui.toggle.count.hidden = activeCount === 0;
+ this.ui.toggle.count.textContent = activeCount;
+
for (let status of this.statuses) {
if (status === 'failed_permanent') continue;
let total = operations.filter(op => op.status === status).length;
@@ -681,26 +723,38 @@
const status = this.store.filters?.status ?? 'all';
const operations = (status === 'all') ? this.getAllQueue() : this.getQueueByStatus(status);
+ const sortedOps = this.sortOperations(operations);
- window.removeChildren(this.ui.items.container);
-
- if (operations.length === 0) {
+ if (sortedOps.length === 0) {
+ window.removeChildren(this.ui.items.container);
const empty = window.getTemplate('emptyQueue');
this.ui.items.container.append(empty);
this.a11y.announce('No items in queue');
return;
+ } else {
+ this.ui.items.container.querySelector('.empty-group')?.remove();
}
- operations.forEach(op => {
- let item = this.items.get(op.id);
+ // Track which items should exist
+ const expectedIds = new Set(sortedOps.map(op => op.id));
+ // Remove items that shouldn't exist
+ this.items.forEach((item, id) => {
+ if (!expectedIds.has(id)) {
+ item.element?.remove();
+ this.items.delete(id);
+ }
+ });
+
+ // Update/add items in order
+ sortedOps.forEach((op, index) => {
+ let item = this.items.get(op.id);
if (!item) {
- // Create new element and reference
item = this.createOperationElement(op);
}
-
if (item?.element) {
this.updateOperationUI(op.id);
+ // Reorder by re-appending (moves to end in correct order)
this.ui.items.container.append(item.element);
}
});
@@ -742,20 +796,15 @@
item.ui.startedAt.textContent = window.formatTimeAgo(op.created_at);
}
let text = op.status === 'completed' ? 'Completed: ' : 'Last updated: ';
- let shouldShow =Object.hasOwn(op, 'updated_at') || Object.hasOwn(op, 'completed_at');
- item.ui.completed.wrap.hidden = !shouldShow;
- if (shouldShow && item.ui.completed.label && item.ui.completed.time) {
- let time;
- if (Object.hasOwn(op, 'completed_at')) {
- time = op.completed_at;
- } else {
- time = op.updated_at;
- }
-
- item.ui.completed.label.textContent = text;
- item.ui.completed.time.setAttribute('datetime', time);
- item.ui.completed.time.textContent = window.formatTimeAgo(time);
+ const shouldShowCompleted = op.status === 'completed' && (op.completed_at || op.updated_at);
+ item.ui.completed.wrap.hidden = !shouldShowCompleted;
+ if (shouldShowCompleted) {
+ const completedTime = op.completed_at ?? op.updated_at;
+ item.ui.completed.label.textContent = 'Completed: ';
+ item.ui.completed.time.setAttribute('datetime', completedTime);
+ item.ui.completed.time.textContent = window.formatTimeAgo(completedTime);
}
+
window.showProgress(item.ui.progress, progress, 100, this.statusLabel(op.status));
if (item.ui.actions.cancel) item.ui.actions.cancel.hidden = this.completedStatuses.includes(op.status);
if (item.ui.actions['retry']) {
@@ -786,7 +835,7 @@
updatePanel(status = 'syncing') {
if (!this.panelStatuses.includes(status)) return;
- this.ui.panel.classList.remove(this.panelStatuses);
+ this.ui.panel.classList.remove(...this.panelStatuses);
this.ui.panel.classList.add(status);
}
/****************************************************************************
--
Gitblit v1.10.0