diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-08 06:44:56 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-08 06:44:56 -0500 |
| commit | 3e8d5651a3fddcf4afccd46a382ab12d915bbd8c (patch) | |
| tree | 63a94f57df1a31362ba4475d289d57571a1d5f0d | |
| parent | 9bdaf852df922e863307512b043ed75dd930a671 (diff) | |
| download | dotemacs-3e8d5651a3fddcf4afccd46a382ab12d915bbd8c.tar.gz dotemacs-3e8d5651a3fddcf4afccd46a382ab12d915bbd8c.zip | |
feat(theme-selector): sortable package and UI face tables
The package table (face, fg, bg, inherit, size, contrast) and the UI table (face, foreground, background) now sort on a header click, like the code/color assignments table. A generic comparator reads a select value, a numeric input, or cell text (numeric when it leads with a number, so size and contrast sort by value). The sort is remembered per table and re-applied after a rebuild, so editing a face no longer resets the order.
| -rw-r--r-- | scripts/theme-selector/generate.py | 14 | ||||
| -rw-r--r-- | scripts/theme-selector/theme-selector.html | 14 |
2 files changed, 24 insertions, 4 deletions
diff --git a/scripts/theme-selector/generate.py b/scripts/theme-selector/generate.py index ed7cddb1..8e76c174 100644 --- a/scripts/theme-selector/generate.py +++ b/scripts/theme-selector/generate.py @@ -481,7 +481,7 @@ HTML = """<!doctype html><meta charset=utf-8><title>theme-selector</title> <h1>ui faces</h1> <div class="cols stretch"> <section class="pane"> - <table class="leg" id="uitable"><thead><tr><th>face</th><th>foreground</th><th>background</th><th>style</th><th>preview</th></tr></thead><tbody id="uibody"></tbody></table> + <table class="leg" id="uitable"><thead><tr><th onclick="srtTable('uibody',0)">face △</th><th onclick="srtTable('uibody',1)">foreground △</th><th onclick="srtTable('uibody',2)">background △</th><th>style</th><th>preview</th></tr></thead><tbody id="uibody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> <div class="langbar"><label style="color:#b4b1a2">live buffer preview</label></div> @@ -496,7 +496,7 @@ HTML = """<!doctype html><meta charset=utf-8><title>theme-selector</title> </div> <div class="cols stretch"> <section class="pane"> - <table class="leg" id="pkgtable"><thead><tr><th>face</th><th>fg</th><th>bg</th><th>style</th><th>inherit</th><th>size</th><th>contrast</th><th></th></tr></thead><tbody id="pkgbody"></tbody></table> + <table class="leg" id="pkgtable"><thead><tr><th onclick="srtTable('pkgbody',0)">face △</th><th onclick="srtTable('pkgbody',1)">fg △</th><th onclick="srtTable('pkgbody',2)">bg △</th><th>style</th><th onclick="srtTable('pkgbody',4)">inherit △</th><th onclick="srtTable('pkgbody',5)">size △</th><th onclick="srtTable('pkgbody',6)">contrast △</th><th></th></tr></thead><tbody id="pkgbody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> <div class="langbar"><label id="pkgprevlabel" style="color:#b4b1a2">preview</label></div> @@ -750,6 +750,7 @@ function buildPkgTable(){ const cr=document.createElement('td');const rb=document.createElement('button');rb.className='sbtn';rb.textContent='↺';rb.title='reset to default';rb.onclick=()=>{PKGMAP[app][face]=seedFace(def);pkgChanged();};cr.appendChild(rb); tr.append(c0,cf,cb,cw,ci,ch,cc,cr);tb.appendChild(tr); } + applyTableSort('pkgbody'); } function ofs(app,face){const f=PKGMAP[app][face]||{},fg=pkgEffFg(app,face)||MAP['p'],bg=pkgEffBg(app,face);const dec=(f.underline?'underline ':'')+(f.strike?'line-through':'');return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'};font-size:${(f.height||1)}em`;} function os(app,face,txt){return `<span data-face="${face}" style="${ofs(app,face)}">${txt}</span>`;} @@ -1064,12 +1065,21 @@ function buildUITable(){ const cP=document.createElement('td');cP.className='ex';cP.id='uiprev-'+face;cP.textContent=ex;cP.style.padding='4px 10px';cP.style.borderRadius='4px'; tr.appendChild(c0);tr.appendChild(cF);tr.appendChild(cB);tr.appendChild(cS);tr.appendChild(cP);tb.appendChild(tr);paintUI(face); } + applyTableSort('uibody'); } let D={}; function srt(c){const tb=document.getElementById('legbody');const r=[...tb.rows];D[c]=!D[c]; r.sort((a,b)=>{const x=(c===0?a.querySelector('select').value:a.cells[0].innerText).toLowerCase(), y=(c===0?b.querySelector('select').value:b.cells[0].innerText).toLowerCase(); return (x<y?-1:x>y?1:0)*(D[c]?1:-1);});r.forEach(x=>tb.appendChild(x));} +// Generic header-click sort for the package and UI tables. Reads a select +// value, a numeric input, or cell text (numeric when the text leads with a +// number, e.g. contrast or size). The sort is remembered per table and +// re-applied after a rebuild so editing a face does not reset it. +let tableSort={}; +function cellVal(td){if(!td)return '';const s=td.querySelector('select');if(s)return s.value.toLowerCase();const i=td.querySelector('input');if(i)return parseFloat(i.value)||0;const t=td.innerText.trim();const n=parseFloat(t);return (!isNaN(n)&&/^[-\\d.]/.test(t))?n:t.toLowerCase();} +function srtTable(tbId,col){tableSort[tbId]={col,asc:!(tableSort[tbId]&&tableSort[tbId].col===col&&tableSort[tbId].asc)};applyTableSort(tbId);} +function applyTableSort(tbId){const s=tableSort[tbId];if(!s)return;const tb=document.getElementById(tbId);if(!tb)return;const dir=s.asc?1:-1;const r=[...tb.rows];r.sort((a,b)=>{const x=cellVal(a.cells[s.col]),y=cellVal(b.cells[s.col]);return ((typeof x==='number'&&typeof y==='number')?x-y:(x<y?-1:x>y?1:0))*dir;});r.forEach(x=>tb.appendChild(x));} buildLangSel();buildAppSel();renderPalette();buildTable();buildUITable();renderCode();applyGround();updateTitle();initPicker();buildPkgTable();buildPkgPreview();syncMockHeight();syncPkgHeight(); addEventListener('resize',()=>{syncMockHeight();syncPkgHeight();}); // Phase-1 self-test (open with #selftest): seed -> export -> import -> compare. diff --git a/scripts/theme-selector/theme-selector.html b/scripts/theme-selector/theme-selector.html index b64fcded..30279c74 100644 --- a/scripts/theme-selector/theme-selector.html +++ b/scripts/theme-selector/theme-selector.html @@ -110,7 +110,7 @@ <h1>ui faces</h1> <div class="cols stretch"> <section class="pane"> - <table class="leg" id="uitable"><thead><tr><th>face</th><th>foreground</th><th>background</th><th>style</th><th>preview</th></tr></thead><tbody id="uibody"></tbody></table> + <table class="leg" id="uitable"><thead><tr><th onclick="srtTable('uibody',0)">face △</th><th onclick="srtTable('uibody',1)">foreground △</th><th onclick="srtTable('uibody',2)">background △</th><th>style</th><th>preview</th></tr></thead><tbody id="uibody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> <div class="langbar"><label style="color:#b4b1a2">live buffer preview</label></div> @@ -125,7 +125,7 @@ </div> <div class="cols stretch"> <section class="pane"> - <table class="leg" id="pkgtable"><thead><tr><th>face</th><th>fg</th><th>bg</th><th>style</th><th>inherit</th><th>size</th><th>contrast</th><th></th></tr></thead><tbody id="pkgbody"></tbody></table> + <table class="leg" id="pkgtable"><thead><tr><th onclick="srtTable('pkgbody',0)">face △</th><th onclick="srtTable('pkgbody',1)">fg △</th><th onclick="srtTable('pkgbody',2)">bg △</th><th>style</th><th onclick="srtTable('pkgbody',4)">inherit △</th><th onclick="srtTable('pkgbody',5)">size △</th><th onclick="srtTable('pkgbody',6)">contrast △</th><th></th></tr></thead><tbody id="pkgbody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> <div class="langbar"><label id="pkgprevlabel" style="color:#b4b1a2">preview</label></div> @@ -379,6 +379,7 @@ function buildPkgTable(){ const cr=document.createElement('td');const rb=document.createElement('button');rb.className='sbtn';rb.textContent='↺';rb.title='reset to default';rb.onclick=()=>{PKGMAP[app][face]=seedFace(def);pkgChanged();};cr.appendChild(rb); tr.append(c0,cf,cb,cw,ci,ch,cc,cr);tb.appendChild(tr); } + applyTableSort('pkgbody'); } function ofs(app,face){const f=PKGMAP[app][face]||{},fg=pkgEffFg(app,face)||MAP['p'],bg=pkgEffBg(app,face);const dec=(f.underline?'underline ':'')+(f.strike?'line-through':'');return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'};font-size:${(f.height||1)}em`;} function os(app,face,txt){return `<span data-face="${face}" style="${ofs(app,face)}">${txt}</span>`;} @@ -693,12 +694,21 @@ function buildUITable(){ const cP=document.createElement('td');cP.className='ex';cP.id='uiprev-'+face;cP.textContent=ex;cP.style.padding='4px 10px';cP.style.borderRadius='4px'; tr.appendChild(c0);tr.appendChild(cF);tr.appendChild(cB);tr.appendChild(cS);tr.appendChild(cP);tb.appendChild(tr);paintUI(face); } + applyTableSort('uibody'); } let D={}; function srt(c){const tb=document.getElementById('legbody');const r=[...tb.rows];D[c]=!D[c]; r.sort((a,b)=>{const x=(c===0?a.querySelector('select').value:a.cells[0].innerText).toLowerCase(), y=(c===0?b.querySelector('select').value:b.cells[0].innerText).toLowerCase(); return (x<y?-1:x>y?1:0)*(D[c]?1:-1);});r.forEach(x=>tb.appendChild(x));} +// Generic header-click sort for the package and UI tables. Reads a select +// value, a numeric input, or cell text (numeric when the text leads with a +// number, e.g. contrast or size). The sort is remembered per table and +// re-applied after a rebuild so editing a face does not reset it. +let tableSort={}; +function cellVal(td){if(!td)return '';const s=td.querySelector('select');if(s)return s.value.toLowerCase();const i=td.querySelector('input');if(i)return parseFloat(i.value)||0;const t=td.innerText.trim();const n=parseFloat(t);return (!isNaN(n)&&/^[-\d.]/.test(t))?n:t.toLowerCase();} +function srtTable(tbId,col){tableSort[tbId]={col,asc:!(tableSort[tbId]&&tableSort[tbId].col===col&&tableSort[tbId].asc)};applyTableSort(tbId);} +function applyTableSort(tbId){const s=tableSort[tbId];if(!s)return;const tb=document.getElementById(tbId);if(!tb)return;const dir=s.asc?1:-1;const r=[...tb.rows];r.sort((a,b)=>{const x=cellVal(a.cells[s.col]),y=cellVal(b.cells[s.col]);return ((typeof x==='number'&&typeof y==='number')?x-y:(x<y?-1:x>y?1:0))*dir;});r.forEach(x=>tb.appendChild(x));} buildLangSel();buildAppSel();renderPalette();buildTable();buildUITable();renderCode();applyGround();updateTitle();initPicker();buildPkgTable();buildPkgPreview();syncMockHeight();syncPkgHeight(); addEventListener('resize',()=>{syncMockHeight();syncPkgHeight();}); // Phase-1 self-test (open with #selftest): seed -> export -> import -> compare. |
