@1outres/dsh-plugin-background 0.1.1 → 0.3.0

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/README.md CHANGED
@@ -2,9 +2,18 @@
2
2
 
3
3
  Paint an image behind the whole DeepSeek Harness Web UI. The interface stays
4
4
  usable: panels become translucent, a configurable veil keeps text readable, and
5
- the image can be blurred.
5
+ the image can be blurred. The Settings dialog stays opaque so its controls remain
6
+ readable over any image.
6
7
 
7
- Installed through the [`@1outres/dsh-plugins`](../dsh-plugins) bundle.
8
+ Install just this plugin:
9
+
10
+ ```sh
11
+ dsh plugin add @1outres/dsh-plugin-background
12
+ ```
13
+
14
+ Or install it with every plugin in the repository through the
15
+ [`@1outres/dsh-plugins`](../dsh-plugins) bundle. Enable one or the other: both
16
+ insert the same `background` row.
8
17
 
9
18
  ## Settings
10
19
 
@@ -44,7 +53,10 @@ a component. Instead it:
44
53
  2. reads the active theme's alias colors from the stylesheet and re-registers
45
54
  them through `ctx.theme.overrideTokens()` as `color-mix(... transparent)`
46
55
  surfaces, so panels, cards, popovers and the sidebar let the image through;
47
- 3. registers a `settings.section` page that reads and writes the Host settings
56
+ 3. restores the opaque alias colors inside the body-portaled Settings dialog
57
+ (`[data-shortcut-modal="settings"]`), so opening Settings never shows the
58
+ image through the panel;
59
+ 4. registers a `settings.section` page that reads and writes the Host settings
48
60
  entry (`background`) through `ctx.configForms`.
49
61
 
50
62
  Theme switches re-read the palette, so the effect follows light and dark mode.
