beyond-simulator-web 0.3.4 → 0.3.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/README.md +2 -0
- package/dist/public/editor.js +14 -13
- package/dist/public/play.js +73 -4
- package/dist/server.js +9 -9
- package/package.json +1 -1
package/dist/public/play.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
'use strict'
|
|
6
6
|
const API = '/editor/api/play'
|
|
7
7
|
const sessionId = decodeURIComponent(location.hash.slice(1))
|
|
8
|
+
const embedded = window.parent !== window && new URLSearchParams(location.search).get('embedded') === '1'
|
|
9
|
+
let closing = false
|
|
10
|
+
let stopped = false
|
|
11
|
+
let embeddedApi = null
|
|
8
12
|
const $ = (id) => document.getElementById(id)
|
|
9
13
|
const stage = $('stage')
|
|
10
14
|
const cv = $('cv')
|
|
@@ -27,11 +31,16 @@
|
|
|
27
31
|
if (!showLog) requestAnimationFrame(() => fitStage(true))
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
function api(action, args = {}) {
|
|
35
|
+
if (closing && action !== 'stop') return Promise.reject(new Error('试玩正在关闭'))
|
|
36
|
+
return embeddedApi ? embeddedApi(action, args) : request(action, args)
|
|
37
|
+
}
|
|
38
|
+
async function request(action, args) {
|
|
31
39
|
const response = await fetch(API, {
|
|
32
40
|
method: 'POST',
|
|
33
41
|
headers: { 'content-type': 'application/json' },
|
|
34
42
|
body: JSON.stringify({ sessionId, action, args }),
|
|
43
|
+
signal: AbortSignal.timeout(15_000),
|
|
35
44
|
})
|
|
36
45
|
const envelope = await response.json().catch(() => null)
|
|
37
46
|
if (!response.ok || !envelope || envelope.ok !== true) throw new Error(envelope?.error || '试玩请求失败')
|
|
@@ -188,6 +197,7 @@
|
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
function showError(error) {
|
|
200
|
+
if (closing) return
|
|
191
201
|
$('loading').classList.add('hidden')
|
|
192
202
|
$('fatal').classList.remove('hidden')
|
|
193
203
|
$('fatal').textContent = error?.message || String(error)
|
|
@@ -201,6 +211,7 @@
|
|
|
201
211
|
onSnapshot: renderChrome,
|
|
202
212
|
onStatus: applyStatus,
|
|
203
213
|
onError: showError,
|
|
214
|
+
isActive: () => !closing,
|
|
204
215
|
})
|
|
205
216
|
play.bindInput(stage, { keysOn: window })
|
|
206
217
|
|
|
@@ -263,7 +274,40 @@
|
|
|
263
274
|
revealStage()
|
|
264
275
|
} catch (error) { showError(error) }
|
|
265
276
|
})
|
|
266
|
-
|
|
277
|
+
function quiesce() {
|
|
278
|
+
closing = true
|
|
279
|
+
document.querySelector('.app').inert = true
|
|
280
|
+
play.reset()
|
|
281
|
+
}
|
|
282
|
+
function focusStage() {
|
|
283
|
+
stage.tabIndex = -1
|
|
284
|
+
stage.focus({ preventScroll: true })
|
|
285
|
+
}
|
|
286
|
+
stage.addEventListener('pointerdown', focusStage)
|
|
287
|
+
function requestEmbeddedClose() {
|
|
288
|
+
// Custom desktop schemes can serialize their origin as "null".
|
|
289
|
+
// The parent checks the exact iframe WindowProxy and session id.
|
|
290
|
+
parent.postMessage({ type: 'qxqy-play-close', sessionId }, location.origin === 'null' ? '*' : location.origin)
|
|
291
|
+
}
|
|
292
|
+
addEventListener('keydown', event => {
|
|
293
|
+
if (embedded && event.key === 'Escape') { event.preventDefault(); requestEmbeddedClose() }
|
|
294
|
+
})
|
|
295
|
+
$('close').addEventListener('click', async () => {
|
|
296
|
+
if (embedded) {
|
|
297
|
+
requestEmbeddedClose()
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
quiesce()
|
|
302
|
+
await api('stop')
|
|
303
|
+
stopped = true
|
|
304
|
+
window.close()
|
|
305
|
+
} catch (error) {
|
|
306
|
+
closing = false
|
|
307
|
+
document.querySelector('.app').inert = false
|
|
308
|
+
showError(error)
|
|
309
|
+
}
|
|
310
|
+
})
|
|
267
311
|
$('theme').addEventListener('click', () => { theme = theme === 'dark' ? 'light' : 'dark'; applyTheme() })
|
|
268
312
|
let lastCase = null
|
|
269
313
|
function parseAsserts() {
|
|
@@ -290,11 +334,36 @@
|
|
|
290
334
|
for (const button of document.querySelectorAll('[data-view]')) button.addEventListener('click', () => setView(button.dataset.view))
|
|
291
335
|
addEventListener('resize', () => fitStage(true))
|
|
292
336
|
addEventListener('pagehide', () => {
|
|
337
|
+
closing = true
|
|
293
338
|
play.destroy()
|
|
294
|
-
|
|
339
|
+
// Embedded close/unmount is owned by the parent. A late keepalive stop
|
|
340
|
+
// from the old frame must never terminate the next play session.
|
|
341
|
+
if (!embedded && !stopped) fetch(API, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sessionId, action: 'stop', args: {} }), keepalive: true }).catch(() => {})
|
|
295
342
|
})
|
|
343
|
+
if (embedded) {
|
|
344
|
+
// Closing the whole application document has no awaitable teardown.
|
|
345
|
+
// This fallback is separate from normal modal close, which awaits stop.
|
|
346
|
+
const parentPagehide = () => {
|
|
347
|
+
quiesce()
|
|
348
|
+
fetch(API, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sessionId, action: 'stop', args: {} }), keepalive: true }).catch(() => {})
|
|
349
|
+
}
|
|
350
|
+
parent.addEventListener('pagehide', parentPagehide)
|
|
351
|
+
addEventListener('pagehide', () => parent.removeEventListener('pagehide', parentPagehide))
|
|
352
|
+
}
|
|
296
353
|
|
|
297
354
|
if (!sessionId) showError(new Error('缺少会话 ID'))
|
|
298
|
-
else
|
|
355
|
+
else if (embedded) {
|
|
356
|
+
// Wait for the parent's load handler: closing a still-loading iframe
|
|
357
|
+
// must not race an automatic start from this document.
|
|
358
|
+
window.qxqyEmbeddedPlay = {
|
|
359
|
+
start: request => {
|
|
360
|
+
if (closing) return Promise.resolve()
|
|
361
|
+
embeddedApi = request
|
|
362
|
+
return play.start().then(() => { if (closing) play.reset(); else { revealStage(); focusStage() } })
|
|
363
|
+
},
|
|
364
|
+
quiesce,
|
|
365
|
+
focus: focusStage,
|
|
366
|
+
}
|
|
367
|
+
} else play.start().then(() => setTimeout(revealStage, 0)).catch(showError)
|
|
299
368
|
})()
|
|
300
369
|
|