From afd2ddad818cdbf9f4b77d43efb91c35b6c57946 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 16 Jun 2026 05:10:38 -0500 Subject: feat(theme-studio): alphabetize packages in the assignment dropdown The assignment-view dropdown listed package faces in APPS build order (bespoke apps first, then inventory). generate.py builds them that way, so the list wasn't alphabetical. I added a pure appViewKeysSorted helper that orders the app keys by display label, and buildViewSel uses it. The @code and @ui editor entries above the divider are unchanged. --- scripts/theme-studio/theme-studio.html | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'scripts/theme-studio/theme-studio.html') diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index deaba24f2..5501135b5 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -911,6 +911,16 @@ function spanNeighborHex(cur,palette,ground,dir){ } return null; } + +// The package apps for the assignment-view dropdown, keyed and sorted by display +// label (case-insensitive). generate.py builds APPS as bespoke apps first then +// inventory apps, so the raw key order isn't alphabetical; this orders the list +// the reader scans. An app missing a label falls back to its key. +function appViewKeysSorted(apps){ + return Object.keys(apps||{}).sort((a,b)=> + String((apps[a]&&apps[a].label)||a).localeCompare( + String((apps[b]&&apps[b].label)||b), undefined, {sensitivity:'base'})); +} // Pure color/UI-boundary helpers (normHex, ratingColor, textOn), inlined from // app-util.js. textOn uses rl from the colormath core above. // Pure color/UI-boundary helpers: hex-input parsing, the contrast-rating status @@ -2083,14 +2093,14 @@ function curApp(){const s=document.getElementById('viewsel');const v=s&&s.value; function pkgEffFg(app,face,seen){return effResolve(PKGMAP,app,face,'fg',seen);} function pkgEffBg(app,face,seen){return effResolve(PKGMAP,app,face,'bg',seen);} // One dropdown drives the whole assignment panel: two editor entries (@code, -// @ui) then a non-selectable "package faces" optgroup holding every app, in -// APPS order. onViewChange shows exactly one of the three view blocks. +// @ui) then a non-selectable "package faces" optgroup holding every app, +// alphabetically by label. onViewChange shows exactly one of the three view blocks. function buildViewSel(){const s=document.getElementById('viewsel');if(!s)return;s.innerHTML=''; const mk=(v,t)=>{const o=document.createElement('option');o.value=v;o.textContent=t;return o;}; s.appendChild(mk('@code','color/code assignments')); s.appendChild(mk('@ui','ui faces')); const og=document.createElement('optgroup');og.label='package faces'; - for(const app in APPS)og.appendChild(mk(app,APPS[app].label)); + for(const app of appViewKeysSorted(APPS))og.appendChild(mk(app,APPS[app].label)); s.appendChild(og);} function onViewChange(){const s=document.getElementById('viewsel');const v=(s&&s.value)||'@code'; const show=(id,on)=>{const e=document.getElementById(id);if(e)e.style.display=on?'':'none';}; @@ -3283,8 +3293,8 @@ if(location.hash==='#roundtriptest'){let ok=true;const notes=[];const A=(c,n)=>{ const d=document.createElement('div');d.id='roundtriptest';d.textContent='ROUNDTRIPTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} // View-selector gate (open with #viewtest): the assignment panel is driven by a // single #viewsel dropdown -- two editor entries (@code, @ui) then a "package -// faces" optgroup of every app, in order -- and switching it shows exactly one -// of the three view blocks. +// faces" optgroup of every app, alphabetically by label -- and switching it +// shows exactly one of the three view blocks. if(location.hash==='#viewtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; const sel=document.getElementById('viewsel'); A(!!sel,'viewsel-exists'); @@ -3294,7 +3304,7 @@ if(location.hash==='#viewtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c const og=sel.querySelector('optgroup'); A(og&&og.label==='package faces','package-faces-optgroup'); if(og){const appOpts=[...og.querySelectorAll('option')].map(o=>o.value); - A(JSON.stringify(appOpts)===JSON.stringify(Object.keys(APPS)),'optgroup-lists-apps-in-order');} + A(JSON.stringify(appOpts)===JSON.stringify(appViewKeysSorted(APPS)),'optgroup-lists-apps-alphabetically');} const vis=id=>{const e=document.getElementById(id);return !!e&&e.style.display!=='none';}; sel.value='@code';onViewChange(); A(vis('view-code')&&!vis('view-ui')&&!vis('view-pkg'),'code-view-only'); -- cgit v1.2.3