dsh-any-background 0.1.8 → 0.2.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/README.md +20 -5
- package/README.zh.md +20 -5
- package/lib/client.js +1097 -192
- package/lib/client.js.map +1 -1
- package/lib/index.js +314 -47
- package/lib/invariant.js +0 -11
- package/package.json +8 -6
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: "在色轮外圈选择色相,在内部方形中调整饱和度和明度;双击滑块可恢复默认值",
|
|
@@ -32,20 +32,31 @@ window.__ModuleLoader__.load({
|
|
|
32
32
|
uiOpacityBg: "主背景",
|
|
33
33
|
uiOpacitySide: "侧边栏",
|
|
34
34
|
uiOpacityCard: "对话框中选项面板",
|
|
35
|
+
uiOpacityInput: "输入框与控件",
|
|
35
36
|
uiSop: "设置界面透明度",
|
|
37
|
+
uiChatRegion: "对话文本框",
|
|
38
|
+
uiTrajectory: "轨迹页",
|
|
36
39
|
bgTitle: "背景",
|
|
37
|
-
bgChoose: "
|
|
40
|
+
bgChoose: "选择图片或视频",
|
|
38
41
|
bgRemove: "移除背景",
|
|
39
42
|
bgEdit: "编辑位置",
|
|
43
|
+
bgEditLocked: "仅「适应」模式可编辑位置",
|
|
40
44
|
wpOpacity: "背景透明度",
|
|
41
45
|
bgBlur: "背景模糊",
|
|
42
|
-
|
|
46
|
+
bgModeTitle: "布局模式",
|
|
47
|
+
bgModeFit: "适应",
|
|
48
|
+
bgModeFill: "填充",
|
|
49
|
+
bgModeStretch: "拉伸",
|
|
50
|
+
bgModeTile: "平铺",
|
|
51
|
+
bgModeCenter: "居中",
|
|
52
|
+
bgVideoBadge: "视频",
|
|
53
|
+
bgSourceImage: "图片 / 视频",
|
|
43
54
|
bgSourceGenerated: "动态生成",
|
|
44
|
-
dropHint: "
|
|
55
|
+
dropHint: "点击选择图片或视频,或将文件拖到这里",
|
|
45
56
|
liveBadge: "动态壁纸",
|
|
46
|
-
bgHint: "
|
|
57
|
+
bgHint: "「适应」布局模式下悬停预览可编辑图片/视频的位置与缩放(其余布局模式按模式自动排布);动态生成模式下可选择类型、预设与参数。绑定种子后刷新页面壁纸保持不变,取消绑定则每次刷新随机换新",
|
|
47
58
|
editorTitle: "背景编辑器",
|
|
48
|
-
editorHint: "
|
|
59
|
+
editorHint: "拖动移动画面,滚轮缩放大小",
|
|
49
60
|
editorCommit: "确认",
|
|
50
61
|
editorCancel: "取消",
|
|
51
62
|
editorReset: "重置",
|
|
@@ -107,7 +118,7 @@ window.__ModuleLoader__.load({
|
|
|
107
118
|
pageProfile: "Profile",
|
|
108
119
|
descColor: "Drag the wheel or type exact values — the whole UI follows live",
|
|
109
120
|
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",
|
|
121
|
+
descBackground: "Upload an image or a video as wallpaper and place it freely, or let the algorithm paint a live background",
|
|
111
122
|
descProfile: "Export the current theme as a file, or restore it from one",
|
|
112
123
|
colorTitle: "Theme color",
|
|
113
124
|
colorHint: "Pick hue on the outer ring, adjust saturation & lightness in the square; double-click a slider to reset",
|
|
@@ -119,18 +130,29 @@ window.__ModuleLoader__.load({
|
|
|
119
130
|
uiOpacityBg: "Main background",
|
|
120
131
|
uiOpacitySide: "Sidebar",
|
|
121
132
|
uiOpacityCard: "Cards & panels",
|
|
133
|
+
uiOpacityInput: "Input & controls",
|
|
122
134
|
uiSop: "Settings interface opacity",
|
|
135
|
+
uiChatRegion: "Conversation text frame",
|
|
136
|
+
uiTrajectory: "Trajectory view",
|
|
123
137
|
bgTitle: "Background",
|
|
124
|
-
bgChoose: "Choose image",
|
|
138
|
+
bgChoose: "Choose image or video",
|
|
125
139
|
bgRemove: "Remove background",
|
|
126
140
|
bgEdit: "Edit position",
|
|
141
|
+
bgEditLocked: "Position editing is only available in Fit mode",
|
|
127
142
|
wpOpacity: "Background opacity",
|
|
128
143
|
bgBlur: "Background blur",
|
|
129
|
-
|
|
144
|
+
bgModeTitle: "Layout mode",
|
|
145
|
+
bgModeFit: "Fit",
|
|
146
|
+
bgModeFill: "Fill",
|
|
147
|
+
bgModeStretch: "Stretch",
|
|
148
|
+
bgModeTile: "Tile",
|
|
149
|
+
bgModeCenter: "Center",
|
|
150
|
+
bgVideoBadge: "Video",
|
|
151
|
+
bgSourceImage: "Image / Video",
|
|
130
152
|
bgSourceGenerated: "Generated",
|
|
131
|
-
dropHint: "Click to choose an image, or drop one here",
|
|
153
|
+
dropHint: "Click to choose an image or video, or drop one here",
|
|
132
154
|
liveBadge: "Live wallpaper",
|
|
133
|
-
bgHint: "In
|
|
155
|
+
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
156
|
editorTitle: "Background editor",
|
|
135
157
|
editorHint: "Drag to move, scroll to zoom",
|
|
136
158
|
editorCommit: "Confirm",
|
|
@@ -191,13 +213,17 @@ window.__ModuleLoader__.load({
|
|
|
191
213
|
opacities: {
|
|
192
214
|
bg: .85,
|
|
193
215
|
sidebar: .93,
|
|
194
|
-
card: 1
|
|
216
|
+
card: 1,
|
|
217
|
+
input: 1
|
|
195
218
|
},
|
|
196
219
|
blurs: {
|
|
197
220
|
bg: 0,
|
|
198
221
|
sidebar: 0,
|
|
199
222
|
card: 0,
|
|
200
|
-
settings: 0
|
|
223
|
+
settings: 0,
|
|
224
|
+
chat: 0,
|
|
225
|
+
trajectory: 0,
|
|
226
|
+
input: 0
|
|
201
227
|
},
|
|
202
228
|
settingsOpacity: 1,
|
|
203
229
|
wallpaperOpacity: 1,
|
|
@@ -209,24 +235,68 @@ window.__ModuleLoader__.load({
|
|
|
209
235
|
iw: 0,
|
|
210
236
|
ih: 0
|
|
211
237
|
},
|
|
238
|
+
videoBgState: {
|
|
239
|
+
zoom: 1,
|
|
240
|
+
x: 0,
|
|
241
|
+
y: 0,
|
|
242
|
+
iw: 0,
|
|
243
|
+
ih: 0
|
|
244
|
+
},
|
|
212
245
|
backgroundType: "image",
|
|
246
|
+
bgMode: "fit",
|
|
247
|
+
videoMime: null,
|
|
213
248
|
generatedBg: null,
|
|
214
|
-
regenerateOnReload: false
|
|
249
|
+
regenerateOnReload: false,
|
|
250
|
+
chatTextOpacity: 0,
|
|
251
|
+
trajectoryOpacity: 1
|
|
215
252
|
};
|
|
253
|
+
const clamp01 = (n, def) => typeof n === "number" ? Math.min(1, Math.max(0, n)) : def;
|
|
216
254
|
let cfg = {
|
|
217
255
|
...DEFAULT_CONFIG,
|
|
218
256
|
opacities: { ...DEFAULT_CONFIG.opacities },
|
|
219
257
|
blurs: { ...DEFAULT_CONFIG.blurs },
|
|
220
|
-
bgState: { ...DEFAULT_CONFIG.bgState }
|
|
258
|
+
bgState: { ...DEFAULT_CONFIG.bgState },
|
|
259
|
+
videoBgState: { ...DEFAULT_CONFIG.videoBgState }
|
|
221
260
|
};
|
|
222
261
|
let wpImageUrl = null;
|
|
223
262
|
let wpUrl = null;
|
|
263
|
+
let wpVideoUrl = null;
|
|
264
|
+
/** Captured video frame standing in for previews/color extraction (still-image APIs). */
|
|
265
|
+
let wpVideoSnapshot = null;
|
|
266
|
+
/** Local blob URL backing an in-session video; revoked when replaced or cleared. */
|
|
267
|
+
let wpVideoObjectUrl = null;
|
|
224
268
|
function setWpImageUrl(url) {
|
|
225
269
|
wpImageUrl = url;
|
|
226
270
|
}
|
|
227
271
|
function setWpUrl(url) {
|
|
228
272
|
wpUrl = url;
|
|
229
273
|
}
|
|
274
|
+
let videoRev = 0;
|
|
275
|
+
function setWpVideoUrl(url, mime) {
|
|
276
|
+
if (url === null) {
|
|
277
|
+
wpVideoUrl = null;
|
|
278
|
+
wpVideoSnapshot = null;
|
|
279
|
+
} else {
|
|
280
|
+
videoRev++;
|
|
281
|
+
wpVideoUrl = url.startsWith("blob:") ? url : `${url}${url.includes("?") ? "&" : "?"}r=${videoRev}`;
|
|
282
|
+
}
|
|
283
|
+
if (wpVideoObjectUrl !== null && wpVideoObjectUrl !== url) {
|
|
284
|
+
URL.revokeObjectURL(wpVideoObjectUrl);
|
|
285
|
+
wpVideoObjectUrl = null;
|
|
286
|
+
}
|
|
287
|
+
if (url !== null && url.startsWith("blob:")) wpVideoObjectUrl = url;
|
|
288
|
+
cfg.videoMime = url ? mime : null;
|
|
289
|
+
}
|
|
290
|
+
function setWpVideoSnapshot(url) {
|
|
291
|
+
wpVideoSnapshot = url;
|
|
292
|
+
}
|
|
293
|
+
/** Release the in-session video object URL (plugin teardown). */
|
|
294
|
+
function disposeVideoObjectUrl() {
|
|
295
|
+
if (wpVideoObjectUrl !== null) {
|
|
296
|
+
URL.revokeObjectURL(wpVideoObjectUrl);
|
|
297
|
+
wpVideoObjectUrl = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
230
300
|
function setBgState(s) {
|
|
231
301
|
cfg.bgState = s;
|
|
232
302
|
}
|
|
@@ -250,39 +320,78 @@ window.__ModuleLoader__.load({
|
|
|
250
320
|
function rWpImage() {
|
|
251
321
|
return wpImageUrl;
|
|
252
322
|
}
|
|
253
|
-
|
|
323
|
+
function rWpVideo() {
|
|
324
|
+
return wpVideoUrl;
|
|
325
|
+
}
|
|
326
|
+
function rBgMode() {
|
|
327
|
+
return cfg.bgMode ?? DEFAULT_CONFIG.bgMode;
|
|
328
|
+
}
|
|
329
|
+
function rChatTextOpacity() {
|
|
330
|
+
return clamp01(cfg.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity);
|
|
331
|
+
}
|
|
332
|
+
function rTrajectoryOpacity() {
|
|
333
|
+
return clamp01(cfg.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity);
|
|
334
|
+
}
|
|
335
|
+
/** Display URL: the uploaded image/video snapshot per active type, else the generated snapshot. */
|
|
254
336
|
function rWp() {
|
|
255
|
-
|
|
337
|
+
if (cfg.backgroundType === "image") return wpImageUrl;
|
|
338
|
+
if (cfg.backgroundType === "video") return wpVideoSnapshot;
|
|
339
|
+
return wpUrl;
|
|
256
340
|
}
|
|
257
341
|
function rOps() {
|
|
258
342
|
const o = cfg.opacities ?? {};
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
343
|
+
const out = {};
|
|
344
|
+
for (const k of [
|
|
345
|
+
"bg",
|
|
346
|
+
"sidebar",
|
|
347
|
+
"card",
|
|
348
|
+
"input"
|
|
349
|
+
]) out[k] = clamp01(o[k], DEFAULT_CONFIG.opacities[k]);
|
|
350
|
+
return out;
|
|
264
351
|
}
|
|
265
352
|
function rBlurs() {
|
|
266
353
|
const b = cfg.blurs ?? {};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
354
|
+
const out = {};
|
|
355
|
+
for (const k of [
|
|
356
|
+
"bg",
|
|
357
|
+
"sidebar",
|
|
358
|
+
"card",
|
|
359
|
+
"settings",
|
|
360
|
+
"chat",
|
|
361
|
+
"trajectory",
|
|
362
|
+
"input"
|
|
363
|
+
]) {
|
|
364
|
+
const v = b[k];
|
|
365
|
+
out[k] = typeof v === "number" ? Math.min(60, Math.max(0, v)) : DEFAULT_CONFIG.blurs[k];
|
|
366
|
+
}
|
|
367
|
+
return out;
|
|
273
368
|
}
|
|
274
369
|
function rWop() {
|
|
275
|
-
return
|
|
370
|
+
return clamp01(cfg.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity);
|
|
276
371
|
}
|
|
277
372
|
function rBl() {
|
|
278
373
|
return typeof cfg.blur === "number" ? Math.min(60, Math.max(0, cfg.blur)) : DEFAULT_CONFIG.blur;
|
|
279
374
|
}
|
|
280
375
|
function rSop() {
|
|
281
|
-
return
|
|
376
|
+
return clamp01(cfg.settingsOpacity, DEFAULT_CONFIG.settingsOpacity);
|
|
282
377
|
}
|
|
283
378
|
function rBgState() {
|
|
284
379
|
return cfg.bgState;
|
|
285
380
|
}
|
|
381
|
+
function rVideoBgState() {
|
|
382
|
+
return cfg.videoBgState;
|
|
383
|
+
}
|
|
384
|
+
const num = (n, def) => typeof n === "number" ? n : def;
|
|
385
|
+
const cl = (n, lo, hi, def) => typeof n === "number" ? Math.min(hi, Math.max(lo, n)) : def;
|
|
386
|
+
function adoptBgState(s) {
|
|
387
|
+
return {
|
|
388
|
+
zoom: num(s.zoom, 1),
|
|
389
|
+
x: num(s.x, 0),
|
|
390
|
+
y: num(s.y, 0),
|
|
391
|
+
iw: typeof s.iw === "number" && s.iw > 0 ? s.iw : 0,
|
|
392
|
+
ih: typeof s.ih === "number" && s.ih > 0 ? s.ih : 0
|
|
393
|
+
};
|
|
394
|
+
}
|
|
286
395
|
/** Move a possibly-absent partial config into the shape the UI reads. */
|
|
287
396
|
function adoptConfig(raw) {
|
|
288
397
|
const c = raw ?? {};
|
|
@@ -291,48 +400,64 @@ window.__ModuleLoader__.load({
|
|
|
291
400
|
c.color[1],
|
|
292
401
|
c.color[2]
|
|
293
402
|
] : null;
|
|
294
|
-
const bg = c.bgState ?? {};
|
|
295
403
|
const legacy = typeof c.opacity === "number" ? c.opacity : null;
|
|
296
404
|
const ops = c.opacities ?? {};
|
|
297
405
|
const bl = c.blurs ?? {};
|
|
298
|
-
const
|
|
406
|
+
const blurs = {};
|
|
407
|
+
for (const k of [
|
|
408
|
+
"bg",
|
|
409
|
+
"sidebar",
|
|
410
|
+
"card",
|
|
411
|
+
"settings",
|
|
412
|
+
"chat",
|
|
413
|
+
"trajectory",
|
|
414
|
+
"input"
|
|
415
|
+
]) blurs[k] = num(bl[k], DEFAULT_CONFIG.blurs[k]);
|
|
416
|
+
const bgType = [
|
|
417
|
+
"video",
|
|
418
|
+
"mesh",
|
|
419
|
+
"shader",
|
|
420
|
+
"pattern"
|
|
421
|
+
].includes(c.backgroundType) ? c.backgroundType : DEFAULT_CONFIG.backgroundType;
|
|
422
|
+
const bgMode = [
|
|
423
|
+
"fit",
|
|
424
|
+
"fill",
|
|
425
|
+
"stretch",
|
|
426
|
+
"tile",
|
|
427
|
+
"center"
|
|
428
|
+
].includes(c.bgMode) ? c.bgMode : DEFAULT_CONFIG.bgMode;
|
|
299
429
|
const gen = c.generatedBg && typeof c.generatedBg === "object" ? c.generatedBg : null;
|
|
300
430
|
const generatedBg = gen && gen.type === bgType ? c.generatedBg : null;
|
|
301
431
|
cfg = {
|
|
302
432
|
color,
|
|
303
433
|
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
|
|
434
|
+
bg: num(ops.bg, legacy ?? DEFAULT_CONFIG.opacities.bg),
|
|
435
|
+
sidebar: num(ops.sidebar, legacy !== null ? Math.min(1, legacy + .08) : DEFAULT_CONFIG.opacities.sidebar),
|
|
436
|
+
card: num(ops.card, DEFAULT_CONFIG.opacities.card),
|
|
437
|
+
input: num(ops.input, DEFAULT_CONFIG.opacities.input)
|
|
323
438
|
},
|
|
439
|
+
blurs,
|
|
440
|
+
settingsOpacity: num(c.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
|
|
441
|
+
wallpaperOpacity: num(c.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity),
|
|
442
|
+
blur: num(c.blur, DEFAULT_CONFIG.blur),
|
|
443
|
+
bgState: adoptBgState(c.bgState ?? {}),
|
|
444
|
+
videoBgState: adoptBgState(c.videoBgState ?? {}),
|
|
324
445
|
backgroundType: bgType,
|
|
446
|
+
bgMode,
|
|
447
|
+
videoMime: typeof c.videoMime === "string" ? c.videoMime : null,
|
|
325
448
|
generatedBg: generatedBg ? normalizeGeneratedBg(generatedBg) : null,
|
|
326
|
-
regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload
|
|
449
|
+
regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
450
|
+
chatTextOpacity: clamp01(c.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
451
|
+
trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity)
|
|
327
452
|
};
|
|
328
453
|
}
|
|
329
454
|
function normalizeGeneratedBg(p) {
|
|
330
455
|
if (!p) return null;
|
|
331
456
|
if (p.type === "mesh") return {
|
|
332
457
|
type: "mesh",
|
|
333
|
-
seed:
|
|
334
|
-
scale:
|
|
335
|
-
intensity:
|
|
458
|
+
seed: num(p.seed, 0),
|
|
459
|
+
scale: cl(p.scale, .3, 3, 1),
|
|
460
|
+
intensity: cl(p.intensity, 0, 1, .6)
|
|
336
461
|
};
|
|
337
462
|
if (p.type === "shader") return {
|
|
338
463
|
type: "shader",
|
|
@@ -341,8 +466,8 @@ window.__ModuleLoader__.load({
|
|
|
341
466
|
"nebula",
|
|
342
467
|
"noise"
|
|
343
468
|
].includes(p.preset) ? p.preset : "aurora",
|
|
344
|
-
speed:
|
|
345
|
-
scale:
|
|
469
|
+
speed: cl(p.speed, 0, 2, .3),
|
|
470
|
+
scale: cl(p.scale, .3, 3, 1),
|
|
346
471
|
seed: typeof p.seed === "number" ? Math.floor(p.seed) : 0
|
|
347
472
|
};
|
|
348
473
|
return {
|
|
@@ -352,14 +477,18 @@ window.__ModuleLoader__.load({
|
|
|
352
477
|
"waves",
|
|
353
478
|
"poly"
|
|
354
479
|
].includes(p.preset) ? p.preset : "dots",
|
|
355
|
-
density:
|
|
356
|
-
scale:
|
|
480
|
+
density: cl(p.density, 0, 1, .5),
|
|
481
|
+
scale: cl(p.scale, .3, 3, 1),
|
|
357
482
|
seed: typeof p.seed === "number" ? Math.floor(p.seed) : 0
|
|
358
483
|
};
|
|
359
484
|
}
|
|
360
485
|
//#endregion
|
|
361
486
|
//#region src/client/rpc.ts
|
|
362
487
|
const RPC_CHANNEL = "/dsh-any-background";
|
|
488
|
+
/** Same-origin serve URL of the persisted video (enough for <video src>/fetch). */
|
|
489
|
+
const VIDEO_SERVE_URL = "/dsh-any-background/video";
|
|
490
|
+
/** HTTP route new videos are POSTed to as raw bytes (see uploadVideo). */
|
|
491
|
+
const VIDEO_UPLOAD_URL = "/dsh-any-background/video/upload";
|
|
363
492
|
const RPC_NS = "dshAnyBackground";
|
|
364
493
|
const rpcEndpoint = (method) => `${RPC_NS}/${method}`;
|
|
365
494
|
let rpcCallFn = null;
|
|
@@ -397,7 +526,7 @@ window.__ModuleLoader__.load({
|
|
|
397
526
|
function persistConfig() {
|
|
398
527
|
rpcCall("writeConfig", { config: cfg });
|
|
399
528
|
}
|
|
400
|
-
/** Load the persisted theme (config + wallpaper) from the node half. */
|
|
529
|
+
/** Load the persisted theme (config + wallpaper + video URL) from the node half. */
|
|
401
530
|
async function loadPersisted() {
|
|
402
531
|
const data = await rpcCall("read", {});
|
|
403
532
|
if (data && typeof data === "object") {
|
|
@@ -405,13 +534,34 @@ window.__ModuleLoader__.load({
|
|
|
405
534
|
if (d.config) adoptConfig(d.config);
|
|
406
535
|
if (typeof d.wallpaper === "string") setWpImageUrl(d.wallpaper);
|
|
407
536
|
else if (d.wallpaper === null) setWpImageUrl(null);
|
|
537
|
+
if (typeof d.videoUrl === "string") setWpVideoUrl(d.videoUrl, cfg.videoMime);
|
|
538
|
+
else if (d.videoUrl === null) setWpVideoUrl(null, null);
|
|
408
539
|
if (cfg.backgroundType === "image") setWpUrl(d.wallpaper === null ? null : d.wallpaper);
|
|
409
540
|
}
|
|
410
541
|
}
|
|
411
|
-
/** Persist a wallpaper (null removes it)
|
|
542
|
+
/** Persist a wallpaper (null removes it); one-shot, no debounce. */
|
|
412
543
|
function persistWallpaper(dataUrl) {
|
|
413
544
|
rpcCall("setWallpaper", { dataUrl });
|
|
414
545
|
}
|
|
546
|
+
/** Persist a background video (null removes it); resolves true once on disk,
|
|
547
|
+
* so callers only switch playback to the serve URL after acceptance. */
|
|
548
|
+
async function persistVideo(dataUrl) {
|
|
549
|
+
return await rpcCall("setVideo", { dataUrl }) === true;
|
|
550
|
+
}
|
|
551
|
+
/** Upload a video's raw bytes over HTTP (MIME in Content-Type, body untouched
|
|
552
|
+
* — no base64 inflation that would blow the RPC body limit on large clips). */
|
|
553
|
+
async function uploadVideo(blob, mime) {
|
|
554
|
+
try {
|
|
555
|
+
return (await fetch(VIDEO_UPLOAD_URL, {
|
|
556
|
+
method: "POST",
|
|
557
|
+
headers: { "Content-Type": mime || "application/octet-stream" },
|
|
558
|
+
body: blob
|
|
559
|
+
})).ok;
|
|
560
|
+
} catch (e) {
|
|
561
|
+
console.warn("dsh-any-background: video upload failed", e);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
415
565
|
//#endregion
|
|
416
566
|
//#region src/client/utils/color.ts
|
|
417
567
|
function hsvToHsl(h, s, v) {
|
|
@@ -437,13 +587,8 @@ window.__ModuleLoader__.load({
|
|
|
437
587
|
* same token set, and applyWp / applyCustomTokens / applySettingsOverrides call
|
|
438
588
|
* this repeatedly (slider drags, viewport re-applies), so cache the last result
|
|
439
589
|
* 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
590
|
*/
|
|
446
|
-
function genTokens(hue, sat, lit
|
|
591
|
+
function genTokens(hue, sat, lit) {
|
|
447
592
|
const key = `${hue}|${sat}|${lit}`;
|
|
448
593
|
if (tokensCacheKey === key && tokensCache) return tokensCache;
|
|
449
594
|
tokensCacheKey = key;
|
|
@@ -478,6 +623,7 @@ window.__ModuleLoader__.load({
|
|
|
478
623
|
"--dsw-alias-button-primary-hover": hsl(h(0), s(.1), Math.max(l(.28), .58)),
|
|
479
624
|
"--dsw-alias-button-primary-dimmed": hsl(h(0), s(0), l(.07)),
|
|
480
625
|
"--dsw-alias-button-elevated-fill": hsl(h(0), s(0), l(.04)),
|
|
626
|
+
"--dsw-alias-button-floating-hover": hsl(h(0), s(0), l(.1)),
|
|
481
627
|
"--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), Math.max(l(.15), .4), .12),
|
|
482
628
|
"--dsw-alias-interactive-bg-active": rgba(h(0), s(0), Math.max(l(.15), .4), .2),
|
|
483
629
|
"--dsw-alias-markdown-code-block": hsl(h(0), s(0), l(-.06)),
|
|
@@ -489,6 +635,7 @@ window.__ModuleLoader__.load({
|
|
|
489
635
|
"--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(0), l(.04)),
|
|
490
636
|
"--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(0), l(0)),
|
|
491
637
|
"--dsw-specific-input-major": hsl(h(0), s(0), l(.02)),
|
|
638
|
+
"--dsw-specific-menu": hsl(h(0), s(0), l(.08)),
|
|
492
639
|
"--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.05), l(.12)),
|
|
493
640
|
"--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.05), l(.16)),
|
|
494
641
|
"--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.05), l(.22)),
|
|
@@ -516,6 +663,7 @@ window.__ModuleLoader__.load({
|
|
|
516
663
|
"--dsw-alias-button-primary-hover": hsl(h(0), s(.05), Math.min(l(-.12), .5)),
|
|
517
664
|
"--dsw-alias-button-primary-dimmed": hsl(h(0), s(-.1), l(-.03)),
|
|
518
665
|
"--dsw-alias-button-elevated-fill": hsl(h(0), s(-.1), l(.1)),
|
|
666
|
+
"--dsw-alias-button-floating-hover": hsl(h(0), s(-.1), l(.16)),
|
|
519
667
|
"--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), l(-.3), .08),
|
|
520
668
|
"--dsw-alias-interactive-bg-active": rgba(h(0), s(0), l(-.3), .14),
|
|
521
669
|
"--dsw-alias-markdown-code-block": hsl(h(0), s(-.1), l(-.03)),
|
|
@@ -524,6 +672,7 @@ window.__ModuleLoader__.load({
|
|
|
524
672
|
"--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(-.08), l(.05)),
|
|
525
673
|
"--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(-.12), l(0)),
|
|
526
674
|
"--dsw-specific-input-major": hsl(h(0), s(-.12), l(.1)),
|
|
675
|
+
"--dsw-specific-menu": hsl(h(0), s(-.12), l(.15)),
|
|
527
676
|
"--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.1), l(-.08)),
|
|
528
677
|
"--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.08), l(-.12)),
|
|
529
678
|
"--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.08), l(-.16)),
|
|
@@ -1313,6 +1462,7 @@ window.__ModuleLoader__.load({
|
|
|
1313
1462
|
//#endregion
|
|
1314
1463
|
//#region src/client/wallpaper.ts
|
|
1315
1464
|
let wpEl = null;
|
|
1465
|
+
let videoEl = null;
|
|
1316
1466
|
let appliedTokenNames = [];
|
|
1317
1467
|
let wpController = null;
|
|
1318
1468
|
let snapshotListener = null;
|
|
@@ -1339,15 +1489,6 @@ window.__ModuleLoader__.load({
|
|
|
1339
1489
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1340
1490
|
appliedTokenNames = [];
|
|
1341
1491
|
}
|
|
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
1492
|
/** Label tokens flipped by the generated-background brightness verdict. */
|
|
1352
1493
|
const LABEL_TOKENS = [
|
|
1353
1494
|
"--dsw-alias-label-primary",
|
|
@@ -1357,7 +1498,54 @@ window.__ModuleLoader__.load({
|
|
|
1357
1498
|
"--dsw-alias-label-dimmed",
|
|
1358
1499
|
"--dsw-alias-label-quaternary"
|
|
1359
1500
|
];
|
|
1501
|
+
const OPACITY_TOKEN_GROUPS = [
|
|
1502
|
+
{
|
|
1503
|
+
part: "bg",
|
|
1504
|
+
names: ["--dsw-alias-bg-base"]
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
part: "sidebar",
|
|
1508
|
+
names: ["--dsw-specific-sidebar-fill"]
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
part: "card",
|
|
1512
|
+
names: [
|
|
1513
|
+
"--dsw-alias-bg-layer-1",
|
|
1514
|
+
"--dsw-alias-bg-layer-2",
|
|
1515
|
+
"--dsw-alias-bg-layer-3",
|
|
1516
|
+
"--dsw-specific-menu"
|
|
1517
|
+
]
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
part: "input",
|
|
1521
|
+
names: ["--dsw-specific-input-major"]
|
|
1522
|
+
}
|
|
1523
|
+
];
|
|
1524
|
+
const OPACITY_VARS = {
|
|
1525
|
+
"--dsw-alias-bg-base": "--dsh-any-op-bg",
|
|
1526
|
+
"--dsw-specific-sidebar-fill": "--dsh-any-op-sidebar",
|
|
1527
|
+
"--dsw-alias-bg-layer-1": "--dsh-any-op-card-1",
|
|
1528
|
+
"--dsw-alias-bg-layer-2": "--dsh-any-op-card-2",
|
|
1529
|
+
"--dsw-alias-bg-layer-3": "--dsh-any-op-card-3",
|
|
1530
|
+
"--dsw-specific-input-major": "--dsh-any-op-input",
|
|
1531
|
+
"--dsw-specific-menu": "--dsh-any-op-menu"
|
|
1532
|
+
};
|
|
1533
|
+
let baseTokenKey = "";
|
|
1534
|
+
let pendingOps = null;
|
|
1535
|
+
let tokensRaf = null;
|
|
1360
1536
|
function applyCustomTokens(ops) {
|
|
1537
|
+
pendingOps = ops;
|
|
1538
|
+
if (tokensRaf !== null) return;
|
|
1539
|
+
tokensRaf = requestAnimationFrame(() => {
|
|
1540
|
+
tokensRaf = null;
|
|
1541
|
+
if (pendingOps === null) return;
|
|
1542
|
+
const o = pendingOps;
|
|
1543
|
+
pendingOps = null;
|
|
1544
|
+
applyCustomTokensNow(o);
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
let lastBgKey = "";
|
|
1548
|
+
function applyCustomTokensNow(ops) {
|
|
1361
1549
|
const [h, s, l] = rColor();
|
|
1362
1550
|
let { tokens } = genTokens(h, s, l);
|
|
1363
1551
|
try {
|
|
@@ -1367,49 +1555,68 @@ window.__ModuleLoader__.load({
|
|
|
1367
1555
|
const font = dark ? "#fff" : "#000";
|
|
1368
1556
|
for (const name of LABEL_TOKENS) tokens[name] = font;
|
|
1369
1557
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1558
|
+
const forceDark = dark ?? l < .55;
|
|
1559
|
+
if (`${h}|${s}|${l}|${dark}` !== baseTokenKey) {
|
|
1560
|
+
baseTokenKey = `${h}|${s}|${l}|${dark}`;
|
|
1561
|
+
if (forceDark) document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
|
|
1562
|
+
else document.body.removeAttribute("data-ds-dark-theme");
|
|
1563
|
+
const decls = [`color-scheme:${forceDark ? "dark" : "light"}`];
|
|
1564
|
+
for (const [name, value] of Object.entries(tokens)) {
|
|
1565
|
+
const opVar = OPACITY_VARS[name];
|
|
1566
|
+
decls.push(`${name}:${opVar !== void 0 ? `var(${opVar})` : value}!important`);
|
|
1567
|
+
}
|
|
1568
|
+
ensureTokenStyle().textContent = `body{${decls.join(";")}}`;
|
|
1569
|
+
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1570
|
+
appliedTokenNames = Object.keys(tokens);
|
|
1379
1571
|
}
|
|
1380
|
-
|
|
1381
|
-
for (const name of
|
|
1382
|
-
|
|
1383
|
-
|
|
1572
|
+
const root = document.documentElement;
|
|
1573
|
+
for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) root.style.setProperty(OPACITY_VARS[name], toRgba(tokens[name] ?? "#000", ops[g.part]));
|
|
1574
|
+
root.style.setProperty("--dsh-any-op-menu-cordis", toRgba(tokens["--dsw-specific-menu"] ?? "#000", ops.input));
|
|
1575
|
+
const bgKey = `${baseTokenKey}|${ops.bg}`;
|
|
1576
|
+
if (bgKey !== lastBgKey) {
|
|
1577
|
+
lastBgKey = bgKey;
|
|
1578
|
+
applyPartOpacities(ops);
|
|
1579
|
+
}
|
|
1580
|
+
} catch {}
|
|
1581
|
+
}
|
|
1582
|
+
const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
|
|
1583
|
+
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)}`;
|
|
1584
|
+
const INPUT_BLUR_RULE = "[data-composer-card],[data-cordis-panel]{-webkit-backdrop-filter:var(--dsh-any-input-blur,none);backdrop-filter:var(--dsh-any-input-blur,none)}[data-cordis-panel]{--dsw-specific-menu:var(--dsh-any-op-menu-cordis)!important}";
|
|
1585
|
+
function applyInputBlur(px) {
|
|
1586
|
+
if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
|
|
1587
|
+
else document.documentElement.style.removeProperty("--dsh-any-input-blur");
|
|
1384
1588
|
}
|
|
1385
|
-
const SETTINGS_STYLE_RULE = `[role="dialog"][aria-modal="true"][aria-labelledby]{background:var(--dsh-any-bg-settings-surface,var(--dsw-alias-bg-layer-2));backdrop-filter:var(--dsh-any-blur-settings,none)}`;
|
|
1386
1589
|
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
1590
|
const [h, s, l] = rColor();
|
|
1393
|
-
const
|
|
1591
|
+
const tokens = genTokens(h, s, l).tokens;
|
|
1592
|
+
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1593
|
+
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1594
|
+
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
1394
1595
|
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-surface", toRgba(layer2, op));
|
|
1596
|
+
if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-1", toRgba(layer1, op));
|
|
1597
|
+
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-2", toRgba(layer2, op));
|
|
1598
|
+
if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-3", toRgba(layer3, op));
|
|
1395
1599
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1600
|
+
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)}";
|
|
1601
|
+
function applyTrajectoryOverrides(op) {
|
|
1602
|
+
const [h, s, l] = rColor();
|
|
1603
|
+
const tokens = genTokens(h, s, l).tokens;
|
|
1604
|
+
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1605
|
+
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1606
|
+
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
1607
|
+
if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-1", toRgba(layer1, op));
|
|
1608
|
+
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-2", toRgba(layer2, op));
|
|
1609
|
+
if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-3", toRgba(layer3, op));
|
|
1401
1610
|
}
|
|
1402
|
-
/** Apply the theme color
|
|
1403
|
-
*
|
|
1404
|
-
* current wallpaper (one HSL sample, no palette), so new uploads still get a
|
|
1405
|
-
* matching theme automatically. */
|
|
1611
|
+
/** Apply the theme color: use the saved pick directly, or fall back to
|
|
1612
|
+
* extracting a dominant color from the current wallpaper. */
|
|
1406
1613
|
function applyThemeColor() {
|
|
1407
1614
|
if (rHasColor()) {
|
|
1408
1615
|
applyWp();
|
|
1409
1616
|
return;
|
|
1410
1617
|
}
|
|
1411
1618
|
const url = rWp();
|
|
1412
|
-
if (url) extractWallpaperColor(url, rBgState()).then((hsl) => {
|
|
1619
|
+
if (url) extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState()).then((hsl) => {
|
|
1413
1620
|
if (hsl) cfg.color = hsl;
|
|
1414
1621
|
applyWp();
|
|
1415
1622
|
});
|
|
@@ -1426,6 +1633,13 @@ window.__ModuleLoader__.load({
|
|
|
1426
1633
|
applyThemeColor();
|
|
1427
1634
|
return;
|
|
1428
1635
|
}
|
|
1636
|
+
if (type === "video") {
|
|
1637
|
+
clearDynamicBg();
|
|
1638
|
+
setBgDark(null);
|
|
1639
|
+
setWpUrl(null);
|
|
1640
|
+
applyThemeColor();
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1429
1643
|
if (!cfg.generatedBg || cfg.generatedBg.type !== type) cfg.generatedBg = defaultParamsFor(type);
|
|
1430
1644
|
applyGeneratedBg(cfg.generatedBg);
|
|
1431
1645
|
}
|
|
@@ -1437,15 +1651,11 @@ window.__ModuleLoader__.load({
|
|
|
1437
1651
|
function regenerateGeneratedBg() {
|
|
1438
1652
|
const params = cfg.generatedBg;
|
|
1439
1653
|
if (!params || cfg.backgroundType === "image") return;
|
|
1440
|
-
|
|
1441
|
-
...params,
|
|
1442
|
-
seed: randomSeed()
|
|
1443
|
-
} : {
|
|
1654
|
+
cfg.generatedBg = {
|
|
1444
1655
|
...params,
|
|
1445
1656
|
seed: randomSeed()
|
|
1446
1657
|
};
|
|
1447
|
-
cfg.generatedBg
|
|
1448
|
-
applyGeneratedBg(next);
|
|
1658
|
+
applyGeneratedBg(cfg.generatedBg);
|
|
1449
1659
|
}
|
|
1450
1660
|
/** Update a generated background's parameters and re-render. */
|
|
1451
1661
|
function updateGeneratedBg(params) {
|
|
@@ -1455,6 +1665,7 @@ window.__ModuleLoader__.load({
|
|
|
1455
1665
|
}
|
|
1456
1666
|
function applyGeneratedBg(params) {
|
|
1457
1667
|
clearDynamicBg();
|
|
1668
|
+
clearVideoEl();
|
|
1458
1669
|
ensureWpContainer();
|
|
1459
1670
|
wpController = createDynamicBackground(params);
|
|
1460
1671
|
if (wpEl) {
|
|
@@ -1466,7 +1677,7 @@ window.__ModuleLoader__.load({
|
|
|
1466
1677
|
const controller = wpController;
|
|
1467
1678
|
const frame = controller.snapshot();
|
|
1468
1679
|
setWpUrl(frame);
|
|
1469
|
-
|
|
1680
|
+
applyWp();
|
|
1470
1681
|
snapshotListener?.();
|
|
1471
1682
|
setBgDark(null);
|
|
1472
1683
|
analyzeFrameDark(frame).then((dark) => {
|
|
@@ -1532,14 +1743,39 @@ window.__ModuleLoader__.load({
|
|
|
1532
1743
|
if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-settings", `blur(${px}px)`);
|
|
1533
1744
|
else document.documentElement.style.removeProperty("--dsh-any-blur-settings");
|
|
1534
1745
|
}
|
|
1746
|
+
/** Apply the main-background opacity to the center/details columns instead of
|
|
1747
|
+
* the frame. The frame's translucent bg-base sits UNDER the sidebar, so
|
|
1748
|
+
* reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
|
|
1749
|
+
* the alpha onto the columns keeps the sidebar owned by its own slider. */
|
|
1750
|
+
function applyPartOpacities(ops) {
|
|
1751
|
+
if (!(rHasColor() || rBgDark() !== null)) return;
|
|
1752
|
+
discoverParts();
|
|
1753
|
+
if (frameEl === null) return;
|
|
1754
|
+
const [h, s, l] = rColor();
|
|
1755
|
+
const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
|
|
1756
|
+
frameEl.style.background = "transparent";
|
|
1757
|
+
if (centerEl !== null) centerEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
1758
|
+
if (detailsEl !== null) detailsEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
1759
|
+
}
|
|
1760
|
+
/** Blur of the option panels inside the settings dialog (.dab-card), owned by
|
|
1761
|
+
* the "dialog option panel" (card) blur slider. Written as a plugin-owned
|
|
1762
|
+
* variable consumed by SETTINGS_STYLE_RULE — deliberately NOT applied to the
|
|
1763
|
+
* homepage center/details columns, which this slider must never touch. */
|
|
1764
|
+
function applyCardPanelsBlur(px) {
|
|
1765
|
+
if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-card-panels", `blur(${px}px)`);
|
|
1766
|
+
else document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
|
|
1767
|
+
}
|
|
1535
1768
|
/** Apply per-part interface blur to the AppFrame columns + settings panel. */
|
|
1536
1769
|
function applyPartBlurs(blurs) {
|
|
1537
1770
|
discoverParts();
|
|
1538
|
-
setBlur(frameEl,
|
|
1771
|
+
setBlur(frameEl, 0);
|
|
1539
1772
|
setBlur(sidebarEl, blurs.sidebar);
|
|
1540
|
-
setBlur(centerEl, blurs.
|
|
1541
|
-
setBlur(detailsEl, blurs.
|
|
1773
|
+
setBlur(centerEl, blurs.bg);
|
|
1774
|
+
setBlur(detailsEl, blurs.bg);
|
|
1775
|
+
applyCardPanelsBlur(blurs.card);
|
|
1542
1776
|
applySettingsBlur(blurs.settings);
|
|
1777
|
+
applyInputBlur(blurs.input);
|
|
1778
|
+
applyViewCards();
|
|
1543
1779
|
}
|
|
1544
1780
|
/** Live per-part blur update during slider drag (no full re-apply). */
|
|
1545
1781
|
function setPartBlur(part, v) {
|
|
@@ -1547,13 +1783,182 @@ window.__ModuleLoader__.load({
|
|
|
1547
1783
|
applySettingsBlur(v);
|
|
1548
1784
|
return;
|
|
1549
1785
|
}
|
|
1786
|
+
if (part === "card") {
|
|
1787
|
+
applyCardPanelsBlur(v);
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
if (part === "input") {
|
|
1791
|
+
applyInputBlur(v);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
if (part === "chat" || part === "trajectory") {
|
|
1795
|
+
applyViewCards();
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1550
1798
|
discoverParts();
|
|
1551
|
-
if (part === "bg")
|
|
1552
|
-
else if (part === "sidebar") setBlur(sidebarEl, v);
|
|
1553
|
-
else {
|
|
1799
|
+
if (part === "bg") {
|
|
1554
1800
|
setBlur(centerEl, v);
|
|
1555
1801
|
setBlur(detailsEl, v);
|
|
1802
|
+
} else setBlur(sidebarEl, v);
|
|
1803
|
+
}
|
|
1804
|
+
const VIEW_CARDS = [{
|
|
1805
|
+
sel: "[data-chat-flow]",
|
|
1806
|
+
mark: "data-dab-chat-card",
|
|
1807
|
+
prev: "dabChatPrev",
|
|
1808
|
+
opacity: rChatTextOpacity,
|
|
1809
|
+
blur: () => rBlurs().chat,
|
|
1810
|
+
fallback: true
|
|
1811
|
+
}, {
|
|
1812
|
+
sel: "[data-conversation-composer-overlay]",
|
|
1813
|
+
mark: "data-dab-traj-card",
|
|
1814
|
+
prev: "dabTrajPrev",
|
|
1815
|
+
opacity: rTrajectoryOpacity,
|
|
1816
|
+
blur: () => rBlurs().trajectory,
|
|
1817
|
+
plain: true
|
|
1818
|
+
}];
|
|
1819
|
+
const viewTargets = VIEW_CARDS.map(() => null);
|
|
1820
|
+
function isScrollableY(el) {
|
|
1821
|
+
const oy = getComputedStyle(el).overflowY;
|
|
1822
|
+
return oy === "auto" || oy === "scroll" || oy === "overlay";
|
|
1823
|
+
}
|
|
1824
|
+
/** Whether the subtree hosts the chat input (textarea / contenteditable /
|
|
1825
|
+
* textbox role) — used to keep the card off the input row. */
|
|
1826
|
+
function containsChatEditor(el) {
|
|
1827
|
+
return el.querySelector("textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[contenteditable=\"plaintext-only\"],[role=\"textbox\"]") !== null;
|
|
1828
|
+
}
|
|
1829
|
+
/** Walk down from a coarse candidate toward the actual message column: stop
|
|
1830
|
+
* at a scroll container (the card surface must stay pinned to the scroll
|
|
1831
|
+
* port); while the chat input lives inside, descend into the tallest child that
|
|
1832
|
+
* does NOT contain it (the header row is short, the input row holds the
|
|
1833
|
+
* editor); otherwise peel wrappers dominated (>= 85%) by a single child so
|
|
1834
|
+
* tab bars / titles stay outside the card. */
|
|
1835
|
+
function refineMessageColumn(start) {
|
|
1836
|
+
let cur = start;
|
|
1837
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
1838
|
+
if (isScrollableY(cur)) break;
|
|
1839
|
+
const kids = Array.from(cur.children).filter((k) => k instanceof HTMLElement);
|
|
1840
|
+
if (kids.length === 0) break;
|
|
1841
|
+
const tallest = kids.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
|
|
1842
|
+
if (containsChatEditor(cur)) {
|
|
1843
|
+
const candidates = kids.filter((k) => !containsChatEditor(k) && k.clientHeight >= cur.clientHeight * .4);
|
|
1844
|
+
if (candidates.length === 0) break;
|
|
1845
|
+
cur = candidates.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
if (kids.length > 1 && tallest.clientHeight >= cur.clientHeight * .85) {
|
|
1849
|
+
cur = tallest;
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
break;
|
|
1556
1853
|
}
|
|
1854
|
+
return cur;
|
|
1855
|
+
}
|
|
1856
|
+
function discoverViewTarget(idx, spec) {
|
|
1857
|
+
if (centerEl === null || !document.body.contains(centerEl)) {
|
|
1858
|
+
viewTargets[idx] = null;
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
const marked = centerEl.querySelector(spec.sel);
|
|
1862
|
+
const cached = viewTargets[idx];
|
|
1863
|
+
if (marked !== null) {
|
|
1864
|
+
if (cached !== null && cached !== marked) {
|
|
1865
|
+
setBlur(cached, 0);
|
|
1866
|
+
restoreCardHost(cached, spec.mark, spec.prev, spec.plain === true);
|
|
1867
|
+
}
|
|
1868
|
+
viewTargets[idx] = marked;
|
|
1869
|
+
return marked;
|
|
1870
|
+
}
|
|
1871
|
+
if (cached !== null && centerEl.contains(cached)) return cached;
|
|
1872
|
+
viewTargets[idx] = null;
|
|
1873
|
+
if (spec.fallback !== true) return null;
|
|
1874
|
+
if (centerEl.querySelector("[data-conversation-scroll]") !== null) return null;
|
|
1875
|
+
if (spec.opacity() <= 0 && spec.blur() <= 0) return null;
|
|
1876
|
+
let best = null;
|
|
1877
|
+
let bestArea = 0;
|
|
1878
|
+
for (const el of Array.from(centerEl.querySelectorAll("*"))) {
|
|
1879
|
+
if (!isScrollableY(el)) continue;
|
|
1880
|
+
if (el.clientHeight < centerEl.clientHeight * .35) continue;
|
|
1881
|
+
const area = el.clientWidth * el.clientHeight;
|
|
1882
|
+
if (area > bestArea) {
|
|
1883
|
+
bestArea = area;
|
|
1884
|
+
best = el;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
if (best === null) for (const el of Array.from(centerEl.children)) {
|
|
1888
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
1889
|
+
if (el.clientHeight < centerEl.clientHeight * .5) continue;
|
|
1890
|
+
if (el.clientHeight > (best?.clientHeight ?? 0)) best = el;
|
|
1891
|
+
}
|
|
1892
|
+
const refined = best !== null ? refineMessageColumn(best) : null;
|
|
1893
|
+
viewTargets[idx] = refined;
|
|
1894
|
+
return refined;
|
|
1895
|
+
}
|
|
1896
|
+
/** Stash the host's own inline values so teardown restores them exactly.
|
|
1897
|
+
* Plain views only get a background override, so only that is stashed. */
|
|
1898
|
+
function stashCardPrev(el, prev, plain) {
|
|
1899
|
+
const ds = el.dataset;
|
|
1900
|
+
ds[prev + "Bg"] = el.style.getPropertyValue("background");
|
|
1901
|
+
if (plain) return;
|
|
1902
|
+
ds[prev + "Border"] = el.style.getPropertyValue("border");
|
|
1903
|
+
ds[prev + "Radius"] = el.style.getPropertyValue("border-radius");
|
|
1904
|
+
ds[prev + "Padding"] = el.style.getPropertyValue("padding");
|
|
1905
|
+
}
|
|
1906
|
+
/** Undo the inline styling, restoring the host's previous inline values. */
|
|
1907
|
+
function restoreCardHost(el, mark, prev, plain) {
|
|
1908
|
+
if (!el.hasAttribute(mark)) return;
|
|
1909
|
+
const ds = el.dataset;
|
|
1910
|
+
const restore = (prop, v) => {
|
|
1911
|
+
if (v !== void 0 && v !== "") el.style.setProperty(prop, v);
|
|
1912
|
+
else el.style.removeProperty(prop);
|
|
1913
|
+
};
|
|
1914
|
+
restore("background", ds[prev + "Bg"]);
|
|
1915
|
+
if (!plain) {
|
|
1916
|
+
restore("border", ds[prev + "Border"]);
|
|
1917
|
+
restore("border-radius", ds[prev + "Radius"]);
|
|
1918
|
+
restore("padding", ds[prev + "Padding"]);
|
|
1919
|
+
delete ds[prev + "Border"];
|
|
1920
|
+
delete ds[prev + "Radius"];
|
|
1921
|
+
delete ds[prev + "Padding"];
|
|
1922
|
+
}
|
|
1923
|
+
delete ds[prev + "Bg"];
|
|
1924
|
+
el.removeAttribute(mark);
|
|
1925
|
+
}
|
|
1926
|
+
/** Teardown only: strip every view treatment and hand the hosts back untouched. */
|
|
1927
|
+
function removeViewCards() {
|
|
1928
|
+
VIEW_CARDS.forEach((spec, i) => {
|
|
1929
|
+
const el = viewTargets[i];
|
|
1930
|
+
if (el !== null) {
|
|
1931
|
+
setBlur(el, 0);
|
|
1932
|
+
restoreCardHost(el, spec.mark, spec.prev, spec.plain === true);
|
|
1933
|
+
}
|
|
1934
|
+
viewTargets[i] = null;
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
/** Re-derive the conversation view cards from the current config. Cheap
|
|
1938
|
+
* enough for live slider drags; the card structure is applied unconditionally
|
|
1939
|
+
* once the host exists so the layout never reflows when a slider leaves zero. */
|
|
1940
|
+
function applyViewCards() {
|
|
1941
|
+
discoverParts();
|
|
1942
|
+
if (centerEl === null) return;
|
|
1943
|
+
const [h, s, l] = rColor();
|
|
1944
|
+
const surface = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-1"];
|
|
1945
|
+
VIEW_CARDS.forEach((spec, i) => {
|
|
1946
|
+
const target = discoverViewTarget(i, spec);
|
|
1947
|
+
if (target === null) return;
|
|
1948
|
+
const plain = spec.plain === true;
|
|
1949
|
+
const opacity = spec.opacity();
|
|
1950
|
+
const blurPx = spec.blur();
|
|
1951
|
+
if (!plain) {
|
|
1952
|
+
if (!target.hasAttribute(spec.mark)) stashCardPrev(target, spec.prev, false);
|
|
1953
|
+
const borderAlpha = opacity > 0 ? Math.min(1, opacity * 1.5) : blurPx > 0 ? .35 : 0;
|
|
1954
|
+
target.style.background = surface !== void 0 ? toRgba(surface, opacity) : "transparent";
|
|
1955
|
+
target.style.border = surface !== void 0 ? `1px solid ${toRgba(surface, borderAlpha)}` : "1px solid transparent";
|
|
1956
|
+
target.style.borderRadius = "16px";
|
|
1957
|
+
target.style.padding = "18px";
|
|
1958
|
+
}
|
|
1959
|
+
target.setAttribute(spec.mark, "1");
|
|
1960
|
+
setBlur(target, blurPx);
|
|
1961
|
+
});
|
|
1557
1962
|
}
|
|
1558
1963
|
let partsObserver = null;
|
|
1559
1964
|
/** Watch for the AppFrame mounting so persisted blurs land even when the shell
|
|
@@ -1562,8 +1967,9 @@ window.__ModuleLoader__.load({
|
|
|
1562
1967
|
function watchParts() {
|
|
1563
1968
|
if (partsObserver !== null || typeof MutationObserver === "undefined") return;
|
|
1564
1969
|
partsObserver = new MutationObserver(() => {
|
|
1565
|
-
if (frameEl !== null && sidebarEl !== null && centerEl !== null && detailsEl !== null && document.body.contains(frameEl)) return;
|
|
1970
|
+
if (frameEl !== null && sidebarEl !== null && centerEl !== null && detailsEl !== null && document.body.contains(frameEl) && viewTargets.every((el) => el !== null && document.body.contains(el))) return;
|
|
1566
1971
|
applyPartBlurs(rBlurs());
|
|
1972
|
+
applyPartOpacities(rOps());
|
|
1567
1973
|
});
|
|
1568
1974
|
partsObserver.observe(document.body, {
|
|
1569
1975
|
childList: true,
|
|
@@ -1581,24 +1987,185 @@ window.__ModuleLoader__.load({
|
|
|
1581
1987
|
document.body.prepend(wpEl);
|
|
1582
1988
|
}
|
|
1583
1989
|
}
|
|
1990
|
+
function clearVideoEl() {
|
|
1991
|
+
if (videoEl === null) return;
|
|
1992
|
+
videoEl.pause();
|
|
1993
|
+
videoEl.removeAttribute("src");
|
|
1994
|
+
videoEl.load();
|
|
1995
|
+
videoEl.remove();
|
|
1996
|
+
videoEl = null;
|
|
1997
|
+
}
|
|
1998
|
+
/** Intrinsic-size cache for the center mode (native pixels of the current image). */
|
|
1999
|
+
let imgNat = null;
|
|
2000
|
+
function imageNatSize(url, cb) {
|
|
2001
|
+
if (imgNat !== null && imgNat.url === url) {
|
|
2002
|
+
cb(imgNat.w, imgNat.h);
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
const img = new Image();
|
|
2006
|
+
img.onload = () => {
|
|
2007
|
+
imgNat = {
|
|
2008
|
+
url,
|
|
2009
|
+
w: img.naturalWidth,
|
|
2010
|
+
h: img.naturalHeight
|
|
2011
|
+
};
|
|
2012
|
+
cb(img.naturalWidth, img.naturalHeight);
|
|
2013
|
+
};
|
|
2014
|
+
img.onerror = () => cb(0, 0);
|
|
2015
|
+
img.src = url;
|
|
2016
|
+
}
|
|
2017
|
+
const DRAG_MAX_SIDE = 720;
|
|
2018
|
+
let lowResUrl = null;
|
|
2019
|
+
let lowResFor = "";
|
|
2020
|
+
let dragLow = false;
|
|
2021
|
+
function captureLowRes(url, cb) {
|
|
2022
|
+
if (lowResFor === url) {
|
|
2023
|
+
cb(lowResUrl);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
const img = new Image();
|
|
2027
|
+
img.onload = () => {
|
|
2028
|
+
const k = Math.min(1, DRAG_MAX_SIDE / Math.max(img.naturalWidth, img.naturalHeight));
|
|
2029
|
+
if (k >= 1) {
|
|
2030
|
+
lowResFor = url;
|
|
2031
|
+
lowResUrl = null;
|
|
2032
|
+
cb(null);
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const c = document.createElement("canvas");
|
|
2036
|
+
c.width = Math.max(1, Math.round(img.naturalWidth * k));
|
|
2037
|
+
c.height = Math.max(1, Math.round(img.naturalHeight * k));
|
|
2038
|
+
const g = c.getContext("2d");
|
|
2039
|
+
if (!g) {
|
|
2040
|
+
lowResFor = url;
|
|
2041
|
+
lowResUrl = null;
|
|
2042
|
+
cb(null);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
g.drawImage(img, 0, 0, c.width, c.height);
|
|
2046
|
+
const low = c.toDataURL("image/jpeg", .85);
|
|
2047
|
+
lowResFor = url;
|
|
2048
|
+
lowResUrl = low;
|
|
2049
|
+
cb(low);
|
|
2050
|
+
};
|
|
2051
|
+
img.onerror = () => cb(null);
|
|
2052
|
+
img.src = url;
|
|
2053
|
+
}
|
|
2054
|
+
function setDragLow(on) {
|
|
2055
|
+
if (cfg.backgroundType !== "image" || on === dragLow || !wpEl) return;
|
|
2056
|
+
const full = rWpImage();
|
|
2057
|
+
if (!full) return;
|
|
2058
|
+
if (on) {
|
|
2059
|
+
dragLow = true;
|
|
2060
|
+
captureLowRes(full, (low) => {
|
|
2061
|
+
if (!dragLow || !wpEl || low === null) return;
|
|
2062
|
+
if (wpEl.style.backgroundImage !== `url("${low}")`) wpEl.style.backgroundImage = `url("${low}")`;
|
|
2063
|
+
});
|
|
2064
|
+
} else {
|
|
2065
|
+
dragLow = false;
|
|
2066
|
+
if (wpEl.style.backgroundImage !== `url("${full}")`) wpEl.style.backgroundImage = `url("${full}")`;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
/** While any range slider in the app is being dragged, run the wallpaper at
|
|
2070
|
+
* reduced resolution; restore on release. Returns a disposer for teardown. */
|
|
2071
|
+
function watchWallpaperDragQuality() {
|
|
2072
|
+
const isRange = (t) => t instanceof HTMLInputElement && t.type === "range";
|
|
2073
|
+
const down = (e) => {
|
|
2074
|
+
if (isRange(e.target)) setDragLow(true);
|
|
2075
|
+
};
|
|
2076
|
+
const up = () => {
|
|
2077
|
+
if (dragLow) setDragLow(false);
|
|
2078
|
+
};
|
|
2079
|
+
window.addEventListener("pointerdown", down, true);
|
|
2080
|
+
window.addEventListener("pointerup", up, true);
|
|
2081
|
+
window.addEventListener("pointercancel", up, true);
|
|
2082
|
+
return () => {
|
|
2083
|
+
window.removeEventListener("pointerdown", down, true);
|
|
2084
|
+
window.removeEventListener("pointerup", up, true);
|
|
2085
|
+
window.removeEventListener("pointercancel", up, true);
|
|
2086
|
+
if (dragLow) setDragLow(false);
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
1584
2089
|
function applyImageWp(url) {
|
|
1585
2090
|
clearDynamicBg();
|
|
2091
|
+
clearVideoEl();
|
|
1586
2092
|
ensureWpContainer();
|
|
1587
2093
|
const bg = rBgState();
|
|
2094
|
+
const mode = rBgMode();
|
|
1588
2095
|
const next = `url("${url}")`;
|
|
1589
|
-
if (wpEl.style.backgroundImage !== next)
|
|
1590
|
-
|
|
2096
|
+
if (wpEl.style.backgroundImage !== next) wpEl.style.backgroundImage = next;
|
|
2097
|
+
if (mode === "fit") {
|
|
2098
|
+
wpEl.style.backgroundRepeat = "no-repeat";
|
|
2099
|
+
if (bg.iw > 0) {
|
|
2100
|
+
const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
|
|
2101
|
+
const w = bg.iw * fit * bg.zoom;
|
|
2102
|
+
const h = bg.ih * fit * bg.zoom;
|
|
2103
|
+
wpEl.style.backgroundSize = `${w}px ${h}px`;
|
|
2104
|
+
wpEl.style.backgroundPosition = `${bg.x * window.innerWidth - w / 2}px ${bg.y * window.innerHeight - h / 2}px`;
|
|
2105
|
+
} else {
|
|
2106
|
+
wpEl.style.backgroundSize = "contain";
|
|
2107
|
+
wpEl.style.backgroundPosition = "center";
|
|
2108
|
+
}
|
|
2109
|
+
} else if (mode === "fill") {
|
|
2110
|
+
wpEl.style.backgroundRepeat = "no-repeat";
|
|
2111
|
+
wpEl.style.backgroundSize = "cover";
|
|
2112
|
+
wpEl.style.backgroundPosition = "center";
|
|
2113
|
+
} else if (mode === "stretch") {
|
|
1591
2114
|
wpEl.style.backgroundRepeat = "no-repeat";
|
|
2115
|
+
wpEl.style.backgroundSize = "100% 100%";
|
|
2116
|
+
wpEl.style.backgroundPosition = "center";
|
|
2117
|
+
} else if (mode === "tile") {
|
|
2118
|
+
wpEl.style.backgroundRepeat = "repeat";
|
|
2119
|
+
wpEl.style.backgroundSize = "auto";
|
|
2120
|
+
wpEl.style.backgroundPosition = "0px 0px";
|
|
2121
|
+
} else {
|
|
2122
|
+
wpEl.style.backgroundRepeat = "no-repeat";
|
|
2123
|
+
wpEl.style.backgroundSize = "contain";
|
|
2124
|
+
wpEl.style.backgroundPosition = "center";
|
|
2125
|
+
imageNatSize(url, (w, h) => {
|
|
2126
|
+
if (!wpEl || wpEl.style.backgroundImage !== next || rBgMode() !== "center") return;
|
|
2127
|
+
if (w > 0 && h > 0) {
|
|
2128
|
+
wpEl.style.backgroundSize = `${w}px ${h}px`;
|
|
2129
|
+
wpEl.style.backgroundPosition = "center";
|
|
2130
|
+
}
|
|
2131
|
+
});
|
|
1592
2132
|
}
|
|
1593
|
-
|
|
2133
|
+
captureLowRes(url, () => void 0);
|
|
2134
|
+
applyWpEffects();
|
|
2135
|
+
}
|
|
2136
|
+
/** Video wallpaper: a muted looping <video> inside the wallpaper layer.
|
|
2137
|
+
* Placement modes map onto object-fit (tile has no video equivalent and
|
|
2138
|
+
* falls back to cover). */
|
|
2139
|
+
function applyVideoWp(url) {
|
|
2140
|
+
clearDynamicBg();
|
|
2141
|
+
ensureWpContainer();
|
|
2142
|
+
if (wpEl.style.backgroundImage !== "none") wpEl.style.backgroundImage = "none";
|
|
2143
|
+
if (videoEl === null || !videoEl.isConnected) {
|
|
2144
|
+
videoEl = document.createElement("video");
|
|
2145
|
+
videoEl.muted = true;
|
|
2146
|
+
videoEl.loop = true;
|
|
2147
|
+
videoEl.autoplay = true;
|
|
2148
|
+
videoEl.playsInline = true;
|
|
2149
|
+
videoEl.preload = "auto";
|
|
2150
|
+
videoEl.setAttribute("playsinline", "");
|
|
2151
|
+
videoEl.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-position:center;";
|
|
2152
|
+
wpEl.appendChild(videoEl);
|
|
2153
|
+
videoEl.setAttribute("src", url);
|
|
2154
|
+
videoEl.play().catch(() => void 0);
|
|
2155
|
+
} else if (videoEl.getAttribute("src") !== url) {
|
|
2156
|
+
videoEl.setAttribute("src", url);
|
|
2157
|
+
videoEl.play().catch(() => void 0);
|
|
2158
|
+
}
|
|
2159
|
+
const mode = rBgMode();
|
|
2160
|
+
const bg = rVideoBgState();
|
|
2161
|
+
if (mode === "fit" && bg.iw > 0) {
|
|
1594
2162
|
const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
|
|
1595
2163
|
const w = bg.iw * fit * bg.zoom;
|
|
1596
2164
|
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`;
|
|
2165
|
+
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
2166
|
} else {
|
|
1600
|
-
|
|
1601
|
-
|
|
2167
|
+
videoEl.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-position:center;";
|
|
2168
|
+
videoEl.style.objectFit = mode === "stretch" ? "fill" : mode === "fill" || mode === "tile" ? "cover" : "contain";
|
|
1602
2169
|
}
|
|
1603
2170
|
applyWpEffects();
|
|
1604
2171
|
}
|
|
@@ -1610,7 +2177,17 @@ window.__ModuleLoader__.load({
|
|
|
1610
2177
|
}
|
|
1611
2178
|
function applyWp() {
|
|
1612
2179
|
const url = rWp();
|
|
1613
|
-
if (cfg.backgroundType
|
|
2180
|
+
if (cfg.backgroundType === "video") {
|
|
2181
|
+
const vurl = rWpVideo();
|
|
2182
|
+
if (vurl) applyVideoWp(vurl);
|
|
2183
|
+
else {
|
|
2184
|
+
clearDynamicBg();
|
|
2185
|
+
clearVideoEl();
|
|
2186
|
+
wpEl?.remove();
|
|
2187
|
+
wpEl = null;
|
|
2188
|
+
}
|
|
2189
|
+
} else if (cfg.backgroundType !== "image" && cfg.generatedBg) {
|
|
2190
|
+
clearVideoEl();
|
|
1614
2191
|
if (!wpController) {
|
|
1615
2192
|
applyGeneratedBg(cfg.generatedBg);
|
|
1616
2193
|
return;
|
|
@@ -1621,27 +2198,56 @@ window.__ModuleLoader__.load({
|
|
|
1621
2198
|
} else if (url) applyImageWp(url);
|
|
1622
2199
|
else {
|
|
1623
2200
|
clearDynamicBg();
|
|
2201
|
+
clearVideoEl();
|
|
1624
2202
|
wpEl?.remove();
|
|
1625
2203
|
wpEl = null;
|
|
1626
2204
|
}
|
|
1627
2205
|
if (rHasColor() || rBgDark() !== null) applyCustomTokens(rOps());
|
|
1628
|
-
if (rHasColor())
|
|
2206
|
+
if (rHasColor()) {
|
|
2207
|
+
applySettingsOverrides(rSop());
|
|
2208
|
+
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
2209
|
+
}
|
|
1629
2210
|
applyPartBlurs(rBlurs());
|
|
1630
2211
|
}
|
|
1631
2212
|
function teardownWp() {
|
|
1632
2213
|
clearDynamicBg();
|
|
2214
|
+
clearVideoEl();
|
|
2215
|
+
disposeVideoObjectUrl();
|
|
1633
2216
|
setBgDark(null);
|
|
1634
2217
|
wpEl?.remove();
|
|
1635
2218
|
wpEl = null;
|
|
1636
2219
|
clearCustomTokens();
|
|
1637
2220
|
tokenStyleEl?.remove();
|
|
1638
2221
|
tokenStyleEl = null;
|
|
2222
|
+
removeViewCards();
|
|
2223
|
+
document.body.removeAttribute("data-ds-dark-theme");
|
|
2224
|
+
document.body.style.removeProperty("color-scheme");
|
|
1639
2225
|
document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
|
|
2226
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-1");
|
|
2227
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-2");
|
|
2228
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-3");
|
|
2229
|
+
document.documentElement.style.removeProperty("--dsh-any-traj-layer-1");
|
|
2230
|
+
document.documentElement.style.removeProperty("--dsh-any-traj-layer-2");
|
|
2231
|
+
document.documentElement.style.removeProperty("--dsh-any-traj-layer-3");
|
|
2232
|
+
document.documentElement.style.removeProperty("--dsh-any-bg-settings-card-surface");
|
|
1640
2233
|
document.documentElement.style.removeProperty("--dsh-any-blur-settings");
|
|
2234
|
+
document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
|
|
2235
|
+
document.documentElement.style.removeProperty("--dsh-any-input-blur");
|
|
2236
|
+
for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
|
|
2237
|
+
baseTokenKey = "";
|
|
2238
|
+
lastBgKey = "";
|
|
2239
|
+
if (tokensRaf !== null) {
|
|
2240
|
+
cancelAnimationFrame(tokensRaf);
|
|
2241
|
+
tokensRaf = null;
|
|
2242
|
+
}
|
|
2243
|
+
pendingOps = null;
|
|
1641
2244
|
setBlur(frameEl, 0);
|
|
1642
2245
|
setBlur(sidebarEl, 0);
|
|
1643
2246
|
setBlur(centerEl, 0);
|
|
1644
2247
|
setBlur(detailsEl, 0);
|
|
2248
|
+
if (frameEl !== null) frameEl.style.removeProperty("background");
|
|
2249
|
+
if (centerEl !== null) centerEl.style.removeProperty("background");
|
|
2250
|
+
if (detailsEl !== null) detailsEl.style.removeProperty("background");
|
|
1645
2251
|
stopWatchingParts();
|
|
1646
2252
|
}
|
|
1647
2253
|
/** Live wallpaper-opacity updates during slider drag (no full re-apply). */
|
|
@@ -1652,6 +2258,69 @@ window.__ModuleLoader__.load({
|
|
|
1652
2258
|
function setWpBlur(v) {
|
|
1653
2259
|
if (wpEl) wpEl.style.filter = v > 0 ? `blur(${v}px)` : "none";
|
|
1654
2260
|
}
|
|
2261
|
+
//#endregion
|
|
2262
|
+
//#region src/client/utils/video.ts
|
|
2263
|
+
/**
|
|
2264
|
+
* Video-background helpers: capturing a single frame as a JPEG snapshot.
|
|
2265
|
+
* The snapshot stands in for everything that only understands still images:
|
|
2266
|
+
* the settings preview, theme-color extraction and the eyedropper. Uploads
|
|
2267
|
+
* stream the raw file over the binary upload route (see rpc.uploadVideo) —
|
|
2268
|
+
* a data-URL detour would inflate the bytes and trip the RPC body limit.
|
|
2269
|
+
*/
|
|
2270
|
+
function grabFrame(v) {
|
|
2271
|
+
const w = v.videoWidth, h = v.videoHeight;
|
|
2272
|
+
if (w <= 0 || h <= 0) return null;
|
|
2273
|
+
const canvas = document.createElement("canvas");
|
|
2274
|
+
canvas.width = w;
|
|
2275
|
+
canvas.height = h;
|
|
2276
|
+
const ctx = canvas.getContext("2d");
|
|
2277
|
+
if (!ctx) return null;
|
|
2278
|
+
ctx.drawImage(v, 0, 0, w, h);
|
|
2279
|
+
try {
|
|
2280
|
+
return canvas.toDataURL("image/jpeg", .85);
|
|
2281
|
+
} catch {
|
|
2282
|
+
return null;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Capture one representative frame from a video data URL as a JPEG data URL.
|
|
2287
|
+
* Seeks to ~10% of the duration (first frame is often black) and waits for
|
|
2288
|
+
* the seek to land; resolves null when the video cannot be decoded. A timeout
|
|
2289
|
+
* keeps a broken file from hanging the upload flow.
|
|
2290
|
+
*/
|
|
2291
|
+
function captureVideoSnapshot(url) {
|
|
2292
|
+
return new Promise((resolve) => {
|
|
2293
|
+
const v = document.createElement("video");
|
|
2294
|
+
let settled = false;
|
|
2295
|
+
const done = (r) => {
|
|
2296
|
+
if (settled) return;
|
|
2297
|
+
settled = true;
|
|
2298
|
+
window.clearTimeout(timer);
|
|
2299
|
+
v.removeAttribute("src");
|
|
2300
|
+
v.load();
|
|
2301
|
+
resolve(r);
|
|
2302
|
+
};
|
|
2303
|
+
const timer = window.setTimeout(() => done(grabFrame(v)), 6e3);
|
|
2304
|
+
v.muted = true;
|
|
2305
|
+
v.preload = "auto";
|
|
2306
|
+
v.playsInline = true;
|
|
2307
|
+
v.onerror = () => done(null);
|
|
2308
|
+
v.onloadeddata = () => {
|
|
2309
|
+
const target = isFinite(v.duration) && v.duration > .6 ? Math.min(v.duration * .1, 2) : 0;
|
|
2310
|
+
if (target <= 0) {
|
|
2311
|
+
done(grabFrame(v));
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
v.onseeked = () => done(grabFrame(v));
|
|
2315
|
+
try {
|
|
2316
|
+
v.currentTime = target;
|
|
2317
|
+
} catch {
|
|
2318
|
+
done(grabFrame(v));
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
v.src = url;
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
1655
2324
|
const UI_CSS = `
|
|
1656
2325
|
/* The section is rendered INLINE inside the host settings dialog's content
|
|
1657
2326
|
* column: the host provides the modal chrome (backdrop, centering, closing).
|
|
@@ -1934,6 +2603,44 @@ window.__ModuleLoader__.load({
|
|
|
1934
2603
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14 10.4l-2.8-2.8-4.8 4.8" })
|
|
1935
2604
|
]
|
|
1936
2605
|
});
|
|
2606
|
+
const VideoIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
2607
|
+
size,
|
|
2608
|
+
className,
|
|
2609
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
2610
|
+
x: "1.5",
|
|
2611
|
+
y: "4.2",
|
|
2612
|
+
width: "8.8",
|
|
2613
|
+
height: "7.6",
|
|
2614
|
+
rx: "2"
|
|
2615
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10.3 6.9l4.2-2.4v7l-4.2-2.4" })]
|
|
2616
|
+
});
|
|
2617
|
+
const TextIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
2618
|
+
size,
|
|
2619
|
+
className,
|
|
2620
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 4.2h10M3 8h10M3 11.8h6.5" })
|
|
2621
|
+
});
|
|
2622
|
+
const TrajectoryIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
2623
|
+
size,
|
|
2624
|
+
className,
|
|
2625
|
+
children: [
|
|
2626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 3.2h11M2.5 6.6h11M2.5 10h11" }),
|
|
2627
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2628
|
+
cx: "4.4",
|
|
2629
|
+
cy: "3.2",
|
|
2630
|
+
r: "1.1"
|
|
2631
|
+
}),
|
|
2632
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2633
|
+
cx: "8",
|
|
2634
|
+
cy: "6.6",
|
|
2635
|
+
r: "1.1"
|
|
2636
|
+
}),
|
|
2637
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2638
|
+
cx: "11.4",
|
|
2639
|
+
cy: "10",
|
|
2640
|
+
r: "1.1"
|
|
2641
|
+
})
|
|
2642
|
+
]
|
|
2643
|
+
});
|
|
1937
2644
|
const SlidersIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
1938
2645
|
size,
|
|
1939
2646
|
className,
|
|
@@ -2059,6 +2766,17 @@ window.__ModuleLoader__.load({
|
|
|
2059
2766
|
r: "2.1"
|
|
2060
2767
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 1.7v1.8M8 12.5v1.8M1.7 8h1.8M12.5 8h1.8M3.6 3.6l1.3 1.3M11.1 11.1l1.3 1.3M12.4 3.6l-1.3 1.3M4.9 11.1l-1.3 1.3" })]
|
|
2061
2768
|
});
|
|
2769
|
+
const InputIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
2770
|
+
size,
|
|
2771
|
+
className,
|
|
2772
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
2773
|
+
x: "2.2",
|
|
2774
|
+
y: "4",
|
|
2775
|
+
width: "11.6",
|
|
2776
|
+
height: "8",
|
|
2777
|
+
rx: "2"
|
|
2778
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 6.6v2M7 8h2" })]
|
|
2779
|
+
});
|
|
2062
2780
|
//#endregion
|
|
2063
2781
|
//#region src/client/components/ErrorBoundary.tsx
|
|
2064
2782
|
/**
|
|
@@ -2893,7 +3611,7 @@ window.__ModuleLoader__.load({
|
|
|
2893
3611
|
paint(inputRef.current, v);
|
|
2894
3612
|
}
|
|
2895
3613
|
if (valRef.current) valRef.current.textContent = fmt(v);
|
|
2896
|
-
onInput(v);
|
|
3614
|
+
onInput?.(v);
|
|
2897
3615
|
onChange(v);
|
|
2898
3616
|
};
|
|
2899
3617
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2922,7 +3640,7 @@ window.__ModuleLoader__.load({
|
|
|
2922
3640
|
const el = e.target;
|
|
2923
3641
|
const v = Number(el.value);
|
|
2924
3642
|
paint(el, v);
|
|
2925
|
-
onInput(v);
|
|
3643
|
+
onInput?.(v);
|
|
2926
3644
|
if (valRef.current) valRef.current.textContent = fmt(v);
|
|
2927
3645
|
},
|
|
2928
3646
|
onChange: (e) => onChange(Number(e.target.value))
|
|
@@ -2952,10 +3670,25 @@ window.__ModuleLoader__.load({
|
|
|
2952
3670
|
labelKey: "uiOpacityCard",
|
|
2953
3671
|
Icon: ChatIcon
|
|
2954
3672
|
},
|
|
3673
|
+
{
|
|
3674
|
+
opKey: "input",
|
|
3675
|
+
labelKey: "uiOpacityInput",
|
|
3676
|
+
Icon: InputIcon
|
|
3677
|
+
},
|
|
2955
3678
|
{
|
|
2956
3679
|
isSettings: true,
|
|
2957
3680
|
labelKey: "uiSop",
|
|
2958
3681
|
Icon: GearIcon
|
|
3682
|
+
},
|
|
3683
|
+
{
|
|
3684
|
+
isChat: true,
|
|
3685
|
+
labelKey: "uiChatRegion",
|
|
3686
|
+
Icon: TextIcon
|
|
3687
|
+
},
|
|
3688
|
+
{
|
|
3689
|
+
isTrajectory: true,
|
|
3690
|
+
labelKey: "uiTrajectory",
|
|
3691
|
+
Icon: TrajectoryIcon
|
|
2959
3692
|
}
|
|
2960
3693
|
];
|
|
2961
3694
|
function InterfacePage({ p }) {
|
|
@@ -2980,10 +3713,10 @@ window.__ModuleLoader__.load({
|
|
|
2980
3713
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2981
3714
|
className: "dab-grid-parts",
|
|
2982
3715
|
children: PARTS.map((part, i) => {
|
|
2983
|
-
const { labelKey, Icon, isSettings } = part;
|
|
3716
|
+
const { labelKey, Icon, isSettings, isChat, isTrajectory } = part;
|
|
2984
3717
|
const opKey = part.opKey;
|
|
2985
|
-
const blurKey =
|
|
2986
|
-
const opacity = isSettings ? rSop() : rOps()[opKey];
|
|
3718
|
+
const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : opKey;
|
|
3719
|
+
const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : rOps()[opKey];
|
|
2987
3720
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2988
3721
|
className: "dab-card dab-card-hover dab-rise",
|
|
2989
3722
|
style: { "--d": i + 1 },
|
|
@@ -3014,7 +3747,13 @@ window.__ModuleLoader__.load({
|
|
|
3014
3747
|
fmt: (v) => `${v}%`,
|
|
3015
3748
|
onInput: (v) => {
|
|
3016
3749
|
const op = v / 100;
|
|
3017
|
-
if (
|
|
3750
|
+
if (isChat) {
|
|
3751
|
+
cfg.chatTextOpacity = op;
|
|
3752
|
+
applyViewCards();
|
|
3753
|
+
} else if (isTrajectory) {
|
|
3754
|
+
cfg.trajectoryOpacity = op;
|
|
3755
|
+
applyTrajectoryOverrides(op);
|
|
3756
|
+
} else if (isSettings) {
|
|
3018
3757
|
cfg.settingsOpacity = op;
|
|
3019
3758
|
applySettingsOverrides(op);
|
|
3020
3759
|
} else {
|
|
@@ -3027,7 +3766,15 @@ window.__ModuleLoader__.load({
|
|
|
3027
3766
|
},
|
|
3028
3767
|
onChange: (v) => {
|
|
3029
3768
|
const op = v / 100;
|
|
3030
|
-
if (
|
|
3769
|
+
if (isChat) {
|
|
3770
|
+
cfg.chatTextOpacity = op;
|
|
3771
|
+
applyViewCards();
|
|
3772
|
+
saveConfig();
|
|
3773
|
+
} else if (isTrajectory) {
|
|
3774
|
+
cfg.trajectoryOpacity = op;
|
|
3775
|
+
applyTrajectoryOverrides(op);
|
|
3776
|
+
saveConfig();
|
|
3777
|
+
} else if (isSettings) setSop(op);
|
|
3031
3778
|
else {
|
|
3032
3779
|
const ops = { ...rOps() };
|
|
3033
3780
|
ops[opKey] = op;
|
|
@@ -3076,10 +3823,13 @@ window.__ModuleLoader__.load({
|
|
|
3076
3823
|
}
|
|
3077
3824
|
//#endregion
|
|
3078
3825
|
//#region src/client/components/BgEditor.tsx
|
|
3826
|
+
/** Placement slot for the active background type: video framing lives in its
|
|
3827
|
+
* own state so image and video edits never overwrite each other. */
|
|
3828
|
+
const activeState = () => cfg.backgroundType === "video" ? cfg.videoBgState : cfg.bgState;
|
|
3079
3829
|
function BgEditor({ url, t, onClose, onCommit }) {
|
|
3080
3830
|
const pw = Math.min(window.innerWidth * .75, 860);
|
|
3081
3831
|
const ph = Math.round(pw * window.innerHeight / window.innerWidth);
|
|
3082
|
-
const saved =
|
|
3832
|
+
const saved = activeState();
|
|
3083
3833
|
const [zoom, setZoom] = (0, react.useState)(saved.iw > 0 ? saved.zoom : 1);
|
|
3084
3834
|
const [pos, setPos] = (0, react.useState)(saved.iw > 0 ? {
|
|
3085
3835
|
x: saved.x * pw,
|
|
@@ -3110,7 +3860,7 @@ window.__ModuleLoader__.load({
|
|
|
3110
3860
|
w,
|
|
3111
3861
|
h
|
|
3112
3862
|
});
|
|
3113
|
-
const s =
|
|
3863
|
+
const s = activeState();
|
|
3114
3864
|
if (s.iw > 0 && s.iw === img.width && s.ih === img.height) {
|
|
3115
3865
|
setZoom(s.zoom);
|
|
3116
3866
|
setPos({
|
|
@@ -3264,8 +4014,30 @@ window.__ModuleLoader__.load({
|
|
|
3264
4014
|
//#endregion
|
|
3265
4015
|
//#region src/client/components/pages/BackgroundPage.tsx
|
|
3266
4016
|
const SEG_W = 108;
|
|
4017
|
+
const BG_MODES = [
|
|
4018
|
+
{
|
|
4019
|
+
mode: "fit",
|
|
4020
|
+
labelKey: "bgModeFit"
|
|
4021
|
+
},
|
|
4022
|
+
{
|
|
4023
|
+
mode: "fill",
|
|
4024
|
+
labelKey: "bgModeFill"
|
|
4025
|
+
},
|
|
4026
|
+
{
|
|
4027
|
+
mode: "stretch",
|
|
4028
|
+
labelKey: "bgModeStretch"
|
|
4029
|
+
},
|
|
4030
|
+
{
|
|
4031
|
+
mode: "tile",
|
|
4032
|
+
labelKey: "bgModeTile"
|
|
4033
|
+
},
|
|
4034
|
+
{
|
|
4035
|
+
mode: "center",
|
|
4036
|
+
labelKey: "bgModeCenter"
|
|
4037
|
+
}
|
|
4038
|
+
];
|
|
3267
4039
|
function BackgroundPage({ p }) {
|
|
3268
|
-
const { t, setWp, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, useStore } = p;
|
|
4040
|
+
const { t, setWp, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, useStore } = p;
|
|
3269
4041
|
const store = useStore((s) => s);
|
|
3270
4042
|
const storeUrl = store.url;
|
|
3271
4043
|
const backgroundType = store.backgroundType;
|
|
@@ -3275,9 +4047,16 @@ window.__ModuleLoader__.load({
|
|
|
3275
4047
|
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
3276
4048
|
const [dragOver, setDragOver] = (0, react.useState)(false);
|
|
3277
4049
|
const [spinTick, setSpinTick] = (0, react.useState)(0);
|
|
3278
|
-
const
|
|
4050
|
+
const [mode, setModeState] = (0, react.useState)(rBgMode());
|
|
4051
|
+
const isVideo = backgroundType === "video";
|
|
4052
|
+
const isStatic = backgroundType === "image" || isVideo;
|
|
4053
|
+
const isGenerated = !isStatic;
|
|
3279
4054
|
const activeGenType = isGenerated && generatedBg ? generatedBg.type : "mesh";
|
|
3280
4055
|
const onFileSelect = (f) => {
|
|
4056
|
+
if (f.type.startsWith("video/")) {
|
|
4057
|
+
setVideo(f, f.type);
|
|
4058
|
+
return;
|
|
4059
|
+
}
|
|
3281
4060
|
readImg(f, (d) => {
|
|
3282
4061
|
if (d) setWp(d);
|
|
3283
4062
|
});
|
|
@@ -3286,10 +4065,18 @@ window.__ModuleLoader__.load({
|
|
|
3286
4065
|
e.preventDefault();
|
|
3287
4066
|
setDragOver(false);
|
|
3288
4067
|
const f = e.dataTransfer.files?.[0];
|
|
3289
|
-
if (f && f.type.startsWith("image/")) onFileSelect(f);
|
|
4068
|
+
if (f && (f.type.startsWith("image/") || f.type.startsWith("video/"))) onFileSelect(f);
|
|
4069
|
+
};
|
|
4070
|
+
const switchToStatic = () => {
|
|
4071
|
+
if (isStatic) return;
|
|
4072
|
+
setBgType(isVideo || cfg.videoMime !== null ? "video" : "image");
|
|
3290
4073
|
};
|
|
3291
|
-
const
|
|
3292
|
-
if (
|
|
4074
|
+
const setMode = (m) => {
|
|
4075
|
+
if (m === mode) return;
|
|
4076
|
+
setModeState(m);
|
|
4077
|
+
cfg.bgMode = m;
|
|
4078
|
+
applyWp();
|
|
4079
|
+
saveConfig();
|
|
3293
4080
|
};
|
|
3294
4081
|
const setGenType = (type) => {
|
|
3295
4082
|
if (type === activeGenType) return;
|
|
@@ -3367,15 +4154,20 @@ window.__ModuleLoader__.load({
|
|
|
3367
4154
|
isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3368
4155
|
className: "dab-hero-badge",
|
|
3369
4156
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SparkleIcon, { size: 11 }), t("liveBadge")]
|
|
4157
|
+
}) : isVideo ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4158
|
+
className: "dab-hero-badge",
|
|
4159
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VideoIcon, { size: 11 }), t("bgVideoBadge")]
|
|
3370
4160
|
}) : null,
|
|
3371
4161
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3372
4162
|
className: "dab-hero-veil",
|
|
3373
|
-
children: [
|
|
4163
|
+
children: [isStatic && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3374
4164
|
type: "button",
|
|
3375
4165
|
className: "dab-btn",
|
|
4166
|
+
disabled: mode !== "fit",
|
|
4167
|
+
title: mode !== "fit" ? t("bgEditLocked") : void 0,
|
|
3376
4168
|
onClick: () => setEditorOpen(true),
|
|
3377
4169
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 13 }), t("bgEdit")]
|
|
3378
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4170
|
+
}) : isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3379
4171
|
type: "button",
|
|
3380
4172
|
className: "dab-btn",
|
|
3381
4173
|
onClick: () => {
|
|
@@ -3383,7 +4175,7 @@ window.__ModuleLoader__.load({
|
|
|
3383
4175
|
setSpinTick((x) => x + 1);
|
|
3384
4176
|
},
|
|
3385
4177
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefreshIcon, { size: 13 }), t("bgRegenerate")]
|
|
3386
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4178
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3387
4179
|
type: "button",
|
|
3388
4180
|
className: "dab-btn dab-btn-danger",
|
|
3389
4181
|
onClick: () => setWp(null),
|
|
@@ -3418,7 +4210,7 @@ window.__ModuleLoader__.load({
|
|
|
3418
4210
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3419
4211
|
type: "button",
|
|
3420
4212
|
className: `dab-seg-item${!isGenerated ? " is-active" : ""}`,
|
|
3421
|
-
onClick:
|
|
4213
|
+
onClick: switchToStatic,
|
|
3422
4214
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhotoIcon, { size: 14 }), t("bgSourceImage")]
|
|
3423
4215
|
}),
|
|
3424
4216
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -3432,28 +4224,44 @@ window.__ModuleLoader__.load({
|
|
|
3432
4224
|
]
|
|
3433
4225
|
})
|
|
3434
4226
|
}),
|
|
3435
|
-
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
4227
|
+
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3436
4228
|
className: "dab-card dab-card-hover dab-rise",
|
|
3437
4229
|
style: { "--d": 3 },
|
|
3438
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4230
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3439
4231
|
className: "dab-chip-row",
|
|
3440
4232
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3441
4233
|
type: "button",
|
|
3442
4234
|
className: "dab-btn dab-btn-primary",
|
|
3443
4235
|
onClick: () => fileRef.current?.click(),
|
|
3444
4236
|
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", {
|
|
4237
|
+
}), storeUrl || isVideo ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [isStatic && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3446
4238
|
type: "button",
|
|
3447
4239
|
className: "dab-btn",
|
|
4240
|
+
disabled: mode !== "fit",
|
|
4241
|
+
title: mode !== "fit" ? t("bgEditLocked") : void 0,
|
|
3448
4242
|
onClick: () => setEditorOpen(true),
|
|
3449
4243
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 14 }), t("bgEdit")]
|
|
3450
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4244
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3451
4245
|
type: "button",
|
|
3452
4246
|
className: "dab-btn dab-btn-ghost dab-btn-danger",
|
|
3453
4247
|
onClick: () => setWp(null),
|
|
3454
4248
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 14 }), t("bgRemove")]
|
|
3455
4249
|
})] }) : null]
|
|
3456
|
-
})
|
|
4250
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4251
|
+
style: { marginTop: 16 },
|
|
4252
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4253
|
+
className: "dab-swatch-title",
|
|
4254
|
+
children: t("bgModeTitle")
|
|
4255
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4256
|
+
className: "dab-chip-row",
|
|
4257
|
+
children: BG_MODES.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4258
|
+
type: "button",
|
|
4259
|
+
className: `dab-chip${mode === m.mode ? " is-active" : ""}`,
|
|
4260
|
+
onClick: () => setMode(m.mode),
|
|
4261
|
+
children: t(m.labelKey)
|
|
4262
|
+
}, m.mode))
|
|
4263
|
+
})]
|
|
4264
|
+
})]
|
|
3457
4265
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3458
4266
|
className: "dab-card dab-rise",
|
|
3459
4267
|
style: { "--d": 3 },
|
|
@@ -3529,7 +4337,6 @@ window.__ModuleLoader__.load({
|
|
|
3529
4337
|
step: 1,
|
|
3530
4338
|
def: Math.round(generatedBg.scale * 100),
|
|
3531
4339
|
fmt: (v) => `${v}%`,
|
|
3532
|
-
onInput: () => {},
|
|
3533
4340
|
onChange: (v) => updateGenerated({ scale: v / 100 })
|
|
3534
4341
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
3535
4342
|
label: t("bgMeshIntensity"),
|
|
@@ -3538,7 +4345,6 @@ window.__ModuleLoader__.load({
|
|
|
3538
4345
|
step: 1,
|
|
3539
4346
|
def: Math.round(generatedBg.intensity * 100),
|
|
3540
4347
|
fmt: (v) => `${v}%`,
|
|
3541
|
-
onInput: () => {},
|
|
3542
4348
|
onChange: (v) => updateGenerated({ intensity: v / 100 })
|
|
3543
4349
|
})] }) : null,
|
|
3544
4350
|
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 +4354,6 @@ window.__ModuleLoader__.load({
|
|
|
3548
4354
|
step: 1,
|
|
3549
4355
|
def: Math.round(generatedBg.speed * 100),
|
|
3550
4356
|
fmt: (v) => `${v}%`,
|
|
3551
|
-
onInput: () => {},
|
|
3552
4357
|
onChange: (v) => updateGenerated({ speed: v / 100 })
|
|
3553
4358
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
3554
4359
|
label: t("bgShaderScale"),
|
|
@@ -3557,7 +4362,6 @@ window.__ModuleLoader__.load({
|
|
|
3557
4362
|
step: 1,
|
|
3558
4363
|
def: Math.round(generatedBg.scale * 100),
|
|
3559
4364
|
fmt: (v) => `${v}%`,
|
|
3560
|
-
onInput: () => {},
|
|
3561
4365
|
onChange: (v) => updateGenerated({ scale: v / 100 })
|
|
3562
4366
|
})] }) : null,
|
|
3563
4367
|
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 +4371,6 @@ window.__ModuleLoader__.load({
|
|
|
3567
4371
|
step: 1,
|
|
3568
4372
|
def: Math.round(generatedBg.density * 100),
|
|
3569
4373
|
fmt: (v) => `${v}%`,
|
|
3570
|
-
onInput: () => {},
|
|
3571
4374
|
onChange: (v) => updateGenerated({ density: v / 100 })
|
|
3572
4375
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
3573
4376
|
label: t("bgPatternScale"),
|
|
@@ -3576,7 +4379,6 @@ window.__ModuleLoader__.load({
|
|
|
3576
4379
|
step: 1,
|
|
3577
4380
|
def: Math.round(generatedBg.scale * 100),
|
|
3578
4381
|
fmt: (v) => `${v}%`,
|
|
3579
|
-
onInput: () => {},
|
|
3580
4382
|
onChange: (v) => updateGenerated({ scale: v / 100 })
|
|
3581
4383
|
})] }) : null
|
|
3582
4384
|
]
|
|
@@ -3666,7 +4468,7 @@ window.__ModuleLoader__.load({
|
|
|
3666
4468
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3667
4469
|
ref: fileRef,
|
|
3668
4470
|
type: "file",
|
|
3669
|
-
accept: "image/*",
|
|
4471
|
+
accept: "image/*,video/*",
|
|
3670
4472
|
style: { display: "none" },
|
|
3671
4473
|
onChange: (e) => {
|
|
3672
4474
|
const f = e.target.files?.[0];
|
|
@@ -3675,18 +4477,20 @@ window.__ModuleLoader__.load({
|
|
|
3675
4477
|
e.target.value = "";
|
|
3676
4478
|
}
|
|
3677
4479
|
}),
|
|
3678
|
-
editorOpen && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BgEditor, {
|
|
4480
|
+
editorOpen && storeUrl && isStatic && mode === "fit" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BgEditor, {
|
|
3679
4481
|
url: storeUrl,
|
|
3680
4482
|
t,
|
|
3681
4483
|
onClose: () => setEditorOpen(false),
|
|
3682
4484
|
onCommit: (z, x, y, iw, ih) => {
|
|
3683
|
-
|
|
4485
|
+
const st = {
|
|
3684
4486
|
zoom: z,
|
|
3685
4487
|
x,
|
|
3686
4488
|
y,
|
|
3687
4489
|
iw,
|
|
3688
4490
|
ih
|
|
3689
4491
|
};
|
|
4492
|
+
if (backgroundType === "video") cfg.videoBgState = st;
|
|
4493
|
+
else cfg.bgState = st;
|
|
3690
4494
|
applyWp();
|
|
3691
4495
|
saveConfig();
|
|
3692
4496
|
setEditorOpen(false);
|
|
@@ -3908,25 +4712,9 @@ window.__ModuleLoader__.load({
|
|
|
3908
4712
|
/**
|
|
3909
4713
|
* dsh-any-background — browser half entry.
|
|
3910
4714
|
*
|
|
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)
|
|
4715
|
+
* Wires the plugin lifecycle: theme registration, wallpaper layer, viewport
|
|
4716
|
+
* watch, i18n, settings-section injection, boot restore, watchdog. The heavy
|
|
4717
|
+
* lifting lives in the sibling modules (state/rpc/wallpaper/utils/components).
|
|
3930
4718
|
*/
|
|
3931
4719
|
const name = "dsh-any-background";
|
|
3932
4720
|
const inject = [
|
|
@@ -3959,16 +4747,15 @@ window.__ModuleLoader__.load({
|
|
|
3959
4747
|
customDispose?.();
|
|
3960
4748
|
if (colorTimerRef.current !== null) window.clearTimeout(colorTimerRef.current);
|
|
3961
4749
|
}, "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
|
-
}
|
|
4750
|
+
const styleEl = document.createElement("style");
|
|
4751
|
+
styleEl.dataset.plugin = "dsh-any-background";
|
|
4752
|
+
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}${INPUT_BLUR_RULE}`;
|
|
4753
|
+
document.head.appendChild(styleEl);
|
|
3969
4754
|
ctx.effect(() => () => {
|
|
3970
4755
|
styleEl?.parentNode?.removeChild(styleEl);
|
|
3971
4756
|
}, "dsh-any-background: gradient");
|
|
4757
|
+
const disposeDragQuality = watchWallpaperDragQuality();
|
|
4758
|
+
ctx.effect(() => () => disposeDragQuality(), "dsh-any-background: drag quality");
|
|
3972
4759
|
let rev = 0;
|
|
3973
4760
|
let colorRev = 0;
|
|
3974
4761
|
let bgRev = 0;
|
|
@@ -4020,7 +4807,22 @@ window.__ModuleLoader__.load({
|
|
|
4020
4807
|
const [h, s, l] = rColor();
|
|
4021
4808
|
registerCustom(h, s, l);
|
|
4022
4809
|
}
|
|
4023
|
-
if (cfg.backgroundType
|
|
4810
|
+
if (cfg.backgroundType === "video") {
|
|
4811
|
+
const v = rWpVideo();
|
|
4812
|
+
if (v) {
|
|
4813
|
+
captureVideoSnapshot(v).then((snap) => {
|
|
4814
|
+
if (rWpVideo() !== v) return;
|
|
4815
|
+
setWpVideoSnapshot(snap);
|
|
4816
|
+
applyThemeColor();
|
|
4817
|
+
syncBg();
|
|
4818
|
+
});
|
|
4819
|
+
applyWp();
|
|
4820
|
+
} else {
|
|
4821
|
+
cfg.backgroundType = "image";
|
|
4822
|
+
setWpUrl(rWpImage());
|
|
4823
|
+
applyThemeColor();
|
|
4824
|
+
}
|
|
4825
|
+
} else if (cfg.backgroundType !== "image") {
|
|
4024
4826
|
if (cfg.regenerateOnReload) regenerateGeneratedBg();
|
|
4025
4827
|
else if (cfg.generatedBg) updateGeneratedBg(cfg.generatedBg);
|
|
4026
4828
|
persistConfig();
|
|
@@ -4068,6 +4870,25 @@ window.__ModuleLoader__.load({
|
|
|
4068
4870
|
const sectionInject = (actions) => {
|
|
4069
4871
|
bound = actions;
|
|
4070
4872
|
syncBg();
|
|
4873
|
+
const playVideoFromBlob = (blob, mime) => {
|
|
4874
|
+
const localUrl = URL.createObjectURL(blob);
|
|
4875
|
+
cfg.backgroundType = "video";
|
|
4876
|
+
setWpUrl(null);
|
|
4877
|
+
cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
|
|
4878
|
+
setWpVideoUrl(localUrl, mime ?? blob.type ?? "video/mp4");
|
|
4879
|
+
applyWp();
|
|
4880
|
+
syncBg();
|
|
4881
|
+
const applied = rWpVideo();
|
|
4882
|
+
captureVideoSnapshot(localUrl).then((snap) => {
|
|
4883
|
+
if (rWpVideo() !== applied) return;
|
|
4884
|
+
setWpVideoSnapshot(snap);
|
|
4885
|
+
applyThemeColor();
|
|
4886
|
+
syncBg();
|
|
4887
|
+
});
|
|
4888
|
+
uploadVideo(blob, mime ?? blob.type ?? "video/mp4").then((ok) => {
|
|
4889
|
+
if (ok) persistConfig();
|
|
4890
|
+
});
|
|
4891
|
+
};
|
|
4071
4892
|
const [wh, ws, wl] = rColor();
|
|
4072
4893
|
const [dh, ds, dv] = hslToHsv(wh, ws, wl);
|
|
4073
4894
|
return {
|
|
@@ -4103,9 +4924,45 @@ window.__ModuleLoader__.load({
|
|
|
4103
4924
|
setWpUrl(u);
|
|
4104
4925
|
setBgState({ ...DEFAULT_CONFIG.bgState });
|
|
4105
4926
|
persistWallpaper(u);
|
|
4927
|
+
if (u === null) {
|
|
4928
|
+
setWpVideoUrl(null, null);
|
|
4929
|
+
persistVideo(null);
|
|
4930
|
+
}
|
|
4106
4931
|
applyThemeColor();
|
|
4107
4932
|
syncBg();
|
|
4108
4933
|
},
|
|
4934
|
+
setVideo: async (u, mime) => {
|
|
4935
|
+
setBgDark(null);
|
|
4936
|
+
if (u === null) {
|
|
4937
|
+
setWpVideoUrl(null, null);
|
|
4938
|
+
cfg.backgroundType = "image";
|
|
4939
|
+
setWpUrl(rWpImage());
|
|
4940
|
+
persistVideo(null);
|
|
4941
|
+
applyThemeColor();
|
|
4942
|
+
syncBg();
|
|
4943
|
+
saveConfig();
|
|
4944
|
+
return;
|
|
4945
|
+
}
|
|
4946
|
+
if (typeof u !== "string") {
|
|
4947
|
+
playVideoFromBlob(u, mime);
|
|
4948
|
+
return;
|
|
4949
|
+
}
|
|
4950
|
+
const live = await persistVideo(u) ? VIDEO_SERVE_URL : u;
|
|
4951
|
+
cfg.backgroundType = "video";
|
|
4952
|
+
setWpUrl(null);
|
|
4953
|
+
cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
|
|
4954
|
+
setWpVideoUrl(live, mime ?? null);
|
|
4955
|
+
applyWp();
|
|
4956
|
+
saveConfig();
|
|
4957
|
+
syncBg();
|
|
4958
|
+
const applied = rWpVideo();
|
|
4959
|
+
captureVideoSnapshot(live).then((snap) => {
|
|
4960
|
+
if (rWpVideo() !== applied) return;
|
|
4961
|
+
setWpVideoSnapshot(snap);
|
|
4962
|
+
applyThemeColor();
|
|
4963
|
+
syncBg();
|
|
4964
|
+
});
|
|
4965
|
+
},
|
|
4109
4966
|
setBgType: (type) => {
|
|
4110
4967
|
setBackgroundType(type);
|
|
4111
4968
|
saveConfig();
|
|
@@ -4158,7 +5015,7 @@ window.__ModuleLoader__.load({
|
|
|
4158
5015
|
extractColor: async () => {
|
|
4159
5016
|
const url = rWp();
|
|
4160
5017
|
if (!url) return false;
|
|
4161
|
-
const hsl = await extractWallpaperColor(url, rBgState());
|
|
5018
|
+
const hsl = await extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState());
|
|
4162
5019
|
if (!hsl) return false;
|
|
4163
5020
|
cfg.color = hsl;
|
|
4164
5021
|
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
@@ -4169,12 +5026,29 @@ window.__ModuleLoader__.load({
|
|
|
4169
5026
|
bound?.syncColor(hsv, colorRev);
|
|
4170
5027
|
return true;
|
|
4171
5028
|
},
|
|
4172
|
-
exportTheme: () => {
|
|
5029
|
+
exportTheme: async () => {
|
|
5030
|
+
let videoPayload = null;
|
|
5031
|
+
if (cfg.backgroundType === "video") {
|
|
5032
|
+
const vurl = rWpVideo();
|
|
5033
|
+
if (vurl) try {
|
|
5034
|
+
const blob = await fetch(vurl).then((r) => r.blob());
|
|
5035
|
+
videoPayload = await new Promise((resolve, reject) => {
|
|
5036
|
+
const fr = new FileReader();
|
|
5037
|
+
fr.onload = () => resolve(fr.result);
|
|
5038
|
+
fr.onerror = () => reject(fr.error);
|
|
5039
|
+
fr.readAsDataURL(blob);
|
|
5040
|
+
});
|
|
5041
|
+
if (videoPayload && !/^data:video\//.test(videoPayload)) videoPayload = videoPayload.replace(/^data:[^;,]*/, `data:${cfg.videoMime ?? "video/mp4"}`);
|
|
5042
|
+
} catch {
|
|
5043
|
+
videoPayload = null;
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
4173
5046
|
const payload = {
|
|
4174
5047
|
version: 2,
|
|
4175
5048
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4176
5049
|
config: cfg,
|
|
4177
|
-
wallpaper: cfg.backgroundType === "image" ? rWp() : null
|
|
5050
|
+
wallpaper: cfg.backgroundType === "image" ? rWp() : null,
|
|
5051
|
+
video: videoPayload
|
|
4178
5052
|
};
|
|
4179
5053
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
|
4180
5054
|
const url = URL.createObjectURL(blob);
|
|
@@ -4191,7 +5065,38 @@ window.__ModuleLoader__.load({
|
|
|
4191
5065
|
const d = data;
|
|
4192
5066
|
if (typeof d.config !== "object" || d.config === null) return false;
|
|
4193
5067
|
adoptConfig(d.config);
|
|
4194
|
-
if (cfg.backgroundType === "
|
|
5068
|
+
if (cfg.backgroundType === "video") {
|
|
5069
|
+
const video = typeof d.video === "string" && /^data:video\//.test(d.video) ? d.video : null;
|
|
5070
|
+
if (video !== null) {
|
|
5071
|
+
let blob = null;
|
|
5072
|
+
try {
|
|
5073
|
+
blob = await fetch(video).then((r) => r.blob());
|
|
5074
|
+
} catch {
|
|
5075
|
+
blob = null;
|
|
5076
|
+
}
|
|
5077
|
+
if (blob !== null) playVideoFromBlob(blob, cfg.videoMime);
|
|
5078
|
+
else {
|
|
5079
|
+
const live = await persistVideo(video) ? VIDEO_SERVE_URL : video;
|
|
5080
|
+
setWpVideoUrl(live, cfg.videoMime);
|
|
5081
|
+
applyWp();
|
|
5082
|
+
const applied = rWpVideo();
|
|
5083
|
+
captureVideoSnapshot(live).then((snap) => {
|
|
5084
|
+
if (rWpVideo() !== applied) return;
|
|
5085
|
+
setWpVideoSnapshot(snap);
|
|
5086
|
+
applyThemeColor();
|
|
5087
|
+
syncBg();
|
|
5088
|
+
});
|
|
5089
|
+
}
|
|
5090
|
+
} else {
|
|
5091
|
+
setWpVideoUrl(null, null);
|
|
5092
|
+
persistVideo(null);
|
|
5093
|
+
cfg.backgroundType = "image";
|
|
5094
|
+
setWpImageUrl(null);
|
|
5095
|
+
setWpUrl(null);
|
|
5096
|
+
persistWallpaper(null);
|
|
5097
|
+
applyThemeColor();
|
|
5098
|
+
}
|
|
5099
|
+
} else if (cfg.backgroundType === "image") {
|
|
4195
5100
|
const wallpaper = typeof d.wallpaper === "string" && /^data:image\//.test(d.wallpaper) ? d.wallpaper : null;
|
|
4196
5101
|
setWpImageUrl(wallpaper);
|
|
4197
5102
|
setWpUrl(wallpaper);
|