wallpaper-engine-dsh 1.0.0 → 1.0.2

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,7 +10,7 @@ A DSH bundle that turns your local **Wallpaper Engine** library into the live ba
10
10
 
11
11
  **Legibility on any wallpaper.** Text stays readable by construction, not by luck. The veil always opposes the text colour — black in the dark theme, white in the light theme — so neither a pitch-black nor a pure-white wallpaper can swallow the UI. On top of that, a Smart veil samples the wallpaper's brightness every few seconds and automatically strengthens the veil when a scene drifts too bright (or too dark) for the current theme; it also strengthens the border contrast and the glass frosting on the same evidence. Dropdowns, menus, and dialogs sit on DSH's opaque surfaces and are never affected.
12
12
 
13
- **Manual framing.** Pick a base layout — Cover, Contain, Stretch, or Original — then frame the shot yourself. **Adjust** opens the live wallpaper fullscreen: drag to pan, scroll to zoom (0.25×–4×), Esc or Done to finish. The framing persists per selection, and **Reset crop** restores the default in one click.
13
+ **Manual framing.** Pick a base layout — Cover, Contain, Stretch, or Original — then frame the shot yourself. **Adjust** opens the live wallpaper fullscreen: scroll to zoom in (up to 4×), drag to pan within the zoomed framing — the crop is clamped so the image always fills the screen with no exposed edges. Esc or Done finishes, the framing persists per selection, and **Reset crop** restores the default in one click.
14
14
 
15
15
  **Four live sliders.** Wallpaper blur, Scrim, Borders, and Glass — every change lands instantly and persists. Wallpaper blur uses clipped overscan, so blurring never warps or shifts the image; a whisper of dither noise keeps smooth gradients band-free; and at zero blur the media bypasses the filter pipeline entirely for bit-exact pixels.
16
16
 
package/README.zh.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  **任意壁纸都可读。** 文字可读靠的是结构保障,不是运气。遮罩颜色始终与文字颜色相对——深色主题压黑、浅色主题提亮——无论纯黑还是纯白的壁纸都吞不掉界面。在此基础上,"智能可视"每隔几秒采样一次壁纸亮度,画面偏离当前主题的舒适区时自动加强遮罩。下拉菜单、弹窗等浮层坐在 DSH 原生的不透明表面上,从不受影响。
12
12
 
13
- **手动取景。** 先选基础排布——填充、适应、拉伸、原始——然后自己构图:点「调整画面」把壁纸全屏展开,拖动平移、滚轮缩放(0.25×–4×),Esc 或「完成」退出。取景随选择持久保存,「重置裁剪」一键还原。
13
+ **手动取景。** 先选基础排布——填充、适应、拉伸、原始——然后自己构图:点「调整画面」把壁纸全屏展开,滚轮放大(最高 4×)、拖动在放大的画面内平移——裁剪范围经过钳制,画面永远铺满屏幕、不露边。Esc 或「完成」退出,取景随选择持久保存,「重置裁剪」一键还原。
14
14
 
15
15
  **四个实时滑杆。** 壁纸模糊、暗化、边框、玻璃——每一步调节即时生效并持久保存。壁纸模糊采用裁边式超采样,模糊不变形、不偏移;叠加微量抖动噪声消除渐变色带;模糊归零时媒体完全绕开滤镜管线,像素逐位精确。
16
16
 
