From 0679c47c54dd935a0cc7d87c64081262b4367697 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 20 Jun 2026 03:34:57 -0400 Subject: feat(theme-studio): export through the save-file picker to overwrite in place Re-exporting a theme used to land a "name (1).json" duplicate. The export built a blob and clicked a download link, which routes through the browser's downloads folder, and the browser uniquifies a re-save rather than replacing the file. I switched export to the File System Access API (showSaveFilePicker): it writes straight to the file you pick, so re-exporting the same WIP.json overwrites it. Where the API is absent the old blob download still runs, mirroring importTheme's picker-with-fallback shape. A new #savetest browser gate stubs the picker and checks the written content and the close. --- scripts/theme-studio/app.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'scripts/theme-studio/app.js') diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index f6e4cd2cd..d6b42a324 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -436,7 +436,18 @@ function exportObj(){normalizePalette();const o={name:themeName(),palette:PALETT function exportState(){const t=document.getElementById('export');t.value=JSON.stringify(exportObj(),null,1);t.style.display='block';t.focus();t.select();} function toggleJSON(){const t=document.getElementById('export'),b=document.getElementById('jsonbtn');if(t.style.display==='block'){t.style.display='none';b.textContent='show';}else{exportState();b.textContent='hide';}} function updateTitle(){const n=document.getElementById('themename').value.trim();document.getElementById('pagetitle').textContent=(n||'Untitled')+': theme';} -function exportTheme(){const blob=new Blob([JSON.stringify(exportObj(),null,1)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=fileSlug()+'.json';a.click();} +// Export the theme JSON. Prefer the File System Access API (showSaveFilePicker) +// so re-exporting overwrites the chosen file in place -- a blob download routes +// through the browser's downloads folder, which uniquifies a re-save as +// "name (1).json" rather than replacing it. Fall back to the blob download where +// the API is absent (mirrors importTheme's showOpenFilePicker/fileinput fallback). +async function exportTheme(){ + const data=JSON.stringify(exportObj(),null,1); + if(!window.showSaveFilePicker){const blob=new Blob([data],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=fileSlug()+'.json';a.click();return;} + try{const h=await window.showSaveFilePicker({suggestedName:fileSlug()+'.json',types:[{description:'theme JSON',accept:{'application/json':['.json']}}]}); + const w=await h.createWritable();await w.write(data);await w.close(); + notify('saved "'+fileSlug()+'.json"',false); + }catch(e){if(e&&e.name!=='AbortError')notify('export failed: '+e.message,true);}} function applyImported(text){const d=JSON.parse(text);lastGone={};if(d.name)document.getElementById('themename').value=d.name;if(d.palette)PALETTE=d.palette.map(normalizePaletteEntry); if(!d.syntax)throw new Error('theme JSON is missing syntax; convert older files first'); SYNTAX={};CATS.forEach(c=>{const k=c[0];SYNTAX[k]=Object.assign(syntaxBlank(k),migrateLegacyFace(d.syntax[k]||{}));});syncAllSyntaxCache(); -- cgit v1.2.3