diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-13 16:16:29 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-13 16:16:29 -0500 |
| commit | a71d8e0eaf7a26805a261c8854636d7d13d084ca (patch) | |
| tree | 912f88ff8da136dafc7c08ff2a4535d779e5d811 /scripts/theme-studio/app.js | |
| parent | 02dfe94eba40ae351a257656b5563c98a82a4b37 (diff) | |
| download | dotemacs-a71d8e0eaf7a26805a261c8854636d7d13d084ca.tar.gz dotemacs-a71d8e0eaf7a26805a261c8854636d7d13d084ca.zip | |
Add theme studio column selection and reorder controls
Diffstat (limited to 'scripts/theme-studio/app.js')
| -rw-r--r-- | scripts/theme-studio/app.js | 58 |
1 files changed, 54 insertions, 4 deletions
diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index db544e33..f38df6bb 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -222,6 +222,49 @@ function paletteChip(i,nearest){ d.onclick=(e)=>{if(e.target.closest('.rm')||e.target.closest('.nm'))return;selectColor(i);}; return d; } +function paletteIndexByHexName(hex,name){ + for(let i=0;i<PALETTE.length;i++)if(PALETTE[i][0]===hex&&PALETTE[i][1]===name)return i; + return -1; +} +function selectColumnBase(f){ + const baseMember=f.members.find(m=>m.hex.toLowerCase()===f.base.toLowerCase())||f.members[0]; + const i=paletteIndexByHexName(baseMember.hex,baseMember.name); + if(i>=0)selectColor(i); +} +function isGroundEntry(entry){ + const [hex,name]=entry; + const lower=(hex||'').toLowerCase(); + return lower===MAP['bg'].toLowerCase()||lower===MAP['p'].toLowerCase()||/^ground-\d+$/i.test(name||''); +} +function moveColumn(columnId,dir){ + normalizePalette(); + const columns=sortColumns(columnsFromPalette(PALETTE,{bg:MAP['bg'],fg:MAP['p']}).columns); + const pos=columns.findIndex(f=>f.column===columnId); + const next=columns[pos+dir]; + if(pos<0||!next)return; + const moving=[],rest=[]; + PALETTE.forEach(entry=>{ + if(!isGroundEntry(entry)&&columnIdOf(entry)===columnId)moving.push(entry); + else rest.push(entry); + }); + const nextPositions=[]; + rest.forEach((entry,i)=>{if(!isGroundEntry(entry)&&columnIdOf(entry)===next.column)nextPositions.push(i);}); + if(!nextPositions.length)return; + const at=dir<0?nextPositions[0]:nextPositions[nextPositions.length-1]+1; + PALETTE=rest.slice(0,at).concat(moving,rest.slice(at)); + selectedIdx=null;renderPalette();buildTable();buildUITable();renderCode();applyGround(); + notify('moved "'+columnId+'" '+(dir<0?'left':'right'),false); +} +function columnHeader(f,position,count){ + const h=document.createElement('div');h.className='fhead'; + const label=(f.members.find(m=>m.hex.toLowerCase()===f.base.toLowerCase())||{}).name||f.column||f.base; + h.innerHTML=`<button class="cmove left" title="move column left" ${position===0?'disabled':''}>‹</button><button class="ctitle" title="select base color"></button><button class="cmove right" title="move column right" ${position===count-1?'disabled':''}>›</button>`; + h.querySelector('.ctitle').textContent=label; + h.querySelector('.ctitle').onclick=()=>selectColumnBase(f); + h.querySelector('.left').onclick=(e)=>{e.stopPropagation();moveColumn(f.column,-1);}; + h.querySelector('.right').onclick=(e)=>{e.stopPropagation();moveColumn(f.column,1);}; + return h; +} // Render the palette as structural color columns: pinned ground column, then // first-seen palette columns. Grouping uses the stable column id stored on each // palette entry, so renaming a color never moves it. @@ -247,11 +290,10 @@ function renderPalette(){ // The too-similar warning stays on the full flat palette: a generated ramp's // steps are a stepL apart (well above the warning's ΔE threshold), so they never // trigger it, and any pair that does is a genuine near-duplicate worth flagging. - sortColumns(columns).forEach(f=>{ + const ordered=sortColumns(columns); + ordered.forEach((f,pos)=>{ const s=strip('');s.dataset.column=f.column||f.base; - const h=document.createElement('div');h.className='fhead'; - h.textContent=(f.members.find(m=>m.hex.toLowerCase()===f.base.toLowerCase())||{}).name||f.column||f.base; - s.appendChild(h); + s.appendChild(columnHeader(f,pos,ordered.length)); s.appendChild(columnCountControl(f)); f.members.forEach(m=>{const i=idxOf(m.hex,m.name);if(i>=0)s.appendChild(paletteChip(i,nearest));}); }); @@ -1242,6 +1284,14 @@ if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;con A(strips[0].querySelectorAll('.pchip').length===2,'ground column carries bg + fg endpoints'); A(!!strips[0].querySelector('.fhead + .fcount + .pchip'),'span control sits between header and tiles for ground'); A(strips.length>=4,'ground + red + blue + gray columns, got '+strips.length); + const blueHead=strips.find(s=>s.dataset.column==='blue')&&strips.find(s=>s.dataset.column==='blue').querySelector('.ctitle'); + A(!!blueHead,'normal column header has a selectable title'); + if(blueHead)blueHead.click(); + A(selectedIdx!==null&&PALETTE[selectedIdx][1]==='blue'&&document.getElementById('newhexstr').value.toLowerCase()==='#3a6ea5','clicking a column title selects its base color'); + const blueRight=strips.find(s=>s.dataset.column==='blue')&&strips.find(s=>s.dataset.column==='blue').querySelector('.cmove.right'); + if(blueRight)blueRight.click(); + const moved=[...document.querySelectorAll('#pals .fstrip')].map(s=>s.dataset.column); + A(moved.indexOf('blue')>moved.indexOf('gray'),'right arrow moves a color column after its neighbor'); const redChip=[...document.querySelectorAll('#pals .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='red'); A(!!redChip&&!!redChip.querySelector('.rm')&&!!redChip.querySelector('.nm'),'a column chip keeps remove + rename controls'); const redColumn=redChip&&redChip.closest('.fstrip').dataset.column; |