@@ -0,0 +1,6 @@
1
+ # Installs this plugin on its own (dsh plugin add @1outres/dsh-plugin-background).
2
+ # The @1outres/dsh-plugins bundle inserts the same "background" row, so select
3
+ # one bundle or the other, never both.
4
+ - insert:
5
+ - id: background
6
+ name: "@1outres/dsh-plugin-background"
package/lib/client.js CHANGED
@@ -104,6 +104,7 @@ window.__ModuleLoader__.load({
104
104
  title: "Background",
105
105
  description: "Paint an image behind the whole interface. Point at an absolute path on the machine running DSH, or at an https URL.",
106
106
  enable: "Enable background",
107
+ enableHint: "Turns the wallpaper off without clearing the image or the readability controls.",
107
108
  image: "Image path or URL",
108
109
  imageHint: "Absolute local path, for example /home/you/wallpaper.jpg, or an https:// URL. Local images are served through the authenticated file route, so they never leave the Host.",
109
110
  invalidImage: "Enter an absolute path or an http(s) URL.",
@@ -126,6 +127,7 @@ window.__ModuleLoader__.load({
126
127
  };
127
128
 
128
129
  // src/client/SettingsPage.tsx
130
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
129
131
  var import_react = require("react");
130
132
  var import_jsx_runtime = require("react/jsx-runtime");
131
133
  var FIELDS = ["enabled", "image", "panelOpacity", "dim", "blur"];
@@ -166,6 +168,21 @@ window.__ModuleLoader__.load({
166
168
  fontSize: 12,
167
169
  lineHeight: 1.5
168
170
  };
171
+ var switchHintStyle = {
172
+ ...hintStyle,
173
+ marginTop: 4
174
+ };
175
+ var switchRowStyle = {
176
+ display: "flex",
177
+ alignItems: "center",
178
+ justifyContent: "space-between",
179
+ gap: 24,
180
+ marginTop: 22
181
+ };
182
+ var switchTitleStyle = {
183
+ fontSize: 13,
184
+ fontWeight: 600
185
+ };
169
186
  var errorStyle = {
170
187
  margin: 0,
171
188
  color: "var(--dsw-alias-state-error-primary)",
@@ -214,6 +231,51 @@ window.__ModuleLoader__.load({
214
231
  fontSize: 13,
215
232
  lineHeight: 1.6
216
233
  };
234
+ var SLIDER_COMMIT_DELAY = 300;
235
+ function useSliderDraft(committed, commit) {
236
+ const [value, setValue] = (0, import_react.useState)(committed);
237
+ const latest = (0, import_react.useRef)(committed);
238
+ const dirty = (0, import_react.useRef)(false);
239
+ const inFlight = (0, import_react.useRef)(0);
240
+ const timer = (0, import_react.useRef)(void 0);
241
+ (0, import_react.useEffect)(() => {
242
+ if (dirty.current || inFlight.current > 0) return;
243
+ latest.current = committed;
244
+ setValue(committed);
245
+ }, [committed]);
246
+ (0, import_react.useEffect)(
247
+ () => () => {
248
+ window.clearTimeout(timer.current);
249
+ },
250
+ []
251
+ );
252
+ const send = () => {
253
+ window.clearTimeout(timer.current);
254
+ timer.current = void 0;
255
+ if (!dirty.current) return;
256
+ dirty.current = false;
257
+ const sent = latest.current;
258
+ inFlight.current += 1;
259
+ void commit(sent).then((accepted) => {
260
+ inFlight.current -= 1;
261
+ if (accepted || dirty.current || inFlight.current > 0) return;
262
+ latest.current = committed;
263
+ setValue(committed);
264
+ });
265
+ };
266
+ const change = (next) => {
267
+ if (next === latest.current) return;
268
+ latest.current = next;
269
+ dirty.current = true;
270
+ setValue(next);
271
+ window.clearTimeout(timer.current);
272
+ timer.current = window.setTimeout(send, SLIDER_COMMIT_DELAY);
273
+ };
274
+ const flush = () => {
275
+ send();
276
+ };
277
+ return { value, change, flush };
278
+ }
217
279
  function SettingsPage({ form, t }) {
218
280
  const subscribe = (0, import_react.useCallback)((listener) => form.subscribe(listener), [form]);
219
281
  const getSnapshot = (0, import_react.useCallback)(() => form.getSnapshot(), [form]);
@@ -231,12 +293,22 @@ window.__ModuleLoader__.load({
231
293
  const draftSource = resolveImageSource(draft, document.baseURI);
232
294
  const draftInvalid = draft.trim().length > 0 && draftSource === void 0;
233
295
  const previewFailed = previewFailure !== null && previewFailure.src === source && previewFailure.failed;
234
- const write = (field, value) => {
235
- void form.set(field, value).then((accepted) => setWriteError(!accepted)).catch(() => setWriteError(true));
236
- };
296
+ const write = (field, value) => form.set(field, value).then(
297
+ (accepted) => {
298
+ setWriteError(!accepted);
299
+ return accepted;
300
+ },
301
+ () => {
302
+ setWriteError(true);
303
+ return false;
304
+ }
305
+ );
306
+ const opacitySlider = useSliderDraft(config.panelOpacity, (value) => write("panelOpacity", value));
307
+ const dimSlider = useSliderDraft(config.dim, (value) => write("dim", value));
308
+ const blurSlider = useSliderDraft(config.blur, (value) => write("blur", value));
237
309
  const commitImage = () => {
238
310
  const next = draft.trim();
239
- if (next !== config.image) write("image", next);
311
+ if (next !== config.image) void write("image", next);
240
312
  };
241
313
  const onImageChange = (event) => {
242
314
  setDraft(event.target.value);
@@ -254,23 +326,30 @@ window.__ModuleLoader__.load({
254
326
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: mutedStyle, children: t("unavailable") });
255
327
  }
256
328
  const disabled = snapshot.writable === false;
257
- const dim = dimColor(config.dim);
258
- const alpha = clamp(config.panelOpacity, 0, 100) / 100;
329
+ const dim = dimColor(dimSlider.value);
330
+ const alpha = clamp(opacitySlider.value, 0, 100) / 100;
331
+ const blur = clamp(blurSlider.value, 0, 40);
259
332
  const dark = document.body.hasAttribute("data-ds-dark-theme");
260
333
  const surfaceColor = dark ? `rgba(21, 21, 23, ${alpha})` : `rgba(255, 255, 255, ${alpha})`;
261
334
  const surfaceText = dark ? "#e9ecf2" : "#0f1115";
262
335
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: sectionStyle, children: [
263
336
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: headingStyle, children: t("title") }),
264
337
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: introStyle, children: t("description") }),
265
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { ...labelRowStyle, marginTop: 22, cursor: "pointer" }, children: [
266
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("enable") }),
338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: switchRowStyle, children: [
339
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 0 }, children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: switchTitleStyle, children: t("enable") }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: switchHintStyle, children: t("enableHint") })
342
+ ] }),
267
343
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
268
- "input",
344
+ import_dsh_client_ui_primitives.Switch,
269
345
  {
270
- type: "checkbox",
271
346
  checked: config.enabled,
272
347
  disabled,
273
- onChange: (event) => write("enabled", event.target.checked)
348
+ label: t("enable"),
349
+ title: disabled ? t("readOnly") : void 0,
350
+ onChange: (next) => {
351
+ void write("enabled", next);
352
+ }
274
353
  }
