@rainbow-robotics/plugin-ui 1.3.8-dev.4 → 1.10.1-dev.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 +13 -13
- 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/sdk.ts
CHANGED
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
/**
|
|
3
|
-
* Plugin runtime SDK (`useRb()`).
|
|
4
|
-
*
|
|
5
|
-
* pub/sub/action/serve talk to the Zenoh router over WebSocket. Topics are
|
|
6
|
-
* namespaced to {pluginId}/... ; a leading "/" makes the path absolute.
|
|
7
|
-
* alert/confirm/prompt/toast delegate to the host.
|
|
8
|
-
*/
|
|
9
|
-
import { createContext, useContext, useEffect, useRef } from "react";
|
|
10
|
-
import type {
|
|
11
|
-
WsSubscriber as Subscriber,
|
|
12
|
-
WsQueryable as Queryable,
|
|
13
|
-
} from "./ws";
|
|
14
|
-
// Type-only import — no runtime coupling to host-ui.ts (which itself has no
|
|
15
|
-
// dependency back on sdk.ts, so this stays one-directional).
|
|
16
|
-
import type { PluginUIServices } from "./host-ui";
|
|
17
|
-
|
|
18
|
-
export type RbNotifyType = "info" | "success" | "warning" | "error";
|
|
19
|
-
|
|
20
|
-
export interface RbSdk {
|
|
21
|
-
/**
|
|
22
|
-
* Publish JSON payload to a topic.
|
|
23
|
-
* "sensor/data" → {pluginId}/sensor/data
|
|
24
|
-
* "/broadcast/x" → broadcast/x (absolute)
|
|
25
|
-
*/
|
|
26
|
-
pub: <P = any>(topic: string, payload: P) => Promise<void>;
|
|
27
|
-
|
|
28
|
-
/** Subscribe to a topic. Returns a handle with undeclare(). */
|
|
29
|
-
sub: <P = any>(
|
|
30
|
-
topic: string,
|
|
31
|
-
handler: (payload: P) => void,
|
|
32
|
-
) => Promise<Subscriber>;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Send a request and await the first reply (Zenoh query).
|
|
36
|
-
* Use this to call a backend handler registered with rb.serve() / ZenohRouter.
|
|
37
|
-
*/
|
|
38
|
-
action: <R = any, P = any>(topic: string, payload?: P) => Promise<R>;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Register a queryable — the frontend responds to backend queries.
|
|
42
|
-
* Mirror of rb.action() on the other side.
|
|
43
|
-
*/
|
|
44
|
-
serve: <P = any, R = any>(
|
|
45
|
-
topic: string,
|
|
46
|
-
handler: (payload?: P) => R | Promise<R>,
|
|
47
|
-
) => Promise<Queryable>;
|
|
48
|
-
|
|
49
|
-
/** Show a transient notification through the host's toast system. */
|
|
50
|
-
toast: PluginUIServices["toast"];
|
|
51
|
-
|
|
52
|
-
/** Show a host alert dialog. */
|
|
53
|
-
alert: PluginUIServices["alert"];
|
|
54
|
-
/** Show a host confirm dialog. Resolves true/false. */
|
|
55
|
-
confirm: PluginUIServices["confirm"];
|
|
56
|
-
/** Show a host prompt dialog. Resolves the entered value, or undefined if cancelled. */
|
|
57
|
-
prompt: PluginUIServices["prompt"];
|
|
58
|
-
|
|
59
|
-
/** Push a step tree into the host Program page as an unsaved edit. */
|
|
60
|
-
program: PluginUIServices["program"];
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Host-provided initial data for this mount — e.g. the previously-saved
|
|
64
|
-
* values when the host re-opens a form. `undefined` for a fresh mount.
|
|
65
|
-
* Prefer the `useRbFormData()` hook over reading this directly.
|
|
66
|
-
*/
|
|
67
|
-
data?: unknown;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Register a getter the host calls (synchronously) to collect this plugin's
|
|
71
|
-
* save payload — e.g. when the host's Save button is clicked. The returned
|
|
72
|
-
* value must be JSON-serialisable (the host persists it). Returns an
|
|
73
|
-
* unregister fn. Prefer the `useRbSave()` hook.
|
|
74
|
-
*/
|
|
75
|
-
onSave: (getPayload: () => unknown) => () => void;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* PCs allowed to talk to this plugin's ROS 2 nodes. ROS discovery is robot-local
|
|
79
|
-
* (ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST); Muscat keeps this list per plugin, starts
|
|
80
|
-
* the plugin with ROS_STATIC_PEERS set to it, and restarts the plugin when it changes.
|
|
81
|
-
*/
|
|
82
|
-
rosPeers: {
|
|
83
|
-
get: () => Promise<string[]>;
|
|
84
|
-
/** Saves the list and restarts the plugin backend. Rejects on an invalid IP. */
|
|
85
|
-
set: (peers: string[]) => Promise<string[]>;
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const noopSdk: RbSdk = {
|
|
90
|
-
pub: async <P = any>(_topic?: string, _payload?: P) => {
|
|
91
|
-
console.warn("[plugin-ui] rb.pub called outside of a host context");
|
|
92
|
-
},
|
|
93
|
-
sub: async <P = any>(_topic?: string, _handler?: (payload: P) => void) => {
|
|
94
|
-
console.warn("[plugin-ui] rb.sub called outside of a host context");
|
|
95
|
-
return { undeclare: async () => {} } as unknown as Subscriber;
|
|
96
|
-
},
|
|
97
|
-
action: async <R = any, P = any>(_topic?: string, _payload?: P) => {
|
|
98
|
-
console.warn("[plugin-ui] rb.action called outside of a host context");
|
|
99
|
-
return undefined as R;
|
|
100
|
-
},
|
|
101
|
-
serve: async <P = any, R = any>(
|
|
102
|
-
_topic?: string,
|
|
103
|
-
_handler?: (payload?: P) => R,
|
|
104
|
-
) => {
|
|
105
|
-
console.warn("[plugin-ui] rb.serve called outside of a host context");
|
|
106
|
-
return { undeclare: async () => {} } as unknown as Queryable;
|
|
107
|
-
},
|
|
108
|
-
toast: (params) => {
|
|
109
|
-
console.warn("[plugin-ui] rb.toast (no host):", params);
|
|
110
|
-
},
|
|
111
|
-
alert: async (_) => {
|
|
112
|
-
console.warn("[plugin-ui] rb.alert called outside of a host context");
|
|
113
|
-
},
|
|
114
|
-
confirm: async (_) => {
|
|
115
|
-
console.warn("[plugin-ui] rb.confirm called outside of a host context");
|
|
116
|
-
return false;
|
|
117
|
-
},
|
|
118
|
-
prompt: async (_) => {
|
|
119
|
-
console.warn("[plugin-ui] rb.prompt called outside of a host context");
|
|
120
|
-
return undefined;
|
|
121
|
-
},
|
|
122
|
-
program: {
|
|
123
|
-
setSteps: async () => {
|
|
124
|
-
console.warn("[plugin-ui] rb.program called outside of a host context");
|
|
125
|
-
},
|
|
126
|
-
getCatalog: async () => {
|
|
127
|
-
console.warn("[plugin-ui] rb.program called outside of a host context");
|
|
128
|
-
return [];
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
data: undefined,
|
|
132
|
-
onSave: () => () => {},
|
|
133
|
-
rosPeers: {
|
|
134
|
-
get: async () => {
|
|
135
|
-
console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
|
|
136
|
-
return [];
|
|
137
|
-
},
|
|
138
|
-
set: async () => {
|
|
139
|
-
console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
|
|
140
|
-
return [];
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
export const RbContext = createContext<RbSdk>(noopSdk);
|
|
146
|
-
|
|
147
|
-
/** Access the host SDK from within a plugin component. */
|
|
148
|
-
export function useRb(): RbSdk {
|
|
149
|
-
return useContext(RbContext);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Read the host-provided initial data for this mount (e.g. previously-saved
|
|
154
|
-
* form values). `undefined` for a fresh mount.
|
|
155
|
-
*
|
|
156
|
-
* const saved = useRbFormData<MyArgs>();
|
|
157
|
-
* const methods = useForm({ defaultValues: saved ?? DEFAULTS });
|
|
158
|
-
*/
|
|
159
|
-
export function useRbFormData<T = unknown>(): T | undefined {
|
|
160
|
-
return useRb().data as T | undefined;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Register a getter the host calls to collect this plugin's save payload when
|
|
165
|
-
* the host's Save is clicked. The latest `getPayload` is always used, so it can
|
|
166
|
-
* close over current state. The returned value must be JSON-serialisable.
|
|
167
|
-
*
|
|
168
|
-
* useRbSave(() => ({ args: getValues(), summary: buildSummary() }));
|
|
169
|
-
*/
|
|
170
|
-
export function useRbSave<T = unknown>(getPayload: () => T): void {
|
|
171
|
-
const rb = useRb();
|
|
172
|
-
const ref = useRef(getPayload);
|
|
173
|
-
ref.current = getPayload;
|
|
174
|
-
useEffect(() => rb.onSave(() => ref.current()), [rb]);
|
|
175
|
-
}
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/**
|
|
3
|
+
* Plugin runtime SDK (`useRb()`).
|
|
4
|
+
*
|
|
5
|
+
* pub/sub/action/serve talk to the Zenoh router over WebSocket. Topics are
|
|
6
|
+
* namespaced to {pluginId}/... ; a leading "/" makes the path absolute.
|
|
7
|
+
* alert/confirm/prompt/toast delegate to the host.
|
|
8
|
+
*/
|
|
9
|
+
import { createContext, useContext, useEffect, useRef } from "react";
|
|
10
|
+
import type {
|
|
11
|
+
WsSubscriber as Subscriber,
|
|
12
|
+
WsQueryable as Queryable,
|
|
13
|
+
} from "./ws";
|
|
14
|
+
// Type-only import — no runtime coupling to host-ui.ts (which itself has no
|
|
15
|
+
// dependency back on sdk.ts, so this stays one-directional).
|
|
16
|
+
import type { PluginUIServices } from "./host-ui";
|
|
17
|
+
|
|
18
|
+
export type RbNotifyType = "info" | "success" | "warning" | "error";
|
|
19
|
+
|
|
20
|
+
export interface RbSdk {
|
|
21
|
+
/**
|
|
22
|
+
* Publish JSON payload to a topic.
|
|
23
|
+
* "sensor/data" → {pluginId}/sensor/data
|
|
24
|
+
* "/broadcast/x" → broadcast/x (absolute)
|
|
25
|
+
*/
|
|
26
|
+
pub: <P = any>(topic: string, payload: P) => Promise<void>;
|
|
27
|
+
|
|
28
|
+
/** Subscribe to a topic. Returns a handle with undeclare(). */
|
|
29
|
+
sub: <P = any>(
|
|
30
|
+
topic: string,
|
|
31
|
+
handler: (payload: P) => void,
|
|
32
|
+
) => Promise<Subscriber>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Send a request and await the first reply (Zenoh query).
|
|
36
|
+
* Use this to call a backend handler registered with rb.serve() / ZenohRouter.
|
|
37
|
+
*/
|
|
38
|
+
action: <R = any, P = any>(topic: string, payload?: P) => Promise<R>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register a queryable — the frontend responds to backend queries.
|
|
42
|
+
* Mirror of rb.action() on the other side.
|
|
43
|
+
*/
|
|
44
|
+
serve: <P = any, R = any>(
|
|
45
|
+
topic: string,
|
|
46
|
+
handler: (payload?: P) => R | Promise<R>,
|
|
47
|
+
) => Promise<Queryable>;
|
|
48
|
+
|
|
49
|
+
/** Show a transient notification through the host's toast system. */
|
|
50
|
+
toast: PluginUIServices["toast"];
|
|
51
|
+
|
|
52
|
+
/** Show a host alert dialog. */
|
|
53
|
+
alert: PluginUIServices["alert"];
|
|
54
|
+
/** Show a host confirm dialog. Resolves true/false. */
|
|
55
|
+
confirm: PluginUIServices["confirm"];
|
|
56
|
+
/** Show a host prompt dialog. Resolves the entered value, or undefined if cancelled. */
|
|
57
|
+
prompt: PluginUIServices["prompt"];
|
|
58
|
+
|
|
59
|
+
/** Push a step tree into the host Program page as an unsaved edit. */
|
|
60
|
+
program: PluginUIServices["program"];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Host-provided initial data for this mount — e.g. the previously-saved
|
|
64
|
+
* values when the host re-opens a form. `undefined` for a fresh mount.
|
|
65
|
+
* Prefer the `useRbFormData()` hook over reading this directly.
|
|
66
|
+
*/
|
|
67
|
+
data?: unknown;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Register a getter the host calls (synchronously) to collect this plugin's
|
|
71
|
+
* save payload — e.g. when the host's Save button is clicked. The returned
|
|
72
|
+
* value must be JSON-serialisable (the host persists it). Returns an
|
|
73
|
+
* unregister fn. Prefer the `useRbSave()` hook.
|
|
74
|
+
*/
|
|
75
|
+
onSave: (getPayload: () => unknown) => () => void;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* PCs allowed to talk to this plugin's ROS 2 nodes. ROS discovery is robot-local
|
|
79
|
+
* (ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST); Muscat keeps this list per plugin, starts
|
|
80
|
+
* the plugin with ROS_STATIC_PEERS set to it, and restarts the plugin when it changes.
|
|
81
|
+
*/
|
|
82
|
+
rosPeers: {
|
|
83
|
+
get: () => Promise<string[]>;
|
|
84
|
+
/** Saves the list and restarts the plugin backend. Rejects on an invalid IP. */
|
|
85
|
+
set: (peers: string[]) => Promise<string[]>;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const noopSdk: RbSdk = {
|
|
90
|
+
pub: async <P = any>(_topic?: string, _payload?: P) => {
|
|
91
|
+
console.warn("[plugin-ui] rb.pub called outside of a host context");
|
|
92
|
+
},
|
|
93
|
+
sub: async <P = any>(_topic?: string, _handler?: (payload: P) => void) => {
|
|
94
|
+
console.warn("[plugin-ui] rb.sub called outside of a host context");
|
|
95
|
+
return { undeclare: async () => {} } as unknown as Subscriber;
|
|
96
|
+
},
|
|
97
|
+
action: async <R = any, P = any>(_topic?: string, _payload?: P) => {
|
|
98
|
+
console.warn("[plugin-ui] rb.action called outside of a host context");
|
|
99
|
+
return undefined as R;
|
|
100
|
+
},
|
|
101
|
+
serve: async <P = any, R = any>(
|
|
102
|
+
_topic?: string,
|
|
103
|
+
_handler?: (payload?: P) => R,
|
|
104
|
+
) => {
|
|
105
|
+
console.warn("[plugin-ui] rb.serve called outside of a host context");
|
|
106
|
+
return { undeclare: async () => {} } as unknown as Queryable;
|
|
107
|
+
},
|
|
108
|
+
toast: (params) => {
|
|
109
|
+
console.warn("[plugin-ui] rb.toast (no host):", params);
|
|
110
|
+
},
|
|
111
|
+
alert: async (_) => {
|
|
112
|
+
console.warn("[plugin-ui] rb.alert called outside of a host context");
|
|
113
|
+
},
|
|
114
|
+
confirm: async (_) => {
|
|
115
|
+
console.warn("[plugin-ui] rb.confirm called outside of a host context");
|
|
116
|
+
return false;
|
|
117
|
+
},
|
|
118
|
+
prompt: async (_) => {
|
|
119
|
+
console.warn("[plugin-ui] rb.prompt called outside of a host context");
|
|
120
|
+
return undefined;
|
|
121
|
+
},
|
|
122
|
+
program: {
|
|
123
|
+
setSteps: async () => {
|
|
124
|
+
console.warn("[plugin-ui] rb.program called outside of a host context");
|
|
125
|
+
},
|
|
126
|
+
getCatalog: async () => {
|
|
127
|
+
console.warn("[plugin-ui] rb.program called outside of a host context");
|
|
128
|
+
return [];
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
data: undefined,
|
|
132
|
+
onSave: () => () => {},
|
|
133
|
+
rosPeers: {
|
|
134
|
+
get: async () => {
|
|
135
|
+
console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
|
|
136
|
+
return [];
|
|
137
|
+
},
|
|
138
|
+
set: async () => {
|
|
139
|
+
console.warn("[plugin-ui] rb.rosPeers called outside of a host context");
|
|
140
|
+
return [];
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const RbContext = createContext<RbSdk>(noopSdk);
|
|
146
|
+
|
|
147
|
+
/** Access the host SDK from within a plugin component. */
|
|
148
|
+
export function useRb(): RbSdk {
|
|
149
|
+
return useContext(RbContext);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Read the host-provided initial data for this mount (e.g. previously-saved
|
|
154
|
+
* form values). `undefined` for a fresh mount.
|
|
155
|
+
*
|
|
156
|
+
* const saved = useRbFormData<MyArgs>();
|
|
157
|
+
* const methods = useForm({ defaultValues: saved ?? DEFAULTS });
|
|
158
|
+
*/
|
|
159
|
+
export function useRbFormData<T = unknown>(): T | undefined {
|
|
160
|
+
return useRb().data as T | undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Register a getter the host calls to collect this plugin's save payload when
|
|
165
|
+
* the host's Save is clicked. The latest `getPayload` is always used, so it can
|
|
166
|
+
* close over current state. The returned value must be JSON-serialisable.
|
|
167
|
+
*
|
|
168
|
+
* useRbSave(() => ({ args: getValues(), summary: buildSummary() }));
|
|
169
|
+
*/
|
|
170
|
+
export function useRbSave<T = unknown>(getPayload: () => T): void {
|
|
171
|
+
const rb = useRb();
|
|
172
|
+
const ref = useRef(getPayload);
|
|
173
|
+
ref.current = getPayload;
|
|
174
|
+
useEffect(() => rb.onSave(() => ref.current()), [rb]);
|
|
175
|
+
}
|