@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.
- package/dist/assets/{axe-C7YBtOuM.js → axe-BJjvBeOZ.js} +1 -1
- package/dist/assets/{browserAll-DDtGGE48.js → browserAll-Dn0pgD87.js} +1 -1
- package/dist/assets/{create-runtime-DbTlBlKG.js → create-runtime-BF9OD-U8.js} +1 -1
- package/dist/assets/{game-Dch9sAPc.js → game-B346oj_f.js} +1 -1
- package/dist/assets/{game-BE0r7DMI.js → game-BCNN63uV.js} +1 -1
- package/dist/assets/{game-SAFn90pA.js → game-BR0GBNgF.js} +1 -1
- package/dist/assets/{game-Cqzj_lpD.js → game-CLNm2g0y.js} +1 -1
- package/dist/assets/{game-CcHfYxjP.js → game-DQxhCYsZ.js} +1 -1
- package/dist/assets/{game-BdTHR2_X.js → game-DeTGdgYt.js} +1 -1
- package/dist/assets/{game-loop-DMIgSMV-.js → game-loop-t5W_oqxz.js} +1 -1
- package/dist/assets/{game-source-Dk83w26t.js → game-source-DzE9taML.js} +1 -1
- package/dist/assets/{game-DSZdeCHu.js → game-u6c2Patu.js} +1 -1
- package/dist/assets/{game-C3iQGSqo.js → game-umrHIqFB.js} +1 -1
- package/dist/assets/{index-C-LhOTBN.js → index-CgcH1RpF.js} +1 -1
- package/dist/assets/{index-CDkxceqE.js → index-D3tcNB2T.js} +1 -1
- package/dist/assets/{index-CQEfi217.js → index-DbQzGLxv.js} +771 -771
- package/dist/assets/{index-4idIsHHJ.js → index-Fc8Ezr20.js} +1 -1
- package/dist/assets/index-bAGhEnnO.css +1 -0
- package/dist/assets/{ingest-siblings-s0P5I1Ub.js → ingest-siblings-CW_xbvrF.js} +1 -1
- package/dist/assets/{mount-manifest-DXMOlF79.js → mount-manifest-DgUscyaD.js} +1 -1
- package/dist/assets/{react-18-C9eHn8x1.js → react-18-C4tVCii_.js} +1 -1
- package/dist/assets/webworkerAll-BA4aq7xq.js +1 -0
- package/dist/assets/{world-documents-GM0a9Kv7.js → world-documents-BSaA5YIF.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/packaged.mjs +21 -1
- package/package.json +3 -3
- package/server/packaged.ts +20 -0
- package/src/asset-editor-context.tsx +130 -0
- package/src/asset-preview.ts +4 -0
- package/src/asset-selection.ts +9 -4
- package/src/asset-workflow/thumbnail-system.ts +16 -7
- package/src/components/AssetBrowser.tsx +330 -539
- package/src/components/AssetEditorShell.tsx +42 -112
- package/src/components/AssetImportDetails.tsx +24 -6
- package/src/components/GameHierarchy.tsx +26 -1
- package/src/components/Inspector.tsx +38 -2
- package/src/components/OnlineAssetBrowser.tsx +295 -343
- package/src/components/WorkspaceDock.tsx +6 -12
- package/src/components/asset-documents.tsx +41 -12
- package/src/components/asset-selection-section.tsx +34 -1
- package/src/components/asset-viewers/ModelViewer.tsx +7 -4
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +233 -205
- package/src/components/asset-workflow.css +197 -264
- package/src/editor-api.ts +1 -1
- package/template/vgai.game.json +1 -1
- package/dist/assets/index-Dvi-V0NE.css +0 -1
- package/dist/assets/webworkerAll-Fn0e-ouF.js +0 -1
package/server/packaged.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/asset-preview.ts
CHANGED
|
@@ -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
|
}
|
package/src/asset-selection.ts
CHANGED
|
@@ -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
|
|
17
|
-
*
|
|
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
|
-
/**
|
|
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
|
}
|