275
354
  )
276
355
  ] }),
@@ -307,7 +386,7 @@ window.__ModuleLoader__.load({
307
386
  backgroundImage: imageCssValue(source),
308
387
  backgroundPosition: "center",
309
388
  backgroundSize: "cover",
310
- filter: `blur(${clamp(config.blur, 0, 40)}px)`,
389
+ filter: `blur(${blur}px)`,
311
390
  transform: "scale(1.06)"
312
391
  }
313
392
  }
@@ -364,7 +443,7 @@ window.__ModuleLoader__.load({
364
443
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: labelRowStyle, htmlFor: "dsh-background-panel-opacity", children: [
365
444
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("panelOpacity") }),
366
445
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: mutedStyle, children: [
367
- Math.round(config.panelOpacity),
446
+ Math.round(opacitySlider.value),
368
447
  "%"
369
448
  ] })
370
449
  ] }),
@@ -376,10 +455,14 @@ window.__ModuleLoader__.load({
376
455
  min: 0,
377
456
  max: 100,
378
457
  step: 1,
379
- value: config.panelOpacity,
458
+ value: opacitySlider.value,
380
459
  disabled,
381
460
  style: sliderStyle,
382
- onChange: (event) => write("panelOpacity", Number(event.target.value))
461
+ onChange: (event) => opacitySlider.change(Number(event.target.value)),
462
+ onPointerUp: opacitySlider.flush,
463
+ onPointerCancel: opacitySlider.flush,
464
+ onKeyUp: opacitySlider.flush,
465
+ onBlur: opacitySlider.flush
383
466
  }
384
467
  ),
385
468
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("panelOpacityHint") })
@@ -388,7 +471,7 @@ window.__ModuleLoader__.load({
388
471
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: labelRowStyle, htmlFor: "dsh-background-dim", children: [
389
472
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("dim") }),
390
473
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: mutedStyle, children: [
391
- Math.round(config.dim),
474
+ Math.round(dimSlider.value),
392
475
  "%"
393
476
  ] })
394
477
  ] }),
@@ -400,10 +483,14 @@ window.__ModuleLoader__.load({
400
483
  min: 0,
401
484
  max: 100,
402
485
  step: 1,
403
- value: config.dim,
486
+ value: dimSlider.value,
404
487
  disabled,
405
488
  style: sliderStyle,
406
- onChange: (event) => write("dim", Number(event.target.value))
489
+ onChange: (event) => dimSlider.change(Number(event.target.value)),
490
+ onPointerUp: dimSlider.flush,
491
+ onPointerCancel: dimSlider.flush,
492
+ onKeyUp: dimSlider.flush,
493
+ onBlur: dimSlider.flush
407
494
  }
408
495
  ),
409
496
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("dimHint") })
@@ -412,7 +499,7 @@ window.__ModuleLoader__.load({
412
499
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: labelRowStyle, htmlFor: "dsh-background-blur", children: [
413
500
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("blur") }),
414
501
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: mutedStyle, children: [
415
- Math.round(config.blur),
502
+ Math.round(blurSlider.value),
416
503
  " px"
417
504
  ] })
418
505
  ] }),
@@ -424,10 +511,14 @@ window.__ModuleLoader__.load({
424
511
  min: 0,
425
512
  max: 40,
426
513
  step: 1,
427
- value: config.blur,
514
+ value: blurSlider.value,
428
515
  disabled,
429
516
  style: sliderStyle,
430
- onChange: (event) => write("blur", Number(event.target.value))
517
+ onChange: (event) => blurSlider.change(Number(event.target.value)),
518
+ onPointerUp: blurSlider.flush,
519
+ onPointerCancel: blurSlider.flush,
520
+ onKeyUp: blurSlider.flush,
521
+ onBlur: blurSlider.flush
431
522
  }
432
523
  ),
