@lvce-editor/api 9.12.0 → 9.14.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/parts/Rpc/Rpc.d.ts +10 -3
- package/dist/parts/Rpc/Rpc.js +21 -1
- package/dist/parts/View/View.d.ts +2 -0
- package/dist/parts/ViewRegistry/ViewRegistry.js +29 -16
- package/dist/parts/ViewStatusBarItems/ViewStatusBarItems.d.ts +4 -0
- package/dist/parts/ViewStatusBarItems/ViewStatusBarItems.js +90 -0
- package/package.json +1 -1
package/dist/parts/Rpc/Rpc.d.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { type Rpc } from '@lvce-editor/rpc';
|
|
2
|
-
|
|
2
|
+
interface CreateRpcBaseOptions {
|
|
3
3
|
readonly commandMap?: Record<string, unknown>;
|
|
4
|
-
|
|
4
|
+
}
|
|
5
|
+
interface CreateDeclaredRpcOptions extends CreateRpcBaseOptions {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
}
|
|
8
|
+
interface CreateLegacyRpcOptions extends CreateRpcBaseOptions {
|
|
9
|
+
readonly contentSecurityPolicy?: readonly string[] | string;
|
|
5
10
|
readonly name?: string;
|
|
6
11
|
readonly url: string;
|
|
7
12
|
}
|
|
13
|
+
export type CreateRpcOptions = CreateDeclaredRpcOptions | CreateLegacyRpcOptions;
|
|
8
14
|
export interface CreateNodeRpcOptions {
|
|
9
15
|
readonly id: string;
|
|
10
16
|
readonly legacyName?: string;
|
|
11
17
|
readonly legacyPath?: string;
|
|
12
18
|
}
|
|
13
|
-
export declare const createRpc: (
|
|
19
|
+
export declare const createRpc: (options: CreateRpcOptions) => Promise<Rpc>;
|
|
14
20
|
export declare const createNodeRpc: (options: CreateNodeRpcOptions) => Promise<Rpc>;
|
|
21
|
+
export {};
|
package/dist/parts/Rpc/Rpc.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { MessagePortRpcParent, WebSocketRpcParent } from '@lvce-editor/rpc';
|
|
2
2
|
import { ExtensionManagementWorker } from '@lvce-editor/rpc-registry';
|
|
3
|
+
const resolveRpcOptions = async (options) => {
|
|
4
|
+
if ('id' in options && options.id) {
|
|
5
|
+
const info = (await ExtensionManagementWorker.invoke('Extensions.getRpcInfo', options.id));
|
|
6
|
+
return {
|
|
7
|
+
contentSecurityPolicy: info.contentSecurityPolicy || '',
|
|
8
|
+
name: info.name || '',
|
|
9
|
+
url: info.url,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if ('url' in options && options.url) {
|
|
13
|
+
return {
|
|
14
|
+
contentSecurityPolicy: options.contentSecurityPolicy || '',
|
|
15
|
+
name: options.name || '',
|
|
16
|
+
url: options.url,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
throw new TypeError('createRpc requires an id or url');
|
|
20
|
+
};
|
|
3
21
|
const sendMessagePortToWebWorker = async (port, contentSecurityPolicy, name, url) => {
|
|
4
22
|
await ExtensionManagementWorker.invokeAndTransfer('Extensions.createWebViewWorkerRpc2', { contentSecurityPolicy, name, url }, port);
|
|
5
23
|
};
|
|
@@ -14,7 +32,9 @@ const createMessagePortRpc = async (commandMap, send) => {
|
|
|
14
32
|
await send(port1);
|
|
15
33
|
return rpcPromise;
|
|
16
34
|
};
|
|
17
|
-
export const createRpc = async (
|
|
35
|
+
export const createRpc = async (options) => {
|
|
36
|
+
const { commandMap = {} } = options;
|
|
37
|
+
const { contentSecurityPolicy, name, url } = await resolveRpcOptions(options);
|
|
18
38
|
return createMessagePortRpc(commandMap, (port) => sendMessagePortToWebWorker(port, contentSecurityPolicy, name, url));
|
|
19
39
|
};
|
|
20
40
|
const isMissingCommand = (error, command) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { VirtualDomNode } from '@lvce-editor/virtual-dom-worker';
|
|
2
|
+
import type { StatusBarItem } from '../StatusBarItem/StatusBarItem.ts';
|
|
2
3
|
export type ViewKind = 'virtualDom';
|
|
3
4
|
export type ViewPreferredLocation = 'preview' | 'sideBar';
|
|
4
5
|
export interface ViewContext {
|
|
@@ -56,6 +57,7 @@ export interface VirtualDomViewInstance {
|
|
|
56
57
|
readonly renderFocus?: (oldContext: Readonly<Record<string, boolean>>, newContext: Readonly<Record<string, boolean>>) => string | Promise<string>;
|
|
57
58
|
readonly renderScrollPosition?: () => readonly [] | ViewScrollPosition | Promise<readonly [] | ViewScrollPosition>;
|
|
58
59
|
readonly renderSelections?: () => readonly ViewSelection[] | Promise<readonly ViewSelection[]>;
|
|
60
|
+
readonly renderStatusBarItems?: () => readonly StatusBarItem[] | Promise<readonly StatusBarItem[]>;
|
|
59
61
|
readonly renderTitle?: () => string | Promise<string>;
|
|
60
62
|
readonly saveState?: () => unknown;
|
|
61
63
|
}
|
|
@@ -3,6 +3,7 @@ import { diffTree } from '@lvce-editor/virtual-dom-worker';
|
|
|
3
3
|
import { registerCommand } from "../CommandRegistry/CommandRegistry.js";
|
|
4
4
|
import * as ExtensionApiCommandRegistry from "../ExtensionApiCommandRegistry/ExtensionApiCommandRegistry.js";
|
|
5
5
|
import { ExtensionApiError } from "../ExtensionApiError/ExtensionApiError.js";
|
|
6
|
+
import * as ViewStatusBarItems from "../ViewStatusBarItems/ViewStatusBarItems.js";
|
|
6
7
|
const views = Object.create(null);
|
|
7
8
|
const instances = Object.create(null);
|
|
8
9
|
const instanceUidsByView = Object.create(null);
|
|
@@ -420,12 +421,14 @@ const withScrollPosition = async (result, instance) => {
|
|
|
420
421
|
scrollPosition,
|
|
421
422
|
};
|
|
422
423
|
};
|
|
423
|
-
const withRenderMetadata = async (result, instance, contextChange) => {
|
|
424
|
+
const withRenderMetadata = async (uid, viewId, result, instance, contextChange) => {
|
|
424
425
|
const resultWithCss = await withCss(result, instance);
|
|
425
426
|
const resultWithFocus = await withFocusSelector(resultWithCss, instance, contextChange);
|
|
426
427
|
const resultWithSelections = await withSelections(resultWithFocus, instance);
|
|
427
428
|
const resultWithScrollPosition = await withScrollPosition(resultWithSelections, instance);
|
|
428
|
-
|
|
429
|
+
const resultWithTitle = await withTitle(resultWithScrollPosition, instance);
|
|
430
|
+
await ViewStatusBarItems.renderViewStatusBarItems(uid, viewId, instance);
|
|
431
|
+
return resultWithTitle;
|
|
429
432
|
};
|
|
430
433
|
const maybeClearContext = async (uid, viewId) => {
|
|
431
434
|
if (!contexts[uid]) {
|
|
@@ -469,7 +472,7 @@ export const createViewInstance = async (viewId, uid, context) => {
|
|
|
469
472
|
type: 'setDom',
|
|
470
473
|
};
|
|
471
474
|
const contextChange = await maybeNotifyContextChanged(uid, viewId, instance);
|
|
472
|
-
return withRenderMetadata(result, instance, contextChange);
|
|
475
|
+
return withRenderMetadata(uid, viewId, result, instance, contextChange);
|
|
473
476
|
};
|
|
474
477
|
export const dispatchViewEvent = async (uid, event) => {
|
|
475
478
|
const instance = getVirtualDomInstance(uid);
|
|
@@ -488,29 +491,38 @@ export const dispatchViewEvent = async (uid, event) => {
|
|
|
488
491
|
}
|
|
489
492
|
const result = await renderPatches(uid, instance);
|
|
490
493
|
const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
|
|
491
|
-
return withRenderMetadata(result, instance, contextChange);
|
|
494
|
+
return withRenderMetadata(uid, contextViewIds[uid], result, instance, contextChange);
|
|
492
495
|
};
|
|
493
496
|
export const renderViewInstance = async (uid) => {
|
|
494
497
|
const instance = getVirtualDomInstance(uid);
|
|
495
498
|
const result = await renderPatches(uid, instance);
|
|
496
499
|
const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
|
|
497
|
-
return withRenderMetadata(result, instance, contextChange);
|
|
500
|
+
return withRenderMetadata(uid, contextViewIds[uid], result, instance, contextChange);
|
|
498
501
|
};
|
|
499
502
|
export const disposeViewInstance = async (uid) => {
|
|
500
503
|
const instance = instances[uid];
|
|
501
|
-
if (instance && typeof instance.dispose === 'function') {
|
|
502
|
-
await instance.dispose();
|
|
503
|
-
}
|
|
504
|
-
await maybeClearContext(uid, contextViewIds[uid]);
|
|
505
504
|
const viewId = contextViewIds[uid];
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
505
|
+
try {
|
|
506
|
+
if (instance && typeof instance.dispose === 'function') {
|
|
507
|
+
await instance.dispose();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
ViewStatusBarItems.disposeViewStatusBarItems(uid);
|
|
512
|
+
try {
|
|
513
|
+
await maybeClearContext(uid, viewId);
|
|
514
|
+
}
|
|
515
|
+
finally {
|
|
516
|
+
const instanceUids = instanceUidsByView[viewId];
|
|
517
|
+
instanceUids?.delete(uid);
|
|
518
|
+
if (instanceUids?.size === 0) {
|
|
519
|
+
delete instanceUidsByView[viewId];
|
|
520
|
+
}
|
|
521
|
+
delete instances[uid];
|
|
522
|
+
delete renderedDoms[uid];
|
|
523
|
+
delete contextViewIds[uid];
|
|
524
|
+
}
|
|
510
525
|
}
|
|
511
|
-
delete instances[uid];
|
|
512
|
-
delete renderedDoms[uid];
|
|
513
|
-
delete contextViewIds[uid];
|
|
514
526
|
};
|
|
515
527
|
export const saveViewInstanceState = async (uid) => {
|
|
516
528
|
const instance = getVirtualDomInstance(uid);
|
|
@@ -563,6 +575,7 @@ const commandMap = {
|
|
|
563
575
|
'ExtensionApi.saveViewInstanceState': saveViewInstanceState,
|
|
564
576
|
};
|
|
565
577
|
export const resetViewRegistry = () => {
|
|
578
|
+
ViewStatusBarItems.resetViewStatusBarItems();
|
|
566
579
|
for (const id of Object.keys(views)) {
|
|
567
580
|
for (const disposable of viewCommandDisposables[id] || []) {
|
|
568
581
|
disposable.dispose();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { VirtualDomViewInstance } from '../View/View.ts';
|
|
2
|
+
export declare const renderViewStatusBarItems: (uid: number, viewId: string, instance: VirtualDomViewInstance) => Promise<void>;
|
|
3
|
+
export declare const disposeViewStatusBarItems: (uid: number) => void;
|
|
4
|
+
export declare const resetViewStatusBarItems: () => void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ExtensionApiError } from "../ExtensionApiError/ExtensionApiError.js";
|
|
2
|
+
import { registerStatusBarItemProvider } from "../StatusBarItemProviderRegistry/StatusBarItemProviderRegistry.js";
|
|
3
|
+
const handlesByUid = Object.create(null);
|
|
4
|
+
const itemsByUid = Object.create(null);
|
|
5
|
+
const stringProperties = ['ariaLabel', 'icon', 'name', 'onClick', 'text', 'title'];
|
|
6
|
+
const normalizeStatusBarItem = (item, index) => {
|
|
7
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
8
|
+
throw new ExtensionApiError(`view status bar item ${index} must be an object`);
|
|
9
|
+
}
|
|
10
|
+
const statusBarItem = item;
|
|
11
|
+
for (const property of stringProperties) {
|
|
12
|
+
if (statusBarItem[property] !== undefined && typeof statusBarItem[property] !== 'string') {
|
|
13
|
+
throw new ExtensionApiError(`view status bar item ${index} has invalid ${property}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (statusBarItem.spinning !== undefined && typeof statusBarItem.spinning !== 'boolean') {
|
|
17
|
+
throw new ExtensionApiError(`view status bar item ${index} has invalid spinning`);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
...(statusBarItem.ariaLabel !== undefined && { ariaLabel: statusBarItem.ariaLabel }),
|
|
21
|
+
...(statusBarItem.icon !== undefined && { icon: statusBarItem.icon }),
|
|
22
|
+
...(statusBarItem.name !== undefined && { name: statusBarItem.name }),
|
|
23
|
+
...(statusBarItem.onClick !== undefined && { onClick: statusBarItem.onClick }),
|
|
24
|
+
...(statusBarItem.spinning !== undefined && { spinning: statusBarItem.spinning }),
|
|
25
|
+
...(statusBarItem.text !== undefined && { text: statusBarItem.text }),
|
|
26
|
+
...(statusBarItem.title !== undefined && { title: statusBarItem.title }),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const normalizeStatusBarItems = (items) => {
|
|
30
|
+
if (!Array.isArray(items)) {
|
|
31
|
+
throw new ExtensionApiError('view renderStatusBarItems result must be an array');
|
|
32
|
+
}
|
|
33
|
+
return items.map(normalizeStatusBarItem);
|
|
34
|
+
};
|
|
35
|
+
const areItemsEqual = (oldItems, newItems) => {
|
|
36
|
+
if (oldItems.length !== newItems.length) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return oldItems.every((oldItem, index) => {
|
|
40
|
+
const newItem = newItems[index];
|
|
41
|
+
return (oldItem.ariaLabel === newItem.ariaLabel &&
|
|
42
|
+
oldItem.icon === newItem.icon &&
|
|
43
|
+
oldItem.name === newItem.name &&
|
|
44
|
+
oldItem.onClick === newItem.onClick &&
|
|
45
|
+
oldItem.spinning === newItem.spinning &&
|
|
46
|
+
oldItem.text === newItem.text &&
|
|
47
|
+
oldItem.title === newItem.title);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const disposeHandles = (uid) => {
|
|
51
|
+
for (const handle of handlesByUid[uid] || []) {
|
|
52
|
+
handle.dispose();
|
|
53
|
+
}
|
|
54
|
+
delete handlesByUid[uid];
|
|
55
|
+
};
|
|
56
|
+
const replaceItems = (uid, viewId, items) => {
|
|
57
|
+
disposeHandles(uid);
|
|
58
|
+
itemsByUid[uid] = items;
|
|
59
|
+
handlesByUid[uid] = items.map((_item, index) => registerStatusBarItemProvider({
|
|
60
|
+
getStatusBarItem() {
|
|
61
|
+
return itemsByUid[uid]?.[index];
|
|
62
|
+
},
|
|
63
|
+
id: `view:${viewId}:${uid}:${index}`,
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
export const renderViewStatusBarItems = async (uid, viewId, instance) => {
|
|
67
|
+
if (typeof instance.renderStatusBarItems !== 'function') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const items = normalizeStatusBarItems(await instance.renderStatusBarItems());
|
|
71
|
+
const oldItems = itemsByUid[uid] || [];
|
|
72
|
+
if (areItemsEqual(oldItems, items)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if ((handlesByUid[uid]?.length || 0) !== items.length) {
|
|
76
|
+
replaceItems(uid, viewId, items);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
itemsByUid[uid] = items;
|
|
80
|
+
await Promise.all((handlesByUid[uid] || []).map((handle) => handle.refresh()));
|
|
81
|
+
};
|
|
82
|
+
export const disposeViewStatusBarItems = (uid) => {
|
|
83
|
+
disposeHandles(uid);
|
|
84
|
+
delete itemsByUid[uid];
|
|
85
|
+
};
|
|
86
|
+
export const resetViewStatusBarItems = () => {
|
|
87
|
+
for (const uid of Object.keys(itemsByUid)) {
|
|
88
|
+
disposeViewStatusBarItems(Number(uid));
|
|
89
|
+
}
|
|
90
|
+
};
|