beyond-simulator-web 0.4.1 → 0.5.0
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 +12 -0
- package/dist/public/app.js +54 -43
- package/dist/public/editor.html +1 -1
- package/dist/public/editor.js +17 -14
- package/dist/public/index.html +43 -42
- package/dist/public/play-renderer.js +73 -72
- package/dist/public/play.html +9 -9
- package/dist/public/play.js +47 -31
- package/dist/server.js +18 -18
- package/dist/worker.js +10 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
单用户自托管 Web 编辑器和磁盘存档预览,无需 Harness。复用共享 Studio、Lua Runtime、固定步长试玩 Worker、Pixi 渲染器及 `editor-ui` 编辑界面。
|
|
4
4
|
|
|
5
|
+
## 0.5.0 更新
|
|
6
|
+
|
|
7
|
+
- 新增中英文界面、顶部语言切换、本地字体设置和更新检查。
|
|
8
|
+
- 字体范围可选「仅游戏内文本」或「整体 UI」,默认整体 UI。
|
|
9
|
+
- 完善键鼠输入、文字排版与缓存,修复部分 GIA 导入和脚本参数校验。
|
|
10
|
+
|
|
11
|
+
更新或修改字体后,重启 Web 服务并刷新页面。
|
|
12
|
+
|
|
5
13
|
## 安装与启动
|
|
6
14
|
|
|
7
15
|
推荐 Node.js 22+。源码开发在仓库根执行:
|
|
@@ -54,6 +62,8 @@ MCP 0.3.0 起提供 `qxqy_preview_status()` 和 `qxqy_preview_open({ path: "game
|
|
|
54
62
|
|
|
55
63
|
源码更新后需运行 `pnpm build` 并重启 Web/MCP;`pnpm start:web` 直接运行已有 `web/dist`,不会自动构建。npm 安装用户更新包后也需重启进程。
|
|
56
64
|
|
|
65
|
+
大工作区可用 `QXQY_SCAN_DIR_LIMIT`、`QXQY_SCAN_ENTRY_LIMIT`、`QXQY_SCAN_DEPTH_LIMIT` 调整自动发现预算;只接受正安全整数,其他值使用上述默认值。每次扫描开始时读取一次,诊断回执返回实际限制。环境变量不会放宽显式文件路径的工作区边界。
|
|
66
|
+
|
|
57
67
|
### 2026-09-26 预览同步修复记录
|
|
58
68
|
|
|
59
69
|
Web/MCP 0.3.0、共享宿主所在 DSH 2.0.3:修复空工作区启动后跳过新文件发现、默认保存路径与打开路径脱节、大文件不出现在列表及前 50 项截断遗漏最新文件。补充显式预览桥接、加载错误与版本信息。证据为仓库内 `web/test/preview-state.test.mjs`、`studio/test/workspace.test.mjs`、`studio/test/controller-archive.test.mjs` 与实际启动两个进程的 `mcp/test/web-integration.test.mjs`;`evidence_source: observed`,运行端 `simulator`,`device_status: not_required`。包公开状态以 npm 审核结果为准。
|
|
@@ -62,6 +72,8 @@ Web/MCP 0.3.0、共享宿主所在 DSH 2.0.3:修复空工作区启动后跳过
|
|
|
62
72
|
|
|
63
73
|
试玩使用固定 30 FPS Worker、增量场景和 Pixi/WebGL;刷新试玩页会接回已有 Worker,保留暂停和当前玩家。截图仍由 Host 根据统一场景输出 PNG。
|
|
64
74
|
|
|
75
|
+
试玩工具栏「键位响应」默认开启,按官方文档转发键盘及鼠标左右键的 Down/Up;关闭后光标点击和滚动仍可用,失焦会释放已按键。映射、文档/回传差异及范围见 [浏览器键位响应](../studio/docs/browser-input.md)。
|
|
76
|
+
|
|
65
77
|
白名单静态图片按需从素材源加载,通过同源 `/qxqy-assets/` 路由显示,浏览器复用 HTTP 缓存,Host 与 MCP 共用磁盘缓存。可用 `QXQY_IMAGE_CACHE_DIR` 指定目录;图片属性面板提供「重新下载此素材」。详见 [素材缓存与预览边界](../studio/docs/image-assets.md)。
|
|
66
78
|
|
|
67
79
|
Lua `game.PlayAudio2D(id)` 触发白名单 MP3 按需缓存与播放,同源路由为 `/qxqy-audio/`,缓存目录可用 `QXQY_AUDIO_CACHE_DIR` 指定。首次自动播放被浏览器阻止时点击「开启声音」;支持停止、试玩暂停/继续和静音。参见 [音频说明](../studio/docs/audio-assets.md)。
|
package/dist/public/app.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { PixiPlayRenderer, createPlaySession } from './play-renderer.js'
|
|
1
|
+
import { PixiPlayRenderer, createPlaySession, loadBrowserFont, t, getLocale, localizePage, subscribeLocale, loadSystemLanguage } from './play-renderer.js'
|
|
2
|
+
|
|
3
|
+
await loadSystemLanguage('/editor/api/locale')
|
|
4
|
+
localizePage()
|
|
5
|
+
subscribeLocale(() => { localizePage(); if (model) render() })
|
|
2
6
|
|
|
3
7
|
const byId = (id) => document.getElementById(id)
|
|
4
8
|
|
|
@@ -16,6 +20,7 @@ const elements = {
|
|
|
16
20
|
device: byId('device-select'),
|
|
17
21
|
playControls: byId('play-controls'),
|
|
18
22
|
playStart: byId('play-start'),
|
|
23
|
+
playKeys: byId('play-keys'),
|
|
19
24
|
playAudio: byId('play-audio'),
|
|
20
25
|
playPause: byId('play-pause'),
|
|
21
26
|
playResume: byId('play-resume'),
|
|
@@ -68,13 +73,13 @@ let toastTimer = null
|
|
|
68
73
|
let editorImageRefreshing = false
|
|
69
74
|
let editorImageRefreshPending = false
|
|
70
75
|
|
|
71
|
-
const playRenderer = new PixiPlayRenderer(elements.stageCanvas)
|
|
76
|
+
const playRenderer = new PixiPlayRenderer(elements.stageCanvas, { fontReady: loadBrowserFont() })
|
|
72
77
|
|
|
73
78
|
function applyTheme(theme) {
|
|
74
79
|
const next = theme === 'light' ? 'light' : 'dark'
|
|
75
80
|
document.documentElement.dataset.theme = next
|
|
76
81
|
document.querySelector('meta[name="theme-color"]').content = next === 'light' ? '#f5f6f8' : '#20242e'
|
|
77
|
-
elements.theme.title = next === 'dark' ? '切换到亮色主题' : '切换到暗色主题'
|
|
82
|
+
elements.theme.title = next === 'dark' ? t('切换到亮色主题') : t('切换到暗色主题')
|
|
78
83
|
localStorage.setItem('qxqy-simulator-theme', next)
|
|
79
84
|
}
|
|
80
85
|
|
|
@@ -115,7 +120,7 @@ function renderArchives() {
|
|
|
115
120
|
elements.archive.replaceChildren()
|
|
116
121
|
const archives = model?.archives || []
|
|
117
122
|
if (!archives.length && !model?.activePath) {
|
|
118
|
-
const empty = option('', '未发现 qxqy-simulator-save')
|
|
123
|
+
const empty = option('', t('未发现 qxqy-simulator-save'))
|
|
119
124
|
empty.disabled = true
|
|
120
125
|
elements.archive.append(empty)
|
|
121
126
|
return
|
|
@@ -124,7 +129,7 @@ function renderArchives() {
|
|
|
124
129
|
elements.archive.append(option(archive.path, `${archive.name} · ${archive.path}`))
|
|
125
130
|
}
|
|
126
131
|
if (model.activePath && !archives.some(archive => archive.path === model.activePath)) {
|
|
127
|
-
elements.archive.prepend(option(model.activePath, `${model.snapshot?.save?.name || '当前存档'} · ${model.activePath}`))
|
|
132
|
+
elements.archive.prepend(option(model.activePath, `${model.snapshot?.save?.name || t('当前存档')} · ${model.activePath}`))
|
|
128
133
|
}
|
|
129
134
|
elements.archive.value = model.activePath || previous || archives[0].path
|
|
130
135
|
}
|
|
@@ -132,7 +137,7 @@ function renderArchives() {
|
|
|
132
137
|
function renderDevices() {
|
|
133
138
|
const presets = model?.snapshot?.canvas?.presets || []
|
|
134
139
|
const previous = elements.device.value
|
|
135
|
-
elements.device.replaceChildren(...presets.map((preset) => option(preset.id, preset.label)))
|
|
140
|
+
elements.device.replaceChildren(...presets.map((preset) => option(preset.id, t(preset.label))))
|
|
136
141
|
const active = mode === 'play' && model?.play?.canvasId
|
|
137
142
|
? model.play.canvasId
|
|
138
143
|
: model?.snapshot?.canvas?.id
|
|
@@ -143,11 +148,11 @@ function renderTree() {
|
|
|
143
148
|
const rows = model?.activePath ? (model?.snapshot?.tree || []) : []
|
|
144
149
|
elements.tree.replaceChildren()
|
|
145
150
|
elements.treeCount.textContent = String(rows.length)
|
|
146
|
-
elements.treeTitle.textContent = model?.snapshot?.asset?.type === 'client-control-template' ? '模板与子控件' : '控件树'
|
|
151
|
+
elements.treeTitle.textContent = model?.snapshot?.asset?.type === 'client-control-template' ? t('模板与子控件') : t('控件树')
|
|
147
152
|
if (!rows.length) {
|
|
148
153
|
const empty = document.createElement('div')
|
|
149
154
|
empty.className = 'tree-empty'
|
|
150
|
-
empty.textContent = model?.activePath ? '当前资产没有控件。' : '打开存档后显示控件结构。'
|
|
155
|
+
empty.textContent = model?.activePath ? t('当前资产没有控件。') : t('打开存档后显示控件结构。')
|
|
151
156
|
elements.tree.append(empty)
|
|
152
157
|
return
|
|
153
158
|
}
|
|
@@ -187,18 +192,18 @@ function addDetail(label, value) {
|
|
|
187
192
|
function renderNodeDetails() {
|
|
188
193
|
const row = model?.activePath && model?.snapshot?.tree?.find((item) => item.id === selectedId)
|
|
189
194
|
elements.nodeDetails.replaceChildren()
|
|
190
|
-
elements.nodeName.textContent = row?.name || '未选择'
|
|
195
|
+
elements.nodeName.textContent = row?.name || t('未选择')
|
|
191
196
|
if (!row) return
|
|
192
|
-
addDetail('类型', row.label || row.kind)
|
|
197
|
+
addDetail(t('类型'), t(row.label) || row.kind)
|
|
193
198
|
addDetail('ID', row.id)
|
|
194
199
|
addDetail('GUID', row.guid)
|
|
195
|
-
addDetail('树深度', row.depth)
|
|
196
|
-
addDetail('父节点', row.parentId)
|
|
197
|
-
addDetail('子控件', row.childCount)
|
|
200
|
+
addDetail(t('树深度'), row.depth)
|
|
201
|
+
addDetail(t('父节点'), row.parentId)
|
|
202
|
+
addDetail(t('子控件'), row.childCount)
|
|
198
203
|
}
|
|
199
204
|
|
|
200
205
|
function formatLogs(logs) {
|
|
201
|
-
if (!Array.isArray(logs) || !logs.length) return '试玩启动后显示 Lua 日志。'
|
|
206
|
+
if (!Array.isArray(logs) || !logs.length) return t('试玩启动后显示 Lua 日志。')
|
|
202
207
|
return logs.map((entry) => {
|
|
203
208
|
if (typeof entry === 'string') return entry
|
|
204
209
|
const time = entry.time == null ? '' : `[${Number(entry.time).toFixed(3)}] `
|
|
@@ -216,26 +221,26 @@ function renderStage() {
|
|
|
216
221
|
elements.stageEmpty.hidden = showEditor || showPlay
|
|
217
222
|
elements.stageShell.classList.toggle('playing', mode === 'play' && Boolean(model?.play?.running))
|
|
218
223
|
if (!hasArchive) {
|
|
219
|
-
elements.stageEmpty.querySelector('strong').textContent = model?.lastError ? '存档加载失败' : '等待工作区存档'
|
|
220
|
-
const watched = model?.workspace ? `正在监听 ${model.workspace}。` : ''
|
|
224
|
+
elements.stageEmpty.querySelector('strong').textContent = model?.lastError ? t('存档加载失败') : t('等待工作区存档')
|
|
225
|
+
const watched = model?.workspace ? t`正在监听 ${model.workspace}。` : ''
|
|
221
226
|
elements.stageEmpty.querySelector('p').textContent = model?.lastError
|
|
222
227
|
? model.lastError
|
|
223
|
-
: `${watched}Codex 保存 qxqy-simulator-save JSON 后会自动出现在这里。`
|
|
228
|
+
: t`${watched}Codex 保存 qxqy-simulator-save JSON 后会自动出现在这里。`
|
|
224
229
|
} else if (mode === 'play' && !model.play.running) {
|
|
225
|
-
elements.stageEmpty.querySelector('strong').textContent = '试玩尚未启动'
|
|
226
|
-
elements.stageEmpty.querySelector('p').textContent = '选择设备并点击“启动”,即可运行 Lua 和交互逻辑。'
|
|
230
|
+
elements.stageEmpty.querySelector('strong').textContent = t('试玩尚未启动')
|
|
231
|
+
elements.stageEmpty.querySelector('p').textContent = t('选择设备并点击“启动”,即可运行 Lua 和交互逻辑。')
|
|
227
232
|
}
|
|
228
233
|
if (showEditor) refreshEditorImage()
|
|
229
|
-
elements.assetLabel.textContent = hasArchive ? (snapshot?.asset?.label || '等待存档') : '等待存档'
|
|
230
|
-
elements.saveName.textContent = hasArchive ? (snapshot?.save?.name || '未命名存档') : '未命名存档'
|
|
234
|
+
elements.assetLabel.textContent = hasArchive ? (snapshot?.asset?.label || t('等待存档')) : t('等待存档')
|
|
235
|
+
elements.saveName.textContent = hasArchive ? (snapshot?.save?.name || t('未命名存档')) : t('未命名存档')
|
|
231
236
|
elements.canvasLabel.textContent = hasArchive && snapshot?.canvas ? `${snapshot.canvas.label} · ${snapshot.canvas.width}×${snapshot.canvas.height}` : '—'
|
|
232
237
|
elements.revisionLabel.textContent = hasArchive ? `Revision ${snapshot?.version ?? '—'}` : 'Revision —'
|
|
233
|
-
elements.activePath.textContent = model?.activePath || '尚未打开存档'
|
|
238
|
+
elements.activePath.textContent = model?.activePath || t('尚未打开存档')
|
|
234
239
|
elements.playReadout.textContent = mode === 'play'
|
|
235
240
|
? model?.play?.running
|
|
236
|
-
? `Frame ${model.play.frame} · ${model.play.time.toFixed(3)}s${model.play.paused ? ' · 已暂停' : ''}`
|
|
237
|
-
: '等待启动 Runtime'
|
|
238
|
-
: '编辑器静态预览 · 磁盘自动刷新'
|
|
241
|
+
? `Frame ${model.play.frame} · ${model.play.time.toFixed(3)}s${model.play.paused ? t(' · 已暂停') : ''}`
|
|
242
|
+
: t('等待启动 Runtime')
|
|
243
|
+
: t('编辑器静态预览 · 磁盘自动刷新')
|
|
239
244
|
}
|
|
240
245
|
|
|
241
246
|
function refreshEditorImage() {
|
|
@@ -263,7 +268,7 @@ function refreshEditorImage() {
|
|
|
263
268
|
}
|
|
264
269
|
image.onerror = () => {
|
|
265
270
|
elements.stageLoading.hidden = true
|
|
266
|
-
notify('编辑器画面加载失败。', true)
|
|
271
|
+
notify(t('编辑器画面加载失败。'), true)
|
|
267
272
|
finish()
|
|
268
273
|
}
|
|
269
274
|
image.src = next
|
|
@@ -289,16 +294,16 @@ function updatePlayChrome() {
|
|
|
289
294
|
const playState = model?.play
|
|
290
295
|
elements.playReadout.textContent = mode === 'play'
|
|
291
296
|
? playState?.running
|
|
292
|
-
? `Frame ${playState.frame} · ${Number(playState.time || 0).toFixed(3)}s${playState.paused ? ' · 已暂停' : ''}`
|
|
293
|
-
: '等待启动 Runtime'
|
|
294
|
-
: '编辑器静态预览 · 磁盘自动刷新'
|
|
297
|
+
? `Frame ${playState.frame} · ${Number(playState.time || 0).toFixed(3)}s${playState.paused ? t(' · 已暂停') : ''}`
|
|
298
|
+
: t('等待启动 Runtime')
|
|
299
|
+
: t('编辑器静态预览 · 磁盘自动刷新')
|
|
295
300
|
if (mode === 'play') elements.runtimeLogs.textContent = formatLogs(playState?.logs)
|
|
296
301
|
renderControls()
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
function syncPlayPolling() {
|
|
300
305
|
if (mode === 'play' && play.running) play.startPolling()
|
|
301
|
-
else play.stopPolling()
|
|
306
|
+
else { void play.releaseKeys(); play.stopPolling() }
|
|
302
307
|
}
|
|
303
308
|
|
|
304
309
|
async function onPlaySnapshot(next) {
|
|
@@ -335,8 +340,8 @@ function render() {
|
|
|
335
340
|
elements.metricScripts.textContent = hasArchive ? String(snapshot?.scriptCount || 0) : '0'
|
|
336
341
|
elements.metricCanvas.textContent = hasArchive && snapshot?.canvas ? `${snapshot.canvas.width}×${snapshot.canvas.height}` : '—'
|
|
337
342
|
elements.runtimeLogs.textContent = formatLogs(model?.play?.logs)
|
|
338
|
-
elements.serverVersion.textContent = model?.version ? `Web ${model.version}` : '服务版本未知,请更新 Web'
|
|
339
|
-
elements.workspacePath.textContent = model?.workspace ? `工作区:${model.workspace}` : ''
|
|
343
|
+
elements.serverVersion.textContent = model?.version ? `Web ${model.version}` : t('服务版本未知,请更新 Web')
|
|
344
|
+
elements.workspacePath.textContent = model?.workspace ? t`工作区:${model.workspace}` : ''
|
|
340
345
|
elements.diagnostics.textContent = [model?.lastError, ...(model?.discovery?.warnings || [])].filter(Boolean).join('\n')
|
|
341
346
|
elements.diagnostics.hidden = !elements.diagnostics.textContent
|
|
342
347
|
elements.diagnostics.classList.toggle('error', Boolean(model?.lastError))
|
|
@@ -354,10 +359,10 @@ async function refreshState({ quiet = false, force = false, forceDiscovery = fal
|
|
|
354
359
|
mode = 'play'
|
|
355
360
|
await play.attach()
|
|
356
361
|
}
|
|
357
|
-
setConnection('online', '实时监听中')
|
|
362
|
+
setConnection('online', t('实时监听中'))
|
|
358
363
|
render()
|
|
359
364
|
} catch (error) {
|
|
360
|
-
setConnection('error', '连接中断')
|
|
365
|
+
setConnection('error', t('连接中断'))
|
|
361
366
|
if (!quiet) notify(error.message, true)
|
|
362
367
|
}
|
|
363
368
|
}
|
|
@@ -386,12 +391,18 @@ const play = createPlaySession({
|
|
|
386
391
|
onStatus: (next) => { applyPlayStatus(next); updatePlayChrome() },
|
|
387
392
|
onError: (error) => notify(error.message, true),
|
|
388
393
|
onAudioStatus: status => {
|
|
389
|
-
elements.playAudio.textContent = !status.unlocked ? '开启声音' : status.muted ? '声音已关闭' : '声音已开启'
|
|
390
|
-
elements.playAudio.title = status.error || (!status.unlocked ? '点击或在画布内操作以解锁音效' : '切换静音')
|
|
394
|
+
elements.playAudio.textContent = !status.unlocked ? t('开启声音') : status.muted ? t('声音已关闭') : t('声音已开启')
|
|
395
|
+
elements.playAudio.title = status.error || (!status.unlocked ? t('点击或在画布内操作以解锁音效') : t('切换静音'))
|
|
391
396
|
},
|
|
392
397
|
isActive: () => mode === 'play',
|
|
393
398
|
})
|
|
394
399
|
play.bindInput(elements.stageCanvas, { keysOn: elements.stageShell })
|
|
400
|
+
elements.playKeys.addEventListener('click', () => {
|
|
401
|
+
play.setKeyInputEnabled(!play.keyInputEnabled)
|
|
402
|
+
elements.playKeys.setAttribute('aria-pressed', String(play.keyInputEnabled))
|
|
403
|
+
elements.playKeys.textContent = play.keyInputEnabled ? t('键位响应:开') : t('键位响应:关')
|
|
404
|
+
if (play.keyInputEnabled) elements.stageShell.focus()
|
|
405
|
+
})
|
|
395
406
|
elements.playAudio.addEventListener('click', () => play.audio?.toggle())
|
|
396
407
|
|
|
397
408
|
elements.archive.addEventListener('change', () => run(async () => {
|
|
@@ -400,7 +411,7 @@ elements.archive.addEventListener('change', () => run(async () => {
|
|
|
400
411
|
mode = 'editor'
|
|
401
412
|
selectedId = ''
|
|
402
413
|
render()
|
|
403
|
-
notify('已打开存档')
|
|
414
|
+
notify(t('已打开存档'))
|
|
404
415
|
}))
|
|
405
416
|
|
|
406
417
|
elements.reload.addEventListener('click', () => run(async () => {
|
|
@@ -409,7 +420,7 @@ elements.reload.addEventListener('click', () => run(async () => {
|
|
|
409
420
|
model = await requestJson('/api/open', { method: 'POST', body: JSON.stringify({ path: model.activePath }) })
|
|
410
421
|
mode = 'editor'
|
|
411
422
|
render()
|
|
412
|
-
notify('已重新读取磁盘存档')
|
|
423
|
+
notify(t('已重新读取磁盘存档'))
|
|
413
424
|
} else {
|
|
414
425
|
await refreshState({ forceDiscovery: true })
|
|
415
426
|
const newest = model?.archives?.[0]?.path
|
|
@@ -418,7 +429,7 @@ elements.reload.addEventListener('click', () => run(async () => {
|
|
|
418
429
|
mode = 'editor'
|
|
419
430
|
selectedId = ''
|
|
420
431
|
render()
|
|
421
|
-
notify('已打开存档')
|
|
432
|
+
notify(t('已打开存档'))
|
|
422
433
|
}
|
|
423
434
|
}
|
|
424
435
|
}))
|
|
@@ -469,11 +480,11 @@ elements.playStop.addEventListener('click', () => run(async () => {
|
|
|
469
480
|
updatePlayChrome()
|
|
470
481
|
}))
|
|
471
482
|
|
|
472
|
-
elements.clearLogs.addEventListener('click', () => { elements.runtimeLogs.textContent = '日志已在当前视图清空。' })
|
|
483
|
+
elements.clearLogs.addEventListener('click', () => { elements.runtimeLogs.textContent = t('日志已在当前视图清空。') })
|
|
473
484
|
|
|
474
485
|
const events = new EventSource('/api/events')
|
|
475
|
-
events.addEventListener('open', () => setConnection('online', '实时监听中'))
|
|
476
|
-
events.addEventListener('error', () => setConnection('error', '正在重连'))
|
|
486
|
+
events.addEventListener('open', () => setConnection('online', t('实时监听中')))
|
|
487
|
+
events.addEventListener('error', () => setConnection('error', t('正在重连')))
|
|
477
488
|
events.addEventListener('state', (event) => {
|
|
478
489
|
let change = {}
|
|
479
490
|
try { change = JSON.parse(event.data) } catch {}
|
|
@@ -481,7 +492,7 @@ events.addEventListener('state', (event) => {
|
|
|
481
492
|
mode = 'editor'
|
|
482
493
|
selectedId = ''
|
|
483
494
|
play.reset()
|
|
484
|
-
notify(change.reason === 'external-change' ? '检测到存档变化,预览已更新' : '预览存档已打开')
|
|
495
|
+
notify(change.reason === 'external-change' ? t('检测到存档变化,预览已更新') : t('预览存档已打开'))
|
|
485
496
|
}
|
|
486
497
|
// 试玩画面有独立的轻量状态轮询;忽略 play 事件,避免重复刷新。
|
|
487
498
|
if (change.type !== 'play') void refreshState({ quiet: true, force: true })
|
package/dist/public/editor.html
CHANGED