@vgai/editor 0.4.0-canary.20260715.0 → 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 (47) hide show
  1. package/dist/assets/{axe-C7YBtOuM.js → axe-BJjvBeOZ.js} +1 -1
  2. package/dist/assets/{browserAll-DDtGGE48.js → browserAll-Dn0pgD87.js} +1 -1
  3. package/dist/assets/{create-runtime-DbTlBlKG.js → create-runtime-BF9OD-U8.js} +1 -1
  4. package/dist/assets/{game-Dch9sAPc.js → game-B346oj_f.js} +1 -1
  5. package/dist/assets/{game-BE0r7DMI.js → game-BCNN63uV.js} +1 -1
  6. package/dist/assets/{game-SAFn90pA.js → game-BR0GBNgF.js} +1 -1
  7. package/dist/assets/{game-Cqzj_lpD.js → game-CLNm2g0y.js} +1 -1
  8. package/dist/assets/{game-CcHfYxjP.js → game-DQxhCYsZ.js} +1 -1
  9. package/dist/assets/{game-BdTHR2_X.js → game-DeTGdgYt.js} +1 -1
  10. package/dist/assets/{game-loop-DMIgSMV-.js → game-loop-t5W_oqxz.js} +1 -1
  11. package/dist/assets/{game-source-Dk83w26t.js → game-source-DzE9taML.js} +1 -1
  12. package/dist/assets/{game-DSZdeCHu.js → game-u6c2Patu.js} +1 -1
  13. package/dist/assets/{game-C3iQGSqo.js → game-umrHIqFB.js} +1 -1
  14. package/dist/assets/{index-C-LhOTBN.js → index-CgcH1RpF.js} +1 -1
  15. package/dist/assets/{index-CDkxceqE.js → index-D3tcNB2T.js} +1 -1
  16. package/dist/assets/{index-CQEfi217.js → index-DbQzGLxv.js} +771 -771
  17. package/dist/assets/{index-4idIsHHJ.js → index-Fc8Ezr20.js} +1 -1
  18. package/dist/assets/index-bAGhEnnO.css +1 -0
  19. package/dist/assets/{ingest-siblings-s0P5I1Ub.js → ingest-siblings-CW_xbvrF.js} +1 -1
  20. package/dist/assets/{mount-manifest-DXMOlF79.js → mount-manifest-DgUscyaD.js} +1 -1
  21. package/dist/assets/{react-18-C9eHn8x1.js → react-18-C4tVCii_.js} +1 -1
  22. package/dist/assets/webworkerAll-BA4aq7xq.js +1 -0
  23. package/dist/assets/{world-documents-GM0a9Kv7.js → world-documents-BSaA5YIF.js} +1 -1
  24. package/dist/index.html +2 -2
  25. package/dist-server/packaged.mjs +21 -1
  26. package/package.json +3 -3
  27. package/server/packaged.ts +20 -0
  28. package/src/asset-editor-context.tsx +130 -0
  29. package/src/asset-preview.ts +4 -0
  30. package/src/asset-selection.ts +9 -4
  31. package/src/asset-workflow/thumbnail-system.ts +16 -7
  32. package/src/components/AssetBrowser.tsx +330 -539
  33. package/src/components/AssetEditorShell.tsx +42 -112
  34. package/src/components/AssetImportDetails.tsx +24 -6
  35. package/src/components/GameHierarchy.tsx +26 -1
  36. package/src/components/Inspector.tsx +38 -2
  37. package/src/components/OnlineAssetBrowser.tsx +295 -343
  38. package/src/components/WorkspaceDock.tsx +6 -12
  39. package/src/components/asset-documents.tsx +41 -12
  40. package/src/components/asset-selection-section.tsx +34 -1
  41. package/src/components/asset-viewers/ModelViewer.tsx +7 -4
  42. package/src/components/asset-viewers/OnlineAssetDetail.tsx +233 -205
  43. package/src/components/asset-workflow.css +197 -264
  44. package/src/editor-api.ts +1 -1
  45. package/template/vgai.game.json +1 -1
  46. package/dist/assets/index-Dvi-V0NE.css +0 -1
  47. package/dist/assets/webworkerAll-Fn0e-ouF.js +0 -1
@@ -466,6 +466,26 @@ async function main(): Promise<void> {
466
466
  'react-dom/client',
467
467
  'react/jsx-runtime',
468
468
  'react/jsx-dev-runtime',
469
+ // A scaffold's project-authored client modules are loaded lazily by
470
+ // the prebuilt editor. They are outside the manifest-world entry
471
+ // crawl above, so a cold install otherwise discovers these aliases in
472
+ // waves (project tools during editor boot, GameComponent on first
473
+ // Play) and Vite hard-reloads the controlling tab each time. Keep the
474
+ // starter's runtime imports in the initial prebundle so the first SDK
475
+ // command is not disconnected by dependency optimization.
476
+ '@editor/widgets',
477
+ '@engine/data/data-asset',
478
+ '@engine/ecs/game-component',
479
+ '@engine/input/input-manager',
480
+ '@engine/input/rebind-controller',
481
+ '@engine/loader',
482
+ '@engine/react/use-data',
483
+ '@engine/runtime/mount-game',
484
+ '@engine/scene/component-registry',
485
+ '@engine/scene/parse',
486
+ '@engine/tools/define-tool',
487
+ '@engine/world2d',
488
+ 'zod',
469
489
  ],
