@lvce-editor/api 9.29.1 → 9.31.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/README.md +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/parts/ForwardedPort/ForwardedPort.d.ts +7 -0
- package/dist/parts/ForwardedPort/ForwardedPort.js +1 -0
- package/dist/parts/PortProvider/PortProvider.d.ts +5 -0
- package/dist/parts/PortProvider/PortProvider.js +1 -0
- package/dist/parts/PortProviderRegistry/PortProviderRegistry.d.ts +5 -0
- package/dist/parts/PortProviderRegistry/PortProviderRegistry.js +37 -0
- package/dist/parts/View/View.d.ts +4 -2
- package/dist/parts/ViewRegistry/ViewRegistry.d.ts +1 -1
- package/dist/parts/ViewRegistry/ViewRegistry.js +24 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,3 +50,20 @@ await storeSecret('access-token', token)
|
|
|
50
50
|
const savedToken = await getSecret('access-token')
|
|
51
51
|
await deleteSecret('access-token')
|
|
52
52
|
```
|
|
53
|
+
|
|
54
|
+
## Workspace ports
|
|
55
|
+
|
|
56
|
+
Declare `"onPorts:codespaces"` in the extension's `activation` array and register a provider after activating the API:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { registerPortProvider } from '@lvce-editor/api'
|
|
60
|
+
|
|
61
|
+
const registration = registerPortProvider({
|
|
62
|
+
scheme: 'codespaces',
|
|
63
|
+
async providePorts(workspaceUri) {
|
|
64
|
+
return [{ port: 3000, forwardedAddress: 'https://my-codespace-3000.app.github.dev/', origin: 'devcontainer.json' }]
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Ports queries matching extensions with the current workspace URI. Each extension may register one provider per scheme. Return an empty array when no ports are available, and dispose the registration when deactivating. The provider supplies addresses; the API does not create tunnels or change their visibility. Results from disposed registrations are discarded.
|
package/dist/index.d.ts
CHANGED
|
@@ -62,3 +62,6 @@ export type { ShowQuickPickOptions } from './parts/ShowQuickPickOptions/ShowQuic
|
|
|
62
62
|
export type { StatusBarItem, StatusBarItemProvider, StatusBarItemProviderHandle, StatusBarItemProviderRegistrySnapshot, } from './parts/StatusBar/StatusBar.ts';
|
|
63
63
|
export { setRemoteDescription, startWebRtcAudioStream, stopWebRtcAudioStream, readMicLevels } from './parts/WebRtc/WebRtc.ts';
|
|
64
64
|
export type { MicLevelsResult, ReadMicLevelOptions, SetRemoteDescriptionOptions, StartWebRpcAudioStreamOptions, WebRtcAudioConstraints, } from './parts/WebRtc/WebRtc.ts';
|
|
65
|
+
export { registerPortProvider } from './parts/PortProviderRegistry/PortProviderRegistry.ts';
|
|
66
|
+
export type { PortProvider } from './parts/PortProvider/PortProvider.ts';
|
|
67
|
+
export type { ForwardedPort } from './parts/ForwardedPort/ForwardedPort.ts';
|
package/dist/index.js
CHANGED
|
@@ -33,3 +33,4 @@ export { getStatusBarItemProviderRegistrySnapshot, registerStatusBarItemProvider
|
|
|
33
33
|
export { handleExtensionManagementMessagePort } from "./parts/HandleExtensionManagementMessagePort/HandleExtensionManagementMessagePort.js";
|
|
34
34
|
export { createNodeRpc, createRpc } from "./parts/Rpc/Rpc.js";
|
|
35
35
|
export { setRemoteDescription, startWebRtcAudioStream, stopWebRtcAudioStream, readMicLevels } from "./parts/WebRtc/WebRtc.js";
|
|
36
|
+
export { registerPortProvider } from "./parts/PortProviderRegistry/PortProviderRegistry.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Disposable } from '../Disposable/Disposable.ts';
|
|
2
|
+
import type { ForwardedPort } from '../ForwardedPort/ForwardedPort.ts';
|
|
3
|
+
import type { PortProvider } from '../PortProvider/PortProvider.ts';
|
|
4
|
+
export declare const providePorts: (workspaceUri: string) => Promise<readonly ForwardedPort[]>;
|
|
5
|
+
export declare const registerPortProvider: (provider: PortProvider) => Disposable;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as ExtensionApiCommandRegistry from "../ExtensionApiCommandRegistry/ExtensionApiCommandRegistry.js";
|
|
2
|
+
import { ExtensionApiError } from "../ExtensionApiError/ExtensionApiError.js";
|
|
3
|
+
const providers = new Map();
|
|
4
|
+
export const providePorts = async (workspaceUri) => {
|
|
5
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(workspaceUri)?.[1].toLowerCase();
|
|
6
|
+
const provider = scheme ? providers.get(scheme) : undefined;
|
|
7
|
+
if (!provider) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
const ports = await provider.providePorts(workspaceUri);
|
|
11
|
+
if (!Array.isArray(ports) ||
|
|
12
|
+
ports.some((port) => !port || !Number.isSafeInteger(port.port) || port.port < 1 || port.port > 65_535 || typeof port.forwardedAddress !== 'string')) {
|
|
13
|
+
throw new ExtensionApiError('port provider returned invalid ports');
|
|
14
|
+
}
|
|
15
|
+
return providers.get(scheme) === provider ? ports : [];
|
|
16
|
+
};
|
|
17
|
+
export const registerPortProvider = (provider) => {
|
|
18
|
+
if (!provider ||
|
|
19
|
+
typeof provider.scheme !== 'string' ||
|
|
20
|
+
!/^[a-z][a-z0-9+.-]*$/.test(provider.scheme) ||
|
|
21
|
+
typeof provider.providePorts !== 'function') {
|
|
22
|
+
throw new ExtensionApiError('port provider requires a URI scheme and providePorts function');
|
|
23
|
+
}
|
|
24
|
+
const { scheme } = provider;
|
|
25
|
+
if (providers.has(scheme)) {
|
|
26
|
+
throw new ExtensionApiError(`port provider for ${scheme} is already registered`);
|
|
27
|
+
}
|
|
28
|
+
providers.set(scheme, provider);
|
|
29
|
+
ExtensionApiCommandRegistry.registerCommandMap({ 'ExtensionApi.providePorts': providePorts });
|
|
30
|
+
return {
|
|
31
|
+
dispose() {
|
|
32
|
+
if (providers.get(scheme) === provider) {
|
|
33
|
+
providers.delete(scheme);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -73,9 +73,11 @@ interface ViewBase<State> {
|
|
|
73
73
|
readonly preferredLocation?: ViewPreferredLocation;
|
|
74
74
|
readonly title?: string;
|
|
75
75
|
}
|
|
76
|
-
export interface InstanceView<State = unknown> extends ViewBase<State> {
|
|
76
|
+
export interface InstanceView<State = unknown, ComponentState = unknown> extends ViewBase<State> {
|
|
77
77
|
readonly create: (context?: ViewContext) => State | Promise<State>;
|
|
78
78
|
readonly createInitialState?: never;
|
|
79
|
+
readonly getComponentState?: (instance: State) => ComponentState | Promise<ComponentState>;
|
|
80
|
+
readonly setComponentState?: (instance: State, state: ComponentState) => void | Promise<void>;
|
|
79
81
|
}
|
|
80
82
|
export interface StatefulView<State = unknown> extends ViewBase<State> {
|
|
81
83
|
readonly create?: never;
|
|
@@ -95,7 +97,7 @@ export interface StatefulView<State = unknown> extends ViewBase<State> {
|
|
|
95
97
|
readonly renderTitle?: (state: State) => string | Promise<string>;
|
|
96
98
|
readonly saveState?: (state: State) => unknown;
|
|
97
99
|
}
|
|
98
|
-
export type View<State = unknown> = InstanceView<State> | StatefulView<State>;
|
|
100
|
+
export type View<State = unknown, ComponentState = unknown> = InstanceView<State, ComponentState> | StatefulView<State>;
|
|
99
101
|
export interface RegisteredView {
|
|
100
102
|
readonly displayName?: string;
|
|
101
103
|
readonly eventListeners?: readonly DomEventListener[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type VirtualDomNode } from '@lvce-editor/virtual-dom-worker';
|
|
2
2
|
import type { Disposable } from '../Disposable/Disposable.ts';
|
|
3
3
|
import type { MenuEntry, View, ViewAction, ViewContext, ViewEvent, ViewRegistrySnapshot, ViewRenderResult } from '../View/View.ts';
|
|
4
|
-
export declare const registerView: <State>(view: View<State>) => Disposable;
|
|
4
|
+
export declare const registerView: <State, ComponentState = unknown>(view: View<State, ComponentState>) => Disposable;
|
|
5
5
|
export declare const executeViewProvider: (id: string) => unknown;
|
|
6
6
|
export declare const createViewInstance: (viewId: string, uid: number, context?: ViewContext) => Promise<ViewRenderResult>;
|
|
7
7
|
export declare const dispatchViewEvent: (uid: number, event: ViewEvent) => Promise<ViewRenderResult>;
|
|
@@ -91,6 +91,11 @@ const assertView = (view) => {
|
|
|
91
91
|
if (typeof candidate.createInitialState === 'function' && typeof candidate.render !== 'function') {
|
|
92
92
|
throw new ExtensionApiError(`view ${view.id} is missing render function`);
|
|
93
93
|
}
|
|
94
|
+
if (!isStatefulView(view) &&
|
|
95
|
+
(view.getComponentState !== undefined || view.setComponentState !== undefined) &&
|
|
96
|
+
(typeof view.getComponentState !== 'function' || typeof view.setComponentState !== 'function')) {
|
|
97
|
+
throw new ExtensionApiError(`view ${view.id} component state requires getComponentState and setComponentState`);
|
|
98
|
+
}
|
|
94
99
|
if (view.id in views) {
|
|
95
100
|
throw new ExtensionApiError(`view ${view.id} is already registered`);
|
|
96
101
|
}
|
|
@@ -109,7 +114,7 @@ const toRegisteredView = (view) => {
|
|
|
109
114
|
id: view.id,
|
|
110
115
|
name: view.name,
|
|
111
116
|
preferredLocation: view.preferredLocation || 'sideBar',
|
|
112
|
-
...(isStatefulView(view) && { stateful: true }),
|
|
117
|
+
...((isStatefulView(view) || typeof view.getComponentState === 'function') && { stateful: true }),
|
|
113
118
|
title: displayName,
|
|
114
119
|
};
|
|
115
120
|
if (view.kind) {
|
|
@@ -625,13 +630,29 @@ export const saveViewInstanceState = async (uid) => {
|
|
|
625
630
|
return instance.saveState();
|
|
626
631
|
};
|
|
627
632
|
export const getViewInstanceState = (uid) => {
|
|
628
|
-
getVirtualDomInstance(uid);
|
|
633
|
+
const instance = getVirtualDomInstance(uid);
|
|
634
|
+
const view = views[contextViewIds[uid]];
|
|
635
|
+
if (!isStatefulView(view) && view.getComponentState) {
|
|
636
|
+
return view.getComponentState(instance);
|
|
637
|
+
}
|
|
629
638
|
return ViewletStates.get(uid);
|
|
630
639
|
};
|
|
631
640
|
export const setViewInstanceState = async (uid, newState) => {
|
|
632
|
-
getVirtualDomInstance(uid);
|
|
641
|
+
const instance = getVirtualDomInstance(uid);
|
|
633
642
|
const viewId = contextViewIds[uid];
|
|
643
|
+
const view = views[viewId];
|
|
634
644
|
assertViewState(viewId, newState);
|
|
645
|
+
if (!isStatefulView(view) && view.getComponentState && view.setComponentState) {
|
|
646
|
+
const oldState = structuredClone(await view.getComponentState(instance));
|
|
647
|
+
try {
|
|
648
|
+
await view.setComponentState(instance, newState);
|
|
649
|
+
return await renderViewInstance(uid);
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
await view.setComponentState(instance, oldState);
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
635
656
|
const oldState = ViewletStates.get(uid);
|
|
636
657
|
ViewletStates.replace(uid, newState);
|
|
637
658
|
try {
|