aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-07 18:24:51 -0500
committerCraig Jennings <c@cjennings.net>2026-06-07 18:24:51 -0500
commit964dc82c1de8d1675d13daf5bd4e358276095a09 (patch)
treec043777e8a261ad7f32fe20146e855c93c6e31f7
parent94f1660e2f00d14e9b9e7a1221f6eee8d43b03ca (diff)
downloaddotemacs-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.py24
-rw-r--r--scripts/theme-selector/theme-selector.html24
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()">&#8635; 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()">&#8635; 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};}