wallpaper-engine-dsh 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,7 @@ A DSH bundle that turns your local **Wallpaper Engine** library into the live ba
10
10
  - **Search** — filter the grid instantly by title or workshop id.
11
11
  - **Rotation lists** — any number of carousel lists, each with its own wallpapers, interval (1–120 min) and order (sequence/random). Your first playable Wallpaper Engine playlist is imported automatically; others can be imported into any list.
12
12
  - **Four live sliders** — wallpaper blur, scrim, border contrast, and glass frosting, all instant and persisted.
13
+ - **Canvas fit** — cover/contain/stretch/original sizing plus five-way alignment for video and still wallpapers.
13
14
  - **Resource monitor** — FPS readout with a low-framerate hint, plus auto-pause when the tab is hidden or the battery runs low (both optional).
14
15
  - **Bilingual** — the settings UI follows the shell's Language preference (Chinese/English), with a manual override in the picker.
15
16
  - **Crossfade** — wallpaper switches and clearing fade smoothly instead of snapping.
package/README.zh.md CHANGED
@@ -10,6 +10,7 @@
10
10
  - **搜索** —— 按标题或创意工坊 ID 即时筛选网格。
11
11
  - **轮播列表** —— 可建任意多个列表,各自拥有壁纸集合、切换间隔(1–120 分钟)和顺序(顺序/随机)。首次运行自动导入第一个可播放的 Wallpaper Engine 播放列表,其余列表可随时导入。
12
12
  - **四个实时滑杆** —— 壁纸模糊、暗化、边框对比、玻璃磨砂,即时生效并持久保存。
13
+ - **画布排布** —— 视频与静态壁纸支持填充/适应/拉伸/原始四种排布和五向对齐。
13
14
  - **资源监控** —— 帧率读数与低帧率提示,页面隐藏或电量不足时自动暂停(均可开关)。
14
15
  - **中英双语** —— 设置界面跟随 DSH 的"语言"偏好自动切换,也可在面板内手动指定。
15
16
  - **交叉淡入** —— 切换与关闭都平滑过渡,不生硬跳变。
