beyond-simulator-web 0.4.1 → 0.5.1
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 +13 -1
- package/dist/public/app.js +54 -43
- package/dist/public/editor.html +1 -1
- package/dist/public/editor.js +18 -14
- package/dist/public/index.html +43 -42
- package/dist/public/play-renderer.js +72 -71
- package/dist/public/play.html +9 -9
- package/dist/public/play.js +47 -31
- package/dist/server.js +21 -18
- package/dist/worker.js +13 -10
- package/package.json +1 -1
package/dist/public/play.html
CHANGED
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>千星沙箱 · 独立试玩</title>
|
|
6
|
+
<title data-i18n="千星沙箱 · 独立试玩">千星沙箱 · 独立试玩</title>
|
|
7
7
|
<link rel="stylesheet" href="/editor/play.css">
|
|
8
8
|
</head>
|
|
9
|
-
<body>
|
|
9
|
+
<body data-qxqy-font-ui>
|
|
10
10
|
<div class="app">
|
|
11
11
|
<header class="bar">
|
|
12
|
-
<div class="brand"><span class="icon">UI</span><strong>模拟器</strong><select id="deviceSel" class="devsel hidden" aria-label="切换设备画布" title="切换设备画布:以新画布重建运行时,GetUICanvasSize 与预设像素一致"></select><select id="playerCountSel" class="playersel hidden" aria-label="试玩人数" title="试玩人数:最多 8 人,每人一份独立客户端"></select><select id="playerViewSel" class="viewsel hidden" aria-label="当前玩家视角" title="切换当前玩家视角:输入、画面和 PlayerSelf 都跟随该玩家"></select><span class="device">设备
|
|
13
|
-
<div class="view-tabs" role="tablist" aria-label="试玩页面"><button class="active" data-view="play" role="tab">试玩</button><button data-view="log" role="tab">日志</button></div>
|
|
14
|
-
<div class="actions"><button id="audio" data-audio-toggle title="点击解锁浏览器音效">开启声音</button><button id="pause">Ⅱ 暂停运行</button><button id="step" disabled title="暂停后可单帧调试">▷ 单帧调试</button><button class="stop" id="stop">■ 结束试玩</button><button class="primary hidden" id="restart">↻ 重新开始</button><button id="theme" title="切换亮暗主题">◐</button><button id="close">返回编辑</button></div>
|
|
12
|
+
<div class="brand"><span class="icon">UI</span><strong data-i18n="模拟器">模拟器</strong><select id="deviceSel" class="devsel hidden" aria-label="切换设备画布" data-i18n-aria-label="切换设备画布" title="切换设备画布:以新画布重建运行时,GetUICanvasSize 与预设像素一致" data-i18n-title="切换设备画布:以新画布重建运行时,GetUICanvasSize 与预设像素一致"></select><select id="playerCountSel" class="playersel hidden" aria-label="试玩人数" data-i18n-aria-label="试玩人数" title="试玩人数:最多 8 人,每人一份独立客户端" data-i18n-title="试玩人数:最多 8 人,每人一份独立客户端"></select><select id="playerViewSel" class="viewsel hidden" aria-label="当前玩家视角" data-i18n-aria-label="当前玩家视角" title="切换当前玩家视角:输入、画面和 PlayerSelf 都跟随该玩家" data-i18n-title="切换当前玩家视角:输入、画面和 PlayerSelf 都跟随该玩家"></select><span class="device"><span data-i18n="设备">设备 </span><b id="resolution" data-i18n="准备中…">准备中…</b></span></div>
|
|
13
|
+
<div class="view-tabs" role="tablist" aria-label="试玩页面" data-i18n-aria-label="试玩页面"><button class="active" data-view="play" role="tab" data-i18n="试玩">试玩</button><button data-view="log" role="tab" data-i18n="日志">日志</button></div>
|
|
14
|
+
<div class="actions"><button id="keyInput" data-key-input-toggle aria-pressed="true" title="转发官方键位的按下/抬起事件(含鼠标左右键);关闭不影响画布点击" data-i18n-title="转发官方键位的按下/抬起事件(含鼠标左右键);关闭不影响画布点击" data-i18n="键位响应:开">键位响应:开</button><button id="audio" data-audio-toggle title="点击解锁浏览器音效" data-i18n-title="点击解锁浏览器音效" data-i18n="开启声音">开启声音</button><button id="pause" data-i18n="Ⅱ 暂停运行">Ⅱ 暂停运行</button><button id="step" disabled title="暂停后可单帧调试" data-i18n-title="暂停后可单帧调试" data-i18n="▷ 单帧调试">▷ 单帧调试</button><button class="stop" id="stop" data-i18n="■ 结束试玩">■ 结束试玩</button><button class="primary hidden" id="restart" data-i18n="↻ 重新开始">↻ 重新开始</button><button id="theme" title="切换亮暗主题" data-i18n-title="切换亮暗主题">◐</button><button id="close" data-i18n="返回编辑">返回编辑</button></div>
|
|
15
15
|
</header>
|
|
16
16
|
<div class="views">
|
|
17
|
-
<main class="workspace play-view" id="workspace"><div class="stage-shell veiled" id="shell"><div class="stage-label" id="stageLabel"></div><div class="stage" id="stage"><canvas id="cv" class="stage-canvas"></canvas></div></div><div class="loading" id="loading"><span class="spin"></span>正在准备试玩…</div><div class="fatal hidden" id="fatal"></div></main>
|
|
17
|
+
<main class="workspace play-view" id="workspace"><div class="stage-shell veiled" id="shell"><div class="stage-label" id="stageLabel"></div><div class="stage" id="stage"><canvas id="cv" class="stage-canvas"></canvas></div></div><div class="loading" id="loading"><span class="spin"></span><span data-i18n="正在准备试玩…">正在准备试玩…</span></div><div class="fatal hidden" id="fatal"></div></main>
|
|
18
18
|
<section class="debug-view hidden" id="logView">
|
|
19
|
-
<aside class="panel tree-panel"><div class="panel-head">运行时场景树
|
|
20
|
-
<div class="log-stack"><main class="panel log-panel"><div class="panel-head">客户端脚本日志
|
|
19
|
+
<aside class="panel tree-panel"><div class="panel-head"><span data-i18n="运行时场景树">运行时场景树 </span><small id="nodeCount" data-i18n="0 节点">0 节点</small></div><div class="tree" id="sceneTree"></div><div class="panel-head"><span data-i18n="服务端变量">服务端变量 </span><small id="varCount" data-i18n="0 项">0 项</small></div><div class="vars" id="serverVars"></div></aside>
|
|
20
|
+
<div class="log-stack"><main class="panel log-panel"><div class="panel-head"><span data-i18n="客户端脚本日志">客户端脚本日志 </span><small data-i18n="最近 100 条">最近 100 条</small></div><div class="log" id="log"></div></main><aside class="panel log-panel"><div class="panel-head"><span data-i18n="服务端日志">服务端日志 </span><small data-i18n="变量 / 信号">变量 / 信号</small></div><div class="log" id="serverLog"></div></aside><section class="panel case-panel"><div class="panel-head"><span data-i18n="自动测用例">自动测用例 </span><small id="caseHint" data-i18n="先试玩,再保存回放">先试玩,再保存回放</small></div><div class="case-body"><textarea id="caseAsserts" spellcheck="false" placeholder='断言 JSON 数组,例如 [{"kind":"log","contains":"clicked"}]' data-i18n-placeholder="断言 JSON 数组,例如 [{"kind":"log","contains":"clicked"}]"></textarea><button id="saveCase" data-i18n="保存用例">保存用例</button><button id="runCase" data-i18n="回放用例">回放用例</button></div></section></div>
|
|
21
21
|
</section>
|
|
22
22
|
</div>
|
|
23
|
-
<footer class="status"><span class="live" id="state">● 正在启动</span><span id="viewMode">页面:试玩</span><span class="spacer"></span><span class="metric" id="frame">Frame 0</span><span class="metric" id="time">0.00s</span></footer>
|
|
23
|
+
<footer class="status"><span class="live" id="state" data-i18n="● 正在启动">● 正在启动</span><span id="viewMode" data-i18n="页面:试玩">页面:试玩</span><span class="spacer"></span><span class="metric" id="frame">Frame 0</span><span class="metric" id="time">0.00s</span></footer>
|
|
24
24
|
</div>
|
|
25
25
|
<script type="module" src="/editor/play.js"></script>
|
|
26
26
|
</body>
|
package/dist/public/play.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
|
|
2
|
-
import { PixiPlayRenderer, createPlaySession } from '/editor/play-renderer.js'
|
|
2
|
+
import { PixiPlayRenderer, createPlaySession, loadBrowserFont, t, getLocale, localizePage, subscribeLocale, loadSystemLanguage } from '/editor/play-renderer.js'
|
|
3
3
|
|
|
4
4
|
(() => {
|
|
5
5
|
'use strict'
|
|
6
|
+
// The parent reads qxqyEmbeddedPlay synchronously on iframe load. Do not
|
|
7
|
+
// delay that controller's registration behind an OS-language request.
|
|
8
|
+
void loadSystemLanguage('/editor/api/locale')
|
|
9
|
+
localizePage()
|
|
10
|
+
subscribeLocale(() => { localizePage(); if (play.snapshot) applyStatus(play.snapshot) })
|
|
6
11
|
const API = '/editor/api/play'
|
|
7
12
|
const sessionId = decodeURIComponent(location.hash.slice(1))
|
|
8
13
|
const embedded = window.parent !== window && new URLSearchParams(location.search).get('embedded') === '1'
|
|
@@ -12,7 +17,7 @@
|
|
|
12
17
|
const $ = (id) => document.getElementById(id)
|
|
13
18
|
const stage = $('stage')
|
|
14
19
|
const cv = $('cv')
|
|
15
|
-
const renderer = new PixiPlayRenderer(cv)
|
|
20
|
+
const renderer = new PixiPlayRenderer(cv, { fontReady: loadBrowserFont() })
|
|
16
21
|
const shell = $('shell')
|
|
17
22
|
const workspace = $('workspace')
|
|
18
23
|
const logView = $('logView')
|
|
@@ -24,15 +29,16 @@
|
|
|
24
29
|
|
|
25
30
|
function setView(view) {
|
|
26
31
|
const showLog = view === 'log'
|
|
32
|
+
if (showLog) void play.releaseKeys()
|
|
27
33
|
workspace.classList.toggle('hidden', showLog)
|
|
28
34
|
logView.classList.toggle('hidden', !showLog)
|
|
29
|
-
$('viewMode').textContent = showLog ? '页面:日志' : '页面:试玩'
|
|
35
|
+
$('viewMode').textContent = showLog ? t('页面:日志') : t('页面:试玩')
|
|
30
36
|
for (const button of document.querySelectorAll('[data-view]')) button.classList.toggle('active', button.dataset.view === view)
|
|
31
37
|
if (!showLog) requestAnimationFrame(() => fitStage(true))
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
function api(action, args = {}) {
|
|
35
|
-
if (closing && action !== 'stop') return Promise.reject(new Error('试玩正在关闭'))
|
|
41
|
+
if (closing && action !== 'stop') return Promise.reject(new Error(t('试玩正在关闭')))
|
|
36
42
|
return embeddedApi ? embeddedApi(action, args) : request(action, args)
|
|
37
43
|
}
|
|
38
44
|
async function request(action, args) {
|
|
@@ -43,7 +49,7 @@
|
|
|
43
49
|
signal: AbortSignal.timeout(15_000),
|
|
44
50
|
})
|
|
45
51
|
const envelope = await response.json().catch(() => null)
|
|
46
|
-
if (!response.ok || !envelope || envelope.ok !== true) throw new Error(envelope?.error || '试玩请求失败')
|
|
52
|
+
if (!response.ok || !envelope || envelope.ok !== true) throw new Error(envelope?.error || t('试玩请求失败'))
|
|
47
53
|
return envelope.value
|
|
48
54
|
}
|
|
49
55
|
|
|
@@ -51,11 +57,11 @@
|
|
|
51
57
|
const host = $('serverVars')
|
|
52
58
|
host.replaceChildren()
|
|
53
59
|
const list = Array.isArray(rows) ? rows : []
|
|
54
|
-
$('varCount').textContent = list.length + ' 项'
|
|
60
|
+
$('varCount').textContent = list.length + t(' 项')
|
|
55
61
|
if (!list.length) {
|
|
56
62
|
const empty = document.createElement('div')
|
|
57
63
|
empty.className = 'var-empty'
|
|
58
|
-
empty.textContent = '暂无自定义变量。脚本 Get/Set 或 serverSet 之后会出现在这里。'
|
|
64
|
+
empty.textContent = t('暂无自定义变量。脚本 Get/Set 或 serverSet 之后会出现在这里。')
|
|
59
65
|
host.appendChild(empty)
|
|
60
66
|
return
|
|
61
67
|
}
|
|
@@ -65,7 +71,7 @@
|
|
|
65
71
|
const type = row.type || '—'
|
|
66
72
|
const value = row.value === undefined ? 'nil' : JSON.stringify(row.value)
|
|
67
73
|
const entityCell = document.createElement('span')
|
|
68
|
-
entityCell.textContent = row.entityLabel || row.entityType || ''
|
|
74
|
+
entityCell.textContent = t(row.entityLabel) || row.entityType || ''
|
|
69
75
|
const nameCell = document.createElement('b')
|
|
70
76
|
nameCell.textContent = row.name || ''
|
|
71
77
|
const typeCell = document.createElement('span')
|
|
@@ -82,9 +88,9 @@
|
|
|
82
88
|
host.replaceChildren()
|
|
83
89
|
const empty = document.createElement('div')
|
|
84
90
|
empty.className = 'var-empty'
|
|
85
|
-
empty.textContent = '完整运行树请用 Agent 的 inspect 快照。舞台按左树上方盖住下方绘制。'
|
|
91
|
+
empty.textContent = t('完整运行树请用 Agent 的 inspect 快照。舞台按左树上方盖住下方绘制。')
|
|
86
92
|
host.appendChild(empty)
|
|
87
|
-
$('nodeCount').textContent = (snapshot?.scene?.count || snapshot?.paint?.length || 0) + ' 个可见图元'
|
|
93
|
+
$('nodeCount').textContent = (snapshot?.scene?.count || snapshot?.paint?.length || 0) + t(' 个可见图元')
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
function fitStage(force = false) {
|
|
@@ -111,7 +117,7 @@
|
|
|
111
117
|
function applyStatus(next) {
|
|
112
118
|
$('frame').textContent = 'Frame ' + (next.frame || 0)
|
|
113
119
|
$('time').textContent = Number(next.time || 0).toFixed(2) + 's'
|
|
114
|
-
$('pause').textContent = next.paused ? '▶ 继续运行' : 'Ⅱ 暂停运行'
|
|
120
|
+
$('pause').textContent = next.paused ? t('▶ 继续运行') : t('Ⅱ 暂停运行')
|
|
115
121
|
$('pause').disabled = false
|
|
116
122
|
$('step').disabled = !next.paused
|
|
117
123
|
}
|
|
@@ -124,15 +130,16 @@
|
|
|
124
130
|
sel.classList.add('hidden')
|
|
125
131
|
return
|
|
126
132
|
}
|
|
127
|
-
const stale = sel.options.length !== presets.length
|
|
133
|
+
const stale = sel.dataset.locale !== getLocale() || sel.options.length !== presets.length
|
|
128
134
|
|| [...sel.options].some((opt, i) => opt.value !== presets[i].id)
|
|
129
135
|
if (stale) {
|
|
130
136
|
sel.replaceChildren(...presets.map((preset) => {
|
|
131
137
|
const opt = document.createElement('option')
|
|
132
138
|
opt.value = preset.id
|
|
133
|
-
opt.textContent = preset.label || (preset.id + ' · ' + preset.width + '×' + preset.height)
|
|
139
|
+
opt.textContent = t(preset.label) || (preset.id + ' · ' + preset.width + '×' + preset.height)
|
|
134
140
|
return opt
|
|
135
141
|
}))
|
|
142
|
+
sel.dataset.locale = getLocale()
|
|
136
143
|
}
|
|
137
144
|
if (sel.value !== next.canvasId) sel.value = next.canvasId
|
|
138
145
|
sel.classList.remove('hidden')
|
|
@@ -140,13 +147,14 @@
|
|
|
140
147
|
|
|
141
148
|
function fillCountSelect(sel, count) {
|
|
142
149
|
const n = Math.max(1, Math.min(8, Number(count) || 1))
|
|
143
|
-
if (sel.options.length !== 8) {
|
|
150
|
+
if (sel.options.length !== 8 || sel.dataset.locale !== getLocale()) {
|
|
144
151
|
sel.replaceChildren(...Array.from({ length: 8 }, (_, i) => {
|
|
145
152
|
const opt = document.createElement('option')
|
|
146
153
|
opt.value = String(i + 1)
|
|
147
|
-
opt.textContent =
|
|
154
|
+
opt.textContent = i === 0 ? t('1 人') : t('{0} 人', i + 1)
|
|
148
155
|
return opt
|
|
149
156
|
}))
|
|
157
|
+
sel.dataset.locale = getLocale()
|
|
150
158
|
}
|
|
151
159
|
sel.value = String(n)
|
|
152
160
|
sel.classList.remove('hidden')
|
|
@@ -159,16 +167,17 @@
|
|
|
159
167
|
const count = fillCountSelect(countSel, next.playerCount || 1)
|
|
160
168
|
const players = Array.isArray(next.players) && next.players.length
|
|
161
169
|
? next.players
|
|
162
|
-
: Array.from({ length: count }, (_, i) => ({ index: i + 1, label: '玩家' + (i + 1) }))
|
|
163
|
-
const stale = viewSel.options.length !== players.length
|
|
170
|
+
: Array.from({ length: count }, (_, i) => ({ index: i + 1, label: t('玩家') + ' ' + (i + 1) }))
|
|
171
|
+
const stale = viewSel.dataset.locale !== getLocale() || viewSel.options.length !== players.length
|
|
164
172
|
|| [...viewSel.options].some((opt, i) => Number(opt.value) !== players[i].index)
|
|
165
173
|
if (stale) {
|
|
166
174
|
viewSel.replaceChildren(...players.map((player) => {
|
|
167
175
|
const opt = document.createElement('option')
|
|
168
176
|
opt.value = String(player.index)
|
|
169
|
-
opt.textContent = player.label || ('玩家' + player.index)
|
|
177
|
+
opt.textContent = t(player.label) || (t('玩家') + ' ' + player.index)
|
|
170
178
|
return opt
|
|
171
179
|
}))
|
|
180
|
+
viewSel.dataset.locale = getLocale()
|
|
172
181
|
}
|
|
173
182
|
const view = Number(next.viewPlayerIndex) || 1
|
|
174
183
|
if (viewSel.value !== String(view)) viewSel.value = String(view)
|
|
@@ -181,18 +190,18 @@
|
|
|
181
190
|
syncDeviceSelect(next)
|
|
182
191
|
syncPlayerSelects(next)
|
|
183
192
|
$('resolution').textContent = next.canvasWidth + '×' + next.canvasHeight
|
|
184
|
-
const viewLabel = next.playerCount > 1 ? (' · 玩家' + (next.viewPlayerIndex || 1)) : ''
|
|
185
|
-
$('stageLabel').textContent = next.canvasWidth + '×' + next.canvasHeight + viewLabel + ' · 点击输入'
|
|
193
|
+
const viewLabel = next.playerCount > 1 ? (t(' · 玩家') + (next.viewPlayerIndex || 1)) : ''
|
|
194
|
+
$('stageLabel').textContent = next.canvasWidth + '×' + next.canvasHeight + viewLabel + t(' · 点击输入')
|
|
186
195
|
applyStatus(next)
|
|
187
196
|
$('stop').disabled = false
|
|
188
197
|
$('restart').classList.add('hidden')
|
|
189
|
-
$('state').textContent = next.mountError ? '⚠ 脚本挂载失败' : next.paused ? 'Ⅱ 已暂停' : '● 试玩运行中'
|
|
198
|
+
$('state').textContent = next.mountError ? t('⚠ 脚本挂载失败') : next.paused ? t('Ⅱ 已暂停') : t('● 试玩运行中')
|
|
190
199
|
$('state').className = next.mountError ? 'error' : 'live'
|
|
191
200
|
renderTree(next)
|
|
192
201
|
renderVars(next.serverVars)
|
|
193
202
|
$('log').textContent = (next.logs || []).slice(-100).map((entry) => Number(entry.time || 0).toFixed(2).padStart(7, ' ') + ' [' + entry.level + '] ' + entry.text).join('\n')
|
|
194
203
|
$('serverLog').textContent = (next.serverLogs || next.server?.logs || []).slice(-100).map((entry) => Number(entry.time || 0).toFixed(2).padStart(7, ' ') + ' [' + entry.level + '] ' + entry.text).join('\n')
|
|
195
|
-
$('caseHint').textContent = (next.history && next.history.length ? ('已记录 ' + next.history.length + ' 步') : '先试玩,再保存回放')
|
|
204
|
+
$('caseHint').textContent = (next.history && next.history.length ? (t('已记录 ') + next.history.length + t(' 步')) : t('先试玩,再保存回放'))
|
|
196
205
|
if (next.mountError) setView('log')
|
|
197
206
|
}
|
|
198
207
|
|
|
@@ -200,9 +209,9 @@
|
|
|
200
209
|
if (closing) return
|
|
201
210
|
$('loading').classList.add('hidden')
|
|
202
211
|
$('fatal').classList.remove('hidden')
|
|
203
|
-
$('fatal').textContent = error?.message || String(error)
|
|
212
|
+
$('fatal').textContent = t(error?.message || String(error))
|
|
204
213
|
$('state').className = 'error'
|
|
205
|
-
$('state').textContent = '⚠ 试玩错误'
|
|
214
|
+
$('state').textContent = t('⚠ 试玩错误')
|
|
206
215
|
}
|
|
207
216
|
|
|
208
217
|
const play = createPlaySession({
|
|
@@ -212,12 +221,19 @@
|
|
|
212
221
|
onStatus: applyStatus,
|
|
213
222
|
onError: showError,
|
|
214
223
|
onAudioStatus: status => {
|
|
215
|
-
$('audio').textContent = !status.unlocked ? '开启声音' : status.muted ? '声音已关闭' : '声音已开启'
|
|
216
|
-
$('audio').title = status.error || (!status.unlocked ? '点击或在画布内操作以解锁音效' : '切换静音')
|
|
224
|
+
$('audio').textContent = !status.unlocked ? t('开启声音') : status.muted ? t('声音已关闭') : t('声音已开启')
|
|
225
|
+
$('audio').title = status.error || (!status.unlocked ? t('点击或在画布内操作以解锁音效') : t('切换静音'))
|
|
217
226
|
},
|
|
218
227
|
isActive: () => !closing,
|
|
228
|
+
isInputActive: () => !workspace.classList.contains('hidden'),
|
|
219
229
|
})
|
|
220
230
|
play.bindInput(stage, { keysOn: window })
|
|
231
|
+
$('keyInput').addEventListener('click', () => {
|
|
232
|
+
play.setKeyInputEnabled(!play.keyInputEnabled)
|
|
233
|
+
$('keyInput').setAttribute('aria-pressed', String(play.keyInputEnabled))
|
|
234
|
+
$('keyInput').textContent = play.keyInputEnabled ? t('键位响应:开') : t('键位响应:关')
|
|
235
|
+
if (play.keyInputEnabled) focusStage()
|
|
236
|
+
})
|
|
221
237
|
$('audio').addEventListener('click', () => play.audio?.toggle())
|
|
222
238
|
|
|
223
239
|
$('step').addEventListener('click', () => void play.act('step', { dt: 1 / 30 }))
|
|
@@ -225,7 +241,7 @@
|
|
|
225
241
|
$('stop').addEventListener('click', async () => {
|
|
226
242
|
if (!play.running) return
|
|
227
243
|
try { await play.stop() } catch (error) { showError(error); return }
|
|
228
|
-
$('state').textContent = '■ 试玩已结束,可重新开始或返回编辑'
|
|
244
|
+
$('state').textContent = t('■ 试玩已结束,可重新开始或返回编辑')
|
|
229
245
|
$('state').className = ''
|
|
230
246
|
$('pause').disabled = true
|
|
231
247
|
$('step').disabled = true
|
|
@@ -319,20 +335,20 @@
|
|
|
319
335
|
const text = $('caseAsserts').value.trim()
|
|
320
336
|
if (!text) return []
|
|
321
337
|
const parsed = JSON.parse(text)
|
|
322
|
-
if (!Array.isArray(parsed)) throw new Error('断言必须是 JSON 数组')
|
|
338
|
+
if (!Array.isArray(parsed)) throw new Error(t('断言必须是 JSON 数组'))
|
|
323
339
|
return parsed
|
|
324
340
|
}
|
|
325
341
|
$('saveCase').addEventListener('click', async () => {
|
|
326
342
|
try {
|
|
327
343
|
lastCase = await api('saveCase', { name: 'play-case', asserts: parseAsserts() })
|
|
328
|
-
$('caseHint').textContent = '已保存 ' + (lastCase.events || []).length + ' 步 / ' + (lastCase.asserts || []).length + ' 条断言'
|
|
344
|
+
$('caseHint').textContent = t('已保存 ') + (lastCase.events || []).length + t(' 步 / ') + (lastCase.asserts || []).length + t(' 条断言')
|
|
329
345
|
} catch (error) { showError(error) }
|
|
330
346
|
})
|
|
331
347
|
$('runCase').addEventListener('click', async () => {
|
|
332
348
|
try {
|
|
333
349
|
if (!lastCase) lastCase = await api('saveCase', { name: 'play-case', asserts: parseAsserts() })
|
|
334
350
|
const report = await api('runCase', { case: lastCase })
|
|
335
|
-
$('caseHint').textContent = report.passed ? '回放通过' : ('回放失败 @' + (report.failedAt ?? '?') + 's')
|
|
351
|
+
$('caseHint').textContent = report.passed ? t('回放通过') : (t('回放失败 @') + (report.failedAt ?? '?') + 's')
|
|
336
352
|
if (!report.passed) setView('log')
|
|
337
353
|
} catch (error) { showError(error) }
|
|
338
354
|
})
|
|
@@ -356,7 +372,7 @@
|
|
|
356
372
|
addEventListener('pagehide', () => parent.removeEventListener('pagehide', parentPagehide))
|
|
357
373
|
}
|
|
358
374
|
|
|
359
|
-
if (!sessionId) showError(new Error('缺少会话 ID'))
|
|
375
|
+
if (!sessionId) showError(new Error(t('缺少会话 ID')))
|
|
360
376
|
else if (embedded) {
|
|
361
377
|
// Wait for the parent's load handler: closing a still-loading iframe
|
|
362
378
|
// must not race an automatic start from this document.
|