@realsee/vr-cli 0.1.3

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.
Files changed (72) hide show
  1. package/NOTICE.md +7 -0
  2. package/README.md +216 -0
  3. package/TERMS.txt +54 -0
  4. package/agent-assets/AGENTS.md +113 -0
  5. package/agent-assets/adapters/.cursor/rules/realsee-vr.mdc +7 -0
  6. package/agent-assets/adapters/.github/copilot-instructions.md +1 -0
  7. package/agent-assets/adapters/.kiro/steering/realsee-vr.md +6 -0
  8. package/agent-assets/adapters/CLAUDE.md +3 -0
  9. package/bin/vr-cli.mjs +3 -0
  10. package/dist/cli.mjs +24 -0
  11. package/package.json +38 -0
  12. package/skills/realsee-image-resources/SKILL.md +82 -0
  13. package/skills/realsee-image-resources/references/cors-and-three-textures.md +50 -0
  14. package/skills/realsee-image-resources/references/data-wanxiang.md +57 -0
  15. package/skills/realsee-image-resources/references/five-image-url-transform.md +81 -0
  16. package/skills/realsee-image-resources/references/source-and-domain-policy.md +63 -0
  17. package/skills/realsee-vr-app/SKILL.md +157 -0
  18. package/skills/realsee-vr-app/references/continuous-improvement.md +24 -0
  19. package/skills/realsee-vr-app/references/dnalogel-api-capabilities.md +241 -0
  20. package/skills/realsee-vr-app/references/dnalogel-openapi-recipes.md +818 -0
  21. package/skills/realsee-vr-app/references/five-api-capabilities.md +119 -0
  22. package/skills/realsee-vr-app/references/five-runtime.md +83 -0
  23. package/skills/realsee-vr-app/references/official-docs.md +61 -0
  24. package/skills/realsee-vr-app/references/plugin-lifecycle.md +65 -0
  25. package/skills/realsee-vr-app/references/ui-and-validation.md +57 -0
  26. package/skills/realsee-vr-app/references/vapor-dom-layer.md +186 -0
  27. package/skills/realsee-vr-app/scripts/verify-project.mjs +1566 -0
  28. package/skills/threejs-r117-five/SKILL.md +71 -0
  29. package/skills/threejs-r117-five/references/five-ownership.md +70 -0
  30. package/skills/threejs-r117-five/references/r117-capabilities.md +82 -0
  31. package/skills/threejs-r117-five/references/r117-version-guards.md +60 -0
  32. package/templates/base/.editorconfig +12 -0
  33. package/templates/base/.env.example +12 -0
  34. package/templates/base/.gitignore.template +6 -0
  35. package/templates/base/.node-version +1 -0
  36. package/templates/base/.npmrc.template +3 -0
  37. package/templates/base/.nvmrc +1 -0
  38. package/templates/base/.prettierignore +12 -0
  39. package/templates/base/README.md +160 -0
  40. package/templates/base/eslint.config.mjs +75 -0
  41. package/templates/base/index.html +13 -0
  42. package/templates/base/package-lock.json +6444 -0
  43. package/templates/base/package.json +55 -0
  44. package/templates/base/prettier.config.mjs +5 -0
  45. package/templates/base/server/app.ts +54 -0
  46. package/templates/base/server/check-config.ts +4 -0
  47. package/templates/base/server/config.ts +54 -0
  48. package/templates/base/server/index.ts +31 -0
  49. package/templates/base/server/realsee-open-api.ts +316 -0
  50. package/templates/base/server/types.ts +64 -0
  51. package/templates/base/server/work-route.ts +80 -0
  52. package/templates/base/server/work-service.ts +54 -0
  53. package/templates/base/shared/model-floorplan.ts +242 -0
  54. package/templates/base/src/main.tsx +11 -0
  55. package/templates/base/src/shared/useElementSize.ts +28 -0
  56. package/templates/base/src/styles.css +91 -0
  57. package/templates/base/src/vr/five/SpatialRuntimeProvider.tsx +234 -0
  58. package/templates/base/src/vr/five/api.ts +10 -0
  59. package/templates/base/src/vr/five/fiveConfig.ts +42 -0
  60. package/templates/base/src/vr/five/initialState.ts +48 -0
  61. package/templates/base/src/vr/plugins/SpatialPluginRegistry.ts +104 -0
  62. package/templates/base/src/vr/plugins/dnalogel.ts +2 -0
  63. package/templates/base/src/vr/plugins/examples/FeatureOwnedMeshPlugin.ts +73 -0
  64. package/templates/base/src/vr/work/WorkSource.ts +247 -0
  65. package/templates/base/tsconfig.json +27 -0
  66. package/templates/base/tsconfig.server.json +25 -0
  67. package/templates/base/vite.config.ts +21 -0
  68. package/templates/viewer/src/app/App.tsx +182 -0
  69. package/templates/viewer/src/features/floorplan/ModelFloorplanFeature.ts +141 -0
  70. package/templates/viewer/src/features/navigation/ModeSwitcher.tsx +111 -0
  71. package/templates/viewer/src/features/tags/PanoTagFeature.ts +110 -0
  72. package/templates/viewer/src/ui/ViewerOverlay.tsx +141 -0
