@vgai/editor 0.4.0-canary.20260715.1 → 0.4.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.
Files changed (46) hide show
  1. package/dist/assets/{axe-CyiTVRi6.js → axe-BJjvBeOZ.js} +1 -1
  2. package/dist/assets/{browserAll-fGZ_P1P8.js → browserAll-Dn0pgD87.js} +1 -1
  3. package/dist/assets/{create-runtime-BzSHBTYw.js → create-runtime-BF9OD-U8.js} +1 -1
  4. package/dist/assets/{game-NUAnyPkS.js → game-B346oj_f.js} +1 -1
  5. package/dist/assets/{game-uR_lv3Mp.js → game-BCNN63uV.js} +1 -1
  6. package/dist/assets/{game-HqybI_X7.js → game-BR0GBNgF.js} +1 -1
  7. package/dist/assets/{game-DQ7YHNS8.js → game-CLNm2g0y.js} +1 -1
  8. package/dist/assets/{game-DX6Obomf.js → game-DQxhCYsZ.js} +1 -1
  9. package/dist/assets/{game-DoUJj8vY.js → game-DeTGdgYt.js} +1 -1
  10. package/dist/assets/{game-loop-o2CcBMFY.js → game-loop-t5W_oqxz.js} +1 -1
  11. package/dist/assets/{game-source-C8HM7gbP.js → game-source-DzE9taML.js} +1 -1
  12. package/dist/assets/{game-ck_dNIE6.js → game-u6c2Patu.js} +1 -1
  13. package/dist/assets/{game-hIf5LD4E.js → game-umrHIqFB.js} +1 -1
  14. package/dist/assets/{index-CoSRCKPS.js → index-CgcH1RpF.js} +1 -1
  15. package/dist/assets/{index-D93bBuUl.js → index-D3tcNB2T.js} +1 -1
  16. package/dist/assets/{index-B-HA1uu3.js → index-DbQzGLxv.js} +769 -769
  17. package/dist/assets/{index-CSBAZwMx.js → index-Fc8Ezr20.js} +1 -1
  18. package/dist/assets/index-bAGhEnnO.css +1 -0
  19. package/dist/assets/{ingest-siblings-CCkkE2h7.js → ingest-siblings-CW_xbvrF.js} +1 -1
  20. package/dist/assets/{mount-manifest-B2ECssU2.js → mount-manifest-DgUscyaD.js} +1 -1
  21. package/dist/assets/{react-18-Bmj05S8J.js → react-18-C4tVCii_.js} +1 -1
  22. package/dist/assets/webworkerAll-BA4aq7xq.js +1 -0
  23. package/dist/assets/{world-documents-DXPcAEA0.js → world-documents-BSaA5YIF.js} +1 -1
  24. package/dist/index.html +2 -2
  25. package/package.json +3 -3
  26. package/src/asset-editor-context.tsx +130 -0
  27. package/src/asset-preview.ts +4 -0
  28. package/src/asset-selection.ts +9 -4
  29. package/src/asset-workflow/thumbnail-system.ts +16 -7
  30. package/src/components/AssetBrowser.tsx +15 -7
  31. package/src/components/AssetEditorShell.tsx +42 -112
  32. package/src/components/AssetImportDetails.tsx +24 -6
  33. package/src/components/AssetLibraryPanel.tsx +1 -1
  34. package/src/components/GameHierarchy.tsx +26 -1
  35. package/src/components/Inspector.tsx +38 -2
  36. package/src/components/OnlineAssetBrowser.tsx +40 -409
  37. package/src/components/WorkspaceDock.tsx +6 -12
  38. package/src/components/asset-documents.tsx +41 -12
  39. package/src/components/asset-selection-section.tsx +34 -1
  40. package/src/components/asset-viewers/ModelViewer.tsx +7 -4
  41. package/src/components/asset-viewers/OnlineAssetDetail.tsx +233 -205
  42. package/src/components/asset-workflow.css +39 -220
  43. package/src/editor-api.ts +1 -1
  44. package/template/vgai.game.json +1 -1
  45. package/dist/assets/index-bu01fqch.css +0 -1
  46. package/dist/assets/webworkerAll-BC6gMOLZ.js +0 -1
