← back to 4square Admin
viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
707a66cd35f5bee3a659c4d8a95a853141031933 · 2026-08-12 08:53:18 -0700 · Steve
Files touched
Diff
commit 707a66cd35f5bee3a659c4d8a95a853141031933
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 08:53:18 2026 -0700
viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
---
index.html | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/index.html b/index.html
index b7d8739..47bdf70 100644
--- a/index.html
+++ b/index.html
@@ -165,6 +165,35 @@
#toast.show { opacity:1; transform:translateY(0); pointer-events:auto }
#toast.err { border-color:var(--danger) }
#toast.ok { border-color:var(--pub) }
+
+ /* ── List view ── */
+ .grid.list { display:flex; flex-direction:column; gap:4px }
+ .grid.list .card { display:grid; grid-template-columns:40px 40px 36px minmax(100px,1.6fr) minmax(80px,1fr) 70px 80px; align-items:center; column-gap:12px; border-radius:4px; aspect-ratio:auto; padding:0 12px 0 8px; min-height:46px }
+ .grid.list .card img { width:40px; height:40px; object-fit:cover; border-radius:3px }
+ .grid.list .card .meta { display:contents }
+ .grid.list .card .dig { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:10px }
+ .grid.list .card .lbl { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:11px }
+ .grid.list .card .badge { position:static; border-radius:3px; font-size:9px; padding:2px 6px }
+ .grid.list .card .cb { position:static; width:18px; height:18px; font-size:12px }
+ .grid.list .card .actions { position:static; display:flex; gap:3px }
+ .grid.list-head { display:none; grid-template-columns:40px 40px 36px minmax(100px,1.6fr) minmax(80px,1fr) 70px 80px; column-gap:12px; padding:5px 20px 5px 20px; background:#0b0a09; border-bottom:1px solid var(--line); position:sticky; top:54px; z-index:9 }
+ .grid.list-head .lh { font:500 10px/1 -apple-system; text-transform:uppercase; letter-spacing:.14em; color:#888; white-space:nowrap }
+ .grid.list ~ .list-head, .list-head { display:none }
+ body.viewlist .grid.list ~ .list-head, body.viewlist .list-head { display:grid }
+ /* ── Fields ▾ popover ── */
+ .sq-fieldmenu { position:relative }
+ .sq-fbtn { font:inherit; padding:6px 12px; background:#1a1816; color:var(--ink); border:1px solid var(--line); border-radius:4px; cursor:pointer; font-size:12px; white-space:nowrap }
+ .sq-fbtn:hover { border-color:var(--gold); color:var(--gold) }
+ .sq-fpop { position:absolute; top:calc(100% + 6px); right:0; background:#13110f; border:1px solid var(--gold); border-radius:6px; box-shadow:0 12px 30px rgba(0,0,0,.6); padding:8px 4px; z-index:30; min-width:190px }
+ .sq-fpop[hidden] { display:none }
+ .sq-fpop label { display:flex; align-items:center; gap:9px; padding:6px 14px; font-size:12px; color:#bba; cursor:pointer; white-space:nowrap }
+ .sq-fpop label:hover { background:#1a1816; color:var(--ink) }
+ .sq-fpop input { accent-color:var(--gold); margin:0 }
+ /* field-hide: body.sqhf-<field> hides that element in grid mode */
+ body.sqhf-img .grid:not(.list) .card img { display:none }
+ body.sqhf-badge .grid:not(.list) .card .badge { display:none }
+ body.sqhf-dig .grid:not(.list) .card .dig { display:none }
+ body.sqhf-lbl .grid:not(.list) .card .lbl { display:none }
</style></head>
<body>
<header class="top">
@@ -196,6 +225,19 @@
<span class="spacer"></span>
+ <div class="group">
+ <button class="small" id="sq-viewgrid" title="Grid view" onclick="setSqView('grid')">⊞ Grid</button>
+ <button class="small" id="sq-viewlist" title="List view" onclick="setSqView('list')">☰ List</button>
+ <div class="sq-fieldmenu">
+ <button class="sq-fbtn" id="sq-fieldbtn">Fields ▾</button>
+ <div class="sq-fpop" id="sq-fpop" hidden>
+ <label><input type="checkbox" data-sqf="img" checked> Image</label>
+ <label><input type="checkbox" data-sqf="badge" checked> Status badge</label>
+ <label><input type="checkbox" data-sqf="dig" checked> DIG / SKU</label>
+ <label><input type="checkbox" data-sqf="lbl" checked> Title / sub</label>
+ </div>
+ </div>
+ </div>
<div class="group" id="bulk-actions">
<button class="pub" id="b-pub" onclick="bulk('publish')" disabled>✓ Publish</button>
<button id="b-unpub" onclick="bulk('unpublish')" disabled>↻ Unpublish</button>
@@ -823,6 +865,46 @@ async function bulk(action) {
}
}
+// ── VIEW TOGGLE ──────────────────────────────────────────────────────
+function setSqView(v) {
+ document.body.classList.toggle('viewlist', v === 'list');
+ // Add/remove .list on the rendered grid
+ const g = $('#grid-wrap .grid'); if (g) g.classList.toggle('list', v === 'list');
+ $('#sq-viewgrid').style.color = v === 'grid' ? 'var(--gold)' : '';
+ $('#sq-viewgrid').style.borderColor = v === 'grid' ? 'var(--gold)' : '';
+ $('#sq-viewlist').style.color = v === 'list' ? 'var(--gold)' : '';
+ $('#sq-viewlist').style.borderColor = v === 'list' ? 'var(--gold)' : '';
+ try { localStorage.setItem('4sq.view', v); } catch(_) {}
+}
+// Re-apply list class after each grid re-render
+const _origRenderGrid = renderGrid;
+// patch renderGrid: apply list class after
+const _sqPatchRender = () => {
+ const g = $('#grid-wrap .grid'); if (!g) return;
+ if (document.body.classList.contains('viewlist')) g.classList.add('list');
+};
+// Override renderGrid to patch after the original
+window.renderGrid = function() { _origRenderGrid(); _sqPatchRender(); };
+
+// ── FIELDS MENU ───────────────────────────────────────────────────────
+const SQF_DEF = { img:true, badge:true, dig:true, lbl:true };
+let SQF = Object.assign({}, SQF_DEF);
+try { const s = localStorage.getItem('4sq.fields'); if (s) SQF = Object.assign({}, SQF_DEF, JSON.parse(s)); } catch(_) {}
+function applySqFields() {
+ for (const k of Object.keys(SQF_DEF)) document.body.classList.toggle('sqhf-'+k, !SQF[k]);
+ document.querySelectorAll('#sq-fpop input[data-sqf]').forEach(cb => { cb.checked = !!SQF[cb.dataset.sqf]; });
+ try { localStorage.setItem('4sq.fields', JSON.stringify(SQF)); } catch(_) {}
+}
+document.querySelectorAll('#sq-fpop input[data-sqf]').forEach(cb => {
+ cb.addEventListener('change', () => { SQF[cb.dataset.sqf] = cb.checked; applySqFields(); });
+});
+applySqFields();
+$('#sq-fieldbtn').addEventListener('click', e => { e.stopPropagation(); $('#sq-fpop').hidden = !$('#sq-fpop').hidden; });
+document.addEventListener('click', e => { if (!e.target.closest('.sq-fieldmenu')) { const p = $('#sq-fpop'); if (p) p.hidden = true; } });
+
+// Restore saved view
+setSqView(localStorage.getItem('4sq.view') || 'grid');
+
// ── controls wiring ──────────────────────────────────────────────────
$('#sort').addEventListener('change', e => {
localStorage.setItem(LS.sort, e.target.value);
← d9f2548 chore: macstudio3 migration — reconcile from mac2 + repoint
·
back to 4square Admin
·
TK-11046 WS-C: exclude DELETED_FROM_SHOPIFY from counts + de 7e2e777 →