@@ -0,0 +1,42 @@
1
+ import type { FiveInitArgs } from '@realsee/five'
2
+ import { BackgroundPlugin, PanoAnimePlugin } from '@realsee/five/plugins'
3
+
4
+ function supportsWebGL2() {
5
+ const match = navigator.userAgent.match(/OS (\d+)_/i)
6
+ const iosVersion = match ? Number(match[1]) : 0
7
+ return !iosVersion || iosVersion >= 16
8
+ }
9
+
10
+ export function createFiveInitArgs(): FiveInitArgs {
11
+ return {
12
+ panorama: {
13
+ maxLatitude: Math.PI / 4,
14
+ minLatitude: -Math.PI / 4,
15
+ minFov: 40,
16
+ maxFov: 120,
17
+ adjustPanoCircleMeshPositionBasedOnModel: true,
18
+ },
19
+ floorplan: {
20
+ minLatitude: 0,
21
+ maxLatitude: Math.PI / 2,
22
+ defaultFov: 80,
23
+ },
24
+ imageOptions: {
25
+ size: 512,
26
+ format: 'avif',
27
+ },
28
+ textureOptions: {
29
+ quality: 95,
30
+ format: 'avif',
31
+ autoResize: true,
32
+ },
33
+ plugins: [
34
+ [BackgroundPlugin, 'BackgroundPlugin', {}],
35
+ [PanoAnimePlugin, 'PanoAnimePlugin', {}],
36
+ ],
37
+ backgroundAlpha: 0,
38
+ onlyRenderIfNeeds: true,
39
+ poweredByRealsee: false,
40
+ webgl2: supportsWebGL2(),
41
+ }
42
+ }
@@ -0,0 +1,48 @@
1
+ import { Five, type State } from '@realsee/five'
2
+ import * as THREE from 'three'
3
+ import type { RawRecord } from '../work/WorkSource'
4
+
5
+ const asRecord = (value: unknown): RawRecord =>
6
+ value && typeof value === 'object' && !Array.isArray(value)
7
+ ? (value as RawRecord)
8
+ : {}
9
+
10
+ const finiteNumber = (...values: unknown[]) => {
11
+ for (const value of values)
12
+ if (typeof value === 'number' && Number.isFinite(value)) return value
13
+ return undefined
14
+ }
15
+
16
+ export function initialStateFrom(
17
+ work: RawRecord,
18
+ override?: Partial<State>,
19
+ ): Partial<State> {
20
+ const initial = asRecord(work.initial)
21
+ const offset = asRecord(initial.offset)
22
+ const state: Partial<State> = {
23
+ mode:
24
+ typeof initial.mode === 'string'
25
+ ? (initial.mode as State['mode'])
26
+ : Five.Mode.Panorama,
27
+ }
28
+ const panoIndex = finiteNumber(
29
+ initial.panoIndex,
30
+ initial.pano_index,
31
+ initial.pano,
32
+ )
33
+ const longitude = finiteNumber(initial.longitude)
34
+ const latitude = finiteNumber(initial.latitude)
35
+ const fov = finiteNumber(initial.fov)
36
+ const distance = finiteNumber(initial.distance)
37
+ if (panoIndex !== undefined) state.panoIndex = panoIndex
38
+ if (longitude !== undefined) state.longitude = longitude
39
+ if (latitude !== undefined) state.latitude = latitude
40
+ if (fov !== undefined) state.fov = fov
41
+ if (distance !== undefined) state.distance = distance
42
+ const x = finiteNumber(offset.x)
43
+ const y = finiteNumber(offset.y)
44
+ const z = finiteNumber(offset.z)
45
+ if (x !== undefined && y !== undefined && z !== undefined)
46
+ state.offset = new THREE.Vector3(x, y, z)
47
+ return { ...state, ...override }
48
+ }
@@ -0,0 +1,104 @@
1
+ import type { Five } from '@realsee/five'
2
+
3
+ export type SpatialPluginFactory<Parameters extends unknown[], Plugin> = (
4
+ five: Five,
5
+ ...parameters: Parameters
6
+ ) => Plugin
7
+
8
+ type DisposablePlugin = {
9
+ dispose?: () => unknown
10
+ }
11
+
12
+ const disposePlugin = (plugin: unknown) => {
13
+ if (!plugin || typeof plugin !== 'object') return
14
+ const dispose = (plugin as DisposablePlugin).dispose
15
+ if (typeof dispose === 'function') dispose.call(plugin)
16
+ }
17
+
18
+ /**
19
+ * Owns dynamically created spatial plugins for one Five instance.
20
+ *
21
+ * dnalogel presets and application-defined Five plugins do not share one
22
+ * universal lifecycle interface. Feature adapters remain responsible for
23
+ * plugin-specific hooks, media and load/enable/disable methods; the registry
24
+ * guarantees conflict-safe ownership and best-effort drain.
25
+ */
26
+ export class SpatialPluginRegistry {
27
+ private readonly owned = new Map<string, unknown>()
28
+
29
+ constructor(readonly five: Five) {}
30
+
31
+ create<Parameters extends unknown[], Plugin>(
32
+ name: string,
33
+ factory: SpatialPluginFactory<Parameters, Plugin>,
34
+ ...parameters: Parameters
35
+ ) {
36
+ const plugin = factory(this.five, ...parameters)
37
+ try {
38
+ return this.register(name, plugin)
39
+ } catch (error) {
40
+ try {
41
+ disposePlugin(plugin)
42
+ } catch (disposeError) {
43
+ console.error(
44
+ `注册空间插件 ${name} 失败后清理候选实例失败。`,
45
+ disposeError,
46
+ )
47
+ }
48
+ throw error
49
+ }
50
+ }
51
+
52
+ register<Plugin>(name: string, plugin: Plugin) {
53
+ const current = this.owned.get(name)
54
+ const registered: unknown = this.five.plugins[name]
55
+ const hasRegisteredPlugin = Object.prototype.hasOwnProperty.call(
56
+ this.five.plugins,
57
+ name,
58
+ )
59
+ if (
60
+ hasRegisteredPlugin &&
61
+ registered !== current &&
62
+ registered !== plugin
63
+ ) {
64
+ throw new Error(
65
+ `空间插件名称冲突:${name} 已由当前 registry 之外的实例占用。`,
66
+ )
67
+ }
68
+
69
+ if (current && current !== plugin) {
70
+ throw new Error(
71
+ `空间插件名称冲突:${name} 已由当前 registry 中的活动实例占用。`,
72
+ )
73
+ }
74
+ this.five.plugins[name] = plugin
75
+ this.owned.set(name, plugin)
76
+ return plugin
77
+ }
78
+
79
+ get<Plugin>(name: string) {
80
+ return this.five.plugins[name] as Plugin | undefined
81
+ }
82
+
83
+ unregister(name: string, expected?: unknown) {
84
+ const plugin = this.owned.get(name)
85
+ if (!plugin || (expected !== undefined && plugin !== expected)) return
86
+ this.owned.delete(name)
87
+ if (this.five.plugins[name] === plugin) delete this.five.plugins[name]
88
+ try {
89
+ disposePlugin(plugin)
90
+ return undefined
91
+ } catch (error) {
92
+ return error
93
+ }
94
+ }
95
+
96
+ disposeAll() {
97
+ const errors: unknown[] = []
98
+ for (const [name, plugin] of [...this.owned.entries()].reverse()) {
99
+ const error = this.unregister(name, plugin)
100
+ if (error) errors.push(error)
101
+ }
102
+ return errors
103
+ }
104
+ }
@@ -0,0 +1,2 @@
1
+ // Version-bound dnalogel preset surface. Custom Five plugins live beside this file under src/vr/plugins.
2
+ export * from '@realsee/dnalogel'
@@ -0,0 +1,73 @@
1
+ import type { Five } from '@realsee/five'
2
+ import { BoxBufferGeometry, Group, Mesh, MeshBasicMaterial } from 'three'
3
+ import type { SpatialPluginRegistry } from '../SpatialPluginRegistry'
4
+
5
+ // Opt-in reference for Agent-generated Five/Three features. The default App does not import or register it.
6
+ export const FEATURE_OWNED_MESH_PLUGIN_NAME = 'example.featureOwnedMesh'
7
+
8
+ export interface FeatureOwnedMeshPluginOptions {
9
+ size?: number
10
+ color?: string | number
11
+ position?: readonly [number, number, number]
12
+ }
13
+
14
+ export interface FeatureOwnedMeshPlugin {
15
+ readonly root: Group
16
+ readonly mesh: Mesh<BoxBufferGeometry, MeshBasicMaterial>
17
+ setVisible(visible: boolean): void
18
+ dispose(): void
19
+ }
20
+
21
+ export function createFeatureOwnedMeshPlugin(
22
+ five: Five,
23
+ options: FeatureOwnedMeshPluginOptions = {},
24
+ ): FeatureOwnedMeshPlugin {
25
+ const size = options.size ?? 0.2
26
+ if (!Number.isFinite(size) || size <= 0)
27
+ throw new Error('自定义 Mesh 插件的 size 必须是正数。')
28
+
29
+ const root = new Group()
30
+ root.name = FEATURE_OWNED_MESH_PLUGIN_NAME
31
+ const geometry = new BoxBufferGeometry(size, size, size)
32
+ const material = new MeshBasicMaterial({ color: options.color ?? 0x4f8cff })
33
+ const mesh = new Mesh(geometry, material)
34
+ root.add(mesh)
35
+
36
+ const [x, y, z] = options.position ?? [0, 0, 0]
37
+ root.position.set(x, y, z)
38
+ five.scene.add(root)
39
+ five.needsRender = true
40
+
41
+ let disposed = false
42
+ return {
43
+ root,
44
+ mesh,
45
+ setVisible(visible) {
46
+ if (disposed) return
47
+ root.visible = visible
48
+ five.needsRender = true
49
+ },
50
+ dispose() {
51
+ if (disposed) return
52
+ disposed = true
53
+ root.parent?.remove(root)
54
+ geometry.dispose()
55
+ material.dispose()
56
+ five.needsRender = true
57
+ },
58
+ }
59
+ }
60
+
61
+ export function registerFeatureOwnedMeshPlugin(
62
+ plugins: SpatialPluginRegistry,
63
+ options: FeatureOwnedMeshPluginOptions = {},
64
+ ) {
65
+ const plugin = createFeatureOwnedMeshPlugin(plugins.five, options)
66
+ try {
67
+ return plugins.register(FEATURE_OWNED_MESH_PLUGIN_NAME, plugin)
68
+ } catch (error) {
69
+ // Handles both external name collisions and registry-owned replacement rejection.
70
+ plugin.dispose()
71
+ throw error
72
+ }
73
+ }
@@ -0,0 +1,247 @@
1
+ import type { State } from '@realsee/five'
2
+ import type { Tags } from '@realsee/dnalogel/libs/PanoTagPlugin'
3
+ import {
4
+ asModelFloorplanServerData,
5
+ type ModelFloorplanServerData,
6
+ } from '../../../shared/model-floorplan'
7
+
8
+ export type RawRecord = Record<string, unknown>
9
+
10
+ export type WorkPresetData = {
11
+ panoTagList?: Tags['tagList']
12
+ }
13
+
14
+ export type WorkCapabilities = {
15
+ modelFloorplan?: ModelFloorplanServerData
16
+ }
17
+
18
+ export type WorkPayload = {
19
+ work: RawRecord
20
+ workCode: string
21
+ displayName: string
22
+ baseURL?: string
23
+ initialState?: Partial<State>
24
+ presetData?: WorkPresetData
25
+ capabilities?: WorkCapabilities
26
+ }
27
+
28
+ const RESOURCE_CODE_PATTERN = /^[A-Za-z0-9_-]{1,128}$/
29
+
30
+ const isRecord = (value: unknown): value is RawRecord =>
31
+ Boolean(value && typeof value === 'object' && !Array.isArray(value))
32
+
33
+ const asRecord = (value: unknown): RawRecord => (isRecord(value) ? value : {})
34
+
35
+ const firstString = (...values: unknown[]) =>
36
+ values.find(
37
+ (value): value is string => typeof value === 'string' && value.length > 0,
38
+ )
39
+
40
+ const displayNameFrom = (value: unknown): string | undefined => {
41
+ if (typeof value !== 'string') return undefined
42
+ const displayName = value.trim()
43
+ return displayName || undefined
44
+ }
45
+
46
+ const PANO_TAG_CONTENT_TYPES = new Set([
47
+ 'Text',
48
+ 'Audio',
49
+ 'ImageText',
50
+ 'Image',
51
+ 'Video',
52
+ 'Panorama',
53
+ 'Link',
54
+ 'VRLink',
55
+ 'Sticker',
56
+ 'PanoLink',
57
+ 'Marketing',
58
+ 'MediaPlane',
59
+ 'MediaModel',
60
+ 'Model',
61
+ 'Custom',
62
+ 'Unknown',
63
+ ])
64
+
65
+ const isNonEmptyString = (value: unknown): value is string =>
66
+ typeof value === 'string' && value.length > 0
67
+
68
+ const isNumberArray = (value: unknown, minimumLength = 1): value is number[] =>
69
+ Array.isArray(value) &&
70
+ value.length >= minimumLength &&
71
+ value.every((item) => typeof item === 'number' && Number.isFinite(item))
72
+
73
+ const isPosition = (value: unknown): value is number[] =>
74
+ isNumberArray(value, 3)
75
+
76
+ const isPositionList = (value: unknown, length: number, exact = false) =>
77
+ Array.isArray(value) &&
78
+ (exact ? value.length === length : value.length >= length) &&
79
+ value.every(isPosition)
80
+
81
+ const isMediaDataList = (value: unknown) =>
82
+ Array.isArray(value) &&
83
+ value.every((item) => {
84
+ if (!isRecord(item) || !isNonEmptyString(item.url)) return false
85
+ return item.type === 'Image' || item.type === 'Video'
86
+ })
87
+
88
+ const hasCanonicalPanoTagData = (contentType: string, data: RawRecord) => {
89
+ switch (contentType) {
90
+ case 'Audio':
91
+ return isNonEmptyString(data.audioUrl)
92
+ case 'ImageText':
93
+ case 'Image':
94
+ case 'Video':
95
+ case 'Panorama':
96
+ return isMediaDataList(data.mediaData)
97
+ case 'Marketing':
98
+ return isNonEmptyString(data.title)
99
+ case 'MediaPlane':
100
+ return isMediaDataList(data.mediaData)
101
+ case 'MediaModel':
102
+ return (
103
+ isNonEmptyString(data.modelUrl) &&
104
+ isMediaDataList(data.mediaData) &&
105
+ isPositionList(data.mediaPosition, 4, true)
106
+ )
107
+ case 'Model':
108
+ return isNonEmptyString(data.modelUrl)
109
+ default:
110
+ return true
111
+ }
112
+ }
113
+
114
+ const hasCanonicalPanoTagAnchor = (tag: RawRecord) => {
115
+ const stickType = tag.stickType ?? '2DPoint'
116
+ switch (stickType) {
117
+ case '2DPoint':
118
+ return isPosition(tag.position)
119
+ case '3DPoint':
120
+ return isPosition(tag.position) && isPosition(tag.normal)
121
+ case 'Plane':
122
+ return isPositionList(tag.position, 4, true)
123
+ case 'Model':
124
+ return isPosition(tag.position) && isNumberArray(tag.matrix, 16)
125
+ case '3DBox': {
126
+ if (!isRecord(tag.position)) return false
127
+ return (
128
+ isPosition(tag.position.start) &&
129
+ isPosition(tag.position.end) &&
130
+ isPosition(tag.position.rotation)
131
+ )
132
+ }
133
+ case 'Polygon':
134
+ return isPositionList(tag.position, 3)
135
+ case 'Mask':
136
+ return (
137
+ isPosition(tag.position) &&
138
+ isNonEmptyString(tag.mask) &&
139
+ (typeof tag.color === 'string' ||
140
+ typeof tag.color === 'number' ||
141
+ isNumberArray(tag.color, 3))
142
+ )
143
+ default:
144
+ return false
145
+ }
146
+ }
147
+
148
+ const hasExplicitPanoTagShape = (
149
+ value: unknown,
150
+ ): value is Tags['tagList'][number] => {
151
+ if (
152
+ !isRecord(value) ||
153
+ !isNonEmptyString(value.contentType) ||
154
+ !isRecord(value.data)
155
+ )
156
+ return false
157
+ return (
158
+ PANO_TAG_CONTENT_TYPES.has(value.contentType) &&
159
+ hasCanonicalPanoTagData(value.contentType, value.data) &&
160
+ hasCanonicalPanoTagAnchor(value)
161
+ )
162
+ }
163
+
164
+ const asExplicitPanoTagList = (value: unknown): Tags['tagList'] | undefined => {
165
+ if (value === undefined) return undefined
166
+ if (!Array.isArray(value))
167
+ throw new Error('presetData.panoTagList 必须是数组。')
168
+ return value.map((item, index) => {
169
+ if (!hasExplicitPanoTagShape(item)) {
170
+ throw new Error(
171
+ `presetData.panoTagList[${index}] 不符合 canonical content/data/stickType anchor 契约。`,
172
+ )
173
+ }
174
+ return item
175
+ })
176
+ }
177
+
178
+ export function resourceCodeFromPathname(pathname: string) {
179
+ const segments = pathname.split('/').filter(Boolean)
180
+ if (segments.length !== 1) throw new Error('访问路径必须为 /{resourceCode}。')
181
+ const [encodedResourceCode = ''] = segments
182
+
183
+ let resourceCode: string
184
+ try {
185
+ resourceCode = decodeURIComponent(encodedResourceCode)
186
+ } catch {
187
+ throw new Error('resourceCode 不是有效的 URL 路径参数。')
188
+ }
189
+ if (!RESOURCE_CODE_PATTERN.test(resourceCode))
190
+ throw new Error('resourceCode 格式无效。')
191
+ return resourceCode
192
+ }
193
+
194
+ export interface WorkSource {
195
+ load(signal?: AbortSignal): Promise<WorkPayload>
196
+ }
197
+
198
+ export class RouteWorkSource implements WorkSource {
199
+ async load(signal?: AbortSignal): Promise<WorkPayload> {
200
+ const resourceCode = resourceCodeFromPathname(window.location.pathname)
201
+ // The generated Fastify BFF implements this contract; another language may replace it without changing the browser.
202
+ const response = await fetch(
203
+ `/api/work/${encodeURIComponent(resourceCode)}`,
204
+ {
205
+ signal,
206
+ headers: { Accept: 'application/json' },
207
+ },
208
+ )
209
+ if (!response.ok) throw new Error(`Work 请求失败:HTTP ${response.status}`)
210
+
211
+ const raw = (await response.json()) as unknown
212
+ const envelope = asRecord(raw)
213
+ const nestedWork = asRecord(envelope.work)
214
+ const work = Object.keys(nestedWork).length ? nestedWork : envelope
215
+ if (!Object.keys(work).length) throw new Error('Work 响应不是有效对象。')
216
+
217
+ const workCode =
218
+ firstString(
219
+ envelope.workCode,
220
+ envelope.work_code,
221
+ work.workCode,
222
+ work.work_code,
223
+ work.code,
224
+ ) ?? resourceCode
225
+ const presetData = asRecord(envelope.presetData)
226
+ const panoTagList = asExplicitPanoTagList(presetData.panoTagList)
227
+ const modelFloorplan = asModelFloorplanServerData(
228
+ asRecord(envelope.capabilities).modelFloorplan,
229
+ )
230
+ return {
231
+ work,
232
+ workCode,
233
+ displayName: displayNameFrom(work.name) ?? workCode,
234
+ baseURL: firstString(
235
+ envelope.baseURL,
236
+ envelope.base_url,
237
+ work.baseURL,
238
+ work.base_url,
239
+ ),
240
+ initialState: asRecord(envelope.initialState),
241
+ ...(panoTagList !== undefined ? { presetData: { panoTagList } } : {}),
242
+ ...(modelFloorplan ? { capabilities: { modelFloorplan } } : {}),
243
+ }
244
+ }
245
+ }
246
+
247
+ export const workSource: WorkSource = new RouteWorkSource()
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noImplicitReturns": true,
20
+ "noImplicitOverride": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedIndexedAccess": true,
23
+ "jsx": "react-jsx",
24
+ "types": ["vite/client"]
25
+ },
26
+ "include": ["src", "shared", "vite.config.ts"]
27
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "lib": ["ES2023"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "types": ["node"],
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "noImplicitReturns": true,
13
+ "noImplicitOverride": true,
14
+ "noFallthroughCasesInSwitch": true,
15
+ "noUncheckedIndexedAccess": true,
16
+ "exactOptionalPropertyTypes": true,
17
+ "isolatedModules": true,
18
+ "verbatimModuleSyntax": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "skipLibCheck": true,
21
+ "allowImportingTsExtensions": true,
22
+ "erasableSyntaxOnly": true
23
+ },
24
+ "include": ["server", "shared"]
25
+ }
@@ -0,0 +1,21 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ host: '127.0.0.1',
8
+ port: 8087,
9
+ strictPort: true,
10
+ proxy: {
11
+ '/api': {
12
+ target: 'http://127.0.0.1:8088',
13
+ },
14
+ },
15
+ },
16
+ preview: {
17
+ host: '127.0.0.1',
18
+ port: 8087,
19
+ strictPort: true,
20
+ },
21
+ })