← back to Wallco Ai
designs grid: add 'Digital for Sale' bulk action (unpublish + tag digital-file + stamp digital_file_at)
333f44d4b0afeca370481665aa382dcdfbd42e89 · 2026-06-03 14:07:36 -0700 · Steve Abrams
Files touched
Diff
commit 333f44d4b0afeca370481665aa382dcdfbd42e89
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jun 3 14:07:36 2026 -0700
designs grid: add 'Digital for Sale' bulk action (unpublish + tag digital-file + stamp digital_file_at)
---
server.js | 24 +++++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/server.js b/server.js
index b3657db..3a887a8 100644
--- a/server.js
+++ b/server.js
@@ -4833,7 +4833,7 @@ app.post('/api/designs/bulk-action', express.json({ limit: '64kb' }), (req, res)
const ids = Array.isArray(req.body?.ids) ? req.body.ids.map((x) => parseInt(x, 10)).filter((n) => Number.isFinite(n) && n > 0) : [];
const action = String(req.body?.action || '').toLowerCase();
if (!ids.length) return res.status(400).json({ error: 'ids[] required' });
- if (!['delete', 'publish', 'unpublish', 'tag'].includes(action)) return res.status(400).json({ error: 'unknown action' });
+ if (!['delete', 'publish', 'unpublish', 'digital', 'tag'].includes(action)) return res.status(400).json({ error: 'unknown action' });
const idList = ids.join(',');
try {
let affected = 0;
@@ -4851,6 +4851,23 @@ app.post('/api/designs/bulk-action', express.json({ limit: '64kb' }), (req, res)
} else if (action === 'unpublish') {
psqlExecLocal(`UPDATE spoon_all_designs SET is_published=FALSE WHERE id IN (${idList});`);
affected = ids.length;
+ } else if (action === 'digital') {
+ // Unpublish from the wallpaper catalog AND mark as a digital file for sale.
+ // Mirrors the proven cactus-curator 'digital' verdict (lines ~3018): off
+ // the live catalog (is_published/web_viewer=FALSE), stamp digital_file_at,
+ // and tag 'digital-file' so the digital-pack pipeline picks it up.
+ // Written against the base table all_designs (the columns digital_file_at /
+ // web_viewer / tags live there) — same target the cactus path uses.
+ psqlExecLocal(`UPDATE all_designs
+ SET is_published=FALSE, web_viewer=FALSE, digital_file_at=now(),
+ tags = array_remove(COALESCE(tags, ARRAY[]::text[]), 'digital-file') || ARRAY['digital-file']::text[]
+ WHERE id IN (${idList});`);
+ affected = ids.length;
+ // Drop from in-memory catalog so it leaves the live feed immediately.
+ for (const id of ids) {
+ const idx = DESIGNS.findIndex((x) => x.id === id);
+ if (idx >= 0) DESIGNS.splice(idx, 1);
+ }
} else if (action === 'tag') {
// For now — accept but don't implement tag-write path (needs UI for tag input)
return res.status(501).json({ error: 'tag action not yet implemented — coming soon' });
@@ -8500,6 +8517,7 @@ ${(req.query.source === 'all') ? `
<button type="button" data-bulk="tag" style="background:#ddc28a;color:#1a1816;border:0;padding:6px 14px;border-radius:999px;cursor:pointer;font-weight:600">Tag…</button>
<button type="button" data-bulk="publish" style="background:#5a8a5a;color:#fff;border:0;padding:6px 14px;border-radius:999px;cursor:pointer;font-weight:600">Publish</button>
<button type="button" data-bulk="unpublish" style="background:#8a7a5a;color:#fff;border:0;padding:6px 14px;border-radius:999px;cursor:pointer;font-weight:600">Unpublish</button>
+ <button type="button" data-bulk="digital" style="background:#6a5acd;color:#fff;border:0;padding:6px 14px;border-radius:999px;cursor:pointer;font-weight:600" title="Unpublish from the wallpaper catalog and mark as a digital file for sale (tags digital-file, stamps digital_file_at)">💾 Digital for Sale</button>
<button type="button" data-bulk="delete" style="background:#b04030;color:#fff;border:0;padding:6px 14px;border-radius:999px;cursor:pointer;font-weight:600">Delete</button>
<button type="button" data-bulk="contactsheet" style="background:#3a6ea5;color:#fff;border:0;padding:6px 14px;border-radius:999px;cursor:pointer;font-weight:600" title="Build a downloadable contact sheet PNG of the selected designs">📇 Contact sheet</button>
<button type="button" data-bulk="cancel" style="background:transparent;color:#fff;border:1px solid rgba(255,255,255,.3);padding:6px 12px;border-radius:999px;cursor:pointer">Cancel</button>
@@ -8755,7 +8773,7 @@ ${(req.query.source === 'all') ? `
if (action === 'cancel') { clearSelection(); return; }
if (action === 'contactsheet') { buildContactSheet(Array.from(selected), btn); return; }
var ids = Array.from(selected); if (!ids.length) return;
- var labels = { tag:'Tag', publish:'Publish', unpublish:'Unpublish', 'delete':'Delete' };
+ var labels = { tag:'Tag', publish:'Publish', unpublish:'Unpublish', digital:'Add Digital for Sale', 'delete':'Delete' };
if (!confirm(labels[action] + ' ' + ids.length + ' design(s)?')) return;
btn.disabled = true; btn.style.opacity = '.6';
// Build absolute URL via origin-without-credentials so Chrome doesn't refuse:
@@ -8771,7 +8789,7 @@ ${(req.query.source === 'all') ? `
btn.disabled = false; btn.style.opacity = '1';
if (j && j.ok) {
alert(labels[action] + ' applied to ' + (j.affected || ids.length) + ' design(s).');
- if (action === 'delete') ids.forEach(function(id){ var c = grid.querySelector('[data-design-id="' + id + '"]'); if (c) c.remove(); });
+ if (action === 'delete' || action === 'digital') ids.forEach(function(id){ var c = grid.querySelector('[data-design-id="' + id + '"]'); if (c) c.remove(); });
clearSelection();
} else { alert('Bulk action failed: ' + (j && j.error || 'unknown')); }
}).catch(function(err){ btn.disabled = false; btn.style.opacity = '1'; alert('Network error: ' + err.message); });
← 8282219 dogs-curator: fix greedy edges parse (split_part slurped app
·
back to Wallco Ai
·
build-mural-master: retry per-tile ESRGAN calls (4x backoff) bd8def2 →