@peers-app/peers-ui 0.26.4 → 0.26.6
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/components/group-switcher.d.ts +6 -0
- package/dist/components/group-switcher.js +8 -1
- package/dist/components/markdown-with-mentions.d.ts +4 -0
- package/dist/components/markdown-with-mentions.js +6 -1
- package/dist/components/save-button.d.ts +15 -0
- package/dist/components/save-button.js +14 -3
- package/dist/globals.js +34 -8
- package/dist/host-shell/host-shell-consumer.d.ts +21 -0
- package/dist/host-shell/host-shell-consumer.js +50 -0
- package/dist/host-shell/host-shell-consumer.test.d.ts +1 -0
- package/dist/host-shell/host-shell-consumer.test.js +28 -0
- package/dist/mention-configs.js +2 -1
- package/dist/screens/groups/group-list.js +4 -1
- package/dist/screens/packages/package-details.d.ts +1 -0
- package/dist/screens/packages/package-details.js +27 -8
- package/dist/screens/packages/package-helpers.d.ts +3 -23
- package/dist/screens/packages/package-helpers.js +12 -46
- package/dist/screens/packages/package-helpers.test.d.ts +1 -0
- package/dist/screens/packages/package-helpers.test.js +53 -0
- package/dist/screens/packages/package-info.js +3 -2
- package/dist/screens/packages/package-list.d.ts +4 -2
- package/dist/screens/packages/package-list.js +50 -10
- package/dist/screens/packages/package-new-local.js +7 -4
- package/dist/screens/packages/package-runtime-status.d.ts +12 -0
- package/dist/screens/packages/package-runtime-status.js +33 -0
- package/dist/screens/packages/package-runtime-status.test.d.ts +1 -0
- package/dist/screens/packages/package-runtime-status.test.js +50 -0
- package/dist/screens/packages/package-versions.js +8 -55
- package/dist/screens/packages/packages-ipc.d.ts +41 -0
- package/dist/screens/packages/packages-ipc.js +102 -0
- package/dist/screens/packages/packages-ipc.test.d.ts +1 -0
- package/dist/screens/packages/packages-ipc.test.js +43 -0
- package/dist/screens/tools/tool-catalog-ipc.test.js +6 -0
- package/dist/screens/variables/variable-details.d.ts +1 -0
- package/dist/screens/variables/variable-details.js +11 -5
- package/dist/ui-router/ui-loader.js +10 -37
- package/package.json +3 -3
- package/src/components/group-switcher.tsx +12 -4
- package/src/components/markdown-with-mentions.tsx +7 -2
- package/src/components/save-button.tsx +24 -3
- package/src/globals.tsx +32 -11
- package/src/host-shell/host-shell-consumer.test.ts +32 -0
- package/src/host-shell/host-shell-consumer.ts +53 -0
- package/src/mention-configs.ts +2 -2
- package/src/screens/groups/group-list.tsx +6 -4
- package/src/screens/packages/package-details.tsx +31 -8
- package/src/screens/packages/package-helpers.test.ts +57 -0
- package/src/screens/packages/package-helpers.ts +13 -55
- package/src/screens/packages/package-info.tsx +3 -3
- package/src/screens/packages/package-list.tsx +51 -16
- package/src/screens/packages/package-new-local.tsx +8 -13
- package/src/screens/packages/package-runtime-status.test.tsx +58 -0
- package/src/screens/packages/package-runtime-status.tsx +68 -0
- package/src/screens/packages/package-versions.tsx +9 -72
- package/src/screens/packages/packages-ipc.test.ts +47 -0
- package/src/screens/packages/packages-ipc.ts +125 -0
- package/src/screens/tools/tool-catalog-ipc.test.ts +7 -1
- package/src/screens/variables/variable-details.tsx +11 -6
- package/src/ui-router/ui-loader.tsx +12 -51
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDefaultDataContext,
|
|
3
|
+
getPackagesContractDefinition,
|
|
4
|
+
hostContractConsumer,
|
|
5
|
+
type IHostContractConsumer,
|
|
6
|
+
type IPackageObservedRuntime,
|
|
7
|
+
type IPackagesConsumer,
|
|
8
|
+
type PackageRuntimeStates,
|
|
9
|
+
} from "@peers-app/peers-sdk";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import { useObservable } from "../../hooks";
|
|
12
|
+
import { useHostContract } from "../../hooks/use-host-contract";
|
|
13
|
+
|
|
14
|
+
const handles = new Map<string, IHostContractConsumer<IPackagesConsumer>>();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Shared System Packages handle for one data context; stable for `useHostContract`.
|
|
18
|
+
*
|
|
19
|
+
* @param dataContextId - Personal context (user id) or group id.
|
|
20
|
+
*/
|
|
21
|
+
export function packagesContractFor(
|
|
22
|
+
dataContextId: string,
|
|
23
|
+
): IHostContractConsumer<IPackagesConsumer> {
|
|
24
|
+
let handle = handles.get(dataContextId);
|
|
25
|
+
if (!handle) {
|
|
26
|
+
handle = hostContractConsumer<IPackagesConsumer>(getPackagesContractDefinition(), {
|
|
27
|
+
dataContextId: async () => dataContextId,
|
|
28
|
+
});
|
|
29
|
+
handles.set(dataContextId, handle);
|
|
30
|
+
}
|
|
31
|
+
return handle;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the System Packages consumer for a data context.
|
|
36
|
+
*
|
|
37
|
+
* @param dataContextId - Context to bind; defaults to the active group, the same context
|
|
38
|
+
* `Packages()` reads.
|
|
39
|
+
*/
|
|
40
|
+
export async function getPackagesConsumer(dataContextId?: string): Promise<IPackagesConsumer> {
|
|
41
|
+
const id = dataContextId ?? (await getDefaultDataContext()).dataContextId;
|
|
42
|
+
return packagesContractFor(id).get();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* System Packages tools bound to a data context.
|
|
47
|
+
*
|
|
48
|
+
* @param dataContextId - Context to bind; defaults to the active group.
|
|
49
|
+
*/
|
|
50
|
+
export async function packagesTools(dataContextId?: string): Promise<IPackagesConsumer["tools"]> {
|
|
51
|
+
return (await getPackagesConsumer(dataContextId)).tools;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A handle that never connects, used until the data context id is known. */
|
|
55
|
+
const pendingHandle: IHostContractConsumer<IPackagesConsumer> = {
|
|
56
|
+
get: () => new Promise<IPackagesConsumer>(() => undefined),
|
|
57
|
+
dispose: () => undefined,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Live runtime state of a context's packages on this device.
|
|
62
|
+
*
|
|
63
|
+
* @param dataContextId - Context to watch; defaults to the active group.
|
|
64
|
+
* @returns States keyed by package id, or `undefined` until the host answers.
|
|
65
|
+
*/
|
|
66
|
+
export function usePackageRuntimeStates(dataContextId?: string): PackageRuntimeStates | undefined {
|
|
67
|
+
const [handle, setHandle] = useState<IHostContractConsumer<IPackagesConsumer>>();
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
let cancelled = false;
|
|
70
|
+
(dataContextId
|
|
71
|
+
? Promise.resolve(dataContextId)
|
|
72
|
+
: getDefaultDataContext().then((dc) => dc.dataContextId)
|
|
73
|
+
).then(
|
|
74
|
+
(id) => {
|
|
75
|
+
if (!cancelled) setHandle(packagesContractFor(id));
|
|
76
|
+
},
|
|
77
|
+
() => undefined,
|
|
78
|
+
);
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
};
|
|
82
|
+
}, [dataContextId]);
|
|
83
|
+
const { consumer } = useHostContract(handle ?? pendingHandle);
|
|
84
|
+
const [states] = useObservable<PackageRuntimeStates | undefined>(
|
|
85
|
+
consumer?.observables.runtimeStates,
|
|
86
|
+
);
|
|
87
|
+
return states;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Display label and Bootstrap badge color for an observed package status. */
|
|
91
|
+
export interface IPackageStatusBadge {
|
|
92
|
+
/** Short status label. */
|
|
93
|
+
label: string;
|
|
94
|
+
/** Bootstrap text-bg color suffix (`success`, `danger`, ...). */
|
|
95
|
+
color: "success" | "danger" | "warning" | "secondary";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Describe a package's observed runtime status for a badge.
|
|
100
|
+
*
|
|
101
|
+
* @param runtime - Observed runtime state, or `undefined` before the host reports one.
|
|
102
|
+
* @returns The badge, or `undefined` when there is nothing to show.
|
|
103
|
+
*/
|
|
104
|
+
export function packageStatusBadge(
|
|
105
|
+
runtime: Pick<IPackageObservedRuntime, "status" | "running" | "worker"> | undefined,
|
|
106
|
+
): IPackageStatusBadge | undefined {
|
|
107
|
+
if (!runtime) return undefined;
|
|
108
|
+
if (runtime.worker?.lastWorkerError) return { label: "Worker failed", color: "danger" };
|
|
109
|
+
switch (runtime.status) {
|
|
110
|
+
case "running":
|
|
111
|
+
return { label: "Running", color: "success" };
|
|
112
|
+
case "failed":
|
|
113
|
+
return runtime.running
|
|
114
|
+
? { label: "Update failed", color: "warning" }
|
|
115
|
+
: { label: "Failed", color: "danger" };
|
|
116
|
+
case "unavailable":
|
|
117
|
+
return { label: "Waiting for files", color: "warning" };
|
|
118
|
+
case "unsupported":
|
|
119
|
+
return { label: "Unsupported here", color: "secondary" };
|
|
120
|
+
case "disabled":
|
|
121
|
+
return { label: "Disabled", color: "secondary" };
|
|
122
|
+
default:
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const list = jest.fn();
|
|
2
2
|
const get = jest.fn();
|
|
3
3
|
const rows = jest.fn();
|
|
4
|
+
const mainContentPath = jest.fn();
|
|
5
|
+
jest.mock("../../globals", () => ({ mainContentPath }));
|
|
4
6
|
let transport = true;
|
|
5
7
|
jest.mock("@peers-app/peers-sdk", () => ({
|
|
6
8
|
getDefaultDataContext: async () => ({ dataContextId: "group" }),
|
|
@@ -10,7 +12,7 @@ jest.mock("@peers-app/peers-sdk", () => ({
|
|
|
10
12
|
Tools: () => ({ list: rows, get: async (toolId: string) => ({ toolId, name: "historical" }) }),
|
|
11
13
|
}));
|
|
12
14
|
|
|
13
|
-
import { toolMentionConfig } from "../../mention-configs";
|
|
15
|
+
import { toolMentionConfig, valueTypeMentionConfig } from "../../mention-configs";
|
|
14
16
|
import { getToolDetails, listWorkflowMentionTools } from "./tool-catalog-ipc";
|
|
15
17
|
|
|
16
18
|
beforeEach(() => {
|
|
@@ -32,6 +34,10 @@ test("tool mentions include only workflow-invocable catalog tools", async () =>
|
|
|
32
34
|
name: "historical",
|
|
33
35
|
});
|
|
34
36
|
});
|
|
37
|
+
test("type mentions navigate directly through the UI route observable", () => {
|
|
38
|
+
valueTypeMentionConfig.onClick?.({ kind: "valueType", id: "type-id", name: "Type" });
|
|
39
|
+
expect(mainContentPath).toHaveBeenCalledWith("peer-types/type-id");
|
|
40
|
+
});
|
|
35
41
|
test("browser fallback omits quarantined rows and preserves historical detail", async () => {
|
|
36
42
|
transport = false;
|
|
37
43
|
rows.mockResolvedValue([
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
type Observable,
|
|
8
8
|
observable,
|
|
9
9
|
PersistentVars,
|
|
10
|
-
rpcServerCalls,
|
|
11
10
|
} from "@peers-app/peers-sdk";
|
|
12
11
|
import { useEffect, useState } from "react";
|
|
13
12
|
import { Checkbox } from "../../components/checkbox";
|
|
@@ -17,12 +16,14 @@ import { MarkdownEditorInline } from "../../components/markdown-editor/editor-in
|
|
|
17
16
|
import { SaveButton } from "../../components/save-button";
|
|
18
17
|
import { ScreenTabBody, Tabs } from "../../components/tabs";
|
|
19
18
|
import { useObservable, usePromise, useSubscription } from "../../hooks";
|
|
19
|
+
import { hostShellTools } from "../../host-shell/host-shell-consumer";
|
|
20
20
|
import { updateActiveTabTitle } from "../../tabs-layout/tabs-state";
|
|
21
21
|
|
|
22
22
|
interface IProps {
|
|
23
23
|
persistentVarId: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** Details screen for one persistent variable: name, value (copyable), and settings. */
|
|
26
27
|
export const VariableDetails = (props: IProps) => {
|
|
27
28
|
const persistentVariable = usePromise(
|
|
28
29
|
async () => {
|
|
@@ -242,9 +243,9 @@ const VariableValue = (props: { pvar: IDoc<IPersistentVar> }) => {
|
|
|
242
243
|
|
|
243
244
|
/**
|
|
244
245
|
* Button that copies a persistent variable's value to the clipboard. Decryption (for
|
|
245
|
-
* secret values) and the clipboard write happen entirely on the
|
|
246
|
-
*
|
|
247
|
-
*
|
|
246
|
+
* secret values) and the clipboard write happen entirely on the host, through System Host
|
|
247
|
+
* Shell `copyVariableToClipboard`, so the plaintext is never exposed to the UI. Shows
|
|
248
|
+
* transient success/error feedback on the icon.
|
|
248
249
|
*/
|
|
249
250
|
const CopyValueButton = (props: { pvar: IDoc<IPersistentVar> }) => {
|
|
250
251
|
const { pvar } = props;
|
|
@@ -254,8 +255,12 @@ const CopyValueButton = (props: { pvar: IDoc<IPersistentVar> }) => {
|
|
|
254
255
|
try {
|
|
255
256
|
const userContext = await getUserContext();
|
|
256
257
|
const groupId = userContext.currentlyActiveGroupId();
|
|
257
|
-
const
|
|
258
|
-
|
|
258
|
+
const tools = await hostShellTools();
|
|
259
|
+
const result = await tools.copyVariableToClipboard({
|
|
260
|
+
persistentVarId: pvar.persistentVarId,
|
|
261
|
+
groupId,
|
|
262
|
+
});
|
|
263
|
+
setStatus(result.copied ? "copied" : "error");
|
|
259
264
|
} catch (err) {
|
|
260
265
|
console.error("Failed to copy variable value", err);
|
|
261
266
|
setStatus("error");
|
|
@@ -256,10 +256,6 @@ const UILoader = (args: { peersUIId: string; props?: Record<string, unknown> })
|
|
|
256
256
|
|
|
257
257
|
const uiLoadingPromises: Record<string, Promise<void>> = {};
|
|
258
258
|
|
|
259
|
-
// Check if we're running in a React Native WebView (has injectUIBundle available)
|
|
260
|
-
const isReactNativeWebView =
|
|
261
|
-
typeof (window as Window & { ReactNativeWebView?: unknown }).ReactNativeWebView !== "undefined";
|
|
262
|
-
|
|
263
259
|
async function resolveUiBundleFileId(pkg: IPackage): Promise<string | undefined> {
|
|
264
260
|
let pvId = pkg.activePackageVersionId;
|
|
265
261
|
try {
|
|
@@ -278,12 +274,6 @@ async function resolveUiBundleFileId(pkg: IPackage): Promise<string | undefined>
|
|
|
278
274
|
}
|
|
279
275
|
}
|
|
280
276
|
|
|
281
|
-
type PeersUiBundleWindow = Window & {
|
|
282
|
-
__peersUIs?: Record<string, IPeersUI>;
|
|
283
|
-
__peersUIBundleLoaded?: (loadedFileId: string) => void;
|
|
284
|
-
__peersUIBundleError?: (errorFileId: string, errorMsg: string) => void;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
277
|
function loadUIBundle(pkg: IPackage, forceRefresh?: boolean) {
|
|
288
278
|
let importPromise: Promise<void> = uiLoadingPromises[pkg.packageId];
|
|
289
279
|
if (!importPromise || forceRefresh) {
|
|
@@ -295,48 +285,19 @@ function loadUIBundle(pkg: IPackage, forceRefresh?: boolean) {
|
|
|
295
285
|
return;
|
|
296
286
|
}
|
|
297
287
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
peersUIs[peersUIId] = store[peersUIId];
|
|
308
|
-
});
|
|
309
|
-
resolveLoad();
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
_w.__peersUIBundleError = (errorFileId: string, errorMsg: string) => {
|
|
314
|
-
if (errorFileId === uiBundleFileId) {
|
|
315
|
-
rejectLoad(new Error(errorMsg));
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
await rpcServerCalls.injectUIBundle(uiBundleFileId);
|
|
321
|
-
await loadPromise;
|
|
322
|
-
console.log(
|
|
323
|
-
`finished loading ui bundle for ${pkg.name}: ${(Date.now() - sTime).toFixed(0)}ms`,
|
|
324
|
-
);
|
|
325
|
-
} else {
|
|
326
|
-
const bundleCode = await rpcServerCalls.getFileContents(uiBundleFileId);
|
|
327
|
-
if (bundleCode) {
|
|
328
|
-
const exportUIs = (peerUIs: IPeersPackageUIs) => {
|
|
329
|
-
peerUIs?.uis?.forEach((ui) => {
|
|
330
|
-
peersUIs[ui.peersUIId] = ui;
|
|
331
|
-
});
|
|
332
|
-
};
|
|
333
|
-
const bundleFunction = new Function("exportUIs", bundleCode);
|
|
334
|
-
await bundleFunction(exportUIs);
|
|
335
|
-
}
|
|
336
|
-
console.log(
|
|
337
|
-
`finished loading ui bundle for ${pkg.name}: ${(Date.now() - sTime).toFixed(0)}ms, ${bundleCode ? (bundleCode.length / 1000).toFixed(0) : "0"} KB`,
|
|
338
|
-
);
|
|
288
|
+
const bundleCode = await rpcServerCalls.getFileContents(uiBundleFileId);
|
|
289
|
+
if (bundleCode) {
|
|
290
|
+
const exportUIs = (peerUIs: IPeersPackageUIs) => {
|
|
291
|
+
peerUIs?.uis?.forEach((ui) => {
|
|
292
|
+
peersUIs[ui.peersUIId] = ui;
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const bundleFunction = new Function("exportUIs", bundleCode);
|
|
296
|
+
await bundleFunction(exportUIs);
|
|
339
297
|
}
|
|
298
|
+
console.log(
|
|
299
|
+
`finished loading ui bundle for ${pkg.name}: ${(Date.now() - sTime).toFixed(0)}ms, ${bundleCode ? (bundleCode.length / 1000).toFixed(0) : "0"} KB`,
|
|
300
|
+
);
|
|
340
301
|
})().catch((err: unknown) => {
|
|
341
302
|
console.error(`Error while loading ui module for ${pkg.name}`, err);
|
|
342
303
|
throw err;
|