diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-07 18:24:51 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-07 18:24:51 -0500 |
| commit | 964dc82c1de8d1675d13daf5bd4e358276095a09 (patch) | |
| tree | c043777e8a261ad7f32fe20146e855c93c6e31f7 /scripts/theme-selector | |
| parent | 94f1660e2f00d14e9b9e7a1221f6eee8d43b03ca (diff) | |
| download | dotemacs-964dc82c1de8d1675d13daf5bd4e358276095a09.tar.gz dotemacs-964dc82c1de8d1675d13daf5bd4e358276095a09.zip | |
fix(theme-selector): stop accidental duplicate colors when editing a value
Editing a color's value could create a new color instead of updating it. Enter in the hex or name field now applies to the selected color rather than adding, and Add color refuses a name that already exists with a noticeable inline notice, so re-saving an existing color can't silently spawn a duplicate. Picking from the native swatch focuses the hex field so Enter applies right after, and both add and update report what happened in the same notice line.
Diffstat (limited to 'scripts/theme-selector')
| -rw-r--r-- | scripts/theme-selector/generate.py | 24 | ||||
| -rw-r--r-- | scripts/theme-selector/theme-selector.html | 24 |
2 files changed, 32 insertions, 16 deletions
diff --git a/scripts/theme-selector/generate.py b/scripts/theme-selector/generate.py index 5eac534f..c15d63a1 100644 --- a/scripts/theme-selector/generate.py +++ b/scripts/theme-selector/generate.py @@ -62,6 +62,7 @@ HTML = """<!doctype html><meta charset=utf-8><title>theme-selector</title> .palctl input[type=text]::placeholder{color:#b4b1a2;opacity:1} .palctl input[type=color]{width:128px;height:58px;border:1px solid #00000060;border-radius:6px;padding:2px;cursor:pointer} .palctl button,.filebar button,.fbtn{background:#252321;color:#e8bd30;border:1px solid #3a3a3a;border-radius:4px;padding:6px 12px;font:10pt monospace;cursor:pointer} + #palmsg{font:10pt monospace;opacity:0;transition:opacity .35s;margin-left:6px} #export{width:100%;height:180px;margin-top:10px;background:#0d0b0a;color:#a4ac64;border:1px solid #252321;border-radius:6px;font:10pt monospace;padding:10px} .filebar{margin:6px 0 0;display:flex;gap:8px;align-items:center} #pagetitle{font-size:30px;color:#cdced1;font-weight:normal;border:none;margin:4px 0 18px;padding:0} @@ -84,11 +85,12 @@ HTML = """<!doctype html><meta charset=utf-8><title>theme-selector</title> <h1>palette</h1> <div class="pals" id="pals"></div> <div class="palctl"> - <input type="color" id="newhex" value="#888888" oninput="syncHex('swatch')"> - <input type="text" id="newhexstr" placeholder="#rrggbb" value="#888888" oninput="syncHex('text')" style="width:110px"> - <input type="text" id="newname" placeholder="name"> + <input type="color" id="newhex" value="#888888" oninput="syncHex('swatch')" onchange="document.getElementById('newhexstr').focus()"> + <input type="text" id="newhexstr" placeholder="#rrggbb" value="#888888" oninput="syncHex('text')" onkeydown="if(event.key==='Enter')applyEdit()" style="width:110px"> + <input type="text" id="newname" placeholder="name" onkeydown="if(event.key==='Enter')applyEdit()"> <button onclick="addColor()">+ add color</button> <button onclick="updateColor()">↻ update selected</button> + <span id="palmsg"></span> </div> </section> <section class="pane saveload"> @@ -193,21 +195,27 @@ function renderPalette(){ p.appendChild(d);}); buildUITable(); } -function selectColor(i){selectedIdx=i;const [hex,name]=PALETTE[i];document.getElementById('newhexstr').value=hex;document.getElementById('newhex').value=hex;document.getElementById('newname').value=name;renderPalette();} +function notify(msg,err){const m=document.getElementById('palmsg');if(!m)return;m.textContent=msg;m.style.color=err?'#cb6b4d':'#8a9496';m.style.opacity='1';clearTimeout(m._t);m._t=setTimeout(()=>{m.style.opacity='0';},err?4000:2800);} +function applyEdit(){if(selectedIdx!==null)updateColor();else addColor();} +function selectColor(i){selectedIdx=i;const [hex,name]=PALETTE[i];document.getElementById('newhexstr').value=hex;document.getElementById('newhex').value=hex;document.getElementById('newname').value=name;renderPalette();notify('editing "'+name+'" — change the value, then Enter (or Update selected) to save',false);} function updateColor(){ - if(selectedIdx===null){alert('click a palette color to select it first');return;} + if(selectedIdx===null){notify('click a palette color to select it first',true);return;} const i=selectedIdx,oldHex=PALETTE[i][0]; const newHex=normHex(document.getElementById('newhexstr').value)||document.getElementById('newhex').value; - const newName=document.getElementById('newname').value||PALETTE[i][1]; + const newName=(document.getElementById('newname').value.trim())||PALETTE[i][1]; + if(PALETTE.some((p,j)=>j!==i&&p[1].toLowerCase()===newName.toLowerCase())){notify('another color is already named "'+newName+'" — names must be unique',true);return;} PALETTE[i]=[newHex,newName]; for(const k in MAP){if(MAP[k]===oldHex)MAP[k]=newHex;} for(const f in UIMAP){if(UIMAP[f].fg===oldHex)UIMAP[f].fg=newHex;if(UIMAP[f].bg===oldHex)UIMAP[f].bg=newHex;} - renderPalette();buildTable();buildUITable();renderCode();applyGround(); + renderPalette();buildTable();buildUITable();renderCode();applyGround();notify('updated "'+newName+'"',false); } function normHex(s){s=s.trim();if(/^[0-9a-fA-F]{6}$/.test(s))s='#'+s;return /^#[0-9a-fA-F]{6}$/.test(s)?s.toLowerCase():null;} function syncHex(src){const sw=document.getElementById('newhex'),tx=document.getElementById('newhexstr'); if(src==='swatch'){tx.value=sw.value;}else{const h=normHex(tx.value);if(h)sw.value=h;}} -function addColor(){const h=normHex(document.getElementById('newhexstr').value)||document.getElementById('newhex').value;const name=document.getElementById('newname').value||h;PALETTE.push([h,name]);document.getElementById('newname').value='';selectedIdx=null;renderPalette();buildTable();} +function addColor(){const h=normHex(document.getElementById('newhexstr').value)||document.getElementById('newhex').value;const name=document.getElementById('newname').value.trim(); + if(!name){notify('name the color before adding it',true);return;} + if(PALETTE.some(p=>p[1].toLowerCase()===name.toLowerCase())){notify('a color named "'+name+'" already exists — select it and use Update selected to change its value',true);return;} + PALETTE.push([h,name]);document.getElementById('newname').value='';selectedIdx=null;renderPalette();buildTable();notify('added "'+name+'"',false);} function themeName(){return (document.getElementById('themename').value||'theme').trim()||'theme';} function fileSlug(){return themeName().replace(/[^A-Za-z0-9._-]+/g,'-').replace(/^-+|-+$/g,'')||'theme';} function exportObj(){const a={};CATS.forEach(c=>a[c[0]]=MAP[c[0]]);return {name:themeName(),palette:PALETTE,assignments:a,bold:Object.keys(BOLD).filter(k=>BOLD[k]),italic:Object.keys(ITALIC).filter(k=>ITALIC[k]),ui:UIMAP};} diff --git a/scripts/theme-selector/theme-selector.html b/scripts/theme-selector/theme-selector.html index e51687f3..fcfb2d48 100644 --- a/scripts/theme-selector/theme-selector.html +++ b/scripts/theme-selector/theme-selector.html @@ -22,6 +22,7 @@ .palctl input[type=text]::placeholder{color:#b4b1a2;opacity:1} .palctl input[type=color]{width:128px;height:58px;border:1px solid #00000060;border-radius:6px;padding:2px;cursor:pointer} .palctl button,.filebar button,.fbtn{background:#252321;color:#e8bd30;border:1px solid #3a3a3a;border-radius:4px;padding:6px 12px;font:10pt monospace;cursor:pointer} + #palmsg{font:10pt monospace;opacity:0;transition:opacity .35s;margin-left:6px} #export{width:100%;height:180px;margin-top:10px;background:#0d0b0a;color:#a4ac64;border:1px solid #252321;border-radius:6px;font:10pt monospace;padding:10px} .filebar{margin:6px 0 0;display:flex;gap:8px;align-items:center} #pagetitle{font-size:30px;color:#cdced1;font-weight:normal;border:none;margin:4px 0 18px;padding:0} @@ -44,11 +45,12 @@ <h1>palette</h1> <div class="pals" id="pals"></div> <div class="palctl"> - <input type="color" id="newhex" value="#888888" oninput="syncHex('swatch')"> - <input type="text" id="newhexstr" placeholder="#rrggbb" value="#888888" oninput="syncHex('text')" style="width:110px"> - <input type="text" id="newname" placeholder="name"> + <input type="color" id="newhex" value="#888888" oninput="syncHex('swatch')" onchange="document.getElementById('newhexstr').focus()"> + <input type="text" id="newhexstr" placeholder="#rrggbb" value="#888888" oninput="syncHex('text')" onkeydown="if(event.key==='Enter')applyEdit()" style="width:110px"> + <input type="text" id="newname" placeholder="name" onkeydown="if(event.key==='Enter')applyEdit()"> <button onclick="addColor()">+ add color</button> <button onclick="updateColor()">↻ update selected</button> + <span id="palmsg"></span> </div> </section> <section class="pane saveload"> @@ -153,21 +155,27 @@ function renderPalette(){ p.appendChild(d);}); buildUITable(); } -function selectColor(i){selectedIdx=i;const [hex,name]=PALETTE[i];document.getElementById('newhexstr').value=hex;document.getElementById('newhex').value=hex;document.getElementById('newname').value=name;renderPalette();} +function notify(msg,err){const m=document.getElementById('palmsg');if(!m)return;m.textContent=msg;m.style.color=err?'#cb6b4d':'#8a9496';m.style.opacity='1';clearTimeout(m._t);m._t=setTimeout(()=>{m.style.opacity='0';},err?4000:2800);} +function applyEdit(){if(selectedIdx!==null)updateColor();else addColor();} +function selectColor(i){selectedIdx=i;const [hex,name]=PALETTE[i];document.getElementById('newhexstr').value=hex;document.getElementById('newhex').value=hex;document.getElementById('newname').value=name;renderPalette();notify('editing "'+name+'" — change the value, then Enter (or Update selected) to save',false);} function updateColor(){ - if(selectedIdx===null){alert('click a palette color to select it first');return;} + if(selectedIdx===null){notify('click a palette color to select it first',true);return;} const i=selectedIdx,oldHex=PALETTE[i][0]; const newHex=normHex(document.getElementById('newhexstr').value)||document.getElementById('newhex').value; - const newName=document.getElementById('newname').value||PALETTE[i][1]; + const newName=(document.getElementById('newname').value.trim())||PALETTE[i][1]; + if(PALETTE.some((p,j)=>j!==i&&p[1].toLowerCase()===newName.toLowerCase())){notify('another color is already named "'+newName+'" — names must be unique',true);return;} PALETTE[i]=[newHex,newName]; for(const k in MAP){if(MAP[k]===oldHex)MAP[k]=newHex;} for(const f in UIMAP){if(UIMAP[f].fg===oldHex)UIMAP[f].fg=newHex;if(UIMAP[f].bg===oldHex)UIMAP[f].bg=newHex;} - renderPalette();buildTable();buildUITable();renderCode();applyGround(); + renderPalette();buildTable();buildUITable();renderCode();applyGround();notify('updated "'+newName+'"',false); } function normHex(s){s=s.trim();if(/^[0-9a-fA-F]{6}$/.test(s))s='#'+s;return /^#[0-9a-fA-F]{6}$/.test(s)?s.toLowerCase():null;} function syncHex(src){const sw=document.getElementById('newhex'),tx=document.getElementById('newhexstr'); if(src==='swatch'){tx.value=sw.value;}else{const h=normHex(tx.value);if(h)sw.value=h;}} -function addColor(){const h=normHex(document.getElementById('newhexstr').value)||document.getElementById('newhex').value;const name=document.getElementById('newname').value||h;PALETTE.push([h,name]);document.getElementById('newname').value='';selectedIdx=null;renderPalette();buildTable();} +function addColor(){const h=normHex(document.getElementById('newhexstr').value)||document.getElementById('newhex').value;const name=document.getElementById('newname').value.trim(); + if(!name){notify('name the color before adding it',true);return;} + if(PALETTE.some(p=>p[1].toLowerCase()===name.toLowerCase())){notify('a color named "'+name+'" already exists — select it and use Update selected to change its value',true);return;} + PALETTE.push([h,name]);document.getElementById('newname').value='';selectedIdx=null;renderPalette();buildTable();notify('added "'+name+'"',false);} function themeName(){return (document.getElementById('themename').value||'theme').trim()||'theme';} function fileSlug(){return themeName().replace(/[^A-Za-z0-9._-]+/g,'-').replace(/^-+|-+$/g,'')||'theme';} function exportObj(){const a={};CATS.forEach(c=>a[c[0]]=MAP[c[0]]);return {name:themeName(),palette:PALETTE,assignments:a,bold:Object.keys(BOLD).filter(k=>BOLD[k]),italic:Object.keys(ITALIC).filter(k=>ITALIC[k]),ui:UIMAP};} |
