@webspatial/core-sdk 1.5.0 → 1.6.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/CHANGELOG.md +23 -0
- package/dist/iife/index.d.ts +164 -12
- package/dist/iife/index.global.js +11 -11
- package/dist/iife/index.global.js.map +1 -1
- package/dist/index.d.ts +164 -12
- package/dist/index.js +403 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/JSBCommand.ts +7 -2
- package/src/Spatial.ts +1 -1
- package/src/SpatialSession.ts +4 -2
- package/src/SpatializedElement.ts +10 -10
- package/src/SpatializedElementCreator.ts +5 -2
- package/src/SpatializedStatic3DElement.test.ts +15 -1
- package/src/SpatializedStatic3DElement.ts +167 -8
- package/src/WebMsgCommand.ts +22 -0
- package/src/scene-polyfill.manifest.test.ts +402 -0
- package/src/scene-polyfill.test.ts +108 -18
- package/src/scene-polyfill.ts +275 -29
- package/src/types/global.d.ts +3 -0
- package/src/types/types.ts +71 -0
- package/src/utils.ts +21 -0
package/src/scene-polyfill.ts
CHANGED
|
@@ -9,8 +9,13 @@ import {
|
|
|
9
9
|
isValidWorldScalingType,
|
|
10
10
|
isValidWorldAlignmentType,
|
|
11
11
|
isValidBaseplateVisibilityType,
|
|
12
|
+
PWAManifest,
|
|
13
|
+
XRSpatialSceneConfig,
|
|
14
|
+
XRSpatialSceneDefaults,
|
|
12
15
|
} from './types/types'
|
|
13
16
|
import { SpatialSceneCreationOptionsInternal } from './types/internal'
|
|
17
|
+
import { deepCloneJSON } from './utils'
|
|
18
|
+
import { pointToPhysical, physicalToPoint } from './physicalMetrics'
|
|
14
19
|
|
|
15
20
|
const defaultSceneConfig: SpatialSceneCreationOptions = {
|
|
16
21
|
defaultSize: {
|
|
@@ -21,17 +26,80 @@ const defaultSceneConfig: SpatialSceneCreationOptions = {
|
|
|
21
26
|
|
|
22
27
|
const defaultSceneConfigVolume: SpatialSceneCreationOptions = {
|
|
23
28
|
defaultSize: {
|
|
24
|
-
width: 0.
|
|
25
|
-
height: 0.
|
|
26
|
-
depth: 0.
|
|
29
|
+
width: '0.94m',
|
|
30
|
+
height: '0.94m',
|
|
31
|
+
depth: '0.94m',
|
|
27
32
|
},
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
let xr_window_defaults: SpatialSceneCreationOptions = {
|
|
36
|
+
...defaultSceneConfig,
|
|
37
|
+
}
|
|
38
|
+
let xr_volume_defaults: SpatialSceneCreationOptions = {
|
|
39
|
+
...defaultSceneConfigVolume,
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
const INTERNAL_SCHEMA_PREFIX = 'webspatial://'
|
|
31
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Deep-merge two plain object trees (no arrays, no special classes).
|
|
46
|
+
* - Creates a shallow clone of base, then recursively merges properties from over.
|
|
47
|
+
* - When both sides at a key are plain objects, merges recursively; otherwise, replaces with over.
|
|
48
|
+
* - Ignores arrays (treated as replace).
|
|
49
|
+
* Intended for small configuration objects like manifest overrides.
|
|
50
|
+
*/
|
|
51
|
+
function deepMergePlain<
|
|
52
|
+
T extends Record<string, any>,
|
|
53
|
+
U extends Record<string, any> | undefined,
|
|
54
|
+
>(base: T, over: U): T & (U extends undefined ? {} : U) {
|
|
55
|
+
if (!over) return { ...(base || {}) } as any
|
|
56
|
+
const out: any = { ...(base || {}) }
|
|
57
|
+
for (const k of Object.keys(over)) {
|
|
58
|
+
const bv = out[k]
|
|
59
|
+
const ov = (over as any)[k]
|
|
60
|
+
if (
|
|
61
|
+
ov &&
|
|
62
|
+
typeof ov === 'object' &&
|
|
63
|
+
!Array.isArray(ov) &&
|
|
64
|
+
bv &&
|
|
65
|
+
typeof bv === 'object' &&
|
|
66
|
+
!Array.isArray(bv)
|
|
67
|
+
) {
|
|
68
|
+
out[k] = deepMergePlain(bv, ov)
|
|
69
|
+
} else {
|
|
70
|
+
out[k] = ov
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Normalize XRSpatialSceneDefaults (manifest shape) into SpatialSceneCreationOptions (runtime shape).
|
|
78
|
+
* - Only remap default_size -> defaultSize when present.
|
|
79
|
+
* - Leaves other keys (resizability, worldScaling, etc.) unchanged.
|
|
80
|
+
* - Units are left as-is; downstream formatting is handled by formatSceneConfig.
|
|
81
|
+
*/
|
|
82
|
+
function normalizeXRDefaultsToSceneOptions(
|
|
83
|
+
src: XRSpatialSceneDefaults | Record<string, any>,
|
|
84
|
+
): SpatialSceneCreationOptions {
|
|
85
|
+
const out: any = { ...(src || {}) }
|
|
86
|
+
const ds =
|
|
87
|
+
(src as any).defaultSize !== undefined
|
|
88
|
+
? (src as any).defaultSize
|
|
89
|
+
: (src as any).default_size
|
|
90
|
+
if (ds !== undefined) {
|
|
91
|
+
out.defaultSize = ds
|
|
92
|
+
}
|
|
93
|
+
if ('default_size' in out) {
|
|
94
|
+
delete out.default_size
|
|
95
|
+
}
|
|
96
|
+
return out
|
|
97
|
+
}
|
|
98
|
+
|
|
32
99
|
class SceneManager {
|
|
33
100
|
private originalOpen: any
|
|
34
101
|
private static instance: SceneManager
|
|
102
|
+
private manifestReady: Promise<void> | null = null
|
|
35
103
|
static getInstance() {
|
|
36
104
|
if (!SceneManager.instance) {
|
|
37
105
|
SceneManager.instance = new SceneManager()
|
|
@@ -40,16 +108,26 @@ class SceneManager {
|
|
|
40
108
|
}
|
|
41
109
|
|
|
42
110
|
init(window: WindowProxy) {
|
|
111
|
+
this.manifestReady = this.setupManifest()
|
|
43
112
|
this.originalOpen = window.open.bind(window)
|
|
44
113
|
;(window as any).open = this.open
|
|
45
114
|
}
|
|
46
115
|
|
|
47
|
-
|
|
116
|
+
// Stores the latest formatted config used by the platform (per scene name).
|
|
117
|
+
// This object contains normalized values and is safe for internal consumption.
|
|
118
|
+
private configMap: Record<string, SpatialSceneCreationOptionsInternal> = {}
|
|
119
|
+
// Stores the raw callback return value (per scene name) to feed into the next initScene call as `pre`.
|
|
120
|
+
// We keep this unformatted so developers receive exactly what they last returned.
|
|
121
|
+
private callbackReturnMap: Record<string, SpatialSceneCreationOptions> = {}
|
|
48
122
|
private getConfig(name?: string) {
|
|
49
123
|
if (name === undefined || !this.configMap[name]) return undefined
|
|
50
124
|
return this.configMap[name]
|
|
51
125
|
}
|
|
52
126
|
|
|
127
|
+
waitManifest(): Promise<void> {
|
|
128
|
+
return this.manifestReady ?? Promise.resolve()
|
|
129
|
+
}
|
|
130
|
+
|
|
53
131
|
// Ensure URL is absolute; only convert when a relative path is provided
|
|
54
132
|
// - Keep external and special schemes untouched (http, https, data, blob, about, file, mailto, etc.)
|
|
55
133
|
// - Handle protocol-relative URLs (//example.com/path)
|
|
@@ -73,15 +151,48 @@ class SceneManager {
|
|
|
73
151
|
}
|
|
74
152
|
}
|
|
75
153
|
|
|
154
|
+
private async setupManifest() {
|
|
155
|
+
const manifest = await this.getPWAManifest()
|
|
156
|
+
try {
|
|
157
|
+
const xr = manifest?.xr_spatial_scene
|
|
158
|
+
if (!xr || typeof xr !== 'object') return
|
|
159
|
+
const { overrides, ...topLevel } = xr as XRSpatialSceneConfig
|
|
160
|
+
// Merge top-level defaults with per-scene overrides.
|
|
161
|
+
const windowRaw = deepMergePlain(topLevel, overrides?.window_scene)
|
|
162
|
+
const volumeRaw = deepMergePlain(topLevel, overrides?.volume_scene)
|
|
163
|
+
const windowNext = normalizeXRDefaultsToSceneOptions(windowRaw)
|
|
164
|
+
|
|
165
|
+
const volumeNext = normalizeXRDefaultsToSceneOptions(volumeRaw)
|
|
166
|
+
if (windowNext && Object.keys(windowNext).length > 0) {
|
|
167
|
+
xr_window_defaults = windowNext
|
|
168
|
+
}
|
|
169
|
+
if (volumeNext && Object.keys(volumeNext).length > 0) {
|
|
170
|
+
xr_volume_defaults = volumeNext
|
|
171
|
+
}
|
|
172
|
+
} catch (error: any) {
|
|
173
|
+
console.warn(
|
|
174
|
+
'SceneManager.setupManifest failed; using built-in defaults.',
|
|
175
|
+
error?.message || error,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
76
180
|
private open = (url?: string, target?: string, features?: string) => {
|
|
77
181
|
// bypass internal
|
|
78
182
|
if (url?.startsWith(INTERNAL_SCHEMA_PREFIX)) {
|
|
79
|
-
if (
|
|
80
|
-
|
|
183
|
+
if (
|
|
184
|
+
url.includes('createSpatialized2DElement') ||
|
|
185
|
+
url.includes('createAttachment')
|
|
186
|
+
) {
|
|
187
|
+
const token = //@ts-ignore
|
|
188
|
+
(window.webSpatial || window.__webspatialShell__)?.genToken?.()
|
|
81
189
|
if (token) {
|
|
190
|
+
const command = url.includes('createAttachment')
|
|
191
|
+
? 'createAttachment'
|
|
192
|
+
: 'createSpatialized2DElement'
|
|
82
193
|
const host = window.location.host
|
|
83
194
|
const protocol = window.location.protocol
|
|
84
|
-
const finalURL = `${protocol}//${host}/${token}/?command
|
|
195
|
+
const finalURL = `${protocol}//${host}/${token}/?command=${command}`
|
|
85
196
|
const rid = new URL(url).searchParams.get('rid')
|
|
86
197
|
const final = new URL(finalURL)
|
|
87
198
|
if (rid) final.searchParams.set('rid', rid)
|
|
@@ -100,7 +211,16 @@ class SceneManager {
|
|
|
100
211
|
return newWindow
|
|
101
212
|
}
|
|
102
213
|
|
|
103
|
-
|
|
214
|
+
let cfg = target ? this.getConfig(target) : undefined
|
|
215
|
+
|
|
216
|
+
if (cfg === undefined) {
|
|
217
|
+
// if no config, use default window config
|
|
218
|
+
const preFormatted = deepCloneJSON(getSceneDefaultConfig('window'))
|
|
219
|
+
|
|
220
|
+
const [ans] = formatSceneConfig(preFormatted, 'window')
|
|
221
|
+
cfg = { ...ans, type: 'window' }
|
|
222
|
+
}
|
|
223
|
+
|
|
104
224
|
const cmd = new createSpatialSceneCommand(url!, cfg, target, features)
|
|
105
225
|
const result = cmd.executeSync()
|
|
106
226
|
|
|
@@ -120,25 +240,137 @@ class SceneManager {
|
|
|
120
240
|
options?: { type: SpatialSceneType },
|
|
121
241
|
) {
|
|
122
242
|
const sceneType = options?.type ?? 'window'
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
|
|
243
|
+
const defaultConfigRaw = getSceneDefaultConfig(sceneType)
|
|
244
|
+
const previousOrDefault =
|
|
245
|
+
this.callbackReturnMap[name] ??
|
|
246
|
+
((): SpatialSceneCreationOptions => {
|
|
247
|
+
// Clone default config to avoid mutating shared defaults during formatting.
|
|
248
|
+
const cloned = deepCloneJSON(defaultConfigRaw)
|
|
249
|
+
return cloned
|
|
250
|
+
})()
|
|
251
|
+
const rawReturnVal = callback(previousOrDefault)
|
|
252
|
+
const sanitizedReturnVal = sanitizeSceneOptionsUnits(
|
|
253
|
+
deepCloneJSON(rawReturnVal),
|
|
254
|
+
)
|
|
255
|
+
const clonedForFormat = deepCloneJSON(sanitizedReturnVal)
|
|
256
|
+
// Merge normalized user return with scene-type defaults before final formatting.
|
|
257
|
+
// This ensures missing fields fall back to the appropriate xr_window_defaults / xr_volume_defaults.
|
|
258
|
+
const baseDefaults = deepCloneJSON(getSceneDefaultConfig(sceneType))
|
|
259
|
+
const mergedForFormat = deepMergePlain(baseDefaults, clonedForFormat)
|
|
260
|
+
|
|
261
|
+
const [formattedConfig, errors] = formatSceneConfig(
|
|
262
|
+
mergedForFormat,
|
|
263
|
+
sceneType,
|
|
264
|
+
)
|
|
265
|
+
|
|
126
266
|
if (errors.length > 0) {
|
|
127
267
|
console.warn(`initScene ${name} with errors: ${errors.join(', ')}`)
|
|
128
268
|
}
|
|
269
|
+
this.callbackReturnMap[name] = sanitizedReturnVal
|
|
129
270
|
this.configMap[name] = {
|
|
130
271
|
...formattedConfig,
|
|
131
272
|
type: sceneType,
|
|
132
273
|
}
|
|
133
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Resolve and load a PWA manifest as JSON:
|
|
277
|
+
* 1) Determine href:
|
|
278
|
+
* - Prefer explicit manifestUrl if provided;
|
|
279
|
+
* - Fallback to <link rel="manifest">, preferring the raw attribute over computed href.
|
|
280
|
+
* 2) Normalize href to absolute using ensureAbsoluteUrl (respects <base href>).
|
|
281
|
+
* 3) Handle data URLs inline:
|
|
282
|
+
* - data:...;base64,... → atob then JSON.parse
|
|
283
|
+
* - data:...,... → decodeURIComponent then JSON.parse
|
|
284
|
+
* 4) Fetch with credentials same-origin first; if that fails (e.g., CORS), attempt unauthenticated fetch.
|
|
285
|
+
* 5) Parse as JSON; if response body is text, parse the text as JSON.
|
|
286
|
+
*/
|
|
287
|
+
async getPWAManifest(manifestUrl?: string): Promise<PWAManifest | undefined> {
|
|
288
|
+
let href: string | undefined = manifestUrl
|
|
289
|
+
if (!href) {
|
|
290
|
+
const el = document.querySelector(
|
|
291
|
+
'link[rel="manifest"]',
|
|
292
|
+
) as HTMLLinkElement | null
|
|
293
|
+
href = el?.getAttribute('href') || el?.href
|
|
294
|
+
}
|
|
295
|
+
if (!href) return
|
|
296
|
+
href = this.ensureAbsoluteUrl(href)
|
|
297
|
+
if (!href) return
|
|
298
|
+
if (href.startsWith('data:')) {
|
|
299
|
+
// Inline data URL manifest: data:[<mediatype>][;base64],<data>
|
|
300
|
+
try {
|
|
301
|
+
const comma = href.indexOf(',')
|
|
302
|
+
if (comma < 0) return
|
|
303
|
+
const meta = href.slice(5, comma)
|
|
304
|
+
const data = href.slice(comma + 1)
|
|
305
|
+
const isBase64 = /;base64/i.test(meta)
|
|
306
|
+
const decoded = isBase64 ? atob(data) : decodeURIComponent(data)
|
|
307
|
+
return JSON.parse(decoded)
|
|
308
|
+
} catch {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
// Same-origin fetch with credentials first.
|
|
314
|
+
const res = await fetch(href, { credentials: 'same-origin' })
|
|
315
|
+
if (!res.ok) throw new Error(String(res.status))
|
|
316
|
+
try {
|
|
317
|
+
return await res.json()
|
|
318
|
+
} catch {
|
|
319
|
+
const t = await res.text()
|
|
320
|
+
return JSON.parse(t)
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
try {
|
|
324
|
+
// Fallback: unauthenticated fetch (may help when same-origin credentials fail due to CORS).
|
|
325
|
+
const res = await fetch(href)
|
|
326
|
+
if (!res.ok) return
|
|
327
|
+
try {
|
|
328
|
+
return await res.json()
|
|
329
|
+
} catch {
|
|
330
|
+
const t = await res.text()
|
|
331
|
+
return JSON.parse(t)
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function sanitizeSceneOptionsUnits(
|
|
341
|
+
val: SpatialSceneCreationOptions,
|
|
342
|
+
): SpatialSceneCreationOptions {
|
|
343
|
+
if (val?.defaultSize) {
|
|
344
|
+
const keys = ['width', 'height', 'depth'] as const
|
|
345
|
+
for (const k of keys) {
|
|
346
|
+
if (
|
|
347
|
+
k in (val.defaultSize as any) &&
|
|
348
|
+
!isValidSceneUnit((val.defaultSize as any)[k])
|
|
349
|
+
) {
|
|
350
|
+
delete (val.defaultSize as any)[k]
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (val?.resizability) {
|
|
355
|
+
const keys = ['minWidth', 'minHeight', 'maxWidth', 'maxHeight'] as const
|
|
356
|
+
for (const k of keys) {
|
|
357
|
+
if (
|
|
358
|
+
k in (val.resizability as any) &&
|
|
359
|
+
!isValidSceneUnit((val.resizability as any)[k])
|
|
360
|
+
) {
|
|
361
|
+
delete (val.resizability as any)[k]
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return val
|
|
134
366
|
}
|
|
135
367
|
|
|
136
368
|
function pxToMeter(px: number): number {
|
|
137
|
-
return px
|
|
369
|
+
return pointToPhysical(px)
|
|
138
370
|
}
|
|
139
371
|
|
|
140
372
|
function meterToPx(meter: number): number {
|
|
141
|
-
return meter
|
|
373
|
+
return physicalToPoint(meter)
|
|
142
374
|
}
|
|
143
375
|
|
|
144
376
|
function formatToNumber(
|
|
@@ -215,12 +447,11 @@ export function formatSceneConfig(
|
|
|
215
447
|
;(config.defaultSize as any)[k] = formatToNumber(
|
|
216
448
|
(config.defaultSize as any)[k],
|
|
217
449
|
isWindow ? 'px' : 'm',
|
|
218
|
-
|
|
450
|
+
'px',
|
|
219
451
|
)
|
|
220
452
|
} else {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)[k]
|
|
453
|
+
// delete invalid unit
|
|
454
|
+
delete (config.defaultSize as any)[k]
|
|
224
455
|
errors.push(`defaultSize.${k}`)
|
|
225
456
|
}
|
|
226
457
|
}
|
|
@@ -235,10 +466,11 @@ export function formatSceneConfig(
|
|
|
235
466
|
;(config.resizability as any)[k] = formatToNumber(
|
|
236
467
|
(config.resizability as any)[k],
|
|
237
468
|
'px',
|
|
238
|
-
|
|
469
|
+
'px',
|
|
239
470
|
)
|
|
240
471
|
} else {
|
|
241
|
-
|
|
472
|
+
// delete invalid unit
|
|
473
|
+
delete (config.resizability as any)[k]
|
|
242
474
|
errors.push(`resizability.${k}`)
|
|
243
475
|
}
|
|
244
476
|
}
|
|
@@ -277,6 +509,13 @@ export function initScene(
|
|
|
277
509
|
return SceneManager.getInstance().initScene(name, callback, options)
|
|
278
510
|
}
|
|
279
511
|
|
|
512
|
+
export function __getSceneConfigSnapshotForTest(
|
|
513
|
+
name: string,
|
|
514
|
+
): SpatialSceneCreationOptionsInternal | undefined {
|
|
515
|
+
const mgr = SceneManager.getInstance() as any
|
|
516
|
+
return mgr?.configMap?.[name]
|
|
517
|
+
}
|
|
518
|
+
|
|
280
519
|
export function hijackWindowOpen(window: WindowProxy) {
|
|
281
520
|
SceneManager.getInstance().init(window)
|
|
282
521
|
}
|
|
@@ -325,7 +564,9 @@ function handleATag(event: MouseEvent) {
|
|
|
325
564
|
}
|
|
326
565
|
|
|
327
566
|
function getSceneDefaultConfig(sceneType: SpatialSceneType) {
|
|
328
|
-
return sceneType === 'window'
|
|
567
|
+
return sceneType === 'window'
|
|
568
|
+
? xr_window_defaults || defaultSceneConfig
|
|
569
|
+
: xr_volume_defaults || defaultSceneConfigVolume
|
|
329
570
|
}
|
|
330
571
|
|
|
331
572
|
async function injectScenePolyfill() {
|
|
@@ -348,13 +589,16 @@ async function injectScenePolyfill() {
|
|
|
348
589
|
}
|
|
349
590
|
|
|
350
591
|
onContentLoaded(async () => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
354
|
-
|
|
592
|
+
await SceneManager.getInstance().waitManifest()
|
|
593
|
+
const sceneType = window.xrCurrentSceneType ?? 'window'
|
|
594
|
+
const rawDefault = getSceneDefaultConfig(sceneType)
|
|
595
|
+
// Provide a formatted 'pre' to the callback for consistent units and types.
|
|
596
|
+
const pre = deepCloneJSON(rawDefault)
|
|
597
|
+
|
|
598
|
+
let cfg = pre
|
|
355
599
|
if (typeof window.xrCurrentSceneDefaults === 'function') {
|
|
356
600
|
try {
|
|
357
|
-
cfg = await window.xrCurrentSceneDefaults?.(
|
|
601
|
+
cfg = await window.xrCurrentSceneDefaults?.(pre)
|
|
358
602
|
} catch (error) {
|
|
359
603
|
console.error(error)
|
|
360
604
|
}
|
|
@@ -366,17 +610,19 @@ async function injectScenePolyfill() {
|
|
|
366
610
|
}, 1000)
|
|
367
611
|
})
|
|
368
612
|
|
|
369
|
-
|
|
370
|
-
const
|
|
613
|
+
// Merge callback return with base defaults to ensure missing fields are filled.
|
|
614
|
+
const mergedCfg = deepMergePlain(deepCloneJSON(rawDefault), cfg)
|
|
615
|
+
const [formattedConfig, errors] = formatSceneConfig(mergedCfg, sceneType)
|
|
371
616
|
if (errors.length > 0) {
|
|
372
617
|
console.warn(
|
|
373
618
|
`window.xrCurrentSceneDefaults with errors: ${errors.join(', ')}`,
|
|
374
619
|
)
|
|
375
620
|
}
|
|
376
|
-
|
|
621
|
+
const finalCfg = {
|
|
377
622
|
...formattedConfig,
|
|
378
623
|
type: sceneType,
|
|
379
|
-
}
|
|
624
|
+
}
|
|
625
|
+
await SpatialScene.getInstance().updateSceneCreationConfig(finalCfg)
|
|
380
626
|
})
|
|
381
627
|
}
|
|
382
628
|
|
package/src/types/global.d.ts
CHANGED
package/src/types/types.ts
CHANGED
|
@@ -95,10 +95,20 @@ export interface Spatialized2DElementProperties
|
|
|
95
95
|
scrollEdgeInsetsMarginRight: number
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
export interface ModelSource {
|
|
99
|
+
src: string
|
|
100
|
+
type?: string
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
export interface SpatializedStatic3DElementProperties
|
|
99
104
|
extends SpatializedElementProperties {
|
|
100
105
|
modelURL: string
|
|
106
|
+
sources?: ModelSource[]
|
|
101
107
|
modelTransform?: number[]
|
|
108
|
+
autoplay?: boolean
|
|
109
|
+
loop?: boolean
|
|
110
|
+
animationPaused?: boolean
|
|
111
|
+
playbackRate?: number
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
export interface SpatialSceneCreationOptions {
|
|
@@ -411,3 +421,64 @@ export interface AttachmentEntityUpdateOptions {
|
|
|
411
421
|
position?: [number, number, number]
|
|
412
422
|
size?: { width: number; height: number }
|
|
413
423
|
}
|
|
424
|
+
|
|
425
|
+
// manifest
|
|
426
|
+
|
|
427
|
+
export type SceneUnitPx = `${number}px`
|
|
428
|
+
export type SceneUnitM = `${number}m`
|
|
429
|
+
export type SceneUnit = number | SceneUnitPx | SceneUnitM
|
|
430
|
+
|
|
431
|
+
export interface XRSceneSize {
|
|
432
|
+
width: SceneUnit
|
|
433
|
+
height: SceneUnit
|
|
434
|
+
depth?: SceneUnit
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export interface XRSceneResizability {
|
|
438
|
+
minWidth?: SceneUnit
|
|
439
|
+
minHeight?: SceneUnit
|
|
440
|
+
maxWidth?: SceneUnit
|
|
441
|
+
maxHeight?: SceneUnit
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export interface XRMainSceneConfig extends XRSpatialSceneDefaults {
|
|
445
|
+
type?: SpatialSceneType
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export interface XRSpatialSceneDefaults {
|
|
449
|
+
default_size?: XRSceneSize
|
|
450
|
+
resizability?: XRSceneResizability
|
|
451
|
+
worldScaling?: WorldScalingType
|
|
452
|
+
worldAlignment?: WorldAlignmentType
|
|
453
|
+
baseplateVisibility?: BaseplateVisibilityType
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export interface XRSpatialSceneOverrides {
|
|
457
|
+
window_scene?: XRSpatialSceneDefaults
|
|
458
|
+
volume_scene?: XRSpatialSceneDefaults
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export interface XRSpatialSceneConfig extends XRSpatialSceneDefaults {
|
|
462
|
+
overrides?: XRSpatialSceneOverrides
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export interface XRPrdConfig {
|
|
466
|
+
xr_main_scene?: XRMainSceneConfig
|
|
467
|
+
xr_spatial_scene?: XRSpatialSceneConfig
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export interface PWAManifest extends XRPrdConfig {
|
|
471
|
+
name?: string
|
|
472
|
+
short_name?: string
|
|
473
|
+
start_url?: string
|
|
474
|
+
display?: string
|
|
475
|
+
icons?: Array<{
|
|
476
|
+
src: string
|
|
477
|
+
sizes?: string
|
|
478
|
+
type?: string
|
|
479
|
+
purpose?: string
|
|
480
|
+
}>
|
|
481
|
+
id?: string
|
|
482
|
+
scope?: string
|
|
483
|
+
[key: string]: any
|
|
484
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -59,3 +59,24 @@ export function composeSRT(position: Vec3, rotation: Vec3, scale: Vec3) {
|
|
|
59
59
|
m = m.scale(sx, sy, sz)
|
|
60
60
|
return m
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deep-clone a plain JSON-serializable object by value.
|
|
65
|
+
*
|
|
66
|
+
* Notes:
|
|
67
|
+
* - Only use for data composed of primitives, arrays, and plain objects.
|
|
68
|
+
* - Functions, Dates, Maps/Sets, DOM nodes, and circular structures are not supported.
|
|
69
|
+
*/
|
|
70
|
+
export function deepCloneJSON<T>(value: T): T {
|
|
71
|
+
const sc = (globalThis as any).structuredClone as
|
|
72
|
+
| ((v: any) => any)
|
|
73
|
+
| undefined
|
|
74
|
+
if (typeof sc === 'function') {
|
|
75
|
+
try {
|
|
76
|
+
return sc(value)
|
|
77
|
+
} catch {
|
|
78
|
+
// fall through to JSON method
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return JSON.parse(JSON.stringify(value))
|
|
82
|
+
}
|