dsh-any-background 0.1.8 → 0.1.9
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 +11 -3
- package/README.zh.md +11 -3
- package/lib/client.js +869 -179
- package/lib/client.js.map +1 -1
- package/lib/index.js +308 -45
- package/lib/invariant.js +0 -11
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -20,7 +20,7 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
pageProfile: "配置",
|
|
21
21
|
descColor: "拖动色轮或输入精确数值,整个界面的配色将实时跟随变化",
|
|
22
22
|
descInterface: "为主界面的各个区域单独调节透明度与模糊,营造空间层次感",
|
|
23
|
-
descBackground: "
|
|
23
|
+
descBackground: "上传一张图片或一段视频作为壁纸并自由摆放,或让算法为你实时生成动态背景",
|
|
24
24
|
descProfile: "将当前主题导出为文件备份,或从文件一键恢复完整配置",
|
|
25
25
|
colorTitle: "主题色",
|
|
26
26
|
colorHint: "在色轮外圈选择色相,在内部方形中调整饱和度和明度;双击滑块可恢复默认值",
|
|
@@ -33,19 +33,29 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
uiOpacitySide: "侧边栏",
|
|
34
34
|
uiOpacityCard: "对话框中选项面板",
|
|
35
35
|
uiSop: "设置界面透明度",
|
|
36
|
+
uiChatRegion: "对话文本框",
|
|
37
|
+
uiTrajectory: "轨迹页",
|
|
36
38
|
bgTitle: "背景",
|
|
37
|
-
bgChoose: "
|
|
39
|
+
bgChoose: "选择图片或视频",
|
|
38
40
|
bgRemove: "移除背景",
|
|
39
41
|
bgEdit: "编辑位置",
|
|
42
|
+
bgEditLocked: "仅「适应」模式可编辑位置",
|
|
40
43
|
wpOpacity: "背景透明度",
|
|
41
44
|
bgBlur: "背景模糊",
|
|
42
|
-
|
|
45
|
+
bgModeTitle: "布局模式",
|
|
46
|
+
bgModeFit: "适应",
|
|
47
|
+
bgModeFill: "填充",
|
|
48
|
+
bgModeStretch: "拉伸",
|
|
49
|
+
bgModeTile: "平铺",
|
|
50
|
+
bgModeCenter: "居中",
|
|
51
|
+
bgVideoBadge: "视频",
|
|
52
|
+
bgSourceImage: "图片 / 视频",
|
|
43
53
|
bgSourceGenerated: "动态生成",
|
|
44
|
-
dropHint: "
|
|
54
|
+
dropHint: "点击选择图片或视频,或将文件拖到这里",
|
|
45
55
|
liveBadge: "动态壁纸",
|
|
46
|
-
bgHint: "
|
|
56
|
+
bgHint: "「适应」布局模式下悬停预览可编辑图片/视频的位置与缩放(其余布局模式按模式自动排布);动态生成模式下可选择类型、预设与参数。绑定种子后刷新页面壁纸保持不变,取消绑定则每次刷新随机换新",
|
|
47
57
|
editorTitle: "背景编辑器",
|
|
48
|
-
editorHint: "
|
|
58
|
+
editorHint: "拖动移动画面,滚轮缩放大小",
|
|
49
59
|
editorCommit: "确认",
|
|
50
60
|
editorCancel: "取消",
|
|
51
61
|
editorReset: "重置",
|
|
@@ -107,7 +117,7 @@ window.__ModuleLoader__.load({
|
|
|
107
117
|
pageProfile: "Profile",
|
|
108
118
|
descColor: "Drag the wheel or type exact values — the whole UI follows live",
|
|
109
119
|
descInterface: "Tune opacity and blur per surface to build depth",
|
|
110
|
-
descBackground: "Upload an image as wallpaper, or let the algorithm paint a live background",
|
|
120
|
+
descBackground: "Upload an image or a video as wallpaper and place it freely, or let the algorithm paint a live background",
|
|
111
121
|
descProfile: "Export the current theme as a file, or restore it from one",
|
|
112
122
|
colorTitle: "Theme color",
|
|
113
123
|
colorHint: "Pick hue on the outer ring, adjust saturation & lightness in the square; double-click a slider to reset",
|
|
@@ -120,17 +130,27 @@ window.__ModuleLoader__.load({
|
|
|
120
130
|
uiOpacitySide: "Sidebar",
|
|
121
131
|
uiOpacityCard: "Cards & panels",
|
|
122
132
|
uiSop: "Settings interface opacity",
|
|
133
|
+
uiChatRegion: "Conversation text frame",
|
|
134
|
+
uiTrajectory: "Trajectory view",
|
|
123
135
|
bgTitle: "Background",
|
|
124
|
-
bgChoose: "Choose image",
|
|
136
|
+
bgChoose: "Choose image or video",
|
|
125
137
|
bgRemove: "Remove background",
|
|
126
138
|
bgEdit: "Edit position",
|
|
139
|
+
bgEditLocked: "Position editing is only available in Fit mode",
|
|
127
140
|
wpOpacity: "Background opacity",
|
|
128
141
|
bgBlur: "Background blur",
|
|
129
|
-
|
|
142
|
+
bgModeTitle: "Layout mode",
|
|
143
|
+
bgModeFit: "Fit",
|
|
144
|
+
bgModeFill: "Fill",
|
|
145
|
+
bgModeStretch: "Stretch",
|
|
146
|
+
bgModeTile: "Tile",
|
|
147
|
+
bgModeCenter: "Center",
|
|
148
|
+
bgVideoBadge: "Video",
|
|
149
|
+
bgSourceImage: "Image / Video",
|
|
130
150
|
bgSourceGenerated: "Generated",
|
|
131
|
-
dropHint: "Click to choose an image, or drop one here",
|
|
151
|
+
dropHint: "Click to choose an image or video, or drop one here",
|
|
132
152
|
liveBadge: "Live wallpaper",
|
|
133
|
-
bgHint: "In
|
|
153
|
+
bgHint: "In Fit layout mode hover the preview to reposition and zoom the image/video (other modes arrange it automatically); in generated mode pick a type, preset and parameters. With the seed locked the wallpaper stays the same across refreshes; unlocked, every refresh randomizes it",
|
|
134
154
|
editorTitle: "Background editor",
|
|
135
155
|
editorHint: "Drag to move, scroll to zoom",
|
|
136
156
|
editorCommit: "Confirm",
|
|
@@ -197,7 +217,9 @@ window.__ModuleLoader__.load({
|
|
|
197
217
|
bg: 0,
|
|
198
218
|
sidebar: 0,
|
|
199
219
|
card: 0,
|
|
200
|
-
settings: 0
|
|
220
|
+
settings: 0,
|
|
221
|
+
chat: 0,
|
|
222
|
+
trajectory: 0
|
|
201
223
|
},
|
|
202
224
|
settingsOpacity: 1,
|
|
203
225
|
wallpaperOpacity: 1,
|
|
@@ -209,24 +231,54 @@ window.__ModuleLoader__.load({
|
|
|
209
231
|
iw: 0,
|
|
210
232
|
ih: 0
|
|
211
233
|
},
|
|
234
|
+
videoBgState: {
|
|
235
|
+
zoom: 1,
|
|
236
|
+
x: 0,
|
|
237
|
+
y: 0,
|
|
238
|
+
iw: 0,
|
|
239
|
+
ih: 0
|
|
240
|
+
},
|
|
212
241
|
backgroundType: "image",
|
|
242
|
+
bgMode: "fit",
|
|
243
|
+
videoMime: null,
|
|
213
244
|
generatedBg: null,
|
|
214
|
-
regenerateOnReload: false
|
|
245
|
+
regenerateOnReload: false,
|
|
246
|
+
chatTextOpacity: 0,
|
|
247
|
+
trajectoryOpacity: 1
|
|
215
248
|
};
|
|
249
|
+
const clamp01 = (n, def) => typeof n === "number" ? Math.min(1, Math.max(0, n)) : def;
|
|
216
250
|
let cfg = {
|
|
217
251
|
...DEFAULT_CONFIG,
|
|
218
252
|
opacities: { ...DEFAULT_CONFIG.opacities },
|
|
219
253
|
blurs: { ...DEFAULT_CONFIG.blurs },
|
|
220
|
-
bgState: { ...DEFAULT_CONFIG.bgState }
|
|
254
|
+
bgState: { ...DEFAULT_CONFIG.bgState },
|
|
255
|
+
videoBgState: { ...DEFAULT_CONFIG.videoBgState }
|
|
221
256
|
};
|
|
222
257
|
let wpImageUrl = null;
|
|
223
258
|
let wpUrl = null;
|
|
259
|
+
let wpVideoUrl = null;
|
|
260
|
+
/** Captured video frame standing in for previews/color extraction (still-image APIs). */
|
|
261
|
+
let wpVideoSnapshot = null;
|
|
224
262
|
function setWpImageUrl(url) {
|
|
225
263
|
wpImageUrl = url;
|
|
226
264
|
}
|
|
227
265
|
function setWpUrl(url) {
|
|
228
266
|
wpUrl = url;
|
|
229
267
|
}
|
|
268
|
+
let videoRev = 0;
|
|
269
|
+
function setWpVideoUrl(url, mime) {
|
|
270
|
+
if (url === null) {
|
|
271
|
+
wpVideoUrl = null;
|
|
272
|
+
wpVideoSnapshot = null;
|
|
273
|
+
} else {
|
|
274
|
+
videoRev++;
|
|
275
|
+
wpVideoUrl = `${url}${url.includes("?") ? "&" : "?"}r=${videoRev}`;
|
|
276
|
+
}
|
|
277
|
+
cfg.videoMime = url ? mime : null;
|
|
278
|
+
}
|
|
279
|
+
function setWpVideoSnapshot(url) {
|
|
280
|
+
wpVideoSnapshot = url;
|
|
281
|
+
}
|
|
230
282
|
function setBgState(s) {
|
|
231
283
|
cfg.bgState = s;
|
|
232
284
|
}
|
|
@@ -250,39 +302,76 @@ window.__ModuleLoader__.load({
|
|
|
250
302
|
function rWpImage() {
|
|
251
303
|
return wpImageUrl;
|
|
252
304
|
}
|
|
253
|
-
|
|
305
|
+
function rWpVideo() {
|
|
306
|
+
return wpVideoUrl;
|
|
307
|
+
}
|
|
308
|
+
function rBgMode() {
|
|
309
|
+
return cfg.bgMode ?? DEFAULT_CONFIG.bgMode;
|
|
310
|
+
}
|
|
311
|
+
function rChatTextOpacity() {
|
|
312
|
+
return clamp01(cfg.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity);
|
|
313
|
+
}
|
|
314
|
+
function rTrajectoryOpacity() {
|
|
315
|
+
return clamp01(cfg.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity);
|
|
316
|
+
}
|
|
317
|
+
/** Display URL: the uploaded image/video snapshot per active type, else the generated snapshot. */
|
|
254
318
|
function rWp() {
|
|
255
|
-
|
|
319
|
+
if (cfg.backgroundType === "image") return wpImageUrl;
|
|
320
|
+
if (cfg.backgroundType === "video") return wpVideoSnapshot;
|
|
321
|
+
return wpUrl;
|
|
256
322
|
}
|
|
257
323
|
function rOps() {
|
|
258
324
|
const o = cfg.opacities ?? {};
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
325
|
+
const out = {};
|
|
326
|
+
for (const k of [
|
|
327
|
+
"bg",
|
|
328
|
+
"sidebar",
|
|
329
|
+
"card"
|
|
330
|
+
]) out[k] = clamp01(o[k], DEFAULT_CONFIG.opacities[k]);
|
|
331
|
+
return out;
|
|
264
332
|
}
|
|
265
333
|
function rBlurs() {
|
|
266
334
|
const b = cfg.blurs ?? {};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
335
|
+
const out = {};
|
|
336
|
+
for (const k of [
|
|
337
|
+
"bg",
|
|
338
|
+
"sidebar",
|
|
339
|
+
"card",
|
|
340
|
+
"settings",
|
|
341
|
+
"chat",
|
|
342
|
+
"trajectory"
|
|
343
|
+
]) {
|
|
344
|
+
const v = b[k];
|
|
345
|
+
out[k] = typeof v === "number" ? Math.min(60, Math.max(0, v)) : DEFAULT_CONFIG.blurs[k];
|
|
346
|
+
}
|
|
347
|
+
return out;
|
|
273
348
|
}
|
|
274
349
|
function rWop() {
|
|
275
|
-
return
|
|
350
|
+
return clamp01(cfg.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity);
|
|
276
351
|
}
|
|
277
352
|
function rBl() {
|
|
278
353
|
return typeof cfg.blur === "number" ? Math.min(60, Math.max(0, cfg.blur)) : DEFAULT_CONFIG.blur;
|
|
279
354
|
}
|
|
280
355
|
function rSop() {
|
|
281
|
-
return
|
|
356
|
+
return clamp01(cfg.settingsOpacity, DEFAULT_CONFIG.settingsOpacity);
|
|
282
357
|
}
|
|
283
358
|
function rBgState() {
|
|
284
359
|
return cfg.bgState;
|
|
285
360
|
}
|
|
361
|
+
function rVideoBgState() {
|
|
362
|
+
return cfg.videoBgState;
|
|
363
|
+
}
|
|
364
|
+
const num = (n, def) => typeof n === "number" ? n : def;
|
|
365
|
+
const cl = (n, lo, hi, def) => typeof n === "number" ? Math.min(hi, Math.max(lo, n)) : def;
|
|
366
|
+
function adoptBgState(s) {
|
|
367
|
+
return {
|
|
368
|
+
zoom: num(s.zoom, 1),
|
|
369
|
+
x: num(s.x, 0),
|
|
370
|
+
y: num(s.y, 0),
|
|
371
|
+
iw: typeof s.iw === "number" && s.iw > 0 ? s.iw : 0,
|
|
372
|
+
ih: typeof s.ih === "number" && s.ih > 0 ? s.ih : 0
|
|
373
|
+
};
|
|
374
|
+
}
|
|
286
375
|
/** Move a possibly-absent partial config into the shape the UI reads. */
|
|
287
376
|
function adoptConfig(raw) {
|
|
288
377
|
const c = raw ?? {};
|
|
@@ -291,48 +380,62 @@ window.__ModuleLoader__.load({
|
|
|
291
380
|
c.color[1],
|
|
292
381
|
c.color[2]
|
|
293
382
|
] : null;
|
|
294
|
-
const bg = c.bgState ?? {};
|
|
295
383
|
const legacy = typeof c.opacity === "number" ? c.opacity : null;
|
|
296
384
|
const ops = c.opacities ?? {};
|
|
297
385
|
const bl = c.blurs ?? {};
|
|
298
|
-
const
|
|
386
|
+
const blurs = {};
|
|
387
|
+
for (const k of [
|
|
388
|
+
"bg",
|
|
389
|
+
"sidebar",
|
|
390
|
+
"card",
|
|
391
|
+
"settings",
|
|
392
|
+
"chat",
|
|
393
|
+
"trajectory"
|
|
394
|
+
]) blurs[k] = num(bl[k], DEFAULT_CONFIG.blurs[k]);
|
|
395
|
+
const bgType = [
|
|
396
|
+
"video",
|
|
397
|
+
"mesh",
|
|
398
|
+
"shader",
|
|
399
|
+
"pattern"
|
|
400
|
+
].includes(c.backgroundType) ? c.backgroundType : DEFAULT_CONFIG.backgroundType;
|
|
401
|
+
const bgMode = [
|
|
402
|
+
"fit",
|
|
403
|
+
"fill",
|
|
404
|
+
"stretch",
|
|
405
|
+
"tile",
|
|
406
|
+
"center"
|
|
407
|
+
].includes(c.bgMode) ? c.bgMode : DEFAULT_CONFIG.bgMode;
|
|
299
408
|
const gen = c.generatedBg && typeof c.generatedBg === "object" ? c.generatedBg : null;
|
|
300
409
|
const generatedBg = gen && gen.type === bgType ? c.generatedBg : null;
|
|
301
410
|
cfg = {
|
|
302
411
|
color,
|
|
303
412
|
opacities: {
|
|
304
|
-
bg:
|
|
305
|
-
sidebar:
|
|
306
|
-
card:
|
|
307
|
-
},
|
|
308
|
-
blurs: {
|
|
309
|
-
bg: typeof bl.bg === "number" ? bl.bg : DEFAULT_CONFIG.blurs.bg,
|
|
310
|
-
sidebar: typeof bl.sidebar === "number" ? bl.sidebar : DEFAULT_CONFIG.blurs.sidebar,
|
|
311
|
-
card: typeof bl.card === "number" ? bl.card : DEFAULT_CONFIG.blurs.card,
|
|
312
|
-
settings: typeof bl.settings === "number" ? bl.settings : DEFAULT_CONFIG.blurs.settings
|
|
313
|
-
},
|
|
314
|
-
settingsOpacity: typeof c.settingsOpacity === "number" ? c.settingsOpacity : DEFAULT_CONFIG.settingsOpacity,
|
|
315
|
-
wallpaperOpacity: typeof c.wallpaperOpacity === "number" ? c.wallpaperOpacity : DEFAULT_CONFIG.wallpaperOpacity,
|
|
316
|
-
blur: typeof c.blur === "number" ? c.blur : DEFAULT_CONFIG.blur,
|
|
317
|
-
bgState: {
|
|
318
|
-
zoom: typeof bg.zoom === "number" ? bg.zoom : 1,
|
|
319
|
-
x: typeof bg.x === "number" ? bg.x : 0,
|
|
320
|
-
y: typeof bg.y === "number" ? bg.y : 0,
|
|
321
|
-
iw: typeof bg.iw === "number" && bg.iw > 0 ? bg.iw : 0,
|
|
322
|
-
ih: typeof bg.ih === "number" && bg.ih > 0 ? bg.ih : 0
|
|
413
|
+
bg: num(ops.bg, legacy ?? DEFAULT_CONFIG.opacities.bg),
|
|
414
|
+
sidebar: num(ops.sidebar, legacy !== null ? Math.min(1, legacy + .08) : DEFAULT_CONFIG.opacities.sidebar),
|
|
415
|
+
card: num(ops.card, DEFAULT_CONFIG.opacities.card)
|
|
323
416
|
},
|
|
417
|
+
blurs,
|
|
418
|
+
settingsOpacity: num(c.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
|
|
419
|
+
wallpaperOpacity: num(c.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity),
|
|
420
|
+
blur: num(c.blur, DEFAULT_CONFIG.blur),
|
|
421
|
+
bgState: adoptBgState(c.bgState ?? {}),
|
|
422
|
+
videoBgState: adoptBgState(c.videoBgState ?? {}),
|
|
324
423
|
backgroundType: bgType,
|
|
424
|
+
bgMode,
|
|
425
|
+
videoMime: typeof c.videoMime === "string" ? c.videoMime : null,
|
|
325
426
|
generatedBg: generatedBg ? normalizeGeneratedBg(generatedBg) : null,
|
|
326
|
-
regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload
|
|
427
|
+
regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
428
|
+
chatTextOpacity: clamp01(c.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
429
|
+
trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity)
|
|
327
430
|
};
|
|
328
431
|
}
|
|
329
432
|
function normalizeGeneratedBg(p) {
|
|
330
433
|
if (!p) return null;
|
|
331
434
|
if (p.type === "mesh") return {
|
|
332
435
|
type: "mesh",
|
|
333
|
-
seed:
|
|
334
|
-
scale:
|
|
335
|
-
intensity:
|
|
436
|
+
seed: num(p.seed, 0),
|
|
437
|
+
scale: cl(p.scale, .3, 3, 1),
|
|
438
|
+
intensity: cl(p.intensity, 0, 1, .6)
|
|
336
439
|
};
|
|
337
440
|
if (p.type === "shader") return {
|
|
338
441
|
type: "shader",
|
|
@@ -341,8 +444,8 @@ window.__ModuleLoader__.load({
|
|
|
341
444
|
"nebula",
|
|
342
445
|
"noise"
|
|
343
446
|
].includes(p.preset) ? p.preset : "aurora",
|
|
344
|
-
speed:
|
|
345
|
-
scale:
|
|
447
|
+
speed: cl(p.speed, 0, 2, .3),
|
|
448
|
+
scale: cl(p.scale, .3, 3, 1),
|
|
346
449
|
seed: typeof p.seed === "number" ? Math.floor(p.seed) : 0
|
|
347
450
|
};
|
|
348
451
|
return {
|
|
@@ -352,14 +455,18 @@ window.__ModuleLoader__.load({
|
|
|
352
455
|
"waves",
|
|
353
456
|
"poly"
|
|
354
457
|
].includes(p.preset) ? p.preset : "dots",
|
|
355
|
-
density:
|
|
356
|
-
scale:
|
|
458
|
+
density: cl(p.density, 0, 1, .5),
|
|
459
|
+
scale: cl(p.scale, .3, 3, 1),
|
|
357
460
|
seed: typeof p.seed === "number" ? Math.floor(p.seed) : 0
|
|
358
461
|
};
|
|
359
462
|
}
|
|
360
463
|
//#endregion
|
|
361
464
|
//#region src/client/rpc.ts
|
|
362
465
|
const RPC_CHANNEL = "/dsh-any-background";
|
|
466
|
+
/** Same-origin serve URL of the persisted video (enough for <video src>/fetch). */
|
|
467
|
+
const VIDEO_SERVE_URL = "/dsh-any-background/video";
|
|
468
|
+
/** HTTP route new videos are POSTed to as raw bytes (see uploadVideo). */
|
|
469
|
+
const VIDEO_UPLOAD_URL = "/dsh-any-background/video/upload";
|
|
363
470
|
const RPC_NS = "dshAnyBackground";
|
|
364
471
|
const rpcEndpoint = (method) => `${RPC_NS}/${method}`;
|
|
365
472
|
let rpcCallFn = null;
|
|
@@ -397,7 +504,7 @@ window.__ModuleLoader__.load({
|
|
|
397
504
|
function persistConfig() {
|
|
398
505
|
rpcCall("writeConfig", { config: cfg });
|
|
399
506
|
}
|
|
400
|
-
/** Load the persisted theme (config + wallpaper) from the node half. */
|
|
507
|
+
/** Load the persisted theme (config + wallpaper + video URL) from the node half. */
|
|
401
508
|
async function loadPersisted() {
|
|
402
509
|
const data = await rpcCall("read", {});
|
|
403
510
|
if (data && typeof data === "object") {
|
|
@@ -405,13 +512,34 @@ window.__ModuleLoader__.load({
|
|
|
405
512
|
if (d.config) adoptConfig(d.config);
|
|
406
513
|
if (typeof d.wallpaper === "string") setWpImageUrl(d.wallpaper);
|
|
407
514
|
else if (d.wallpaper === null) setWpImageUrl(null);
|
|
515
|
+
if (typeof d.videoUrl === "string") setWpVideoUrl(d.videoUrl, cfg.videoMime);
|
|
516
|
+
else if (d.videoUrl === null) setWpVideoUrl(null, null);
|
|
408
517
|
if (cfg.backgroundType === "image") setWpUrl(d.wallpaper === null ? null : d.wallpaper);
|
|
409
518
|
}
|
|
410
519
|
}
|
|
411
|
-
/** Persist a wallpaper (null removes it)
|
|
520
|
+
/** Persist a wallpaper (null removes it); one-shot, no debounce. */
|
|
412
521
|
function persistWallpaper(dataUrl) {
|
|
413
522
|
rpcCall("setWallpaper", { dataUrl });
|
|
414
523
|
}
|
|
524
|
+
/** Persist a background video (null removes it); resolves true once on disk,
|
|
525
|
+
* so callers only switch playback to the serve URL after acceptance. */
|
|
526
|
+
async function persistVideo(dataUrl) {
|
|
527
|
+
return await rpcCall("setVideo", { dataUrl }) === true;
|
|
528
|
+
}
|
|
529
|
+
/** Upload a video's raw bytes over HTTP (MIME in Content-Type, body untouched
|
|
530
|
+
* — no base64 inflation that would blow the RPC body limit on large clips). */
|
|
531
|
+
async function uploadVideo(blob, mime) {
|
|
532
|
+
try {
|
|
533
|
+
return (await fetch(VIDEO_UPLOAD_URL, {
|
|
534
|
+
method: "POST",
|
|
535
|
+
headers: { "Content-Type": mime || "application/octet-stream" },
|
|
536
|
+
body: blob
|
|
537
|
+
})).ok;
|
|
538
|
+
} catch (e) {
|
|
539
|
+
console.warn("dsh-any-background: video upload failed", e);
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
415
543
|
//#endregion
|
|
416
544
|
//#region src/client/utils/color.ts
|
|
417
545
|
function hsvToHsl(h, s, v) {
|
|
@@ -437,13 +565,8 @@ window.__ModuleLoader__.load({
|
|
|
437
565
|
* same token set, and applyWp / applyCustomTokens / applySettingsOverrides call
|
|
438
566
|
* this repeatedly (slider drags, viewport re-applies), so cache the last result
|
|
439
567
|
* and skip the 30+ hsl() string builds when nothing changed.
|
|
440
|
-
*
|
|
441
|
-
* This is the simple, pre-Material-You version: dark/light is decided only by
|
|
442
|
-
* lit < 0.55, and all tokens are derived directly from the picked hue/sat/lit.
|
|
443
|
-
* The optional palette argument is kept for API compatibility but ignored here,
|
|
444
|
-
* so saved picks never get rewritten by a wallpaper-derived palette.
|
|
445
568
|
*/
|
|
446
|
-
function genTokens(hue, sat, lit
|
|
569
|
+
function genTokens(hue, sat, lit) {
|
|
447
570
|
const key = `${hue}|${sat}|${lit}`;
|
|
448
571
|
if (tokensCacheKey === key && tokensCache) return tokensCache;
|
|
449
572
|
tokensCacheKey = key;
|
|
@@ -1313,6 +1436,7 @@ window.__ModuleLoader__.load({
|
|
|
1313
1436
|
//#endregion
|
|
1314
1437
|
//#region src/client/wallpaper.ts
|
|
1315
1438
|
let wpEl = null;
|
|
1439
|
+
let videoEl = null;
|
|
1316
1440
|
let appliedTokenNames = [];
|
|
1317
1441
|
let wpController = null;
|
|
1318
1442
|
let snapshotListener = null;
|
|
@@ -1339,15 +1463,6 @@ window.__ModuleLoader__.load({
|
|
|
1339
1463
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1340
1464
|
appliedTokenNames = [];
|
|
1341
1465
|
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Write the saved color's full token set as inline variables on body — the
|
|
1344
|
-
* same write surface the theme presenter owns, but derived DIRECTLY from the
|
|
1345
|
-
* saved pick, so the theme color never depends on the theme service's active
|
|
1346
|
-
* state or the presenter's timing. The bg-base, sidebar and layer surfaces are
|
|
1347
|
-
* re-emitted at their per-part alpha; every other token (labels, borders,
|
|
1348
|
-
* brand) is written verbatim. No reads: nothing can observe a stale or reset
|
|
1349
|
-
* theme value and leave the homepage on the system color.
|
|
1350
|
-
*/
|
|
1351
1466
|
/** Label tokens flipped by the generated-background brightness verdict. */
|
|
1352
1467
|
const LABEL_TOKENS = [
|
|
1353
1468
|
"--dsw-alias-label-primary",
|
|
@@ -1380,36 +1495,42 @@ window.__ModuleLoader__.load({
|
|
|
1380
1495
|
ensureTokenStyle().textContent = `body{${decls.join(";")}}`;
|
|
1381
1496
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1382
1497
|
appliedTokenNames = Object.keys(tokens);
|
|
1383
|
-
|
|
1498
|
+
applyPartOpacities(ops);
|
|
1499
|
+
} catch {}
|
|
1384
1500
|
}
|
|
1385
|
-
const
|
|
1501
|
+
const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
|
|
1502
|
+
const SETTINGS_STYLE_RULE = `${SETTINGS_PANEL_SEL}{background:var(--dsh-any-bg-settings-surface,var(--dsw-alias-bg-layer-2));backdrop-filter:var(--dsh-any-blur-settings,none);--dsw-alias-bg-layer-1:var(--dsh-any-bg-settings-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-bg-settings-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-bg-settings-layer-3)}${SETTINGS_PANEL_SEL} .dab-card{backdrop-filter:var(--dsh-any-blur-card-panels,none);-webkit-backdrop-filter:var(--dsh-any-blur-card-panels,none)}`;
|
|
1386
1503
|
function applySettingsOverrides(op) {
|
|
1387
|
-
if (op >= 1) {
|
|
1388
|
-
document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
if (!rHasColor()) return;
|
|
1392
1504
|
const [h, s, l] = rColor();
|
|
1393
|
-
const
|
|
1505
|
+
const tokens = genTokens(h, s, l).tokens;
|
|
1506
|
+
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1507
|
+
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1508
|
+
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
1394
1509
|
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-surface", toRgba(layer2, op));
|
|
1510
|
+
if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-1", toRgba(layer1, op));
|
|
1511
|
+
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-2", toRgba(layer2, op));
|
|
1512
|
+
if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-3", toRgba(layer3, op));
|
|
1395
1513
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1514
|
+
const TRAJECTORY_STYLE_RULE = "[data-conversation-composer-overlay]{--dsw-alias-bg-layer-1:var(--dsh-any-traj-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-traj-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-traj-layer-3)}";
|
|
1515
|
+
function applyTrajectoryOverrides(op) {
|
|
1516
|
+
const [h, s, l] = rColor();
|
|
1517
|
+
const tokens = genTokens(h, s, l).tokens;
|
|
1518
|
+
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1519
|
+
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1520
|
+
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
1521
|
+
if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-1", toRgba(layer1, op));
|
|
1522
|
+
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-2", toRgba(layer2, op));
|
|
1523
|
+
if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-3", toRgba(layer3, op));
|
|
1401
1524
|
}
|
|
1402
|
-
/** Apply the theme color
|
|
1403
|
-
*
|
|
1404
|
-
* current wallpaper (one HSL sample, no palette), so new uploads still get a
|
|
1405
|
-
* matching theme automatically. */
|
|
1525
|
+
/** Apply the theme color: use the saved pick directly, or fall back to
|
|
1526
|
+
* extracting a dominant color from the current wallpaper. */
|
|
1406
1527
|
function applyThemeColor() {
|
|
1407
1528
|
if (rHasColor()) {
|
|
1408
1529
|
applyWp();
|
|
1409
1530
|
return;
|
|
1410
1531
|
}
|
|
1411
1532
|
const url = rWp();
|
|
1412
|
-
if (url) extractWallpaperColor(url, rBgState()).then((hsl) => {
|
|
1533
|
+
if (url) extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState()).then((hsl) => {
|
|
1413
1534
|
if (hsl) cfg.color = hsl;
|
|
1414
1535
|
applyWp();
|
|
1415
1536
|
});
|
|
@@ -1426,6 +1547,13 @@ window.__ModuleLoader__.load({
|
|
|
1426
1547
|
applyThemeColor();
|
|
1427
1548
|
return;
|
|
1428
1549
|
}
|
|
1550
|
+
if (type === "video") {
|
|
1551
|
+
clearDynamicBg();
|
|
1552
|
+
setBgDark(null);
|
|
1553
|
+
setWpUrl(null);
|
|
1554
|
+
applyThemeColor();
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1429
1557
|
if (!cfg.generatedBg || cfg.generatedBg.type !== type) cfg.generatedBg = defaultParamsFor(type);
|
|
1430
1558
|
applyGeneratedBg(cfg.generatedBg);
|
|
1431
1559
|
}
|
|
@@ -1437,15 +1565,11 @@ window.__ModuleLoader__.load({
|
|
|
1437
1565
|
function regenerateGeneratedBg() {
|
|
1438
1566
|
const params = cfg.generatedBg;
|
|
1439
1567
|
if (!params || cfg.backgroundType === "image") return;
|
|
1440
|
-
|
|
1441
|
-
...params,
|
|
1442
|
-
seed: randomSeed()
|
|
1443
|
-
} : {
|
|
1568
|
+
cfg.generatedBg = {
|
|
1444
1569
|
...params,
|
|
1445
1570
|
seed: randomSeed()
|
|
1446
1571
|
};
|
|
1447
|
-
cfg.generatedBg
|
|
1448
|
-
applyGeneratedBg(next);
|
|
1572
|
+
applyGeneratedBg(cfg.generatedBg);
|
|
1449
1573
|
}
|
|
1450
1574
|
/** Update a generated background's parameters and re-render. */
|
|
1451
1575
|
function updateGeneratedBg(params) {
|
|
@@ -1455,6 +1579,7 @@ window.__ModuleLoader__.load({
|
|
|
1455
1579
|
}
|
|
1456
1580
|
function applyGeneratedBg(params) {
|
|
1457
1581
|
clearDynamicBg();
|
|
1582
|
+
clearVideoEl();
|
|
1458
1583
|
ensureWpContainer();
|
|
1459
1584
|
wpController = createDynamicBackground(params);
|
|
1460
1585
|
if (wpEl) {
|
|
@@ -1466,7 +1591,7 @@ window.__ModuleLoader__.load({
|
|
|
1466
1591
|
const controller = wpController;
|
|
1467
1592
|
const frame = controller.snapshot();
|
|
1468
1593
|
setWpUrl(frame);
|
|
1469
|
-
|
|
1594
|
+
applyWp();
|
|
1470
1595
|
snapshotListener?.();
|
|
1471
1596
|
setBgDark(null);
|
|
1472
1597
|
analyzeFrameDark(frame).then((dark) => {
|
|
@@ -1532,14 +1657,38 @@ window.__ModuleLoader__.load({
|
|
|
1532
1657
|
if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-settings", `blur(${px}px)`);
|
|
1533
1658
|
else document.documentElement.style.removeProperty("--dsh-any-blur-settings");
|
|
1534
1659
|
}
|
|
1660
|
+
/** Apply the main-background opacity to the center/details columns instead of
|
|
1661
|
+
* the frame. The frame's translucent bg-base sits UNDER the sidebar, so
|
|
1662
|
+
* reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
|
|
1663
|
+
* the alpha onto the columns keeps the sidebar owned by its own slider. */
|
|
1664
|
+
function applyPartOpacities(ops) {
|
|
1665
|
+
if (!(rHasColor() || rBgDark() !== null)) return;
|
|
1666
|
+
discoverParts();
|
|
1667
|
+
if (frameEl === null) return;
|
|
1668
|
+
const [h, s, l] = rColor();
|
|
1669
|
+
const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
|
|
1670
|
+
frameEl.style.background = "transparent";
|
|
1671
|
+
if (centerEl !== null) centerEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
1672
|
+
if (detailsEl !== null) detailsEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
1673
|
+
}
|
|
1674
|
+
/** Blur of the option panels inside the settings dialog (.dab-card), owned by
|
|
1675
|
+
* the "dialog option panel" (card) blur slider. Written as a plugin-owned
|
|
1676
|
+
* variable consumed by SETTINGS_STYLE_RULE — deliberately NOT applied to the
|
|
1677
|
+
* homepage center/details columns, which this slider must never touch. */
|
|
1678
|
+
function applyCardPanelsBlur(px) {
|
|
1679
|
+
if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-card-panels", `blur(${px}px)`);
|
|
1680
|
+
else document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
|
|
1681
|
+
}
|
|
1535
1682
|
/** Apply per-part interface blur to the AppFrame columns + settings panel. */
|
|
1536
1683
|
function applyPartBlurs(blurs) {
|
|
1537
1684
|
discoverParts();
|
|
1538
|
-
setBlur(frameEl,
|
|
1685
|
+
setBlur(frameEl, 0);
|
|
1539
1686
|
setBlur(sidebarEl, blurs.sidebar);
|
|
1540
|
-
setBlur(centerEl, blurs.
|
|
1541
|
-
setBlur(detailsEl, blurs.
|
|
1687
|
+
setBlur(centerEl, blurs.bg);
|
|
1688
|
+
setBlur(detailsEl, blurs.bg);
|
|
1689
|
+
applyCardPanelsBlur(blurs.card);
|
|
1542
1690
|
applySettingsBlur(blurs.settings);
|
|
1691
|
+
applyViewCards();
|
|
1543
1692
|
}
|
|
1544
1693
|
/** Live per-part blur update during slider drag (no full re-apply). */
|
|
1545
1694
|
function setPartBlur(part, v) {
|
|
@@ -1547,13 +1696,178 @@ window.__ModuleLoader__.load({
|
|
|
1547
1696
|
applySettingsBlur(v);
|
|
1548
1697
|
return;
|
|
1549
1698
|
}
|
|
1699
|
+
if (part === "card") {
|
|
1700
|
+
applyCardPanelsBlur(v);
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
if (part === "chat" || part === "trajectory") {
|
|
1704
|
+
applyViewCards();
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1550
1707
|
discoverParts();
|
|
1551
|
-
if (part === "bg")
|
|
1552
|
-
else if (part === "sidebar") setBlur(sidebarEl, v);
|
|
1553
|
-
else {
|
|
1708
|
+
if (part === "bg") {
|
|
1554
1709
|
setBlur(centerEl, v);
|
|
1555
1710
|
setBlur(detailsEl, v);
|
|
1711
|
+
} else setBlur(sidebarEl, v);
|
|
1712
|
+
}
|
|
1713
|
+
const VIEW_CARDS = [{
|
|
1714
|
+
sel: "[data-chat-flow]",
|
|
1715
|
+
mark: "data-dab-chat-card",
|
|
1716
|
+
prev: "dabChatPrev",
|
|
1717
|
+
opacity: rChatTextOpacity,
|
|
1718
|
+
blur: () => rBlurs().chat,
|
|
1719
|
+
fallback: true
|
|
1720
|
+
}, {
|
|
1721
|
+
sel: "[data-conversation-composer-overlay]",
|
|
1722
|
+
mark: "data-dab-traj-card",
|
|
1723
|
+
prev: "dabTrajPrev",
|
|
1724
|
+
opacity: rTrajectoryOpacity,
|
|
1725
|
+
blur: () => rBlurs().trajectory,
|
|
1726
|
+
plain: true
|
|
1727
|
+
}];
|
|
1728
|
+
const viewTargets = VIEW_CARDS.map(() => null);
|
|
1729
|
+
function isScrollableY(el) {
|
|
1730
|
+
const oy = getComputedStyle(el).overflowY;
|
|
1731
|
+
return oy === "auto" || oy === "scroll" || oy === "overlay";
|
|
1732
|
+
}
|
|
1733
|
+
/** Whether the subtree hosts the chat input (textarea / contenteditable /
|
|
1734
|
+
* textbox role) — used to keep the card off the input row. */
|
|
1735
|
+
function containsChatEditor(el) {
|
|
1736
|
+
return el.querySelector("textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[contenteditable=\"plaintext-only\"],[role=\"textbox\"]") !== null;
|
|
1737
|
+
}
|
|
1738
|
+
/** Walk down from a coarse candidate toward the actual message column: stop
|
|
1739
|
+
* at a scroll container (the card surface must stay pinned to the scroll
|
|
1740
|
+
* port); while the chat input lives inside, descend into the tallest child that
|
|
1741
|
+
* does NOT contain it (the header row is short, the input row holds the
|
|
1742
|
+
* editor); otherwise peel wrappers dominated (>= 85%) by a single child so
|
|
1743
|
+
* tab bars / titles stay outside the card. */
|
|
1744
|
+
function refineMessageColumn(start) {
|
|
1745
|
+
let cur = start;
|
|
1746
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
1747
|
+
if (isScrollableY(cur)) break;
|
|
1748
|
+
const kids = Array.from(cur.children).filter((k) => k instanceof HTMLElement);
|
|
1749
|
+
if (kids.length === 0) break;
|
|
1750
|
+
const tallest = kids.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
|
|
1751
|
+
if (containsChatEditor(cur)) {
|
|
1752
|
+
const candidates = kids.filter((k) => !containsChatEditor(k) && k.clientHeight >= cur.clientHeight * .4);
|
|
1753
|
+
if (candidates.length === 0) break;
|
|
1754
|
+
cur = candidates.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
if (kids.length > 1 && tallest.clientHeight >= cur.clientHeight * .85) {
|
|
1758
|
+
cur = tallest;
|
|
1759
|
+
continue;
|
|
1760
|
+
}
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
return cur;
|
|
1764
|
+
}
|
|
1765
|
+
function discoverViewTarget(idx, spec) {
|
|
1766
|
+
if (centerEl === null || !document.body.contains(centerEl)) {
|
|
1767
|
+
viewTargets[idx] = null;
|
|
1768
|
+
return null;
|
|
1769
|
+
}
|
|
1770
|
+
const marked = centerEl.querySelector(spec.sel);
|
|
1771
|
+
const cached = viewTargets[idx];
|
|
1772
|
+
if (marked !== null) {
|
|
1773
|
+
if (cached !== null && cached !== marked) {
|
|
1774
|
+
setBlur(cached, 0);
|
|
1775
|
+
restoreCardHost(cached, spec.mark, spec.prev, spec.plain === true);
|
|
1776
|
+
}
|
|
1777
|
+
viewTargets[idx] = marked;
|
|
1778
|
+
return marked;
|
|
1556
1779
|
}
|
|
1780
|
+
if (cached !== null && centerEl.contains(cached)) return cached;
|
|
1781
|
+
viewTargets[idx] = null;
|
|
1782
|
+
if (spec.fallback !== true) return null;
|
|
1783
|
+
if (centerEl.querySelector("[data-conversation-scroll]") !== null) return null;
|
|
1784
|
+
if (spec.opacity() <= 0 && spec.blur() <= 0) return null;
|
|
1785
|
+
let best = null;
|
|
1786
|
+
let bestArea = 0;
|
|
1787
|
+
for (const el of Array.from(centerEl.querySelectorAll("*"))) {
|
|
1788
|
+
if (!isScrollableY(el)) continue;
|
|
1789
|
+
if (el.clientHeight < centerEl.clientHeight * .35) continue;
|
|
1790
|
+
const area = el.clientWidth * el.clientHeight;
|
|
1791
|
+
if (area > bestArea) {
|
|
1792
|
+
bestArea = area;
|
|
1793
|
+
best = el;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
if (best === null) for (const el of Array.from(centerEl.children)) {
|
|
1797
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
1798
|
+
if (el.clientHeight < centerEl.clientHeight * .5) continue;
|
|
1799
|
+
if (el.clientHeight > (best?.clientHeight ?? 0)) best = el;
|
|
1800
|
+
}
|
|
1801
|
+
const refined = best !== null ? refineMessageColumn(best) : null;
|
|
1802
|
+
viewTargets[idx] = refined;
|
|
1803
|
+
return refined;
|
|
1804
|
+
}
|
|
1805
|
+
/** Stash the host's own inline values so teardown restores them exactly.
|
|
1806
|
+
* Plain views only get a background override, so only that is stashed. */
|
|
1807
|
+
function stashCardPrev(el, prev, plain) {
|
|
1808
|
+
const ds = el.dataset;
|
|
1809
|
+
ds[prev + "Bg"] = el.style.getPropertyValue("background");
|
|
1810
|
+
if (plain) return;
|
|
1811
|
+
ds[prev + "Border"] = el.style.getPropertyValue("border");
|
|
1812
|
+
ds[prev + "Radius"] = el.style.getPropertyValue("border-radius");
|
|
1813
|
+
ds[prev + "Padding"] = el.style.getPropertyValue("padding");
|
|
1814
|
+
}
|
|
1815
|
+
/** Undo the inline styling, restoring the host's previous inline values. */
|
|
1816
|
+
function restoreCardHost(el, mark, prev, plain) {
|
|
1817
|
+
if (!el.hasAttribute(mark)) return;
|
|
1818
|
+
const ds = el.dataset;
|
|
1819
|
+
const restore = (prop, v) => {
|
|
1820
|
+
if (v !== void 0 && v !== "") el.style.setProperty(prop, v);
|
|
1821
|
+
else el.style.removeProperty(prop);
|
|
1822
|
+
};
|
|
1823
|
+
restore("background", ds[prev + "Bg"]);
|
|
1824
|
+
if (!plain) {
|
|
1825
|
+
restore("border", ds[prev + "Border"]);
|
|
1826
|
+
restore("border-radius", ds[prev + "Radius"]);
|
|
1827
|
+
restore("padding", ds[prev + "Padding"]);
|
|
1828
|
+
delete ds[prev + "Border"];
|
|
1829
|
+
delete ds[prev + "Radius"];
|
|
1830
|
+
delete ds[prev + "Padding"];
|
|
1831
|
+
}
|
|
1832
|
+
delete ds[prev + "Bg"];
|
|
1833
|
+
el.removeAttribute(mark);
|
|
1834
|
+
}
|
|
1835
|
+
/** Teardown only: strip every view treatment and hand the hosts back untouched. */
|
|
1836
|
+
function removeViewCards() {
|
|
1837
|
+
VIEW_CARDS.forEach((spec, i) => {
|
|
1838
|
+
const el = viewTargets[i];
|
|
1839
|
+
if (el !== null) {
|
|
1840
|
+
setBlur(el, 0);
|
|
1841
|
+
restoreCardHost(el, spec.mark, spec.prev, spec.plain === true);
|
|
1842
|
+
}
|
|
1843
|
+
viewTargets[i] = null;
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
/** Re-derive the conversation view cards from the current config. Cheap
|
|
1847
|
+
* enough for live slider drags; the card structure is applied unconditionally
|
|
1848
|
+
* once the host exists so the layout never reflows when a slider leaves zero. */
|
|
1849
|
+
function applyViewCards() {
|
|
1850
|
+
discoverParts();
|
|
1851
|
+
if (centerEl === null) return;
|
|
1852
|
+
const [h, s, l] = rColor();
|
|
1853
|
+
const surface = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-1"];
|
|
1854
|
+
VIEW_CARDS.forEach((spec, i) => {
|
|
1855
|
+
const target = discoverViewTarget(i, spec);
|
|
1856
|
+
if (target === null) return;
|
|
1857
|
+
const plain = spec.plain === true;
|
|
1858
|
+
const opacity = spec.opacity();
|
|
1859
|
+
const blurPx = spec.blur();
|
|
1860
|
+
if (!plain) {
|
|
1861
|
+
if (!target.hasAttribute(spec.mark)) stashCardPrev(target, spec.prev, false);
|
|
1862
|
+
const borderAlpha = opacity > 0 ? Math.min(1, opacity * 1.5) : blurPx > 0 ? .35 : 0;
|
|
1863
|
+
target.style.background = surface !== void 0 ? toRgba(surface, opacity) : "transparent";
|
|
1864
|
+
target.style.border = surface !== void 0 ? `1px solid ${toRgba(surface, borderAlpha)}` : "1px solid transparent";
|
|
1865
|
+
target.style.borderRadius = "16px";
|
|
1866
|
+
target.style.padding = "18px";
|
|
1867
|
+
}
|
|
1868
|
+
target.setAttribute(spec.mark, "1");
|
|
1869
|
+
setBlur(target, blurPx);
|
|
1870
|
+
});
|
|
1557
1871
|
}
|
|
1558
1872
|
let partsObserver = null;
|
|
1559
1873
|
/** Watch for the AppFrame mounting so persisted blurs land even when the shell
|
|
@@ -1562,8 +1876,9 @@ window.__ModuleLoader__.load({
|
|
|
1562
1876
|
function watchParts() {
|
|
1563
1877
|
if (partsObserver !== null || typeof MutationObserver === "undefined") return;
|
|
1564
1878
|
partsObserver = new MutationObserver(() => {
|
|
1565
|
-
if (frameEl !== null && sidebarEl !== null && centerEl !== null && detailsEl !== null && document.body.contains(frameEl)) return;
|
|
1879
|
+
if (frameEl !== null && sidebarEl !== null && centerEl !== null && detailsEl !== null && document.body.contains(frameEl) && viewTargets.every((el) => el !== null && document.body.contains(el))) return;
|
|
1566
1880
|
applyPartBlurs(rBlurs());
|
|
1881
|
+
applyPartOpacities(rOps());
|
|
1567
1882
|
});
|
|
1568
1883
|
partsObserver.observe(document.body, {
|
|
1569
1884
|
childList: true,
|
|
@@ -1581,24 +1896,111 @@ window.__ModuleLoader__.load({
|
|
|
1581
1896
|
document.body.prepend(wpEl);
|
|
1582
1897
|
}
|
|
1583
1898
|
}
|
|
1899
|
+
function clearVideoEl() {
|
|
1900
|
+
if (videoEl === null) return;
|
|
1901
|
+
videoEl.pause();
|
|
1902
|
+
videoEl.removeAttribute("src");
|
|
1903
|
+
videoEl.load();
|
|
1904
|
+
videoEl.remove();
|
|
1905
|
+
videoEl = null;
|
|
1906
|
+
}
|
|
1907
|
+
/** Intrinsic-size cache for the center mode (native pixels of the current image). */
|
|
1908
|
+
let imgNat = null;
|
|
1909
|
+
function imageNatSize(url, cb) {
|
|
1910
|
+
if (imgNat !== null && imgNat.url === url) {
|
|
1911
|
+
cb(imgNat.w, imgNat.h);
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
const img = new Image();
|
|
1915
|
+
img.onload = () => {
|
|
1916
|
+
imgNat = {
|
|
1917
|
+
url,
|
|
1918
|
+
w: img.naturalWidth,
|
|
1919
|
+
h: img.naturalHeight
|
|
1920
|
+
};
|
|
1921
|
+
cb(img.naturalWidth, img.naturalHeight);
|
|
1922
|
+
};
|
|
1923
|
+
img.onerror = () => cb(0, 0);
|
|
1924
|
+
img.src = url;
|
|
1925
|
+
}
|
|
1584
1926
|
function applyImageWp(url) {
|
|
1585
1927
|
clearDynamicBg();
|
|
1928
|
+
clearVideoEl();
|
|
1586
1929
|
ensureWpContainer();
|
|
1587
1930
|
const bg = rBgState();
|
|
1931
|
+
const mode = rBgMode();
|
|
1588
1932
|
const next = `url("${url}")`;
|
|
1589
|
-
if (wpEl.style.backgroundImage !== next)
|
|
1590
|
-
|
|
1933
|
+
if (wpEl.style.backgroundImage !== next) wpEl.style.backgroundImage = next;
|
|
1934
|
+
if (mode === "fit") {
|
|
1935
|
+
wpEl.style.backgroundRepeat = "no-repeat";
|
|
1936
|
+
if (bg.iw > 0) {
|
|
1937
|
+
const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
|
|
1938
|
+
const w = bg.iw * fit * bg.zoom;
|
|
1939
|
+
const h = bg.ih * fit * bg.zoom;
|
|
1940
|
+
wpEl.style.backgroundSize = `${w}px ${h}px`;
|
|
1941
|
+
wpEl.style.backgroundPosition = `${bg.x * window.innerWidth - w / 2}px ${bg.y * window.innerHeight - h / 2}px`;
|
|
1942
|
+
} else {
|
|
1943
|
+
wpEl.style.backgroundSize = "contain";
|
|
1944
|
+
wpEl.style.backgroundPosition = "center";
|
|
1945
|
+
}
|
|
1946
|
+
} else if (mode === "fill") {
|
|
1591
1947
|
wpEl.style.backgroundRepeat = "no-repeat";
|
|
1948
|
+
wpEl.style.backgroundSize = "cover";
|
|
1949
|
+
wpEl.style.backgroundPosition = "center";
|
|
1950
|
+
} else if (mode === "stretch") {
|
|
1951
|
+
wpEl.style.backgroundRepeat = "no-repeat";
|
|
1952
|
+
wpEl.style.backgroundSize = "100% 100%";
|
|
1953
|
+
wpEl.style.backgroundPosition = "center";
|
|
1954
|
+
} else if (mode === "tile") {
|
|
1955
|
+
wpEl.style.backgroundRepeat = "repeat";
|
|
1956
|
+
wpEl.style.backgroundSize = "auto";
|
|
1957
|
+
wpEl.style.backgroundPosition = "0px 0px";
|
|
1958
|
+
} else {
|
|
1959
|
+
wpEl.style.backgroundRepeat = "no-repeat";
|
|
1960
|
+
wpEl.style.backgroundSize = "contain";
|
|
1961
|
+
wpEl.style.backgroundPosition = "center";
|
|
1962
|
+
imageNatSize(url, (w, h) => {
|
|
1963
|
+
if (!wpEl || wpEl.style.backgroundImage !== next || rBgMode() !== "center") return;
|
|
1964
|
+
if (w > 0 && h > 0) {
|
|
1965
|
+
wpEl.style.backgroundSize = `${w}px ${h}px`;
|
|
1966
|
+
wpEl.style.backgroundPosition = "center";
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1592
1969
|
}
|
|
1593
|
-
|
|
1970
|
+
applyWpEffects();
|
|
1971
|
+
}
|
|
1972
|
+
/** Video wallpaper: a muted looping <video> inside the wallpaper layer.
|
|
1973
|
+
* Placement modes map onto object-fit (tile has no video equivalent and
|
|
1974
|
+
* falls back to cover). */
|
|
1975
|
+
function applyVideoWp(url) {
|
|
1976
|
+
clearDynamicBg();
|
|
1977
|
+
ensureWpContainer();
|
|
1978
|
+
if (wpEl.style.backgroundImage !== "none") wpEl.style.backgroundImage = "none";
|
|
1979
|
+
if (videoEl === null || !videoEl.isConnected) {
|
|
1980
|
+
videoEl = document.createElement("video");
|
|
1981
|
+
videoEl.muted = true;
|
|
1982
|
+
videoEl.loop = true;
|
|
1983
|
+
videoEl.autoplay = true;
|
|
1984
|
+
videoEl.playsInline = true;
|
|
1985
|
+
videoEl.setAttribute("playsinline", "");
|
|
1986
|
+
videoEl.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-position:center;";
|
|
1987
|
+
videoEl.setAttribute("src", url);
|
|
1988
|
+
videoEl.play().catch(() => void 0);
|
|
1989
|
+
wpEl.appendChild(videoEl);
|
|
1990
|
+
} else if (videoEl.getAttribute("src") !== url) {
|
|
1991
|
+
videoEl.setAttribute("src", url);
|
|
1992
|
+
videoEl.play().catch(() => void 0);
|
|
1993
|
+
}
|
|
1994
|
+
const mode = rBgMode();
|
|
1995
|
+
const bg = rVideoBgState();
|
|
1996
|
+
if (mode === "fit" && bg.iw > 0) {
|
|
1594
1997
|
const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
|
|
1595
1998
|
const w = bg.iw * fit * bg.zoom;
|
|
1596
1999
|
const h = bg.ih * fit * bg.zoom;
|
|
1597
|
-
|
|
1598
|
-
wpEl.style.backgroundPosition = `${bg.x * window.innerWidth - w / 2}px ${bg.y * window.innerHeight - h / 2}px`;
|
|
2000
|
+
videoEl.style.cssText = `position:absolute;left:${bg.x * window.innerWidth - w / 2}px;top:${bg.y * window.innerHeight - h / 2}px;width:${w}px;height:${h}px;object-fit:fill;`;
|
|
1599
2001
|
} else {
|
|
1600
|
-
|
|
1601
|
-
|
|
2002
|
+
videoEl.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-position:center;";
|
|
2003
|
+
videoEl.style.objectFit = mode === "stretch" ? "fill" : mode === "fill" || mode === "tile" ? "cover" : "contain";
|
|
1602
2004
|
}
|
|
1603
2005
|
applyWpEffects();
|
|
1604
2006
|
}
|
|
@@ -1610,7 +2012,17 @@ window.__ModuleLoader__.load({
|
|
|
1610
2012
|
}
|
|
1611
2013
|
function applyWp() {
|
|
1612
2014
|
const url = rWp();
|
|
1613
|
-
if (cfg.backgroundType
|
|
2015
|
+
if (cfg.backgroundType === "video") {
|
|
2016
|
+
const vurl = rWpVideo();
|
|
2017
|
+
if (vurl) applyVideoWp(vurl);
|
|
2018
|
+
else {
|
|
2019
|
+
clearDynamicBg();
|
|
2020
|
+
clearVideoEl();
|
|
2021
|
+
wpEl?.remove();
|
|
2022
|
+
wpEl = null;
|
|
2023
|
+
}
|
|
2024
|
+
} else if (cfg.backgroundType !== "image" && cfg.generatedBg) {
|
|
2025
|
+
clearVideoEl();
|
|
1614
2026
|
if (!wpController) {
|
|
1615
2027
|
applyGeneratedBg(cfg.generatedBg);
|
|
1616
2028
|
return;
|
|
@@ -1621,27 +2033,44 @@ window.__ModuleLoader__.load({
|
|
|
1621
2033
|
} else if (url) applyImageWp(url);
|
|
1622
2034
|
else {
|
|
1623
2035
|
clearDynamicBg();
|
|
2036
|
+
clearVideoEl();
|
|
1624
2037
|
wpEl?.remove();
|
|
1625
2038
|
wpEl = null;
|
|
1626
2039
|
}
|
|
1627
2040
|
if (rHasColor() || rBgDark() !== null) applyCustomTokens(rOps());
|
|
1628
|
-
if (rHasColor())
|
|
2041
|
+
if (rHasColor()) {
|
|
2042
|
+
applySettingsOverrides(rSop());
|
|
2043
|
+
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
2044
|
+
}
|
|
1629
2045
|
applyPartBlurs(rBlurs());
|
|
1630
2046
|
}
|
|
1631
2047
|
function teardownWp() {
|
|
1632
2048
|
clearDynamicBg();
|
|
2049
|
+
clearVideoEl();
|
|
1633
2050
|
setBgDark(null);
|
|
1634
2051
|
wpEl?.remove();
|
|
1635
2052
|
wpEl = null;
|
|
1636
2053
|
clearCustomTokens();
|
|
1637
2054
|
tokenStyleEl?.remove();
|
|
1638
2055
|
tokenStyleEl = null;
|
|
2056
|
+
removeViewCards();
|
|
1639
2057
|
document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
|
|
2058
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-1");
|
|
2059
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-2");
|
|
2060
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-3");
|
|
2061
|
+
document.documentElement.style.removeProperty("--dsh-any-traj-layer-1");
|
|
2062
|
+
document.documentElement.style.removeProperty("--dsh-any-traj-layer-2");
|
|
2063
|
+
document.documentElement.style.removeProperty("--dsh-any-traj-layer-3");
|
|
2064
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-card-surface");
|
|
1640
2065
|
document.documentElement.style.removeProperty("--dsh-any-blur-settings");
|
|
2066
|
+
document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
|
|
1641
2067
|
setBlur(frameEl, 0);
|
|
1642
2068
|
setBlur(sidebarEl, 0);
|
|
1643
2069
|
setBlur(centerEl, 0);
|
|
1644
2070
|
setBlur(detailsEl, 0);
|
|
2071
|
+
if (frameEl !== null) frameEl.style.removeProperty("background");
|
|
2072
|
+
if (centerEl !== null) centerEl.style.removeProperty("background");
|
|
2073
|
+
if (detailsEl !== null) detailsEl.style.removeProperty("background");
|
|
1645
2074
|
stopWatchingParts();
|
|
1646
2075
|
}
|
|
1647
2076
|
/** Live wallpaper-opacity updates during slider drag (no full re-apply). */
|
|
@@ -1652,6 +2081,69 @@ window.__ModuleLoader__.load({
|
|
|
1652
2081
|
function setWpBlur(v) {
|
|
1653
2082
|
if (wpEl) wpEl.style.filter = v > 0 ? `blur(${v}px)` : "none";
|
|
1654
2083
|
}
|
|
2084
|
+
//#endregion
|
|
2085
|
+
//#region src/client/utils/video.ts
|
|
2086
|
+
/**
|
|
2087
|
+
* Video-background helpers: capturing a single frame as a JPEG snapshot.
|
|
2088
|
+
* The snapshot stands in for everything that only understands still images:
|
|
2089
|
+
* the settings preview, theme-color extraction and the eyedropper. Uploads
|
|
2090
|
+
* stream the raw file over the binary upload route (see rpc.uploadVideo) —
|
|
2091
|
+
* a data-URL detour would inflate the bytes and trip the RPC body limit.
|
|
2092
|
+
*/
|
|
2093
|
+
function grabFrame(v) {
|
|
2094
|
+
const w = v.videoWidth, h = v.videoHeight;
|
|
2095
|
+
if (w <= 0 || h <= 0) return null;
|
|
2096
|
+
const canvas = document.createElement("canvas");
|
|
2097
|
+
canvas.width = w;
|
|
2098
|
+
canvas.height = h;
|
|
2099
|
+
const ctx = canvas.getContext("2d");
|
|
2100
|
+
if (!ctx) return null;
|
|
2101
|
+
ctx.drawImage(v, 0, 0, w, h);
|
|
2102
|
+
try {
|
|
2103
|
+
return canvas.toDataURL("image/jpeg", .85);
|
|
2104
|
+
} catch {
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Capture one representative frame from a video data URL as a JPEG data URL.
|
|
2110
|
+
* Seeks to ~10% of the duration (first frame is often black) and waits for
|
|
2111
|
+
* the seek to land; resolves null when the video cannot be decoded. A timeout
|
|
2112
|
+
* keeps a broken file from hanging the upload flow.
|
|
2113
|
+
*/
|
|
2114
|
+
function captureVideoSnapshot(url) {
|
|
2115
|
+
return new Promise((resolve) => {
|
|
2116
|
+
const v = document.createElement("video");
|
|
2117
|
+
let settled = false;
|
|
2118
|
+
const done = (r) => {
|
|
2119
|
+
if (settled) return;
|
|
2120
|
+
settled = true;
|
|
2121
|
+
window.clearTimeout(timer);
|
|
2122
|
+
v.removeAttribute("src");
|
|
2123
|
+
v.load();
|
|
2124
|
+
resolve(r);
|
|
2125
|
+
};
|
|
2126
|
+
const timer = window.setTimeout(() => done(grabFrame(v)), 6e3);
|
|
2127
|
+
v.muted = true;
|
|
2128
|
+
v.preload = "auto";
|
|
2129
|
+
v.playsInline = true;
|
|
2130
|
+
v.onerror = () => done(null);
|
|
2131
|
+
v.onloadeddata = () => {
|
|
2132
|
+
const target = isFinite(v.duration) && v.duration > .6 ? Math.min(v.duration * .1, 2) : 0;
|
|
2133
|
+
if (target <= 0) {
|
|
2134
|
+
done(grabFrame(v));
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
v.onseeked = () => done(grabFrame(v));
|
|
2138
|
+
try {
|
|
2139
|
+
v.currentTime = target;
|
|
2140
|
+
} catch {
|
|
2141
|
+
done(grabFrame(v));
|
|
2142
|
+
}
|
|
2143
|
+
};
|
|
2144
|
+
v.src = url;
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
1655
2147
|
const UI_CSS = `
|
|
1656
2148
|
/* The section is rendered INLINE inside the host settings dialog's content
|
|
1657
2149
|
* column: the host provides the modal chrome (backdrop, centering, closing).
|
|
@@ -1934,6 +2426,44 @@ window.__ModuleLoader__.load({
|
|
|
1934
2426
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14 10.4l-2.8-2.8-4.8 4.8" })
|
|
1935
2427
|
]
|
|
1936
2428
|
});
|
|
2429
|
+
const VideoIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
2430
|
+
size,
|
|
2431
|
+
className,
|
|
2432
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
2433
|
+
x: "1.5",
|
|
2434
|
+
y: "4.2",
|
|
2435
|
+
width: "8.8",
|
|
2436
|
+
height: "7.6",
|
|
2437
|
+
rx: "2"
|
|
2438
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10.3 6.9l4.2-2.4v7l-4.2-2.4" })]
|
|
2439
|
+
});
|
|
2440
|
+
const TextIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
2441
|
+
size,
|
|
2442
|
+
className,
|
|
2443
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 4.2h10M3 8h10M3 11.8h6.5" })
|
|
2444
|
+
});
|
|
2445
|
+
const TrajectoryIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
2446
|
+
size,
|
|
2447
|
+
className,
|
|
2448
|
+
children: [
|
|
2449
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 3.2h11M2.5 6.6h11M2.5 10h11" }),
|
|
2450
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2451
|
+
cx: "4.4",
|
|
2452
|
+
cy: "3.2",
|
|
2453
|
+
r: "1.1"
|
|
2454
|
+
}),
|
|
2455
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2456
|
+
cx: "8",
|
|
2457
|
+
cy: "6.6",
|
|
2458
|
+
r: "1.1"
|
|
2459
|
+
}),
|
|
2460
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2461
|
+
cx: "11.4",
|
|
2462
|
+
cy: "10",
|
|
2463
|
+
r: "1.1"
|
|
2464
|
+
})
|
|
2465
|
+
]
|
|
2466
|
+
});
|
|
1937
2467
|
const SlidersIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
1938
2468
|
size,
|
|
1939
2469
|
className,
|
|
@@ -2893,7 +3423,7 @@ window.__ModuleLoader__.load({
|
|
|
2893
3423
|
paint(inputRef.current, v);
|
|
2894
3424
|
}
|
|
2895
3425
|
if (valRef.current) valRef.current.textContent = fmt(v);
|
|
2896
|
-
onInput(v);
|
|
3426
|
+
onInput?.(v);
|
|
2897
3427
|
onChange(v);
|
|
2898
3428
|
};
|
|
2899
3429
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2922,7 +3452,7 @@ window.__ModuleLoader__.load({
|
|
|
2922
3452
|
const el = e.target;
|
|
2923
3453
|
const v = Number(el.value);
|
|
2924
3454
|
paint(el, v);
|
|
2925
|
-
onInput(v);
|
|
3455
|
+
onInput?.(v);
|
|
2926
3456
|
if (valRef.current) valRef.current.textContent = fmt(v);
|
|
2927
3457
|
},
|
|
2928
3458
|
onChange: (e) => onChange(Number(e.target.value))
|
|
@@ -2956,6 +3486,16 @@ window.__ModuleLoader__.load({
|
|
|
2956
3486
|
isSettings: true,
|
|
2957
3487
|
labelKey: "uiSop",
|
|
2958
3488
|
Icon: GearIcon
|
|
3489
|
+
},
|
|
3490
|
+
{
|
|
3491
|
+
isChat: true,
|
|
3492
|
+
labelKey: "uiChatRegion",
|
|
3493
|
+
Icon: TextIcon
|
|
3494
|
+
},
|
|
3495
|
+
{
|
|
3496
|
+
isTrajectory: true,
|
|
3497
|
+
labelKey: "uiTrajectory",
|
|
3498
|
+
Icon: TrajectoryIcon
|
|
2959
3499
|
}
|
|
2960
3500
|
];
|
|
2961
3501
|
function InterfacePage({ p }) {
|
|
@@ -2980,10 +3520,10 @@ window.__ModuleLoader__.load({
|
|
|
2980
3520
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2981
3521
|
className: "dab-grid-parts",
|
|
2982
3522
|
children: PARTS.map((part, i) => {
|
|
2983
|
-
const { labelKey, Icon, isSettings } = part;
|
|
3523
|
+
const { labelKey, Icon, isSettings, isChat, isTrajectory } = part;
|
|
2984
3524
|
const opKey = part.opKey;
|
|
2985
|
-
const blurKey =
|
|
2986
|
-
const opacity = isSettings ? rSop() : rOps()[opKey];
|
|
3525
|
+
const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : opKey;
|
|
3526
|
+
const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : rOps()[opKey];
|
|
2987
3527
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2988
3528
|
className: "dab-card dab-card-hover dab-rise",
|
|
2989
3529
|
style: { "--d": i + 1 },
|
|
@@ -3014,7 +3554,13 @@ window.__ModuleLoader__.load({
|
|
|
3014
3554
|
fmt: (v) => `${v}%`,
|
|
3015
3555
|
onInput: (v) => {
|
|
3016
3556
|
const op = v / 100;
|
|
3017
|
-
if (
|
|
3557
|
+
if (isChat) {
|
|
3558
|
+
cfg.chatTextOpacity = op;
|
|
3559
|
+
applyViewCards();
|
|
3560
|
+
} else if (isTrajectory) {
|
|
3561
|
+
cfg.trajectoryOpacity = op;
|
|
3562
|
+
applyTrajectoryOverrides(op);
|
|
3563
|
+
} else if (isSettings) {
|
|
3018
3564
|
cfg.settingsOpacity = op;
|
|
3019
3565
|
applySettingsOverrides(op);
|
|
3020
3566
|
} else {
|
|
@@ -3027,7 +3573,15 @@ window.__ModuleLoader__.load({
|
|
|
3027
3573
|
},
|
|
3028
3574
|
onChange: (v) => {
|
|
3029
3575
|
const op = v / 100;
|
|
3030
|
-
if (
|
|
3576
|
+
if (isChat) {
|
|
3577
|
+
cfg.chatTextOpacity = op;
|
|
3578
|
+
applyViewCards();
|
|
3579
|
+
saveConfig();
|
|
3580
|
+
} else if (isTrajectory) {
|
|
3581
|
+
cfg.trajectoryOpacity = op;
|
|
3582
|
+
applyTrajectoryOverrides(op);
|
|
3583
|
+
saveConfig();
|
|
3584
|
+
} else if (isSettings) setSop(op);
|
|
3031
3585
|
else {
|
|
3032
3586
|
const ops = { ...rOps() };
|
|
3033
3587
|
ops[opKey] = op;
|
|
@@ -3076,10 +3630,13 @@ window.__ModuleLoader__.load({
|
|
|
3076
3630
|
}
|
|
3077
3631
|
//#endregion
|
|
3078
3632
|
//#region src/client/components/BgEditor.tsx
|
|
3633
|
+
/** Placement slot for the active background type: video framing lives in its
|
|
3634
|
+
* own state so image and video edits never overwrite each other. */
|
|
3635
|
+
const activeState = () => cfg.backgroundType === "video" ? cfg.videoBgState : cfg.bgState;
|
|
3079
3636
|
function BgEditor({ url, t, onClose, onCommit }) {
|
|
3080
3637
|
const pw = Math.min(window.innerWidth * .75, 860);
|
|
3081
3638
|
const ph = Math.round(pw * window.innerHeight / window.innerWidth);
|
|
3082
|
-
const saved =
|
|
3639
|
+
const saved = activeState();
|
|
3083
3640
|
const [zoom, setZoom] = (0, react.useState)(saved.iw > 0 ? saved.zoom : 1);
|
|
3084
3641
|
const [pos, setPos] = (0, react.useState)(saved.iw > 0 ? {
|
|
3085
3642
|
x: saved.x * pw,
|
|
@@ -3110,7 +3667,7 @@ window.__ModuleLoader__.load({
|
|
|
3110
3667
|
w,
|
|
3111
3668
|
h
|
|
3112
3669
|
});
|
|
3113
|
-
const s =
|
|
3670
|
+
const s = activeState();
|
|
3114
3671
|
if (s.iw > 0 && s.iw === img.width && s.ih === img.height) {
|
|
3115
3672
|
setZoom(s.zoom);
|
|
3116
3673
|
setPos({
|
|
@@ -3264,8 +3821,30 @@ window.__ModuleLoader__.load({
|
|
|
3264
3821
|
//#endregion
|
|
3265
3822
|
//#region src/client/components/pages/BackgroundPage.tsx
|
|
3266
3823
|
const SEG_W = 108;
|
|
3824
|
+
const BG_MODES = [
|
|
3825
|
+
{
|
|
3826
|
+
mode: "fit",
|
|
3827
|
+
labelKey: "bgModeFit"
|
|
3828
|
+
},
|
|
3829
|
+
{
|
|
3830
|
+
mode: "fill",
|
|
3831
|
+
labelKey: "bgModeFill"
|
|
3832
|
+
},
|
|
3833
|
+
{
|
|
3834
|
+
mode: "stretch",
|
|
3835
|
+
labelKey: "bgModeStretch"
|
|
3836
|
+
},
|
|
3837
|
+
{
|
|
3838
|
+
mode: "tile",
|
|
3839
|
+
labelKey: "bgModeTile"
|
|
3840
|
+
},
|
|
3841
|
+
{
|
|
3842
|
+
mode: "center",
|
|
3843
|
+
labelKey: "bgModeCenter"
|
|
3844
|
+
}
|
|
3845
|
+
];
|
|
3267
3846
|
function BackgroundPage({ p }) {
|
|
3268
|
-
const { t, setWp, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, useStore } = p;
|
|
3847
|
+
const { t, setWp, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, useStore } = p;
|
|
3269
3848
|
const store = useStore((s) => s);
|
|
3270
3849
|
const storeUrl = store.url;
|
|
3271
3850
|
const backgroundType = store.backgroundType;
|
|
@@ -3275,9 +3854,16 @@ window.__ModuleLoader__.load({
|
|
|
3275
3854
|
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
3276
3855
|
const [dragOver, setDragOver] = (0, react.useState)(false);
|
|
3277
3856
|
const [spinTick, setSpinTick] = (0, react.useState)(0);
|
|
3278
|
-
const
|
|
3857
|
+
const [mode, setModeState] = (0, react.useState)(rBgMode());
|
|
3858
|
+
const isVideo = backgroundType === "video";
|
|
3859
|
+
const isStatic = backgroundType === "image" || isVideo;
|
|
3860
|
+
const isGenerated = !isStatic;
|
|
3279
3861
|
const activeGenType = isGenerated && generatedBg ? generatedBg.type : "mesh";
|
|
3280
3862
|
const onFileSelect = (f) => {
|
|
3863
|
+
if (f.type.startsWith("video/")) {
|
|
3864
|
+
setVideo(f, f.type);
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3281
3867
|
readImg(f, (d) => {
|
|
3282
3868
|
if (d) setWp(d);
|
|
3283
3869
|
});
|
|
@@ -3286,10 +3872,18 @@ window.__ModuleLoader__.load({
|
|
|
3286
3872
|
e.preventDefault();
|
|
3287
3873
|
setDragOver(false);
|
|
3288
3874
|
const f = e.dataTransfer.files?.[0];
|
|
3289
|
-
if (f && f.type.startsWith("image/")) onFileSelect(f);
|
|
3875
|
+
if (f && (f.type.startsWith("image/") || f.type.startsWith("video/"))) onFileSelect(f);
|
|
3290
3876
|
};
|
|
3291
|
-
const
|
|
3292
|
-
if (
|
|
3877
|
+
const switchToStatic = () => {
|
|
3878
|
+
if (isStatic) return;
|
|
3879
|
+
setBgType(isVideo || cfg.videoMime !== null ? "video" : "image");
|
|
3880
|
+
};
|
|
3881
|
+
const setMode = (m) => {
|
|
3882
|
+
if (m === mode) return;
|
|
3883
|
+
setModeState(m);
|
|
3884
|
+
cfg.bgMode = m;
|
|
3885
|
+
applyWp();
|
|
3886
|
+
saveConfig();
|
|
3293
3887
|
};
|
|
3294
3888
|
const setGenType = (type) => {
|
|
3295
3889
|
if (type === activeGenType) return;
|
|
@@ -3367,15 +3961,20 @@ window.__ModuleLoader__.load({
|
|
|
3367
3961
|
isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3368
3962
|
className: "dab-hero-badge",
|
|
3369
3963
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SparkleIcon, { size: 11 }), t("liveBadge")]
|
|
3964
|
+
}) : isVideo ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3965
|
+
className: "dab-hero-badge",
|
|
3966
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VideoIcon, { size: 11 }), t("bgVideoBadge")]
|
|
3370
3967
|
}) : null,
|
|
3371
3968
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3372
3969
|
className: "dab-hero-veil",
|
|
3373
|
-
children: [
|
|
3970
|
+
children: [isStatic && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3374
3971
|
type: "button",
|
|
3375
3972
|
className: "dab-btn",
|
|
3973
|
+
disabled: mode !== "fit",
|
|
3974
|
+
title: mode !== "fit" ? t("bgEditLocked") : void 0,
|
|
3376
3975
|
onClick: () => setEditorOpen(true),
|
|
3377
3976
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 13 }), t("bgEdit")]
|
|
3378
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3977
|
+
}) : isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3379
3978
|
type: "button",
|
|
3380
3979
|
className: "dab-btn",
|
|
3381
3980
|
onClick: () => {
|
|
@@ -3383,7 +3982,7 @@ window.__ModuleLoader__.load({
|
|
|
3383
3982
|
setSpinTick((x) => x + 1);
|
|
3384
3983
|
},
|
|
3385
3984
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefreshIcon, { size: 13 }), t("bgRegenerate")]
|
|
3386
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3985
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3387
3986
|
type: "button",
|
|
3388
3987
|
className: "dab-btn dab-btn-danger",
|
|
3389
3988
|
onClick: () => setWp(null),
|
|
@@ -3418,7 +4017,7 @@ window.__ModuleLoader__.load({
|
|
|
3418
4017
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3419
4018
|
type: "button",
|
|
3420
4019
|
className: `dab-seg-item${!isGenerated ? " is-active" : ""}`,
|
|
3421
|
-
onClick:
|
|
4020
|
+
onClick: switchToStatic,
|
|
3422
4021
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhotoIcon, { size: 14 }), t("bgSourceImage")]
|
|
3423
4022
|
}),
|
|
3424
4023
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -3432,28 +4031,44 @@ window.__ModuleLoader__.load({
|
|
|
3432
4031
|
]
|
|
3433
4032
|
})
|
|
3434
4033
|
}),
|
|
3435
|
-
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
4034
|
+
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3436
4035
|
className: "dab-card dab-card-hover dab-rise",
|
|
3437
4036
|
style: { "--d": 3 },
|
|
3438
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4037
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3439
4038
|
className: "dab-chip-row",
|
|
3440
4039
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3441
4040
|
type: "button",
|
|
3442
4041
|
className: "dab-btn dab-btn-primary",
|
|
3443
4042
|
onClick: () => fileRef.current?.click(),
|
|
3444
4043
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UploadIcon, { size: 14 }), t("bgChoose")]
|
|
3445
|
-
}), storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4044
|
+
}), storeUrl || isVideo ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [isStatic && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3446
4045
|
type: "button",
|
|
3447
4046
|
className: "dab-btn",
|
|
4047
|
+
disabled: mode !== "fit",
|
|
4048
|
+
title: mode !== "fit" ? t("bgEditLocked") : void 0,
|
|
3448
4049
|
onClick: () => setEditorOpen(true),
|
|
3449
4050
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 14 }), t("bgEdit")]
|
|
3450
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4051
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3451
4052
|
type: "button",
|
|
3452
4053
|
className: "dab-btn dab-btn-ghost dab-btn-danger",
|
|
3453
4054
|
onClick: () => setWp(null),
|
|
3454
4055
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 14 }), t("bgRemove")]
|
|
3455
4056
|
})] }) : null]
|
|
3456
|
-
})
|
|
4057
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4058
|
+
style: { marginTop: 16 },
|
|
4059
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4060
|
+
className: "dab-swatch-title",
|
|
4061
|
+
children: t("bgModeTitle")
|
|
4062
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4063
|
+
className: "dab-chip-row",
|
|
4064
|
+
children: BG_MODES.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4065
|
+
type: "button",
|
|
4066
|
+
className: `dab-chip${mode === m.mode ? " is-active" : ""}`,
|
|
4067
|
+
onClick: () => setMode(m.mode),
|
|
4068
|
+
children: t(m.labelKey)
|
|
4069
|
+
}, m.mode))
|
|
4070
|
+
})]
|
|
4071
|
+
})]
|
|
3457
4072
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3458
4073
|
className: "dab-card dab-rise",
|
|
3459
4074
|
style: { "--d": 3 },
|
|
@@ -3529,7 +4144,6 @@ window.__ModuleLoader__.load({
|
|
|
3529
4144
|
step: 1,
|
|
3530
4145
|
def: Math.round(generatedBg.scale * 100),
|
|
3531
4146
|
fmt: (v) => `${v}%`,
|
|
3532
|
-
onInput: () => {},
|
|
3533
4147
|
onChange: (v) => updateGenerated({ scale: v / 100 })
|
|
3534
4148
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
3535
4149
|
label: t("bgMeshIntensity"),
|
|
@@ -3538,7 +4152,6 @@ window.__ModuleLoader__.load({
|
|
|
3538
4152
|
step: 1,
|
|
3539
4153
|
def: Math.round(generatedBg.intensity * 100),
|
|
3540
4154
|
fmt: (v) => `${v}%`,
|
|
3541
|
-
onInput: () => {},
|
|
3542
4155
|
onChange: (v) => updateGenerated({ intensity: v / 100 })
|
|
3543
4156
|
})] }) : null,
|
|
3544
4157
|
activeGenType === "shader" && generatedBg?.type === "shader" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
@@ -3548,7 +4161,6 @@ window.__ModuleLoader__.load({
|
|
|
3548
4161
|
step: 1,
|
|
3549
4162
|
def: Math.round(generatedBg.speed * 100),
|
|
3550
4163
|
fmt: (v) => `${v}%`,
|
|
3551
|
-
onInput: () => {},
|
|
3552
4164
|
onChange: (v) => updateGenerated({ speed: v / 100 })
|
|
3553
4165
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
3554
4166
|
label: t("bgShaderScale"),
|
|
@@ -3557,7 +4169,6 @@ window.__ModuleLoader__.load({
|
|
|
3557
4169
|
step: 1,
|
|
3558
4170
|
def: Math.round(generatedBg.scale * 100),
|
|
3559
4171
|
fmt: (v) => `${v}%`,
|
|
3560
|
-
onInput: () => {},
|
|
3561
4172
|
onChange: (v) => updateGenerated({ scale: v / 100 })
|
|
3562
4173
|
})] }) : null,
|
|
3563
4174
|
activeGenType === "pattern" && generatedBg?.type === "pattern" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
@@ -3567,7 +4178,6 @@ window.__ModuleLoader__.load({
|
|
|
3567
4178
|
step: 1,
|
|
3568
4179
|
def: Math.round(generatedBg.density * 100),
|
|
3569
4180
|
fmt: (v) => `${v}%`,
|
|
3570
|
-
onInput: () => {},
|
|
3571
4181
|
onChange: (v) => updateGenerated({ density: v / 100 })
|
|
3572
4182
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
3573
4183
|
label: t("bgPatternScale"),
|
|
@@ -3576,7 +4186,6 @@ window.__ModuleLoader__.load({
|
|
|
3576
4186
|
step: 1,
|
|
3577
4187
|
def: Math.round(generatedBg.scale * 100),
|
|
3578
4188
|
fmt: (v) => `${v}%`,
|
|
3579
|
-
onInput: () => {},
|
|
3580
4189
|
onChange: (v) => updateGenerated({ scale: v / 100 })
|
|
3581
4190
|
})] }) : null
|
|
3582
4191
|
]
|
|
@@ -3666,7 +4275,7 @@ window.__ModuleLoader__.load({
|
|
|
3666
4275
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3667
4276
|
ref: fileRef,
|
|
3668
4277
|
type: "file",
|
|
3669
|
-
accept: "image/*",
|
|
4278
|
+
accept: "image/*,video/*",
|
|
3670
4279
|
style: { display: "none" },
|
|
3671
4280
|
onChange: (e) => {
|
|
3672
4281
|
const f = e.target.files?.[0];
|
|
@@ -3675,18 +4284,20 @@ window.__ModuleLoader__.load({
|
|
|
3675
4284
|
e.target.value = "";
|
|
3676
4285
|
}
|
|
3677
4286
|
}),
|
|
3678
|
-
editorOpen && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BgEditor, {
|
|
4287
|
+
editorOpen && storeUrl && isStatic && mode === "fit" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BgEditor, {
|
|
3679
4288
|
url: storeUrl,
|
|
3680
4289
|
t,
|
|
3681
4290
|
onClose: () => setEditorOpen(false),
|
|
3682
4291
|
onCommit: (z, x, y, iw, ih) => {
|
|
3683
|
-
|
|
4292
|
+
const st = {
|
|
3684
4293
|
zoom: z,
|
|
3685
4294
|
x,
|
|
3686
4295
|
y,
|
|
3687
4296
|
iw,
|
|
3688
4297
|
ih
|
|
3689
4298
|
};
|
|
4299
|
+
if (backgroundType === "video") cfg.videoBgState = st;
|
|
4300
|
+
else cfg.bgState = st;
|
|
3690
4301
|
applyWp();
|
|
3691
4302
|
saveConfig();
|
|
3692
4303
|
setEditorOpen(false);
|
|
@@ -3908,25 +4519,9 @@ window.__ModuleLoader__.load({
|
|
|
3908
4519
|
/**
|
|
3909
4520
|
* dsh-any-background — browser half entry.
|
|
3910
4521
|
*
|
|
3911
|
-
*
|
|
3912
|
-
*
|
|
3913
|
-
*
|
|
3914
|
-
* 2. Background image editor modal with drag-to-pan and scroll-to-zoom
|
|
3915
|
-
* inside a viewport-proportional preview rectangle.
|
|
3916
|
-
* 3. Opacity / blur sliders with zero-lag direct DOM manipulation: the
|
|
3917
|
-
* homepage background opacity (主界面) and the settings panel opacity
|
|
3918
|
-
* (设置界面透明度) are separate sliders.
|
|
3919
|
-
*
|
|
3920
|
-
* This file wires the plugin lifecycle (theme registration, wallpaper layer,
|
|
3921
|
-
* viewport watch, i18n, settings-section injection, boot restore, watchdog).
|
|
3922
|
-
* The heavy lifting lives in the sibling modules:
|
|
3923
|
-
* state.ts in-memory config mirror + getters
|
|
3924
|
-
* rpc.ts file-backed persistence over the /dsh-any-background channel
|
|
3925
|
-
* wallpaper.ts wallpaper DOM layer + inline token writes
|
|
3926
|
-
* utils/ color math, token generation, image compression
|
|
3927
|
-
* components/ ThemeSection shell + pages/, ColorWheel, BgEditor, LiveSlider
|
|
3928
|
-
* i18n.ts zh/en dictionaries
|
|
3929
|
-
* ui.css.ts scoped design-system stylesheet (classes + keyframes)
|
|
4522
|
+
* Wires the plugin lifecycle: theme registration, wallpaper layer, viewport
|
|
4523
|
+
* watch, i18n, settings-section injection, boot restore, watchdog. The heavy
|
|
4524
|
+
* lifting lives in the sibling modules (state/rpc/wallpaper/utils/components).
|
|
3930
4525
|
*/
|
|
3931
4526
|
const name = "dsh-any-background";
|
|
3932
4527
|
const inject = [
|
|
@@ -3959,13 +4554,10 @@ window.__ModuleLoader__.load({
|
|
|
3959
4554
|
customDispose?.();
|
|
3960
4555
|
if (colorTimerRef.current !== null) window.clearTimeout(colorTimerRef.current);
|
|
3961
4556
|
}, "dsh-any-background: skin dispose");
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}`;
|
|
3967
|
-
document.head.appendChild(styleEl);
|
|
3968
|
-
}
|
|
4557
|
+
const styleEl = document.createElement("style");
|
|
4558
|
+
styleEl.dataset.plugin = "dsh-any-background";
|
|
4559
|
+
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${TRAJECTORY_STYLE_RULE}`;
|
|
4560
|
+
document.head.appendChild(styleEl);
|
|
3969
4561
|
ctx.effect(() => () => {
|
|
3970
4562
|
styleEl?.parentNode?.removeChild(styleEl);
|
|
3971
4563
|
}, "dsh-any-background: gradient");
|
|
@@ -4020,7 +4612,22 @@ window.__ModuleLoader__.load({
|
|
|
4020
4612
|
const [h, s, l] = rColor();
|
|
4021
4613
|
registerCustom(h, s, l);
|
|
4022
4614
|
}
|
|
4023
|
-
if (cfg.backgroundType
|
|
4615
|
+
if (cfg.backgroundType === "video") {
|
|
4616
|
+
const v = rWpVideo();
|
|
4617
|
+
if (v) {
|
|
4618
|
+
captureVideoSnapshot(v).then((snap) => {
|
|
4619
|
+
if (rWpVideo() !== v) return;
|
|
4620
|
+
setWpVideoSnapshot(snap);
|
|
4621
|
+
applyThemeColor();
|
|
4622
|
+
syncBg();
|
|
4623
|
+
});
|
|
4624
|
+
applyWp();
|
|
4625
|
+
} else {
|
|
4626
|
+
cfg.backgroundType = "image";
|
|
4627
|
+
setWpUrl(rWpImage());
|
|
4628
|
+
applyThemeColor();
|
|
4629
|
+
}
|
|
4630
|
+
} else if (cfg.backgroundType !== "image") {
|
|
4024
4631
|
if (cfg.regenerateOnReload) regenerateGeneratedBg();
|
|
4025
4632
|
else if (cfg.generatedBg) updateGeneratedBg(cfg.generatedBg);
|
|
4026
4633
|
persistConfig();
|
|
@@ -4103,9 +4710,45 @@ window.__ModuleLoader__.load({
|
|
|
4103
4710
|
setWpUrl(u);
|
|
4104
4711
|
setBgState({ ...DEFAULT_CONFIG.bgState });
|
|
4105
4712
|
persistWallpaper(u);
|
|
4713
|
+
if (u === null) {
|
|
4714
|
+
setWpVideoUrl(null, null);
|
|
4715
|
+
persistVideo(null);
|
|
4716
|
+
}
|
|
4106
4717
|
applyThemeColor();
|
|
4107
4718
|
syncBg();
|
|
4108
4719
|
},
|
|
4720
|
+
setVideo: async (u, mime) => {
|
|
4721
|
+
setBgDark(null);
|
|
4722
|
+
if (u === null) {
|
|
4723
|
+
setWpVideoUrl(null, null);
|
|
4724
|
+
cfg.backgroundType = "image";
|
|
4725
|
+
setWpUrl(rWpImage());
|
|
4726
|
+
persistVideo(null);
|
|
4727
|
+
applyThemeColor();
|
|
4728
|
+
syncBg();
|
|
4729
|
+
saveConfig();
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
let ok;
|
|
4733
|
+
if (typeof u === "string") ok = await persistVideo(u);
|
|
4734
|
+
else ok = await uploadVideo(u, mime ?? u.type ?? "video/mp4");
|
|
4735
|
+
const live = ok ? VIDEO_SERVE_URL : typeof u === "string" ? u : null;
|
|
4736
|
+
if (live === null) return;
|
|
4737
|
+
cfg.backgroundType = "video";
|
|
4738
|
+
setWpUrl(null);
|
|
4739
|
+
cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
|
|
4740
|
+
setWpVideoUrl(live, mime ?? (typeof u === "string" ? null : u.type) ?? null);
|
|
4741
|
+
applyWp();
|
|
4742
|
+
saveConfig();
|
|
4743
|
+
syncBg();
|
|
4744
|
+
const applied = rWpVideo();
|
|
4745
|
+
captureVideoSnapshot(live).then((snap) => {
|
|
4746
|
+
if (rWpVideo() !== applied) return;
|
|
4747
|
+
setWpVideoSnapshot(snap);
|
|
4748
|
+
applyThemeColor();
|
|
4749
|
+
syncBg();
|
|
4750
|
+
});
|
|
4751
|
+
},
|
|
4109
4752
|
setBgType: (type) => {
|
|
4110
4753
|
setBackgroundType(type);
|
|
4111
4754
|
saveConfig();
|
|
@@ -4158,7 +4801,7 @@ window.__ModuleLoader__.load({
|
|
|
4158
4801
|
extractColor: async () => {
|
|
4159
4802
|
const url = rWp();
|
|
4160
4803
|
if (!url) return false;
|
|
4161
|
-
const hsl = await extractWallpaperColor(url, rBgState());
|
|
4804
|
+
const hsl = await extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState());
|
|
4162
4805
|
if (!hsl) return false;
|
|
4163
4806
|
cfg.color = hsl;
|
|
4164
4807
|
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
@@ -4169,12 +4812,29 @@ window.__ModuleLoader__.load({
|
|
|
4169
4812
|
bound?.syncColor(hsv, colorRev);
|
|
4170
4813
|
return true;
|
|
4171
4814
|
},
|
|
4172
|
-
exportTheme: () => {
|
|
4815
|
+
exportTheme: async () => {
|
|
4816
|
+
let videoPayload = null;
|
|
4817
|
+
if (cfg.backgroundType === "video") {
|
|
4818
|
+
const vurl = rWpVideo();
|
|
4819
|
+
if (vurl) try {
|
|
4820
|
+
const blob = await fetch(vurl).then((r) => r.blob());
|
|
4821
|
+
videoPayload = await new Promise((resolve, reject) => {
|
|
4822
|
+
const fr = new FileReader();
|
|
4823
|
+
fr.onload = () => resolve(fr.result);
|
|
4824
|
+
fr.onerror = () => reject(fr.error);
|
|
4825
|
+
fr.readAsDataURL(blob);
|
|
4826
|
+
});
|
|
4827
|
+
if (videoPayload && !/^data:video\//.test(videoPayload)) videoPayload = videoPayload.replace(/^data:[^;,]*/, `data:${cfg.videoMime ?? "video/mp4"}`);
|
|
4828
|
+
} catch {
|
|
4829
|
+
videoPayload = null;
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4173
4832
|
const payload = {
|
|
4174
4833
|
version: 2,
|
|
4175
4834
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4176
4835
|
config: cfg,
|
|
4177
|
-
wallpaper: cfg.backgroundType === "image" ? rWp() : null
|
|
4836
|
+
wallpaper: cfg.backgroundType === "image" ? rWp() : null,
|
|
4837
|
+
video: videoPayload
|
|
4178
4838
|
};
|
|
4179
4839
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
|
4180
4840
|
const url = URL.createObjectURL(blob);
|
|
@@ -4191,7 +4851,37 @@ window.__ModuleLoader__.load({
|
|
|
4191
4851
|
const d = data;
|
|
4192
4852
|
if (typeof d.config !== "object" || d.config === null) return false;
|
|
4193
4853
|
adoptConfig(d.config);
|
|
4194
|
-
if (cfg.backgroundType === "
|
|
4854
|
+
if (cfg.backgroundType === "video") {
|
|
4855
|
+
const video = typeof d.video === "string" && /^data:video\//.test(d.video) ? d.video : null;
|
|
4856
|
+
if (video !== null) {
|
|
4857
|
+
let ok = false;
|
|
4858
|
+
try {
|
|
4859
|
+
const blob = await fetch(video).then((r) => r.blob());
|
|
4860
|
+
ok = await uploadVideo(blob, cfg.videoMime ?? blob.type ?? "video/mp4");
|
|
4861
|
+
} catch {
|
|
4862
|
+
ok = false;
|
|
4863
|
+
}
|
|
4864
|
+
if (!ok) ok = await persistVideo(video);
|
|
4865
|
+
const live = ok ? VIDEO_SERVE_URL : video;
|
|
4866
|
+
setWpVideoUrl(live, cfg.videoMime);
|
|
4867
|
+
applyWp();
|
|
4868
|
+
const applied = rWpVideo();
|
|
4869
|
+
captureVideoSnapshot(live).then((snap) => {
|
|
4870
|
+
if (rWpVideo() !== applied) return;
|
|
4871
|
+
setWpVideoSnapshot(snap);
|
|
4872
|
+
applyThemeColor();
|
|
4873
|
+
syncBg();
|
|
4874
|
+
});
|
|
4875
|
+
} else {
|
|
4876
|
+
setWpVideoUrl(null, null);
|
|
4877
|
+
persistVideo(null);
|
|
4878
|
+
cfg.backgroundType = "image";
|
|
4879
|
+
setWpImageUrl(null);
|
|
4880
|
+
setWpUrl(null);
|
|
4881
|
+
persistWallpaper(null);
|
|
4882
|
+
applyThemeColor();
|
|
4883
|
+
}
|
|
4884
|
+
} else if (cfg.backgroundType === "image") {
|
|
4195
4885
|
const wallpaper = typeof d.wallpaper === "string" && /^data:image\//.test(d.wallpaper) ? d.wallpaper : null;
|
|
4196
4886
|
setWpImageUrl(wallpaper);
|
|
4197
4887
|
setWpUrl(wallpaper);
|