433
524
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("blurHint") })
@@ -469,6 +560,18 @@ window.__ModuleLoader__.load({
469
560
  z-index: 2;
470
561
  }
471
562
  `;
563
+ var SETTINGS_DIALOG = '[data-shortcut-modal="settings"]';
564
+ function settingsSurfaceCss(solid) {
565
+ const declarations = Object.entries(solid).filter(([, value]) => isSafeCssValue(value)).map(([name, value]) => ` ${name}: ${value};`);
566
+ if (declarations.length === 0) return "";
567
+ return `html[data-dsh-background="on"] ${SETTINGS_DIALOG} {
568
+ ${declarations.join("\n")}
569
+ }
570
+ `;
571
+ }
572
+ function isSafeCssValue(value) {
573
+ return value.length > 0 && !/[{;}@]/.test(value) && !value.includes("\n");
574
+ }
472
575
 
473
576
  // src/client/index.tsx
474
577
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -479,13 +582,18 @@ window.__ModuleLoader__.load({
479
582
  function apply(ctx) {
480
583
  const t = ctx.locale.bind(CONFIG_NAMESPACE);
481
584
  ctx.effect(() => ctx.locale.register(CONFIG_NAMESPACE, { en }), "background: dictionaries");
585
+ let styleTag;
482
586
  ctx.effect(() => {
483
587
  const tag = document.createElement("style");
484
588
  tag.dataset.plugin = PLUGIN_ID;
485
589
  tag.dataset.pluginCss = `${PLUGIN_ID}/background.css`;
486
590
  tag.textContent = BACKGROUND_CSS;
487
591
  document.head.append(tag);
488
- return () => tag.remove();
592
+ styleTag = tag;
593
+ return () => {
594
+ tag.remove();
595
+ styleTag = void 0;
596
+ };
489
597
  }, "background: stylesheet");
490
598
  const form = ctx.configForms.get(CONFIG_NAMESPACE);
491
599
  let tokenDispose;
@@ -501,14 +609,17 @@ window.__ModuleLoader__.load({
501
609
  tokenDispose?.();
502
610
  tokenDispose = void 0;
503
611
  const tokens = {};
612
+ const solid = {};
504
613
  for (const name of TOKEN_NAMES) {
505
614
  const base = readBaseColor(name);
506
615
  if (base === void 0) continue;
507
616
  const percent = name === OVERLAY_TOKEN ? Math.min(100, config.panelOpacity + 20) : config.panelOpacity;
508
617
  const value = alphaColor(base, percent);
509
618
  tokens[name] = { light: value, dark: value };
619
+ solid[name] = base;
510
620
  }
511
621
  if (Object.keys(tokens).length > 0) tokenDispose = ctx.theme.overrideTokens(PLUGIN_ID, tokens);
622
+ if (styleTag !== void 0) styleTag.textContent = BACKGROUND_CSS + settingsSurfaceCss(solid);
512
623
  };
513
624
  const deactivate = () => {
514
625
  activeConfig = void 0;
@@ -519,6 +630,7 @@ window.__ModuleLoader__.load({
519
630
  root.style.removeProperty("--dsh-background-image");
520
631
  root.style.removeProperty("--dsh-background-blur");
521
632
  root.style.removeProperty("--dsh-background-dim");
633
+ if (styleTag !== void 0) styleTag.textContent = BACKGROUND_CSS;
522
634
  };
523
635
  const applyConfig = (config) => {
524
636
  if (applying) return;
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@1outres/dsh-plugin-background",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Set a background image behind the DeepSeek Harness Web UI, with panel opacity, dim and blur controls.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/1outres/my-dsh-plugins.git",
8
+ "directory": "packages/dsh-plugin-background"
9
+ },
5
10
  "type": "module",
6
11
  "main": "lib/index.js",
7
12
  "exports": {
@@ -10,11 +15,9 @@
10
15
  "./package.json": "./package.json"
11
16
  },
12
17
  "files": [
13
- "lib"
18
+ "lib",
19
+ "cordis.patch.yml"
14
20
  ],
15
- "scripts": {
16
- "prepublishOnly": "node ../../scripts/build.mjs"
17
- },
18
21
  "keywords": [
19
22
  "dsh",
20
23
  "dsh-plugin",
@@ -38,6 +41,11 @@
38
41
  "react": "^18.2.0"
39
42
  },
40
43
  "dsh": {
44
+ "bundle": {
45
+ "patch": [
46
+ "./cordis.patch.yml"
47
+ ]
48
+ },
41
49
  "client": {
42
50
  "platform": "web",
43
51
  "inject": [
@@ -46,5 +54,9 @@
46
54
  "@deepseek-ai/dsh-client-ui-theme"
47
55
  ]
48
56
  }
49
- }
50
- }
57
+ },
58
+ "devDependencies": {
59
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.5"
60
+ },
61
+ "scripts": {}
62
+ }