dsh-openup-lewis 1.2.5 → 1.2.6

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,11 +10,11 @@
10
10
 
11
11
  ## 产品功能
12
12
 
13
- - **完整开场**:内置「启」「数字生命」「朋克」「半面」四段视频和「莱茵生命」动画,视频按原长度、原速播放;桌面开场请求真正全屏,进入应用后还原窗口状态。
13
+ - **完整开场**:内置「启」「数字生命」「朋克」「半面」四段视频和「莱茵生命」动画,视频按原长度、原速播放,铺满当前 Harness 窗口,保持窗口大小、位置和最大化状态。
14
14
  - **任意键进入**:动画中按任意键或点击画面进入标题页,再操作一次进入 Harness;动画自然结束后也会停留在标题页。
15
15
  - **三款标题封面**:远山、黑金、蓝雾,保留原有图片与文字。
16
16
  - **自定义素材**:上传自己的视频作为开场,上传喜欢的图片作为结束封面。
17
- - **声音与画面**:支持静音、音量调整、铺满窗口或完整显示;固定播放喜欢的动画,或每次随机选择。
17
+ - **声音与画面**:支持静音和音量调整;开场视频始终铺满当前窗口,可固定播放喜欢的动画,或每次随机选择。
18
18
  - **启动检查更新**:每次重新打开 Harness 自动检查新版本,开场结束并进入应用后再提醒;可选择稍后或直接更新,无需先卸载。
19
19
  - **雾光更新提醒**:半透明磨砂弹窗,支持自定义磨砂度与圆角,可在设置中预览。
20
20
  - **可选快捷入口**:默认从设置进入,可自行开启头像上方的“开机动画”按钮;附魔台系列共享最多 17 个快捷入口。
@@ -42,9 +42,10 @@
42
42
  | 统一品牌 | DSH LEWIS ENCHANT · 附魔台 |
43
43
  | 功能项目 | 开机动画 |
44
44
  | 插件名称 | dsh-openup-lewis |
45
- | 当前版本 | 1.2.5 |
45
+ | 当前版本 | 1.2.6 |
46
46
  | 作者 | FerrariLewis |
47
- | 适用版本 | DeepSeek Harness ≥ 0.2.1-rc.2 且 < 0.3.0;自动全屏取决于桌面版支持。 |
47
+ | 适用版本 | DeepSeek Harness ≥ 0.2.1-rc.2 且 < 0.3.0 |
48
+ | 窗口按钮 | 当前 Harness 桌面版尚未提供插件单独隐藏原生关闭、最大化、最小化按钮的接口。 |
48
49
  | 安装方法 | 在 Harness → 插件 → 添加插件 → 包名或地址中,粘贴本文第一行的安装码,安装并启用后重新打开 Harness。无需 Git、npm 账号或命令行。 |
49
50
  | 设置入口 | 设置 → 开机动画 |
50
51
  | 视频格式 | MP4 / M4V / WebM,单个文件最大 200 MB |
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.2.5",
2
+ "version": "1.2.6",
3
3
  "format": "DSH ModuleLoader CJS + Node ESM",
