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 | |
| 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.
| -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};} |
