@1outres/dsh-plugin-background 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +56 -2
- package/lib/client.js +583 -0
- package/lib/index.js +22 -0
- package/package.json +48 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 1outres
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @1outres/dsh-plugin-background
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Paint an image behind the whole DeepSeek Harness Web UI. The interface stays
|
|
4
|
+
usable: panels become translucent, a configurable veil keeps text readable, and
|
|
5
|
+
the image can be blurred.
|
|
6
|
+
|
|
7
|
+
Installed through the [`@1outres/dsh-plugins`](../dsh-plugins) bundle.
|
|
8
|
+
|
|
9
|
+
## Settings
|
|
10
|
+
|
|
11
|
+
Open **Settings → Background**.
|
|
12
|
+
|
|
13
|
+
| Field | Default | Meaning |
|
|
14
|
+
| ----------------- | ------- | -------------------------------------------------------------------------------------- |
|
|
15
|
+
| Enable background | on | Master switch. No image set means no visual change. |
|
|
16
|
+
| Image path or URL | empty | Absolute path on the machine running DSH, or an `https://` URL. |
|
|
17
|
+
| Panel opacity | 70% | How opaque interface surfaces stay over the image. 100% hides the image behind panels. |
|
|
18
|
+
| Dim | 30% | Black veil between the image and the interface; raise it when text is hard to read. |
|
|
19
|
+
| Blur | 0 px | Blurs the image (0–40 px). |
|
|
20
|
+
|
|
21
|
+
**Reset to defaults** clears the stored overrides and falls back to the schema
|
|
22
|
+
defaults.
|
|
23
|
+
|
|
24
|
+
### Local images
|
|
25
|
+
|
|
26
|
+
Local paths are served through the Harness's authenticated `GET /api/file?path=`
|
|
27
|
+
route, so the image never leaves the Host. Example:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
/home/you/Pictures/wallpaper.jpg
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The route is authenticated by the existing browser session; nothing is copied
|
|
34
|
+
into the profile. URLs must be `https://` because the GUI is served over HTTPS
|
|
35
|
+
and browsers block mixed content.
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
There is no background slot in the Harness shell, so this plugin does not render
|
|
40
|
+
a component. Instead it:
|
|
41
|
+
|
|
42
|
+
1. injects one global stylesheet that paints `html::before` (image),
|
|
43
|
+
`html::after` (veil) and lifts `#root` above both;
|
|
44
|
+
2. reads the active theme's alias colors from the stylesheet and re-registers
|
|
45
|
+
them through `ctx.theme.overrideTokens()` as `color-mix(... transparent)`
|
|
46
|
+
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
|
|
48
|
+
entry (`background`) through `ctx.configForms`.
|
|
49
|
+
|
|
50
|
+
Theme switches re-read the palette, so the effect follows light and dark mode.
|
|
51
|
+
|
|
52
|
+
## Limits
|
|
53
|
+
|
|
54
|
+
- One image, shared by every authenticated browser.
|
|
55
|
+
- No per-theme image, video, GIF rotation or slideshow (v1).
|
|
56
|
+
- The image is repainted by the browser; large files are fine, but the Host has
|
|
57
|
+
to read them on every load through `/api/file`.
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
/* Generated by scripts/build.mjs — do not edit. */
|
|
2
|
+
window.__ModuleLoader__.load({
|
|
3
|
+
id: "@1outres/dsh-plugin-background",
|
|
4
|
+
factory: (require) => {
|
|
5
|
+
var module = { exports: {} };
|
|
6
|
+
var exports = module.exports;
|
|
7
|
+
"use strict";
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
|
|
26
|
+
// src/client/index.tsx
|
|
27
|
+
var index_exports = {};
|
|
28
|
+
__export(index_exports, {
|
|
29
|
+
apply: () => apply,
|
|
30
|
+
inject: () => inject
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/client/config.ts
|
|
35
|
+
var CONFIG_NAMESPACE = "background";
|
|
36
|
+
var DEFAULT_CONFIG = {
|
|
37
|
+
enabled: true,
|
|
38
|
+
image: "",
|
|
39
|
+
panelOpacity: 70,
|
|
40
|
+
dim: 30,
|
|
41
|
+
blur: 0
|
|
42
|
+
};
|
|
43
|
+
var TOKEN_NAMES = [
|
|
44
|
+
"--dsw-alias-bg-base",
|
|
45
|
+
"--dsw-alias-bg-layer-1",
|
|
46
|
+
"--dsw-alias-bg-layer-2",
|
|
47
|
+
"--dsw-alias-bg-layer-3",
|
|
48
|
+
"--dsw-alias-bg-layer-4",
|
|
49
|
+
"--dsw-alias-bg-overlay",
|
|
50
|
+
"--dsw-specific-sidebar-fill"
|
|
51
|
+
];
|
|
52
|
+
function normalizeConfig(value) {
|
|
53
|
+
return {
|
|
54
|
+
enabled: typeof value?.enabled === "boolean" ? value.enabled : DEFAULT_CONFIG.enabled,
|
|
55
|
+
image: typeof value?.image === "string" ? value.image : DEFAULT_CONFIG.image,
|
|
56
|
+
panelOpacity: typeof value?.panelOpacity === "number" ? value.panelOpacity : DEFAULT_CONFIG.panelOpacity,
|
|
57
|
+
dim: typeof value?.dim === "number" ? value.dim : DEFAULT_CONFIG.dim,
|
|
58
|
+
blur: typeof value?.blur === "number" ? value.blur : DEFAULT_CONFIG.blur
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/client/format.ts
|
|
63
|
+
function clamp(value, min, max) {
|
|
64
|
+
if (!Number.isFinite(value)) return min;
|
|
65
|
+
return Math.min(max, Math.max(min, value));
|
|
66
|
+
}
|
|
67
|
+
function isRemoteUrl(value) {
|
|
68
|
+
return /^https?:\/\//i.test(value.trim());
|
|
69
|
+
}
|
|
70
|
+
function isAbsolutePath(value) {
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
return trimmed.startsWith("/") || /^[A-Za-z]:[\\/]/.test(trimmed);
|
|
73
|
+
}
|
|
74
|
+
function resolveImageSource(value, base) {
|
|
75
|
+
const trimmed = value.trim();
|
|
76
|
+
if (trimmed.length === 0) return void 0;
|
|
77
|
+
if (isRemoteUrl(trimmed)) return trimmed;
|
|
78
|
+
if (!isAbsolutePath(trimmed)) return void 0;
|
|
79
|
+
try {
|
|
80
|
+
return new URL(`api/file?path=${encodeURIComponent(trimmed)}`, base).href;
|
|
81
|
+
} catch {
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function imageCssValue(url) {
|
|
86
|
+
return `url(${JSON.stringify(url)})`;
|
|
87
|
+
}
|
|
88
|
+
function alphaColor(color, percent) {
|
|
89
|
+
const clamped = clamp(percent, 0, 100);
|
|
90
|
+
if (clamped >= 100) return color;
|
|
91
|
+
return `color-mix(in srgb, ${color} ${formatNumber(clamped)}%, transparent)`;
|
|
92
|
+
}
|
|
93
|
+
function dimColor(percent) {
|
|
94
|
+
return `rgba(0, 0, 0, ${formatNumber(clamp(percent, 0, 100) / 100)})`;
|
|
95
|
+
}
|
|
96
|
+
function formatNumber(value) {
|
|
97
|
+
if (Number.isInteger(value)) return String(value);
|
|
98
|
+
return value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/client/i18n.ts
|
|
102
|
+
var en = {
|
|
103
|
+
nav: "Background",
|
|
104
|
+
title: "Background",
|
|
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
|
+
enable: "Enable background",
|
|
107
|
+
image: "Image path or URL",
|
|
108
|
+
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
|
+
invalidImage: "Enter an absolute path or an http(s) URL.",
|
|
110
|
+
preview: "Preview",
|
|
111
|
+
previewTitle: "Background",
|
|
112
|
+
previewBody: "Panels stay readable over the image.",
|
|
113
|
+
previewEmpty: "Set an image to preview it here.",
|
|
114
|
+
previewFailed: "This image could not be loaded.",
|
|
115
|
+
panelOpacity: "Panel opacity",
|
|
116
|
+
panelOpacityHint: "How opaque interface panels stay over the image.",
|
|
117
|
+
dim: "Dim",
|
|
118
|
+
dimHint: "Darkens the image so text stays readable.",
|
|
119
|
+
blur: "Blur",
|
|
120
|
+
blurHint: "Blurs the image; higher values hide detail.",
|
|
121
|
+
reset: "Reset to defaults",
|
|
122
|
+
loading: "Loading configuration\u2026",
|
|
123
|
+
unavailable: "This configuration is not available.",
|
|
124
|
+
readOnly: "Configuration is read-only.",
|
|
125
|
+
writeError: "The Host refused the change."
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/client/SettingsPage.tsx
|
|
129
|
+
var import_react = require("react");
|
|
130
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
131
|
+
var FIELDS = ["enabled", "image", "panelOpacity", "dim", "blur"];
|
|
132
|
+
var sectionStyle = {
|
|
133
|
+
maxWidth: 680,
|
|
134
|
+
padding: "4px 0 32px",
|
|
135
|
+
color: "var(--dsw-alias-label-primary)"
|
|
136
|
+
};
|
|
137
|
+
var headingStyle = {
|
|
138
|
+
margin: 0,
|
|
139
|
+
fontSize: 18,
|
|
140
|
+
fontWeight: 600,
|
|
141
|
+
lineHeight: 1.4
|
|
142
|
+
};
|
|
143
|
+
var introStyle = {
|
|
144
|
+
margin: "8px 0 0",
|
|
145
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
146
|
+
fontSize: 13,
|
|
147
|
+
lineHeight: 1.6
|
|
148
|
+
};
|
|
149
|
+
var fieldStyle = {
|
|
150
|
+
display: "flex",
|
|
151
|
+
flexDirection: "column",
|
|
152
|
+
gap: 6,
|
|
153
|
+
marginTop: 22
|
|
154
|
+
};
|
|
155
|
+
var labelRowStyle = {
|
|
156
|
+
display: "flex",
|
|
157
|
+
alignItems: "center",
|
|
158
|
+
justifyContent: "space-between",
|
|
159
|
+
gap: 12,
|
|
160
|
+
fontSize: 13,
|
|
161
|
+
fontWeight: 600
|
|
162
|
+
};
|
|
163
|
+
var hintStyle = {
|
|
164
|
+
margin: 0,
|
|
165
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
166
|
+
fontSize: 12,
|
|
167
|
+
lineHeight: 1.5
|
|
168
|
+
};
|
|
169
|
+
var errorStyle = {
|
|
170
|
+
margin: 0,
|
|
171
|
+
color: "var(--dsw-alias-state-error-primary)",
|
|
172
|
+
fontSize: 12,
|
|
173
|
+
lineHeight: 1.5
|
|
174
|
+
};
|
|
175
|
+
var inputStyle = {
|
|
176
|
+
width: "100%",
|
|
177
|
+
boxSizing: "border-box",
|
|
178
|
+
padding: "8px 10px",
|
|
179
|
+
borderRadius: 8,
|
|
180
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
181
|
+
background: "var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1))",
|
|
182
|
+
color: "inherit",
|
|
183
|
+
font: "inherit",
|
|
184
|
+
outline: "none"
|
|
185
|
+
};
|
|
186
|
+
var sliderStyle = {
|
|
187
|
+
width: "100%",
|
|
188
|
+
accentColor: "var(--dsw-alias-brand-primary)"
|
|
189
|
+
};
|
|
190
|
+
var previewStyle = {
|
|
191
|
+
position: "relative",
|
|
192
|
+
height: 180,
|
|
193
|
+
marginTop: 16,
|
|
194
|
+
borderRadius: 10,
|
|
195
|
+
overflow: "hidden",
|
|
196
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
197
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
198
|
+
display: "flex",
|
|
199
|
+
alignItems: "center",
|
|
200
|
+
justifyContent: "center"
|
|
201
|
+
};
|
|
202
|
+
var buttonStyle = {
|
|
203
|
+
marginTop: 26,
|
|
204
|
+
padding: "8px 12px",
|
|
205
|
+
borderRadius: 8,
|
|
206
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
207
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
208
|
+
color: "inherit",
|
|
209
|
+
font: "inherit",
|
|
210
|
+
cursor: "pointer"
|
|
211
|
+
};
|
|
212
|
+
var mutedStyle = {
|
|
213
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
214
|
+
fontSize: 13,
|
|
215
|
+
lineHeight: 1.6
|
|
216
|
+
};
|
|
217
|
+
function SettingsPage({ form, t }) {
|
|
218
|
+
const subscribe = (0, import_react.useCallback)((listener) => form.subscribe(listener), [form]);
|
|
219
|
+
const getSnapshot = (0, import_react.useCallback)(() => form.getSnapshot(), [form]);
|
|
220
|
+
const snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
221
|
+
const config = normalizeConfig(snapshot.value);
|
|
222
|
+
const [draft, setDraft] = (0, import_react.useState)(config.image);
|
|
223
|
+
const [previewFailure, setPreviewFailure] = (0, import_react.useState)(
|
|
224
|
+
null
|
|
225
|
+
);
|
|
226
|
+
const [writeError, setWriteError] = (0, import_react.useState)(false);
|
|
227
|
+
(0, import_react.useEffect)(() => {
|
|
228
|
+
setDraft(config.image);
|
|
229
|
+
}, [config.image]);
|
|
230
|
+
const source = resolveImageSource(config.image, document.baseURI);
|
|
231
|
+
const draftSource = resolveImageSource(draft, document.baseURI);
|
|
232
|
+
const draftInvalid = draft.trim().length > 0 && draftSource === void 0;
|
|
233
|
+
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
|
+
};
|
|
237
|
+
const commitImage = () => {
|
|
238
|
+
const next = draft.trim();
|
|
239
|
+
if (next !== config.image) write("image", next);
|
|
240
|
+
};
|
|
241
|
+
const onImageChange = (event) => {
|
|
242
|
+
setDraft(event.target.value);
|
|
243
|
+
};
|
|
244
|
+
const onImageKeyDown = (event) => {
|
|
245
|
+
if (event.key === "Enter") {
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
event.currentTarget.blur();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const reset = () => {
|
|
251
|
+
void Promise.all(FIELDS.map((field) => form.unset(field))).then((results) => setWriteError(results.some((accepted) => !accepted))).catch(() => setWriteError(true));
|
|
252
|
+
};
|
|
253
|
+
if (snapshot.status === "unavailable") {
|
|
254
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: mutedStyle, children: t("unavailable") });
|
|
255
|
+
}
|
|
256
|
+
const disabled = snapshot.writable === false;
|
|
257
|
+
const dim = dimColor(config.dim);
|
|
258
|
+
const alpha = clamp(config.panelOpacity, 0, 100) / 100;
|
|
259
|
+
const dark = document.body.hasAttribute("data-ds-dark-theme");
|
|
260
|
+
const surfaceColor = dark ? `rgba(21, 21, 23, ${alpha})` : `rgba(255, 255, 255, ${alpha})`;
|
|
261
|
+
const surfaceText = dark ? "#e9ecf2" : "#0f1115";
|
|
262
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: sectionStyle, children: [
|
|
263
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: headingStyle, children: t("title") }),
|
|
264
|
+
/* @__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") }),
|
|
267
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
268
|
+
"input",
|
|
269
|
+
{
|
|
270
|
+
type: "checkbox",
|
|
271
|
+
checked: config.enabled,
|
|
272
|
+
disabled,
|
|
273
|
+
onChange: (event) => write("enabled", event.target.checked)
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
] }),
|
|
277
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
|
|
278
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { style: labelRowStyle, htmlFor: "dsh-background-image", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("image") }) }),
|
|
279
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
280
|
+
"input",
|
|
281
|
+
{
|
|
282
|
+
id: "dsh-background-image",
|
|
283
|
+
type: "text",
|
|
284
|
+
spellCheck: false,
|
|
285
|
+
autoComplete: "off",
|
|
286
|
+
placeholder: "/home/you/wallpaper.jpg",
|
|
287
|
+
value: draft,
|
|
288
|
+
disabled,
|
|
289
|
+
style: inputStyle,
|
|
290
|
+
onChange: onImageChange,
|
|
291
|
+
onBlur: commitImage,
|
|
292
|
+
onKeyDown: onImageKeyDown
|
|
293
|
+
}
|
|
294
|
+
),
|
|
295
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("imageHint") }),
|
|
296
|
+
draftInvalid ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: errorStyle, children: t("invalidImage") }) : null
|
|
297
|
+
] }),
|
|
298
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
|
|
299
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: labelRowStyle, children: t("preview") }),
|
|
300
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: previewStyle, children: source ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
301
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
302
|
+
"div",
|
|
303
|
+
{
|
|
304
|
+
style: {
|
|
305
|
+
position: "absolute",
|
|
306
|
+
inset: 0,
|
|
307
|
+
backgroundImage: imageCssValue(source),
|
|
308
|
+
backgroundPosition: "center",
|
|
309
|
+
backgroundSize: "cover",
|
|
310
|
+
filter: `blur(${clamp(config.blur, 0, 40)}px)`,
|
|
311
|
+
transform: "scale(1.06)"
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
),
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: dim } }),
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
317
|
+
"div",
|
|
318
|
+
{
|
|
319
|
+
style: {
|
|
320
|
+
position: "relative",
|
|
321
|
+
padding: "12px 16px",
|
|
322
|
+
borderRadius: 10,
|
|
323
|
+
maxWidth: "72%",
|
|
324
|
+
textAlign: "center",
|
|
325
|
+
background: surfaceColor,
|
|
326
|
+
color: surfaceText,
|
|
327
|
+
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.18)"
|
|
328
|
+
},
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { display: "block", fontSize: 13 }, children: t("previewTitle") }),
|
|
331
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { display: "block", marginTop: 4, fontSize: 12, opacity: 0.75 }, children: t("previewBody") })
|
|
332
|
+
]
|
|
333
|
+
}
|
|
334
|
+
),
|
|
335
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
336
|
+
"img",
|
|
337
|
+
{
|
|
338
|
+
src: source,
|
|
339
|
+
alt: "",
|
|
340
|
+
style: { display: "none" },
|
|
341
|
+
onError: () => setPreviewFailure({ src: source, failed: true }),
|
|
342
|
+
onLoad: () => setPreviewFailure({ src: source, failed: false })
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
previewFailed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
346
|
+
"span",
|
|
347
|
+
{
|
|
348
|
+
style: {
|
|
349
|
+
position: "absolute",
|
|
350
|
+
inset: 0,
|
|
351
|
+
display: "flex",
|
|
352
|
+
alignItems: "center",
|
|
353
|
+
justifyContent: "center",
|
|
354
|
+
background: "rgba(0, 0, 0, 0.55)",
|
|
355
|
+
color: "#fff",
|
|
356
|
+
fontSize: 12
|
|
357
|
+
},
|
|
358
|
+
children: t("previewFailed")
|
|
359
|
+
}
|
|
360
|
+
) : null
|
|
361
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: t("previewEmpty") }) })
|
|
362
|
+
] }),
|
|
363
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
|
|
364
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: labelRowStyle, htmlFor: "dsh-background-panel-opacity", children: [
|
|
365
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("panelOpacity") }),
|
|
366
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: mutedStyle, children: [
|
|
367
|
+
Math.round(config.panelOpacity),
|
|
368
|
+
"%"
|
|
369
|
+
] })
|
|
370
|
+
] }),
|
|
371
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
372
|
+
"input",
|
|
373
|
+
{
|
|
374
|
+
id: "dsh-background-panel-opacity",
|
|
375
|
+
type: "range",
|
|
376
|
+
min: 0,
|
|
377
|
+
max: 100,
|
|
378
|
+
step: 1,
|
|
379
|
+
value: config.panelOpacity,
|
|
380
|
+
disabled,
|
|
381
|
+
style: sliderStyle,
|
|
382
|
+
onChange: (event) => write("panelOpacity", Number(event.target.value))
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("panelOpacityHint") })
|
|
386
|
+
] }),
|
|
387
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
|
|
388
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: labelRowStyle, htmlFor: "dsh-background-dim", children: [
|
|
389
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("dim") }),
|
|
390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: mutedStyle, children: [
|
|
391
|
+
Math.round(config.dim),
|
|
392
|
+
"%"
|
|
393
|
+
] })
|
|
394
|
+
] }),
|
|
395
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
396
|
+
"input",
|
|
397
|
+
{
|
|
398
|
+
id: "dsh-background-dim",
|
|
399
|
+
type: "range",
|
|
400
|
+
min: 0,
|
|
401
|
+
max: 100,
|
|
402
|
+
step: 1,
|
|
403
|
+
value: config.dim,
|
|
404
|
+
disabled,
|
|
405
|
+
style: sliderStyle,
|
|
406
|
+
onChange: (event) => write("dim", Number(event.target.value))
|
|
407
|
+
}
|
|
408
|
+
),
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("dimHint") })
|
|
410
|
+
] }),
|
|
411
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
|
|
412
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: labelRowStyle, htmlFor: "dsh-background-blur", children: [
|
|
413
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("blur") }),
|
|
414
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: mutedStyle, children: [
|
|
415
|
+
Math.round(config.blur),
|
|
416
|
+
" px"
|
|
417
|
+
] })
|
|
418
|
+
] }),
|
|
419
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
420
|
+
"input",
|
|
421
|
+
{
|
|
422
|
+
id: "dsh-background-blur",
|
|
423
|
+
type: "range",
|
|
424
|
+
min: 0,
|
|
425
|
+
max: 40,
|
|
426
|
+
step: 1,
|
|
427
|
+
value: config.blur,
|
|
428
|
+
disabled,
|
|
429
|
+
style: sliderStyle,
|
|
430
|
+
onChange: (event) => write("blur", Number(event.target.value))
|
|
431
|
+
}
|
|
432
|
+
),
|
|
433
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("blurHint") })
|
|
434
|
+
] }),
|
|
435
|
+
snapshot.writable === false ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("readOnly") }) : null,
|
|
436
|
+
writeError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: errorStyle, children: t("writeError") }) : null,
|
|
437
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: buttonStyle, onClick: reset, disabled, children: t("reset") })
|
|
438
|
+
] });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/client/styles.ts
|
|
442
|
+
var BACKGROUND_CSS = `
|
|
443
|
+
html[data-dsh-background="on"]::before {
|
|
444
|
+
content: "";
|
|
445
|
+
position: fixed;
|
|
446
|
+
inset: 0;
|
|
447
|
+
z-index: 0;
|
|
448
|
+
pointer-events: none;
|
|
449
|
+
background-image: var(--dsh-background-image, none);
|
|
450
|
+
background-position: center center;
|
|
451
|
+
background-repeat: no-repeat;
|
|
452
|
+
background-size: cover;
|
|
453
|
+
filter: blur(var(--dsh-background-blur, 0px));
|
|
454
|
+
transform: scale(1.06);
|
|
455
|
+
transform-origin: center center;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
html[data-dsh-background="on"]::after {
|
|
459
|
+
content: "";
|
|
460
|
+
position: fixed;
|
|
461
|
+
inset: 0;
|
|
462
|
+
z-index: 1;
|
|
463
|
+
pointer-events: none;
|
|
464
|
+
background: var(--dsh-background-dim, rgba(0, 0, 0, 0.3));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
html[data-dsh-background="on"] #root {
|
|
468
|
+
position: relative;
|
|
469
|
+
z-index: 2;
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
|
|
473
|
+
// src/client/index.tsx
|
|
474
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
475
|
+
var inject = ["slots", "locale", "configForms", "theme"];
|
|
476
|
+
var PLUGIN_ID = "@1outres/dsh-plugin-background";
|
|
477
|
+
var ATTRIBUTE = "data-dsh-background";
|
|
478
|
+
var OVERLAY_TOKEN = "--dsw-alias-bg-overlay";
|
|
479
|
+
function apply(ctx) {
|
|
480
|
+
const t = ctx.locale.bind(CONFIG_NAMESPACE);
|
|
481
|
+
ctx.effect(() => ctx.locale.register(CONFIG_NAMESPACE, { en }), "background: dictionaries");
|
|
482
|
+
ctx.effect(() => {
|
|
483
|
+
const tag = document.createElement("style");
|
|
484
|
+
tag.dataset.plugin = PLUGIN_ID;
|
|
485
|
+
tag.dataset.pluginCss = `${PLUGIN_ID}/background.css`;
|
|
486
|
+
tag.textContent = BACKGROUND_CSS;
|
|
487
|
+
document.head.append(tag);
|
|
488
|
+
return () => tag.remove();
|
|
489
|
+
}, "background: stylesheet");
|
|
490
|
+
const form = ctx.configForms.get(CONFIG_NAMESPACE);
|
|
491
|
+
let tokenDispose;
|
|
492
|
+
let activeConfig;
|
|
493
|
+
let applying = false;
|
|
494
|
+
let signature = "";
|
|
495
|
+
const themeSignature = (snapshot) => `${snapshot.active.id}:${snapshot.active.colorScheme}`;
|
|
496
|
+
const readBaseColor = (name) => {
|
|
497
|
+
const value = getComputedStyle(document.body).getPropertyValue(name).trim();
|
|
498
|
+
return value.length > 0 ? value : void 0;
|
|
499
|
+
};
|
|
500
|
+
const applyTokens = (config) => {
|
|
501
|
+
tokenDispose?.();
|
|
502
|
+
tokenDispose = void 0;
|
|
503
|
+
const tokens = {};
|
|
504
|
+
for (const name of TOKEN_NAMES) {
|
|
505
|
+
const base = readBaseColor(name);
|
|
506
|
+
if (base === void 0) continue;
|
|
507
|
+
const percent = name === OVERLAY_TOKEN ? Math.min(100, config.panelOpacity + 20) : config.panelOpacity;
|
|
508
|
+
const value = alphaColor(base, percent);
|
|
509
|
+
tokens[name] = { light: value, dark: value };
|
|
510
|
+
}
|
|
511
|
+
if (Object.keys(tokens).length > 0) tokenDispose = ctx.theme.overrideTokens(PLUGIN_ID, tokens);
|
|
512
|
+
};
|
|
513
|
+
const deactivate = () => {
|
|
514
|
+
activeConfig = void 0;
|
|
515
|
+
tokenDispose?.();
|
|
516
|
+
tokenDispose = void 0;
|
|
517
|
+
const root = document.documentElement;
|
|
518
|
+
root.removeAttribute(ATTRIBUTE);
|
|
519
|
+
root.style.removeProperty("--dsh-background-image");
|
|
520
|
+
root.style.removeProperty("--dsh-background-blur");
|
|
521
|
+
root.style.removeProperty("--dsh-background-dim");
|
|
522
|
+
};
|
|
523
|
+
const applyConfig = (config) => {
|
|
524
|
+
if (applying) return;
|
|
525
|
+
applying = true;
|
|
526
|
+
try {
|
|
527
|
+
activeConfig = config;
|
|
528
|
+
signature = themeSignature(ctx.theme.getTheme());
|
|
529
|
+
const source = config.enabled ? resolveImageSource(config.image, document.baseURI) : void 0;
|
|
530
|
+
if (source === void 0) {
|
|
531
|
+
deactivate();
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const root = document.documentElement;
|
|
535
|
+
root.setAttribute(ATTRIBUTE, "on");
|
|
536
|
+
root.style.setProperty("--dsh-background-image", imageCssValue(source));
|
|
537
|
+
root.style.setProperty("--dsh-background-blur", `${clamp(config.blur, 0, 40)}px`);
|
|
538
|
+
root.style.setProperty("--dsh-background-dim", dimColor(config.dim));
|
|
539
|
+
applyTokens(config);
|
|
540
|
+
} finally {
|
|
541
|
+
applying = false;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
const sync = () => applyConfig(normalizeConfig(form.getSnapshot().value));
|
|
545
|
+
ctx.effect(() => {
|
|
546
|
+
const off = form.subscribe(sync);
|
|
547
|
+
sync();
|
|
548
|
+
return () => {
|
|
549
|
+
off();
|
|
550
|
+
deactivate();
|
|
551
|
+
};
|
|
552
|
+
}, "background: configuration");
|
|
553
|
+
ctx.on("theme/change", (snapshot) => {
|
|
554
|
+
if (applying) return;
|
|
555
|
+
if (themeSignature(snapshot) === signature) return;
|
|
556
|
+
signature = themeSignature(snapshot);
|
|
557
|
+
if (activeConfig !== void 0) applyConfig(activeConfig);
|
|
558
|
+
});
|
|
559
|
+
const Page = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingsPage, { form, t });
|
|
560
|
+
ctx.effect(
|
|
561
|
+
() => ctx.configForms.whileServed(
|
|
562
|
+
[CONFIG_NAMESPACE],
|
|
563
|
+
() => ctx.slots.inject(
|
|
564
|
+
"settings.section",
|
|
565
|
+
() => ctx.slots.register(
|
|
566
|
+
{
|
|
567
|
+
name: "settings.section",
|
|
568
|
+
id: "background",
|
|
569
|
+
order: 60,
|
|
570
|
+
label: () => t("nav"),
|
|
571
|
+
locale: CONFIG_NAMESPACE
|
|
572
|
+
},
|
|
573
|
+
Page
|
|
574
|
+
)
|
|
575
|
+
)
|
|
576
|
+
),
|
|
577
|
+
"background: settings section"
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return module.exports;
|
|
582
|
+
}
|
|
583
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
var Config = z.object({
|
|
4
|
+
enabled: z.boolean().default(true).volatile(),
|
|
5
|
+
image: z.string().default("").volatile(),
|
|
6
|
+
panelOpacity: z.number().min(0).max(100).step(1).default(70).volatile(),
|
|
7
|
+
dim: z.number().min(0).max(100).step(1).default(30).volatile(),
|
|
8
|
+
blur: z.number().min(0).max(40).step(1).default(0).volatile()
|
|
9
|
+
});
|
|
10
|
+
function apply(ctx) {
|
|
11
|
+
ctx.inject(["settings"], (child) => {
|
|
12
|
+
const settings = child.settings;
|
|
13
|
+
child.effect(
|
|
14
|
+
() => settings.configure({ auto: false }, ctx.fiber),
|
|
15
|
+
"background: settings page policy"
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
Config,
|
|
21
|
+
apply
|
|
22
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1outres/dsh-plugin-background",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Set a background image behind the DeepSeek Harness Web UI, with panel opacity, dim and blur controls.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prepublishOnly": "node ../../scripts/build.mjs"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"dsh",
|
|
20
|
+
"dsh-plugin",
|
|
21
|
+
"background",
|
|
22
|
+
"wallpaper",
|
|
23
|
+
"deepseek-harness"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"dsh": ">=0.2.1-rc.5 <0.3.0",
|
|
31
|
+
"node": ">=22"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@deepseek-ai/schemastery": "~3.18.5"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@deepseek-ai/cordis": "~4.0.5",
|
|
38
|
+
"react": "^18.2.0"
|
|
39
|
+
},
|
|
40
|
+
"dsh": {
|
|
41
|
+
"client": {
|
|
42
|
+
"platform": "web",
|
|
43
|
+
"inject": [
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-theme"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|