bluedither 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/install.js +5 -0
- package/dist/bluedither-tuner.js +15 -15
- package/fine-tuner/dev-middleware.js +110 -0
- package/fine-tuner/inject.js +3 -28
- package/fine-tuner/tuner.js +88 -32
- package/package.json +1 -1
package/cli/commands/install.js
CHANGED
|
@@ -103,6 +103,10 @@ export default async function install(args) {
|
|
|
103
103
|
if (existsSync(injectPath)) {
|
|
104
104
|
filesToCopy.push(['fine-tuner/inject.js', 'bluedither-tuner-inject.js']);
|
|
105
105
|
}
|
|
106
|
+
const middlewarePath = resolve(BLUEDITHER_ROOT, 'fine-tuner', 'dev-middleware.js');
|
|
107
|
+
if (existsSync(middlewarePath)) {
|
|
108
|
+
filesToCopy.push(['fine-tuner/dev-middleware.js', 'dev-middleware.js']);
|
|
109
|
+
}
|
|
106
110
|
|
|
107
111
|
let copied = 0;
|
|
108
112
|
for (const [src, dest] of filesToCopy) {
|
|
@@ -203,6 +207,7 @@ async function installFromRegistry(slug, targetDir) {
|
|
|
203
207
|
['dist/bluedither-tuner.js', 'bluedither-tuner.js'],
|
|
204
208
|
['fine-tuner/tuner.css', 'bluedither-tuner.css'],
|
|
205
209
|
['fine-tuner/inject.js', 'bluedither-tuner-inject.js'],
|
|
210
|
+
['fine-tuner/dev-middleware.js', 'dev-middleware.js'],
|
|
206
211
|
];
|
|
207
212
|
for (const [src, dest] of tunerFiles) {
|
|
208
213
|
const srcPath = resolve(BLUEDITHER_ROOT, src);
|
package/dist/bluedither-tuner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(()=>{var
|
|
1
|
+
(()=>{var p=structuredClone(window.__BD_TOKENS__||JSON.parse(document.querySelector('script[type="application/json"][data-bluedither-tokens]')?.textContent||"{}")),N=structuredClone(window.__BD_DEFAULTS__||JSON.parse(document.querySelector('script[type="application/json"][data-bluedither-defaults]')?.textContent||"{}")),j=!!(window.__BD_TOKENS__&&window.__BD_DEFAULTS__)||!!window.__BD_TUNER_SERVER_MODE__,$=["Arial","Arial Black","Bebas Neue Pro","Consolas","Courier New","Georgia","Helvetica","Impact","Lucida Console","Segoe UI","Tahoma","Times New Roman","Trebuchet MS","Verdana","SF Pro Display","SF Mono","Cascadia Code","Menlo","Monaco"],H=["Bebas Neue","Space Mono","Inter","Roboto","Roboto Mono","Roboto Condensed","Open Sans","Montserrat","Lato","Oswald","Raleway","Poppins","Nunito","Playfair Display","Merriweather","Source Sans 3","Source Code Pro","PT Sans","PT Serif","PT Mono","Ubuntu","Ubuntu Mono","Fira Sans","Fira Code","Fira Mono","Work Sans","Noto Sans","Noto Serif","DM Sans","DM Serif Display","DM Mono","IBM Plex Sans","IBM Plex Mono","IBM Plex Serif","JetBrains Mono","Inconsolata","Space Grotesk","Archivo","Archivo Black","Barlow","Barlow Condensed","Lexend","Outfit","Sora","Manrope","Bitter","Crimson Text","Libre Baskerville","Abril Fatface","Anton","Permanent Marker","Righteous","Orbitron","Teko","Rubik","Quicksand","Cabin","Karla","Josefin Sans","Comfortaa","Fredoka","Geologica","Instrument Sans","Instrument Serif"],A=[...new Set([...$,...H])].sort();function v(n,t,e){let i=t.split("."),o=n;for(let d=0;d<i.length-1;d++)o=o[i[d]];o[i[i.length-1]]=e}function E(n,t){return t.split(".").reduce((e,i)=>e?.[i],n)}function k(n,t){document.documentElement.style.setProperty(n,t)}function S(n,t){let e=window.__BD_SHADER__;e&&e.updateParams({[n]:t})}function U(n){let t=p.layout.designWidth,e=n/16,i=n/t*100;return`clamp(${(e*.55).toFixed(4)}rem, ${i.toFixed(4)}vw, ${e.toFixed(4)}rem)`}function R(n){let t="bd-gf-"+n.replace(/\s+/g,"-").toLowerCase();if(document.getElementById(t))return;let e=document.createElement("link");e.id=t,e.rel="stylesheet",e.href=`https://fonts.googleapis.com/css2?family=${encodeURIComponent(n)}:wght@400;700&display=swap`,document.head.appendChild(e)}var x=document.createElement("div");x.id="bd-tuner";var M=document.createElement("button");M.id="bd-tuner-toggle";M.textContent="Tuner";M.onclick=()=>x.classList.remove("collapsed");document.body.appendChild(x);document.body.appendChild(M);x.innerHTML=`
|
|
2
2
|
<div class="bd-tuner-title">
|
|
3
3
|
<span>BlueDither Tuner</span>
|
|
4
4
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
@@ -6,37 +6,37 @@
|
|
|
6
6
|
<button id="bd-tuner-close" title="Close">×</button>
|
|
7
7
|
</div>
|
|
8
8
|
</div>
|
|
9
|
-
`;x.querySelector("#bd-tuner-close").onclick=()=>x.classList.add("collapsed");x.querySelector("#bd-tuner-reset").onclick=()=>{confirm("Reset all tokens to defaults?")&&(Object.assign(
|
|
9
|
+
`;x.querySelector("#bd-tuner-close").onclick=()=>x.classList.add("collapsed");x.querySelector("#bd-tuner-reset").onclick=async()=>{confirm("Reset all tokens to defaults?")&&(Object.assign(p,structuredClone(N)),await I(),location.reload())};function T(n){let t=document.createElement("div");return t.className="bd-tuner-section",t.innerHTML=`<div class="bd-tuner-section-label">${n}</div>`,x.appendChild(t),t}function C(n,t,e,i,o){let d=E(p,e)||"#000000",a=document.createElement("div");a.className="bd-tuner-row",a.innerHTML=`
|
|
10
10
|
<span class="bd-tuner-label">${t}</span>
|
|
11
|
-
<span class="bd-tuner-input"><input type="color" value="${
|
|
12
|
-
<span class="bd-tuner-value">${
|
|
13
|
-
`;let
|
|
11
|
+
<span class="bd-tuner-input"><input type="color" value="${d.substring(0,7)}"></span>
|
|
12
|
+
<span class="bd-tuner-value">${d.substring(0,7)}</span>
|
|
13
|
+
`;let s=a.querySelector("input"),c=a.querySelector(".bd-tuner-value");return s.addEventListener("input",l=>{let m=l.target.value;v(p,e,m),c.textContent=m,i&&k(i,m),o&&o(m)}),n.appendChild(a),{setValue(l){v(p,e,l),s.value=l.substring(0,7),c.textContent=l.substring(0,7),i&&k(i,l)}}}function f(n,t,e,i,o,d,a){let s=E(p,e),c=document.createElement("div");c.className="bd-tuner-row bd-tuner-row-stacked",c.innerHTML=`
|
|
14
14
|
<div class="bd-tuner-row-top">
|
|
15
15
|
<span class="bd-tuner-label">${t}</span>
|
|
16
16
|
<div class="bd-tuner-px-input-wrap">
|
|
17
|
-
<input type="number" class="bd-tuner-px-num" value="${
|
|
17
|
+
<input type="number" class="bd-tuner-px-num" value="${s}" min="${i}" max="${o}" step="${d}">
|
|
18
18
|
<span class="bd-tuner-unit">px</span>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
|
-
<input type="range" class="bd-tuner-slider" min="${
|
|
22
|
-
`;let c
|
|
21
|
+
<input type="range" class="bd-tuner-slider" min="${i}" max="${o}" step="${d}" value="${s}">
|
|
22
|
+
`;let l=c.querySelector(".bd-tuner-px-num"),m=c.querySelector(".bd-tuner-slider");function u(r){if(r=Math.max(i,Math.min(o,r)),v(p,e,r),l.value=r,m.value=r,a){let w=U(r);(Array.isArray(a)?a:[a]).forEach(g=>k(g,w))}}l.addEventListener("input",r=>u(parseFloat(r.target.value)||0)),m.addEventListener("input",r=>u(parseFloat(r.target.value))),l.addEventListener("keydown",r=>{if(r.key==="ArrowUp"||r.key==="ArrowDown"){r.preventDefault();let w=r.shiftKey?10:1,g=(r.key==="ArrowUp"?d:-d)*w;u(parseFloat(l.value)+g)}}),n.appendChild(c)}function P(n,t,e,i,o,d,a){let s=E(p,e),c=document.createElement("div");c.className="bd-tuner-row bd-tuner-row-stacked",c.innerHTML=`
|
|
23
23
|
<div class="bd-tuner-row-top">
|
|
24
24
|
<span class="bd-tuner-label">${t}</span>
|
|
25
|
-
<span class="bd-tuner-value">${
|
|
25
|
+
<span class="bd-tuner-value">${s}</span>
|
|
26
26
|
</div>
|
|
27
|
-
<input type="range" class="bd-tuner-slider" min="${
|
|
28
|
-
`;let c
|
|
27
|
+
<input type="range" class="bd-tuner-slider" min="${i}" max="${o}" step="${d}" value="${s}">
|
|
28
|
+
`;let l=c.querySelector(".bd-tuner-slider"),m=c.querySelector(".bd-tuner-value");l.addEventListener("input",u=>{let r=parseFloat(u.target.value);v(p,e,r),m.textContent=r,a&&a(r)}),n.appendChild(c)}function B(n,t,e,i){let o=E(p,e),d=document.createElement("div");d.className="bd-tuner-row",d.innerHTML=`
|
|
29
29
|
<span class="bd-tuner-label">${t}</span>
|
|
30
30
|
<span class="bd-tuner-input bd-tuner-font-input">
|
|
31
|
-
<input type="text" class="bd-tuner-font-search" value="${
|
|
31
|
+
<input type="text" class="bd-tuner-font-search" value="${o}" placeholder="Search fonts...">
|
|
32
32
|
<div class="bd-tuner-font-dropdown"></div>
|
|
33
33
|
</span>
|
|
34
|
-
`;let
|
|
34
|
+
`;let a=d.querySelector(".bd-tuner-font-search"),s=d.querySelector(".bd-tuner-font-dropdown"),c=m=>$.some(u=>u.toLowerCase()===m.toLowerCase());function l(m=""){let u=A.filter(r=>r.toLowerCase().includes(m.toLowerCase())).slice(0,20);s.innerHTML=u.map(r=>{let w=c(r)?' <span style="opacity:0.4;font-size:9px">SYSTEM</span>':"";return`<div class="bd-tuner-font-option" data-font="${r}" style="font-family:'${r}',system-ui">${r}${w}</div>`}).join(""),u.filter(r=>!c(r)).forEach(R),s.querySelectorAll(".bd-tuner-font-option").forEach(r=>{r.addEventListener("mousedown",w=>{w.preventDefault();let g=r.dataset.font;a.value=g,v(p,e,g),c(g)||R(g),k(i,`"${g}", system-ui, sans-serif`),s.classList.remove("open")})})}a.addEventListener("focus",()=>{l(a.value),s.classList.add("open")}),a.addEventListener("input",()=>{l(a.value),s.classList.add("open")}),a.addEventListener("blur",()=>{setTimeout(()=>s.classList.remove("open"),150)}),a.addEventListener("keydown",m=>{if(m.key==="Enter"){let u=a.value.trim();u&&(v(p,e,u),c(u)||R(u),k(i,`"${u}", system-ui, sans-serif`),s.classList.remove("open"),a.blur())}}),n.appendChild(d)}function D(n,t,e,i,o){let d=E(p,e),a=document.createElement("div");a.className="bd-tuner-row bd-tuner-row-stacked",a.innerHTML=`
|
|
35
35
|
<span class="bd-tuner-label">${t}</span>
|
|
36
36
|
<div class="bd-tuner-segmented">
|
|
37
|
-
${
|
|
37
|
+
${i.map(s=>`<button class="bd-tuner-seg-btn${s===d?" active":""}" data-val="${s}">${s}</button>`).join("")}
|
|
38
38
|
</div>
|
|
39
|
-
`,
|
|
39
|
+
`,a.querySelectorAll(".bd-tuner-seg-btn").forEach(s=>{s.addEventListener("click",()=>{a.querySelectorAll(".bd-tuner-seg-btn").forEach(l=>l.classList.remove("active")),s.classList.add("active");let c=s.dataset.val;v(p,e,c),o&&o(c)})}),n.appendChild(a)}var _=T("Colors");C(_,"Background","colors.background","--bd-color-background");var O;C(_,"Primary","colors.primary","--bd-color-primary",n=>{O.setValue(n),S("colorFront",n)});C(_,"Text","colors.text","--bd-color-text");C(_,"CTA Background","colors.ctaBackground","--bd-color-cta-bg");C(_,"CTA Text","colors.ctaText","--bd-color-cta-text");O=C(_,"Shader Front","colors.shaderFront",null,n=>S("colorFront",n));var y=T("Typography");B(y,"Primary Font","typography.primaryFont","--bd-font-primary");B(y,"Secondary Font","typography.secondaryFont","--bd-font-secondary");f(y,"Headline Size","typography.headline.referencePx",32,300,1,"--bd-headline-size");f(y,"Headline LH","typography.headline.lineHeightPx",24,280,1,"--bd-headline-lh");f(y,"Sub Size","typography.subHeadline.referencePx",10,48,1,"--bd-subheadline-size");f(y,"Sub LH","typography.subHeadline.lineHeightPx",12,80,1,"--bd-subheadline-lh");f(y,"Logo Size","typography.logo.referencePx",12,80,1,"--bd-logo-size");f(y,"Nav Size","typography.navItem.referencePx",10,48,1,"--bd-nav-size");var h=T("Spacing");f(h,"Header Pad X","spacing.headerPaddingX",0,80,1,"--bd-header-px");f(h,"Header Pad Y","spacing.headerPaddingY",0,60,1,"--bd-header-py");f(h,"Hero Pad Top","spacing.heroPaddingTop",0,120,1,"--bd-hero-pt");f(h,"Hero Pad Bottom","spacing.heroPaddingBottom",0,120,1,"--bd-hero-pb");f(h,"Hero Pad X","spacing.heroPaddingX",0,120,1,"--bd-hero-px");f(h,"Nav Gap","spacing.navGap",0,100,1,"--bd-nav-gap");f(h,"CTA Pad X","spacing.ctaPaddingX",0,60,1,"--bd-cta-px");f(h,"CTA Pad Y","spacing.ctaPaddingY",0,30,1,"--bd-cta-py");f(h,"CTA Radius","spacing.ctaBorderRadius",0,32,1,"--bd-cta-radius");var L=T("Shader");D(L,"Shape","shader.shape",["warp","simplex","dots","wave","ripple","swirl","sphere"],n=>S("shape",n));D(L,"Dither Type","shader.type",["random","2x2","4x4","8x8"],n=>S("type",n));P(L,"Speed","shader.speed",0,2,.01,n=>S("speed",n));P(L,"Scale","shader.scale",.1,5,.01,n=>S("scale",n));P(L,"Dither Size","shader.size",.5,10,.1,n=>S("size",n));var q=T("Opacity");P(q,"Nav Links","opacity.navLinks",0,1,.01,n=>k("--bd-nav-opacity",n));var b=document.createElement("button");b.id="bd-tuner-commit";b.textContent="Commit Changes";var F=null;async function I(){let n=JSON.stringify(p,null,2);try{let o=await fetch("/__bluedither/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(o.ok&&(await o.json()).ok)return"saved"}catch{}try{let o=await fetch("http://localhost:3344/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(o.ok&&(await o.json()).ok)return"saved"}catch{}if(j)try{let o=await fetch("/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(o.ok&&(await o.json()).ok)return"saved"}catch{}if(window.showSaveFilePicker)try{F||(F=await window.showSaveFilePicker({suggestedName:"tokens.json",types:[{description:"JSON",accept:{"application/json":[".json"]}}]}));let o=await F.createWritable();return await o.write(n),await o.close(),"saved"}catch(o){if(o.name==="AbortError")return"cancelled";F=null}let t=new Blob([n],{type:"application/json"}),e=URL.createObjectURL(t),i=document.createElement("a");return i.href=e,i.download="tokens.json",i.click(),URL.revokeObjectURL(e),"downloaded"}b.onclick=async()=>{b.textContent="Exporting...",b.disabled=!0;try{if(window.__BD_SERVER_MODE__){let t=await(await fetch("/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p,null,2)})).json();if(!t.ok)throw new Error(t.error);b.textContent="Committed!"}else{let n=new Blob([JSON.stringify(p,null,2)],{type:"application/json"}),t=URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download="tokens.json",e.click(),URL.revokeObjectURL(t),b.textContent="Downloaded!"}b.classList.add("success"),setTimeout(()=>{b.textContent=window.__BD_SERVER_MODE__?"Commit Changes":"Export Tokens",b.classList.remove("success"),b.disabled=!1},2e3)}catch(n){b.textContent="Error: "+n.message,b.disabled=!1,setTimeout(()=>{b.textContent=window.__BD_SERVER_MODE__?"Commit Changes":"Export Tokens"},3e3)}};window.__BD_SERVER_MODE__||(b.textContent="Export Tokens");x.appendChild(b);var z=document.createElement("style");z.textContent=`/* =============================================
|
|
40
40
|
BlueDither Fine-Tuner \u2014 Overlay Panel v2
|
|
41
41
|
============================================= */
|
|
42
42
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueDither Dev Middleware
|
|
3
|
+
*
|
|
4
|
+
* Universal middleware that handles POST /___bluedither/commit
|
|
5
|
+
* to write tokens.json to disk. Works with any Node.js dev server.
|
|
6
|
+
*
|
|
7
|
+
* Exports:
|
|
8
|
+
* bluedither() — Vite plugin
|
|
9
|
+
* blueditherMiddleware() — Express/Connect middleware
|
|
10
|
+
* blueditherHandler() — Raw Node http handler (for custom servers)
|
|
11
|
+
*
|
|
12
|
+
* The grab-theme command auto-wires the right export for the detected framework.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { writeFileSync, existsSync } from 'fs';
|
|
16
|
+
import { resolve, dirname } from 'path';
|
|
17
|
+
|
|
18
|
+
const ENDPOINT = '/__bluedither/commit';
|
|
19
|
+
|
|
20
|
+
function findTokensPath() {
|
|
21
|
+
// Search from cwd for bluedither/tokens.json
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
const paths = [
|
|
24
|
+
resolve(cwd, 'bluedither', 'tokens.json'),
|
|
25
|
+
resolve(cwd, 'public', 'bluedither', 'tokens.json'),
|
|
26
|
+
resolve(cwd, 'theme', 'tokens.json'),
|
|
27
|
+
];
|
|
28
|
+
for (const p of paths) {
|
|
29
|
+
if (existsSync(p)) return p;
|
|
30
|
+
}
|
|
31
|
+
// Default to bluedither/tokens.json even if it doesn't exist yet
|
|
32
|
+
return resolve(cwd, 'bluedither', 'tokens.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function handleRequest(req, res) {
|
|
36
|
+
// CORS preflight
|
|
37
|
+
if (req.method === 'OPTIONS' && req.url === ENDPOINT) {
|
|
38
|
+
res.writeHead(204, {
|
|
39
|
+
'Access-Control-Allow-Origin': '*',
|
|
40
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
41
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
42
|
+
});
|
|
43
|
+
res.end();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (req.method === 'POST' && req.url === ENDPOINT) {
|
|
48
|
+
let body = '';
|
|
49
|
+
req.on('data', chunk => { body += chunk; });
|
|
50
|
+
req.on('end', () => {
|
|
51
|
+
try {
|
|
52
|
+
// Validate it's JSON
|
|
53
|
+
JSON.parse(body);
|
|
54
|
+
const tokensPath = findTokensPath();
|
|
55
|
+
writeFileSync(tokensPath, body);
|
|
56
|
+
res.writeHead(200, {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'Access-Control-Allow-Origin': '*',
|
|
59
|
+
});
|
|
60
|
+
res.end('{"ok":true}');
|
|
61
|
+
} catch (e) {
|
|
62
|
+
res.writeHead(500, {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'Access-Control-Allow-Origin': '*',
|
|
65
|
+
});
|
|
66
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Vite plugin — add to vite.config.js:
|
|
77
|
+
* import { bluedither } from './bluedither/dev-middleware.js'
|
|
78
|
+
* export default { plugins: [bluedither()] }
|
|
79
|
+
*/
|
|
80
|
+
export function bluedither() {
|
|
81
|
+
return {
|
|
82
|
+
name: 'bluedither-tuner',
|
|
83
|
+
configureServer(server) {
|
|
84
|
+
server.middlewares.use((req, res, next) => {
|
|
85
|
+
if (!handleRequest(req, res)) next();
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Express/Connect middleware — add to your server:
|
|
93
|
+
* import { blueditherMiddleware } from './bluedither/dev-middleware.js'
|
|
94
|
+
* app.use(blueditherMiddleware())
|
|
95
|
+
*/
|
|
96
|
+
export function blueditherMiddleware() {
|
|
97
|
+
return (req, res, next) => {
|
|
98
|
+
if (!handleRequest(req, res)) next();
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Raw handler for custom Node http servers:
|
|
104
|
+
* import { blueditherHandler } from './bluedither/dev-middleware.js'
|
|
105
|
+
* // In your request handler:
|
|
106
|
+
* if (!blueditherHandler(req, res)) { /* your normal handling */ }
|
|
107
|
+
*/
|
|
108
|
+
export { handleRequest as blueditherHandler };
|
|
109
|
+
|
|
110
|
+
export default bluedither;
|
package/fine-tuner/inject.js
CHANGED
|
@@ -3,13 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Drop this script into ANY page to get the tuner overlay.
|
|
5
5
|
* Reads tokens from bluedither/tokens.json, updates CSS vars live.
|
|
6
|
-
*
|
|
6
|
+
* "Commit Changes" saves via dev middleware → sidecar → File System API → download.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
9
|
* <script src="./bluedither/bluedither-tuner-inject.js"></script>
|
|
10
|
-
*
|
|
11
|
-
* The sidecar save server:
|
|
12
|
-
* npx bluedither tune --save-only
|
|
13
10
|
*/
|
|
14
11
|
|
|
15
12
|
(async function () {
|
|
@@ -38,30 +35,8 @@
|
|
|
38
35
|
window.__BD_TOKENS__ = tokens;
|
|
39
36
|
window.__BD_DEFAULTS__ = defaults;
|
|
40
37
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
let sidecarAvailable = false;
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const probe = await fetch(`http://localhost:${SIDECAR_PORT}/ping`, { method: 'GET', signal: AbortSignal.timeout(500) });
|
|
47
|
-
sidecarAvailable = probe.ok;
|
|
48
|
-
} catch {
|
|
49
|
-
// Sidecar not running
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Patch the commit mechanism
|
|
53
|
-
if (sidecarAvailable) {
|
|
54
|
-
// Override fetch for /commit to redirect to sidecar
|
|
55
|
-
const origFetch = window.fetch;
|
|
56
|
-
window.fetch = function (url, opts) {
|
|
57
|
-
if (url === '/commit' && opts?.method === 'POST') {
|
|
58
|
-
return origFetch.call(this, `http://localhost:${SIDECAR_PORT}/commit`, opts);
|
|
59
|
-
}
|
|
60
|
-
return origFetch.apply(this, arguments);
|
|
61
|
-
};
|
|
62
|
-
// Signal to tuner that server mode is available
|
|
63
|
-
window.__BD_TUNER_SERVER_MODE__ = true;
|
|
64
|
-
}
|
|
38
|
+
// Signal that we're in injectable mode (tuner will use commitTokens cascade)
|
|
39
|
+
window.__BD_TUNER_SERVER_MODE__ = true;
|
|
65
40
|
|
|
66
41
|
// Load tuner CSS
|
|
67
42
|
try {
|
package/fine-tuner/tuner.js
CHANGED
|
@@ -112,18 +112,11 @@ panel.innerHTML = `
|
|
|
112
112
|
</div>
|
|
113
113
|
`;
|
|
114
114
|
panel.querySelector('#bd-tuner-close').onclick = () => panel.classList.add('collapsed');
|
|
115
|
-
panel.querySelector('#bd-tuner-reset').onclick = () => {
|
|
115
|
+
panel.querySelector('#bd-tuner-reset').onclick = async () => {
|
|
116
116
|
if (!confirm('Reset all tokens to defaults?')) return;
|
|
117
117
|
Object.assign(tokens, structuredClone(defaults));
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
method: 'POST',
|
|
121
|
-
headers: { 'Content-Type': 'application/json' },
|
|
122
|
-
body: JSON.stringify(tokens, null, 2)
|
|
123
|
-
}).then(() => location.reload());
|
|
124
|
-
} else {
|
|
125
|
-
location.reload();
|
|
126
|
-
}
|
|
118
|
+
await commitTokens();
|
|
119
|
+
location.reload();
|
|
127
120
|
};
|
|
128
121
|
|
|
129
122
|
// ── Section builders ──
|
|
@@ -404,42 +397,105 @@ addRange(opacitySection, 'Nav Links', 'opacity.navLinks', 0, 1, 0.01, (v) => set
|
|
|
404
397
|
const commitBtn = document.createElement('button');
|
|
405
398
|
commitBtn.id = 'bd-tuner-commit';
|
|
406
399
|
commitBtn.textContent = 'Commit Changes';
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
400
|
+
|
|
401
|
+
// Try to commit via dev middleware, then sidecar, then File System Access API, then download
|
|
402
|
+
let __fileHandle = null; // Cached File System Access API handle
|
|
403
|
+
|
|
404
|
+
async function commitTokens() {
|
|
405
|
+
const payload = JSON.stringify(tokens, null, 2);
|
|
406
|
+
|
|
407
|
+
// Strategy 1: Dev middleware (Vite plugin, Express middleware, etc.)
|
|
412
408
|
try {
|
|
413
|
-
|
|
409
|
+
const res = await fetch('/__bluedither/commit', {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
headers: { 'Content-Type': 'application/json' },
|
|
412
|
+
body: payload,
|
|
413
|
+
});
|
|
414
|
+
if (res.ok) {
|
|
415
|
+
const data = await res.json();
|
|
416
|
+
if (data.ok) return 'saved';
|
|
417
|
+
}
|
|
418
|
+
} catch {}
|
|
419
|
+
|
|
420
|
+
// Strategy 2: Sidecar on port 3344 (if running)
|
|
421
|
+
try {
|
|
422
|
+
const res = await fetch('http://localhost:3344/commit', {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: { 'Content-Type': 'application/json' },
|
|
425
|
+
body: payload,
|
|
426
|
+
});
|
|
427
|
+
if (res.ok) {
|
|
428
|
+
const data = await res.json();
|
|
429
|
+
if (data.ok) return 'saved';
|
|
430
|
+
}
|
|
431
|
+
} catch {}
|
|
432
|
+
|
|
433
|
+
// Strategy 3: Legacy /commit endpoint (BlueDither's own dev server)
|
|
434
|
+
if (__serverMode) {
|
|
435
|
+
try {
|
|
414
436
|
const res = await fetch('/commit', {
|
|
415
437
|
method: 'POST',
|
|
416
438
|
headers: { 'Content-Type': 'application/json' },
|
|
417
|
-
body:
|
|
439
|
+
body: payload,
|
|
418
440
|
});
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
441
|
+
if (res.ok) {
|
|
442
|
+
const data = await res.json();
|
|
443
|
+
if (data.ok) return 'saved';
|
|
444
|
+
}
|
|
445
|
+
} catch {}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Strategy 4: File System Access API (Chrome/Edge — no server needed)
|
|
449
|
+
if (window.showSaveFilePicker) {
|
|
450
|
+
try {
|
|
451
|
+
if (!__fileHandle) {
|
|
452
|
+
__fileHandle = await window.showSaveFilePicker({
|
|
453
|
+
suggestedName: 'tokens.json',
|
|
454
|
+
types: [{ description: 'JSON', accept: { 'application/json': ['.json'] } }],
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const writable = await __fileHandle.createWritable();
|
|
458
|
+
await writable.write(payload);
|
|
459
|
+
await writable.close();
|
|
460
|
+
return 'saved';
|
|
461
|
+
} catch (e) {
|
|
462
|
+
if (e.name === 'AbortError') return 'cancelled';
|
|
463
|
+
__fileHandle = null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Strategy 5: Download as last resort
|
|
468
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
469
|
+
const url = URL.createObjectURL(blob);
|
|
470
|
+
const a = document.createElement('a');
|
|
471
|
+
a.href = url;
|
|
472
|
+
a.download = 'tokens.json';
|
|
473
|
+
a.click();
|
|
474
|
+
URL.revokeObjectURL(url);
|
|
475
|
+
return 'downloaded';
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
commitBtn.onclick = async () => {
|
|
479
|
+
commitBtn.textContent = 'Saving...';
|
|
480
|
+
commitBtn.disabled = true;
|
|
481
|
+
try {
|
|
482
|
+
const result = await commitTokens();
|
|
483
|
+
if (result === 'cancelled') {
|
|
484
|
+
commitBtn.textContent = 'Commit Changes';
|
|
485
|
+
commitBtn.disabled = false;
|
|
486
|
+
return;
|
|
432
487
|
}
|
|
488
|
+
commitBtn.textContent = result === 'saved' ? 'Saved!' : 'Downloaded!';
|
|
433
489
|
commitBtn.classList.add('success');
|
|
434
490
|
setTimeout(() => {
|
|
435
|
-
commitBtn.textContent =
|
|
491
|
+
commitBtn.textContent = 'Commit Changes';
|
|
436
492
|
commitBtn.classList.remove('success');
|
|
437
493
|
commitBtn.disabled = false;
|
|
438
494
|
}, 2000);
|
|
439
495
|
} catch (e) {
|
|
440
496
|
commitBtn.textContent = 'Error: ' + e.message;
|
|
441
497
|
commitBtn.disabled = false;
|
|
442
|
-
setTimeout(() => { commitBtn.textContent =
|
|
498
|
+
setTimeout(() => { commitBtn.textContent = 'Commit Changes'; }, 3000);
|
|
443
499
|
}
|
|
444
500
|
};
|
|
445
501
|
panel.appendChild(commitBtn);
|