[object Object]

← back to Wallco Ai

elements: add Sort (newest/oldest/category/color-by-hue/id↑↓ + Shuffled) coexisting with the Shuffle button; persists, restored sort overrides cold-start shuffle [yolo tick6/N8, local-only]

82cf88b834aa4732309bb1c9d8b44ad7e7a226f7 · 2026-06-02 13:38:07 -0700 · Steve Abrams

Files touched

Diff

commit 82cf88b834aa4732309bb1c9d8b44ad7e7a226f7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 2 13:38:07 2026 -0700

    elements: add Sort (newest/oldest/category/color-by-hue/id↑↓ + Shuffled) coexisting with the Shuffle button; persists, restored sort overrides cold-start shuffle [yolo tick6/N8, local-only]
---
 public/admin/elements.html | 49 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 47 insertions(+), 2 deletions(-)

diff --git a/public/admin/elements.html b/public/admin/elements.html
index 255cfee..7bd9b04 100644
--- a/public/admin/elements.html
+++ b/public/admin/elements.html
@@ -242,6 +242,17 @@
     <label>Search</label>
     <input type="text" id="f-search" placeholder="id, category, or hex…" style="min-width:200px">
 
+    <label>Sort</label>
+    <select id="f-sort">
+      <option value="shuffle">Shuffled</option>
+      <option value="newest">Newest</option>
+      <option value="oldest">Oldest</option>
+      <option value="category">Category A→Z</option>
+      <option value="color">Color (by hue)</option>
+      <option value="id">ID ↑</option>
+      <option value="id_desc">ID ↓</option>
+    </select>
+
     <label style="margin-left:auto">Density</label>
     <input id="density-slider" type="range" min="140" max="380" step="20" value="220">
     <span class="density-val" id="density-val">220</span>
@@ -278,8 +289,31 @@
     filter: { category:'', colorway:'', density:'', quality:true, search:'' },
     cardMin: parseInt(localStorage.getItem('el-card-min') || '220', 10),
     shuffleSeed: 0,
+    sort: localStorage.getItem('el-sort') || 'shuffle',
   };
 
+  // Sort the view (Steve standing rule). dominant_hex exists → color-by-hue is available.
+  function hexHue(hex) {
+    const m = /^#?([0-9a-fA-F]{6})$/.exec(hex || ''); if (!m) return 1e6;
+    const n = parseInt(m[1], 16), r = (n >> 16 & 255) / 255, g = (n >> 8 & 255) / 255, b = (n & 255) / 255;
+    const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min; let h = 0;
+    if (d === 0) h = 0; else if (max === r) h = ((g - b) / d) % 6; else if (max === g) h = (b - r) / d + 2; else h = (r - g) / d + 4;
+    h *= 60; if (h < 0) h += 360;
+    const s = max === 0 ? 0 : d / max;
+    if (s < 0.12) return 1e5 + (1 - max);
+    return h;
+  }
+  function sortView(arr) {
+    const r = arr.slice(), m = state.sort;
+    if (m === 'newest')        r.sort((a, b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+    else if (m === 'oldest')   r.sort((a, b) => String(a.created_at || '').localeCompare(String(b.created_at || '')));
+    else if (m === 'category') r.sort((a, b) => categoryOf(a).localeCompare(categoryOf(b)) || (b.id - a.id));
+    else if (m === 'color')    r.sort((a, b) => hexHue(a.dominant_hex) - hexHue(b.dominant_hex) || (b.id - a.id));
+    else if (m === 'id')       r.sort((a, b) => a.id - b.id);
+    else if (m === 'id_desc')  r.sort((a, b) => b.id - a.id);
+    return r;
+  }
+
   function applyCardMin(px) {
     document.documentElement.style.setProperty('--card-min', px + 'px');
     document.getElementById('density-val').textContent = px;
@@ -364,6 +398,7 @@
     const grid = document.getElementById('grid');
     let view = state.filtered;
     if (state.shuffleSeed) view = shuffleList(view, state.shuffleSeed);
+    else if (state.sort !== 'shuffle') view = sortView(view);
     view = view.slice(0, PER);
 
     if (view.length === 0) {
@@ -508,7 +543,16 @@
     localStorage.setItem('el-card-min', String(state.cardMin));
     applyCardMin(state.cardMin);
   });
+  document.getElementById('f-sort').addEventListener('change', e => {
+    state.sort = e.target.value;
+    localStorage.setItem('el-sort', state.sort);
+    state.shuffleSeed = 0;           // an explicit sort overrides the random shuffle
+    render();
+  });
   document.getElementById('shuffle-btn').addEventListener('click', () => {
+    state.sort = 'shuffle';
+    document.getElementById('f-sort').value = 'shuffle';
+    localStorage.setItem('el-sort', 'shuffle');
     state.shuffleSeed = Date.now() & 0xffffffff;
     render();
   });
@@ -553,6 +597,7 @@
   // Boot
   applyCardMin(state.cardMin);
   document.getElementById('density-slider').value = state.cardMin;
+  document.getElementById('f-sort').value = state.sort;
 
   fetch('/api/admin/elements/items')
     .then(r => r.json())
@@ -566,8 +611,8 @@
       document.getElementById('stat-baked').textContent = state.items.filter(i => i.element_motif_path).length;
       document.getElementById('stat-clean').textContent = state.items.length;
       populateFilterOptions();
-      // Cold-start: Shuffle 12.
-      state.shuffleSeed = Date.now() & 0xffffffff;
+      // Cold-start shuffle only when no explicit sort is persisted (default 'shuffle').
+      if (state.sort === 'shuffle') state.shuffleSeed = Date.now() & 0xffffffff;
       applyFilters();
     })
     .catch(err => {

← a375967 etsy-bucket: add Sort (newest/oldest/id↑↓/dpi) + persist; fo  ·  back to Wallco Ai  ·  yolo-backlog: T6 — N7 etsy-bucket + N8 elements done, LOOP C 9b4f2b9 →