[object Object]

← back to Catalog Crawl Atlas

viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer

883813948df6c85affbbd565f30b686f4b7fe89c · 2026-08-12 08:53:19 -0700 · Steve

Files touched

Diff

commit 883813948df6c85affbbd565f30b686f4b7fe89c
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 12 08:53:19 2026 -0700

    viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
---
 public/index.html | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 63 insertions(+), 1 deletion(-)

diff --git a/public/index.html b/public/index.html
index 48613d3..31831bd 100644
--- a/public/index.html
+++ b/public/index.html
@@ -93,7 +93,31 @@ svg{width:100%;height:100%;display:block}
 .pctl .mono{font-family:'Share Tech Mono',monospace;letter-spacing:0}
 .pctl select{font:inherit;font-size:12px;color:var(--cream);background:#14151a;border:1px solid var(--edge);border-radius:8px;padding:5px 9px}
 .pctl input[type=range]{width:150px;accent-color:var(--amber)}
+/* view toggle */
+.vbtn{font-size:11px;letter-spacing:.5px;padding:4px 10px;border-radius:6px;border:1px solid var(--edge);background:#14151a;color:var(--muted);cursor:pointer}
+.vbtn.on{background:var(--amber);border-color:var(--amber);color:#0c0d10}
+/* fields menu */
+.ca-fieldmenu{position:relative}
+.ca-fbtn{font-size:11px;letter-spacing:.5px;padding:4px 10px;border-radius:6px;border:1px solid var(--edge);background:#14151a;color:var(--muted);cursor:pointer}
+.ca-fbtn:hover{border-color:var(--amber);color:var(--cream)}
+.ca-fpop{position:absolute;top:calc(100% + 6px);right:0;background:#14151a;border:1px solid var(--edge);border-radius:10px;box-shadow:0 10px 28px rgba(0,0,0,.7);padding:8px 4px;z-index:30;min-width:160px}
+.ca-fpop[hidden]{display:none}
+.ca-fpop label{display:flex;align-items:center;gap:8px;padding:6px 12px;font-size:11px;letter-spacing:.4px;color:var(--muted);cursor:pointer;white-space:nowrap}
+.ca-fpop label:hover{color:var(--cream)}
+.ca-fpop input{accent-color:var(--amber);width:13px;height:13px;margin:0;padding:0}
+/* grid view */
 .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--card,150px),1fr));gap:12px;padding:16px 22px 40px}
+/* list view */
+.grid.list{display:flex;flex-direction:column;gap:6px;padding:12px 22px 40px}
+.grid.list .card{flex-direction:row;align-items:center;border-radius:8px;min-height:54px}
+.grid.list .card img{width:54px;min-width:54px;height:54px;aspect-ratio:auto;object-fit:cover;border-radius:6px 0 0 6px}
+.grid.list .card .cap{flex:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:8px 12px}
+.grid.list .card .ct{max-height:none;font-size:13px;flex:1;min-width:120px}
+.grid.list .card .tags{margin-top:0}
+/* field visibility */
+body.ca-hf-image .card img{display:none!important}
+body.ca-hf-title [data-f="title"]{display:none!important}
+body.ca-hf-tags [data-f="tags"]{display:none!important}
 .card{background:#121318;border:1px solid var(--edge);border-radius:12px;overflow:hidden;text-decoration:none;color:var(--cream);transition:.14s;display:flex;flex-direction:column}
 .card:hover{border-color:var(--amber);transform:translateY(-2px);box-shadow:0 8px 22px rgba(0,0,0,.6)}
 .card img{width:100%;aspect-ratio:1;object-fit:cover;background:#1c1d22;display:block}
@@ -149,6 +173,13 @@ svg{width:100%;height:100%;display:block}
           <span class="pl">Density</span>
           <input type="range" id="densRange" min="100" max="340" step="10">
           <span class="pl mono" id="densVal"></span>
+          <span class="pl" style="margin-left:10px">View</span>
+          <button class="vbtn on" id="caVGrid" title="Grid view">⊞ Grid</button>
+          <button class="vbtn" id="caVList" title="List view">☰ List</button>
+          <div class="ca-fieldmenu">
+            <button class="ca-fbtn" id="caFieldsBtn" aria-haspopup="true" aria-expanded="false">Fields ▾</button>
+            <div class="ca-fpop" id="caFpop" hidden role="menu"></div>
+          </div>
         </div>
         <div class="grid" id="grid"></div>
       </div>
@@ -272,6 +303,26 @@ function sortRows(rows){
   const idx={title:1,color:3,style:4,motif:5,material:6}[sortMode];
   return rows.slice().sort((a,b)=>String(a[idx]||'~').localeCompare(String(b[idx]||'~')));
 }
+// ── Grid / List + Fields ──
+const CA_FIELDS=[['image','Image'],['title','Title'],['tags','Tags']];
+const CA_FIELDS_DEF=Object.fromEntries(CA_FIELDS.map(([k])=>[k,true]));
+let caFieldVis=Object.assign({},CA_FIELDS_DEF,JSON.parse(localStorage.getItem('ca_fields')||'{}'));
+function applyCaFields(){
+  CA_FIELDS.forEach(([k])=>document.body.classList.toggle('ca-hf-'+k,!caFieldVis[k]));
+  document.querySelectorAll('#caFpop input[data-field]').forEach(cb=>cb.checked=!!caFieldVis[cb.dataset.field]);
+  localStorage.setItem('ca_fields',JSON.stringify(caFieldVis));
+}
+function buildCaFieldMenu(){
+  const fp=document.getElementById('caFpop');
+  fp.innerHTML=CA_FIELDS.map(([k,lbl])=>`<label><input type="checkbox" data-field="${k}"${caFieldVis[k]?' checked':''}> ${lbl}</label>`).join('');
+  fp.querySelectorAll('input').forEach(cb=>cb.addEventListener('change',()=>{caFieldVis[cb.dataset.field]=cb.checked;localStorage.setItem('ca_fields',JSON.stringify(caFieldVis));applyCaFields();}));
+}
+function setCaView(v){
+  document.getElementById('grid').classList.toggle('list',v==='list');
+  document.getElementById('caVGrid').classList.toggle('on',v==='grid');
+  document.getElementById('caVList').classList.toggle('on',v==='list');
+  localStorage.setItem('ca_view',v);
+}
 function initControls(){
   const ss=document.getElementById('sortSel'), dr=document.getElementById('densRange'), dv=document.getElementById('densVal');
   ss.value=sortMode;
@@ -280,6 +331,17 @@ function initControls(){
   const applyDens=v=>{ document.getElementById('grid').style.setProperty('--card',v+'px'); dv.textContent=v+'px'; };
   dr.value=dens; applyDens(dens);
   dr.oninput=()=>{ applyDens(+dr.value); localStorage.setItem('ca_dens',dr.value); };
+  // view + fields
+  document.getElementById('caVGrid').onclick=()=>setCaView('grid');
+  document.getElementById('caVList').onclick=()=>setCaView('list');
+  setCaView(localStorage.getItem('ca_view')||'grid');
+  buildCaFieldMenu();
+  applyCaFields();
+  // fields popover open/close
+  const fb=document.getElementById('caFieldsBtn'), fp=document.getElementById('caFpop');
+  fb.addEventListener('click',e=>{e.stopPropagation();const h=fp.hidden;fp.hidden=!h;fb.setAttribute('aria-expanded',String(h));});
+  document.addEventListener('click',()=>{fp.hidden=true;fb.setAttribute('aria-expanded','false');});
+  fp.addEventListener('click',e=>e.stopPropagation());
 }
 function colorHex(v){ const n=G.nodes['color:'+v]; return n?n.hex:'#888'; }
 function applyFilter(){
@@ -318,7 +380,7 @@ function renderMore(){
     const admin=`https://admin.shopify.com/store/designer-laboratory-sandbox/products/${g}`;
     return `<a class="card" href="${admin}" target="_blank" rel="noopener">
       <img loading="lazy" src="${img(im,240)}" alt="">
-      <div class="cap"><div class="ct">${esc(t)}</div><div class="tags">${tags}</div></div></a>`;
+      <div class="cap"><div class="ct" data-f="title">${esc(t)}</div><div class="tags" data-f="tags">${tags}</div></div></a>`;
   }).join('');
   grid.insertAdjacentHTML('beforeend',html);
   gridState.shown+=slice.length;

← 69a677b Atlas refine: filter-aware neighbors (spokes show +N within  ·  back to Catalog Crawl Atlas  ·  nav-agent: universal grid-controls drop-in on internal dashb b4b5a8d →