@dxos/plugin-registry 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a
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/lib/neutral/RegistryPlugin.node.mjs +10 -10
- package/dist/lib/neutral/RegistryPlugin.node.mjs.map +3 -3
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/translations.mjs +5 -5
- package/dist/lib/neutral/translations.mjs.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/settings.d.ts +1 -10
- package/dist/types/src/capabilities/settings.d.ts.map +1 -1
- package/dist/types/src/commands/registry/publish-package.d.ts +1 -1
- package/dist/types/src/commands/registry/publish.d.ts +2 -2
- package/dist/types/src/commands/registry/unpublish.d.ts +2 -2
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +6 -5
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +1 -3
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts +5 -5
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +5 -1
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -1
- package/dist/types/src/components/RegistrySettings/RegistrySettings.stories.d.ts +78 -0
- package/dist/types/src/components/RegistrySettings/RegistrySettings.stories.d.ts.map +1 -0
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -1
- package/dist/types/src/containers/RegistryArticle/index.d.ts +1 -1
- package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +4 -0
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +5 -5
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +39 -39
- package/src/capabilities/app-graph-builder.ts +3 -2
- package/src/commands/registry/publish-package.ts +5 -5
- package/src/commands/registry/publish.ts +5 -5
- package/src/commands/registry/records.ts +1 -1
- package/src/commands/registry/unpublish.ts +6 -6
- package/src/commands/registry/util.ts +1 -1
- package/src/components/PluginDetail/PluginDetail.stories.tsx +31 -17
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +20 -23
- package/src/components/PluginList/PluginItem.tsx +11 -8
- package/src/components/PluginList/PluginList.tsx +18 -13
- package/src/components/RegistrySettings/RegistrySettings.stories.tsx +45 -0
- package/src/components/RegistrySettings/RegistrySettings.tsx +43 -32
- package/src/containers/PublicRegistryArticle/index.ts +1 -1
- package/src/containers/RegistryArticle/index.ts +1 -1
- package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +1 -1
- package/src/translations.ts +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-registry",
|
|
3
|
-
"version": "0.9.1-
|
|
3
|
+
"version": "0.9.1-staging.ee54ba693a",
|
|
4
4
|
"description": "Braneframe plugin gallery",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -59,52 +59,52 @@
|
|
|
59
59
|
"@effect-atom/atom-react": "^0.5.0",
|
|
60
60
|
"@effect/cli": "0.75.2",
|
|
61
61
|
"yaml": "^2.8.2",
|
|
62
|
-
"@dxos/app-framework": "0.9.1-
|
|
63
|
-
"@dxos/
|
|
64
|
-
"@dxos/client": "0.9.1-
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/compute": "0.9.1-
|
|
68
|
-
"@dxos/
|
|
69
|
-
"@dxos/
|
|
70
|
-
"@dxos/echo": "0.9.1-
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/log": "0.9.1-
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/react-ui-
|
|
79
|
-
"@dxos/react-ui-
|
|
80
|
-
"@dxos/types": "0.9.1-
|
|
81
|
-
"@dxos/react-ui-
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/
|
|
62
|
+
"@dxos/app-framework": "0.9.1-staging.ee54ba693a",
|
|
63
|
+
"@dxos/cli-util": "0.9.1-staging.ee54ba693a",
|
|
64
|
+
"@dxos/client": "0.9.1-staging.ee54ba693a",
|
|
65
|
+
"@dxos/app-toolkit": "0.9.1-staging.ee54ba693a",
|
|
66
|
+
"@dxos/client-protocol": "0.9.1-staging.ee54ba693a",
|
|
67
|
+
"@dxos/compute": "0.9.1-staging.ee54ba693a",
|
|
68
|
+
"@dxos/context": "0.9.1-staging.ee54ba693a",
|
|
69
|
+
"@dxos/edge-client": "0.9.1-staging.ee54ba693a",
|
|
70
|
+
"@dxos/echo": "0.9.1-staging.ee54ba693a",
|
|
71
|
+
"@dxos/invariant": "0.9.1-staging.ee54ba693a",
|
|
72
|
+
"@dxos/effect": "0.9.1-staging.ee54ba693a",
|
|
73
|
+
"@dxos/log": "0.9.1-staging.ee54ba693a",
|
|
74
|
+
"@dxos/plugin-graph": "0.9.1-staging.ee54ba693a",
|
|
75
|
+
"@dxos/keys": "0.9.1-staging.ee54ba693a",
|
|
76
|
+
"@dxos/protocols": "0.9.1-staging.ee54ba693a",
|
|
77
|
+
"@dxos/plugin-observability": "0.9.1-staging.ee54ba693a",
|
|
78
|
+
"@dxos/react-ui-form": "0.9.1-staging.ee54ba693a",
|
|
79
|
+
"@dxos/react-ui-markdown": "0.9.1-staging.ee54ba693a",
|
|
80
|
+
"@dxos/types": "0.9.1-staging.ee54ba693a",
|
|
81
|
+
"@dxos/react-ui-mosaic": "0.9.1-staging.ee54ba693a",
|
|
82
|
+
"@dxos/react-ui-list": "0.9.1-staging.ee54ba693a",
|
|
83
|
+
"@dxos/util": "0.9.1-staging.ee54ba693a"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@effect-atom/atom-react": "^0.5.0",
|
|
87
|
-
"@effect/platform": "0.96.
|
|
88
|
-
"@types/react": "~19.2.
|
|
87
|
+
"@effect/platform": "0.96.2",
|
|
88
|
+
"@types/react": "~19.2.17",
|
|
89
89
|
"@types/react-dom": "~19.2.3",
|
|
90
|
-
"effect": "3.21.
|
|
91
|
-
"react": "~19.2.
|
|
92
|
-
"react-dom": "~19.2.
|
|
90
|
+
"effect": "3.21.4",
|
|
91
|
+
"react": "~19.2.7",
|
|
92
|
+
"react-dom": "~19.2.7",
|
|
93
93
|
"vite": "^8.0.16",
|
|
94
|
-
"@dxos/
|
|
95
|
-
"@dxos/
|
|
96
|
-
"@dxos/
|
|
97
|
-
"@dxos/ui
|
|
98
|
-
"@dxos/
|
|
94
|
+
"@dxos/random": "0.9.1-staging.ee54ba693a",
|
|
95
|
+
"@dxos/plugin-testing": "0.9.1-staging.ee54ba693a",
|
|
96
|
+
"@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
|
|
97
|
+
"@dxos/react-ui": "0.9.1-staging.ee54ba693a",
|
|
98
|
+
"@dxos/storybook-utils": "0.9.1-staging.ee54ba693a"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
101
|
"@effect-atom/atom-react": "^0.5.0",
|
|
102
|
-
"@effect/platform": "0.96.
|
|
103
|
-
"effect": "3.21.
|
|
104
|
-
"react": "~19.2.
|
|
105
|
-
"react-dom": "~19.2.
|
|
106
|
-
"@dxos/react-ui": "0.9.1-
|
|
107
|
-
"@dxos/ui-theme": "0.9.1-
|
|
102
|
+
"@effect/platform": "0.96.2",
|
|
103
|
+
"effect": "3.21.4",
|
|
104
|
+
"react": "~19.2.7",
|
|
105
|
+
"react-dom": "~19.2.7",
|
|
106
|
+
"@dxos/react-ui": "0.9.1-staging.ee54ba693a",
|
|
107
|
+
"@dxos/ui-theme": "0.9.1-staging.ee54ba693a"
|
|
108
108
|
},
|
|
109
109
|
"publishConfig": {
|
|
110
110
|
"access": "public"
|
|
@@ -9,8 +9,9 @@ import { AppCapabilities, LayoutOperation, SettingsOperation } from '@dxos/app-t
|
|
|
9
9
|
import { Operation } from '@dxos/compute';
|
|
10
10
|
import { DXN } from '@dxos/keys';
|
|
11
11
|
import { GraphBuilder, Node, NodeMatcher } from '@dxos/plugin-graph';
|
|
12
|
+
import { Position } from '@dxos/util';
|
|
12
13
|
|
|
13
|
-
import { REGISTRY_ID, REGISTRY_KEY,
|
|
14
|
+
import { REGISTRY_ID, REGISTRY_KEY, meta, registryCategoryId } from '#meta';
|
|
14
15
|
|
|
15
16
|
import { getCategoryPredicate, getRemotePluginIds } from '../categories';
|
|
16
17
|
import { LOAD_PLUGIN_DIALOG } from '../containers';
|
|
@@ -82,7 +83,7 @@ export default Capability.makeModule(
|
|
|
82
83
|
label: ['plugin-registry.label', { ns: meta.profile.key }],
|
|
83
84
|
icon: 'ph--squares-four--regular',
|
|
84
85
|
disposition: 'pin-end',
|
|
85
|
-
position:
|
|
86
|
+
position: Position.first,
|
|
86
87
|
testId: 'treeView.pluginRegistry',
|
|
87
88
|
},
|
|
88
89
|
nodes: [
|
|
@@ -17,7 +17,7 @@ import { AUTH_OPTION_DESCRIPTIONS, NSID, putRecord, resolveSession } from './uti
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* `dx registry publish-package` — publishes both the mutable
|
|
20
|
-
* `
|
|
20
|
+
* `plugin.profile` (rkey = key) and a `plugin.release`
|
|
21
21
|
* (rkey = `<key>:<version>`) record to the authenticated user's PDS.
|
|
22
22
|
*
|
|
23
23
|
* The two writes are independent `putRecord` (upsert) calls; if the second fails the
|
|
@@ -94,12 +94,12 @@ export const publishPackage = Command.make(
|
|
|
94
94
|
createdAt,
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
const profileResult = yield* putRecord(session, NSID.
|
|
97
|
+
const profileResult = yield* putRecord(session, NSID.PluginProfile, options.key, profile);
|
|
98
98
|
yield* Console.log(`Profile ${profileResult.uri}`);
|
|
99
99
|
|
|
100
100
|
const manifestHash = Option.getOrUndefined(options.manifestHash);
|
|
101
|
-
const release: PluginRelease & {
|
|
102
|
-
|
|
101
|
+
const release: PluginRelease & { pluginKey: string; manifestHash?: string; createdAt: string } = {
|
|
102
|
+
pluginKey: options.key,
|
|
103
103
|
version: options.version,
|
|
104
104
|
moduleUrl: options.moduleUrl,
|
|
105
105
|
createdAt,
|
|
@@ -108,7 +108,7 @@ export const publishPackage = Command.make(
|
|
|
108
108
|
|
|
109
109
|
const releaseResult = yield* putRecord(
|
|
110
110
|
session,
|
|
111
|
-
NSID.
|
|
111
|
+
NSID.PluginRelease,
|
|
112
112
|
`${options.key}:${options.version}`,
|
|
113
113
|
release,
|
|
114
114
|
);
|
|
@@ -43,8 +43,8 @@ const sha256Base64 = async (bytes: Uint8Array): Promise<string> => {
|
|
|
43
43
|
* Reads the build/publish orchestration from `dx.config.ts`, runs the declared build command,
|
|
44
44
|
* reads the emitted `manifest.json`, hosts the bundle (default: upload the build
|
|
45
45
|
* output to the DXOS edge registry; override with `publish.assetBaseUrl` to point
|
|
46
|
-
* at your own already-hosted directory), then writes the `
|
|
47
|
-
* `
|
|
46
|
+
* at your own already-hosted directory), then writes the `plugin.profile` and
|
|
47
|
+
* `plugin.release` records to the authenticated publisher's PDS. Release
|
|
48
48
|
* integrity is anchored by `manifestHash` (sha256 of `manifest.json`) in the
|
|
49
49
|
* signed release record.
|
|
50
50
|
*/
|
|
@@ -181,11 +181,11 @@ export const publish = Command.make(
|
|
|
181
181
|
profile.spec = manifest.spec;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
const profileResult = yield* putRecord(session, NSID.
|
|
184
|
+
const profileResult = yield* putRecord(session, NSID.PluginProfile, key, profile);
|
|
185
185
|
yield* Console.log(`Profile ${profileResult.uri}`);
|
|
186
186
|
|
|
187
|
-
const releaseResult = yield* putRecord(session, NSID.
|
|
188
|
-
|
|
187
|
+
const releaseResult = yield* putRecord(session, NSID.PluginRelease, `${key}:${version}`, {
|
|
188
|
+
pluginKey: key,
|
|
189
189
|
version,
|
|
190
190
|
moduleUrl,
|
|
191
191
|
manifestHash,
|
|
@@ -13,7 +13,7 @@ import * as Option from 'effect/Option';
|
|
|
13
13
|
import { CommandConfig } from '@dxos/cli-util';
|
|
14
14
|
import { ClientService } from '@dxos/client';
|
|
15
15
|
|
|
16
|
-
import { ALL_NSIDS, AUTH_OPTION_DESCRIPTIONS, listRecords, resolveSession
|
|
16
|
+
import { ALL_NSIDS, AUTH_OPTION_DESCRIPTIONS, type ListRecordsEntry, listRecords, resolveSession } from './util';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* `dx registry records` — lists all `org.dxos.experimental.*` records on the
|
|
@@ -17,8 +17,8 @@ import { AUTH_OPTION_DESCRIPTIONS, NSID, deleteRecord, listRecords, resolveSessi
|
|
|
17
17
|
const rkeyOf = (uri: string): string => uri.split('/').pop() ?? '';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* `dx registry unpublish` — removes a
|
|
21
|
-
* deletes the `
|
|
20
|
+
* `dx registry unpublish` — removes a plugin from the authenticated user's PDS:
|
|
21
|
+
* deletes the `plugin.profile` (rkey = key) and every `plugin.release`
|
|
22
22
|
* (rkey = `<key>:<version>`). The registry indexer unindexes on its next sweep.
|
|
23
23
|
* Hosted bundles in edge R2 are immutable and are not removed.
|
|
24
24
|
*/
|
|
@@ -31,7 +31,7 @@ export const unpublish = Command.make(
|
|
|
31
31
|
Options.optional,
|
|
32
32
|
),
|
|
33
33
|
key: Options.text('key').pipe(
|
|
34
|
-
Options.withDescription('
|
|
34
|
+
Options.withDescription('Plugin key (the profile rkey). Removes its profile and all releases.'),
|
|
35
35
|
),
|
|
36
36
|
},
|
|
37
37
|
(options) =>
|
|
@@ -49,12 +49,12 @@ export const unpublish = Command.make(
|
|
|
49
49
|
let cursor: string | undefined;
|
|
50
50
|
let deletedReleases = 0;
|
|
51
51
|
do {
|
|
52
|
-
const releases = yield* listRecords(session, NSID.
|
|
52
|
+
const releases = yield* listRecords(session, NSID.PluginRelease, { limit: 100, cursor });
|
|
53
53
|
cursor = releases.cursor;
|
|
54
54
|
for (const record of releases.records) {
|
|
55
55
|
const rkey = rkeyOf(record.uri);
|
|
56
56
|
if (rkey.startsWith(`${key}:`)) {
|
|
57
|
-
yield* deleteRecord(session, NSID.
|
|
57
|
+
yield* deleteRecord(session, NSID.PluginRelease, rkey);
|
|
58
58
|
yield* Console.log(`Deleted release ${rkey}`);
|
|
59
59
|
deletedReleases += 1;
|
|
60
60
|
}
|
|
@@ -62,7 +62,7 @@ export const unpublish = Command.make(
|
|
|
62
62
|
} while (cursor !== undefined);
|
|
63
63
|
|
|
64
64
|
// Delete the profile (rkey = key).
|
|
65
|
-
yield* deleteRecord(session, NSID.
|
|
65
|
+
yield* deleteRecord(session, NSID.PluginProfile, key);
|
|
66
66
|
yield* Console.log(`Deleted profile ${key} (${deletedReleases} release(s) removed)`);
|
|
67
67
|
}),
|
|
68
68
|
Effect.provide(FetchHttpClient.layer),
|
|
@@ -32,7 +32,7 @@ export { ALL_NSIDS, NSID };
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
// `AccessToken.source` of the default atproto / login integration ("Atmosphere"). Mirrors
|
|
35
|
-
// `ATMOSPHERE_SOURCE` in plugin-
|
|
35
|
+
// `ATMOSPHERE_SOURCE` in plugin-connector; inlined to avoid a plugin-registry -> plugin-connector
|
|
36
36
|
// dependency.
|
|
37
37
|
const ATMOSPHERE_SOURCE = 'atproto.local';
|
|
38
38
|
|
|
@@ -13,6 +13,23 @@ import { translations } from '#translations';
|
|
|
13
13
|
|
|
14
14
|
import { PluginDetail } from './PluginDetail';
|
|
15
15
|
|
|
16
|
+
const plugin = Plugin.define(
|
|
17
|
+
Plugin.makeMeta({
|
|
18
|
+
key: DXN.make('com.example.plugin.test'),
|
|
19
|
+
name: 'Test Plugin',
|
|
20
|
+
author: 'DXOS',
|
|
21
|
+
description: random.lorem.paragraphs(2),
|
|
22
|
+
icon: { key: 'ph--bug--regular', hue: 'sky' },
|
|
23
|
+
homePage: 'https://example.com',
|
|
24
|
+
source: 'https://github.com/example/test-plugin',
|
|
25
|
+
screenshots: [
|
|
26
|
+
{
|
|
27
|
+
light: 'https://placehold.co/1728x990',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
|
|
16
33
|
const meta = {
|
|
17
34
|
title: 'plugins/plugin-registry/components/PluginDetail',
|
|
18
35
|
component: PluginDetail,
|
|
@@ -29,22 +46,19 @@ type Story = StoryObj<typeof meta>;
|
|
|
29
46
|
|
|
30
47
|
export const Default: Story = {
|
|
31
48
|
args: {
|
|
32
|
-
plugin: Plugin.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
],
|
|
47
|
-
}),
|
|
48
|
-
).pipe(Plugin.make)(),
|
|
49
|
+
plugin: plugin.pipe(Plugin.make)(),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Failure: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
plugin: plugin.pipe(Plugin.make)(),
|
|
56
|
+
failure: {
|
|
57
|
+
id: 'com.example.plugin.test',
|
|
58
|
+
phase: 'activation',
|
|
59
|
+
reason: 'error',
|
|
60
|
+
error: new Error(random.lorem.paragraph(2)),
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
},
|
|
49
63
|
},
|
|
50
64
|
};
|
|
@@ -15,8 +15,6 @@ type IconSize = 4 | 5 | 6 | 8;
|
|
|
15
15
|
|
|
16
16
|
export type PluginFailureBadgeProps = {
|
|
17
17
|
failure: PluginManager.PluginFailure;
|
|
18
|
-
/** Optional override for the trigger icon's tailwind classes. */
|
|
19
|
-
iconClassNames?: string;
|
|
20
18
|
/** Visual size of the warning icon (passed through to react-ui `<Icon />`). */
|
|
21
19
|
size?: IconSize;
|
|
22
20
|
};
|
|
@@ -28,41 +26,40 @@ export type PluginFailureBadgeProps = {
|
|
|
28
26
|
* message — enough for an operator to tell "remote host is offline" apart
|
|
29
27
|
* from "the plugin crashed".
|
|
30
28
|
*/
|
|
31
|
-
export const PluginFailureBadge = ({ failure,
|
|
29
|
+
export const PluginFailureBadge = ({ failure, size }: PluginFailureBadgeProps) => {
|
|
32
30
|
const { t } = useTranslation(meta.profile.key);
|
|
33
31
|
|
|
34
|
-
const phaseLabel = failure.phase === 'load' ? t('failure-phase-load.label') : t('failure-phase-activation.label');
|
|
35
|
-
const reasonLabel =
|
|
36
|
-
failure.reason === 'timeout' ? t('failure-reason-timeout.label') : t('failure-reason-error.label');
|
|
37
|
-
|
|
38
32
|
return (
|
|
39
33
|
<Popover.Root>
|
|
40
34
|
<Popover.Trigger asChild>
|
|
41
35
|
<IconButton
|
|
42
|
-
variant='
|
|
36
|
+
variant='destructive'
|
|
37
|
+
icon='ph--warning--bold'
|
|
43
38
|
iconOnly
|
|
44
39
|
noTooltip
|
|
45
|
-
icon='ph--warning--bold'
|
|
46
40
|
size={size}
|
|
47
|
-
iconClassNames={iconClassNames}
|
|
48
41
|
label={t('failure-badge.label')}
|
|
49
42
|
data-testid={`pluginFailureBadge.${failure.id}`}
|
|
50
|
-
// Don't bubble up to row click handlers (e.g. the card's `onClick`
|
|
51
|
-
// that opens the details pane). The popover is the action here.
|
|
52
43
|
onClick={(event) => event.stopPropagation()}
|
|
53
44
|
/>
|
|
54
45
|
</Popover.Trigger>
|
|
55
|
-
<Popover.
|
|
56
|
-
<Popover.
|
|
57
|
-
<
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
<Popover.Portal>
|
|
47
|
+
<Popover.Content>
|
|
48
|
+
<Popover.Viewport>
|
|
49
|
+
<div className='px-3 py-2 min-w-[18rem] max-w-[28rem] flex flex-col gap-1'>
|
|
50
|
+
<p className='font-medium text-sm'>
|
|
51
|
+
{t('failure-title.label', {
|
|
52
|
+
phase: failure.phase === 'load' ? t('failure-phase-load.label') : t('failure-phase-activation.label'),
|
|
53
|
+
reason:
|
|
54
|
+
failure.reason === 'timeout' ? t('failure-reason-timeout.label') : t('failure-reason-error.label'),
|
|
55
|
+
})}
|
|
56
|
+
</p>
|
|
57
|
+
<p className='text-description text-sm break-words'>{failure.error.message}</p>
|
|
58
|
+
</div>
|
|
59
|
+
</Popover.Viewport>
|
|
60
|
+
<Popover.Arrow />
|
|
61
|
+
</Popover.Content>
|
|
62
|
+
</Popover.Portal>
|
|
66
63
|
</Popover.Root>
|
|
67
64
|
);
|
|
68
65
|
};
|
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
IconButton,
|
|
13
13
|
Input,
|
|
14
14
|
Link,
|
|
15
|
-
ListItem,
|
|
16
15
|
type NeutralPalette,
|
|
17
16
|
Tag,
|
|
18
17
|
useTranslation,
|
|
19
18
|
} from '@dxos/react-ui';
|
|
19
|
+
import { Listbox } from '@dxos/react-ui-list';
|
|
20
20
|
import { mx } from '@dxos/ui-theme';
|
|
21
21
|
import { getStyles } from '@dxos/ui-theme';
|
|
22
22
|
|
|
@@ -99,7 +99,6 @@ export const PluginItem = ({
|
|
|
99
99
|
const showInstallButton = !!onInstall && !isInstalled;
|
|
100
100
|
const showUpdateButton = !!onUpdate && isInstalled && !!hasUpdate;
|
|
101
101
|
const inputId = `${id}-input`;
|
|
102
|
-
const labelId = `${id}-label`;
|
|
103
102
|
const descriptionId = `${id}-description`;
|
|
104
103
|
const handleClick = useCallback(() => onClick?.(id), [id, onClick]);
|
|
105
104
|
|
|
@@ -134,12 +133,16 @@ export const PluginItem = ({
|
|
|
134
133
|
const gridRows = 'grid grid-cols-1 grid-rows-[40px_1fr_min-content_40px]';
|
|
135
134
|
|
|
136
135
|
return (
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
labelId={labelId}
|
|
136
|
+
<Listbox.Item
|
|
137
|
+
id={id}
|
|
140
138
|
data-testid={`pluginList.${id}`}
|
|
141
139
|
aria-describedby={descriptionId}
|
|
142
|
-
classNames={mx(
|
|
140
|
+
classNames={mx(
|
|
141
|
+
gridCols,
|
|
142
|
+
// Override `Listbox.Item`'s default row chrome (flex/items-center/padding/cursor) so the
|
|
143
|
+
// bespoke card grid stretches both columns to full height and controls its own padding.
|
|
144
|
+
'items-stretch p-0 pe-2 cursor-default h-[14rem] w-full gap-3 bg-modal-surface rounded-md overflow-hidden',
|
|
145
|
+
)}
|
|
143
146
|
>
|
|
144
147
|
<div className={mx(gridRows, 'rounded-l-md', styles.surface)}>
|
|
145
148
|
<div className='flex justify-center row-start-2 cursor-pointer' onClick={handleClick}>
|
|
@@ -159,7 +162,7 @@ export const PluginItem = ({
|
|
|
159
162
|
|
|
160
163
|
<div className='flex -ms-0.5 overflow-x-auto scrollbar-none'>
|
|
161
164
|
{displayTags.map((tag: string) => (
|
|
162
|
-
<Tag key={tag}
|
|
165
|
+
<Tag key={tag} hue={tagColors[tag as RegistryTagType]} classNames='text-xs uppercase'>
|
|
163
166
|
{tag}
|
|
164
167
|
</Tag>
|
|
165
168
|
))}
|
|
@@ -209,7 +212,7 @@ export const PluginItem = ({
|
|
|
209
212
|
</div>
|
|
210
213
|
</div>
|
|
211
214
|
</div>
|
|
212
|
-
</
|
|
215
|
+
</Listbox.Item>
|
|
213
216
|
);
|
|
214
217
|
};
|
|
215
218
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
import { type Plugin, type PluginManager } from '@dxos/app-framework';
|
|
8
|
-
import {
|
|
8
|
+
import { Listbox } from '@dxos/react-ui-list';
|
|
9
9
|
|
|
10
10
|
import { PluginItem, type PluginItemProps } from './PluginItem';
|
|
11
11
|
|
|
@@ -36,17 +36,22 @@ export const PluginList = ({
|
|
|
36
36
|
...props
|
|
37
37
|
}: PluginListProps) => {
|
|
38
38
|
return (
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
<Listbox.Root>
|
|
40
|
+
<Listbox.Content
|
|
41
|
+
aria-label='plugins'
|
|
42
|
+
classNames='grid grid-cols-[repeat(auto-fill,minmax(18rem,1fr))] auto-rows-[max-content] gap-4 p-4'
|
|
43
|
+
>
|
|
44
|
+
{plugins.map((plugin) => (
|
|
45
|
+
<PluginItem
|
|
46
|
+
key={plugin.meta.profile.key}
|
|
47
|
+
plugin={plugin}
|
|
48
|
+
extraTags={extraTagsById?.[plugin.meta.profile.key]}
|
|
49
|
+
hasUpdate={updateAvailableIds?.has(plugin.meta.profile.key)}
|
|
50
|
+
failure={failuresById?.[plugin.meta.profile.key]}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
))}
|
|
54
|
+
</Listbox.Content>
|
|
55
|
+
</Listbox.Root>
|
|
51
56
|
);
|
|
52
57
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
|
|
7
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
8
|
+
|
|
9
|
+
import { translations } from '#translations';
|
|
10
|
+
|
|
11
|
+
import { RegistrySettings } from './RegistrySettings';
|
|
12
|
+
|
|
13
|
+
const meta = {
|
|
14
|
+
title: 'plugins/plugin-registry/components/RegistrySettings',
|
|
15
|
+
component: RegistrySettings,
|
|
16
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'fullscreen',
|
|
19
|
+
translations,
|
|
20
|
+
},
|
|
21
|
+
} satisfies Meta<typeof RegistrySettings>;
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof meta>;
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
settings: { devPluginUrl: 'http://localhost:3967', devPluginEnabled: false },
|
|
30
|
+
onSettingsChange: () => {},
|
|
31
|
+
activeDevPluginIds: [],
|
|
32
|
+
onEnableDev: async () => {},
|
|
33
|
+
onDisableDev: async () => {},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Enabled: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
settings: { devPluginUrl: 'http://localhost:3967', devPluginEnabled: true },
|
|
40
|
+
onSettingsChange: () => {},
|
|
41
|
+
activeDevPluginIds: [],
|
|
42
|
+
onEnableDev: async () => {},
|
|
43
|
+
onDisableDev: async () => {},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -7,13 +7,13 @@ import React, { useCallback, useState } from 'react';
|
|
|
7
7
|
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
import { Button, Input, Message, useTranslation } from '@dxos/react-ui';
|
|
10
|
-
import {
|
|
10
|
+
import { Form } from '@dxos/react-ui-form';
|
|
11
11
|
|
|
12
12
|
import { meta } from '#meta';
|
|
13
13
|
|
|
14
|
-
import { type RegistrySettings as RegistrySettingsType } from '../../types';
|
|
14
|
+
import { RegistrySettingsSchema, type RegistrySettings as RegistrySettingsType } from '../../types';
|
|
15
15
|
|
|
16
|
-
export type RegistrySettingsProps = AppSurface.
|
|
16
|
+
export type RegistrySettingsProps = AppSurface.SettingsProps<
|
|
17
17
|
RegistrySettingsType,
|
|
18
18
|
{
|
|
19
19
|
activeDevPluginIds: readonly string[];
|
|
@@ -29,6 +29,10 @@ export type RegistrySettingsProps = AppSurface.SettingsArticleProps<
|
|
|
29
29
|
* re-attach it on the next reload, surviving HMR-triggered reloads. If the
|
|
30
30
|
* dev server is offline at boot, the toggle stays on and a warning is logged
|
|
31
31
|
* (the manager's `failed` atom also surfaces a badge on the plugin list).
|
|
32
|
+
*
|
|
33
|
+
* The URL input and toggle are rendered as `Form.Row` action rows (not schema
|
|
34
|
+
* fields): the input needs a dynamic disabled state and the toggle runs async
|
|
35
|
+
* enable/disable side effects, neither of which a plain schema field expresses.
|
|
32
36
|
*/
|
|
33
37
|
export const RegistrySettings = ({
|
|
34
38
|
settings,
|
|
@@ -87,34 +91,41 @@ export const RegistrySettings = ({
|
|
|
87
91
|
: t('dev-plugin.enable.label');
|
|
88
92
|
|
|
89
93
|
return (
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
94
|
+
<Form.Root variant='settings' readonly={!onSettingsChange} schema={RegistrySettingsSchema} values={settings}>
|
|
95
|
+
<Form.Viewport scroll>
|
|
96
|
+
<Form.Content>
|
|
97
|
+
<Form.Section title={t('dev-plugin.section.title')}>
|
|
98
|
+
<Message.Root valence='neutral'>
|
|
99
|
+
<Message.Content>{t('dev-plugin.description')}</Message.Content>
|
|
100
|
+
</Message.Root>
|
|
101
|
+
<Form.Row label={t('dev-plugin.url.label')} description={t('dev-plugin.url.description')}>
|
|
102
|
+
<Input.Root>
|
|
103
|
+
<Input.TextInput
|
|
104
|
+
disabled={!onSettingsChange || enabled || busy}
|
|
105
|
+
value={url}
|
|
106
|
+
onChange={(event) =>
|
|
107
|
+
onSettingsChange?.((current) => ({ ...current, devPluginUrl: event.target.value }))
|
|
108
|
+
}
|
|
109
|
+
/>
|
|
110
|
+
</Input.Root>
|
|
111
|
+
</Form.Row>
|
|
112
|
+
<Form.Row label={t('dev-plugin.toggle.label')} description={t('dev-plugin.toggle.description')}>
|
|
113
|
+
<Button
|
|
114
|
+
variant={enabled ? undefined : 'primary'}
|
|
115
|
+
disabled={!onSettingsChange || busy || (!enabled && !trimmedUrl)}
|
|
116
|
+
onClick={() => void handleToggle()}
|
|
117
|
+
>
|
|
118
|
+
{buttonLabel}
|
|
119
|
+
</Button>
|
|
120
|
+
</Form.Row>
|
|
121
|
+
{enabled && !loadedDevId && !busy && (
|
|
122
|
+
<Message.Root valence='warning'>
|
|
123
|
+
<Message.Content>{t('dev-plugin.not-loaded.message')}</Message.Content>
|
|
124
|
+
</Message.Root>
|
|
125
|
+
)}
|
|
126
|
+
</Form.Section>
|
|
127
|
+
</Form.Content>
|
|
128
|
+
</Form.Viewport>
|
|
129
|
+
</Form.Root>
|
|
119
130
|
);
|
|
120
131
|
};
|