@@ -12,14 +12,14 @@
12
12
  * open-asset`) keeps its meaning unchanged:
13
13
  *
14
14
  * - project asset → the serving path itself (`/textures/crate.png`)
15
- * - Asset Lab → `asset-lab:entity:<entityId>`
15
+ * - legacy entity editor key → `asset-lab:entity:<entityId>`
16
16
  * - online asset → `online:<source>:<id>`
17
17
  *
18
18
  * Titles are source-owned file names as authored (§8 — `crate.png`, never a
19
19
  * technology bucket); the online document keeps the catalog's own asset name.
20
20
  *
21
21
  * Per-document RUNTIME state (an online asset resolving to a local path after
22
- * download) lives in a module-scope spec map in the house
22
+ * download) lives in an HMR-stable module spec map in the house
23
23
  * `useSyncExternalStore` shape — the registry's descriptors stay immutable,
24
24
  * and `AssetDocumentContent` re-renders from the spec map. Specs are dropped
25
25
  * on document dispose (close / project teardown via
@@ -60,7 +60,7 @@ import './asset-workflow.css';
60
60
  export interface AssetDocumentSpec {
61
61
  readonly assetPath: string;
62
62
  readonly kind: AssetKind;
63
- /** Entity source for a session-only Asset Lab document. Never serialized. */
63
+ /** Entity source for a session-only live-object Asset Editor. Never serialized. */
64
64
  readonly entityId?: string;
65
65
  /** Present while this document shows an online (not-yet-downloaded) asset. */
66
66
  readonly online?: OnlineAssetInfo;
@@ -74,8 +74,20 @@ export interface AssetDocumentMutation {
74
74
  paths?: readonly string[];
75
75
  }
76
76
 
77
- const _specs = new Map<string, AssetDocumentSpec>();
78
- const _pathAliases = new Map<string, string>();
77
+ const hotData = import.meta.hot?.data;
78
+ const _specs =
79
+ (hotData?.['vgai:asset-document-specs'] as Map<string, AssetDocumentSpec> | undefined) ??
80
+ new Map<string, AssetDocumentSpec>();
81
+ const _pathAliases =
82
+ (hotData?.['vgai:asset-document-path-aliases'] as Map<string, string> | undefined) ??
83
+ new Map<string, string>();
84
+ if (hotData) {
85
+ // Workspace descriptors intentionally survive compatible Fast Refreshes.
86
+ // Their specs must survive with them or the next persistence snapshot drops
87
+ // the open document and leaves a titled-but-empty Dockview tab on reload.
88
+ hotData['vgai:asset-document-specs'] = _specs;
89
+ hotData['vgai:asset-document-path-aliases'] = _pathAliases;
90
+ }
79
91
  let _version = 0;
80
92
  const _listeners = new Set<() => void>();
81
93
 
@@ -113,7 +125,7 @@ export interface AssetDocumentStore {
113
125
  setActiveViewportTab(tab: 'scene' | 'game'): void;
114
126
  }
115
127
 
116
- /** The Asset Lab additionally needs the live object map for its §8 title. */
128
+ /** A live-entity Asset Editor additionally needs the object map for its §8 title. */
117
129
  export interface EntityAssetDocumentStore extends AssetDocumentStore {
118
130
  readonly objectMap: Map<string, THREE.Object3D>;
119
131
  }
@@ -159,7 +171,7 @@ export function openAssetDocument(
159
171
  return openSpecDocument(store, id, title, { assetPath, kind }, { sourcePath: assetPath });
160
172
  }
161
173
 
162
- /** Open the Asset Lab document for a live entity (`Open in Asset Lab`). */
174
+ /** Open the typed Asset Editor for a live entity (legacy id retained for API compatibility). */
163
175
  export function openEntityAssetDocument(store: EntityAssetDocumentStore, entityId: string): void {
164
176
  const object = store.objectMap.get(entityId);
165
177
  if (!object) return;
@@ -277,6 +289,8 @@ function AssetViewerBody({
277
289
  if (spec.unavailableReason) {
278
290
  return (
279
291
  <AssetEditorShell
292
+ documentId={id}
293
+ active={active}
280
294
  type={spec.kind}
281
295
  title={spec.assetPath.split('/').pop() || 'Deleted asset'}
282
296
  status="Source unavailable"
@@ -288,16 +302,29 @@ function AssetViewerBody({
288
302
  const route = assetDocumentViewerRoute(spec);
289
303
  if (route === 'online' && spec.online) {
290
304
  return (
291
- <OnlineAssetDetail
292
- online={spec.online}
293
- onResolved={(localPath) => resolveOnlineAssetDocument(id, localPath)}
294
- />
305
+ <AssetEditorShell
306
+ documentId={id}
307
+ active={active}
308
+ type="online"
309
+ title={spec.online.name}
310
+ details={
311
+ <OnlineAssetDetail
312
+ online={spec.online}
313
+ mode="inspector"
314
+ onResolved={(localPath) => resolveOnlineAssetDocument(id, localPath)}
315
+ />
316
+ }
317
+ status={`${spec.online.type} · ${spec.online.source}`}
318
+ >
319
+ <OnlineAssetDetail online={spec.online} mode="preview" />
320
+ </AssetEditorShell>
295
321
  );
296
322
  }
297
323
  const title = spec.assetPath.split('/').pop() || spec.entityId || 'Asset';
298
324
  if (route === 'model')
299
325
  return (
300
326
  <ModelViewer
327
+ documentId={id}
301
328
  assetPath={spec.assetPath || undefined}
302
329
  entityId={spec.entityId}
303
330
  active={active}
@@ -330,6 +357,8 @@ function AssetViewerBody({
330
357
  }
331
358
  return (
332
359
  <AssetEditorShell
360
+ documentId={id}
361
+ active={active}
333
362
  type={route}
334
363
  title={title}
335
364
  details={
@@ -369,7 +398,7 @@ export function AssetDocumentContent({ documentId, active }: WorkspaceDocumentCo
369
398
  style={{
370
399
  flex: 1,
371
400
  minHeight: 0,
372
- overflowY: 'auto',
401
+ overflow: 'hidden',
373
402
  pointerEvents: 'auto',
374
403
  background: bg[1],
375
404
  }}
@@ -22,13 +22,14 @@
22
22
  import { resolveUrl } from '@engine/loader';
23
23
  import { MaterialFileSchema } from '@engine/scene/schema/material';
24
24
  import { useEffect, useState } from 'react';
25
- import { getSelectedAsset, type SelectedAsset } from '../asset-selection';
25
+ import { getSelectedAsset, type SelectedAsset, setSelectedAsset } from '../asset-selection';
26
26
  import { assetDocumentKind } from '../asset-workflow/asset-capabilities';
27
27
  import { PROJECT_ASSET_COMMANDS } from '../asset-workflow/project-asset-commands';
28
28
  import { Button, bg, border, radius, text } from '../design-system';
29
29
  import { registerInspectorSections } from '../inspector-section-registry';
30
30
  import { getModelThumbnailRenderer } from '../model-thumbnail';
31
31
  import { validateInputMapForViewer } from './asset-viewers/InputMapViewer';
32
+ import { OnlineAssetDetail } from './asset-viewers/OnlineAssetDetail';
32
33
 
33
34
  const labelStyle: React.CSSProperties = { fontSize: 11, color: text[3], marginBottom: 2 };
34
35
  const valueStyle: React.CSSProperties = { fontSize: 11, color: text[1], wordBreak: 'break-all' };
@@ -227,6 +228,38 @@ export function AssetSelectionSection() {
227
228
  const asset = getSelectedAsset();
228
229
  if (!asset) return null;
229
230
  const ext = asset.name.includes('.') ? (asset.name.split('.').pop() ?? '') : '';
231
+ if (asset.online) {
232
+ const online = asset.online;
233
+ return (
234
+ <div
235
+ data-testid="inspector-asset-selection"
236
+ style={{
237
+ pointerEvents: 'auto',
238
+ borderBottom: `1px solid ${border[1]}`,
239
+ display: 'flex',
240
+ flexDirection: 'column',
241
+ gap: 6,
242
+ }}
243
+ >
244
+ <OnlineAssetDetail
245
+ online={online}
246
+ mode="inspector"
247
+ onResolved={(localPath) => setSelectedAsset({ ...asset, path: localPath })}
248
+ />
249
+ <div style={{ padding: '0 8px 8px' }}>
250
+ <Button
251
+ type="button"
252
+ style={{ width: '100%' }}
253
+ onClick={() =>
254
+ window.dispatchEvent(new CustomEvent('editor:open-online-asset', { detail: online }))
255
+ }
256
+ >
257
+ Open in Asset Editor
258
+ </Button>
259
+ </div>
260
+ </div>
261
+ );
262
+ }
230
263
  return (
231
264
  <div
232
265
  data-testid="inspector-asset-selection"
@@ -50,6 +50,7 @@ import { AssetImportDetails } from '../AssetImportDetails';
50
50
  import { writeProjectAsset } from '../asset-editor-persistence';
51
51
 
52
52
  interface ModelViewerProps {
53
+ documentId?: string | undefined;
53
54
  assetPath: string | undefined;
54
55
  entityId: string | undefined;
55
56
  materialPath?: string | undefined;
@@ -71,6 +72,7 @@ interface ModelViewerRuntime {
71
72
 
72
73
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one viewer coordinates embedded/full chrome and both live-entity and file-backed Asset Editor sources.
73
74
  export function ModelViewer({
75
+ documentId,
74
76
  assetPath,
75
77
  entityId,
76
78
  materialPath,
@@ -493,6 +495,8 @@ export function ModelViewer({
493
495
 
494
496
  return (
495
497
  <AssetEditorShell
498
+ documentId={documentId ?? `asset-editor:${assetPath ?? entityId ?? displayName}`}
499
+ active={active}
496
500
  type="model"
497
501
  title={displayName}
498
502
  toolbar={
@@ -663,7 +667,8 @@ export function ModelViewer({
663
667
  )}
664
668
  </div>
665
669
  }
666
- status={
670
+ status={`${loading ? `${loadStage}…` : error ? `${issue?.stage ?? 'rendering'} failed · ${issue?.code}` : 'Ready'} · ${inspection?.triangles.toLocaleString() ?? 0} triangles`}
671
+ controls={
667
672
  inspection?.clips ? (
668
673
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
669
674
  <Select
@@ -712,9 +717,7 @@ export function ModelViewer({
712
717
  </Select>
713
718
  </label>
714
719
  </div>
715
- ) : (
716
- `${loading ? `${loadStage}…` : error ? `${issue?.stage ?? 'rendering'} failed · ${issue?.code}` : 'Ready'} · ${inspection?.triangles.toLocaleString() ?? 0} triangles`
717
- )
720
+ ) : undefined
718
721
  }
719
722
  >
720
723
  {viewport}