@volter/editor-live 0.5.57
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/BUNDLED_NOTICES +434 -0
- package/LICENSE +202 -0
- package/README.md +20 -0
- package/dist/editor-document.d.ts +152 -0
- package/dist/editor.d.ts +399 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +2080 -0
- package/dist/index.js.map +7 -0
- package/dist/lazy-proxy.d.ts +26 -0
- package/dist/session.d.ts +32 -0
- package/dist/singleton.d.ts +18 -0
- package/dist/tools.d.ts +23 -0
- package/package.json +46 -0
- package/src/editor-document.ts +234 -0
- package/src/editor.ts +663 -0
- package/src/index.ts +38 -0
- package/src/lazy-proxy.ts +68 -0
- package/src/session.ts +267 -0
- package/src/singleton.ts +30 -0
- package/src/tools.ts +45 -0
|
@@ -0,0 +1,234 @@
|
|
|
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, is recorded once
|
|
5
|
+
* 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.
|
|
10
|
+
*
|
|
11
|
+
* WHAT IT ADDRESSES is a closed VOCABULARY of named surfaces, passed as
|
|
12
|
+
* `{ scope }` on every verb:
|
|
13
|
+
*
|
|
14
|
+
* `document` (default) the active centre document's whole box
|
|
15
|
+
* `header` / `shelf` that document's toolbar strip / tool rail
|
|
16
|
+
* `rail` the PROPERTIES view — its tabs, sections and fields
|
|
17
|
+
* `outliner` the OUTLINER view — its rows and their controls
|
|
18
|
+
* `content` the CONTENT view — its categories and asset rows
|
|
19
|
+
*
|
|
20
|
+
* await editor.document.query('[role=tab]', { scope: 'rail' });
|
|
21
|
+
* await editor.document.click('[data-testid=properties-tab-modifiers]', { scope: 'rail' });
|
|
22
|
+
* await editor.document.click('[data-ingest-name=Cube]', { scope: 'outliner', clicks: 2 });
|
|
23
|
+
* await editor.document.type('Wheel', { scope: 'outliner' });
|
|
24
|
+
*
|
|
25
|
+
* It is not page automation: a selector resolving outside the named surface is
|
|
26
|
+
* refused by a message naming the scope and listing the others. `rail` and
|
|
27
|
+
* `outliner`, and `content` are editor chrome and stay readable while the Game document is
|
|
28
|
+
* active; `editor.hierarchy()` / `editor.inspect()` keep answering what those
|
|
29
|
+
* panels RESOLVED, where this door answers what they DREW.
|
|
30
|
+
*
|
|
31
|
+
* A field on `LiveEditor` rather than methods on it, so `vgai eval --list`
|
|
32
|
+
* shows the verbs as one named surface — the same reason `game.input`
|
|
33
|
+
* and `game.events` are instance fields.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type {
|
|
37
|
+
DocumentProbeResult,
|
|
38
|
+
DocumentProbeScope,
|
|
39
|
+
DocumentProbeStep,
|
|
40
|
+
EditorClient,
|
|
41
|
+
} from '@volter/editor-sdk';
|
|
42
|
+
|
|
43
|
+
/** Which named surface a verb runs against; `document` when omitted. */
|
|
44
|
+
export interface DocumentScopeOption {
|
|
45
|
+
scope?: DocumentProbeScope;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Modifier/targeting options shared by the gesture verbs. */
|
|
49
|
+
export interface DocumentGestureOptions extends DocumentScopeOption {
|
|
50
|
+
/** Which match to drive when the selector matches several (default 0). */
|
|
51
|
+
index?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface DocumentKeyOptions extends DocumentGestureOptions {
|
|
55
|
+
/** Target one element instead of whatever inside the scope has focus. */
|
|
56
|
+
selector?: string;
|
|
57
|
+
code?: string;
|
|
58
|
+
ctrlKey?: boolean;
|
|
59
|
+
metaKey?: boolean;
|
|
60
|
+
shiftKey?: boolean;
|
|
61
|
+
altKey?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface DocumentPasteOptions extends DocumentGestureOptions {
|
|
65
|
+
selector?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DocumentTypeOptions extends DocumentGestureOptions {
|
|
69
|
+
/** The field; omitted types into whatever inside the scope has focus. */
|
|
70
|
+
selector?: string;
|
|
71
|
+
/** Replace what the field holds first (default true). */
|
|
72
|
+
replace?: boolean;
|
|
73
|
+
/** Press Enter after the text (default true). */
|
|
74
|
+
enter?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class LiveEditorDocument {
|
|
78
|
+
readonly #client: EditorClient;
|
|
79
|
+
|
|
80
|
+
constructor(client: EditorClient) {
|
|
81
|
+
this.#client = client;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Read matching elements inside the active document: tag, text, attributes,
|
|
86
|
+
* value/checked/disabled and rect. `matched` is the total before `limit`.
|
|
87
|
+
*
|
|
88
|
+
* `styles` additionally resolves named properties per match — and resolving
|
|
89
|
+
* is the point, because a theme token is an expression until an element
|
|
90
|
+
* paints it. Ask for the standard property to learn the colour a person
|
|
91
|
+
* sees; ask for a `--vgai-…` custom property to learn what a rule WOULD
|
|
92
|
+
* paint, which is the only way to measure a `:hover` colour (`:hover` is a
|
|
93
|
+
* browser state no synthetic event can enter, so there is deliberately no
|
|
94
|
+
* hover verb on this door).
|
|
95
|
+
*
|
|
96
|
+
* await editor.document.query('.vgai-tree-row', {
|
|
97
|
+
* styles: ['backgroundColor', '--vgai-widget-regular-hover'],
|
|
98
|
+
* });
|
|
99
|
+
*/
|
|
100
|
+
async query(
|
|
101
|
+
selector: string,
|
|
102
|
+
options?: DocumentScopeOption & { limit?: number; styles?: readonly string[] },
|
|
103
|
+
): Promise<DocumentProbeResult> {
|
|
104
|
+
return this.#probe({
|
|
105
|
+
action: 'query',
|
|
106
|
+
selector,
|
|
107
|
+
...(options?.scope === undefined ? {} : { scope: options.scope }),
|
|
108
|
+
...(options?.limit === undefined ? {} : { limit: options.limit }),
|
|
109
|
+
...(options?.styles === undefined ? {} : { styles: [...options.styles] }),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A REAL pointer gesture (pointerdown/mousedown/focus/pointerup/mouseup/click)
|
|
114
|
+
* — not `element.click()`, which a `pointerdown` listener never sees. */
|
|
115
|
+
async click(
|
|
116
|
+
selector: string,
|
|
117
|
+
options?: DocumentGestureOptions & { clicks?: number },
|
|
118
|
+
): Promise<DocumentProbeResult> {
|
|
119
|
+
return this.#probe({
|
|
120
|
+
action: 'click',
|
|
121
|
+
selector,
|
|
122
|
+
...(options?.scope === undefined ? {} : { scope: options.scope }),
|
|
123
|
+
...(options?.index === undefined ? {} : { index: options.index }),
|
|
124
|
+
...(options?.clicks === undefined ? {} : { clicks: options.clicks }),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* TYPE into a field and commit with Enter, the way a person does — one
|
|
130
|
+
* character at a time through the prototype's value setter, between real
|
|
131
|
+
* `keydown`/`keyup`.
|
|
132
|
+
*
|
|
133
|
+
* `paste` is not a substitute: an untrusted `ClipboardEvent` performs no
|
|
134
|
+
* default action, so a plain `<input>` with no paste handler keeps its old
|
|
135
|
+
* value. Omit `selector` to type into whatever inside the scope has focus —
|
|
136
|
+
* which is what a rename field is, one gesture after
|
|
137
|
+
* `click(row, { clicks: 2 })`.
|
|
138
|
+
*/
|
|
139
|
+
async type(text: string, options?: DocumentTypeOptions): Promise<DocumentProbeResult> {
|
|
140
|
+
return this.#probe({ action: 'type', text, ...(options ?? {}) });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A real pointer DRAG across one matched element — press at `from`, move,
|
|
145
|
+
* release at `to`. The gesture a direct-manipulation canvas needs; a
|
|
146
|
+
* zero-length drag is a click at that fraction, which `click` (always the
|
|
147
|
+
* center) cannot place.
|
|
148
|
+
*
|
|
149
|
+
* `from`, `to` and every point in `via` are `[x, y]` FRACTIONS OF THE
|
|
150
|
+
* MATCHED ELEMENT'S BOX, 0..1 from its top-left — NEVER pixels and never
|
|
151
|
+
* page coordinates. `[0.5, 0.5]` is its center, `[1, 0]` its top-right.
|
|
152
|
+
* Compute a pixel target by measuring the element first: `query` answers
|
|
153
|
+
* its `rect`, and `(px - rect.x) / rect.width` is the fraction to pass.
|
|
154
|
+
*/
|
|
155
|
+
async drag(
|
|
156
|
+
selector: string,
|
|
157
|
+
options: DocumentGestureOptions & {
|
|
158
|
+
from: [number, number];
|
|
159
|
+
to: [number, number];
|
|
160
|
+
via?: [number, number][];
|
|
161
|
+
steps?: number;
|
|
162
|
+
altKey?: boolean;
|
|
163
|
+
ctrlKey?: boolean;
|
|
164
|
+
metaKey?: boolean;
|
|
165
|
+
shiftKey?: boolean;
|
|
166
|
+
},
|
|
167
|
+
): Promise<DocumentProbeResult> {
|
|
168
|
+
return this.#probe({
|
|
169
|
+
action: 'drag',
|
|
170
|
+
selector,
|
|
171
|
+
...(options.scope === undefined ? {} : { scope: options.scope }),
|
|
172
|
+
from: options.from,
|
|
173
|
+
to: options.to,
|
|
174
|
+
...(options.via === undefined ? {} : { via: options.via }),
|
|
175
|
+
...(options.steps === undefined ? {} : { steps: options.steps }),
|
|
176
|
+
...(options.index === undefined ? {} : { index: options.index }),
|
|
177
|
+
...(options.altKey === undefined ? {} : { altKey: options.altKey }),
|
|
178
|
+
...(options.ctrlKey === undefined ? {} : { ctrlKey: options.ctrlKey }),
|
|
179
|
+
...(options.metaKey === undefined ? {} : { metaKey: options.metaKey }),
|
|
180
|
+
...(options.shiftKey === undefined ? {} : { shiftKey: options.shiftKey }),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** A real keydown/keyup on the target, or on whatever inside the document has focus. */
|
|
185
|
+
async key(key: string, options?: DocumentKeyOptions): Promise<DocumentProbeResult> {
|
|
186
|
+
return this.#probe({ action: 'key', key, ...(options ?? {}) });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** A real `ClipboardEvent` carrying `text/plain` — the gesture nothing else
|
|
190
|
+
* in the product can produce. */
|
|
191
|
+
async paste(text: string, options?: DocumentPasteOptions): Promise<DocumentProbeResult> {
|
|
192
|
+
return this.#probe({ action: 'paste', text, ...(options ?? {}) });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Choose `value` on a `<select>` — a native dropdown's options are drawn by
|
|
197
|
+
* the OS, so `click` has nothing in the document to resolve, and a plain
|
|
198
|
+
* `element.value =` is invisible to React. Set through the prototype's own
|
|
199
|
+
* value setter plus `input`/`change`; `value` is the option's `value`, not
|
|
200
|
+
* its label. An unknown value is refused with the options it does offer.
|
|
201
|
+
*/
|
|
202
|
+
async select(
|
|
203
|
+
selector: string,
|
|
204
|
+
value: string,
|
|
205
|
+
options?: DocumentGestureOptions,
|
|
206
|
+
): Promise<DocumentProbeResult> {
|
|
207
|
+
return this.#probe({
|
|
208
|
+
action: 'select',
|
|
209
|
+
selector,
|
|
210
|
+
value,
|
|
211
|
+
...(options?.scope === undefined ? {} : { scope: options.scope }),
|
|
212
|
+
...(options?.index === undefined ? {} : { index: options.index }),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* THE REPL over the open document: run `step` in the editor page against the
|
|
218
|
+
* object the ACTIVE document published as its context (the mesh document
|
|
219
|
+
* publishes its `MeshEditSession`, whose `ctx` is the bpy-shaped edit
|
|
220
|
+
* context — `ctx.ops.mesh.bevel({ offset: 0.1 })`, `ctx.selection`,
|
|
221
|
+
* `ctx.history`, `session.commit()`). Edit mode, no play. Serialized like
|
|
222
|
+
* `game.page`: the step's own source travels, so inline every value it
|
|
223
|
+
* needs and return plain data.
|
|
224
|
+
*/
|
|
225
|
+
async run<T = unknown>(
|
|
226
|
+
step: (ctx: unknown, info: { documentId: string }) => T | Promise<T>,
|
|
227
|
+
): Promise<T> {
|
|
228
|
+
return this.#client.documentScript<T>(step.toString());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#probe(step: DocumentProbeStep): Promise<DocumentProbeResult> {
|
|
232
|
+
return this.#client.documentProbe(step);
|
|
233
|
+
}
|
|
234
|
+
}
|