@vgai/live 0.5.11 → 0.5.13
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/.tsbuildinfo +1 -1
- package/dist/editor-document.d.ts +56 -0
- package/dist/editor-document.js +57 -0
- package/dist/editor.d.ts +66 -1
- package/dist/editor.js +80 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/package.json +3 -3
- package/src/editor-document.ts +86 -0
- package/src/editor.ts +88 -5
- package/src/index.ts +13 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `editor.document` — the session binding for the scoped editor-chrome door.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT IS A SEPARATE OBJECT, and why the verbs are these four, is recorded
|
|
5
|
+
* once in the implementation's header
|
|
6
|
+
* (`packages/editor/src/editor-document-probe.ts`); the short version is that
|
|
7
|
+
* `game.page()` is play-mode-gated and rooted at the GAME container, so an
|
|
8
|
+
* editor surface that is not a running game could be neither read nor driven
|
|
9
|
+
* through the product. This door is the answer for exactly one subject: the
|
|
10
|
+
* ACTIVE center document. It is not page automation — a selector resolving
|
|
11
|
+
* outside that document's container is refused by a message naming the scope.
|
|
12
|
+
*
|
|
13
|
+
* It reaches editor chrome only in the sense that a project-tool document IS
|
|
14
|
+
* chrome; the dock, hierarchy and inspector keep their own doors
|
|
15
|
+
* (`editor.hierarchy()`, `editor.inspect()`), which report what those panels
|
|
16
|
+
* RENDERED rather than what a DOM scrape can find.
|
|
17
|
+
*
|
|
18
|
+
* A field on `LiveEditor` rather than methods on it, so `vgai eval --list`
|
|
19
|
+
* shows the four verbs as one named surface — the same reason `game.input`
|
|
20
|
+
* and `game.events` are instance fields.
|
|
21
|
+
*/
|
|
22
|
+
import type { DocumentProbeResult, EditorClient } from '@vgai/editor-sdk';
|
|
23
|
+
/** Modifier/targeting options shared by the gesture verbs. */
|
|
24
|
+
export interface DocumentGestureOptions {
|
|
25
|
+
/** Which match to drive when the selector matches several (default 0). */
|
|
26
|
+
index?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface DocumentKeyOptions extends DocumentGestureOptions {
|
|
29
|
+
/** Target one element instead of whatever inside the document has focus. */
|
|
30
|
+
selector?: string;
|
|
31
|
+
code?: string;
|
|
32
|
+
ctrlKey?: boolean;
|
|
33
|
+
metaKey?: boolean;
|
|
34
|
+
shiftKey?: boolean;
|
|
35
|
+
altKey?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface DocumentPasteOptions extends DocumentGestureOptions {
|
|
38
|
+
selector?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare class LiveEditorDocument {
|
|
41
|
+
#private;
|
|
42
|
+
constructor(client: EditorClient);
|
|
43
|
+
/** Read matching elements inside the active document: tag, text, attributes,
|
|
44
|
+
* value/checked/disabled and rect. `matched` is the total before `limit`. */
|
|
45
|
+
query(selector: string, options?: {
|
|
46
|
+
limit?: number;
|
|
47
|
+
}): Promise<DocumentProbeResult>;
|
|
48
|
+
/** A REAL pointer gesture (pointerdown/mousedown/focus/pointerup/mouseup/click)
|
|
49
|
+
* — not `element.click()`, which a `pointerdown` listener never sees. */
|
|
50
|
+
click(selector: string, options?: DocumentGestureOptions): Promise<DocumentProbeResult>;
|
|
51
|
+
/** A real keydown/keyup on the target, or on whatever inside the document has focus. */
|
|
52
|
+
key(key: string, options?: DocumentKeyOptions): Promise<DocumentProbeResult>;
|
|
53
|
+
/** A real `ClipboardEvent` carrying `text/plain` — the gesture nothing else
|
|
54
|
+
* in the product can produce. */
|
|
55
|
+
paste(text: string, options?: DocumentPasteOptions): Promise<DocumentProbeResult>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `editor.document` — the session binding for the scoped editor-chrome door.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT IS A SEPARATE OBJECT, and why the verbs are these four, is recorded
|
|
5
|
+
* once in the implementation's header
|
|
6
|
+
* (`packages/editor/src/editor-document-probe.ts`); the short version is that
|
|
7
|
+
* `game.page()` is play-mode-gated and rooted at the GAME container, so an
|
|
8
|
+
* editor surface that is not a running game could be neither read nor driven
|
|
9
|
+
* through the product. This door is the answer for exactly one subject: the
|
|
10
|
+
* ACTIVE center document. It is not page automation — a selector resolving
|
|
11
|
+
* outside that document's container is refused by a message naming the scope.
|
|
12
|
+
*
|
|
13
|
+
* It reaches editor chrome only in the sense that a project-tool document IS
|
|
14
|
+
* chrome; the dock, hierarchy and inspector keep their own doors
|
|
15
|
+
* (`editor.hierarchy()`, `editor.inspect()`), which report what those panels
|
|
16
|
+
* RENDERED rather than what a DOM scrape can find.
|
|
17
|
+
*
|
|
18
|
+
* A field on `LiveEditor` rather than methods on it, so `vgai eval --list`
|
|
19
|
+
* shows the four verbs as one named surface — the same reason `game.input`
|
|
20
|
+
* and `game.events` are instance fields.
|
|
21
|
+
*/
|
|
22
|
+
export class LiveEditorDocument {
|
|
23
|
+
#client;
|
|
24
|
+
constructor(client) {
|
|
25
|
+
this.#client = client;
|
|
26
|
+
}
|
|
27
|
+
/** Read matching elements inside the active document: tag, text, attributes,
|
|
28
|
+
* value/checked/disabled and rect. `matched` is the total before `limit`. */
|
|
29
|
+
async query(selector, options) {
|
|
30
|
+
return this.#probe({
|
|
31
|
+
action: 'query',
|
|
32
|
+
selector,
|
|
33
|
+
...(options?.limit === undefined ? {} : { limit: options.limit }),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** A REAL pointer gesture (pointerdown/mousedown/focus/pointerup/mouseup/click)
|
|
37
|
+
* — not `element.click()`, which a `pointerdown` listener never sees. */
|
|
38
|
+
async click(selector, options) {
|
|
39
|
+
return this.#probe({
|
|
40
|
+
action: 'click',
|
|
41
|
+
selector,
|
|
42
|
+
...(options?.index === undefined ? {} : { index: options.index }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** A real keydown/keyup on the target, or on whatever inside the document has focus. */
|
|
46
|
+
async key(key, options) {
|
|
47
|
+
return this.#probe({ action: 'key', key, ...(options ?? {}) });
|
|
48
|
+
}
|
|
49
|
+
/** A real `ClipboardEvent` carrying `text/plain` — the gesture nothing else
|
|
50
|
+
* in the product can produce. */
|
|
51
|
+
async paste(text, options) {
|
|
52
|
+
return this.#probe({ action: 'paste', text, ...(options ?? {}) });
|
|
53
|
+
}
|
|
54
|
+
#probe(step) {
|
|
55
|
+
return this.#client.documentProbe(step);
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/editor.d.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* hand-rolls a `fetch` to `/__editor/command` itself. (WO-8 removed the one
|
|
11
11
|
* exception, `applyDiff`, which was FILE mode rather than a live wire command.)
|
|
12
12
|
*/
|
|
13
|
-
import type { ActiveDocumentCapture, AssetKind, AssetPreviewCapture, AssetPreviewOptions, AssetPreviewSource, EditorClient, EditorState, EditorView, InspectedInspection, PresentedEditorView, ShadingMode, ViewPreset, ViewportCapture } from '@vgai/editor-sdk';
|
|
13
|
+
import type { ActiveDocumentCapture, AssetKind, AssetPreviewCapture, AssetPreviewOptions, AssetPreviewSource, EditorClient, EditorState, EditorView, HistoryStep, InspectedHierarchy, InspectedInspection, PresentedEditorView, ShadingMode, ViewPreset, ViewportCapture } from '@vgai/editor-sdk';
|
|
14
|
+
import { LiveEditorDocument } from './editor-document.js';
|
|
14
15
|
/** `vgai show <...>`'s four sub-verbs folded into one action name — see `showPanel`. */
|
|
15
16
|
export type PanelName = 'viewport-edit' | 'viewport-play' | 'inspector' | 'console' | 'build';
|
|
16
17
|
/**
|
|
@@ -23,6 +24,15 @@ export type PanelName = 'viewport-edit' | 'viewport-play' | 'inspector' | 'conso
|
|
|
23
24
|
export declare function inferAssetKind(path: string): AssetKind;
|
|
24
25
|
export declare class LiveEditor {
|
|
25
26
|
#private;
|
|
27
|
+
/**
|
|
28
|
+
* The ACTIVE center document's own DOM: read it, click it, key it, paste
|
|
29
|
+
* into it. The one door onto editor chrome that is not play-mode gated, and
|
|
30
|
+
* deliberately scoped to that document alone —
|
|
31
|
+
* `packages/editor/src/editor-document-probe.ts` carries the design and the
|
|
32
|
+
* refusal contract. Screenshotting the same subject is
|
|
33
|
+
* {@link LiveEditor.captureActiveDocument}, not a fifth verb here.
|
|
34
|
+
*/
|
|
35
|
+
readonly document: LiveEditorDocument;
|
|
26
36
|
constructor(client: EditorClient);
|
|
27
37
|
/**
|
|
28
38
|
* The active authoring adapter's persistence destination — where a save would
|
|
@@ -98,6 +108,24 @@ export declare class LiveEditor {
|
|
|
98
108
|
helpers(on: boolean): Promise<void>;
|
|
99
109
|
stats(on: boolean): Promise<void>;
|
|
100
110
|
shading(mode: ShadingMode): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* CONSENT to edits being written into the game's own source files, for this
|
|
113
|
+
* session — the Game document's "Persist to game source" checkbox, reachable
|
|
114
|
+
* from a script.
|
|
115
|
+
*
|
|
116
|
+
* Off by default every session, on purpose: it is a statement about what you
|
|
117
|
+
* are doing right now, never a property of the game. With it off, an edit
|
|
118
|
+
* lives on the running object and says so; with it on, an edit that can be
|
|
119
|
+
* honestly anchored to the line that CREATED the object is written there,
|
|
120
|
+
* and one that cannot still says so. Answers with the server's own phrase for
|
|
121
|
+
* who records the resulting diff — your version control, or a vendored
|
|
122
|
+
* game's own lock — and refuses, with the reason, where the checkbox is
|
|
123
|
+
* disabled.
|
|
124
|
+
*/
|
|
125
|
+
persistToGameSource(on: boolean): Promise<{
|
|
126
|
+
enabled: boolean;
|
|
127
|
+
recorder: string | null;
|
|
128
|
+
}>;
|
|
101
129
|
/**
|
|
102
130
|
* READ the inspector, as data — the serialized inspection subject
|
|
103
131
|
* (`editor.inspect()`; design: `docs/ARCHITECTURE-CORE.md` §Editor chrome,
|
|
@@ -126,6 +154,43 @@ export declare class LiveEditor {
|
|
|
126
154
|
* `{position, rotation, scale}`, rotation in Euler XYZ degrees).
|
|
127
155
|
*/
|
|
128
156
|
inspect(): Promise<InspectedInspection>;
|
|
157
|
+
/**
|
|
158
|
+
* READ the hierarchy panel, as data — the rows a human is looking at right
|
|
159
|
+
* now, nested exactly as the panel nests them.
|
|
160
|
+
*
|
|
161
|
+
* The companion to {@link inspect}: that one answers "what IS the selected
|
|
162
|
+
* thing", this one answers "what does the tree LOOK LIKE". It is the panel's
|
|
163
|
+
* own output, not a fresh walk of the scene — the adapter's tree after the
|
|
164
|
+
* component marks fold implementation subtrees (bones, particle renderers,
|
|
165
|
+
* instanced pools), after the internals reveal, the document promotion, the
|
|
166
|
+
* child cap, the collapse state, the search filter and the selection scope.
|
|
167
|
+
*
|
|
168
|
+
* Works in play mode and edit mode; the answer says which (`playState`,
|
|
169
|
+
* `activeViewportTab`), because the two are different adapters and a tree
|
|
170
|
+
* that looks wrong is very often the wrong adapter's tree.
|
|
171
|
+
*
|
|
172
|
+
* Prefer this over `status().entities`, which is deliberately a different
|
|
173
|
+
* question — the RAW adapter tree, unprojected. A panel that renders the
|
|
174
|
+
* wrong rows looks perfectly healthy in that facet.
|
|
175
|
+
*
|
|
176
|
+
* Each row carries `childCount` (what its caret opens), `internalChildCount`
|
|
177
|
+
* (what is folded behind "Reveal Internals") and `expandable` (whether the
|
|
178
|
+
* panel draws a caret at all), so "this subtree exists but nothing in the UI
|
|
179
|
+
* opens it" is a fact you can read rather than one you have to notice.
|
|
180
|
+
*
|
|
181
|
+
* Rejects, naming the panel, when no hierarchy panel is mounted — an empty
|
|
182
|
+
* tree would be a fabricated answer about a surface nobody is being shown.
|
|
183
|
+
*/
|
|
184
|
+
hierarchy(): Promise<InspectedHierarchy>;
|
|
185
|
+
/**
|
|
186
|
+
* Write one editable field from `inspect()` by its stable path, through the
|
|
187
|
+
* same Inspector IO and persistence boundary the human control uses.
|
|
188
|
+
*/
|
|
189
|
+
setField(path: string, value: unknown): Promise<InspectedInspection>;
|
|
190
|
+
/** Undo / redo one project transaction, through the session's own history
|
|
191
|
+
* queue — the same one the keyboard shortcut drives. */
|
|
192
|
+
undo(): Promise<HistoryStep>;
|
|
193
|
+
redo(): Promise<HistoryStep>;
|
|
129
194
|
/** Mirrors `vgai status` — the full live editor state as JSON. */
|
|
130
195
|
status(): Promise<EditorState>;
|
|
131
196
|
/** A live viewport PNG (`EditorClient.captureViewport`) — no direct CLI verb exists; this is the closest wire read. */
|
package/dist/editor.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* hand-rolls a `fetch` to `/__editor/command` itself. (WO-8 removed the one
|
|
11
11
|
* exception, `applyDiff`, which was FILE mode rather than a live wire command.)
|
|
12
12
|
*/
|
|
13
|
+
import { LiveEditorDocument } from './editor-document.js';
|
|
13
14
|
const EXTENSION_KIND = {
|
|
14
15
|
'.glb': 'model',
|
|
15
16
|
'.gltf': 'model',
|
|
@@ -19,10 +20,15 @@ const EXTENSION_KIND = {
|
|
|
19
20
|
'.webp': 'image',
|
|
20
21
|
'.gif': 'image',
|
|
21
22
|
'.svg': 'image',
|
|
23
|
+
'.hdr': 'image',
|
|
24
|
+
'.exr': 'image',
|
|
22
25
|
'.mp3': 'audio',
|
|
23
26
|
'.ogg': 'audio',
|
|
24
27
|
'.wav': 'audio',
|
|
25
28
|
'.flac': 'audio',
|
|
29
|
+
'.glsl': 'source',
|
|
30
|
+
'.vert': 'source',
|
|
31
|
+
'.frag': 'source',
|
|
26
32
|
};
|
|
27
33
|
/**
|
|
28
34
|
* Lightweight extension-based `AssetKind` guess for `openAsset`'s optional
|
|
@@ -32,14 +38,10 @@ const EXTENSION_KIND = {
|
|
|
32
38
|
* on. Callers with an unusual extension can always pass `kind` explicitly.
|
|
33
39
|
*/
|
|
34
40
|
export function inferAssetKind(path) {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// AssetKind itself still exists in `@vgai/editor-sdk`, so the mapping is left
|
|
38
|
-
// for that retirement's own follow-up rather than half-removed here.
|
|
41
|
+
// `.prefab.json` is retired (PRs #578/#581/#589) but the `'prefab'`
|
|
42
|
+
// AssetKind itself remains in `@vgai/editor-sdk` for view-link compatibility.
|
|
39
43
|
if (path.endsWith('.prefab.json'))
|
|
40
44
|
return 'prefab';
|
|
41
|
-
if (path.endsWith('.mat.json'))
|
|
42
|
-
return 'material';
|
|
43
45
|
const dot = path.lastIndexOf('.');
|
|
44
46
|
const ext = dot >= 0 ? path.slice(dot).toLowerCase() : '';
|
|
45
47
|
return EXTENSION_KIND[ext] ?? 'json';
|
|
@@ -50,8 +52,18 @@ export class LiveEditor {
|
|
|
50
52
|
* raw `EditorClient` advertised beside them. See `./game-client/`'s
|
|
51
53
|
* `client.ts` (GameClient's field block) for the full reasoning. */
|
|
52
54
|
#client;
|
|
55
|
+
/**
|
|
56
|
+
* The ACTIVE center document's own DOM: read it, click it, key it, paste
|
|
57
|
+
* into it. The one door onto editor chrome that is not play-mode gated, and
|
|
58
|
+
* deliberately scoped to that document alone —
|
|
59
|
+
* `packages/editor/src/editor-document-probe.ts` carries the design and the
|
|
60
|
+
* refusal contract. Screenshotting the same subject is
|
|
61
|
+
* {@link LiveEditor.captureActiveDocument}, not a fifth verb here.
|
|
62
|
+
*/
|
|
63
|
+
document;
|
|
53
64
|
constructor(client) {
|
|
54
65
|
this.#client = client;
|
|
66
|
+
this.document = new LiveEditorDocument(client);
|
|
55
67
|
}
|
|
56
68
|
/**
|
|
57
69
|
* The active authoring adapter's persistence destination — where a save would
|
|
@@ -203,6 +215,23 @@ export class LiveEditor {
|
|
|
203
215
|
async shading(mode) {
|
|
204
216
|
await this.#client.setShadingMode(mode);
|
|
205
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* CONSENT to edits being written into the game's own source files, for this
|
|
220
|
+
* session — the Game document's "Persist to game source" checkbox, reachable
|
|
221
|
+
* from a script.
|
|
222
|
+
*
|
|
223
|
+
* Off by default every session, on purpose: it is a statement about what you
|
|
224
|
+
* are doing right now, never a property of the game. With it off, an edit
|
|
225
|
+
* lives on the running object and says so; with it on, an edit that can be
|
|
226
|
+
* honestly anchored to the line that CREATED the object is written there,
|
|
227
|
+
* and one that cannot still says so. Answers with the server's own phrase for
|
|
228
|
+
* who records the resulting diff — your version control, or a vendored
|
|
229
|
+
* game's own lock — and refuses, with the reason, where the checkbox is
|
|
230
|
+
* disabled.
|
|
231
|
+
*/
|
|
232
|
+
async persistToGameSource(on) {
|
|
233
|
+
return this.#client.setSourcePersistConsent(on);
|
|
234
|
+
}
|
|
206
235
|
/**
|
|
207
236
|
* READ the inspector, as data — the serialized inspection subject
|
|
208
237
|
* (`editor.inspect()`; design: `docs/ARCHITECTURE-CORE.md` §Editor chrome,
|
|
@@ -233,6 +262,51 @@ export class LiveEditor {
|
|
|
233
262
|
async inspect() {
|
|
234
263
|
return this.#client.inspect();
|
|
235
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* READ the hierarchy panel, as data — the rows a human is looking at right
|
|
267
|
+
* now, nested exactly as the panel nests them.
|
|
268
|
+
*
|
|
269
|
+
* The companion to {@link inspect}: that one answers "what IS the selected
|
|
270
|
+
* thing", this one answers "what does the tree LOOK LIKE". It is the panel's
|
|
271
|
+
* own output, not a fresh walk of the scene — the adapter's tree after the
|
|
272
|
+
* component marks fold implementation subtrees (bones, particle renderers,
|
|
273
|
+
* instanced pools), after the internals reveal, the document promotion, the
|
|
274
|
+
* child cap, the collapse state, the search filter and the selection scope.
|
|
275
|
+
*
|
|
276
|
+
* Works in play mode and edit mode; the answer says which (`playState`,
|
|
277
|
+
* `activeViewportTab`), because the two are different adapters and a tree
|
|
278
|
+
* that looks wrong is very often the wrong adapter's tree.
|
|
279
|
+
*
|
|
280
|
+
* Prefer this over `status().entities`, which is deliberately a different
|
|
281
|
+
* question — the RAW adapter tree, unprojected. A panel that renders the
|
|
282
|
+
* wrong rows looks perfectly healthy in that facet.
|
|
283
|
+
*
|
|
284
|
+
* Each row carries `childCount` (what its caret opens), `internalChildCount`
|
|
285
|
+
* (what is folded behind "Reveal Internals") and `expandable` (whether the
|
|
286
|
+
* panel draws a caret at all), so "this subtree exists but nothing in the UI
|
|
287
|
+
* opens it" is a fact you can read rather than one you have to notice.
|
|
288
|
+
*
|
|
289
|
+
* Rejects, naming the panel, when no hierarchy panel is mounted — an empty
|
|
290
|
+
* tree would be a fabricated answer about a surface nobody is being shown.
|
|
291
|
+
*/
|
|
292
|
+
async hierarchy() {
|
|
293
|
+
return this.#client.hierarchy();
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Write one editable field from `inspect()` by its stable path, through the
|
|
297
|
+
* same Inspector IO and persistence boundary the human control uses.
|
|
298
|
+
*/
|
|
299
|
+
async setField(path, value) {
|
|
300
|
+
return this.#client.setInspectionField(path, value);
|
|
301
|
+
}
|
|
302
|
+
/** Undo / redo one project transaction, through the session's own history
|
|
303
|
+
* queue — the same one the keyboard shortcut drives. */
|
|
304
|
+
async undo() {
|
|
305
|
+
return this.#client.undo();
|
|
306
|
+
}
|
|
307
|
+
async redo() {
|
|
308
|
+
return this.#client.redo();
|
|
309
|
+
}
|
|
236
310
|
/** Mirrors `vgai status` — the full live editor state as JSON. */
|
|
237
311
|
async status() {
|
|
238
312
|
return this.#client.getState();
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
* await editor.grid(true);
|
|
28
28
|
* await game.input.tap('jump');
|
|
29
29
|
*
|
|
30
|
+
* `editor.document` is the OTHER surface door and the one that is NOT
|
|
31
|
+
* play-mode gated: `page(step)` is rooted at the running GAME container, so
|
|
32
|
+
* `editor.document.{query,click,key,paste}` is how an editor surface that is
|
|
33
|
+
* not a game — a capability's workspace document, the Data sheet — gets read
|
|
34
|
+
* and driven through the product. It is scoped to the ACTIVE document and
|
|
35
|
+
* refuses anything outside it by name (`editor-document.ts`).
|
|
36
|
+
*
|
|
30
37
|
* `page(step)` = `GameClient.page(step)` (Wave-2's playwright-shim surface,
|
|
31
38
|
* PR #166) — write `step` as a literal `async (page) => {...}` and inline
|
|
32
39
|
* every value it needs. KNOWN WIRE LIMITATION: under the relay transport this
|
|
@@ -44,6 +51,8 @@ import { LiveTools } from './tools.js';
|
|
|
44
51
|
export type { ActiveDocumentCapture, EditorClient, EditorView, EditorViewDocument, PresentedEditorView, } from '@vgai/editor-sdk';
|
|
45
52
|
export type { PanelName } from './editor.js';
|
|
46
53
|
export { inferAssetKind, LiveEditor } from './editor.js';
|
|
54
|
+
export type { DocumentGestureOptions, DocumentKeyOptions, DocumentPasteOptions, } from './editor-document.js';
|
|
55
|
+
export { LiveEditorDocument } from './editor-document.js';
|
|
47
56
|
export { createGameClient, createLiveGame, type LiveGame } from './game.js';
|
|
48
57
|
export * from './game-client/index.js';
|
|
49
58
|
export type { ProjectSessionHint, ResolvedSession, SessionListingTransport, SessionResolutionDeps, } from './session.js';
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
* await editor.grid(true);
|
|
28
28
|
* await game.input.tap('jump');
|
|
29
29
|
*
|
|
30
|
+
* `editor.document` is the OTHER surface door and the one that is NOT
|
|
31
|
+
* play-mode gated: `page(step)` is rooted at the running GAME container, so
|
|
32
|
+
* `editor.document.{query,click,key,paste}` is how an editor surface that is
|
|
33
|
+
* not a game — a capability's workspace document, the Data sheet — gets read
|
|
34
|
+
* and driven through the product. It is scoped to the ACTIVE document and
|
|
35
|
+
* refuses anything outside it by name (`editor-document.ts`).
|
|
36
|
+
*
|
|
30
37
|
* `page(step)` = `GameClient.page(step)` (Wave-2's playwright-shim surface,
|
|
31
38
|
* PR #166) — write `step` as a literal `async (page) => {...}` and inline
|
|
32
39
|
* every value it needs. KNOWN WIRE LIMITATION: under the relay transport this
|
|
@@ -45,6 +52,7 @@ import { resolveSession } from './session.js';
|
|
|
45
52
|
import { createLazySession } from './singleton.js';
|
|
46
53
|
import { LiveTools } from './tools.js';
|
|
47
54
|
export { inferAssetKind, LiveEditor } from './editor.js';
|
|
55
|
+
export { LiveEditorDocument } from './editor-document.js';
|
|
48
56
|
export { createGameClient, createLiveGame } from './game.js';
|
|
49
57
|
export * from './game-client/index.js';
|
|
50
58
|
export { findProjectRootFrom, resolveSession } from './session.js';
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@vgai/live",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.13",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@vgai/editor-sdk": "0.5.
|
|
35
|
-
"@vgai/sdk": "0.5.
|
|
34
|
+
"@vgai/editor-sdk": "0.5.13",
|
|
35
|
+
"@vgai/sdk": "0.5.13"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@playwright/test": ">=1.58.2 <2"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `editor.document` — the session binding for the scoped editor-chrome door.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT IS A SEPARATE OBJECT, and why the verbs are these four, is recorded
|
|
5
|
+
* once in the implementation's header
|
|
6
|
+
* (`packages/editor/src/editor-document-probe.ts`); the short version is that
|
|
7
|
+
* `game.page()` is play-mode-gated and rooted at the GAME container, so an
|
|
8
|
+
* editor surface that is not a running game could be neither read nor driven
|
|
9
|
+
* through the product. This door is the answer for exactly one subject: the
|
|
10
|
+
* ACTIVE center document. It is not page automation — a selector resolving
|
|
11
|
+
* outside that document's container is refused by a message naming the scope.
|
|
12
|
+
*
|
|
13
|
+
* It reaches editor chrome only in the sense that a project-tool document IS
|
|
14
|
+
* chrome; the dock, hierarchy and inspector keep their own doors
|
|
15
|
+
* (`editor.hierarchy()`, `editor.inspect()`), which report what those panels
|
|
16
|
+
* RENDERED rather than what a DOM scrape can find.
|
|
17
|
+
*
|
|
18
|
+
* A field on `LiveEditor` rather than methods on it, so `vgai eval --list`
|
|
19
|
+
* shows the four verbs as one named surface — the same reason `game.input`
|
|
20
|
+
* and `game.events` are instance fields.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { DocumentProbeResult, DocumentProbeStep, EditorClient } from '@vgai/editor-sdk';
|
|
24
|
+
|
|
25
|
+
/** Modifier/targeting options shared by the gesture verbs. */
|
|
26
|
+
export interface DocumentGestureOptions {
|
|
27
|
+
/** Which match to drive when the selector matches several (default 0). */
|
|
28
|
+
index?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DocumentKeyOptions extends DocumentGestureOptions {
|
|
32
|
+
/** Target one element instead of whatever inside the document has focus. */
|
|
33
|
+
selector?: string;
|
|
34
|
+
code?: string;
|
|
35
|
+
ctrlKey?: boolean;
|
|
36
|
+
metaKey?: boolean;
|
|
37
|
+
shiftKey?: boolean;
|
|
38
|
+
altKey?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DocumentPasteOptions extends DocumentGestureOptions {
|
|
42
|
+
selector?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class LiveEditorDocument {
|
|
46
|
+
readonly #client: EditorClient;
|
|
47
|
+
|
|
48
|
+
constructor(client: EditorClient) {
|
|
49
|
+
this.#client = client;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Read matching elements inside the active document: tag, text, attributes,
|
|
53
|
+
* value/checked/disabled and rect. `matched` is the total before `limit`. */
|
|
54
|
+
async query(selector: string, options?: { limit?: number }): Promise<DocumentProbeResult> {
|
|
55
|
+
return this.#probe({
|
|
56
|
+
action: 'query',
|
|
57
|
+
selector,
|
|
58
|
+
...(options?.limit === undefined ? {} : { limit: options.limit }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A REAL pointer gesture (pointerdown/mousedown/focus/pointerup/mouseup/click)
|
|
63
|
+
* — not `element.click()`, which a `pointerdown` listener never sees. */
|
|
64
|
+
async click(selector: string, options?: DocumentGestureOptions): Promise<DocumentProbeResult> {
|
|
65
|
+
return this.#probe({
|
|
66
|
+
action: 'click',
|
|
67
|
+
selector,
|
|
68
|
+
...(options?.index === undefined ? {} : { index: options.index }),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A real keydown/keyup on the target, or on whatever inside the document has focus. */
|
|
73
|
+
async key(key: string, options?: DocumentKeyOptions): Promise<DocumentProbeResult> {
|
|
74
|
+
return this.#probe({ action: 'key', key, ...(options ?? {}) });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A real `ClipboardEvent` carrying `text/plain` — the gesture nothing else
|
|
78
|
+
* in the product can produce. */
|
|
79
|
+
async paste(text: string, options?: DocumentPasteOptions): Promise<DocumentProbeResult> {
|
|
80
|
+
return this.#probe({ action: 'paste', text, ...(options ?? {}) });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#probe(step: DocumentProbeStep): Promise<DocumentProbeResult> {
|
|
84
|
+
return this.#client.documentProbe(step);
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/editor.ts
CHANGED
|
@@ -20,12 +20,15 @@ import type {
|
|
|
20
20
|
EditorClient,
|
|
21
21
|
EditorState,
|
|
22
22
|
EditorView,
|
|
23
|
+
HistoryStep,
|
|
24
|
+
InspectedHierarchy,
|
|
23
25
|
InspectedInspection,
|
|
24
26
|
PresentedEditorView,
|
|
25
27
|
ShadingMode,
|
|
26
28
|
ViewPreset,
|
|
27
29
|
ViewportCapture,
|
|
28
30
|
} from '@vgai/editor-sdk';
|
|
31
|
+
import { LiveEditorDocument } from './editor-document.js';
|
|
29
32
|
|
|
30
33
|
/** `vgai show <...>`'s four sub-verbs folded into one action name — see `showPanel`. */
|
|
31
34
|
export type PanelName = 'viewport-edit' | 'viewport-play' | 'inspector' | 'console' | 'build';
|
|
@@ -39,10 +42,15 @@ const EXTENSION_KIND: Record<string, AssetKind> = {
|
|
|
39
42
|
'.webp': 'image',
|
|
40
43
|
'.gif': 'image',
|
|
41
44
|
'.svg': 'image',
|
|
45
|
+
'.hdr': 'image',
|
|
46
|
+
'.exr': 'image',
|
|
42
47
|
'.mp3': 'audio',
|
|
43
48
|
'.ogg': 'audio',
|
|
44
49
|
'.wav': 'audio',
|
|
45
50
|
'.flac': 'audio',
|
|
51
|
+
'.glsl': 'source',
|
|
52
|
+
'.vert': 'source',
|
|
53
|
+
'.frag': 'source',
|
|
46
54
|
};
|
|
47
55
|
|
|
48
56
|
/**
|
|
@@ -53,12 +61,9 @@ const EXTENSION_KIND: Record<string, AssetKind> = {
|
|
|
53
61
|
* on. Callers with an unusual extension can always pass `kind` explicitly.
|
|
54
62
|
*/
|
|
55
63
|
export function inferAssetKind(path: string): AssetKind {
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
// AssetKind itself still exists in `@vgai/editor-sdk`, so the mapping is left
|
|
59
|
-
// for that retirement's own follow-up rather than half-removed here.
|
|
64
|
+
// `.prefab.json` is retired (PRs #578/#581/#589) but the `'prefab'`
|
|
65
|
+
// AssetKind itself remains in `@vgai/editor-sdk` for view-link compatibility.
|
|
60
66
|
if (path.endsWith('.prefab.json')) return 'prefab';
|
|
61
|
-
if (path.endsWith('.mat.json')) return 'material';
|
|
62
67
|
const dot = path.lastIndexOf('.');
|
|
63
68
|
const ext = dot >= 0 ? path.slice(dot).toLowerCase() : '';
|
|
64
69
|
return EXTENSION_KIND[ext] ?? 'json';
|
|
@@ -71,8 +76,19 @@ export class LiveEditor {
|
|
|
71
76
|
* `client.ts` (GameClient's field block) for the full reasoning. */
|
|
72
77
|
readonly #client: EditorClient;
|
|
73
78
|
|
|
79
|
+
/**
|
|
80
|
+
* The ACTIVE center document's own DOM: read it, click it, key it, paste
|
|
81
|
+
* into it. The one door onto editor chrome that is not play-mode gated, and
|
|
82
|
+
* deliberately scoped to that document alone —
|
|
83
|
+
* `packages/editor/src/editor-document-probe.ts` carries the design and the
|
|
84
|
+
* refusal contract. Screenshotting the same subject is
|
|
85
|
+
* {@link LiveEditor.captureActiveDocument}, not a fifth verb here.
|
|
86
|
+
*/
|
|
87
|
+
readonly document: LiveEditorDocument;
|
|
88
|
+
|
|
74
89
|
constructor(client: EditorClient) {
|
|
75
90
|
this.#client = client;
|
|
91
|
+
this.document = new LiveEditorDocument(client);
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
/**
|
|
@@ -254,6 +270,24 @@ export class LiveEditor {
|
|
|
254
270
|
await this.#client.setShadingMode(mode);
|
|
255
271
|
}
|
|
256
272
|
|
|
273
|
+
/**
|
|
274
|
+
* CONSENT to edits being written into the game's own source files, for this
|
|
275
|
+
* session — the Game document's "Persist to game source" checkbox, reachable
|
|
276
|
+
* from a script.
|
|
277
|
+
*
|
|
278
|
+
* Off by default every session, on purpose: it is a statement about what you
|
|
279
|
+
* are doing right now, never a property of the game. With it off, an edit
|
|
280
|
+
* lives on the running object and says so; with it on, an edit that can be
|
|
281
|
+
* honestly anchored to the line that CREATED the object is written there,
|
|
282
|
+
* and one that cannot still says so. Answers with the server's own phrase for
|
|
283
|
+
* who records the resulting diff — your version control, or a vendored
|
|
284
|
+
* game's own lock — and refuses, with the reason, where the checkbox is
|
|
285
|
+
* disabled.
|
|
286
|
+
*/
|
|
287
|
+
async persistToGameSource(on: boolean): Promise<{ enabled: boolean; recorder: string | null }> {
|
|
288
|
+
return this.#client.setSourcePersistConsent(on);
|
|
289
|
+
}
|
|
290
|
+
|
|
257
291
|
/**
|
|
258
292
|
* READ the inspector, as data — the serialized inspection subject
|
|
259
293
|
* (`editor.inspect()`; design: `docs/ARCHITECTURE-CORE.md` §Editor chrome,
|
|
@@ -285,6 +319,55 @@ export class LiveEditor {
|
|
|
285
319
|
return this.#client.inspect();
|
|
286
320
|
}
|
|
287
321
|
|
|
322
|
+
/**
|
|
323
|
+
* READ the hierarchy panel, as data — the rows a human is looking at right
|
|
324
|
+
* now, nested exactly as the panel nests them.
|
|
325
|
+
*
|
|
326
|
+
* The companion to {@link inspect}: that one answers "what IS the selected
|
|
327
|
+
* thing", this one answers "what does the tree LOOK LIKE". It is the panel's
|
|
328
|
+
* own output, not a fresh walk of the scene — the adapter's tree after the
|
|
329
|
+
* component marks fold implementation subtrees (bones, particle renderers,
|
|
330
|
+
* instanced pools), after the internals reveal, the document promotion, the
|
|
331
|
+
* child cap, the collapse state, the search filter and the selection scope.
|
|
332
|
+
*
|
|
333
|
+
* Works in play mode and edit mode; the answer says which (`playState`,
|
|
334
|
+
* `activeViewportTab`), because the two are different adapters and a tree
|
|
335
|
+
* that looks wrong is very often the wrong adapter's tree.
|
|
336
|
+
*
|
|
337
|
+
* Prefer this over `status().entities`, which is deliberately a different
|
|
338
|
+
* question — the RAW adapter tree, unprojected. A panel that renders the
|
|
339
|
+
* wrong rows looks perfectly healthy in that facet.
|
|
340
|
+
*
|
|
341
|
+
* Each row carries `childCount` (what its caret opens), `internalChildCount`
|
|
342
|
+
* (what is folded behind "Reveal Internals") and `expandable` (whether the
|
|
343
|
+
* panel draws a caret at all), so "this subtree exists but nothing in the UI
|
|
344
|
+
* opens it" is a fact you can read rather than one you have to notice.
|
|
345
|
+
*
|
|
346
|
+
* Rejects, naming the panel, when no hierarchy panel is mounted — an empty
|
|
347
|
+
* tree would be a fabricated answer about a surface nobody is being shown.
|
|
348
|
+
*/
|
|
349
|
+
async hierarchy(): Promise<InspectedHierarchy> {
|
|
350
|
+
return this.#client.hierarchy();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Write one editable field from `inspect()` by its stable path, through the
|
|
355
|
+
* same Inspector IO and persistence boundary the human control uses.
|
|
356
|
+
*/
|
|
357
|
+
async setField(path: string, value: unknown): Promise<InspectedInspection> {
|
|
358
|
+
return this.#client.setInspectionField(path, value);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Undo / redo one project transaction, through the session's own history
|
|
362
|
+
* queue — the same one the keyboard shortcut drives. */
|
|
363
|
+
async undo(): Promise<HistoryStep> {
|
|
364
|
+
return this.#client.undo();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async redo(): Promise<HistoryStep> {
|
|
368
|
+
return this.#client.redo();
|
|
369
|
+
}
|
|
370
|
+
|
|
288
371
|
/** Mirrors `vgai status` — the full live editor state as JSON. */
|
|
289
372
|
async status(): Promise<EditorState> {
|
|
290
373
|
return this.#client.getState();
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
* await editor.grid(true);
|
|
28
28
|
* await game.input.tap('jump');
|
|
29
29
|
*
|
|
30
|
+
* `editor.document` is the OTHER surface door and the one that is NOT
|
|
31
|
+
* play-mode gated: `page(step)` is rooted at the running GAME container, so
|
|
32
|
+
* `editor.document.{query,click,key,paste}` is how an editor surface that is
|
|
33
|
+
* not a game — a capability's workspace document, the Data sheet — gets read
|
|
34
|
+
* and driven through the product. It is scoped to the ACTIVE document and
|
|
35
|
+
* refuses anything outside it by name (`editor-document.ts`).
|
|
36
|
+
*
|
|
30
37
|
* `page(step)` = `GameClient.page(step)` (Wave-2's playwright-shim surface,
|
|
31
38
|
* PR #166) — write `step` as a literal `async (page) => {...}` and inline
|
|
32
39
|
* every value it needs. KNOWN WIRE LIMITATION: under the relay transport this
|
|
@@ -56,6 +63,12 @@ export type {
|
|
|
56
63
|
} from '@vgai/editor-sdk';
|
|
57
64
|
export type { PanelName } from './editor.js';
|
|
58
65
|
export { inferAssetKind, LiveEditor } from './editor.js';
|
|
66
|
+
export type {
|
|
67
|
+
DocumentGestureOptions,
|
|
68
|
+
DocumentKeyOptions,
|
|
69
|
+
DocumentPasteOptions,
|
|
70
|
+
} from './editor-document.js';
|
|
71
|
+
export { LiveEditorDocument } from './editor-document.js';
|
|
59
72
|
export { createGameClient, createLiveGame, type LiveGame } from './game.js';
|
|
60
73
|
export * from './game-client/index.js';
|
|
61
74
|
export type {
|