@rainbow-robotics/plugin-ui 1.10.1-dev.0 → 1.10.1
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 +246 -246
- package/build-dist.mjs +80 -80
- package/dist/host-ui.js.map +1 -1
- package/dist/manifest.js.map +1 -1
- package/dist/react.d.ts +12 -12
- package/dist/react.js.map +1 -1
- package/dist/sdk.js.map +1 -1
- package/dist/viteHost.js.map +1 -1
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js.map +1 -1
- package/package.json +1 -1
- package/src/host-ui.ts +95 -95
- package/src/host.tsx +277 -277
- package/src/index.d.ts +1 -1
- package/src/manifest.ts +135 -135
- package/src/pluginData.tsx +240 -240
- package/src/react.tsx +13 -13
- package/src/sdk.ts +175 -175
- package/src/viteHost.ts +309 -309
- package/src/ws.ts +256 -256
package/src/pluginData.tsx
CHANGED
|
@@ -1,240 +1,240 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
2
|
-
/**
|
|
3
|
-
* Plugin data seam (host-side). Resolves where each plugin's UI/assets load
|
|
4
|
-
* from. `muscat-pk dev` sets `window.__RB_PREVIEW_PLUGIN_ID__`; that id routes
|
|
5
|
-
* to preview-host, everything else to the installed-plugin back end, so both
|
|
6
|
-
* coexist. The host passes its dialog/toast as `hostServices`, registered via
|
|
7
|
-
* setHostServices (see ./host-ui).
|
|
8
|
-
*/
|
|
9
|
-
import {
|
|
10
|
-
createContext,
|
|
11
|
-
useContext,
|
|
12
|
-
useEffect,
|
|
13
|
-
useMemo,
|
|
14
|
-
useState,
|
|
15
|
-
type PropsWithChildren,
|
|
16
|
-
} from "react";
|
|
17
|
-
import type { PluginManifest } from "./manifest";
|
|
18
|
-
// Package specifier, not a relative path — see host.tsx for why (source vs dist
|
|
19
|
-
// host-ui would be two `services` singletons).
|
|
20
|
-
import {
|
|
21
|
-
setHostServices,
|
|
22
|
-
type PluginUIServices,
|
|
23
|
-
} from "@rainbow-robotics/plugin-ui/host-ui";
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Where/how to load a plugin's UI module — no iframe involved, the host
|
|
27
|
-
* mounts it directly.
|
|
28
|
-
*
|
|
29
|
-
* - "dev": the plugin's own Vite dev server (muscat-pk dev). `bootstrapUrl` is
|
|
30
|
-
* a plain ESM module exposing `mount(container, rb, hostServices)`; react
|
|
31
|
-
* and plugin-ui resolve from that dev server's own module graph, so this
|
|
32
|
-
* mount is a fully self-consistent, independently-rooted React tree.
|
|
33
|
-
* - "esm": a built plugin (`muscat-pk build` / robot runtime), a plain ESM
|
|
34
|
-
* module the host dynamically imports. react / rb-components / plugin-ui
|
|
35
|
-
* context modules are left external as bare specifiers, resolved by the host
|
|
36
|
-
* page's import map to the host's own already-loaded instances — so it mounts
|
|
37
|
-
* as a genuine nested React tree with working Context (RbContext, host-ui)
|
|
38
|
-
* and no duplicate React instance.
|
|
39
|
-
*/
|
|
40
|
-
export type PluginModuleSource =
|
|
41
|
-
| { kind: "dev"; bootstrapUrl: string }
|
|
42
|
-
| { kind: "esm"; entryUrl: string };
|
|
43
|
-
|
|
44
|
-
export interface PluginDataSource {
|
|
45
|
-
/** Preview plugin manifest (only while a dev plugin is being previewed). */
|
|
46
|
-
usePluginManifest: () => { data: PluginManifest | null; isLoading: boolean };
|
|
47
|
-
/** Where/how to load a plugin's UI module for a given UI entry. */
|
|
48
|
-
getModuleSource: (pluginId: string, ui: string) => PluginModuleSource;
|
|
49
|
-
/** URL of a plugin asset (e.g. an icon) by its manifest-relative path. */
|
|
50
|
-
getAssetUrl: (pluginId: string, assetPath: string) => string;
|
|
51
|
-
/** Socket.IO server URL the plugin connects to. */
|
|
52
|
-
getSocketUrl: (pluginId: string) => string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const PluginDataContext = createContext<PluginDataSource | null>(null);
|
|
56
|
-
|
|
57
|
-
export function usePluginData(): PluginDataSource {
|
|
58
|
-
const ctx = useContext(PluginDataContext);
|
|
59
|
-
if (!ctx) {
|
|
60
|
-
throw new Error("usePluginData must be used within a PluginDataProvider");
|
|
61
|
-
}
|
|
62
|
-
return ctx;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/* -------------------------------------------------------------------------- */
|
|
66
|
-
/* Preview (preview-host: the developer's locally-built plugin) */
|
|
67
|
-
/* -------------------------------------------------------------------------- */
|
|
68
|
-
|
|
69
|
-
type PreviewWindow = Window & {
|
|
70
|
-
/** Plugin id under `muscat-pk dev` — requests for it route to preview-host. */
|
|
71
|
-
__RB_PREVIEW_PLUGIN_ID__?: string;
|
|
72
|
-
/** Base URL preview-host serves the dev plugin's manifest/assets from. */
|
|
73
|
-
__RB_PLUGIN_PREVIEW_BASE__?: string;
|
|
74
|
-
/** Where the plugin's OWN Vite dev server lives (distinct from the above —
|
|
75
|
-
* that's preview-host's own static route; this is the plugin's dev server,
|
|
76
|
-
* which serves its bootstrap module for direct mounting). */
|
|
77
|
-
__RB_PLUGIN_DEV_SERVER_URL__?: string;
|
|
78
|
-
/** Override URL for the plugin manifest. */
|
|
79
|
-
__RB_PLUGIN_PREVIEW_MANIFEST__?: string;
|
|
80
|
-
/** Socket.IO URL — injected by muscat-pk CLI when starting preview. */
|
|
81
|
-
__RB_PLUGIN_PREVIEW_SOCKET_URL__?: string;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const win = (): PreviewWindow | undefined =>
|
|
85
|
-
typeof window !== "undefined" ? (window as PreviewWindow) : undefined;
|
|
86
|
-
|
|
87
|
-
const isViteDev = import.meta.env.DEV;
|
|
88
|
-
|
|
89
|
-
/** The plugin id being previewed via `muscat-pk dev`, if any. */
|
|
90
|
-
function previewPluginId(): string | undefined {
|
|
91
|
-
return (
|
|
92
|
-
win()?.__RB_PREVIEW_PLUGIN_ID__ ||
|
|
93
|
-
(isViteDev ? "com.example.demo-plugin" : undefined)
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Whether a given plugin id is the one being developed locally. */
|
|
98
|
-
function isPreviewPlugin(pluginId: string): boolean {
|
|
99
|
-
const id = previewPluginId();
|
|
100
|
-
return !!id && id === pluginId;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Where preview-host serves the dev plugin's manifest/assets. */
|
|
104
|
-
function previewBase(): string {
|
|
105
|
-
const base =
|
|
106
|
-
win()?.__RB_PLUGIN_PREVIEW_BASE__ ||
|
|
107
|
-
(isViteDev ? "http://localhost:4180/plugin-dev" : "/plugin-dev");
|
|
108
|
-
return base.replace(/\/$/, "");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Where the plugin's own Vite dev server lives (for the bootstrap module). */
|
|
112
|
-
function devServerUrl(): string {
|
|
113
|
-
const base =
|
|
114
|
-
win()?.__RB_PLUGIN_DEV_SERVER_URL__ ||
|
|
115
|
-
(isViteDev ? "http://localhost:4181" : win()?.location.origin || "");
|
|
116
|
-
return base.replace(/\/$/, "");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function previewManifestUrl(): string {
|
|
120
|
-
return (
|
|
121
|
-
win()?.__RB_PLUGIN_PREVIEW_MANIFEST__ || `${previewBase()}/manifest.json`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function previewSocketUrl(): string {
|
|
126
|
-
return win()?.__RB_PLUGIN_PREVIEW_SOCKET_URL__ || "ws://127.0.0.1:10000";
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Shared, deduped fetch — several components read the manifest, so a single
|
|
130
|
-
// module-level promise avoids duplicate requests (react-query is not needed).
|
|
131
|
-
let manifestPromise: Promise<PluginManifest> | null = null;
|
|
132
|
-
function loadPreviewManifest(): Promise<PluginManifest> {
|
|
133
|
-
if (!manifestPromise) {
|
|
134
|
-
manifestPromise = (async () => {
|
|
135
|
-
try {
|
|
136
|
-
const url = previewManifestUrl();
|
|
137
|
-
const res = await fetch(url);
|
|
138
|
-
if (!res.ok) throw new Error(`GET ${url} failed: ${res.status}`);
|
|
139
|
-
return res.json() as Promise<PluginManifest>;
|
|
140
|
-
} catch (e) {
|
|
141
|
-
if (win()?.__RB_PLUGIN_PREVIEW_BASE__ || win()?.__RB_PREVIEW_PLUGIN_ID__) {
|
|
142
|
-
console.error(e);
|
|
143
|
-
} else {
|
|
144
|
-
console.warn("[plugin-ui] no preview-host at", previewBase(), "— run `muscat-pk dev` to preview a plugin");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
})();
|
|
148
|
-
}
|
|
149
|
-
return manifestPromise;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function usePreviewManifest(): {
|
|
153
|
-
data: PluginManifest | null;
|
|
154
|
-
isLoading: boolean;
|
|
155
|
-
} {
|
|
156
|
-
const enabled = !!previewPluginId();
|
|
157
|
-
const [data, setData] = useState<PluginManifest | null>(null);
|
|
158
|
-
const [isLoading, setIsLoading] = useState(enabled);
|
|
159
|
-
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (!enabled) {
|
|
162
|
-
setData(null);
|
|
163
|
-
setIsLoading(false);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
let alive = true;
|
|
167
|
-
setIsLoading(true);
|
|
168
|
-
loadPreviewManifest()
|
|
169
|
-
.then((m) => alive && setData(m))
|
|
170
|
-
.finally(() => alive && setIsLoading(false));
|
|
171
|
-
return () => {
|
|
172
|
-
alive = false;
|
|
173
|
-
};
|
|
174
|
-
}, [enabled]);
|
|
175
|
-
|
|
176
|
-
return { data, isLoading };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/* -------------------------------------------------------------------------- */
|
|
180
|
-
/* Provider */
|
|
181
|
-
/* -------------------------------------------------------------------------- */
|
|
182
|
-
|
|
183
|
-
export interface PluginDataProviderProps {
|
|
184
|
-
/**
|
|
185
|
-
* The host's real dialog/toast, registered for plugins to use via
|
|
186
|
-
* `@rainbow-robotics/plugin-ui/host-ui`. The host builds these ONCE inside
|
|
187
|
-
* its own React tree (calling its real useDialog) and passes the resulting
|
|
188
|
-
* plain functions here — safe to call from a plugin's separate React
|
|
189
|
-
* instance (dev). See ./host-ui.
|
|
190
|
-
*/
|
|
191
|
-
hostServices: PluginUIServices;
|
|
192
|
-
/** Socket.IO URL for installed (non-preview) plugins. */
|
|
193
|
-
wsUrl?: string;
|
|
194
|
-
/** URL prefix the host serves installed plugins from. */
|
|
195
|
-
liveBase?: string;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function PluginDataProvider({
|
|
199
|
-
children,
|
|
200
|
-
hostServices,
|
|
201
|
-
wsUrl = "ws://127.0.0.1:10000",
|
|
202
|
-
liveBase = "http://localhost:3000/common/plugin",
|
|
203
|
-
}: PropsWithChildren<PluginDataProviderProps>) {
|
|
204
|
-
// Register in an effect (setHostServices is a side effect, not render-safe).
|
|
205
|
-
// Layout effect so it runs before children (a mounting PluginHost) read it.
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
setHostServices(hostServices);
|
|
208
|
-
}, [hostServices]);
|
|
209
|
-
|
|
210
|
-
const source = useMemo<PluginDataSource>(() => {
|
|
211
|
-
const base = liveBase.replace(/\/$/, "");
|
|
212
|
-
return {
|
|
213
|
-
usePluginManifest: usePreviewManifest,
|
|
214
|
-
getModuleSource: (pluginId, ui) =>
|
|
215
|
-
isPreviewPlugin(pluginId)
|
|
216
|
-
? {
|
|
217
|
-
kind: "dev",
|
|
218
|
-
bootstrapUrl: `${devServerUrl()}/@id/__x00__rb-bootstrap:${ui}`,
|
|
219
|
-
}
|
|
220
|
-
: {
|
|
221
|
-
kind: "esm",
|
|
222
|
-
// The plugin's built ESM entry for this ui — bare externals
|
|
223
|
-
// inside are resolved by the host page's import map.
|
|
224
|
-
entryUrl: `${base}/${pluginId}/${ui}.js`,
|
|
225
|
-
},
|
|
226
|
-
getAssetUrl: (pluginId, assetPath) =>
|
|
227
|
-
isPreviewPlugin(pluginId)
|
|
228
|
-
? `${previewBase()}/${assetPath}`
|
|
229
|
-
: `${base}/${pluginId}/${assetPath}`,
|
|
230
|
-
getSocketUrl: (pluginId) =>
|
|
231
|
-
isPreviewPlugin(pluginId) ? previewSocketUrl() : wsUrl,
|
|
232
|
-
};
|
|
233
|
-
}, [wsUrl, liveBase]);
|
|
234
|
-
|
|
235
|
-
return (
|
|
236
|
-
<PluginDataContext.Provider value={source}>
|
|
237
|
-
{children}
|
|
238
|
-
</PluginDataContext.Provider>
|
|
239
|
-
);
|
|
240
|
-
}
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
/**
|
|
3
|
+
* Plugin data seam (host-side). Resolves where each plugin's UI/assets load
|
|
4
|
+
* from. `muscat-pk dev` sets `window.__RB_PREVIEW_PLUGIN_ID__`; that id routes
|
|
5
|
+
* to preview-host, everything else to the installed-plugin back end, so both
|
|
6
|
+
* coexist. The host passes its dialog/toast as `hostServices`, registered via
|
|
7
|
+
* setHostServices (see ./host-ui).
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
createContext,
|
|
11
|
+
useContext,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useState,
|
|
15
|
+
type PropsWithChildren,
|
|
16
|
+
} from "react";
|
|
17
|
+
import type { PluginManifest } from "./manifest";
|
|
18
|
+
// Package specifier, not a relative path — see host.tsx for why (source vs dist
|
|
19
|
+
// host-ui would be two `services` singletons).
|
|
20
|
+
import {
|
|
21
|
+
setHostServices,
|
|
22
|
+
type PluginUIServices,
|
|
23
|
+
} from "@rainbow-robotics/plugin-ui/host-ui";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Where/how to load a plugin's UI module — no iframe involved, the host
|
|
27
|
+
* mounts it directly.
|
|
28
|
+
*
|
|
29
|
+
* - "dev": the plugin's own Vite dev server (muscat-pk dev). `bootstrapUrl` is
|
|
30
|
+
* a plain ESM module exposing `mount(container, rb, hostServices)`; react
|
|
31
|
+
* and plugin-ui resolve from that dev server's own module graph, so this
|
|
32
|
+
* mount is a fully self-consistent, independently-rooted React tree.
|
|
33
|
+
* - "esm": a built plugin (`muscat-pk build` / robot runtime), a plain ESM
|
|
34
|
+
* module the host dynamically imports. react / rb-components / plugin-ui
|
|
35
|
+
* context modules are left external as bare specifiers, resolved by the host
|
|
36
|
+
* page's import map to the host's own already-loaded instances — so it mounts
|
|
37
|
+
* as a genuine nested React tree with working Context (RbContext, host-ui)
|
|
38
|
+
* and no duplicate React instance.
|
|
39
|
+
*/
|
|
40
|
+
export type PluginModuleSource =
|
|
41
|
+
| { kind: "dev"; bootstrapUrl: string }
|
|
42
|
+
| { kind: "esm"; entryUrl: string };
|
|
43
|
+
|
|
44
|
+
export interface PluginDataSource {
|
|
45
|
+
/** Preview plugin manifest (only while a dev plugin is being previewed). */
|
|
46
|
+
usePluginManifest: () => { data: PluginManifest | null; isLoading: boolean };
|
|
47
|
+
/** Where/how to load a plugin's UI module for a given UI entry. */
|
|
48
|
+
getModuleSource: (pluginId: string, ui: string) => PluginModuleSource;
|
|
49
|
+
/** URL of a plugin asset (e.g. an icon) by its manifest-relative path. */
|
|
50
|
+
getAssetUrl: (pluginId: string, assetPath: string) => string;
|
|
51
|
+
/** Socket.IO server URL the plugin connects to. */
|
|
52
|
+
getSocketUrl: (pluginId: string) => string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const PluginDataContext = createContext<PluginDataSource | null>(null);
|
|
56
|
+
|
|
57
|
+
export function usePluginData(): PluginDataSource {
|
|
58
|
+
const ctx = useContext(PluginDataContext);
|
|
59
|
+
if (!ctx) {
|
|
60
|
+
throw new Error("usePluginData must be used within a PluginDataProvider");
|
|
61
|
+
}
|
|
62
|
+
return ctx;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* -------------------------------------------------------------------------- */
|
|
66
|
+
/* Preview (preview-host: the developer's locally-built plugin) */
|
|
67
|
+
/* -------------------------------------------------------------------------- */
|
|
68
|
+
|
|
69
|
+
type PreviewWindow = Window & {
|
|
70
|
+
/** Plugin id under `muscat-pk dev` — requests for it route to preview-host. */
|
|
71
|
+
__RB_PREVIEW_PLUGIN_ID__?: string;
|
|
72
|
+
/** Base URL preview-host serves the dev plugin's manifest/assets from. */
|
|
73
|
+
__RB_PLUGIN_PREVIEW_BASE__?: string;
|
|
74
|
+
/** Where the plugin's OWN Vite dev server lives (distinct from the above —
|
|
75
|
+
* that's preview-host's own static route; this is the plugin's dev server,
|
|
76
|
+
* which serves its bootstrap module for direct mounting). */
|
|
77
|
+
__RB_PLUGIN_DEV_SERVER_URL__?: string;
|
|
78
|
+
/** Override URL for the plugin manifest. */
|
|
79
|
+
__RB_PLUGIN_PREVIEW_MANIFEST__?: string;
|
|
80
|
+
/** Socket.IO URL — injected by muscat-pk CLI when starting preview. */
|
|
81
|
+
__RB_PLUGIN_PREVIEW_SOCKET_URL__?: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const win = (): PreviewWindow | undefined =>
|
|
85
|
+
typeof window !== "undefined" ? (window as PreviewWindow) : undefined;
|
|
86
|
+
|
|
87
|
+
const isViteDev = import.meta.env.DEV;
|
|
88
|
+
|
|
89
|
+
/** The plugin id being previewed via `muscat-pk dev`, if any. */
|
|
90
|
+
function previewPluginId(): string | undefined {
|
|
91
|
+
return (
|
|
92
|
+
win()?.__RB_PREVIEW_PLUGIN_ID__ ||
|
|
93
|
+
(isViteDev ? "com.example.demo-plugin" : undefined)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Whether a given plugin id is the one being developed locally. */
|
|
98
|
+
function isPreviewPlugin(pluginId: string): boolean {
|
|
99
|
+
const id = previewPluginId();
|
|
100
|
+
return !!id && id === pluginId;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Where preview-host serves the dev plugin's manifest/assets. */
|
|
104
|
+
function previewBase(): string {
|
|
105
|
+
const base =
|
|
106
|
+
win()?.__RB_PLUGIN_PREVIEW_BASE__ ||
|
|
107
|
+
(isViteDev ? "http://localhost:4180/plugin-dev" : "/plugin-dev");
|
|
108
|
+
return base.replace(/\/$/, "");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Where the plugin's own Vite dev server lives (for the bootstrap module). */
|
|
112
|
+
function devServerUrl(): string {
|
|
113
|
+
const base =
|
|
114
|
+
win()?.__RB_PLUGIN_DEV_SERVER_URL__ ||
|
|
115
|
+
(isViteDev ? "http://localhost:4181" : win()?.location.origin || "");
|
|
116
|
+
return base.replace(/\/$/, "");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function previewManifestUrl(): string {
|
|
120
|
+
return (
|
|
121
|
+
win()?.__RB_PLUGIN_PREVIEW_MANIFEST__ || `${previewBase()}/manifest.json`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function previewSocketUrl(): string {
|
|
126
|
+
return win()?.__RB_PLUGIN_PREVIEW_SOCKET_URL__ || "ws://127.0.0.1:10000";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Shared, deduped fetch — several components read the manifest, so a single
|
|
130
|
+
// module-level promise avoids duplicate requests (react-query is not needed).
|
|
131
|
+
let manifestPromise: Promise<PluginManifest> | null = null;
|
|
132
|
+
function loadPreviewManifest(): Promise<PluginManifest> {
|
|
133
|
+
if (!manifestPromise) {
|
|
134
|
+
manifestPromise = (async () => {
|
|
135
|
+
try {
|
|
136
|
+
const url = previewManifestUrl();
|
|
137
|
+
const res = await fetch(url);
|
|
138
|
+
if (!res.ok) throw new Error(`GET ${url} failed: ${res.status}`);
|
|
139
|
+
return res.json() as Promise<PluginManifest>;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
if (win()?.__RB_PLUGIN_PREVIEW_BASE__ || win()?.__RB_PREVIEW_PLUGIN_ID__) {
|
|
142
|
+
console.error(e);
|
|
143
|
+
} else {
|
|
144
|
+
console.warn("[plugin-ui] no preview-host at", previewBase(), "— run `muscat-pk dev` to preview a plugin");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
}
|
|
149
|
+
return manifestPromise;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function usePreviewManifest(): {
|
|
153
|
+
data: PluginManifest | null;
|
|
154
|
+
isLoading: boolean;
|
|
155
|
+
} {
|
|
156
|
+
const enabled = !!previewPluginId();
|
|
157
|
+
const [data, setData] = useState<PluginManifest | null>(null);
|
|
158
|
+
const [isLoading, setIsLoading] = useState(enabled);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (!enabled) {
|
|
162
|
+
setData(null);
|
|
163
|
+
setIsLoading(false);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let alive = true;
|
|
167
|
+
setIsLoading(true);
|
|
168
|
+
loadPreviewManifest()
|
|
169
|
+
.then((m) => alive && setData(m))
|
|
170
|
+
.finally(() => alive && setIsLoading(false));
|
|
171
|
+
return () => {
|
|
172
|
+
alive = false;
|
|
173
|
+
};
|
|
174
|
+
}, [enabled]);
|
|
175
|
+
|
|
176
|
+
return { data, isLoading };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* -------------------------------------------------------------------------- */
|
|
180
|
+
/* Provider */
|
|
181
|
+
/* -------------------------------------------------------------------------- */
|
|
182
|
+
|
|
183
|
+
export interface PluginDataProviderProps {
|
|
184
|
+
/**
|
|
185
|
+
* The host's real dialog/toast, registered for plugins to use via
|
|
186
|
+
* `@rainbow-robotics/plugin-ui/host-ui`. The host builds these ONCE inside
|
|
187
|
+
* its own React tree (calling its real useDialog) and passes the resulting
|
|
188
|
+
* plain functions here — safe to call from a plugin's separate React
|
|
189
|
+
* instance (dev). See ./host-ui.
|
|
190
|
+
*/
|
|
191
|
+
hostServices: PluginUIServices;
|
|
192
|
+
/** Socket.IO URL for installed (non-preview) plugins. */
|
|
193
|
+
wsUrl?: string;
|
|
194
|
+
/** URL prefix the host serves installed plugins from. */
|
|
195
|
+
liveBase?: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function PluginDataProvider({
|
|
199
|
+
children,
|
|
200
|
+
hostServices,
|
|
201
|
+
wsUrl = "ws://127.0.0.1:10000",
|
|
202
|
+
liveBase = "http://localhost:3000/common/plugin",
|
|
203
|
+
}: PropsWithChildren<PluginDataProviderProps>) {
|
|
204
|
+
// Register in an effect (setHostServices is a side effect, not render-safe).
|
|
205
|
+
// Layout effect so it runs before children (a mounting PluginHost) read it.
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
setHostServices(hostServices);
|
|
208
|
+
}, [hostServices]);
|
|
209
|
+
|
|
210
|
+
const source = useMemo<PluginDataSource>(() => {
|
|
211
|
+
const base = liveBase.replace(/\/$/, "");
|
|
212
|
+
return {
|
|
213
|
+
usePluginManifest: usePreviewManifest,
|
|
214
|
+
getModuleSource: (pluginId, ui) =>
|
|
215
|
+
isPreviewPlugin(pluginId)
|
|
216
|
+
? {
|
|
217
|
+
kind: "dev",
|
|
218
|
+
bootstrapUrl: `${devServerUrl()}/@id/__x00__rb-bootstrap:${ui}`,
|
|
219
|
+
}
|
|
220
|
+
: {
|
|
221
|
+
kind: "esm",
|
|
222
|
+
// The plugin's built ESM entry for this ui — bare externals
|
|
223
|
+
// inside are resolved by the host page's import map.
|
|
224
|
+
entryUrl: `${base}/${pluginId}/${ui}.js`,
|
|
225
|
+
},
|
|
226
|
+
getAssetUrl: (pluginId, assetPath) =>
|
|
227
|
+
isPreviewPlugin(pluginId)
|
|
228
|
+
? `${previewBase()}/${assetPath}`
|
|
229
|
+
: `${base}/${pluginId}/${assetPath}`,
|
|
230
|
+
getSocketUrl: (pluginId) =>
|
|
231
|
+
isPreviewPlugin(pluginId) ? previewSocketUrl() : wsUrl,
|
|
232
|
+
};
|
|
233
|
+
}, [wsUrl, liveBase]);
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<PluginDataContext.Provider value={source}>
|
|
237
|
+
{children}
|
|
238
|
+
</PluginDataContext.Provider>
|
|
239
|
+
);
|
|
240
|
+
}
|
package/src/react.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Author component surface — re-exports the @repo/rb-components new_components
|
|
3
|
-
* barrel, plus the sdk hooks.
|
|
4
|
-
*
|
|
5
|
-
* import { RBSlider, RBLabelButton, useRb } from "@rainbow-robotics/plugin-ui/react";
|
|
6
|
-
*
|
|
7
|
-
* The runtime import-map shim (./viteHost.ts) reads this module's exports
|
|
8
|
-
* rather than a hardcoded list, so the two stay in sync.
|
|
9
|
-
*/
|
|
10
|
-
export * from "@repo/rb-components/new_components";
|
|
11
|
-
|
|
12
|
-
export { useRb, useRbFormData, useRbSave } from "./sdk.js";
|
|
13
|
-
export type { RbSdk } from "./sdk";
|
|
1
|
+
/**
|
|
2
|
+
* Author component surface — re-exports the @repo/rb-components new_components
|
|
3
|
+
* barrel, plus the sdk hooks.
|
|
4
|
+
*
|
|
5
|
+
* import { RBSlider, RBLabelButton, useRb } from "@rainbow-robotics/plugin-ui/react";
|
|
6
|
+
*
|
|
7
|
+
* The runtime import-map shim (./viteHost.ts) reads this module's exports
|
|
8
|
+
* rather than a hardcoded list, so the two stay in sync.
|
|
9
|
+
*/
|
|
10
|
+
export * from "@repo/rb-components/new_components";
|
|
11
|
+
|
|
12
|
+
export { useRb, useRbFormData, useRbSave } from "./sdk.js";
|
|
13
|
+
export type { RbSdk } from "./sdk";
|