package/lib/client.js CHANGED
@@ -83,6 +83,17 @@ window.__ModuleLoader__.load({
83
83
  lowFpsHint: "帧率偏低,可尝试暂停视频、增大壁纸模糊或换用静态壁纸",
84
84
  language: "语言",
85
85
  langAuto: "自动",
86
+ fit: "排布",
87
+ fitCover: "填充",
88
+ fitContain: "适应",
89
+ fitFill: "拉伸",
90
+ fitNone: "原始",
91
+ position: "对齐",
92
+ posCenter: "居中",
93
+ posTop: "顶部",
94
+ posBottom: "底部",
95
+ posLeft: "居左",
96
+ posRight: "居右",
86
97
  statusGroup: (name, total, usable, interval, order) =>
87
98
  "列表「" + name + "」:" + total + " 项 · " + usable + " 可用 · 每 " + interval + " 分钟 · " + order,
88
99
  statusUsable: (n) => n + " 张可用壁纸",
@@ -148,6 +159,17 @@ window.__ModuleLoader__.load({
148
159
  lowFpsHint: "Low frame rate — try pausing the video, raising wallpaper blur, or using a still wallpaper",
149
160
  language: "Language",
150
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",
151
173
  statusGroup: (name, total, usable, interval, order) =>
152
174
  "List \"" + name + "\": " + total + " items · " + usable + " usable · every " + interval + " min · " + order,
153
175
  statusUsable: (n) => n + " usable wallpapers",
@@ -193,8 +215,13 @@ window.__ModuleLoader__.load({
193
215
  pauseOnHidden: true,
194
216
  pauseOnBattery: true,
195
217
  lang: "auto",
218
+ fit: "cover",
219
+ position: "center",
196
220
  };
197
221
 
222
+ const FIT_MODES = ["cover", "contain", "fill", "none"];
223
+ const POSITIONS = ["center", "top", "bottom", "left", "right"];
224
+
198
225
  // ── Persisted selection ─────────────────────────────────────────────────────
199
226
  function clampNum(v, lo, hi, fallback) {
200
227
  return typeof v === "number" && isFinite(v) && v >= lo && v <= hi ? v : fallback;
@@ -238,6 +265,8 @@ window.__ModuleLoader__.load({
238
265
  pauseOnHidden: o.pauseOnHidden !== false,
239
266
  pauseOnBattery: o.pauseOnBattery !== false,
240
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,
241
270
  };
242
271
  } catch {
243
272
  return { id: "", ...DEFAULTS };
@@ -287,6 +316,8 @@ window.__ModuleLoader__.load({
287
316
  pauseOnHidden: selection.pauseOnHidden,
288
317
  pauseOnBattery: selection.pauseOnBattery,
289
318
  lang: selection.lang,
319
+ fit: selection.fit,
320
+ position: selection.position,
290
321
  }));
291
322
  } catch { /* storage full / blocked → session-only state, not fatal */ }
292
323
  }
@@ -693,6 +724,10 @@ window.__ModuleLoader__.load({
693
724
  s.setProperty("--webg-wallpaper-blur", selection.wallpaperBlur + "px");
694
725
  // Scaling compensates the transparent fringe CSS blur reveals at edges.
695
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);
696
731
 
697
732
  // Write the scrim colour directly too, then force a synchronous layout, so
698
733
  // slider feedback lands on this frame even on stalled compositors.
@@ -712,6 +747,8 @@ window.__ModuleLoader__.load({
712
747
  s.removeProperty("--webg-glass-brightness");
713
748
  s.removeProperty("--webg-wallpaper-blur");
714
749
  s.removeProperty("--webg-wallpaper-scale");
750
+ s.removeProperty("--webg-fit");
751
+ s.removeProperty("--webg-position");
715
752
  const scrim = document.getElementById(SCRIM_ID);
716
753
  if (scrim) scrim.style.background = "";
717
754
  }
@@ -838,6 +875,18 @@ window.__ModuleLoader__.load({
838
875
  const onBorder = (pct) => { selection.border = pct / 100; persistSelection(); applyEffects(); emit(); };
839
876
  const onBlur = (px) => { selection.blur = px; persistSelection(); applyEffects(); emit(); };
840
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
+ };
841
890
 
842
891
  if (!sel.loaded) {
843
892
  return React.createElement("div", { className: "webg-picker" },
@@ -1073,6 +1122,28 @@ window.__ModuleLoader__.load({
1073
1122
  ),
1074
1123
  // ── Effects ──
1075
1124
  sel.id && React.createElement(React.Fragment, null,
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
+ ),
1076
1147
  SliderRow(t.wallpaperBlur, 0, 60, 1, sel.wallpaperBlur, onWallpaperBlur, sel.wallpaperBlur + "px"),
1077
1148
  SliderRow(t.scrim, 0, 90, 5, Math.round(sel.scrim * 100), onScrim, Math.round(sel.scrim * 100) + "%"),
1078
1149
  SliderRow(t.border, 0, 90, 5, Math.round(sel.border * 100), onBorder, Math.round(sel.border * 100) + "%"),
@@ -1128,7 +1199,9 @@ window.__ModuleLoader__.load({
1128
1199
  .webg-layer--enter { opacity: 0; }
1129
1200
  .webg-layer--leave { opacity: 0; }
1130
1201
  .webg-layer .webg-media {
1131
- width: 100%; height: 100%; object-fit: cover; display: block;
1202
+ width: 100%; height: 100%; object-fit: var(--webg-fit, cover);
1203
+ object-position: var(--webg-position, center);
1204
+ display: block;
1132
1205
  background: transparent; border: 0;
1133
1206
  filter: blur(var(--webg-wallpaper-blur, 0px));
1134
1207
  transform: scale(var(--webg-wallpaper-scale, 1));
@@ -1189,11 +1262,14 @@ window.__ModuleLoader__.load({
1189
1262
  }
1190
1263
 
1191
1264
  /* ── Picker chrome (token-driven, theme-aware) ── */
1192
- .webg-picker { display: flex; flex-direction: column; gap: 10px; }
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
+ Type scale follows the shell: 14px body → 13px controls → 12px hints. */
1268
+ .webg-picker { display: flex; flex-direction: column; gap: 12px; padding-top: 10px; }
1193
1269
  .webg-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
1194
- .webg-hint { font-size: 0.8em; opacity: 0.7; color: var(--dsw-alias-label-tertiary, inherit); }
1270
+ .webg-hint { font-size: 12px; opacity: 0.7; color: var(--dsw-alias-label-tertiary, inherit); }
1195
1271
  .webg-error {
1196
- font-size: 0.85em;
1272
+ font-size: 13px;
1197
1273
  color: var(--dsw-alias-state-error-primary, #d44);
1198
1274
  }
1199
1275
  .webg-label { min-width: 28px; }
@@ -1206,21 +1282,26 @@ window.__ModuleLoader__.load({
1206
1282
  border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
1207
1283
  background: var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.12));
1208
1284
  color: var(--dsw-alias-label-primary, inherit);
1209
- font-size: 0.85em;
1285
+ font-size: 12px;
1210
1286
  transition: background-color 120ms ease, border-color 120ms ease;
1211
1287
  }
1288
+ /* Ghost-button hover rides the shell's own interactive-hover token. */
1212
1289
  .webg-btn:hover:not(:disabled) {
1213
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.2));
1290
+ background: var(--dsw-alias-interactive-bg-hover,
1291
+ var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.2)));
1214
1292
  border-color: var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.5));
1215
1293
  }
1216
1294
  .webg-btn:disabled { opacity: 0.45; cursor: not-allowed; }
1295
+ /* brand-primary is monochrome (near-black light / near-white dark), so
1296
+ the label must use its inverse token — never a hardcoded white. */
1217
1297
  .webg-btn--primary {
1218
- background: var(--dsw-alias-brand-primary, #3964fe);
1298
+ background: var(--dsw-alias-brand-primary, rgb(15, 17, 21));
1219
1299
  border-color: transparent;
1220
- color: #fff;
1300
+ color: var(--dsw-alias-brand-primary-invert, rgb(249, 250, 251));
1221
1301
  }
1222
1302
  .webg-btn--primary:hover:not(:disabled) {
1223
- background: var(--dsw-alias-brand-primary-hover, #4f74ff);
1303
+ background: var(--dsw-alias-brand-primary, rgb(15, 17, 21));
1304
+ opacity: 0.85;
1224
1305
  }
1225
1306
 
1226
1307
  .webg-select, .webg-text {
@@ -1229,7 +1310,7 @@ window.__ModuleLoader__.load({
1229
1310
  border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
1230
1311
  background: var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.12));
1231
1312
  color: var(--dsw-alias-label-primary, inherit);
1232
- font-size: 0.85em;
1313
+ font-size: 13px;
1233
1314
  }
1234
1315
  .webg-text { flex: 1; min-width: 0; }
1235
1316
  .webg-search { max-width: 320px; }
@@ -1238,11 +1319,11 @@ window.__ModuleLoader__.load({
1238
1319
  .webg-rotation-interval { margin-left: auto; }
1239
1320
  .webg-toggle {
1240
1321
  display: inline-flex; align-items: center; gap: 6px;
1241
- accent-color: var(--dsw-alias-brand-primary, #3964fe);
1242
- font-size: 0.85em; cursor: pointer;
1322
+ accent-color: var(--dsw-alias-brand-primary, rgb(15, 17, 21));
1323
+ font-size: 13px; cursor: pointer;
1243
1324
  }
1244
1325
 
1245
- .webg-slider { flex: 1; accent-color: var(--dsw-alias-brand-primary, #3964fe); }
1326
+ .webg-slider { flex: 1; accent-color: var(--dsw-alias-brand-primary, rgb(15, 17, 21)); }
1246
1327
  .webg-slider-row { display: flex; align-items: center; gap: 8px; }
1247
1328
 
1248
1329
  .webg-monitor-row { gap: 14px; }
@@ -1268,23 +1349,23 @@ window.__ModuleLoader__.load({
1268
1349
  }
1269
1350
  .webg-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
1270
1351
  .webg-card--selected {
1271
- outline: 2px solid var(--dsw-alias-brand-primary, #3964fe);
1352
+ outline: 2px solid var(--dsw-alias-brand-primary, rgb(15, 17, 21));
1272
1353
  outline-offset: -2px;
1273
1354
  }
1274
1355
  .webg-card-close {
1275
1356
  position: absolute; inset: 0;
1276
1357
  display: flex; align-items: center; justify-content: center;
1277
- font-size: 0.8em; color: var(--dsw-alias-label-secondary, #888);
1358
+ font-size: 12px; color: var(--dsw-alias-label-secondary, #888);
1278
1359
  }
1279
1360
  .webg-card-type {
1280
1361
  position: absolute; top: 4px; right: 4px;
1281
1362
  padding: 1px 6px; border-radius: 4px;
1282
- font-size: 0.65em; line-height: 1.5;
1363
+ font-size: 10px; line-height: 1.5;
1283
1364
  color: #fff; background: rgba(0, 0, 0, 0.5);
1284
1365
  }
1285
1366
  .webg-card-title {
1286
1367
  position: absolute; left: 0; right: 0; bottom: 0; padding: 3px 6px;
1287
- font-size: 0.7em; line-height: 1.2; color: #fff;
1368
+ font-size: 11px; line-height: 1.2; color: #fff;
1288
1369
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
1289
1370
  text-overflow: ellipsis; white-space: nowrap; overflow: hidden;
1290
1371
  text-align: left;
@@ -1292,7 +1373,7 @@ window.__ModuleLoader__.load({
1292
1373
  .webg-card-placeholder {
1293
1374
  position: absolute; inset: 0;
1294
1375
  display: flex; align-items: center; justify-content: center;
1295
- font-size: 0.72em; opacity: 0.55;
1376
+ font-size: 11px; opacity: 0.55;
1296
1377
  }
1297
1378
 
1298
1379
  /* Rotation group editor. */
@@ -1318,12 +1399,14 @@ window.__ModuleLoader__.load({
1318
1399
  .webg-editor-card:hover { transform: translateY(-1px); }
1319
1400
  .webg-editor-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
1320
1401
  .webg-editor-card--checked {
1321
- outline: 2px solid var(--dsw-alias-brand-primary, #3964fe);
1402
+ outline: 2px solid var(--dsw-alias-brand-primary, rgb(15, 17, 21));
1322
1403
  outline-offset: -2px;
1323
1404
  }
1324
1405
  .webg-editor-check {
1325
1406
  position: absolute; top: 4px; left: 4px; width: 18px; height: 18px;
1326
- border-radius: 4px; background: var(--dsw-alias-brand-primary, #3964fe); color: #fff;
1407
+ border-radius: 4px;
1408
+ background: var(--dsw-alias-brand-primary, rgb(15, 17, 21));
1409
+ color: var(--dsw-alias-brand-primary-invert, rgb(249, 250, 251));
1327
1410
  font-size: 12px; line-height: 18px; text-align: center;
1328
1411
  }
1329
1412
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wallpaper-engine-dsh",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Wallpaper Engine Video/Web wallpapers as the DSH web GUI background — discovery, sandboxed rendering, rotation lists, liquid-glass tuning.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",