diff --git a/lib/collectionWatch.js b/lib/collectionWatch.js
index 8061f58..5cce293 100644
--- a/lib/collectionWatch.js
+++ b/lib/collectionWatch.js
@@ -45,7 +45,8 @@ async function list() {
(SELECT COUNT(*)::int FROM watched_collection_items i WHERE i.watch_id = w.id AND i.status IN ('imported', 'partial')) AS imported_count,
(SELECT COUNT(*)::int FROM watched_collection_items i WHERE i.watch_id = w.id AND i.status = 'partial') AS partial_count,
(SELECT COUNT(*)::int FROM watched_collection_items i WHERE i.watch_id = w.id AND i.status = 'error') AS error_count,
- COALESCE(r.recent, '[]'::json) AS recent
+ COALESCE(r.recent, '[]'::json) AS recent,
+ COALESCE(q.pending, '[]'::json) AS pending
FROM watched_collections w
LEFT JOIN projects p ON p.id = w.project_id
LEFT JOIN LATERAL (
@@ -54,6 +55,13 @@ async function list() {
ORDER BY i.seen_at DESC) AS recent
FROM (SELECT * FROM watched_collection_items WHERE watch_id = w.id ORDER BY seen_at DESC LIMIT 5) i
) r ON true
+ -- Every model still waiting for its files, not just the five most recent.
+ LEFT JOIN LATERAL (
+ SELECT json_agg(json_build_object('model_id', i.model_id, 'title', i.title) ORDER BY i.seen_at DESC) AS pending
+ FROM (SELECT * FROM watched_collection_items
+ WHERE watch_id = w.id AND status = 'partial' AND model_id IS NOT NULL
+ ORDER BY seen_at DESC LIMIT 200) i
+ ) q ON true
ORDER BY w.id`);
return rows;
}
@@ -88,8 +96,14 @@ async function add({ url, aiTag = false, intervalHours = 6 }) {
`INSERT INTO watched_collections (site, source_id, url, title, project_id, ai_tag, interval_hours, total, listed, last_status)
VALUES ('makerworld', $1, $2, $3, $4, $5, $6, $7, $8, 'checking') RETURNING id`,
[sourceId, url, collection.title, project.id, Boolean(aiTag), hours, collection.total, collection.designs.length]);
- checkNow(watch.id).catch((err) => console.warn(`Watch ${watch.id} first check failed:`, err.message));
- return get(watch.id);
+ // The collection was just read, so import straight from it rather than
+ // fetching the whole thing again.
+ const added = await get(watch.id);
+ checking.add(watch.id);
+ importFound(added, collection)
+ .catch((err) => console.warn(`Watch ${watch.id} first import failed:`, err.message))
+ .finally(() => checking.delete(watch.id));
+ return added;
}
async function update(id, { enabled, aiTag, intervalHours }) {
diff --git a/public/app.js b/public/app.js
index a4b9e2a..569e9fe 100644
--- a/public/app.js
+++ b/public/app.js
@@ -2204,6 +2204,12 @@ function watchHtml(w) {
? ``
: escapeHtml(i.title || i.design_id)}${i.error ? ` ${escapeHtml(i.error)}` : ''}${(i.error || i.status === 'partial') && i.model_id
? ` ` : ''}`).join('')}` : ''}
+ ${(w.pending || []).length ? `${plural(w.partial_count || w.pending.length, 'model')} still without files
+
Nothing watched yet. Paste a MakerWorld collection link above.
'; - if (watches.some((w) => w.last_status === 'checking')) watchPoll = setTimeout(refreshWatches, 5000); + // Keep the page current even between checks: one may start from the schedule, + // another tab, or another browser. refreshWatches stops itself when you leave. + const checking = watches.some((w) => w.last_status === 'checking'); + watchPoll = setTimeout(refreshWatches, checking ? 5000 : 20000); } async function handleAddWatch(e) { diff --git a/public/styles.css b/public/styles.css index 3c4bf57..56c7a9f 100644 --- a/public/styles.css +++ b/public/styles.css @@ -464,6 +464,9 @@ label { display: block; font-size: 13px; font-weight: 500; color: var(--text-mut .watch-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; font-size: 14px; } .watch-actions { margin-left: auto; display: flex; flex-wrap: wrap; gap: 8px; } .watch-actions .btn-sm { align-self: auto; } +.watch-pending summary { cursor: pointer; font-size: 13px; color: var(--text-muted); } +.watch-pending summary:hover { color: var(--text); } +.watch-pending .watch-recent { margin-top: 6px; } .watch-meta { font-family: var(--mono); font-size: 12px; color: var(--text-dim); } .watch-recent { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--text-muted); max-height: 180px; overflow-y: auto; } #watchStatus.is-error { color: var(--danger); }