dsh-boot-suite 2.16.0 → 2.16.5
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/CHANGELOG.md +36 -0
- package/lib/client.js +220 -41
- package/lib/splash.js +45 -1
- package/package.json +1 -1
- package/templates/whale-girl-scan-4k/template.json +2 -2
- package/templates/whale-girl-scan-4k-h264/assets/whale-girl-scan-4k-h264.mp4 +0 -0
- package/templates/whale-girl-scan-4k-h264/template.json +104 -0
- package/test/smoke-client.mjs +28 -19
- package/test/smoke-package.mjs +27 -0
- package/test/smoke-wallpaper-client.mjs +1 -1
- package/test/smoke-wallpaper-panel.mjs +3 -3
- package/tools/install.ps1 +41 -0
- package/tools/make-upgrade-helper.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## [2.16.5] — 2026-11-04
|
|
2
|
+
|
|
3
|
+
- 下拉菜单加不透明底:皮肤把 `--dsw-alias-bg-layer-2` 定义成半透明,菜单会透出背后的说明文字。现在改用近不透明的 `--dsw-alias-bg-overlay` 双层叠加(≈99%,跟随浅/深主题)+ 轻微背景模糊,选项清晰可读。
|
|
4
|
+
- 悬停底色、选中项颜色补了 fallback,皮肤未定义对应 token 时也能正常高亮。
|
|
5
|
+
- 版本 2.16.5,构建号 cw-71。
|
|
6
|
+
|
|
7
|
+
## [2.16.4] — 2026-11-04
|
|
8
|
+
|
|
9
|
+
- 修复 v2.16.3 的自绘下拉:10 条新样式少了开头的 `.`,实际没有生效——触发按钮的框消失、菜单没有外框、每个选项露出浏览器默认白边。
|
|
10
|
+
- 现在触发按钮恢复原生 select 的边框/底色/内边距,下拉菜单整块是一个带边框和阴影的框(随皮肤 3D 面板倾斜),选项是紧凑的纯文字行(无白框),排版与原生下拉列表一致。
|
|
11
|
+
- 版本 2.16.4,构建号 cw-70。
|
|
12
|
+
|
|
13
|
+
## [2.16.3] — 2026-11-04
|
|
14
|
+
|
|
15
|
+
- 设置页所有下拉(启动动画、聊天背景、配音音色、转场等)改为自绘菜单,不再用系统原生 `<select>`:原生弹层无法被 CSS 3D 变换作用,所以之前不跟封锁橙皮肤的倾斜面板。
|
|
16
|
+
- 自绘菜单渲染在设置面板内部,自动继承面板的 `perspective(1000px) rotateY(-6.5deg) rotateX(1.6deg)`,展开时与面板同一个 3D 平面;默认皮肤和其他皮肤没有这段变换,菜单自然保持水平(无需额外检测)。
|
|
17
|
+
- 下拉空间不足时自动向上展开;支持 Esc 关闭、点击外部关闭、滚动关闭,键盘可 Tab/Enter 操作。
|
|
18
|
+
- 聊天背景的「图片适配 / 背景类型 / 播放模式 / 色彩模式」四个下拉同样改为自绘。
|
|
19
|
+
- 版本 2.16.3,构建号 cw-69。
|
|
20
|
+
|
|
21
|
+
## [2.16.2] — 2026-11-03
|
|
22
|
+
|
|
23
|
+
- **安装器代理加固**:开着代理安装时,pnpm 访问 npm registry 可能长时间卡在 [2/3](用户实测等很久无响应,关代理马上恢复)。现在会先检测并提示(5 秒后自动继续),同时把 pnpm 的 fetch 超时收紧到 30 秒、失败后自动用国内镜像重试一次。
|
|
24
|
+
- **桌面升级助手**增加提示:卡在第二步时关掉代理再双击一次。
|
|
25
|
+
- **修复「鲸鱼娘扫描睁眼(4K H.264)」没有出现在第二幕下拉**:新增的 4K H.264 模板没加进「正式模板」白名单,会被当成测试类模板藏起来;现已补进白名单。
|
|
26
|
+
- 版本号 2.16.2,构建号 cw-68。
|
|
27
|
+
|
|
28
|
+
## [2.16.1] — 2026-11-03
|
|
29
|
+
|
|
30
|
+
- **新增第二幕模板「鲸鱼娘扫描睁眼(4K H.264)」**:把原 4K 视频转码为 H.264 High 8bit(3840×2160 / 24fps / 约 9.6 Mbps,8.4MB),兼容性最好;部分机器没有 AV1 硬解、4K AV1 会走 CPU 软解导致掉帧,可改用本版。
|
|
31
|
+
- **原 4K 模板改名**为「鲸鱼娘扫描睁眼(4K AV1)」,描述里注明 AV1 编码与掉帧时的替代方案。
|
|
32
|
+
- **第二幕视频诊断**:诊断日志新增 `video meta`(分辨率/时长)与 `video stats`(实测帧率、丢帧数/总帧数、是否被暂停),4K 掉帧问题可以直接看日志定位。
|
|
33
|
+
- **省电暂停自动恢复**:Chromium 会把不可见的静音视频暂停省电(video-only background media paused to save power),现在检测到暂停会自动恢复,不再表现为卡住/掉帧。
|
|
34
|
+
- **消除 play() 噪音**:第二幕被跳过/收尾时 `play()` 的未捕获 promise 报错不再写进诊断日志。
|
|
35
|
+
- 版本号 2.16.1,构建号 cw-67。
|
|
36
|
+
|
|
1
37
|
## [2.16.0] — 2026-11-02
|
|
2
38
|
|
|
3
39
|
- **全景大肥鱼新增第二节登录 ID 动画**:第一节(登录 ID 段)延长到约 5.8s、只保留「检测到新的特工登录」;第二节 5s 登录动画,「WELCOME TO DEEPSEEK HARNESS」乱码→逐字、橙色扫描线从上往下、「正在登录:自定义用户名」按贝塞尔曲线平滑居中、用户名破解式逐字出现(最多 20 字符,过滤系统冲突符号)。
|
package/lib/client.js
CHANGED
|
@@ -60,7 +60,17 @@ window.__ModuleLoader__.load({
|
|
|
60
60
|
'.__ss_err{font-size:12px;color:var(--dsw-alias-state-error-primary,#f85149)}',
|
|
61
61
|
'.__ss_swatch{width:38px;height:26px;padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:transparent;cursor:pointer}',
|
|
62
62
|
'.__ss_preview{margin-top:2px;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:12px}',
|
|
63
|
-
'.__ss_previewText{font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.5}'
|
|
63
|
+
'.__ss_previewText{font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.5}',
|
|
64
|
+
'.__ss_selectWrap{position:relative;display:inline-block;min-width:180px;max-width:100%}',
|
|
65
|
+
'.__ss_select{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;cursor:pointer;text-align:left}',
|
|
66
|
+
'.__ss_select.is-open{border-color:var(--dsw-alias-state-business-primary)}',
|
|
67
|
+
'.__ss_selectLabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
68
|
+
'.__ss_selectCaret{flex:none;opacity:.7;font-size:11px}',
|
|
69
|
+
'.__ss_selectMenu{position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:40;display:flex;flex-direction:column;gap:0;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background-color:#1b1d22;background-image:linear-gradient(var(--dsw-alias-bg-overlay, rgba(22,23,27,.95)), var(--dsw-alias-bg-overlay, rgba(22,23,27,.95))),linear-gradient(var(--dsw-alias-bg-overlay, rgba(22,23,27,.95)), var(--dsw-alias-bg-overlay, rgba(22,23,27,.95)));backdrop-filter:blur(8px);box-shadow:0 10px 28px rgba(0,0,0,.45);max-height:280px;overflow:auto;box-sizing:border-box}',
|
|
70
|
+
'.__ss_selectMenu.is-up{top:auto;bottom:calc(100% + 4px)}',
|
|
71
|
+
'.__ss_selectItem{display:block;width:100%;text-align:left;border:none;background:transparent;color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 8px;font:inherit;font-size:13px;cursor:pointer}',
|
|
72
|
+
'.__ss_selectItem:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.12))}',
|
|
73
|
+
'.__ss_selectItem.is-on{color:var(--dsw-alias-state-business-primary, var(--dsw-alias-brand-primary, #e6762b));font-weight:600}'
|
|
64
74
|
].join('')
|
|
65
75
|
var tagId = 'dsh-startup-screen/main.css'
|
|
66
76
|
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="' + tagId + '"]') === null) {
|
|
@@ -93,13 +103,109 @@ window.__ModuleLoader__.load({
|
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
function Select(props) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
var openState = react.useState(false)
|
|
107
|
+
var open = openState[0]
|
|
108
|
+
var setOpen = openState[1]
|
|
109
|
+
var upState = react.useState(false)
|
|
110
|
+
var openUp = upState[0]
|
|
111
|
+
var setOpenUp = upState[1]
|
|
112
|
+
var wrapRef = react.useRef(null)
|
|
113
|
+
var btnRef = react.useRef(null)
|
|
114
|
+
var opts = props.options || []
|
|
115
|
+
var cur = opts.find(function (o) { return String(o[0]) === String(props.value) }) || opts[0] || ['', '']
|
|
116
|
+
var label = cur[1]
|
|
117
|
+
|
|
118
|
+
function measure() {
|
|
119
|
+
var el = btnRef.current
|
|
120
|
+
if (!el) return
|
|
121
|
+
var r = el.getBoundingClientRect()
|
|
122
|
+
var topLimit = 8
|
|
123
|
+
var bottomLimit = (window.innerHeight || 800) - 8
|
|
124
|
+
var node = el.parentNode
|
|
125
|
+
while (node && node.nodeType === 1 && node !== document.body) {
|
|
126
|
+
var st = window.getComputedStyle(node)
|
|
127
|
+
if (/(auto|scroll)/.test(st.overflowY) && node.clientHeight > 0) {
|
|
128
|
+
var nr = node.getBoundingClientRect()
|
|
129
|
+
topLimit = Math.max(topLimit, nr.top + 2)
|
|
130
|
+
bottomLimit = Math.min(bottomLimit, nr.bottom - 2)
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
node = node.parentNode
|
|
134
|
+
}
|
|
135
|
+
var below = bottomLimit - r.bottom
|
|
136
|
+
var above = r.top - topLimit
|
|
137
|
+
var useUp = below < 280 && above > below
|
|
138
|
+
setOpenUp(useUp)
|
|
139
|
+
var maxH = Math.max(120, Math.min(280, (useUp ? above : below) - 8))
|
|
140
|
+
if (wrapRef.current) {
|
|
141
|
+
wrapRef.current.style.setProperty('--ss-menu-max', maxH + 'px')
|
|
142
|
+
var menu = wrapRef.current.querySelector('.__ss_selectMenu')
|
|
143
|
+
if (menu) menu.style.maxHeight = maxH + 'px'
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
react.useEffect(function () {
|
|
148
|
+
if (!open) return
|
|
149
|
+
measure()
|
|
150
|
+
function onDocDown(e) {
|
|
151
|
+
if (!wrapRef.current) return
|
|
152
|
+
if (!wrapRef.current.contains(e.target)) setOpen(false)
|
|
153
|
+
}
|
|
154
|
+
function onKey(e) {
|
|
155
|
+
if (e.key === 'Escape') {
|
|
156
|
+
setOpen(false)
|
|
157
|
+
try { btnRef.current.focus() } catch (err) {}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function onScroll(e) {
|
|
161
|
+
if (!wrapRef.current) return
|
|
162
|
+
if (!wrapRef.current.contains(e.target)) setOpen(false)
|
|
163
|
+
}
|
|
164
|
+
document.addEventListener('mousedown', onDocDown, true)
|
|
165
|
+
document.addEventListener('keydown', onKey, true)
|
|
166
|
+
window.addEventListener('scroll', onScroll, true)
|
|
167
|
+
return function () {
|
|
168
|
+
document.removeEventListener('mousedown', onDocDown, true)
|
|
169
|
+
document.removeEventListener('keydown', onKey, true)
|
|
170
|
+
window.removeEventListener('scroll', onScroll, true)
|
|
171
|
+
}
|
|
172
|
+
}, [open])
|
|
173
|
+
|
|
174
|
+
return h('div', { className: '__ss_selectWrap' + (open ? ' is-open' : ''), ref: wrapRef },
|
|
175
|
+
h('button', {
|
|
176
|
+
className: '__ss_select' + (open ? ' is-open' : ''),
|
|
177
|
+
type: 'button',
|
|
178
|
+
ref: btnRef,
|
|
179
|
+
'aria-haspopup': 'listbox',
|
|
180
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
181
|
+
onClick: function () { setOpen(!open) }
|
|
182
|
+
},
|
|
183
|
+
h('span', { className: '__ss_selectLabel' }, label),
|
|
184
|
+
h('span', { className: '__ss_selectCaret', 'aria-hidden': 'true' }, 'v')
|
|
185
|
+
),
|
|
186
|
+
open ? h('div', {
|
|
187
|
+
className: '__ss_selectMenu' + (openUp ? ' is-up' : ''),
|
|
188
|
+
role: 'listbox',
|
|
189
|
+
style: { maxHeight: 'var(--ss-menu-max, 280px)' }
|
|
190
|
+
}, opts.map(function (o) {
|
|
191
|
+
var on = String(o[0]) === String(props.value)
|
|
192
|
+
return h('button', {
|
|
193
|
+
key: o[0],
|
|
194
|
+
type: 'button',
|
|
195
|
+
role: 'option',
|
|
196
|
+
'aria-selected': on ? 'true' : 'false',
|
|
197
|
+
className: '__ss_selectItem' + (on ? ' is-on' : ''),
|
|
198
|
+
onClick: function () {
|
|
199
|
+
setOpen(false)
|
|
200
|
+
if (on) return
|
|
201
|
+
props.onChange(o[0])
|
|
202
|
+
}
|
|
203
|
+
}, o[1])
|
|
204
|
+
})) : null
|
|
205
|
+
)
|
|
101
206
|
}
|
|
102
207
|
|
|
208
|
+
|
|
103
209
|
function Check(props) {
|
|
104
210
|
return h('label', { className: '__ss_check' },
|
|
105
211
|
h('input', {
|
|
@@ -269,7 +375,7 @@ window.__ModuleLoader__.load({
|
|
|
269
375
|
—— 桌面端走浏览器半侧自举时没有 1,就靠这一条
|
|
270
376
|
3) 下面这个常量:两份都没拿到时的兜底,别让面板显示 undefined
|
|
271
377
|
版本号本身只在 lib/index.js 里读一次,这里不重复维护。 */
|
|
272
|
-
var FALLBACK_VERSION = '2.16.
|
|
378
|
+
var FALLBACK_VERSION = '2.16.5'
|
|
273
379
|
/* 手动选包成功后置 true:让还在后台跑的“浏览器下载监听”不要再弹第二次更新器 */
|
|
274
380
|
var __manualUpdateApplied = false
|
|
275
381
|
function readInjectedVersion() {
|
|
@@ -903,7 +1009,7 @@ window.__ModuleLoader__.load({
|
|
|
903
1009
|
/* v2.14.0:模板分类。正式下拉只放「正式模板」;其余归到「测试类模板」。
|
|
904
1010
|
默认:内置模板里只有 archive-warm(第一幕)与 whale-girl-scan / -4k(第二幕)算正式;
|
|
905
1011
|
用户本地导入的算正式;被手动「归类」或「移出」的以配置为准。 */
|
|
906
|
-
var PROD_TEMPLATES = { template: { 'archive-warm': 1 }, act2: { 'whale-girl-scan': 1, 'whale-girl-scan-4k': 1 } }
|
|
1012
|
+
var PROD_TEMPLATES = { template: { 'archive-warm': 1 }, act2: { 'whale-girl-scan': 1, 'whale-girl-scan-4k': 1, 'whale-girl-scan-4k-h264': 1 } }
|
|
907
1013
|
function findTemplate(id) {
|
|
908
1014
|
for (var fi = 0; fi < (templates || []).length; fi++) if (templates[fi] && templates[fi].id === id) return templates[fi]
|
|
909
1015
|
return null
|
|
@@ -2022,7 +2128,7 @@ window.__ModuleLoader__.load({
|
|
|
2022
2128
|
/* 每次改客户端就把这个号 +1。
|
|
2023
2129
|
有了它在控制台一眼就能判断"浏览器里跑的是不是最新那份" ——
|
|
2024
2130
|
排查问题时,分不清是代码问题还是旧包,会白猜一轮。 */
|
|
2025
|
-
var BUILD = 'cw-
|
|
2131
|
+
var BUILD = 'cw-71'
|
|
2026
2132
|
|
|
2027
2133
|
function log() {
|
|
2028
2134
|
try {
|
|
@@ -2979,6 +3085,107 @@ window.__ModuleLoader__.load({
|
|
|
2979
3085
|
与「启动动画」并列(order 26 → 27)。数据走宿主自带的 HTTP 接口(/chat-wallpaper/*)。 */
|
|
2980
3086
|
var cwReact = require('react')
|
|
2981
3087
|
var cwH = cwReact.createElement
|
|
3088
|
+
/* v2.16.3:自绘下拉(跟随设置面板的 3D 平面;原生 <select> 的系统弹层无法被 CSS 变换) */
|
|
3089
|
+
function CwSelect(props) {
|
|
3090
|
+
var openState = cwReact.useState(false)
|
|
3091
|
+
var open = openState[0]
|
|
3092
|
+
var setOpen = openState[1]
|
|
3093
|
+
var upState = cwReact.useState(false)
|
|
3094
|
+
var openUp = upState[0]
|
|
3095
|
+
var setOpenUp = upState[1]
|
|
3096
|
+
var wrapRef = cwReact.useRef(null)
|
|
3097
|
+
var btnRef = cwReact.useRef(null)
|
|
3098
|
+
var opts = props.options || []
|
|
3099
|
+
var cur = opts.find(function (o) { return String(o[0]) === String(props.value) }) || opts[0] || ['', '']
|
|
3100
|
+
|
|
3101
|
+
function measure() {
|
|
3102
|
+
var el = btnRef.current
|
|
3103
|
+
if (!el) return
|
|
3104
|
+
var r = el.getBoundingClientRect()
|
|
3105
|
+
var topLimit = 8
|
|
3106
|
+
var bottomLimit = (window.innerHeight || 800) - 8
|
|
3107
|
+
var node = el.parentNode
|
|
3108
|
+
while (node && node.nodeType === 1 && node !== document.body) {
|
|
3109
|
+
var st = window.getComputedStyle(node)
|
|
3110
|
+
if (/(auto|scroll)/.test(st.overflowY) && node.clientHeight > 0) {
|
|
3111
|
+
var nr = node.getBoundingClientRect()
|
|
3112
|
+
topLimit = Math.max(topLimit, nr.top + 2)
|
|
3113
|
+
bottomLimit = Math.min(bottomLimit, nr.bottom - 2)
|
|
3114
|
+
break
|
|
3115
|
+
}
|
|
3116
|
+
node = node.parentNode
|
|
3117
|
+
}
|
|
3118
|
+
var below = bottomLimit - r.bottom
|
|
3119
|
+
var above = r.top - topLimit
|
|
3120
|
+
var useUp = below < 280 && above > below
|
|
3121
|
+
setOpenUp(useUp)
|
|
3122
|
+
var maxH = Math.max(120, Math.min(280, (useUp ? above : below) - 8))
|
|
3123
|
+
if (wrapRef.current) {
|
|
3124
|
+
var menu = wrapRef.current.querySelector('.__ss_selectMenu')
|
|
3125
|
+
if (menu) menu.style.maxHeight = maxH + 'px'
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
cwReact.useEffect(function () {
|
|
3130
|
+
if (!open) return
|
|
3131
|
+
measure()
|
|
3132
|
+
function onDocDown(e) {
|
|
3133
|
+
if (!wrapRef.current) return
|
|
3134
|
+
if (!wrapRef.current.contains(e.target)) setOpen(false)
|
|
3135
|
+
}
|
|
3136
|
+
function onKey(e) {
|
|
3137
|
+
if (e.key === 'Escape') {
|
|
3138
|
+
setOpen(false)
|
|
3139
|
+
try { btnRef.current.focus() } catch (err) {}
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function onScroll(e) {
|
|
3143
|
+
if (!wrapRef.current) return
|
|
3144
|
+
if (!wrapRef.current.contains(e.target)) setOpen(false)
|
|
3145
|
+
}
|
|
3146
|
+
document.addEventListener('mousedown', onDocDown, true)
|
|
3147
|
+
document.addEventListener('keydown', onKey, true)
|
|
3148
|
+
window.addEventListener('scroll', onScroll, true)
|
|
3149
|
+
return function () {
|
|
3150
|
+
document.removeEventListener('mousedown', onDocDown, true)
|
|
3151
|
+
document.removeEventListener('keydown', onKey, true)
|
|
3152
|
+
window.removeEventListener('scroll', onScroll, true)
|
|
3153
|
+
}
|
|
3154
|
+
}, [open])
|
|
3155
|
+
|
|
3156
|
+
return cwH('div', { className: '__ss_selectWrap' + (open ? ' is-open' : ''), ref: wrapRef },
|
|
3157
|
+
cwH('button', {
|
|
3158
|
+
className: '__ss_select' + (open ? ' is-open' : ''),
|
|
3159
|
+
type: 'button',
|
|
3160
|
+
ref: btnRef,
|
|
3161
|
+
disabled: !!props.disabled,
|
|
3162
|
+
'aria-haspopup': 'listbox',
|
|
3163
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
3164
|
+
onClick: function () { if (!props.disabled) setOpen(!open) }
|
|
3165
|
+
},
|
|
3166
|
+
cwH('span', { className: '__ss_selectLabel' }, cur[1]),
|
|
3167
|
+
cwH('span', { className: '__ss_selectCaret', 'aria-hidden': 'true' }, 'v')
|
|
3168
|
+
),
|
|
3169
|
+
open ? cwH('div', { className: '__ss_selectMenu' + (openUp ? ' is-up' : ''), role: 'listbox' },
|
|
3170
|
+
opts.map(function (o) {
|
|
3171
|
+
var on = String(o[0]) === String(props.value)
|
|
3172
|
+
return cwH('button', {
|
|
3173
|
+
key: o[0],
|
|
3174
|
+
type: 'button',
|
|
3175
|
+
role: 'option',
|
|
3176
|
+
'aria-selected': on ? 'true' : 'false',
|
|
3177
|
+
className: '__ss_selectItem' + (on ? ' is-on' : ''),
|
|
3178
|
+
onClick: function () {
|
|
3179
|
+
setOpen(false)
|
|
3180
|
+
if (on) return
|
|
3181
|
+
props.onChange(o[0])
|
|
3182
|
+
}
|
|
3183
|
+
}, o[1])
|
|
3184
|
+
})
|
|
3185
|
+
) : null
|
|
3186
|
+
)
|
|
3187
|
+
}
|
|
3188
|
+
|
|
2982
3189
|
|
|
2983
3190
|
/** 把 rgba(...) 里的 alpha 换掉,保留 RGB。 */
|
|
2984
3191
|
function withAlpha(color, a) {
|
|
@@ -3166,14 +3373,7 @@ window.__ModuleLoader__.load({
|
|
|
3166
3373
|
: cwH('div', { className: '__ss_hint' }, '目录里还没有图。点「打开图片文件夹」,把 jpg / png / webp 丢进去,再点刷新。只扫描这一层,子文件夹里的不算(支持 jpg/png/webp/gif/avif/bmp)。'),
|
|
3167
3374
|
cwH('div', { className: '__ss_row', style: { marginTop: 8 } },
|
|
3168
3375
|
cwH('span', { className: '__ss_status' }, '图片适配'),
|
|
3169
|
-
cwH('
|
|
3170
|
-
className: '__ss_select', disabled: busy,
|
|
3171
|
-
value: data.imageFit,
|
|
3172
|
-
onChange: function (ev) { apply({ imageFit: ev.target.value }, true) }
|
|
3173
|
-
},
|
|
3174
|
-
cwH('option', { value: 'cover' }, '铺满窗口(裁切多余部分)'),
|
|
3175
|
-
cwH('option', { value: 'contain' }, '完整显示(不裁切,留底色边)')
|
|
3176
|
-
)
|
|
3376
|
+
cwH(CwSelect, { value: data.imageFit, disabled: busy, options: [['cover', '铺满窗口(裁切多余部分)'], ['contain', '完整显示(不裁切,留底色边)']], onChange: function (v) { apply({ imageFit: v }, true) } })
|
|
3177
3377
|
),
|
|
3178
3378
|
cwH('div', { className: '__ss_row' },
|
|
3179
3379
|
cwH('button', { className: '__ss_btn', type: 'button', disabled: busy, onClick: function () { apply({ image: NO_IMAGE_ID }, true) } }, '不用背景图')
|
|
@@ -3187,34 +3387,13 @@ window.__ModuleLoader__.load({
|
|
|
3187
3387
|
),
|
|
3188
3388
|
cwH('div', { className: '__ss_row', style: { marginTop: 8 } },
|
|
3189
3389
|
cwH('span', { className: '__ss_status' }, '背景类型'),
|
|
3190
|
-
cwH('
|
|
3191
|
-
className: '__ss_select', disabled: busy,
|
|
3192
|
-
value: data.video.bgMode,
|
|
3193
|
-
onChange: function (ev) { apply({ bgMode: ev.target.value }, true) }
|
|
3194
|
-
},
|
|
3195
|
-
cwH('option', { value: 'image' }, '静态底图'),
|
|
3196
|
-
cwH('option', { value: 'video' }, '循环视频')
|
|
3197
|
-
),
|
|
3390
|
+
cwH(CwSelect, { value: data.video.bgMode, disabled: busy, options: [['image', '静态底图'], ['video', '循环视频']], onChange: function (v) { apply({ bgMode: v }, true) } }),
|
|
3198
3391
|
cwH('span', { className: '__ss_status', style: { marginLeft: 16 } }, '播放模式'),
|
|
3199
|
-
cwH('
|
|
3200
|
-
className: '__ss_select', disabled: busy,
|
|
3201
|
-
value: data.video.videoMode,
|
|
3202
|
-
onChange: function (ev) { apply({ videoMode: ev.target.value }, true) }
|
|
3203
|
-
},
|
|
3204
|
-
cwH('option', { value: 'single' }, '手动选择'),
|
|
3205
|
-
cwH('option', { value: 'shuffle' }, '随机轮换')
|
|
3206
|
-
)
|
|
3392
|
+
cwH(CwSelect, { value: data.video.videoMode, disabled: busy, options: [['single', '手动选择'], ['shuffle', '随机轮换']], onChange: function (v) { apply({ videoMode: v }, true) } })
|
|
3207
3393
|
),
|
|
3208
3394
|
cwH('div', { className: '__ss_row', style: { marginTop: 8 } },
|
|
3209
3395
|
cwH('span', { className: '__ss_status' }, '色彩模式'),
|
|
3210
|
-
cwH('
|
|
3211
|
-
className: '__ss_select', disabled: busy,
|
|
3212
|
-
value: data.colorMode,
|
|
3213
|
-
onChange: function (ev) { apply({ colorMode: ev.target.value }, true) }
|
|
3214
|
-
},
|
|
3215
|
-
cwH('option', { value: 'color' }, '彩色(默认)'),
|
|
3216
|
-
cwH('option', { value: 'mono' }, '黑白(原版素材,不染色)')
|
|
3217
|
-
)
|
|
3396
|
+
cwH(CwSelect, { value: data.colorMode, disabled: busy, options: [['color', '彩色(默认)'], ['mono', '黑白(原版素材,不染色)']], onChange: function (v) { apply({ colorMode: v }, true) } })
|
|
3218
3397
|
),
|
|
3219
3398
|
cwH('div', { className: '__ss_hint' }, '黑白模式会关掉主题色纱、保留原画面颜色,适合本身就是黑白的动态壁纸;彩色模式不做任何调色。'),
|
|
3220
3399
|
data.video.videoMode === 'shuffle'
|
package/lib/splash.js
CHANGED
|
@@ -2476,10 +2476,54 @@
|
|
|
2476
2476
|
vnode.addEventListener('play', function () { diagRun('video play ' + vfile + ' vids=' + videoCount()) })
|
|
2477
2477
|
vnode.addEventListener('ended', function () { diagRun('video ended ' + vfile) })
|
|
2478
2478
|
vnode.addEventListener('error', function () { diagRun('video error ' + vfile) })
|
|
2479
|
+
/* v2.16.1:第二幕视频诊断(分辨率/时长/实测帧率/丢帧)+ 省电暂停自动恢复 + play() 失败静默 */
|
|
2480
|
+
vnode.addEventListener('loadedmetadata', function () {
|
|
2481
|
+
diagRun('video meta ' + vfile + ' ' + vnode.videoWidth + 'x' + vnode.videoHeight + ' dur=' + (isFinite(vnode.duration) ? vnode.duration.toFixed(2) : '?'))
|
|
2482
|
+
})
|
|
2483
|
+
var vResumeTimer = null
|
|
2484
|
+
vnode.addEventListener('pause', function () {
|
|
2485
|
+
/* Chromium 会把不可见的静音视频暂停省电;只要还在第二幕里就拉回来,避免“卡住/掉帧” */
|
|
2486
|
+
if (vnode.ended || !vnode.isConnected || vResumeTimer) return
|
|
2487
|
+
vResumeTimer = setTimeout(function () {
|
|
2488
|
+
vResumeTimer = null
|
|
2489
|
+
if (!vnode.isConnected || vnode.ended || !vnode.paused) return
|
|
2490
|
+
try { var vpR = vnode.play(); if (vpR && vpR.catch) vpR.catch(function () {}) } catch (eR) { /* noop */ }
|
|
2491
|
+
}, 260)
|
|
2492
|
+
})
|
|
2479
2493
|
placeLayer(vnode, tlNumber(L.pos && L.pos[0], 640), tlNumber(L.pos && L.pos[1], 360), vw, vh)
|
|
2480
2494
|
stage.appendChild(vnode)
|
|
2481
2495
|
diagRun('video append ' + vfile + ' vids=' + videoCount())
|
|
2482
|
-
try {
|
|
2496
|
+
try {
|
|
2497
|
+
var vp0 = vnode.play()
|
|
2498
|
+
if (vp0 && vp0.catch) vp0.catch(function () {})
|
|
2499
|
+
} catch (ePlay) { /* autoplay blocked */ }
|
|
2500
|
+
if (vnode.requestVideoFrameCallback) {
|
|
2501
|
+
var vFrames = 0, vT0 = 0
|
|
2502
|
+
var onVFrame = function (now) {
|
|
2503
|
+
if (!vnode.isConnected) return
|
|
2504
|
+
if (!vT0) { vT0 = now; vFrames = 0 }
|
|
2505
|
+
vFrames += 1
|
|
2506
|
+
if (now - vT0 < 1200) {
|
|
2507
|
+
try { vnode.requestVideoFrameCallback(onVFrame) } catch (eF) { /* noop */ }
|
|
2508
|
+
return
|
|
2509
|
+
}
|
|
2510
|
+
var vq = null
|
|
2511
|
+
try { vq = vnode.getVideoPlaybackQuality() } catch (eQ) { /* noop */ }
|
|
2512
|
+
var vFps = vT0 ? Math.round(vFrames * 1000 / (now - vT0)) : 0
|
|
2513
|
+
diagRun('video stats ' + vfile + ' ' + vnode.videoWidth + 'x' + vnode.videoHeight + ' fps=' + vFps +
|
|
2514
|
+
' dropped=' + (vq ? vq.droppedVideoFrames : '?') + '/' + (vq ? vq.totalVideoFrames : '?') +
|
|
2515
|
+
' paused=' + vnode.paused)
|
|
2516
|
+
}
|
|
2517
|
+
try { vnode.requestVideoFrameCallback(onVFrame) } catch (eF0) { /* noop */ }
|
|
2518
|
+
} else {
|
|
2519
|
+
setTimeout(function () {
|
|
2520
|
+
if (!vnode.isConnected) return
|
|
2521
|
+
var vq2 = null
|
|
2522
|
+
try { if (vnode.getVideoPlaybackQuality) vq2 = vnode.getVideoPlaybackQuality() } catch (eQ2) { /* noop */ }
|
|
2523
|
+
diagRun('video stats ' + vfile + ' ' + vnode.videoWidth + 'x' + vnode.videoHeight +
|
|
2524
|
+
' dropped=' + (vq2 ? vq2.droppedVideoFrames : '?') + '/' + (vq2 ? vq2.totalVideoFrames : '?'))
|
|
2525
|
+
}, 1500)
|
|
2526
|
+
}
|
|
2483
2527
|
nodes.push({ def: L, node: vnode, video: vnode })
|
|
2484
2528
|
continue
|
|
2485
2529
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"id": "whale-girl-scan-4k",
|
|
4
|
-
"name": "鲸鱼娘扫描睁眼(4K)",
|
|
4
|
+
"name": "鲸鱼娘扫描睁眼(4K AV1)",
|
|
5
5
|
"author": "素材:NativeDog1/dsh-boot-animation(BSD-3-Clause);8K 超分:1catmint1/dsh-boot-animation-8k;模板维护:LeeRolling",
|
|
6
6
|
"homepage": "https://github.com/LeeRolling/dsh-boot-suite",
|
|
7
7
|
"license": "BSD-3-Clause",
|
|
8
8
|
"version": "1.0.0",
|
|
9
|
-
"description": "鲸鱼娘「DeepSeek娘扫描睁眼」4K 版(3840×2160,7
|
|
9
|
+
"description": "鲸鱼娘「DeepSeek娘扫描睁眼」4K 版(3840×2160,7 秒,AV1 编码),全屏播放 + 淡入淡出。AV1 在部分机器上没有硬解、会走 CPU 软解导致掉帧;遇到掉帧请改用「鲸鱼娘扫描睁眼(4K H.264)」或 2K 版。由社区作者 1catmint1 的 8K 超分版下采样而来(8K 源:https://github.com/1catmint1/dsh-boot-animation-8k ,BSD-3-Clause);原片:NativeDog1/dsh-boot-animation(BSD-3-Clause)。桌面版建议用本 4K 版或 2K 版,8K 版在桌面端有内存崩溃风险,仅建议网页端/收藏使用。",
|
|
10
10
|
"tags": [
|
|
11
11
|
"anime",
|
|
12
12
|
"whale",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "whale-girl-scan-4k-h264",
|
|
4
|
+
"name": "鲸鱼娘扫描睁眼(4K H.264)",
|
|
5
|
+
"author": "素材:NativeDog1/dsh-boot-animation(BSD-3-Clause);8K 超分:1catmint1/dsh-boot-animation-8k;模板维护:LeeRolling",
|
|
6
|
+
"homepage": "https://github.com/LeeRolling/dsh-boot-suite",
|
|
7
|
+
"license": "BSD-3-Clause",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"description": "鲸鱼娘「DeepSeek娘扫描睁眼」4K H.264 版(3840×2160,24fps,7 秒,H.264 High 8bit 约 9.6Mbps),全屏播放 + 淡入淡出。由社区作者 1catmint1 的 8K 超分版下采样后转码为 H.264,兼容性最好:AV1 无硬解的机器(如部分 4K 掉帧环境)可改用本版。8K 源:https://github.com/1catmint1/dsh-boot-animation-8k (BSD-3-Clause);原片:NativeDog1/dsh-boot-animation(BSD-3-Clause)。",
|
|
10
|
+
"tags": [
|
|
11
|
+
"anime",
|
|
12
|
+
"whale",
|
|
13
|
+
"video",
|
|
14
|
+
"cinematic"
|
|
15
|
+
],
|
|
16
|
+
"duration": 7200,
|
|
17
|
+
"theme": {
|
|
18
|
+
"accent": "#4d6bfe",
|
|
19
|
+
"glitchA": "#ff3b30",
|
|
20
|
+
"glitchB": "#2971b8"
|
|
21
|
+
},
|
|
22
|
+
"segments": [
|
|
23
|
+
{
|
|
24
|
+
"name": "扫描 · 睁眼",
|
|
25
|
+
"from": 0,
|
|
26
|
+
"to": 7200
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"layers": [
|
|
30
|
+
{
|
|
31
|
+
"id": "bg",
|
|
32
|
+
"type": "panel",
|
|
33
|
+
"pos": [
|
|
34
|
+
640,
|
|
35
|
+
360
|
|
36
|
+
],
|
|
37
|
+
"panelW": 1280,
|
|
38
|
+
"panelH": 720,
|
|
39
|
+
"fill": "#000000",
|
|
40
|
+
"radius": 0,
|
|
41
|
+
"keyframes": [
|
|
42
|
+
{
|
|
43
|
+
"t": 0,
|
|
44
|
+
"opacity": 1
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "whale",
|
|
50
|
+
"type": "video",
|
|
51
|
+
"pos": [
|
|
52
|
+
640,
|
|
53
|
+
360
|
|
54
|
+
],
|
|
55
|
+
"videoW": 1280,
|
|
56
|
+
"videoH": 720,
|
|
57
|
+
"src": "whale-girl-scan-4k-h264.mp4",
|
|
58
|
+
"keyframes": [
|
|
59
|
+
{
|
|
60
|
+
"t": 0,
|
|
61
|
+
"opacity": 1
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"t": 7200,
|
|
65
|
+
"opacity": 1
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "credit",
|
|
71
|
+
"type": "text",
|
|
72
|
+
"text": "WHALE GIRL · NATIVEDOG1/DSH-BOOT-ANIMATION · 8K SOURCE: 1CATMINT1 · BSD-3-CLAUSE",
|
|
73
|
+
"pos": [
|
|
74
|
+
640,
|
|
75
|
+
360
|
|
76
|
+
],
|
|
77
|
+
"align": "center",
|
|
78
|
+
"font": {
|
|
79
|
+
"size": 9,
|
|
80
|
+
"weight": 400,
|
|
81
|
+
"tracking": 0.18
|
|
82
|
+
},
|
|
83
|
+
"color": "rgba(139,232,255,.55)",
|
|
84
|
+
"keyframes": [
|
|
85
|
+
{
|
|
86
|
+
"t": 0,
|
|
87
|
+
"opacity": 0,
|
|
88
|
+
"offsetY": 318
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"t": 600,
|
|
92
|
+
"opacity": 1,
|
|
93
|
+
"offsetY": 318
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"t": 7200,
|
|
97
|
+
"opacity": 1,
|
|
98
|
+
"offsetY": 318
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"effects": []
|
|
104
|
+
}
|
package/test/smoke-client.mjs
CHANGED
|
@@ -34,6 +34,13 @@ const React = {
|
|
|
34
34
|
const i = hookIndex++
|
|
35
35
|
if (!ranEffects.has(i)) { ranEffects.add(i); fn() }
|
|
36
36
|
},
|
|
37
|
+
useRef(init) {
|
|
38
|
+
const i = hookIndex++
|
|
39
|
+
if (hooks[i] === undefined) hooks[i] = { current: init === undefined ? null : init }
|
|
40
|
+
return hooks[i]
|
|
41
|
+
},
|
|
42
|
+
useMemo(fn) { return fn() },
|
|
43
|
+
useCallback(fn) { return fn },
|
|
37
44
|
useStateReset() {},
|
|
38
45
|
}
|
|
39
46
|
function resetHooks() { hookIndex = 0 }
|
|
@@ -180,6 +187,8 @@ function walk(node, out = []) {
|
|
|
180
187
|
if (typeof node !== 'object') return out /* 文本节点本身不收集 */
|
|
181
188
|
if (Array.isArray(node)) { node.forEach((n) => walk(n, out)); return out }
|
|
182
189
|
if (node.type) {
|
|
190
|
+
/* 自绘下拉 Select 有 hooks,不能直接调用;把它当成一个「下拉节点」收集 */
|
|
191
|
+
if (typeof node.type === 'function' && node.type.name === 'Select') { out.push(node); return out }
|
|
183
192
|
/* 函数组件要展开(Field / Check / TextInput… 都不用 hooks,直接调用即可) */
|
|
184
193
|
if (typeof node.type === 'function') return walk(node.type(node.props || {}), out)
|
|
185
194
|
out.push(node)
|
|
@@ -255,8 +264,8 @@ for (const label of ['第一幕入场', '第一幕 → 第二幕', '第二幕
|
|
|
255
264
|
|
|
256
265
|
/* 三段转场的下拉:8 种类型 × 3 个;方向下拉:4 个 */
|
|
257
266
|
const selectOf = (values) => walk(tree).filter((n) =>
|
|
258
|
-
n.type === '
|
|
259
|
-
values.every((v) => n.props.
|
|
267
|
+
typeof n.type === 'function' && Array.isArray(n.props.options) &&
|
|
268
|
+
values.every((v) => n.props.options.some((o) => o && o[0] === v)))
|
|
260
269
|
const TRANSITION_TYPES = ['dissolve', 'flash', 'glitch', 'scanline', 'slide', 'iris', 'black', 'none']
|
|
261
270
|
const TRANSITION_DIRS = ['left', 'right', 'up', 'down']
|
|
262
271
|
const typeSelects = selectOf(TRANSITION_TYPES)
|
|
@@ -283,10 +292,10 @@ check('文件选择器 accept 白名单含 mp4/webm/gif/png/jpg/jpeg/webp',
|
|
|
283
292
|
fileInput && fileInput.accept)
|
|
284
293
|
|
|
285
294
|
/* 选择转场 → 保存 patch 里必须带上(沿用现有的 vm 渲染 + 保存路径) */
|
|
286
|
-
typeSelects[0].props.onChange(
|
|
287
|
-
typeSelects[1].props.onChange(
|
|
288
|
-
typeSelects[2].props.onChange(
|
|
289
|
-
dirSelects[0].props.onChange(
|
|
295
|
+
typeSelects[0].props.onChange('glitch')
|
|
296
|
+
typeSelects[1].props.onChange('flash')
|
|
297
|
+
typeSelects[2].props.onChange('scanline')
|
|
298
|
+
dirSelects[0].props.onChange('up')
|
|
290
299
|
msRange.props.onChange({ target: { value: '900' } })
|
|
291
300
|
tree = renderPanel()
|
|
292
301
|
const typeSelects2 = selectOf(TRANSITION_TYPES)
|
|
@@ -322,17 +331,17 @@ console.log('\n[5c] 删除该动画:本地删文件 / 内置隐藏可恢复(
|
|
|
322
331
|
const groupTitles = () => walk(tree).filter((n) => n.type === 'div' && n.props && n.props.className === '__ss_groupTitle').map((n) => n.props.children)
|
|
323
332
|
const postList = (prefix) => requests.filter((r) => r.init && r.init.method === 'POST' && String(r.url).includes(prefix))
|
|
324
333
|
const restoreBtn = () => find(tree, (n) => n.type === 'button' && /^恢复已隐藏的内置模板(\d+)$/.test(String(n.props.children || '')))
|
|
325
|
-
const hasOption = (sel, id) => !!sel && Array.isArray(sel.props.
|
|
326
|
-
sel.props.
|
|
334
|
+
const hasOption = (sel, id) => !!sel && Array.isArray(sel.props.options) &&
|
|
335
|
+
sel.props.options.some((o) => o && o[0] === id)
|
|
327
336
|
/* 第一幕/第二幕两个模板下拉:都含内置 archive-warm,且都有一项「内置/不播放」说明 */
|
|
328
337
|
const templateSelects = () => walk(tree).filter((n) =>
|
|
329
|
-
n.type === '
|
|
330
|
-
n.props.
|
|
331
|
-
n.props.
|
|
338
|
+
typeof n.type === 'function' && Array.isArray(n.props.options) &&
|
|
339
|
+
n.props.options.some((o) => o && (o[0] === 'archive-warm' || o[0] === 'whale-girl-scan')) &&
|
|
340
|
+
n.props.options.some((o) => o && /内置|不播放/.test(String(o[1]))))
|
|
332
341
|
/* archive-warm 被隐藏后上面的定位会失效:改用「有内置/不播放说明」来找两个下拉 */
|
|
333
342
|
const templateSelectsAny = () => walk(tree).filter((n) =>
|
|
334
|
-
n.type === '
|
|
335
|
-
n.props.
|
|
343
|
+
typeof n.type === 'function' && Array.isArray(n.props.options) &&
|
|
344
|
+
n.props.options.some((o) => o && /内置|不播放/.test(String(o[1]))))
|
|
336
345
|
|
|
337
346
|
/* ── 组顺序:自定义动画 → 模板 → 转场(保留) ── */
|
|
338
347
|
const titles = groupTitles()
|
|
@@ -380,7 +389,7 @@ console.log('\n[5c] 删除该动画:本地删文件 / 内置隐藏可恢复(
|
|
|
380
389
|
/* ── 选内置模板 → 按钮也启用(v2.12.4 不再只对本地生效) ── */
|
|
381
390
|
let tplSel = templateSelects()
|
|
382
391
|
check('找到第一幕 / 第二幕两个模板下拉', tplSel.length === 2, '实际 ' + tplSel.length)
|
|
383
|
-
tplSel[0].props.onChange(
|
|
392
|
+
tplSel[0].props.onChange('archive-warm')
|
|
384
393
|
tree = renderPanel()
|
|
385
394
|
rows = tplRows()
|
|
386
395
|
check('★ 第一幕选内置 archive-warm → 第一幕按钮 enabled(title=隐藏内置模板(可恢复)),第二幕未选仍置灰',
|
|
@@ -389,7 +398,7 @@ console.log('\n[5c] 删除该动画:本地删文件 / 内置隐藏可恢复(
|
|
|
389
398
|
rowValue(rows[1]) === '' && rowBtn(rows[1]).props.disabled === true,
|
|
390
399
|
JSON.stringify(rows.map((r) => ({ v: rowValue(r), disabled: rowBtn(r).props.disabled, title: rowBtn(r).props.title }))))
|
|
391
400
|
tplSel = templateSelects()
|
|
392
|
-
tplSel[1].props.onChange(
|
|
401
|
+
tplSel[1].props.onChange('whale-girl-scan')
|
|
393
402
|
tree = renderPanel()
|
|
394
403
|
rows = tplRows()
|
|
395
404
|
check('★ 第二幕也选内置 whale-girl-scan → 两个按钮都 enabled',
|
|
@@ -450,7 +459,7 @@ console.log('\n[5c] 删除该动画:本地删文件 / 内置隐藏可恢复(
|
|
|
450
459
|
|
|
451
460
|
/* ── 本地模板删除路径保持不变(删文件、不可恢复、不进 hidden) ── */
|
|
452
461
|
tplSel = templateSelects()
|
|
453
|
-
tplSel[0].props.onChange(
|
|
462
|
+
tplSel[0].props.onChange('custom-local-a')
|
|
454
463
|
tree = renderPanel()
|
|
455
464
|
rows = tplRows()
|
|
456
465
|
check('第一幕选本地模板 → 按钮 enabled(title=删除这个本地动画)',
|
|
@@ -502,10 +511,10 @@ check('立即预览先重新拉配置', requests.length > beforePreview && reque
|
|
|
502
511
|
check('立即预览调用 __DSH_STARTUP_API__.replay()', windowStub.__replayed === true)
|
|
503
512
|
/* v2.13.15 回归:选完「配音音色」直接点预览(还没点保存)也必须按当前选择播 */
|
|
504
513
|
tree = renderPanel()
|
|
505
|
-
const voiceSel = walk(tree).filter((n) => n.type === '
|
|
506
|
-
check('配音音色下拉能从 /voices 拿到音色G', !!voiceSel, JSON.stringify(walk(tree).filter((n) => n.type === '
|
|
514
|
+
const voiceSel = walk(tree).filter((n) => typeof n.type === 'function' && Array.isArray(n.props.options) && n.props.options.some((o) => o && o[0] === 'g'))[0]
|
|
515
|
+
check('配音音色下拉能从 /voices 拿到音色G', !!voiceSel, JSON.stringify(walk(tree).filter((n) => typeof n.type === 'function' && Array.isArray(n.props.options)).map((n) => n.props.options.map((o) => o && o[0]))))
|
|
507
516
|
if (voiceSel) {
|
|
508
|
-
voiceSel.props.onChange(
|
|
517
|
+
voiceSel.props.onChange('g')
|
|
509
518
|
await flush()
|
|
510
519
|
tree = renderPanel()
|
|
511
520
|
const previewBtn2 = find(tree, (n) => n.type === 'button' && n.props.children === '立即预览')
|
package/test/smoke-package.mjs
CHANGED
|
@@ -149,4 +149,31 @@ if (!bigfishApp.includes("track.style.transform='translateX('")) throw new Error
|
|
|
149
149
|
if (!bigfishApp.includes("renderVoiceRow($('login-voice')")) throw new Error('app.js 登录段未使用配音行 renderVoiceRow')
|
|
150
150
|
if (!skipCss.includes('width:min(500px,92vw)')) throw new Error('style.css 登录框未缩到 1/3 尺寸')
|
|
151
151
|
|
|
152
|
+
/* v2.16.1:4K AV1 改名 + 新增 4K H.264 模板 + 第二幕视频诊断/暂停恢复 */
|
|
153
|
+
const tpl4k = JSON.parse(fs.readFileSync(path.join(root, 'templates/whale-girl-scan-4k/template.json'), 'utf8'))
|
|
154
|
+
if (tpl4k.name !== '鲸鱼娘扫描睁眼(4K AV1)') throw new Error('4K AV1 模板名称未更新')
|
|
155
|
+
const tpl4kh = JSON.parse(fs.readFileSync(path.join(root, 'templates/whale-girl-scan-4k-h264/template.json'), 'utf8'))
|
|
156
|
+
if (tpl4kh.name !== '鲸鱼娘扫描睁眼(4K H.264)') throw new Error('缺少 4K H.264 模板名称')
|
|
157
|
+
if (!fs.existsSync(path.join(root, 'templates/whale-girl-scan-4k-h264/assets/whale-girl-scan-4k-h264.mp4'))) throw new Error('缺少 4K H.264 视频文件')
|
|
158
|
+
const splashSrc = fs.readFileSync(path.join(root, 'lib/splash.js'), 'utf8')
|
|
159
|
+
if (!splashSrc.includes('video stats ')) throw new Error('splash.js 缺少第二幕视频丢帧统计')
|
|
160
|
+
if (!splashSrc.includes('video meta ')) throw new Error('splash.js 缺少第二幕视频分辨率日志')
|
|
161
|
+
if (!splashSrc.includes('vResumeTimer')) throw new Error('splash.js 缺少省电暂停自动恢复')
|
|
162
|
+
if (!splashSrc.includes('vp0.catch')) throw new Error('splash.js 未静默 play() 失败')
|
|
163
|
+
/* v2.16.2:新增 4K H.264 必须算「正式模板」,否则第二幕下拉里看不到 */
|
|
164
|
+
const clientSrc = fs.readFileSync(path.join(root, 'lib/client.js'), 'utf8')
|
|
165
|
+
if (!clientSrc.includes("'whale-girl-scan-4k-h264': 1")) throw new Error('client.js 的正式模板白名单缺少 4K H.264')
|
|
166
|
+
/* v2.16.2:安装器代理加固 */
|
|
167
|
+
const installPs1V2162 = fs.readFileSync(path.join(root, 'tools/install.ps1'), 'utf8')
|
|
168
|
+
if (!installPs1V2162.includes('Get-ProxyInfo')) throw new Error('install.ps1 缺少代理探测')
|
|
169
|
+
if (!installPs1V2162.includes('npm_config_fetch_timeout')) throw new Error('install.ps1 缺少 pnpm 网络超时设置')
|
|
170
|
+
if (!installPs1V2162.includes('registry.npmmirror.com')) throw new Error('install.ps1 缺少国内镜像重试')
|
|
171
|
+
const helperGen = fs.readFileSync(path.join(root, 'tools/make-upgrade-helper.mjs'), 'utf8')
|
|
172
|
+
if (!helperGen.includes('turn off any proxy')) throw new Error('升级助手缺少代理提示')
|
|
173
|
+
/* v2.16.3:设置页下拉改自绘,跟随皮肤 3D 面板平面 */
|
|
174
|
+
if (!clientSrc.includes('__ss_selectMenu')) throw new Error('client.js 缺少自绘下拉菜单')
|
|
175
|
+
if (!clientSrc.includes('aria-haspopup')) throw new Error('client.js 自绘下拉缺少 aria 属性')
|
|
176
|
+
if (!clientSrc.includes('function CwSelect(')) throw new Error('client.js 聊天背景缺少 CwSelect')
|
|
177
|
+
if (clientSrc.includes("h('select',")) throw new Error('client.js 仍残留原生 select(设置页)')
|
|
178
|
+
if (clientSrc.includes("cwH('select',")) throw new Error('client.js 仍残留原生 select(聊天背景)')
|
|
152
179
|
console.log('ok package.json version=' + pkg.version + ',无 BOM,关键文件齐全(含更新前回滚 checkpoint)');
|
|
@@ -463,7 +463,7 @@ ok(intervals.every((t) => t.cleared) || intervals.length === 0, '旧的 interval
|
|
|
463
463
|
intervals.map((t) => (t.cleared ? 'cleared' : 'live')).join(','))
|
|
464
464
|
|
|
465
465
|
console.log('\n=== ⑦ v2.13.0 测试钩子 / BUILD ===')
|
|
466
|
-
ok(mod.__BUILD === 'cw-
|
|
466
|
+
ok(mod.__BUILD === 'cw-71', '__BUILD = cw-68(排查浏览器跑的是不是新包)', String(mod.__BUILD))
|
|
467
467
|
ok(mod.__VIDEO_ID === 'dsh-cw-video', '__VIDEO_ID = dsh-cw-video', String(mod.__VIDEO_ID))
|
|
468
468
|
ok(typeof mod.__startShuffle === 'function', '__startShuffle 导出')
|
|
469
469
|
ok(typeof mod.__stopShuffle === 'function', '__stopShuffle 导出')
|
|
@@ -365,10 +365,10 @@ ok(MODEL.lastRootsBody && MODEL.lastRootsBody.action === 'remove' && String(MODE
|
|
|
365
365
|
const beforeMode = requests.length
|
|
366
366
|
tree = renderPanel()
|
|
367
367
|
const modeSel = findNode(tree, (n) =>
|
|
368
|
-
n.type === '
|
|
369
|
-
n.props.
|
|
368
|
+
typeof n.type === 'function' && Array.isArray(n.props.options) &&
|
|
369
|
+
n.props.options.some((o) => o && o[0] === 'video'))
|
|
370
370
|
ok(!!modeSel, '「背景类型」下拉含「循环视频」选项')
|
|
371
|
-
modeSel.props.onChange(
|
|
371
|
+
modeSel.props.onChange('video')
|
|
372
372
|
await flushP(); await flushP(); await flushP(); await flushP()
|
|
373
373
|
const modeReq = requests.slice(beforeMode).find((r) => r.url.includes('/chat-wallpaper/set'))
|
|
374
374
|
const modeBody = modeReq && JSON.parse(modeReq.init.body)
|
package/tools/install.ps1
CHANGED
|
@@ -22,6 +22,20 @@ $ErrorActionPreference = 'Stop'
|
|
|
22
22
|
function Say($m) { Write-Host $m }
|
|
23
23
|
function Die($m) { Write-Host ('[x] ' + $m) -ForegroundColor Red; exit 1 }
|
|
24
24
|
|
|
25
|
+
# v2.16.2:代理探测。开着代理时 pnpm 访问 npm registry 可能长时间卡住(用户实测第二步无响应)。
|
|
26
|
+
function Get-ProxyInfo {
|
|
27
|
+
$marks = @()
|
|
28
|
+
foreach ($n in @('HTTP_PROXY','HTTPS_PROXY','ALL_PROXY','http_proxy','https_proxy','all_proxy')) {
|
|
29
|
+
$v = [Environment]::GetEnvironmentVariable($n)
|
|
30
|
+
if ($v) { $marks += ($n + '=' + $v) }
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
$reg = Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction Stop
|
|
34
|
+
if ($reg.ProxyEnable -eq 1 -and $reg.ProxyServer) { $marks += ('系统代理=' + $reg.ProxyServer) }
|
|
35
|
+
} catch {}
|
|
36
|
+
return ($marks -join ';')
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
|
26
40
|
if (-not (Test-Path (Join-Path $RepoRoot 'package.json'))) { Die ('这不像插件目录:' + $RepoRoot) }
|
|
27
41
|
if (-not (Test-Path (Join-Path $RepoRoot 'lib\index.js'))) { Die '源码不完整(缺 lib\index.js),你下载的可能是残缺的压缩包' }
|
|
@@ -319,6 +333,22 @@ foreach ($profile in $targets) {
|
|
|
319
333
|
}
|
|
320
334
|
|
|
321
335
|
Say ' [2/3] 安装本插件(link 方式,改代码即生效)…'
|
|
336
|
+
$proxyInfo = Get-ProxyInfo
|
|
337
|
+
if ($proxyInfo -and -not $ManualMode) {
|
|
338
|
+
Say ''
|
|
339
|
+
Say ' [提示] 检测到代理:' + $proxyInfo
|
|
340
|
+
Say ' 已知问题:开着代理时,安装这一步(pnpm 访问 npm registry)可能长时间卡住。'
|
|
341
|
+
Say ' 如果卡住超过 2 分钟:按 Ctrl+C 关窗口 → 暂时关闭代理 → 重新双击本助手。'
|
|
342
|
+
Say ' 也可以把 registry.npmjs.org 设为直连/绕过代理。'
|
|
343
|
+
Say ''
|
|
344
|
+
for ($pi = 5; $pi -ge 1; $pi--) { Write-Host (' 5 秒后继续…(' + $pi + ')'); Start-Sleep -Seconds 1 }
|
|
345
|
+
}
|
|
346
|
+
$prevFetchTimeout = [Environment]::GetEnvironmentVariable('npm_config_fetch_timeout')
|
|
347
|
+
$prevFetchRetries = [Environment]::GetEnvironmentVariable('npm_config_fetch_retries')
|
|
348
|
+
if (-not $ManualMode) {
|
|
349
|
+
$env:npm_config_fetch_timeout = '30000'
|
|
350
|
+
$env:npm_config_fetch_retries = '2'
|
|
351
|
+
}
|
|
322
352
|
if ($ManualMode) {
|
|
323
353
|
Install-Manual $profileDir
|
|
324
354
|
} else {
|
|
@@ -328,6 +358,15 @@ foreach ($profile in $targets) {
|
|
|
328
358
|
try { $outAdd = (& $dsh plugin --profile $profile add $spec 2>&1 | Out-String) } catch { $outAdd = $_.Exception.Message }
|
|
329
359
|
if ($outAdd.Trim()) { $outAdd.Trim().Split([char]10) | ForEach-Object { ' ' + $_ } }
|
|
330
360
|
$addExit = $LASTEXITCODE
|
|
361
|
+
if ($addExit -ne 0) {
|
|
362
|
+
Say ' 第一次安装没成功,改用 npm 国内镜像重试一次…'
|
|
363
|
+
$prevRegistry = [Environment]::GetEnvironmentVariable('npm_config_registry')
|
|
364
|
+
$env:npm_config_registry = 'https://registry.npmmirror.com'
|
|
365
|
+
try { $outAdd = (& $dsh plugin --profile $profile add $spec 2>&1 | Out-String) } catch { $outAdd = $_.Exception.Message }
|
|
366
|
+
$addExit = $LASTEXITCODE
|
|
367
|
+
if ($outAdd.Trim()) { $outAdd.Trim().Split([char]10) | ForEach-Object { ' ' + $_ } }
|
|
368
|
+
if ($prevRegistry) { $env:npm_config_registry = $prevRegistry } else { Remove-Item Env:npm_config_registry -ErrorAction SilentlyContinue }
|
|
369
|
+
}
|
|
331
370
|
$ErrorActionPreference = $eapAdd
|
|
332
371
|
$installedOk = Test-InstalledOk $profileDir
|
|
333
372
|
if ($addExit -ne 0 -or $outAdd -match 'managed exclusively' -or -not $installedOk) {
|
|
@@ -342,6 +381,8 @@ foreach ($profile in $targets) {
|
|
|
342
381
|
if (Test-InstalledOk $profileDir) { $addExit = 0 } else { $addExit = 1 }
|
|
343
382
|
}
|
|
344
383
|
}
|
|
384
|
+
if ($prevFetchTimeout) { $env:npm_config_fetch_timeout = $prevFetchTimeout } else { Remove-Item Env:npm_config_fetch_timeout -ErrorAction SilentlyContinue }
|
|
385
|
+
if ($prevFetchRetries) { $env:npm_config_fetch_retries = $prevFetchRetries } else { Remove-Item Env:npm_config_fetch_retries -ErrorAction SilentlyContinue }
|
|
345
386
|
if (-not (Test-InstalledOk $profileDir)) {
|
|
346
387
|
Say ' [x] 安装后校验失败:profile 里没有可用的 dsh-boot-suite'
|
|
347
388
|
Say ' (bundles 里不会添加它,避免 DSH 启动时加载一个不存在的插件)'
|
|
@@ -61,6 +61,7 @@ const lines = [
|
|
|
61
61
|
'echo ============================================================',
|
|
62
62
|
`echo Install dsh-boot-suite v${version} from this staged package.`,
|
|
63
63
|
'echo IMPORTANT: fully quit DeepSeek Harness first (window + tray).',
|
|
64
|
+
'echo If it hangs at step 2: close this window, turn off any proxy, then double-click again.',
|
|
64
65
|
'echo ============================================================',
|
|
65
66
|
'echo.',
|
|
66
67
|
`powershell -NoProfile -ExecutionPolicy Bypass -File "${pkgInstall}"`,
|