wallpaper-engine-dsh 0.1.0 → 0.1.3
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 +41 -67
- package/README.zh.md +40 -68
- package/lib/client.js +648 -145
- package/lib/core.js +96 -4
- package/lib/index.js +32 -69
- package/package.json +17 -2
package/lib/client.js
CHANGED
|
@@ -14,6 +14,10 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
const SCRIM_ID = "wallpaper-engine-dsh-scrim";
|
|
15
15
|
const STYLE_TAG_ID = "wallpaper-engine-dsh/styles";
|
|
16
16
|
const CROSSFADE_MS = 520;
|
|
17
|
+
/** Battery at or below this level (and discharging) auto-pauses playback. */
|
|
18
|
+
const BATTERY_SAVER_LEVEL = 0.2;
|
|
19
|
+
/** Below this FPS the picker shows a performance hint. */
|
|
20
|
+
const LOW_FPS = 24;
|
|
17
21
|
|
|
18
22
|
// Respect the OS "reduce motion" preference: start paused rather than
|
|
19
23
|
// autoplaying a video loop at someone who asked for less motion.
|
|
@@ -21,6 +25,182 @@ window.__ModuleLoader__.load({
|
|
|
21
25
|
typeof window.matchMedia === "function" &&
|
|
22
26
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
23
27
|
|
|
28
|
+
// ── i18n ─────────────────────────────────────────────────────────────────────
|
|
29
|
+
const STRINGS = {
|
|
30
|
+
zh: {
|
|
31
|
+
settingsLabel: "壁纸背景 (Wallpaper Engine)",
|
|
32
|
+
scanning: "正在扫描 Wallpaper Engine…",
|
|
33
|
+
connError: "未能连接 Wallpaper Engine:",
|
|
34
|
+
retry: "重试",
|
|
35
|
+
refreshing: "刷新中…",
|
|
36
|
+
refresh: "刷新",
|
|
37
|
+
pause: "暂停",
|
|
38
|
+
play: "播放",
|
|
39
|
+
close: "关闭",
|
|
40
|
+
searchPlaceholder: "搜索壁纸(标题或 ID)…",
|
|
41
|
+
matched: (m, n) => "匹配 " + m + " / " + n + " 张",
|
|
42
|
+
emptyNoInstall: "未检测到 Wallpaper Engine — 请确认已通过 Steam 安装",
|
|
43
|
+
emptyNoUsable: (n) => "已发现 " + n + " 张壁纸,但都缺少可播放内容或预览图",
|
|
44
|
+
emptyNoProjects: "未在任何项目目录中发现壁纸(默认项目/我的项目/创意工坊)",
|
|
45
|
+
noMatch: (q) => "没有匹配「" + q + "」的壁纸",
|
|
46
|
+
closeCard: "✕ 关闭",
|
|
47
|
+
closeTitle: "关闭壁纸",
|
|
48
|
+
noPreview: "无预览",
|
|
49
|
+
badgeVideo: "视频",
|
|
50
|
+
badgeWeb: "网页",
|
|
51
|
+
badgeStill: "静态",
|
|
52
|
+
rotationList: "轮播列表",
|
|
53
|
+
selectList: "— 选择轮播列表 —",
|
|
54
|
+
noLists: "— 暂无轮播列表 —",
|
|
55
|
+
groupOption: (name, count, interval) => name + "(" + count + " 可用 · " + interval + " 分钟)",
|
|
56
|
+
newList: "新建",
|
|
57
|
+
editList: "编辑",
|
|
58
|
+
deleteList: "删除",
|
|
59
|
+
name: "名称",
|
|
60
|
+
interval: "间隔",
|
|
61
|
+
order: "顺序",
|
|
62
|
+
sequence: "顺序",
|
|
63
|
+
random: "随机",
|
|
64
|
+
minutes: (m) => m + " 分钟",
|
|
65
|
+
filterPlaceholder: "搜索以筛选…",
|
|
66
|
+
noUsable: "没有可用壁纸",
|
|
67
|
+
selectedCount: (n) => "已选 " + n + " 个",
|
|
68
|
+
importPlaylist: "从 WE 播放列表导入…",
|
|
69
|
+
playlistOption: (name, count) => name + "(" + count + " 可播放)",
|
|
70
|
+
save: "保存",
|
|
71
|
+
cancel: "取消",
|
|
72
|
+
autoRotate: "自动轮转",
|
|
73
|
+
intervalTitle: "当前列表的切换间隔",
|
|
74
|
+
needList: "请先选择或新建一个轮播列表",
|
|
75
|
+
needTwo: "当前列表至少需要 2 张可用壁纸",
|
|
76
|
+
wallpaperBlur: "壁纸模糊",
|
|
77
|
+
scrim: "暗化",
|
|
78
|
+
border: "边框",
|
|
79
|
+
glass: "玻璃",
|
|
80
|
+
pauseOnHidden: "页面隐藏时暂停",
|
|
81
|
+
pauseOnBattery: "低电量时暂停",
|
|
82
|
+
fpsTitle: "页面当前渲染帧率",
|
|
83
|
+
lowFpsHint: "帧率偏低,可尝试暂停视频、增大壁纸模糊或换用静态壁纸",
|
|
84
|
+
language: "语言",
|
|
85
|
+
langAuto: "自动",
|
|
86
|
+
fit: "排布",
|
|
87
|
+
fitCover: "填充",
|
|
88
|
+
fitContain: "适应",
|
|
89
|
+
fitFill: "拉伸",
|
|
90
|
+
fitNone: "原始",
|
|
91
|
+
position: "对齐",
|
|
92
|
+
posCenter: "居中",
|
|
93
|
+
posTop: "顶部",
|
|
94
|
+
posBottom: "底部",
|
|
95
|
+
posLeft: "居左",
|
|
96
|
+
posRight: "居右",
|
|
97
|
+
statusGroup: (name, total, usable, interval, order) =>
|
|
98
|
+
"列表「" + name + "」:" + total + " 项 · " + usable + " 可用 · 每 " + interval + " 分钟 · " + order,
|
|
99
|
+
statusUsable: (n) => n + " 张可用壁纸",
|
|
100
|
+
autoRotating: "自动轮转中",
|
|
101
|
+
autoPaused: "已自动暂停",
|
|
102
|
+
confirmDelete: (name) => "删除轮播列表「" + name + "」?",
|
|
103
|
+
defaultGroupName: (n) => "轮播列表 " + n,
|
|
104
|
+
defaultGroupBase: "轮播列表",
|
|
105
|
+
},
|
|
106
|
+
en: {
|
|
107
|
+
settingsLabel: "Wallpaper Engine",
|
|
108
|
+
scanning: "Scanning Wallpaper Engine…",
|
|
109
|
+
connError: "Could not reach Wallpaper Engine: ",
|
|
110
|
+
retry: "Retry",
|
|
111
|
+
refreshing: "Refreshing…",
|
|
112
|
+
refresh: "Refresh",
|
|
113
|
+
pause: "Pause",
|
|
114
|
+
play: "Play",
|
|
115
|
+
close: "Close",
|
|
116
|
+
searchPlaceholder: "Search wallpapers (title or ID)…",
|
|
117
|
+
matched: (m, n) => m + " / " + n + " matched",
|
|
118
|
+
emptyNoInstall: "Wallpaper Engine not found — make sure it is installed via Steam",
|
|
119
|
+
emptyNoUsable: (n) => "Found " + n + " wallpapers, but none have playable media or a preview image",
|
|
120
|
+
emptyNoProjects: "No wallpapers found in any project folder (defaults / my projects / workshop)",
|
|
121
|
+
noMatch: (q) => "No wallpapers match \"" + q + "\"",
|
|
122
|
+
closeCard: "✕ Close",
|
|
123
|
+
closeTitle: "Clear wallpaper",
|
|
124
|
+
noPreview: "No preview",
|
|
125
|
+
badgeVideo: "Video",
|
|
126
|
+
badgeWeb: "Web",
|
|
127
|
+
badgeStill: "Still",
|
|
128
|
+
rotationList: "Rotation list",
|
|
129
|
+
selectList: "— Select a list —",
|
|
130
|
+
noLists: "— No lists yet —",
|
|
131
|
+
groupOption: (name, count, interval) => name + "(" + count + " usable · " + interval + " min)",
|
|
132
|
+
newList: "New",
|
|
133
|
+
editList: "Edit",
|
|
134
|
+
deleteList: "Delete",
|
|
135
|
+
name: "Name",
|
|
136
|
+
interval: "Interval",
|
|
137
|
+
order: "Order",
|
|
138
|
+
sequence: "Sequence",
|
|
139
|
+
random: "Random",
|
|
140
|
+
minutes: (m) => m + " min",
|
|
141
|
+
filterPlaceholder: "Filter…",
|
|
142
|
+
noUsable: "No usable wallpapers",
|
|
143
|
+
selectedCount: (n) => n + " selected",
|
|
144
|
+
importPlaylist: "Import from WE playlist…",
|
|
145
|
+
playlistOption: (name, count) => name + "(" + count + " playable)",
|
|
146
|
+
save: "Save",
|
|
147
|
+
cancel: "Cancel",
|
|
148
|
+
autoRotate: "Auto-rotate",
|
|
149
|
+
intervalTitle: "Switch interval for the current list",
|
|
150
|
+
needList: "Create or select a rotation list first",
|
|
151
|
+
needTwo: "The list needs at least 2 usable wallpapers",
|
|
152
|
+
wallpaperBlur: "Wallpaper blur",
|
|
153
|
+
scrim: "Scrim",
|
|
154
|
+
border: "Borders",
|
|
155
|
+
glass: "Glass",
|
|
156
|
+
pauseOnHidden: "Pause when tab hidden",
|
|
157
|
+
pauseOnBattery: "Pause on low battery",
|
|
158
|
+
fpsTitle: "Current page frame rate",
|
|
159
|
+
lowFpsHint: "Low frame rate — try pausing the video, raising wallpaper blur, or using a still wallpaper",
|
|
160
|
+
language: "Language",
|
|
161
|
+
langAuto: "Auto",
|
|
162
|
+
fit: "Fit",
|
|
163
|
+
fitCover: "Cover",
|
|
164
|
+
fitContain: "Contain",
|
|
165
|
+
fitFill: "Stretch",
|
|
166
|
+
fitNone: "Original",
|
|
167
|
+
position: "Align",
|
|
168
|
+
posCenter: "Center",
|
|
169
|
+
posTop: "Top",
|
|
170
|
+
posBottom: "Bottom",
|
|
171
|
+
posLeft: "Left",
|
|
172
|
+
posRight: "Right",
|
|
173
|
+
statusGroup: (name, total, usable, interval, order) =>
|
|
174
|
+
"List \"" + name + "\": " + total + " items · " + usable + " usable · every " + interval + " min · " + order,
|
|
175
|
+
statusUsable: (n) => n + " usable wallpapers",
|
|
176
|
+
autoRotating: "Auto-rotating",
|
|
177
|
+
autoPaused: "Auto-paused",
|
|
178
|
+
confirmDelete: (name) => "Delete rotation list \"" + name + "\"?",
|
|
179
|
+
defaultGroupName: (n) => "Rotation list " + n,
|
|
180
|
+
defaultGroupBase: "Rotation list",
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** Language the DSH shell reports via its locale service (null = unknown). */
|
|
185
|
+
let shellLang = null;
|
|
186
|
+
|
|
187
|
+
function browserLang() {
|
|
188
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
189
|
+
return String(navigator.language).toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function resolveLang() {
|
|
195
|
+
if (selection.lang === "zh" || selection.lang === "en") return selection.lang;
|
|
196
|
+
return shellLang || browserLang() || "zh";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Current string table. */
|
|
200
|
+
function S() {
|
|
201
|
+
return STRINGS[resolveLang()];
|
|
202
|
+
}
|
|
203
|
+
|
|
24
204
|
// ── Defaults ─────────────────────────────────────────────────────────────────
|
|
25
205
|
const DEFAULTS = {
|
|
26
206
|
scrim: 0.25,
|
|
@@ -32,18 +212,22 @@ window.__ModuleLoader__.load({
|
|
|
32
212
|
rotationGroupId: "",
|
|
33
213
|
rotationGroups: [],
|
|
34
214
|
rotationSeeded: false,
|
|
215
|
+
pauseOnHidden: true,
|
|
216
|
+
pauseOnBattery: true,
|
|
217
|
+
lang: "auto",
|
|
218
|
+
fit: "cover",
|
|
219
|
+
position: "center",
|
|
35
220
|
};
|
|
36
221
|
|
|
222
|
+
const FIT_MODES = ["cover", "contain", "fill", "none"];
|
|
223
|
+
const POSITIONS = ["center", "top", "bottom", "left", "right"];
|
|
224
|
+
|
|
37
225
|
// ── Persisted selection ─────────────────────────────────────────────────────
|
|
38
226
|
function clampNum(v, lo, hi, fallback) {
|
|
39
227
|
return typeof v === "number" && isFinite(v) && v >= lo && v <= hi ? v : fallback;
|
|
40
228
|
}
|
|
41
229
|
|
|
42
|
-
|
|
43
|
-
// picked from the inventory, its own switch interval (minutes) and order.
|
|
44
|
-
// Fully client-side (localStorage) — rotation never depends on Wallpaper
|
|
45
|
-
// Engine's own config.json playlist paths.
|
|
46
|
-
function readRotationGroups(raw) {
|
|
230
|
+
function readRotationGroups(raw, baseName) {
|
|
47
231
|
if (!Array.isArray(raw)) return [];
|
|
48
232
|
const groups = [];
|
|
49
233
|
for (const g of raw) {
|
|
@@ -52,7 +236,7 @@ window.__ModuleLoader__.load({
|
|
|
52
236
|
if (!id) continue;
|
|
53
237
|
groups.push({
|
|
54
238
|
id,
|
|
55
|
-
name: typeof g.name === "string" && g.name.trim() ? g.name.trim() :
|
|
239
|
+
name: typeof g.name === "string" && g.name.trim() ? g.name.trim() : baseName,
|
|
56
240
|
interval: clampNum(g.interval, 1, 1440, DEFAULTS.rotationInterval),
|
|
57
241
|
order: g.order === "random" ? "random" : "sequence",
|
|
58
242
|
wallpaperIds: Array.isArray(g.wallpaperIds)
|
|
@@ -76,8 +260,13 @@ window.__ModuleLoader__.load({
|
|
|
76
260
|
wallpaperBlur: clampNum(o.wallpaperBlur, 0, 60, DEFAULTS.wallpaperBlur),
|
|
77
261
|
rotationEnabled: o.rotationEnabled === true,
|
|
78
262
|
rotationGroupId: typeof o.rotationGroupId === "string" ? o.rotationGroupId : "",
|
|
79
|
-
rotationGroups: readRotationGroups(o.rotationGroups),
|
|
263
|
+
rotationGroups: readRotationGroups(o.rotationGroups, STRINGS.zh.defaultGroupBase),
|
|
80
264
|
rotationSeeded: o.rotationSeeded === true,
|
|
265
|
+
pauseOnHidden: o.pauseOnHidden !== false,
|
|
266
|
+
pauseOnBattery: o.pauseOnBattery !== false,
|
|
267
|
+
lang: o.lang === "zh" || o.lang === "en" ? o.lang : "auto",
|
|
268
|
+
fit: FIT_MODES.indexOf(o.fit) >= 0 ? o.fit : DEFAULTS.fit,
|
|
269
|
+
position: POSITIONS.indexOf(o.position) >= 0 ? o.position : DEFAULTS.position,
|
|
81
270
|
};
|
|
82
271
|
} catch {
|
|
83
272
|
return { id: "", ...DEFAULTS };
|
|
@@ -92,6 +281,10 @@ window.__ModuleLoader__.load({
|
|
|
92
281
|
playing: !REDUCED_MOTION,
|
|
93
282
|
loading: false,
|
|
94
283
|
rotationTimer: null,
|
|
284
|
+
// Reasons playback is auto-paused (hidden tab / low battery). Distinct from
|
|
285
|
+
// the user's play/pause choice: effective playback = playing && no reasons.
|
|
286
|
+
autoPauseReasons: new Set(),
|
|
287
|
+
fps: null, // last sampled frames-per-second, null when unsampled
|
|
95
288
|
// Live draft of the group being created/edited (null when editor closed).
|
|
96
289
|
editing: null,
|
|
97
290
|
inventory: { installDir: null, wallpapers: [], total: 0, portableCount: 0, playlists: [], error: null },
|
|
@@ -120,10 +313,34 @@ window.__ModuleLoader__.load({
|
|
|
120
313
|
rotationGroupId: selection.rotationGroupId,
|
|
121
314
|
rotationGroups: selection.rotationGroups,
|
|
122
315
|
rotationSeeded: selection.rotationSeeded,
|
|
316
|
+
pauseOnHidden: selection.pauseOnHidden,
|
|
317
|
+
pauseOnBattery: selection.pauseOnBattery,
|
|
318
|
+
lang: selection.lang,
|
|
319
|
+
fit: selection.fit,
|
|
320
|
+
position: selection.position,
|
|
123
321
|
}));
|
|
124
322
|
} catch { /* storage full / blocked → session-only state, not fatal */ }
|
|
125
323
|
}
|
|
126
324
|
|
|
325
|
+
// ── Wallpaper predicates ─────────────────────────────────────────────────────
|
|
326
|
+
// playable: real media (video/web) renders live.
|
|
327
|
+
// static: scene/application with a preview image renders as a still.
|
|
328
|
+
function isPlayable(w) {
|
|
329
|
+
return Boolean(w && w.playable && (w.type === "video" || w.type === "web"));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function isStatic(w) {
|
|
333
|
+
return Boolean(w && !isPlayable(w) && typeof w.preview === "string" && w.preview);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function isSelectable(w) {
|
|
337
|
+
return isPlayable(w) || isStatic(w);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function selectableInventory() {
|
|
341
|
+
return selection.inventory.wallpapers.filter(isSelectable);
|
|
342
|
+
}
|
|
343
|
+
|
|
127
344
|
// ── Inventory ────────────────────────────────────────────────────────────────
|
|
128
345
|
async function loadInventory(forceRefresh) {
|
|
129
346
|
selection.loading = true;
|
|
@@ -173,8 +390,8 @@ window.__ModuleLoader__.load({
|
|
|
173
390
|
persistSelection();
|
|
174
391
|
}
|
|
175
392
|
|
|
176
|
-
// Drop a selection whose wallpaper vanished or
|
|
177
|
-
if (selection.id && !selection.inventory.wallpapers.some((w) => w.id === selection.id &&
|
|
393
|
+
// Drop a selection whose wallpaper vanished or became unusable.
|
|
394
|
+
if (selection.id && !selection.inventory.wallpapers.some((w) => w.id === selection.id && isSelectable(w))) {
|
|
178
395
|
selection.id = "";
|
|
179
396
|
persistSelection();
|
|
180
397
|
}
|
|
@@ -190,14 +407,6 @@ window.__ModuleLoader__.load({
|
|
|
190
407
|
emit();
|
|
191
408
|
}
|
|
192
409
|
|
|
193
|
-
function isPlayable(w) {
|
|
194
|
-
return Boolean(w && w.playable && (w.type === "video" || w.type === "web"));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function playableInventory() {
|
|
198
|
-
return selection.inventory.wallpapers.filter(isPlayable);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
410
|
// ── Rotation groups ─────────────────────────────────────────────────────────
|
|
202
411
|
function activeRotationGroup() {
|
|
203
412
|
return selection.rotationGroups.find((g) => g.id === selection.rotationGroupId) || null;
|
|
@@ -206,7 +415,7 @@ window.__ModuleLoader__.load({
|
|
|
206
415
|
function groupWallpapers(group) {
|
|
207
416
|
if (!group || !Array.isArray(group.wallpaperIds)) return [];
|
|
208
417
|
const byId = new Map(selection.inventory.wallpapers.map((w) => [w.id, w]));
|
|
209
|
-
return group.wallpaperIds.map((id) => byId.get(id)).filter(
|
|
418
|
+
return group.wallpaperIds.map((id) => byId.get(id)).filter(isSelectable);
|
|
210
419
|
}
|
|
211
420
|
|
|
212
421
|
function rotationCandidates() {
|
|
@@ -220,14 +429,14 @@ window.__ModuleLoader__.load({
|
|
|
220
429
|
function seedGroupsFromPlaylists() {
|
|
221
430
|
const source = selection.inventory.playlists.find((p) => (p.portableCount || 0) >= 2);
|
|
222
431
|
if (!source) return false;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
432
|
+
const byId = new Map(selection.inventory.wallpapers.map((w) => [w.id, w]));
|
|
433
|
+
const ids = Array.isArray(source.wallpaperIds)
|
|
434
|
+
? source.wallpaperIds.filter((id) => isSelectable(byId.get(id)))
|
|
435
|
+
: [];
|
|
227
436
|
if (ids.length < 2) return false;
|
|
228
437
|
selection.rotationGroups.push({
|
|
229
438
|
id: nextGroupId(),
|
|
230
|
-
name: typeof source.name === "string" && source.name.trim() ? source.name.trim() :
|
|
439
|
+
name: typeof source.name === "string" && source.name.trim() ? source.name.trim() : S().defaultGroupBase,
|
|
231
440
|
interval: DEFAULTS.rotationInterval,
|
|
232
441
|
order: source.order === "random" ? "random" : "sequence",
|
|
233
442
|
wallpaperIds: ids,
|
|
@@ -286,7 +495,7 @@ window.__ModuleLoader__.load({
|
|
|
286
495
|
function startCreateGroup() {
|
|
287
496
|
selection.editing = {
|
|
288
497
|
id: nextGroupId(),
|
|
289
|
-
name:
|
|
498
|
+
name: S().defaultGroupName(selection.rotationGroups.length + 1),
|
|
290
499
|
interval: DEFAULTS.rotationInterval,
|
|
291
500
|
order: "sequence",
|
|
292
501
|
wallpaperIds: [],
|
|
@@ -300,7 +509,7 @@ window.__ModuleLoader__.load({
|
|
|
300
509
|
const idx = selection.rotationGroups.findIndex((g) => g.id === draft.id);
|
|
301
510
|
const cleaned = {
|
|
302
511
|
id: draft.id,
|
|
303
|
-
name: typeof draft.name === "string" && draft.name.trim() ? draft.name.trim() :
|
|
512
|
+
name: typeof draft.name === "string" && draft.name.trim() ? draft.name.trim() : S().defaultGroupBase,
|
|
304
513
|
interval: clampNum(draft.interval, 1, 1440, DEFAULTS.rotationInterval),
|
|
305
514
|
order: draft.order === "random" ? "random" : "sequence",
|
|
306
515
|
wallpaperIds: Array.isArray(draft.wallpaperIds)
|
|
@@ -346,8 +555,8 @@ window.__ModuleLoader__.load({
|
|
|
346
555
|
|
|
347
556
|
function importPlaylistIntoDraft(playlist) {
|
|
348
557
|
if (!selection.editing || !playlist || !Array.isArray(playlist.wallpaperIds)) return;
|
|
349
|
-
const
|
|
350
|
-
selection.editing.wallpaperIds = playlist.wallpaperIds.filter((id) =>
|
|
558
|
+
const usable = new Set(selectableInventory().map((w) => w.id));
|
|
559
|
+
selection.editing.wallpaperIds = playlist.wallpaperIds.filter((id) => usable.has(id));
|
|
351
560
|
emit();
|
|
352
561
|
}
|
|
353
562
|
|
|
@@ -357,32 +566,52 @@ window.__ModuleLoader__.load({
|
|
|
357
566
|
const w = selection.id
|
|
358
567
|
? selection.inventory.wallpapers.find((x) => x.id === selection.id)
|
|
359
568
|
: null;
|
|
360
|
-
if (
|
|
569
|
+
if (isPlayable(w)) {
|
|
570
|
+
selection.url = w.media;
|
|
571
|
+
selection.type = w.type; // "video" | "web"
|
|
572
|
+
} else if (w && isStatic(w)) {
|
|
573
|
+
selection.url = w.preview;
|
|
574
|
+
selection.type = "image";
|
|
575
|
+
} else {
|
|
361
576
|
selection.url = null;
|
|
362
577
|
selection.type = null;
|
|
363
|
-
} else {
|
|
364
|
-
selection.url = w.media;
|
|
365
|
-
selection.type = w.type;
|
|
366
578
|
}
|
|
367
579
|
syncRotationTimer();
|
|
368
580
|
emit();
|
|
369
581
|
}
|
|
370
582
|
|
|
583
|
+
// ── Auto-pause (resource monitor) ───────────────────────────────────────────
|
|
584
|
+
function setAutoPause(reason, on) {
|
|
585
|
+
const had = selection.autoPauseReasons.size > 0;
|
|
586
|
+
if (on) selection.autoPauseReasons.add(reason);
|
|
587
|
+
else selection.autoPauseReasons.delete(reason);
|
|
588
|
+
if ((selection.autoPauseReasons.size > 0) !== had) emit();
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function effectivePlaying() {
|
|
592
|
+
return selection.playing && selection.autoPauseReasons.size === 0;
|
|
593
|
+
}
|
|
594
|
+
|
|
371
595
|
// ── Behind-body layer: wallpaper + scrim, with crossfade ────────────────────
|
|
596
|
+
// Set by a media error handler to force a remount with fresh URLs; cleared
|
|
597
|
+
// once the remount happens.
|
|
598
|
+
let forceRemount = false;
|
|
599
|
+
let lastMediaRecoveryAt = 0;
|
|
600
|
+
|
|
372
601
|
function buildMedia(sel) {
|
|
373
|
-
|
|
374
|
-
? document.createElement("video")
|
|
375
|
-
: document.createElement("iframe");
|
|
602
|
+
let media;
|
|
376
603
|
if (sel.type === "video") {
|
|
604
|
+
media = document.createElement("video");
|
|
377
605
|
media.src = sel.url;
|
|
378
|
-
media.autoplay =
|
|
606
|
+
media.autoplay = effectivePlaying();
|
|
379
607
|
media.loop = true;
|
|
380
608
|
media.muted = true;
|
|
381
609
|
media.setAttribute("playsinline", "");
|
|
382
610
|
media.className = "webg-media";
|
|
383
|
-
} else {
|
|
384
|
-
//
|
|
385
|
-
//
|
|
611
|
+
} else if (sel.type === "web") {
|
|
612
|
+
// Sandboxed opaque origin: wallpaper JS runs but cannot touch DSH
|
|
613
|
+
// storage or APIs.
|
|
614
|
+
media = document.createElement("iframe");
|
|
386
615
|
media.src = sel.url;
|
|
387
616
|
media.setAttribute("sandbox", "allow-scripts");
|
|
388
617
|
media.setAttribute("referrerpolicy", "no-referrer");
|
|
@@ -390,24 +619,45 @@ window.__ModuleLoader__.load({
|
|
|
390
619
|
media.setAttribute("scrolling", "no");
|
|
391
620
|
media.setAttribute("title", "Wallpaper Engine web wallpaper");
|
|
392
621
|
media.className = "webg-media webg-iframe";
|
|
622
|
+
} else {
|
|
623
|
+
// Static wallpaper: the scene/application preview as a still image.
|
|
624
|
+
media = document.createElement("img");
|
|
625
|
+
media.src = sel.url;
|
|
626
|
+
media.alt = "";
|
|
627
|
+
media.draggable = false;
|
|
628
|
+
media.setAttribute("decoding", "async");
|
|
629
|
+
media.className = "webg-media";
|
|
393
630
|
}
|
|
631
|
+
// Self-heal: if the media fails to load (e.g. its token died on an
|
|
632
|
+
// inventory rebuild), refetch fresh URLs and remount — throttled to once
|
|
633
|
+
// per 10 s so a permanently-broken wallpaper cannot loop.
|
|
634
|
+
media.addEventListener("error", () => {
|
|
635
|
+
const now = Date.now();
|
|
636
|
+
if (now - lastMediaRecoveryAt < 10000) return;
|
|
637
|
+
lastMediaRecoveryAt = now;
|
|
638
|
+
forceRemount = true;
|
|
639
|
+
loadInventory(true);
|
|
640
|
+
});
|
|
394
641
|
return media;
|
|
395
642
|
}
|
|
396
643
|
|
|
397
644
|
function syncLayers() {
|
|
398
645
|
const existing = document.getElementById(LAYER_ID);
|
|
646
|
+
const hasTimeout = typeof window !== "undefined" && typeof window.setTimeout === "function";
|
|
399
647
|
|
|
400
648
|
if (selection.url) {
|
|
401
|
-
|
|
649
|
+
// Key by wallpaper id, NOT by media URL: inventory rebuilds re-mint
|
|
650
|
+
// tokens, so a URL-keyed comparison would reload the playing video on
|
|
651
|
+
// every refresh. A genuinely dead URL is recovered via forceRemount.
|
|
652
|
+
const wantKey = selection.type + "|" + selection.id;
|
|
402
653
|
const gotKey = existing && existing.dataset.webgKey;
|
|
403
654
|
|
|
404
|
-
if (existing && gotKey === wantKey) {
|
|
655
|
+
if (existing && gotKey === wantKey && !forceRemount) {
|
|
405
656
|
// Same wallpaper — only play/pause may have changed.
|
|
406
657
|
} else {
|
|
407
|
-
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
// new layer for the play/pause step below.
|
|
658
|
+
forceRemount = false;
|
|
659
|
+
// Crossfade: the new layer fades in over the old one; the old layer
|
|
660
|
+
// surrenders the id FIRST so getElementById resolves to the new one.
|
|
411
661
|
if (existing) existing.removeAttribute("id");
|
|
412
662
|
const node = document.createElement("div");
|
|
413
663
|
node.id = LAYER_ID;
|
|
@@ -415,38 +665,43 @@ window.__ModuleLoader__.load({
|
|
|
415
665
|
node.dataset.webgKey = wantKey;
|
|
416
666
|
node.appendChild(buildMedia(selection));
|
|
417
667
|
document.body.appendChild(node);
|
|
418
|
-
|
|
419
|
-
void node.offsetHeight;
|
|
668
|
+
void node.offsetHeight; // style flush so the enter transition runs
|
|
420
669
|
node.classList.remove("webg-layer--enter");
|
|
421
670
|
if (existing) {
|
|
422
671
|
existing.classList.add("webg-layer--leave");
|
|
423
672
|
const stale = existing;
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
} else {
|
|
427
|
-
stale.remove();
|
|
428
|
-
}
|
|
673
|
+
if (hasTimeout) window.setTimeout(() => stale.remove(), CROSSFADE_MS + 120);
|
|
674
|
+
else stale.remove();
|
|
429
675
|
}
|
|
430
676
|
}
|
|
431
677
|
const node = document.getElementById(LAYER_ID);
|
|
432
678
|
const video = node && node.querySelector("video");
|
|
433
679
|
if (video) {
|
|
434
|
-
if (
|
|
680
|
+
if (effectivePlaying()) { try { const p = video.play(); if (p && p.catch) p.catch(() => {}); } catch {} }
|
|
435
681
|
else video.pause();
|
|
436
682
|
}
|
|
437
683
|
} else if (existing) {
|
|
684
|
+
// Clearing: fade out, then tear down scrim + frame transparency when the
|
|
685
|
+
// fade completes — unless a new wallpaper was applied meanwhile.
|
|
686
|
+
existing.removeAttribute("id");
|
|
438
687
|
existing.classList.add("webg-layer--leave");
|
|
439
688
|
const stale = existing;
|
|
440
|
-
|
|
441
|
-
window.setTimeout(() => stale.remove(), CROSSFADE_MS + 120);
|
|
442
|
-
} else {
|
|
689
|
+
const finish = () => {
|
|
443
690
|
stale.remove();
|
|
444
|
-
|
|
691
|
+
if (!document.getElementById(LAYER_ID)) {
|
|
692
|
+
const s = document.getElementById(SCRIM_ID);
|
|
693
|
+
if (s) s.remove();
|
|
694
|
+
document.body.removeAttribute(ACTIVE_ATTR);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
if (hasTimeout) window.setTimeout(finish, CROSSFADE_MS + 120);
|
|
698
|
+
else finish();
|
|
445
699
|
}
|
|
446
700
|
|
|
447
|
-
// Scrim:
|
|
448
|
-
|
|
701
|
+
// Scrim: present while a wallpaper is active. On clear it outlives the
|
|
702
|
+
// layer until the fade-out finishes, so the veil fades WITH the wallpaper.
|
|
449
703
|
if (selection.url) {
|
|
704
|
+
const scrim = document.getElementById(SCRIM_ID);
|
|
450
705
|
if (!scrim) {
|
|
451
706
|
const s = document.createElement("div");
|
|
452
707
|
s.id = SCRIM_ID;
|
|
@@ -454,9 +709,6 @@ window.__ModuleLoader__.load({
|
|
|
454
709
|
document.body.appendChild(s);
|
|
455
710
|
}
|
|
456
711
|
document.body.setAttribute(ACTIVE_ATTR, "on");
|
|
457
|
-
} else {
|
|
458
|
-
if (scrim) scrim.remove();
|
|
459
|
-
document.body.removeAttribute(ACTIVE_ATTR);
|
|
460
712
|
}
|
|
461
713
|
}
|
|
462
714
|
|
|
@@ -472,6 +724,10 @@ window.__ModuleLoader__.load({
|
|
|
472
724
|
s.setProperty("--webg-wallpaper-blur", selection.wallpaperBlur + "px");
|
|
473
725
|
// Scaling compensates the transparent fringe CSS blur reveals at edges.
|
|
474
726
|
s.setProperty("--webg-wallpaper-scale", (1 + selection.wallpaperBlur * 0.006).toFixed(4));
|
|
727
|
+
// Canvas fit: object-fit/position for video and still wallpapers (iframes
|
|
728
|
+
// ignore object-fit — web wallpapers always fill their layer).
|
|
729
|
+
s.setProperty("--webg-fit", selection.fit);
|
|
730
|
+
s.setProperty("--webg-position", selection.position);
|
|
475
731
|
|
|
476
732
|
// Write the scrim colour directly too, then force a synchronous layout, so
|
|
477
733
|
// slider feedback lands on this frame even on stalled compositors.
|
|
@@ -491,6 +747,8 @@ window.__ModuleLoader__.load({
|
|
|
491
747
|
s.removeProperty("--webg-glass-brightness");
|
|
492
748
|
s.removeProperty("--webg-wallpaper-blur");
|
|
493
749
|
s.removeProperty("--webg-wallpaper-scale");
|
|
750
|
+
s.removeProperty("--webg-fit");
|
|
751
|
+
s.removeProperty("--webg-position");
|
|
494
752
|
const scrim = document.getElementById(SCRIM_ID);
|
|
495
753
|
if (scrim) scrim.style.background = "";
|
|
496
754
|
}
|
|
@@ -512,30 +770,50 @@ window.__ModuleLoader__.load({
|
|
|
512
770
|
);
|
|
513
771
|
}
|
|
514
772
|
|
|
515
|
-
function
|
|
773
|
+
function typeBadge(w, t) {
|
|
774
|
+
if (w.type === "video") return t.badgeVideo;
|
|
775
|
+
if (w.type === "web") return t.badgeWeb;
|
|
776
|
+
return t.badgeStill;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function matchesQuery(w, q) {
|
|
780
|
+
if (!q) return true;
|
|
781
|
+
const needle = q.toLowerCase();
|
|
782
|
+
return (w.title || "").toLowerCase().includes(needle) || (w.id || "").toLowerCase().includes(needle);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function ThumbCard(w, selected, onClick, t) {
|
|
516
786
|
return React.createElement("button", {
|
|
517
787
|
key: w.id,
|
|
518
788
|
className: "webg-card" + (selected ? " webg-card--selected" : ""),
|
|
519
789
|
type: "button",
|
|
520
790
|
onClick: onClick,
|
|
521
|
-
title: w.title,
|
|
791
|
+
title: w.title + "(" + typeBadge(w, t) + ")",
|
|
522
792
|
},
|
|
523
793
|
w.preview
|
|
524
794
|
? React.createElement("img", {
|
|
525
|
-
src: w.preview, alt: w.title, loading: "lazy",
|
|
795
|
+
src: w.preview, alt: w.title, loading: "lazy", decoding: "async",
|
|
526
796
|
onError: (e) => { e.target.style.display = "none"; },
|
|
527
797
|
})
|
|
528
|
-
: React.createElement("span", { className: "webg-card-placeholder" },
|
|
529
|
-
React.createElement("span", { className: "webg-card-type" }, w
|
|
798
|
+
: React.createElement("span", { className: "webg-card-placeholder" }, t.noPreview),
|
|
799
|
+
React.createElement("span", { className: "webg-card-type" }, typeBadge(w, t)),
|
|
530
800
|
React.createElement("span", { className: "webg-card-title" }, w.title),
|
|
531
801
|
);
|
|
532
802
|
}
|
|
533
803
|
|
|
534
804
|
function WallpaperPicker() {
|
|
535
805
|
const sel = useStore();
|
|
806
|
+
const [query, setQuery] = React.useState("");
|
|
807
|
+
const [editorQuery, setEditorQuery] = React.useState("");
|
|
808
|
+
const t = S();
|
|
536
809
|
const onTogglePlay = () => { selection.playing = !selection.playing; emit(); };
|
|
537
810
|
const onClear = () => applySelection("");
|
|
538
811
|
const onRefresh = () => loadInventory(true);
|
|
812
|
+
const onLangChange = (e) => {
|
|
813
|
+
selection.lang = e.target.value;
|
|
814
|
+
persistSelection();
|
|
815
|
+
emit();
|
|
816
|
+
};
|
|
539
817
|
const onGroupChange = (e) => {
|
|
540
818
|
selection.rotationGroupId = e.target.value;
|
|
541
819
|
if (selection.rotationEnabled) {
|
|
@@ -576,99 +854,155 @@ window.__ModuleLoader__.load({
|
|
|
576
854
|
const group = activeRotationGroup();
|
|
577
855
|
if (!group) return;
|
|
578
856
|
if (typeof window !== "undefined" && typeof window.confirm === "function") {
|
|
579
|
-
if (!window.confirm(
|
|
857
|
+
if (!window.confirm(t.confirmDelete(group.name))) return;
|
|
580
858
|
}
|
|
581
859
|
deleteGroup(group.id);
|
|
582
860
|
};
|
|
861
|
+
const onTogglePauseOnHidden = () => {
|
|
862
|
+
selection.pauseOnHidden = !selection.pauseOnHidden;
|
|
863
|
+
if (!selection.pauseOnHidden) setAutoPause("hidden", false);
|
|
864
|
+
persistSelection();
|
|
865
|
+
emit();
|
|
866
|
+
};
|
|
867
|
+
const onTogglePauseOnBattery = () => {
|
|
868
|
+
selection.pauseOnBattery = !selection.pauseOnBattery;
|
|
869
|
+
if (!selection.pauseOnBattery) setAutoPause("battery", false);
|
|
870
|
+
persistSelection();
|
|
871
|
+
emit();
|
|
872
|
+
};
|
|
583
873
|
|
|
584
874
|
const onScrim = (pct) => { selection.scrim = pct / 100; persistSelection(); applyEffects(); emit(); };
|
|
585
875
|
const onBorder = (pct) => { selection.border = pct / 100; persistSelection(); applyEffects(); emit(); };
|
|
586
876
|
const onBlur = (px) => { selection.blur = px; persistSelection(); applyEffects(); emit(); };
|
|
587
877
|
const onWallpaperBlur = (px) => { selection.wallpaperBlur = px; persistSelection(); applyEffects(); emit(); };
|
|
878
|
+
const onFit = (e) => {
|
|
879
|
+
selection.fit = FIT_MODES.indexOf(e.target.value) >= 0 ? e.target.value : "cover";
|
|
880
|
+
persistSelection();
|
|
881
|
+
applyEffects();
|
|
882
|
+
emit();
|
|
883
|
+
};
|
|
884
|
+
const onPosition = (e) => {
|
|
885
|
+
selection.position = POSITIONS.indexOf(e.target.value) >= 0 ? e.target.value : "center";
|
|
886
|
+
persistSelection();
|
|
887
|
+
applyEffects();
|
|
888
|
+
emit();
|
|
889
|
+
};
|
|
588
890
|
|
|
589
891
|
if (!sel.loaded) {
|
|
590
892
|
return React.createElement("div", { className: "webg-picker" },
|
|
591
|
-
React.createElement("span", { className: "webg-hint" },
|
|
893
|
+
React.createElement("span", { className: "webg-hint" }, t.scanning));
|
|
592
894
|
}
|
|
593
895
|
if (sel.inventory.error) {
|
|
594
896
|
return React.createElement("div", { className: "webg-picker" },
|
|
595
897
|
React.createElement("div", { className: "webg-error" },
|
|
596
|
-
|
|
898
|
+
t.connError + sel.inventory.error),
|
|
597
899
|
React.createElement("button", {
|
|
598
900
|
className: "webg-btn", type: "button", onClick: onRefresh, disabled: sel.loading,
|
|
599
|
-
}, sel.loading ?
|
|
901
|
+
}, sel.loading ? t.refreshing : t.retry));
|
|
600
902
|
}
|
|
601
903
|
|
|
602
|
-
const
|
|
904
|
+
const usableList = selectableInventory();
|
|
905
|
+
const q = query.trim();
|
|
906
|
+
const filteredList = usableList.filter((w) => matchesQuery(w, q));
|
|
603
907
|
const groups = sel.rotationGroups;
|
|
604
908
|
const group = activeRotationGroup();
|
|
605
909
|
const candidates = rotationCandidates();
|
|
606
|
-
const
|
|
910
|
+
const usableCount = candidates.length;
|
|
607
911
|
const editing = sel.editing;
|
|
912
|
+
const autoPaused = sel.autoPauseReasons.size > 0;
|
|
608
913
|
const INTERVALS = [1, 5, 10, 30, 60, 120];
|
|
609
914
|
|
|
610
915
|
return React.createElement("div", { className: "webg-picker" },
|
|
916
|
+
// ── Search + language ──
|
|
917
|
+
React.createElement("div", { className: "webg-row" },
|
|
918
|
+
React.createElement("input", {
|
|
919
|
+
className: "webg-text webg-search", type: "search",
|
|
920
|
+
placeholder: t.searchPlaceholder,
|
|
921
|
+
value: query,
|
|
922
|
+
onInput: (e) => setQuery(e.target.value),
|
|
923
|
+
}),
|
|
924
|
+
q && React.createElement("span", { className: "webg-hint" },
|
|
925
|
+
t.matched(filteredList.length, usableList.length)),
|
|
926
|
+
React.createElement("label", { className: "webg-lang" },
|
|
927
|
+
React.createElement("span", { className: "webg-hint" }, t.language),
|
|
928
|
+
React.createElement("select", {
|
|
929
|
+
className: "webg-select webg-lang-select",
|
|
930
|
+
value: sel.lang,
|
|
931
|
+
onChange: onLangChange,
|
|
932
|
+
},
|
|
933
|
+
React.createElement("option", { value: "auto" }, t.langAuto),
|
|
934
|
+
React.createElement("option", { value: "zh" }, "中文"),
|
|
935
|
+
React.createElement("option", { value: "en" }, "English"),
|
|
936
|
+
),
|
|
937
|
+
),
|
|
938
|
+
),
|
|
939
|
+
// ── Grid ──
|
|
611
940
|
React.createElement("div", { className: "webg-grid" },
|
|
612
941
|
React.createElement("button", {
|
|
613
942
|
className: "webg-card" + (sel.id ? "" : " webg-card--selected"),
|
|
614
943
|
type: "button",
|
|
615
944
|
onClick: onClear,
|
|
616
|
-
title:
|
|
945
|
+
title: t.closeTitle,
|
|
617
946
|
},
|
|
618
|
-
React.createElement("span", { className: "webg-card-close" },
|
|
947
|
+
React.createElement("span", { className: "webg-card-close" }, t.closeCard),
|
|
619
948
|
),
|
|
620
|
-
|
|
949
|
+
usableList.length === 0
|
|
621
950
|
? React.createElement("span", { className: "webg-hint" },
|
|
622
|
-
sel.inventory.
|
|
623
|
-
?
|
|
624
|
-
|
|
625
|
-
|
|
951
|
+
sel.inventory.installDir
|
|
952
|
+
? (sel.inventory.total > 0
|
|
953
|
+
? t.emptyNoUsable(sel.inventory.total)
|
|
954
|
+
: t.emptyNoProjects)
|
|
955
|
+
: t.emptyNoInstall)
|
|
956
|
+
: filteredList.length === 0
|
|
957
|
+
? React.createElement("span", { className: "webg-hint" }, t.noMatch(q))
|
|
958
|
+
: filteredList.map((w) => ThumbCard(w, w.id === sel.id, () => applySelection(w.id), t)),
|
|
626
959
|
),
|
|
960
|
+
// ── Actions ──
|
|
627
961
|
React.createElement("div", { className: "webg-row" },
|
|
628
962
|
React.createElement("button", {
|
|
629
963
|
className: "webg-btn", type: "button",
|
|
630
964
|
onClick: onTogglePlay, disabled: !sel.url || sel.type !== "video",
|
|
631
|
-
}, sel.playing ?
|
|
965
|
+
}, sel.playing ? t.pause : t.play),
|
|
632
966
|
React.createElement("button", {
|
|
633
967
|
className: "webg-btn", type: "button",
|
|
634
968
|
onClick: onClear, disabled: !sel.id,
|
|
635
|
-
},
|
|
969
|
+
}, t.close),
|
|
636
970
|
React.createElement("button", {
|
|
637
971
|
className: "webg-btn", type: "button",
|
|
638
972
|
onClick: onRefresh, disabled: sel.loading,
|
|
639
|
-
}, sel.loading ?
|
|
973
|
+
}, sel.loading ? t.refreshing : t.refresh),
|
|
640
974
|
),
|
|
641
975
|
// ── Rotation groups ──
|
|
642
976
|
React.createElement("div", { className: "webg-row webg-playlist-row" },
|
|
643
|
-
React.createElement("span", { className: "webg-hint webg-label" },
|
|
977
|
+
React.createElement("span", { className: "webg-hint webg-label" }, t.rotationList),
|
|
644
978
|
React.createElement("select", {
|
|
645
979
|
className: "webg-select webg-playlist-select",
|
|
646
980
|
value: sel.rotationGroupId,
|
|
647
981
|
onChange: onGroupChange,
|
|
648
982
|
disabled: groups.length === 0,
|
|
649
983
|
},
|
|
650
|
-
React.createElement("option", { value: "" }, groups.length ?
|
|
984
|
+
React.createElement("option", { value: "" }, groups.length ? t.selectList : t.noLists),
|
|
651
985
|
...groups.map((g) => React.createElement("option", {
|
|
652
986
|
key: g.id, value: g.id,
|
|
653
|
-
}, g.name
|
|
987
|
+
}, t.groupOption(g.name, groupWallpapers(g).length, g.interval))),
|
|
654
988
|
),
|
|
655
989
|
React.createElement("button", {
|
|
656
990
|
className: "webg-btn", type: "button", onClick: startCreateGroup,
|
|
657
|
-
},
|
|
991
|
+
}, t.newList),
|
|
658
992
|
React.createElement("button", {
|
|
659
993
|
className: "webg-btn", type: "button",
|
|
660
994
|
onClick: () => startEditGroup(sel.rotationGroupId),
|
|
661
995
|
disabled: !sel.rotationGroupId,
|
|
662
|
-
},
|
|
996
|
+
}, t.editList),
|
|
663
997
|
React.createElement("button", {
|
|
664
998
|
className: "webg-btn", type: "button",
|
|
665
999
|
onClick: onDeleteGroup,
|
|
666
1000
|
disabled: !sel.rotationGroupId,
|
|
667
|
-
},
|
|
1001
|
+
}, t.deleteList),
|
|
668
1002
|
),
|
|
669
1003
|
editing && React.createElement("div", { className: "webg-editor" },
|
|
670
1004
|
React.createElement("div", { className: "webg-row" },
|
|
671
|
-
React.createElement("span", { className: "webg-hint webg-label" },
|
|
1005
|
+
React.createElement("span", { className: "webg-hint webg-label" }, t.name),
|
|
672
1006
|
React.createElement("input", {
|
|
673
1007
|
className: "webg-text", type: "text",
|
|
674
1008
|
value: editing.name,
|
|
@@ -676,54 +1010,68 @@ window.__ModuleLoader__.load({
|
|
|
676
1010
|
}),
|
|
677
1011
|
),
|
|
678
1012
|
React.createElement("div", { className: "webg-row" },
|
|
679
|
-
React.createElement("span", { className: "webg-hint webg-label" },
|
|
1013
|
+
React.createElement("span", { className: "webg-hint webg-label" }, t.interval),
|
|
680
1014
|
React.createElement("select", {
|
|
681
1015
|
className: "webg-select",
|
|
682
1016
|
value: String(editing.interval),
|
|
683
1017
|
onChange: (e) => { editing.interval = clampNum(Number(e.target.value), 1, 1440, DEFAULTS.rotationInterval); emit(); },
|
|
684
1018
|
},
|
|
685
1019
|
...INTERVALS.map((minutes) =>
|
|
686
|
-
React.createElement("option", { key: minutes, value: String(minutes) }, minutes
|
|
1020
|
+
React.createElement("option", { key: minutes, value: String(minutes) }, t.minutes(minutes)),
|
|
687
1021
|
)),
|
|
688
|
-
React.createElement("span", { className: "webg-hint webg-label" },
|
|
1022
|
+
React.createElement("span", { className: "webg-hint webg-label" }, t.order),
|
|
689
1023
|
React.createElement("select", {
|
|
690
1024
|
className: "webg-select webg-playlist-select",
|
|
691
1025
|
value: editing.order,
|
|
692
1026
|
onChange: (e) => { editing.order = e.target.value; emit(); },
|
|
693
1027
|
},
|
|
694
|
-
React.createElement("option", { value: "sequence" },
|
|
695
|
-
React.createElement("option", { value: "random" },
|
|
1028
|
+
React.createElement("option", { value: "sequence" }, t.sequence),
|
|
1029
|
+
React.createElement("option", { value: "random" }, t.random),
|
|
696
1030
|
),
|
|
697
1031
|
),
|
|
1032
|
+
React.createElement("input", {
|
|
1033
|
+
className: "webg-text", type: "search",
|
|
1034
|
+
placeholder: t.filterPlaceholder,
|
|
1035
|
+
value: editorQuery,
|
|
1036
|
+
onInput: (e) => setEditorQuery(e.target.value),
|
|
1037
|
+
}),
|
|
698
1038
|
React.createElement("div", { className: "webg-editor-grid" },
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1039
|
+
(() => {
|
|
1040
|
+
const eq = editorQuery.trim();
|
|
1041
|
+
const pool = usableList.filter((w) => matchesQuery(w, eq));
|
|
1042
|
+
if (usableList.length === 0) {
|
|
1043
|
+
return React.createElement("span", { className: "webg-hint" }, t.noUsable);
|
|
1044
|
+
}
|
|
1045
|
+
if (pool.length === 0) {
|
|
1046
|
+
return React.createElement("span", { className: "webg-hint" }, t.noMatch(eq));
|
|
1047
|
+
}
|
|
1048
|
+
return pool.map((w) => {
|
|
1049
|
+
const checked = editing.wallpaperIds.indexOf(w.id) >= 0;
|
|
1050
|
+
return React.createElement("button", {
|
|
1051
|
+
key: w.id,
|
|
1052
|
+
className: "webg-editor-card" + (checked ? " webg-editor-card--checked" : ""),
|
|
1053
|
+
type: "button",
|
|
1054
|
+
title: w.title + "(" + typeBadge(w, t) + ")",
|
|
1055
|
+
onClick: () => {
|
|
1056
|
+
const i = editing.wallpaperIds.indexOf(w.id);
|
|
1057
|
+
if (i >= 0) editing.wallpaperIds.splice(i, 1);
|
|
1058
|
+
else editing.wallpaperIds.push(w.id);
|
|
1059
|
+
emit();
|
|
714
1060
|
},
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1061
|
+
},
|
|
1062
|
+
w.preview
|
|
1063
|
+
? React.createElement("img", {
|
|
1064
|
+
src: w.preview, alt: w.title, loading: "lazy", decoding: "async",
|
|
1065
|
+
onError: (e) => { e.target.style.display = "none"; },
|
|
1066
|
+
})
|
|
1067
|
+
: React.createElement("span", { className: "webg-card-placeholder" }, t.noPreview),
|
|
1068
|
+
checked && React.createElement("span", { className: "webg-editor-check" }, "✓"),
|
|
1069
|
+
);
|
|
1070
|
+
});
|
|
1071
|
+
})(),
|
|
724
1072
|
),
|
|
725
1073
|
React.createElement("div", { className: "webg-row" },
|
|
726
|
-
React.createElement("span", { className: "webg-hint" },
|
|
1074
|
+
React.createElement("span", { className: "webg-hint" }, t.selectedCount(editing.wallpaperIds.length)),
|
|
727
1075
|
sel.inventory.playlists.length > 0 && React.createElement("select", {
|
|
728
1076
|
className: "webg-select webg-playlist-select",
|
|
729
1077
|
value: "",
|
|
@@ -732,58 +1080,108 @@ window.__ModuleLoader__.load({
|
|
|
732
1080
|
if (p) importPlaylistIntoDraft(p);
|
|
733
1081
|
},
|
|
734
1082
|
},
|
|
735
|
-
React.createElement("option", { value: "" },
|
|
1083
|
+
React.createElement("option", { value: "" }, t.importPlaylist),
|
|
736
1084
|
...sel.inventory.playlists.map((p) => React.createElement("option", {
|
|
737
1085
|
key: p.id, value: p.id,
|
|
738
|
-
}, p.name
|
|
1086
|
+
}, t.playlistOption(p.name, p.portableCount || 0))),
|
|
739
1087
|
),
|
|
740
1088
|
),
|
|
741
1089
|
React.createElement("div", { className: "webg-row" },
|
|
742
1090
|
React.createElement("button", {
|
|
743
1091
|
className: "webg-btn webg-btn--primary", type: "button",
|
|
744
1092
|
onClick: saveEditingGroup,
|
|
745
|
-
},
|
|
1093
|
+
}, t.save),
|
|
746
1094
|
React.createElement("button", {
|
|
747
1095
|
className: "webg-btn", type: "button",
|
|
748
1096
|
onClick: cancelEditGroup,
|
|
749
|
-
},
|
|
1097
|
+
}, t.cancel),
|
|
750
1098
|
),
|
|
751
1099
|
),
|
|
752
1100
|
React.createElement("div", { className: "webg-row webg-rotation-row" },
|
|
753
|
-
React.createElement("label", { className: "webg-
|
|
1101
|
+
React.createElement("label", { className: "webg-toggle" },
|
|
754
1102
|
React.createElement("input", {
|
|
755
1103
|
type: "checkbox",
|
|
756
1104
|
checked: sel.rotationEnabled,
|
|
757
1105
|
onChange: onToggleRotation,
|
|
758
|
-
disabled: !sel.rotationGroupId ||
|
|
1106
|
+
disabled: !sel.rotationGroupId || usableCount < 2,
|
|
759
1107
|
}),
|
|
760
|
-
|
|
1108
|
+
t.autoRotate,
|
|
761
1109
|
),
|
|
762
1110
|
React.createElement("select", {
|
|
763
1111
|
className: "webg-select webg-rotation-interval",
|
|
764
1112
|
value: String(group ? group.interval : DEFAULTS.rotationInterval),
|
|
765
1113
|
onChange: onGroupInterval,
|
|
766
|
-
disabled: !sel.rotationEnabled || !sel.rotationGroupId ||
|
|
767
|
-
title:
|
|
1114
|
+
disabled: !sel.rotationEnabled || !sel.rotationGroupId || usableCount < 2,
|
|
1115
|
+
title: t.intervalTitle,
|
|
768
1116
|
},
|
|
769
1117
|
...INTERVALS.map((minutes) =>
|
|
770
|
-
React.createElement("option", { key: minutes, value: String(minutes) }, minutes
|
|
1118
|
+
React.createElement("option", { key: minutes, value: String(minutes) }, t.minutes(minutes)),
|
|
771
1119
|
)),
|
|
772
|
-
!sel.rotationGroupId && React.createElement("span", { className: "webg-hint" },
|
|
773
|
-
sel.rotationGroupId &&
|
|
1120
|
+
!sel.rotationGroupId && React.createElement("span", { className: "webg-hint" }, t.needList),
|
|
1121
|
+
sel.rotationGroupId && usableCount < 2 && React.createElement("span", { className: "webg-hint" }, t.needTwo),
|
|
774
1122
|
),
|
|
1123
|
+
// ── Effects ──
|
|
775
1124
|
sel.id && React.createElement(React.Fragment, null,
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1125
|
+
// Canvas fit only matters for video/still wallpapers — an iframe
|
|
1126
|
+
// ignores object-fit and always fills its layer.
|
|
1127
|
+
sel.type !== "web" && React.createElement("div", { className: "webg-row webg-fit-row" },
|
|
1128
|
+
React.createElement("span", { className: "webg-hint webg-label" }, t.fit),
|
|
1129
|
+
React.createElement("select", {
|
|
1130
|
+
className: "webg-select webg-fit-select",
|
|
1131
|
+
value: sel.fit,
|
|
1132
|
+
onChange: onFit,
|
|
1133
|
+
},
|
|
1134
|
+
...[t.fitCover, t.fitContain, t.fitFill, t.fitNone].map((label, i) =>
|
|
1135
|
+
React.createElement("option", { key: FIT_MODES[i], value: FIT_MODES[i] }, label),
|
|
1136
|
+
)),
|
|
1137
|
+
React.createElement("span", { className: "webg-hint webg-label" }, t.position),
|
|
1138
|
+
React.createElement("select", {
|
|
1139
|
+
className: "webg-select webg-position-select",
|
|
1140
|
+
value: sel.position,
|
|
1141
|
+
onChange: onPosition,
|
|
1142
|
+
},
|
|
1143
|
+
...[t.posCenter, t.posTop, t.posBottom, t.posLeft, t.posRight].map((label, i) =>
|
|
1144
|
+
React.createElement("option", { key: POSITIONS[i], value: POSITIONS[i] }, label),
|
|
1145
|
+
)),
|
|
1146
|
+
),
|
|
1147
|
+
SliderRow(t.wallpaperBlur, 0, 60, 1, sel.wallpaperBlur, onWallpaperBlur, sel.wallpaperBlur + "px"),
|
|
1148
|
+
SliderRow(t.scrim, 0, 90, 5, Math.round(sel.scrim * 100), onScrim, Math.round(sel.scrim * 100) + "%"),
|
|
1149
|
+
SliderRow(t.border, 0, 90, 5, Math.round(sel.border * 100), onBorder, Math.round(sel.border * 100) + "%"),
|
|
1150
|
+
SliderRow(t.glass, 0, 40, 1, sel.blur, onBlur, sel.blur + "px"),
|
|
780
1151
|
),
|
|
1152
|
+
// ── Resource monitor ──
|
|
1153
|
+
sel.id && React.createElement("div", { className: "webg-row webg-monitor-row" },
|
|
1154
|
+
React.createElement("label", { className: "webg-toggle" },
|
|
1155
|
+
React.createElement("input", {
|
|
1156
|
+
type: "checkbox", checked: sel.pauseOnHidden, onChange: onTogglePauseOnHidden,
|
|
1157
|
+
}),
|
|
1158
|
+
t.pauseOnHidden,
|
|
1159
|
+
),
|
|
1160
|
+
React.createElement("label", { className: "webg-toggle" },
|
|
1161
|
+
React.createElement("input", {
|
|
1162
|
+
type: "checkbox", checked: sel.pauseOnBattery, onChange: onTogglePauseOnBattery,
|
|
1163
|
+
}),
|
|
1164
|
+
t.pauseOnBattery,
|
|
1165
|
+
),
|
|
1166
|
+
sel.fps !== null && React.createElement("span", {
|
|
1167
|
+
className: "webg-hint webg-fps" + (sel.fps < LOW_FPS ? " webg-fps--low" : ""),
|
|
1168
|
+
title: t.fpsTitle,
|
|
1169
|
+
}, sel.fps + " fps"),
|
|
1170
|
+
),
|
|
1171
|
+
sel.id && sel.fps !== null && sel.fps < LOW_FPS && sel.type === "video" &&
|
|
1172
|
+
React.createElement("div", { className: "webg-row" },
|
|
1173
|
+
React.createElement("span", { className: "webg-hint" }, t.lowFpsHint),
|
|
1174
|
+
),
|
|
1175
|
+
// ── Status ──
|
|
781
1176
|
React.createElement("div", { className: "webg-row" },
|
|
782
1177
|
React.createElement("span", { className: "webg-hint" },
|
|
783
1178
|
(group
|
|
784
|
-
?
|
|
785
|
-
|
|
786
|
-
|
|
1179
|
+
? t.statusGroup(group.name, group.wallpaperIds.length, usableCount, group.interval,
|
|
1180
|
+
group.order === "random" ? t.random : t.sequence)
|
|
1181
|
+
: t.statusUsable(usableList.length)) +
|
|
1182
|
+
(sel.rotationEnabled ? " · " + t.autoRotating : "") +
|
|
1183
|
+
(autoPaused ? " · " + t.autoPaused : "") +
|
|
1184
|
+
(sel.inventory.installDir ? " · " + sel.inventory.installDir : "")),
|
|
787
1185
|
),
|
|
788
1186
|
);
|
|
789
1187
|
}
|
|
@@ -801,7 +1199,9 @@ window.__ModuleLoader__.load({
|
|
|
801
1199
|
.webg-layer--enter { opacity: 0; }
|
|
802
1200
|
.webg-layer--leave { opacity: 0; }
|
|
803
1201
|
.webg-layer .webg-media {
|
|
804
|
-
width: 100%; height: 100%; object-fit: cover;
|
|
1202
|
+
width: 100%; height: 100%; object-fit: var(--webg-fit, cover);
|
|
1203
|
+
object-position: var(--webg-position, center);
|
|
1204
|
+
display: block;
|
|
805
1205
|
background: transparent; border: 0;
|
|
806
1206
|
filter: blur(var(--webg-wallpaper-blur, 0px));
|
|
807
1207
|
transform: scale(var(--webg-wallpaper-scale, 1));
|
|
@@ -813,6 +1213,7 @@ window.__ModuleLoader__.load({
|
|
|
813
1213
|
position: fixed; inset: 0; z-index: -1;
|
|
814
1214
|
pointer-events: none;
|
|
815
1215
|
background: var(--webg-scrim-color, rgba(0, 0, 0, 0.25));
|
|
1216
|
+
transition: background 120ms linear;
|
|
816
1217
|
}
|
|
817
1218
|
|
|
818
1219
|
/* While active: transparent frame + sidebar, higher border contrast. */
|
|
@@ -861,7 +1262,9 @@ window.__ModuleLoader__.load({
|
|
|
861
1262
|
}
|
|
862
1263
|
|
|
863
1264
|
/* ── Picker chrome (token-driven, theme-aware) ── */
|
|
864
|
-
|
|
1265
|
+
/* padding-top lifts the first row off the settings-section divider so the
|
|
1266
|
+
search/language row breathes; gap keeps every row evenly spaced. */
|
|
1267
|
+
.webg-picker { display: flex; flex-direction: column; gap: 12px; padding-top: 10px; }
|
|
865
1268
|
.webg-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
866
1269
|
.webg-hint { font-size: 0.8em; opacity: 0.7; color: var(--dsw-alias-label-tertiary, inherit); }
|
|
867
1270
|
.webg-error {
|
|
@@ -904,13 +1307,23 @@ window.__ModuleLoader__.load({
|
|
|
904
1307
|
font-size: 0.85em;
|
|
905
1308
|
}
|
|
906
1309
|
.webg-text { flex: 1; min-width: 0; }
|
|
1310
|
+
.webg-search { max-width: 320px; }
|
|
1311
|
+
.webg-lang { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
|
|
907
1312
|
.webg-playlist-select { flex: 1; min-width: 0; }
|
|
908
1313
|
.webg-rotation-interval { margin-left: auto; }
|
|
909
|
-
.webg-
|
|
1314
|
+
.webg-toggle {
|
|
1315
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
1316
|
+
accent-color: var(--dsw-alias-brand-primary, #3964fe);
|
|
1317
|
+
font-size: 0.85em; cursor: pointer;
|
|
1318
|
+
}
|
|
910
1319
|
|
|
911
1320
|
.webg-slider { flex: 1; accent-color: var(--dsw-alias-brand-primary, #3964fe); }
|
|
912
1321
|
.webg-slider-row { display: flex; align-items: center; gap: 8px; }
|
|
913
1322
|
|
|
1323
|
+
.webg-monitor-row { gap: 14px; }
|
|
1324
|
+
.webg-fps { font-variant-numeric: tabular-nums; }
|
|
1325
|
+
.webg-fps--low { color: var(--dsw-alias-state-warn-primary, #b93); opacity: 1; }
|
|
1326
|
+
|
|
914
1327
|
/* Wallpaper thumbnail grid. */
|
|
915
1328
|
.webg-grid {
|
|
916
1329
|
display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
@@ -994,8 +1407,26 @@ window.__ModuleLoader__.load({
|
|
|
994
1407
|
const inject = ["slots"];
|
|
995
1408
|
|
|
996
1409
|
function apply(ctx) {
|
|
997
|
-
//
|
|
998
|
-
//
|
|
1410
|
+
// 0. Language: follow the shell's locale service when present, and react
|
|
1411
|
+
// to its change events. Everything is optional — without the service
|
|
1412
|
+
// the browser language decides, and the manual override always wins.
|
|
1413
|
+
const localeSvc = typeof ctx.get === "function" ? ctx.get("locale") : undefined;
|
|
1414
|
+
if (localeSvc && typeof localeSvc.getLocale === "function") {
|
|
1415
|
+
try {
|
|
1416
|
+
const snap = localeSvc.getLocale();
|
|
1417
|
+
if (snap && snap.active) shellLang = snap.active === "en" ? "en" : "zh";
|
|
1418
|
+
} catch { /* keep browser fallback */ }
|
|
1419
|
+
}
|
|
1420
|
+
if (typeof ctx.on === "function") {
|
|
1421
|
+
try {
|
|
1422
|
+
ctx.on("locale/change", (snap) => {
|
|
1423
|
+
const next = snap && snap.active === "en" ? "en" : "zh";
|
|
1424
|
+
if (next !== shellLang) { shellLang = next; emit(); }
|
|
1425
|
+
});
|
|
1426
|
+
} catch { /* event bus unavailable → static language */ }
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// 1. Styles: owned by the fiber — injected on apply, REMOVED on dispose.
|
|
999
1430
|
if (ctx.effect && typeof document !== "undefined") {
|
|
1000
1431
|
ctx.effect(() => {
|
|
1001
1432
|
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(STYLE_TAG_ID) + "]");
|
|
@@ -1032,11 +1463,83 @@ window.__ModuleLoader__.load({
|
|
|
1032
1463
|
});
|
|
1033
1464
|
}
|
|
1034
1465
|
|
|
1035
|
-
// 3.
|
|
1466
|
+
// 3. Resource monitor: auto-pause on hidden tab / low battery, FPS meter.
|
|
1467
|
+
if (ctx.effect && typeof document !== "undefined" &&
|
|
1468
|
+
typeof document.addEventListener === "function") {
|
|
1469
|
+
ctx.effect(() => {
|
|
1470
|
+
const onVisibility = () => {
|
|
1471
|
+
if (!selection.pauseOnHidden) return;
|
|
1472
|
+
setAutoPause("hidden", document.visibilityState === "hidden");
|
|
1473
|
+
};
|
|
1474
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1475
|
+
onVisibility();
|
|
1476
|
+
return () => {
|
|
1477
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1478
|
+
setAutoPause("hidden", false);
|
|
1479
|
+
};
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
if (ctx.effect && typeof navigator !== "undefined" &&
|
|
1483
|
+
typeof navigator.getBattery === "function") {
|
|
1484
|
+
ctx.effect(() => {
|
|
1485
|
+
let battery = null;
|
|
1486
|
+
let cancelled = false;
|
|
1487
|
+
const update = () => {
|
|
1488
|
+
if (!battery || !selection.pauseOnBattery) return;
|
|
1489
|
+
setAutoPause("battery", !battery.charging && battery.level <= BATTERY_SAVER_LEVEL);
|
|
1490
|
+
};
|
|
1491
|
+
navigator.getBattery().then((b) => {
|
|
1492
|
+
if (cancelled) return;
|
|
1493
|
+
battery = b;
|
|
1494
|
+
b.addEventListener("chargingchange", update);
|
|
1495
|
+
b.addEventListener("levelchange", update);
|
|
1496
|
+
update();
|
|
1497
|
+
}).catch(() => {});
|
|
1498
|
+
return () => {
|
|
1499
|
+
cancelled = true;
|
|
1500
|
+
if (battery) {
|
|
1501
|
+
battery.removeEventListener("chargingchange", update);
|
|
1502
|
+
battery.removeEventListener("levelchange", update);
|
|
1503
|
+
}
|
|
1504
|
+
setAutoPause("battery", false);
|
|
1505
|
+
};
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
if (ctx.effect && typeof window !== "undefined" &&
|
|
1509
|
+
typeof window.requestAnimationFrame === "function") {
|
|
1510
|
+
ctx.effect(() => {
|
|
1511
|
+
// FPS sampler: counts rAF callbacks once per second. rAF stops by
|
|
1512
|
+
// itself in hidden tabs, so the meter idles exactly when the page does.
|
|
1513
|
+
let running = true;
|
|
1514
|
+
let rafId = 0;
|
|
1515
|
+
let frames = 0;
|
|
1516
|
+
let window0 = 0;
|
|
1517
|
+
const loop = (t) => {
|
|
1518
|
+
if (!running) return;
|
|
1519
|
+
frames++;
|
|
1520
|
+
if (window0 === 0) window0 = t;
|
|
1521
|
+
else if (t - window0 >= 1000) {
|
|
1522
|
+
selection.fps = Math.round((frames * 1000) / (t - window0));
|
|
1523
|
+
frames = 0;
|
|
1524
|
+
window0 = t;
|
|
1525
|
+
emit();
|
|
1526
|
+
}
|
|
1527
|
+
rafId = window.requestAnimationFrame(loop);
|
|
1528
|
+
};
|
|
1529
|
+
rafId = window.requestAnimationFrame(loop);
|
|
1530
|
+
return () => {
|
|
1531
|
+
running = false;
|
|
1532
|
+
if (typeof window.cancelAnimationFrame === "function") window.cancelAnimationFrame(rafId);
|
|
1533
|
+
selection.fps = null;
|
|
1534
|
+
};
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// 4. Settings row (this slot is NOT the overlay; safe to register into).
|
|
1036
1539
|
if (ctx.slots) {
|
|
1037
1540
|
ctx.slots.inject("settings.general.item", () =>
|
|
1038
1541
|
ctx.slots.register(
|
|
1039
|
-
{ name: "settings.general.item", id: "we-background", order: 500, label:
|
|
1542
|
+
{ name: "settings.general.item", id: "we-background", order: 500, label: S().settingsLabel },
|
|
1040
1543
|
() => React.createElement(WallpaperPicker),
|
|
1041
1544
|
),
|
|
1042
1545
|
);
|