4
4
  "source": [
5
5
  "src/client.jsx",
package/lib/client.js CHANGED
@@ -91,9 +91,8 @@ var CSS = `
91
91
  @media(max-width:850px){.bs-studio{padding:26px 24px}.bs-library{grid-template-columns:repeat(2,minmax(0,1fr))}.bs-heading h1{font-size:26px}.bs-modal{padding:14px}.bs-modal-close{right:20px;top:20px}}
92
92
  @media(max-width:520px){.bs-studio{padding:24px 16px;border-radius:10px}.bs-heading h1{font-size:23px}.bs-heading h1 span{margin-left:7px}.bs-eyebrow{font-size:8px}.bs-heading p{font-size:11px}.bs-toolbar{align-items:flex-start;flex-direction:column;margin-top:20px;gap:12px}.bs-library{gap:9px}.bs-clip-info{padding:9px 8px 6px;display:block}.bs-clip-info h3{font-size:11px}.bs-clip-info>span{font-size:9px}.bs-clip-actions{padding:0 8px 9px}.bs-clip-actions button{font-size:10px}.bs-upload{min-height:165px;padding:10px}.bs-upload small{font-size:9px}.bs-section-title>span{font-size:9px}.bs-toggle-row,.bs-field{padding:13px 12px;gap:10px}.bs-toggle-row small,.bs-field small{font-size:10px}.bs-toggle-row input{right:12px}.bs-player-top{padding:16px}.bs-player-top>span{font-size:8px;letter-spacing:1px}.bs-player-top b{display:none}.bs-player-top button{font-size:11px;padding:7px 9px}.bs-player-bottom{padding:18px 16px;font-size:10px}.bs-sound{right:16px;bottom:60px}.bs-modal{padding:7px}.bs-modal-close{right:13px;top:17px;font-size:10px}.bs-modal-body .bs-heading h1{font-size:21px}}
93
93
  @media(prefers-reduced-motion:reduce){.bs-player,.bs-studio *,.bs-player *{animation:none!important;transition:none!important}}
94
- /* The native titlebar disappears through the Fullscreen API; this only removes
95
- Harness's optional HTML menu while the Player owns its immersive lease. */
96
- html[data-lewis-immersive="true"] [data-windows-menu]{visibility:hidden!important;pointer-events:none!important}
94
+ /* Hide only the optional HTML menu. The desktop host owns native caption buttons. */
95
+ html[data-lewis-player="true"] [data-windows-menu]{visibility:hidden!important;pointer-events:none!important}
97
96
  .bs-entry-title{margin-top:26px}
98
97
  .bs-reset-area{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:30px;padding:24px 0 2px;border-top:1px solid var(--bs-line)}
99
98
  .bs-reset-area strong{font-size:12px;font-weight:500;color:#d4dfd9}
@@ -3068,204 +3067,6 @@ function UpdatePrompt({ request, status = {}, settings = {}, onDismiss, onUpdate
3068
3067
  } }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "bs-startup-update-card", style: { "--bs-update-radius": `${radius}px`, "--bs-update-blur": `${blur}px` } }, /* @__PURE__ */ import_react3.default.createElement("button", { className: "bs-startup-update-close", type: "button", "aria-label": completed ? "\u5173\u95ED\u66F4\u65B0\u63D0\u793A" : "\u7A0D\u540E\u518D\u8BF4", disabled: busy, onClick: close }, "\xD7"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "bs-startup-update-glyph", "aria-hidden": "true" }, "\u2727"), /* @__PURE__ */ import_react3.default.createElement("p", { className: "bs-startup-update-brand" }, "DSH LEWIS ENCHANT", preview2 && /* @__PURE__ */ import_react3.default.createElement("span", null, " \xB7 \u66F4\u65B0\u63D0\u9192\u5916\u89C2\u9884\u89C8")), /* @__PURE__ */ import_react3.default.createElement("h2", { id: "bs-startup-update-title" }, completed ? "\u65B0\u7684\u5F00\u573A\uFF0C\u5DF2\u51C6\u5907\u5C31\u7EEA\u3002" : "\u65B0\u7684\u5F00\u573A\uFF0C\u51C6\u5907\u597D\u4E86\u3002"), /* @__PURE__ */ import_react3.default.createElement("p", { id: "bs-startup-update-description", className: "bs-startup-update-copy" }, preview2 ? "\u8FD9\u91CC\u53EA\u9884\u89C8\u5F39\u7A97\u5916\u89C2\uFF0C\u4E0D\u4F1A\u4E0B\u8F7D\u6216\u5B89\u88C5\u66F4\u65B0\u3002" : completed ? "\u91CD\u65B0\u6253\u5F00 Harness\uFF0C\u5373\u53EF\u4F7F\u7528\u65B0\u7248\u672C\u3002" : "\u53D1\u73B0\u5F00\u673A\u52A8\u753B\u65B0\u7248\u672C\uFF0C\u4E3A\u4E0B\u4E00\u6B21\u542F\u52A8\u6DFB\u4E00\u70B9\u65B0\u610F\u3002"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "bs-startup-update-versions" }, /* @__PURE__ */ import_react3.default.createElement("span", null, "v", status?.currentVersion || "1.2.5"), /* @__PURE__ */ import_react3.default.createElement("span", { "aria-hidden": "true" }, "\u2192"), /* @__PURE__ */ import_react3.default.createElement("strong", null, preview2 ? "\u540E\u7EED\u7248\u672C" : `v${status.latestVersion}`)), !busy && !completed && /* @__PURE__ */ import_react3.default.createElement("div", { className: "bs-startup-update-summary" }, /* @__PURE__ */ import_react3.default.createElement("p", null, "\u5B8C\u6574\u5B89\u88C5\u5305\u6821\u9A8C\u540E\u66F4\u65B0"), /* @__PURE__ */ import_react3.default.createElement("p", null, "\u4FDD\u7559\u4E2A\u4EBA\u89C6\u9891\u4E0E\u5C01\u9762"), /* @__PURE__ */ import_react3.default.createElement("p", null, "\u91CD\u65B0\u6253\u5F00\u540E\u4F7F\u7528\u65B0\u7248\u672C")), /* @__PURE__ */ import_react3.default.createElement("p", { className: "bs-startup-update-retain" }, "\u4F60\u7684\u89C6\u9891\u3001\u56FE\u7247\u548C\u8BBE\u7F6E\u90FD\u4F1A\u4FDD\u7559"), busy && /* @__PURE__ */ import_react3.default.createElement("p", { className: "bs-startup-update-status", role: "status", "aria-live": "polite" }, phase === "install" ? "\u6821\u9A8C\u901A\u8FC7\uFF0C\u6B63\u5728\u901A\u8FC7 Harness \u5E94\u7528\u66F4\u65B0\u2026" : "\u6B63\u5728\u4E0B\u8F7D\u5E76\u6821\u9A8C\u66F4\u65B0\u5305\u2026"), error && /* @__PURE__ */ import_react3.default.createElement("p", { className: "bs-startup-update-error", role: "alert" }, error), completed && /* @__PURE__ */ import_react3.default.createElement("p", { className: "bs-startup-update-status", role: "status" }, completed.result.application === "restart-required" ? "\u66F4\u65B0\u5B8C\u6210\u3002\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 Harness\u3002" : "\u65B0\u7248\u672C\u5DF2\u5E94\u7528\u3002\u5EFA\u8BAE\u91CD\u65B0\u6253\u5F00 Harness\uFF0C\u8BA9\u6240\u6709\u7A97\u53E3\u4F7F\u7528\u65B0\u7248\u672C\u3002"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "bs-startup-update-actions" }, preview2 ? /* @__PURE__ */ import_react3.default.createElement("button", { className: "bs-startup-update-primary", type: "button", onClick: close }, "\u5173\u95ED\u9884\u89C8") : completed ? /* @__PURE__ */ import_react3.default.createElement("button", { className: "bs-startup-update-primary", type: "button", onClick: close }, "\u77E5\u9053\u4E86") : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("button", { type: "button", disabled: busy, onClick: close }, "\u7A0D\u540E\u518D\u8BF4"), /* @__PURE__ */ import_react3.default.createElement("button", { className: "bs-startup-update-primary", type: "button", disabled: busy, onClick: accept }, busy ? "\u6B63\u5728\u66F4\u65B0\u2026" : error ? "\u91CD\u8BD5\u66F4\u65B0" : `\u66F4\u65B0\u5230 ${status.latestVersion}`)))));
3069
3068
  }
3070
3069
 
3071
- // src/immersive.js
3072
- var sessions = /* @__PURE__ */ new WeakMap();
3073
- var ATTRIBUTE = "data-lewis-immersive";
3074
- function nativeFullscreen(root) {
3075
- return root?.getAttribute?.("data-fullscreen") === "true";
3076
- }
3077
- function hasFullscreen(state) {
3078
- return Boolean(state.doc.fullscreenElement) || nativeFullscreen(state.root);
3079
- }
3080
- function report(state, error) {
3081
- state.lastError = error;
3082
- for (const lease of state.leases) {
3083
- try {
3084
- lease.onError?.(error);
3085
- } catch {
3086
- }
3087
- }
3088
- }
3089
- function mark(state) {
3090
- state.originalAttribute = state.root.getAttribute(ATTRIBUTE);
3091
- state.root.setAttribute(ATTRIBUTE, "true");
3092
- state.marked = true;
3093
- }
3094
- function unmark(state) {
3095
- if (!state.marked) return;
3096
- if (state.originalAttribute === null) state.root.removeAttribute(ATTRIBUTE);
3097
- else state.root.setAttribute(ATTRIBUTE, state.originalAttribute);
3098
- state.marked = false;
3099
- }
3100
- function clean(state) {
3101
- if (state.leases.size || state.pending || state.exiting) return;
3102
- unmark(state);
3103
- state.doc.removeEventListener?.("fullscreenchange", state.changed);
3104
- if (sessions.get(state.doc) === state) sessions.delete(state.doc);
3105
- }
3106
- async function idle(state) {
3107
- if (state.leases.size) return;
3108
- if (state.pending) {
3109
- await state.pending;
3110
- return idle(state);
3111
- }
3112
- if (state.exiting) {
3113
- await state.exiting;
3114
- return idle(state);
3115
- }
3116
- if (state.owned && state.doc.fullscreenElement === state.root) {
3117
- state.owned = false;
3118
- state.status = "restoring";
3119
- let exit;
3120
- state.exiting = Promise.resolve();
3121
- try {
3122
- exit = state.doc.exitFullscreen();
3123
- } catch (error) {
3124
- report(state, error);
3125
- }
3126
- const done = Promise.resolve(exit).catch((error) => report(state, error)).then(() => {
3127
- if (state.exiting === done) state.exiting = null;
3128
- if (!state.leases.size) {
3129
- state.status = "released";
3130
- clean(state);
3131
- }
3132
- });
3133
- state.exiting = done;
3134
- await done;
3135
- return;
3136
- }
3137
- state.owned = false;
3138
- state.status = "released";
3139
- clean(state);
3140
- }
3141
- function enter(state, retry = false) {
3142
- if (!state.leases.size) return Promise.resolve(false);
3143
- if (state.exiting) return state.exiting.then(() => enter(state, retry));
3144
- if (state.pending) return state.pending;
3145
- if (state.leftByUser) {
3146
- state.status = "left-fullscreen";
3147
- return Promise.resolve(false);
3148
- }
3149
- if (hasFullscreen(state)) {
3150
- if (!state.owned) state.preserved = true;
3151
- state.status = state.owned ? "active" : "existing-fullscreen";
3152
- return Promise.resolve(true);
3153
- }
3154
- if (typeof state.root.requestFullscreen !== "function" || state.doc.fullscreenEnabled === false) {
3155
- state.status = "unsupported";
3156
- return Promise.resolve(false);
3157
- }
3158
- if (state.attempted && !retry) return Promise.resolve(false);
3159
- state.attempted = true;
3160
- state.status = "requesting";
3161
- const attempt = { entered: false };
3162
- state.attempt = attempt;
3163
- let request;
3164
- try {
3165
- request = state.root.requestFullscreen({ navigationUI: "hide" });
3166
- } catch (error) {
3167
- request = Promise.reject(error);
3168
- }
3169
- const pending = Promise.resolve(request).then(() => {
3170
- if (state.doc.fullscreenElement === state.root && !state.leftByUser) {
3171
- state.owned = true;
3172
- state.status = "active";
3173
- return true;
3174
- }
3175
- state.status = state.leftByUser ? "left-fullscreen" : "not-entered";
3176
- return false;
3177
- }, (error) => {
3178
- state.status = "denied";
3179
- report(state, error);
3180
- return false;
3181
- }).then(async (active) => {
3182
- if (state.pending === pending) state.pending = null;
3183
- if (state.attempt === attempt) state.attempt = null;
3184
- if (!state.leases.size) {
3185
- await idle(state);
3186
- return false;
3187
- }
3188
- return active;
3189
- });
3190
- state.pending = pending;
3191
- return pending;
3192
- }
3193
- function createSession(doc) {
3194
- const state = {
3195
- doc,
3196
- root: doc.documentElement,
3197
- leases: /* @__PURE__ */ new Set(),
3198
- pending: null,
3199
- exiting: null,
3200
- owned: false,
3201
- leftByUser: false,
3202
- attempted: false,
3203
- retryUsed: false,
3204
- preserved: false,
3205
- attempt: null,
3206
- marked: false,
3207
- originalAttribute: null,
3208
- status: "idle",
3209
- lastError: null
3210
- };
3211
- state.changed = () => {
3212
- const current = doc.fullscreenElement;
3213
- if (state.attempt && current === state.root) state.attempt.entered = true;
3214
- if ((state.owned || state.attempt?.entered) && current !== state.root && !state.exiting) {
3215
- state.owned = false;
3216
- state.leftByUser = true;
3217
- state.status = "left-fullscreen";
3218
- }
3219
- };
3220
- doc.addEventListener?.("fullscreenchange", state.changed);
3221
- sessions.set(doc, state);
3222
- return state;
3223
- }
3224
- function acquireImmersive(doc = globalThis.document, options = {}) {
3225
- if (!doc?.documentElement) {
3226
- return {
3227
- ready: Promise.resolve(false),
3228
- release: () => Promise.resolve(),
3229
- retryFromGesture: () => Promise.resolve(false),
3230
- getStatus: () => "unsupported"
3231
- };
3232
- }
3233
- let state = sessions.get(doc);
3234
- if (!state) state = createSession(doc);
3235
- if (!state.leases.size) {
3236
- state.leftByUser = false;
3237
- state.attempted = false;
3238
- state.retryUsed = false;
3239
- state.preserved = false;
3240
- state.lastError = null;
3241
- mark(state);
3242
- }
3243
- const record = { released: false, onError: options.onError };
3244
- state.leases.add(record);
3245
- const resultForLease = (promise) => promise.then((active) => !record.released && active);
3246
- const ready = resultForLease(enter(state));
3247
- return {
3248
- ready,
3249
- getStatus: () => record.released ? "released" : state.status,
3250
- retryFromGesture(event) {
3251
- if (record.released || state.leftByUser || event?.isTrusted !== true) return Promise.resolve(false);
3252
- if (state.preserved) return Promise.resolve(hasFullscreen(state));
3253
- if (state.pending || state.exiting || hasFullscreen(state)) return resultForLease(enter(state));
3254
- if (state.retryUsed) return Promise.resolve(false);
3255
- state.retryUsed = true;
3256
- return resultForLease(enter(state, true));
3257
- },
3258
- release() {
3259
- if (!record.released) {
3260
- record.released = true;
3261
- state.leases.delete(record);
3262
- if (!state.leases.size) unmark(state);
3263
- }
3264
- return idle(state);
3265
- }
3266
- };
3267
- }
3268
-
3269
3070
  // src/shortcut-registry.js
3270
3071
  var SHORTCUT_LIMIT = 17;
3271
3072
  var SHORTCUT_REGISTRY_KEY = Symbol.for("dsh.lewis.enchant.shortcuts.v1");
@@ -3397,7 +3198,27 @@ var launchRequest = null;
3397
3198
  var launchDecisionSettled = false;
3398
3199
  var latestState = null;
3399
3200
  var subscribers = /* @__PURE__ */ new Set();
3400
- var VERSION2 = true ? "1.2.5" : "1.2.5";
3201
+ var playerWindows = /* @__PURE__ */ new WeakMap();
3202
+ var VERSION2 = true ? "1.2.6" : "1.2.6";
3203
+ function acquirePlayerWindow(doc) {
3204
+ const root = doc.documentElement;
3205
+ let state = playerWindows.get(doc);
3206
+ if (!state) {
3207
+ state = { count: 0, previous: root.getAttribute("data-lewis-player") };
3208
+ playerWindows.set(doc, state);
3209
+ }
3210
+ state.count++;
3211
+ root.setAttribute("data-lewis-player", "true");
3212
+ let released = false;
3213
+ return () => {
3214
+ if (released) return;
3215
+ released = true;
3216
+ if (--state.count) return;
3217
+ if (state.previous === null) root.removeAttribute("data-lewis-player");
3218
+ else root.setAttribute("data-lewis-player", state.previous);
3219
+ playerWindows.delete(doc);
3220
+ };
3221
+ }
3401
3222
  async function api(path, options = {}) {
3402
3223
  const controller = new AbortController();
3403
3224
  const timeout = ["/upload", "/end-images", "/updates/prepare", "/reset"].includes(path) ? 18e4 : path === "/updates/check" ? 2e4 : 7e3;
@@ -3450,7 +3271,6 @@ function Player({ clip, settings, onClose, endOnly = false }) {
3450
3271
  useStyles();
3451
3272
  const video = (0, import_react5.useRef)(null);
3452
3273
  const dialog = (0, import_react5.useRef)(null);
3453
- const immersive = (0, import_react5.useRef)(null);
3454
3274
  const closed = (0, import_react5.useRef)(false);
3455
3275
  const [phase, setPhase] = (0, import_react5.useState)(endOnly ? "end" : "intro");
3456
3276
  const phaseRef = (0, import_react5.useRef)(endOnly ? "end" : "intro");
@@ -3488,24 +3308,19 @@ function Player({ clip, settings, onClose, endOnly = false }) {
3488
3308
  closed.current = false;
3489
3309
  const playerDialog = dialog.current;
3490
3310
  const previous = document.activeElement;
3491
- const lease = acquireImmersive(document);
3492
- immersive.current = lease;
3311
+ const releaseWindow = acquirePlayerWindow(document);
3493
3312
  if (typeof playerDialog.showModal === "function" && !playerDialog.open) playerDialog.showModal();
3494
3313
  else playerDialog.setAttribute("open", "");
3495
3314
  const key = (e) => {
3496
3315
  e.preventDefault();
3497
3316
  e.stopPropagation();
3498
- if (!e.repeat && !e.isComposing) {
3499
- void lease.retryFromGesture(e);
3500
- advance();
3501
- }
3317
+ if (!e.repeat && !e.isComposing) advance();
3502
3318
  };
3503
3319
  window.addEventListener("keydown", key, true);
3504
3320
  playerDialog.focus();
3505
3321
  return () => {
3506
3322
  closed.current = true;
3507
- immersive.current = null;
3508
- void lease.release();
3323
+ releaseWindow();
3509
3324
  window.removeEventListener("keydown", key, true);
3510
3325
  if (playerDialog.open && typeof playerDialog.close === "function") playerDialog.close();
3511
3326
  if (previous?.isConnected) previous.focus?.();
@@ -3600,10 +3415,7 @@ function Player({ clip, settings, onClose, endOnly = false }) {
3600
3415
  advance();
3601
3416
  },
3602
3417
  onPointerDown: (e) => {
3603
- if (!e.target.closest("[data-bs-control]")) {
3604
- void immersive.current?.retryFromGesture(e.nativeEvent);
3605
- advance();
3606
- }
3418
+ if (!e.target.closest("[data-bs-control]")) advance();
3607
3419
  }
3608
3420
  },
3609
3421
  phase === "end" ? /* @__PURE__ */ import_react5.default.createElement(EndTitle, { settings }) : /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, clip.kind === "sequence" ? /* @__PURE__ */ import_react5.default.createElement(StartupSequence, { sound: settings.sound, volume: settings.volume, onEnded: showEnd }) : /* @__PURE__ */ import_react5.default.createElement(
@@ -3615,7 +3427,7 @@ function Player({ clip, settings, onClose, endOnly = false }) {
3615
3427
  loop: false,
3616
3428
  playsInline: true,
3617
3429
  preload: "auto",
3618
- style: { objectFit: settings.fit || "cover" },
3430
+ style: { objectFit: "cover" },
3619
3431
  onPlaying: () => setBlocked(false),
3620
3432
  onEnded: showEnd,
3621
3433
  onError: showEnd
@@ -3922,7 +3734,7 @@ function SettingsPanel({ standalone = false }) {
3922
3734
  e.target.value = "";
3923
3735
  } }), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-section-title" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "\u64AD\u653E\u504F\u597D"), /* @__PURE__ */ import_react5.default.createElement("span", null, "\u4FEE\u6539\u7ACB\u5373\u4FDD\u5B58")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-preferences" }, /* @__PURE__ */ import_react5.default.createElement(Toggle, { label: "\u542F\u52A8\u65F6\u64AD\u653E", description: "\u6BCF\u6B21\u6253\u5F00 Harness \u64AD\u653E\u4E00\u6B21\uFF0C\u5207\u6362\u4F1A\u8BDD\u4E0D\u91CD\u590D", checked: settings.enabled, disabled: busy, onChange: (enabled) => save({ enabled }) }), /* @__PURE__ */ import_react5.default.createElement(Toggle, { label: "\u64AD\u653E\u58F0\u97F3", description: "\u6D4F\u89C8\u5668\u82E5\u62E6\u622A\u6709\u58F0\u81EA\u52A8\u64AD\u653E\uFF0C\u52A8\u753B\u4ECD\u4F1A\u9759\u97F3\u5F00\u59CB", checked: settings.sound, disabled: busy, onChange: (sound) => save({ sound }) }), settings.sound && /* @__PURE__ */ import_react5.default.createElement("label", { className: "bs-field" }, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u97F3\u91CF"), /* @__PURE__ */ import_react5.default.createElement("input", { "aria-label": "\u97F3\u91CF", type: "range", min: "0", max: "100", value: draftVolume, disabled: busy, onChange: (e) => setDraftVolume(Number(e.target.value)), onPointerUp: () => save({ volume: draftVolume / 100 }), onKeyUp: (e) => {
3924
3736
  if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) save({ volume: draftVolume / 100 });
3925
- } }), /* @__PURE__ */ import_react5.default.createElement("output", null, draftVolume, "%")), /* @__PURE__ */ import_react5.default.createElement("label", { className: "bs-field" }, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u753B\u9762\u663E\u793A", /* @__PURE__ */ import_react5.default.createElement("small", null, "\u94FA\u6EE1\u4F1A\u88C1\u5207\u8FB9\u7F18\uFF0C\u5B8C\u6574\u663E\u793A\u4F1A\u4FDD\u7559\u9ED1\u8FB9")), /* @__PURE__ */ import_react5.default.createElement("select", { "aria-label": "\u753B\u9762\u663E\u793A", disabled: busy, value: settings.fit, onChange: (e) => save({ fit: e.target.value }) }, /* @__PURE__ */ import_react5.default.createElement("option", { value: "cover" }, "\u94FA\u6EE1\u7A97\u53E3"), /* @__PURE__ */ import_react5.default.createElement("option", { value: "contain" }, "\u5B8C\u6574\u663E\u793A"))), /* @__PURE__ */ import_react5.default.createElement(Toggle, { label: "\u968F\u673A\u5F00\u573A", description: "\u6BCF\u6B21\u542F\u52A8\u4ECE\u7247\u5E93\u968F\u673A\u9009\u62E9\uFF1B\u5173\u95ED\u540E\u4F7F\u7528\u5F53\u524D\u52A8\u753B", checked: settings.shuffle, disabled: busy, onChange: (shuffle) => save({ shuffle }) })), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-help" }, /* @__PURE__ */ import_react5.default.createElement("strong", null, "\u50CF\u6E38\u620F\u4E00\u6837\u5F00\u542F\u3002"), /* @__PURE__ */ import_react5.default.createElement("p", null, "\u52A8\u753B\u4E2D\u6309\u4EFB\u610F\u952E\u8DF3\u5230\u6807\u9898\u9875\uFF0C\u518D\u6309\u4EFB\u610F\u952E\u8FDB\u5165\u3002\u4E5F\u652F\u6301\u9F20\u6807\u70B9\u51FB\u4E0E\u89E6\u5C4F\uFF1B\u957F\u6309\u4E0D\u4F1A\u8FDE\u8DF3\u3002\u89C6\u9891\u548C\u7ED3\u675F\u56FE\u7247\u90FD\u4FDD\u5B58\u5728\u672C\u673A\u3002"), /* @__PURE__ */ import_react5.default.createElement("span", null, "\u89C6\u9891\uFF1Adsh-boot-animation \xB7 BSD-3-Clause\u3000\u6280\u672F\u6863\u6848\uFF1Adsh-startup-screen \xB7 MIT")), /* @__PURE__ */ import_react5.default.createElement(UpdatePanel, { request: api, standalone }), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-section-title" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "\u66F4\u65B0\u63D0\u9192\u5916\u89C2"), /* @__PURE__ */ import_react5.default.createElement("button", { className: "bs-text-button", type: "button", onClick: () => setAppearancePreview(true) }, "\u9884\u89C8\u66F4\u65B0\u63D0\u9192")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-preferences" }, /* @__PURE__ */ import_react5.default.createElement("label", { className: "bs-field" }, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u78E8\u7802\u5EA6", /* @__PURE__ */ import_react5.default.createElement("small", null, "\u8C03\u6574\u66F4\u65B0\u5F39\u7A97\u7684\u80CC\u666F\u6A21\u7CCA\u7A0B\u5EA6")), /* @__PURE__ */ import_react5.default.createElement("input", { "aria-label": "\u66F4\u65B0\u5F39\u7A97\u78E8\u7802\u5EA6", type: "range", min: "0", max: "48", step: "1", value: draftBlur, disabled: busy, onChange: (e) => setDraftBlur(Number(e.target.value)), onPointerUp: (e) => save({ updatePromptBlur: Number(e.currentTarget.value) }), onKeyUp: (e) => {
3737
+ } }), /* @__PURE__ */ import_react5.default.createElement("output", null, draftVolume, "%")), /* @__PURE__ */ import_react5.default.createElement(Toggle, { label: "\u968F\u673A\u5F00\u573A", description: "\u6BCF\u6B21\u542F\u52A8\u4ECE\u7247\u5E93\u968F\u673A\u9009\u62E9\uFF1B\u5173\u95ED\u540E\u4F7F\u7528\u5F53\u524D\u52A8\u753B", checked: settings.shuffle, disabled: busy, onChange: (shuffle) => save({ shuffle }) })), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-help" }, /* @__PURE__ */ import_react5.default.createElement("strong", null, "\u50CF\u6E38\u620F\u4E00\u6837\u5F00\u542F\u3002"), /* @__PURE__ */ import_react5.default.createElement("p", null, "\u52A8\u753B\u4E2D\u6309\u4EFB\u610F\u952E\u8DF3\u5230\u6807\u9898\u9875\uFF0C\u518D\u6309\u4EFB\u610F\u952E\u8FDB\u5165\u3002\u4E5F\u652F\u6301\u9F20\u6807\u70B9\u51FB\u4E0E\u89E6\u5C4F\uFF1B\u957F\u6309\u4E0D\u4F1A\u8FDE\u8DF3\u3002\u89C6\u9891\u548C\u7ED3\u675F\u56FE\u7247\u90FD\u4FDD\u5B58\u5728\u672C\u673A\u3002"), /* @__PURE__ */ import_react5.default.createElement("span", null, "\u89C6\u9891\uFF1Adsh-boot-animation \xB7 BSD-3-Clause\u3000\u6280\u672F\u6863\u6848\uFF1Adsh-startup-screen \xB7 MIT")), /* @__PURE__ */ import_react5.default.createElement(UpdatePanel, { request: api, standalone }), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-section-title" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "\u66F4\u65B0\u63D0\u9192\u5916\u89C2"), /* @__PURE__ */ import_react5.default.createElement("button", { className: "bs-text-button", type: "button", onClick: () => setAppearancePreview(true) }, "\u9884\u89C8\u66F4\u65B0\u63D0\u9192")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "bs-preferences" }, /* @__PURE__ */ import_react5.default.createElement("label", { className: "bs-field" }, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u78E8\u7802\u5EA6", /* @__PURE__ */ import_react5.default.createElement("small", null, "\u8C03\u6574\u66F4\u65B0\u5F39\u7A97\u7684\u80CC\u666F\u6A21\u7CCA\u7A0B\u5EA6")), /* @__PURE__ */ import_react5.default.createElement("input", { "aria-label": "\u66F4\u65B0\u5F39\u7A97\u78E8\u7802\u5EA6", type: "range", min: "0", max: "48", step: "1", value: draftBlur, disabled: busy, onChange: (e) => setDraftBlur(Number(e.target.value)), onPointerUp: (e) => save({ updatePromptBlur: Number(e.currentTarget.value) }), onKeyUp: (e) => {
3926
3738
  if (["ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"].includes(e.key)) save({ updatePromptBlur: Number(e.currentTarget.value) });
3927
3739
  } }), /* @__PURE__ */ import_react5.default.createElement("output", null, draftBlur, "px")), /* @__PURE__ */ import_react5.default.createElement("label", { className: "bs-field" }, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5F39\u7A97\u5706\u89D2", /* @__PURE__ */ import_react5.default.createElement("small", null, "\u8C03\u6574\u66F4\u65B0\u5F39\u7A97\u8FB9\u89D2\u7684\u5706\u6DA6\u7A0B\u5EA6")), /* @__PURE__ */ import_react5.default.createElement("input", { "aria-label": "\u66F4\u65B0\u5F39\u7A97\u5706\u89D2", type: "range", min: "12", max: "40", step: "1", value: draftRadius, disabled: busy, onChange: (e) => setDraftRadius(Number(e.target.value)), onPointerUp: (e) => save({ updatePromptRadius: Number(e.currentTarget.value) }), onKeyUp: (e) => {
3928
3740
  if (["ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"].includes(e.key)) save({ updatePromptRadius: Number(e.currentTarget.value) });
package/lib/index.js CHANGED
@@ -12,7 +12,7 @@ export const inject = ['webServer']
12
12
  export const BASE_PATH = '/dsh-boot-studio'
13
13
  export const MAX_UPLOAD_BYTES = 200 * 1024 * 1024
14
14
  export const MAX_IMAGE_UPLOAD_BYTES = 20 * 1024 * 1024
15
- export const DEFAULT_SETTINGS = Object.freeze({ enabled: true, sound: false, volume: 0.7, fit: 'cover', shuffle: false, selectedId: 'brand', endScreen: 'logo', endImageId: null, logoVariant: 'silver', endFit: 'contain', updatePromptBlur: 30, updatePromptRadius: 30, showShortcut: false })
15
+ export const DEFAULT_SETTINGS = Object.freeze({ enabled: true, sound: false, volume: 0.7, shuffle: false, selectedId: 'brand', endScreen: 'logo', endImageId: null, logoVariant: 'silver', endFit: 'contain', updatePromptBlur: 30, updatePromptRadius: 30, showShortcut: false })
16
16
  const BUILTINS = [
17
17
  { id: 'brand', name: '启', file: 'deepseek-brand-intro.mp4' },
18
18
  { id: 'cyberpunk', name: '数字生命', file: 'deepseek-cyberpunk-intro.mp4' },
@@ -45,12 +45,12 @@ function validateSettings(patch, current, available, availableImages = new Set()
45
45
  const next = { ...current }
46
46
  for (const [key, value] of Object.entries(patch)) {
47
47
  if (key === 'duration') throw new HttpError(400, '已取消播放时长设置,动画按原始长度完整播放。')
48
+ if (key === 'fit') throw new HttpError(400, '已取消画面显示设置,开机动画始终铺满当前窗口。')
48
49
  if (!Object.hasOwn(DEFAULT_SETTINGS, key)) throw new HttpError(400, `不支持的设置:${key}`)
49
50
  if (['enabled', 'sound', 'shuffle', 'showShortcut'].includes(key) && typeof value !== 'boolean') throw new HttpError(400, `${key} 必须是开关。`)
50
51
  if (key === 'volume' && !(typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1)) throw new HttpError(400, '音量须在 0 到 1 之间。')
51
52
  if (key === 'updatePromptBlur' && !(Number.isInteger(value) && value >= 0 && value <= 48)) throw new HttpError(400, '弹窗磨砂度须为 0 到 48 之间的整数。')
52
53
  if (key === 'updatePromptRadius' && !(Number.isInteger(value) && value >= 12 && value <= 40)) throw new HttpError(400, '弹窗圆角须为 12 到 40 之间的整数。')
53
- if (key === 'fit' && !['cover', 'contain'].includes(value)) throw new HttpError(400, '画面适配须为 cover 或 contain。')
54
54
  if (key === 'selectedId' && !(typeof value === 'string' && available.has(value))) throw new HttpError(400, '所选动画不存在,请刷新片库。')
55
55
  if (key === 'endScreen' && !['logo', 'image'].includes(value)) throw new HttpError(400, '结束封面须为 logo 或 image。')
56
56
  if (key === 'endImageId' && value !== null && !(typeof value === 'string' && availableImages.has(value))) throw new HttpError(400, '所选封面图片不存在,请重新上传。')
@@ -170,11 +170,11 @@ export function createHandler(options = {}) {
170
170
  const uploads = saved.uploads.filter(c => c && UPLOAD_ID.test(c.id) && Object.hasOwn(TYPES, c.ext) && typeof c.name === 'string').map(c => ({ id: c.id, ext: c.ext, name: c.name.slice(0, 120) }))
171
171
  if (saved.endImages !== undefined && !Array.isArray(saved.endImages)) throw new HttpError(500, '结束封面片库格式不正确。')
172
172
  const endImages = (saved.endImages || []).filter(c => c && IMAGE_ID.test(c.id) && Object.hasOwn(IMAGE_TYPES, c.ext) && typeof c.name === 'string').map(c => ({ id: c.id, ext: c.ext, name: c.name.slice(0, 120) }))
173
- // Earlier releases stored a playback cap. Ignore that retired field while
173
+ // Earlier releases stored a playback cap and video-fit preference. Ignore those retired fields while
174
174
  // preserving every remaining setting and personal-media manifest entry.
175
175
  const legacySettings = saved.settings || {}
176
176
  const settings = legacySettings && typeof legacySettings === 'object' && !Array.isArray(legacySettings)
177
- ? Object.fromEntries(Object.entries(legacySettings).filter(([key]) => key !== 'duration')) : legacySettings
177
+ ? Object.fromEntries(Object.entries(legacySettings).filter(([key]) => key !== 'duration' && key !== 'fit')) : legacySettings
178
178
  try { store = { version: 1, settings: validateSettings(settings, DEFAULT_SETTINGS, new Set([...BUILTINS.map(c => c.id), SEQUENCE.id, ...uploads.map(c => c.id)]), new Set(endImages.map(c => c.id))), uploads, endImages } } catch { throw new HttpError(500, '已保存的设置格式不正确,请备份并检查 settings.json。') }
179
179
  })()
180
180
  await initializing
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-openup-lewis",
3
- "version": "1.2.5",
4
- "description": "DSH LEWIS ENCHANT · 附魔台|开机动画:完整开场、自定义视频与封面、启动检查更新和磨砂提示,保留个人素材与设置。",
3
+ "version": "1.2.6",
4
+ "description": "DSH LEWIS ENCHANT · 附魔台|开机动画:铺满当前窗口的完整开场、自定义视频与封面、启动检查更新和磨砂提示。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "exports": {