470
490
  entries: projectOptimizeDepsEntries,
471
491
  // C3 hardening: belt to the entries-scoping suspenders. Entries-scoping
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Context contributed by the active typed Asset Editor to the existing editor
3
+ * shell. Asset documents own content; the workspace owns placement. Keeping
4
+ * this as a tiny external store lets Dockview's persistent Hierarchy,
5
+ * Inspector, bottom drawer, and status bar render the active document's
6
+ * context without an Asset Editor recreating those panes inside the center.
7
+ */
8
+
9
+ import type { ReactNode } from 'react';
10
+ import { useSyncExternalStore } from 'react';
11
+ import { themeVars } from './design-system';
12
+ import { createHmrRegistrationGroup } from './hmr-registration-group';
13
+ import { registerWorkspaceStatus } from './workspace-status-registry';
14
+ import { registerWorkspaceUtility } from './workspace-utility-registry';
15
+
16
+ export const ASSET_EDITOR_CONTROLS_UTILITY_ID = 'asset-editor-controls';
17
+
18
+ export interface AssetEditorContextValue {
19
+ readonly documentId: string;
20
+ readonly title: string;
21
+ readonly type: string;
22
+ readonly structure?: ReactNode;
23
+ readonly details?: ReactNode;
24
+ readonly status?: string;
25
+ readonly controls?: ReactNode;
26
+ }
27
+
28
+ interface ActiveAssetEditorContext extends AssetEditorContextValue {
29
+ readonly token: symbol;
30
+ }
31
+
32
+ let active: ActiveAssetEditorContext | null = null;
33
+ let version = 0;
34
+ const listeners = new Set<() => void>();
35
+
36
+ function notifyChanged(): void {
37
+ version++;
38
+ for (const listener of listeners) listener();
39
+ }
40
+
41
+ export function subscribeAssetEditorContext(listener: () => void): () => void {
42
+ listeners.add(listener);
43
+ return () => listeners.delete(listener);
44
+ }
45
+
46
+ export function assetEditorContextVersion(): number {
47
+ return version;
48
+ }
49
+
50
+ export function getActiveAssetEditorContext(): AssetEditorContextValue | null {
51
+ return active;
52
+ }
53
+
54
+ /** Activate one document contribution. The returned disposer only clears the
55
+ * exact activation it created, so an older tab unmount cannot erase a newer
56
+ * active tab's context. */
57
+ export function activateAssetEditorContext(value: AssetEditorContextValue): () => void {
58
+ const token = Symbol(value.documentId);
59
+ active = { ...value, token };
60
+ notifyChanged();
61
+ return () => {
62
+ if (active?.token !== token) return;
63
+ active = null;
64
+ notifyChanged();
65
+ };
66
+ }
67
+
68
+ export function AssetEditorControlsUtility() {
69
+ useSyncExternalStore(subscribeAssetEditorContext, assetEditorContextVersion);
70
+ const context = getActiveAssetEditorContext();
71
+ if (!context?.controls) return null;
72
+ return (
73
+ <div
74
+ data-testid="asset-editor-controls"
75
+ style={{
76
+ minHeight: 0,
77
+ padding: 8,
78
+ color: themeVars.content.primary,
79
+ pointerEvents: 'auto',
80
+ }}
81
+ >
82
+ {context.controls}
83
+ </div>
84
+ );
85
+ }
86
+
87
+ export function AssetEditorStatus() {
88
+ useSyncExternalStore(subscribeAssetEditorContext, assetEditorContextVersion);
89
+ const context = getActiveAssetEditorContext();
90
+ return context?.status ? (
91
+ <span data-testid="status-asset-editor" style={{ color: themeVars.content.muted }}>
92
+ {context.status}
93
+ </span>
94
+ ) : null;
95
+ }
96
+
97
+ const registrationGroup = createHmrRegistrationGroup(
98
+ import.meta.hot,
99
+ 'asset-editor-shell-contributions',
100
+ );
101
+
102
+ export function ensureAssetEditorShellContributionsRegistered(): void {
103
+ registrationGroup.ensure((track) => {
104
+ track(
105
+ registerWorkspaceUtility({
106
+ id: ASSET_EDITOR_CONTROLS_UTILITY_ID,
107
+ title: 'Asset Controls',
108
+ Content: AssetEditorControlsUtility,
109
+ available: () => getActiveAssetEditorContext()?.controls != null,
110
+ visibleByDefault: false,
111
+ closeable: true,
112
+ }),
113
+ );
114
+ track(
115
+ registerWorkspaceStatus({
116
+ id: 'asset-editor-status',
117
+ align: 'left',
118
+ order: 40,
119
+ Content: AssetEditorStatus,
120
+ }),
121
+ );
122
+ });
123
+ }
124
+
125
+ ensureAssetEditorShellContributionsRegistered();
126
+
127
+ export function __resetAssetEditorContextForTest(): void {
128
+ active = null;
129
+ notifyChanged();
130
+ }
@@ -785,6 +785,10 @@ function parseGltfData(
785
785
  data,
786
786
  new URL('.', sourceUrl).href,
787
787
  (gltf) => {
788
+ // GLTFLoader keeps clips on the GLTF result rather than its scene.
789
+ // Asset Editor's model contract is Object3D-shaped, so preserve those
790
+ // clips instead of silently degrading animated assets into static ones.
791
+ gltf.scene.animations = [...gltf.animations];
788
792
  if (!finish(() => resolve(gltf.scene))) {
789
793
  disposeOwnedModel(gltf.scene);
790
794
  }
@@ -13,19 +13,19 @@
13
13
  * (mirrors `inspector-section-registry.ts`): version counter + subscribe +
14
14
  * `__resetForTest`. It deliberately does NOT touch the EditorStore's entity
15
15
  * selection — clicking an asset must never deselect/reselect scene entities
16
- * (§6.1 "selection never unexpectedly..."); instead the Inspector section
17
- * simply appears for the selected asset, and the selection is auto-cleared
16
+ * (§6.1 "selection never unexpectedly..."); instead the Inspector gives the
17
+ * asset domain precedence while retaining that entity selection, and the asset is auto-cleared
18
18
  * when the ENTITY selection changes (the user moved on — see
19
19
  * `installAssetSelectionAutoClear`).
20
20
  */
21
21
 
22
22
  import type { AssetCapabilities } from './asset-workflow/asset-capabilities';
23
- import type { AssetKind } from './editor-store';
23
+ import type { AssetKind, OnlineAssetInfo } from './editor-store';
24
24
 
25
25
  /** The §5.1 compact-selection payload — enough for the Inspector section to
26
26
  * render preview + metadata without re-deriving browser state. */
27
27
  export interface SelectedAsset {
28
- /** Serving path (`/textures/crate.png`) — doubles as the open-document key. */
28
+ /** Project serving path, or a stable `online:<source>:<id>` document key. */
29
29
  readonly path: string;
30
30
  /** Source-owned file name as authored (§8 — `crate.png`). */
31
31
  readonly name: string;
@@ -39,6 +39,11 @@ export interface SelectedAsset {
39
39
  readonly dependencies?: readonly string[];
40
40
  readonly references?: readonly { ownerPath: string; jsonPath: string }[];
41
41
  readonly healthCodes?: readonly string[];
42
+ /** Selection came from the external acquisition Library rather than the
43
+ * project's owned file tree. `online` carries the provider identity and is
44
+ * intentionally absent from project assets. */
45
+ readonly origin?: 'project' | 'library';
46
+ readonly online?: OnlineAssetInfo;
42
47
  }
43
48
 
44
49
  let _selected: SelectedAsset | null = null;
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { getStorageBackend } from '../storage';
2
+ import { getStorageBackend, type StorageBackend } from '../storage';
3
3
  import type { AssetCatalog } from './asset-types';
4
4
 
5
5
  export const THUMBNAIL_PROFILE = {
@@ -39,15 +39,24 @@ export const thumbnailManifestSchema = z.object({
39
39
  export type ThumbnailManifest = z.infer<typeof thumbnailManifestSchema>;
40
40
 
41
41
  let manifestPromise: Promise<ThumbnailManifest | null> | null = null;
42
+
43
+ /** Optional project thumbnail metadata must never block live fallback previews.
44
+ * HTTP-backed projects can return the Vite HTML shell for an absent file, so
45
+ * existence and parsing are both guarded at this storage boundary. */
46
+ export async function readProjectThumbnailManifest(
47
+ backend: Pick<StorageBackend, 'read'>,
48
+ ): Promise<ThumbnailManifest | null> {
49
+ try {
50
+ return thumbnailManifestSchema.parse(JSON.parse(await backend.read('.vgai/thumbnails.json')));
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
42
56
  export function loadProjectThumbnailManifest(): Promise<ThumbnailManifest | null> {
43
57
  if (!manifestPromise) {
44
58
  const backend = getStorageBackend() as ReturnType<typeof getStorageBackend> | undefined;
45
- manifestPromise = backend
46
- ? backend.read('.vgai/thumbnails.json').then(
47
- (content) => thumbnailManifestSchema.parse(JSON.parse(content)),
48
- () => null,
49
- )
50
- : Promise.resolve(null);
59
+ manifestPromise = backend ? readProjectThumbnailManifest(backend) : Promise.resolve(null);
51
60
  }
52
61
  return manifestPromise;
53
62
  }