@lvce-editor/api 8.40.0 → 8.42.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/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { executeSourceControlAcceptInput, executeSourceControlAdd, executeSource
|
|
|
12
12
|
export { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewRegistrySnapshot, renderViewInstance, registerView, resetViewRegistry, saveViewInstanceState, } from './parts/ViewRegistry/ViewRegistry.ts';
|
|
13
13
|
export { createOutputChannel, getOutputChannelRegistrySnapshot, resetOutputChannelRegistry } from './parts/OutputChannel/OutputChannel.ts';
|
|
14
14
|
export { getStatusBarItemProviderRegistrySnapshot, registerStatusBarItemProvider, resetStatusBarItemProviderRegistry, } from './parts/StatusBar/StatusBar.ts';
|
|
15
|
-
export type { RegisteredView, View, ViewAction, ViewContext, ViewEvent, ViewKind, MenuEntry, ViewRegistrySnapshot, ViewRenderResult, VirtualDomViewInstance, } from './parts/View/View.ts';
|
|
15
|
+
export type { RegisteredView, View, ViewAction, ViewCommand, ViewContext, ViewEvent, ViewKind, MenuEntry, ViewRegistrySnapshot, ViewRenderResult, VirtualDomViewInstance, } from './parts/View/View.ts';
|
|
16
16
|
export { handleExtensionManagementMessagePort } from './parts/HandleExtensionManagementMessagePort/HandleExtensionManagementMessagePort.ts';
|
|
17
17
|
export type { Command } from './parts/Command/Command.ts';
|
|
18
18
|
export type { CommandCallback } from './parts/CommandCallback/CommandCallback.ts';
|
|
@@ -42,10 +42,13 @@ export interface VirtualDomViewInstance {
|
|
|
42
42
|
readonly render: () => readonly VirtualDomNode[] | Promise<readonly VirtualDomNode[]>;
|
|
43
43
|
readonly renderActions?: () => readonly ViewAction[] | Promise<readonly ViewAction[]>;
|
|
44
44
|
readonly renderFocus?: (oldContext: Readonly<Record<string, boolean>>, newContext: Readonly<Record<string, boolean>>) => string | Promise<string>;
|
|
45
|
+
readonly renderTitle?: () => string | Promise<string>;
|
|
45
46
|
readonly saveState?: () => unknown;
|
|
46
47
|
}
|
|
47
|
-
export
|
|
48
|
-
|
|
48
|
+
export type ViewCommand<State = VirtualDomViewInstance, Args extends readonly any[] = readonly any[]> = (state: State, ...args: Args) => State | Promise<State>;
|
|
49
|
+
export interface View<State = unknown> {
|
|
50
|
+
readonly commands?: Readonly<Record<string, ViewCommand<State>>>;
|
|
51
|
+
readonly create: (context?: ViewContext) => State | Promise<State>;
|
|
49
52
|
readonly displayName?: string;
|
|
50
53
|
readonly eventListeners?: readonly DomEventListener[];
|
|
51
54
|
readonly icon?: string;
|
|
@@ -69,11 +72,13 @@ export interface ViewRegistrySnapshot {
|
|
|
69
72
|
export interface ViewRenderResultDom {
|
|
70
73
|
readonly dom: readonly VirtualDomNode[];
|
|
71
74
|
readonly focusSelector?: string;
|
|
75
|
+
readonly title?: string;
|
|
72
76
|
readonly type: 'setDom';
|
|
73
77
|
}
|
|
74
78
|
export interface ViewRenderResultPatches {
|
|
75
79
|
readonly focusSelector?: string;
|
|
76
80
|
readonly patches: readonly unknown[];
|
|
81
|
+
readonly title?: string;
|
|
77
82
|
readonly type: 'setPatches';
|
|
78
83
|
}
|
|
79
84
|
export type ViewRenderResult = ViewRenderResultDom | ViewRenderResultPatches;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Disposable } from '../Disposable/Disposable.ts';
|
|
2
2
|
import type { MenuEntry, View, ViewAction, ViewContext, ViewEvent, ViewRegistrySnapshot, ViewRenderResult } from '../View/View.ts';
|
|
3
|
-
export declare const registerView: (view: View) => Disposable;
|
|
3
|
+
export declare const registerView: <State>(view: View<State>) => Disposable;
|
|
4
4
|
export declare const executeViewProvider: (id: string) => unknown;
|
|
5
5
|
export declare const createViewInstance: (viewId: string, uid: number, context?: ViewContext) => Promise<ViewRenderResult>;
|
|
6
6
|
export declare const dispatchViewEvent: (uid: number, event: ViewEvent) => Promise<ViewRenderResult>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { ExtensionManagementWorker } from '@lvce-editor/rpc-registry';
|
|
2
2
|
import { diffTree } from '@lvce-editor/virtual-dom-worker';
|
|
3
|
+
import { registerCommand } from "../CommandRegistry/CommandRegistry.js";
|
|
3
4
|
import { ExtensionApiError } from "../ExtensionApiError/ExtensionApiError.js";
|
|
4
5
|
const views = Object.create(null);
|
|
5
6
|
const instances = Object.create(null);
|
|
7
|
+
const instanceUidsByView = Object.create(null);
|
|
6
8
|
const renderedDoms = Object.create(null);
|
|
7
9
|
const contexts = Object.create(null);
|
|
8
10
|
const contextViewIds = Object.create(null);
|
|
11
|
+
const viewCommandDisposables = Object.create(null);
|
|
9
12
|
const assertBoolean = (value, message) => {
|
|
10
13
|
if (value !== undefined && typeof value !== 'boolean') {
|
|
11
14
|
throw new ExtensionApiError(message);
|
|
@@ -48,6 +51,23 @@ const assertEventListeners = (view) => {
|
|
|
48
51
|
assertEventListener(view.id, listener, index);
|
|
49
52
|
}
|
|
50
53
|
};
|
|
54
|
+
const assertCommands = (view) => {
|
|
55
|
+
if (view.commands === undefined) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (view.kind !== 'virtualDom') {
|
|
59
|
+
throw new ExtensionApiError(`view ${view.id} commands require virtualDom kind`);
|
|
60
|
+
}
|
|
61
|
+
if (!view.commands || typeof view.commands !== 'object' || Array.isArray(view.commands)) {
|
|
62
|
+
throw new ExtensionApiError(`view ${view.id} commands must be an object`);
|
|
63
|
+
}
|
|
64
|
+
for (const [id, command] of Object.entries(view.commands)) {
|
|
65
|
+
assertString(id, `view ${view.id} command is missing id`);
|
|
66
|
+
if (typeof command !== 'function') {
|
|
67
|
+
throw new ExtensionApiError(`view ${view.id} command ${id} must be a function`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
51
71
|
const assertView = (view) => {
|
|
52
72
|
if (!view) {
|
|
53
73
|
throw new ExtensionApiError('view is not defined');
|
|
@@ -61,6 +81,7 @@ const assertView = (view) => {
|
|
|
61
81
|
if (view.id in views) {
|
|
62
82
|
throw new ExtensionApiError(`view ${view.id} is already registered`);
|
|
63
83
|
}
|
|
84
|
+
assertCommands(view);
|
|
64
85
|
assertEventListeners(view);
|
|
65
86
|
};
|
|
66
87
|
const toRegisteredView = (view) => {
|
|
@@ -81,12 +102,61 @@ const toRegisteredView = (view) => {
|
|
|
81
102
|
}
|
|
82
103
|
return registeredView;
|
|
83
104
|
};
|
|
105
|
+
const getActiveViewInstance = (viewId) => {
|
|
106
|
+
const instanceUids = instanceUidsByView[viewId];
|
|
107
|
+
if (!instanceUids) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const uid = [...instanceUids].at(-1);
|
|
111
|
+
if (uid === undefined) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
return [uid, instances[uid]];
|
|
115
|
+
};
|
|
116
|
+
const executeViewCommand = async (view, commandId, args) => {
|
|
117
|
+
const activeInstance = getActiveViewInstance(view.id);
|
|
118
|
+
if (!activeInstance) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const [uid, instance] = activeInstance;
|
|
122
|
+
const command = view.commands[commandId];
|
|
123
|
+
const newInstance = await command(instance, ...args);
|
|
124
|
+
assertVirtualDomViewInstance(view.id, newInstance);
|
|
125
|
+
instances[uid] = newInstance;
|
|
126
|
+
await ExtensionManagementWorker.invoke('Extensions.requestViewRerender', uid);
|
|
127
|
+
};
|
|
128
|
+
const registerViewCommands = (view) => {
|
|
129
|
+
const disposables = [];
|
|
130
|
+
try {
|
|
131
|
+
for (const id of Object.keys(view.commands || {})) {
|
|
132
|
+
disposables.push(registerCommand({
|
|
133
|
+
execute(...args) {
|
|
134
|
+
return executeViewCommand(view, id, args);
|
|
135
|
+
},
|
|
136
|
+
id,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
for (const disposable of disposables) {
|
|
142
|
+
disposable.dispose();
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
return disposables;
|
|
147
|
+
};
|
|
84
148
|
export const registerView = (view) => {
|
|
85
149
|
assertView(view);
|
|
150
|
+
const commandDisposables = registerViewCommands(view);
|
|
86
151
|
views[view.id] = view;
|
|
152
|
+
viewCommandDisposables[view.id] = commandDisposables;
|
|
87
153
|
return {
|
|
88
154
|
dispose() {
|
|
155
|
+
for (const disposable of commandDisposables) {
|
|
156
|
+
disposable.dispose();
|
|
157
|
+
}
|
|
89
158
|
delete views[view.id];
|
|
159
|
+
delete viewCommandDisposables[view.id];
|
|
90
160
|
},
|
|
91
161
|
};
|
|
92
162
|
};
|
|
@@ -253,6 +323,23 @@ const withFocusSelector = async (result, instance, contextChange) => {
|
|
|
253
323
|
focusSelector,
|
|
254
324
|
};
|
|
255
325
|
};
|
|
326
|
+
const withTitle = async (result, instance) => {
|
|
327
|
+
if (typeof instance.renderTitle !== 'function') {
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
const title = await instance.renderTitle();
|
|
331
|
+
if (typeof title !== 'string') {
|
|
332
|
+
throw new ExtensionApiError('view renderTitle result must be a string');
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
...result,
|
|
336
|
+
title,
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
const withRenderMetadata = async (result, instance, contextChange) => {
|
|
340
|
+
const resultWithFocus = await withFocusSelector(result, instance, contextChange);
|
|
341
|
+
return withTitle(resultWithFocus, instance);
|
|
342
|
+
};
|
|
256
343
|
const maybeClearContext = async (uid, viewId) => {
|
|
257
344
|
if (!contexts[uid]) {
|
|
258
345
|
return;
|
|
@@ -284,6 +371,9 @@ export const createViewInstance = async (viewId, uid, context) => {
|
|
|
284
371
|
});
|
|
285
372
|
assertVirtualDomViewInstance(viewId, instance);
|
|
286
373
|
instances[uid] = instance;
|
|
374
|
+
const instanceUids = instanceUidsByView[viewId] || new Set();
|
|
375
|
+
instanceUids.add(uid);
|
|
376
|
+
instanceUidsByView[viewId] = instanceUids;
|
|
287
377
|
contextViewIds[uid] = viewId;
|
|
288
378
|
const dom = await renderDom(instance);
|
|
289
379
|
renderedDoms[uid] = dom;
|
|
@@ -292,22 +382,25 @@ export const createViewInstance = async (viewId, uid, context) => {
|
|
|
292
382
|
type: 'setDom',
|
|
293
383
|
};
|
|
294
384
|
const contextChange = await maybeNotifyContextChanged(uid, viewId, instance);
|
|
295
|
-
return
|
|
385
|
+
return withRenderMetadata(result, instance, contextChange);
|
|
296
386
|
};
|
|
297
387
|
export const dispatchViewEvent = async (uid, event) => {
|
|
298
388
|
const instance = getVirtualDomInstance(uid);
|
|
389
|
+
const instanceUids = instanceUidsByView[contextViewIds[uid]];
|
|
390
|
+
instanceUids.delete(uid);
|
|
391
|
+
instanceUids.add(uid);
|
|
299
392
|
if (typeof instance.handleEvent === 'function') {
|
|
300
393
|
await instance.handleEvent(event);
|
|
301
394
|
}
|
|
302
395
|
const result = await renderPatches(uid, instance);
|
|
303
396
|
const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
|
|
304
|
-
return
|
|
397
|
+
return withRenderMetadata(result, instance, contextChange);
|
|
305
398
|
};
|
|
306
399
|
export const renderViewInstance = async (uid) => {
|
|
307
400
|
const instance = getVirtualDomInstance(uid);
|
|
308
401
|
const result = await renderPatches(uid, instance);
|
|
309
402
|
const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
|
|
310
|
-
return
|
|
403
|
+
return withRenderMetadata(result, instance, contextChange);
|
|
311
404
|
};
|
|
312
405
|
export const disposeViewInstance = async (uid) => {
|
|
313
406
|
const instance = instances[uid];
|
|
@@ -315,6 +408,12 @@ export const disposeViewInstance = async (uid) => {
|
|
|
315
408
|
await instance.dispose();
|
|
316
409
|
}
|
|
317
410
|
await maybeClearContext(uid, contextViewIds[uid]);
|
|
411
|
+
const viewId = contextViewIds[uid];
|
|
412
|
+
const instanceUids = instanceUidsByView[viewId];
|
|
413
|
+
instanceUids?.delete(uid);
|
|
414
|
+
if (instanceUids?.size === 0) {
|
|
415
|
+
delete instanceUidsByView[viewId];
|
|
416
|
+
}
|
|
318
417
|
delete instances[uid];
|
|
319
418
|
delete renderedDoms[uid];
|
|
320
419
|
delete contextViewIds[uid];
|
|
@@ -348,7 +447,11 @@ export const getViewRegistrySnapshot = () => {
|
|
|
348
447
|
};
|
|
349
448
|
export const resetViewRegistry = () => {
|
|
350
449
|
for (const id of Object.keys(views)) {
|
|
450
|
+
for (const disposable of viewCommandDisposables[id] || []) {
|
|
451
|
+
disposable.dispose();
|
|
452
|
+
}
|
|
351
453
|
delete views[id];
|
|
454
|
+
delete viewCommandDisposables[id];
|
|
352
455
|
}
|
|
353
456
|
for (const uid of Object.keys(instances)) {
|
|
354
457
|
delete instances[Number(uid)];
|
|
@@ -362,4 +465,7 @@ export const resetViewRegistry = () => {
|
|
|
362
465
|
for (const uid of Object.keys(contextViewIds)) {
|
|
363
466
|
delete contextViewIds[Number(uid)];
|
|
364
467
|
}
|
|
468
|
+
for (const viewId of Object.keys(instanceUidsByView)) {
|
|
469
|
+
delete instanceUidsByView[viewId];
|
|
470
|
+
}
|
|
365
471
|
};
|