package/lib/client.js CHANGED
@@ -271,7 +271,9 @@ window.__ModuleLoader__.load({
271
271
  autoScrim: o.autoScrim !== false,
272
272
  lang: o.lang === "zh" || o.lang === "en" ? o.lang : "auto",
273
273
  fit: FIT_MODES.indexOf(o.fit) >= 0 ? o.fit : DEFAULTS.fit,
274
- zoom: clampNum(o.zoom, 0.25, 4, DEFAULTS.zoom),
274
+ zoom: Math.max(1, clampNum(o.zoom, 0.25, 4, DEFAULTS.zoom)),
275
+ // Pan may have been persisted with a larger headroom than the zoom
276
+ // allows (older builds let zoom go below 1) — clampCrop re-seats it.
275
277
  offsetX: clampNum(o.offsetX, -200, 200, DEFAULTS.offsetX),
276
278
  offsetY: clampNum(o.offsetY, -200, 200, DEFAULTS.offsetY),
277
279
  };
@@ -298,6 +300,9 @@ window.__ModuleLoader__.load({
298
300
  inventory: { installDir: null, wallpapers: [], total: 0, portableCount: 0, playlists: [], error: null },
299
301
  loaded: false,
300
302
  };
303
+ // Older builds allowed zoom < 1 and unclamped pans — re-seat any persisted
304
+ // crop into the gap-free envelope before the first render.
305
+ clampCrop();
301
306
 
302
307
  const listeners = new Set();
303
308
  function emit() { for (const fn of [...listeners]) fn(); }
@@ -724,6 +729,18 @@ window.__ModuleLoader__.load({
724
729
  // The layer normally sits at z-index -2 behind the entire app, so it can
725
730
  // never receive pointer events. Adjust mode moves the LIVE layer node into a
726
731
  // topmost transparent overlay — the user edits the real thing, WYSIWYG.
732
+ //
733
+ // Crop geometry invariants: the media box covers the viewport exactly at
734
+ // zoom 1; zooming creates headroom, panning spends it. Zoom floors at 1
735
+ // and pan is clamped to ±(zoom-1)/2·100 %, so a gap-free fullscreen image
736
+ // is guaranteed at every setting.
737
+ function clampCrop() {
738
+ selection.zoom = Math.round(Math.min(4, Math.max(1, selection.zoom)) * 100) / 100;
739
+ const pan = (selection.zoom - 1) * 50;
740
+ selection.offsetX = Math.min(pan, Math.max(-pan, selection.offsetX));
741
+ selection.offsetY = Math.min(pan, Math.max(-pan, selection.offsetY));
742
+ }
743
+
727
744
  let adjustOverlay = null;
728
745
  let adjustCleanup = null;
729
746
 
@@ -780,11 +797,9 @@ window.__ModuleLoader__.load({
780
797
  // content tracks the cursor 1:1 at any blur setting.
781
798
  const w = (overlay.clientWidth || 1) + 2 * selection.wallpaperBlur;
782
799
  const h = (overlay.clientHeight || 1) + 2 * selection.wallpaperBlur;
783
- // True clamping (clampNum is a validate-or-fallback for persisted data).
784
- selection.offsetX = Math.min(200, Math.max(-200,
785
- selection.offsetX + ((e.clientX - drag.x) / w) * 100));
786
- selection.offsetY = Math.min(200, Math.max(-200,
787
- selection.offsetY + ((e.clientY - drag.y) / h) * 100));
800
+ selection.offsetX += ((e.clientX - drag.x) / w) * 100;
801
+ selection.offsetY += ((e.clientY - drag.y) / h) * 100;
802
+ clampCrop(); // gap-free guarantee: pan never outruns the zoom headroom
788
803
  drag = { x: e.clientX, y: e.clientY };
789
804
  applyEffects();
790
805
  };
@@ -802,12 +817,12 @@ window.__ModuleLoader__.load({
802
817
  };
803
818
  const onWheel = (e) => {
804
819
  if (typeof e.preventDefault === "function") e.preventDefault();
805
- // True clamping to [0.25, 4] — clampNum is a validate-or-fallback for
806
- // persisted data and would REJECT an out-of-range zoom instead of
807
- // pinning it to the boundary.
808
- const next = Math.min(4, Math.max(0.25,
809
- selection.zoom * Math.exp(-(e.deltaY || 0) * 0.0012)));
810
- selection.zoom = Math.round(next * 100) / 100;
820
+ // Crop semantics: zoom IN only (1–4×). Zooming below cover would float a
821
+ // shrunken box in empty space — that is not a crop. Tightening zoom also
822
+ // re-clamps the pan into the new headroom, so zooming back to 1 pulls
823
+ // the framing home instead of leaving edge gaps.
824
+ selection.zoom *= Math.exp(-(e.deltaY || 0) * 0.0012);
825
+ clampCrop();
811
826
  applyEffects();
812
827
  persistSelection();
813
828
  };
@@ -1830,6 +1845,12 @@ window.__ModuleLoader__.load({
1830
1845
  const inject = ["slots"];
1831
1846
 
1832
1847
  function apply(ctx) {
1848
+ // A plugin UPDATE re-applies this module WITHOUT re-evaluating it: the old
1849
+ // fiber's cleanup has already set `disposed = true`, which would make the
1850
+ // new fiber's loadInventory return forever (blank picker, no wallpaper).
1851
+ // Every apply starts a fresh lifecycle.
1852
+ disposed = false;
1853
+
1833
1854
  // 0. Language: follow the shell's locale service when present, and react
1834
1855
  // to its change events. Everything is optional — without the service
1835
1856
  // the browser language decides, and the manual override always wins.
package/lib/index.js CHANGED
@@ -228,10 +228,11 @@ export function createRouteRegistrar(webServer, opts = {}) {
228
228
  res.setHeader('X-Content-Type-Options', 'nosniff');
229
229
  if (mime === 'text/html' || mime === 'image/svg+xml') {
230
230
  // Script-capable documents (web wallpapers, SVG previews) get a
231
- // sandboxed CSP even on direct top-level navigation, so they can
232
- // never execute against the DSH origin. The client's iframe sandbox
233
- // already covers embedding; this is the server-side backstop.
234
- res.setHeader('Content-Security-Policy', 'sandbox');
231
+ // sandbox CSP even on direct top-level navigation, so they can
232
+ // never execute against the DSH origin. `allow-scripts` keeps the
233
+ // wallpaper itself functional — inside the sandbox the origin is
234
+ // opaque either way, so the isolation goal is unchanged.
235
+ res.setHeader('Content-Security-Policy', 'sandbox allow-scripts');
235
236
  }
236
237
 
237
238
  const range = core.parseRangeHeader(req.headers.range, st.size);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wallpaper-engine-dsh",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Wallpaper Engine library as the DSH web GUI background — video/web/still wallpapers, rotation lists, search, resource monitor, bilingual UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",