@dxos/plugin-registry 0.8.4-main.422d1c7879 → 0.8.4-main.4f23b4e393
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.mjs +27 -0
- package/dist/lib/neutral/RegistryPlugin.mjs.map +7 -0
- package/dist/lib/{browser/cli/index.mjs → neutral/RegistryPlugin.node.mjs} +29 -95
- package/dist/lib/neutral/RegistryPlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs +33 -0
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YV7A523U.mjs +29 -0
- package/dist/lib/neutral/chunk-YV7A523U.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +21 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs +15 -0
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +18 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +61 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/types/src/RegistryPlugin.d.ts +1 -0
- package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
- package/dist/types/src/{cli/plugin.d.ts → RegistryPlugin.node.d.ts} +2 -1
- package/dist/types/src/RegistryPlugin.node.d.ts.map +1 -0
- package/dist/types/src/RegistryPlugin.test.d.ts +2 -0
- package/dist/types/src/RegistryPlugin.test.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/dev-plugin-loader.d.ts +11 -0
- package/dist/types/src/capabilities/dev-plugin-loader.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +11 -2
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/settings.d.ts +14 -0
- package/dist/types/src/capabilities/settings.d.ts.map +1 -0
- package/dist/types/src/{cli/commands → commands}/disable.d.ts +2 -2
- package/dist/types/src/commands/disable.d.ts.map +1 -0
- package/dist/types/src/{cli/commands → commands}/enable.d.ts +2 -2
- package/dist/types/src/commands/enable.d.ts.map +1 -0
- package/dist/types/src/commands/index.d.ts.map +1 -0
- package/dist/types/src/commands/list.d.ts +7 -0
- package/dist/types/src/commands/list.d.ts.map +1 -0
- package/dist/types/src/commands/util.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +26 -2
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +49 -23
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
- package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
- package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginItem.d.ts +17 -3
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginList.d.ts +14 -4
- package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts +49 -23
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
- package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
- package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -1
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
- package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
- package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +3 -2
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/use-auto-tags.d.ts +4 -4
- package/dist/types/src/hooks/use-auto-tags.d.ts.map +1 -1
- package/dist/types/src/hooks/use-registry-plugins.d.ts +15 -0
- package/dist/types/src/hooks/use-registry-plugins.d.ts.map +1 -0
- package/dist/types/src/hooks/use-update-available.d.ts +18 -0
- package/dist/types/src/hooks/use-update-available.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +1 -1
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/open-plugin-registry.d.ts +1 -1
- package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -1
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/{cli/storage.d.ts → storage.d.ts} +1 -1
- package/dist/types/src/storage.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +48 -23
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +28 -10
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +46 -36
- package/src/{cli/plugin.ts → RegistryPlugin.node.ts} +2 -0
- package/src/RegistryPlugin.test.ts +24 -0
- package/src/RegistryPlugin.tsx +9 -7
- package/src/capabilities/app-graph-builder.ts +25 -17
- package/src/capabilities/dev-plugin-loader.ts +39 -0
- package/src/capabilities/index.ts +3 -2
- package/src/capabilities/operation-handler.ts +1 -1
- package/src/capabilities/react-surface.tsx +63 -47
- package/src/capabilities/settings.ts +34 -0
- package/src/components/PluginDetail/PluginDetail.stories.tsx +3 -2
- package/src/components/PluginDetail/PluginDetail.tsx +105 -15
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
- package/src/components/PluginFailureBadge/index.ts +5 -0
- package/src/components/PluginList/PluginItem.tsx +46 -7
- package/src/components/PluginList/PluginList.stories.tsx +1 -1
- package/src/components/PluginList/PluginList.tsx +28 -5
- package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
- package/src/components/RegistrySettings/index.ts +5 -0
- package/src/components/index.ts +2 -0
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +163 -0
- package/src/containers/BaseRegistryArticle/index.ts +5 -0
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +4 -3
- package/src/containers/PluginArticle/PluginArticle.tsx +296 -32
- package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +192 -0
- package/src/containers/PublicRegistryArticle/index.ts +5 -0
- package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
- package/src/containers/RegistryArticle/index.ts +5 -0
- package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
- package/src/containers/RegistrySettingsContainer/index.ts +5 -0
- package/src/containers/index.ts +3 -2
- package/src/hooks/index.ts +2 -1
- package/src/hooks/use-auto-tags.ts +7 -8
- package/src/hooks/use-registry-plugins.ts +29 -0
- package/src/hooks/use-update-available.ts +31 -0
- package/src/index.ts +1 -1
- package/src/meta.ts +2 -0
- package/src/operations/index.ts +1 -1
- package/src/operations/open-plugin-registry.ts +1 -1
- package/src/plugin.ts +11 -0
- package/src/translations.ts +33 -3
- package/src/types.ts +21 -14
- package/dist/lib/browser/cli/index.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -78
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/cli/index.mjs +0 -221
- package/dist/lib/node-esm/cli/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -80
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/types/src/capabilities/state.d.ts +0 -7
- package/dist/types/src/capabilities/state.d.ts.map +0 -1
- package/dist/types/src/cli/commands/disable.d.ts.map +0 -1
- package/dist/types/src/cli/commands/enable.d.ts.map +0 -1
- package/dist/types/src/cli/commands/index.d.ts.map +0 -1
- package/dist/types/src/cli/commands/list.d.ts +0 -7
- package/dist/types/src/cli/commands/list.d.ts.map +0 -1
- package/dist/types/src/cli/commands/util.d.ts.map +0 -1
- package/dist/types/src/cli/index.d.ts +0 -3
- package/dist/types/src/cli/index.d.ts.map +0 -1
- package/dist/types/src/cli/plugin.d.ts.map +0 -1
- package/dist/types/src/cli/storage.d.ts.map +0 -1
- package/dist/types/src/containers/CommunityRegistry/CommunityRegistry.d.ts +0 -8
- package/dist/types/src/containers/CommunityRegistry/CommunityRegistry.d.ts.map +0 -1
- package/dist/types/src/containers/CommunityRegistry/index.d.ts +0 -2
- package/dist/types/src/containers/CommunityRegistry/index.d.ts.map +0 -1
- package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts +0 -14
- package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts.map +0 -1
- package/dist/types/src/containers/PluginRegistry/index.d.ts +0 -2
- package/dist/types/src/containers/PluginRegistry/index.d.ts.map +0 -1
- package/dist/types/src/hooks/use-community-plugins.d.ts +0 -16
- package/dist/types/src/hooks/use-community-plugins.d.ts.map +0 -1
- package/src/capabilities/state.ts +0 -40
- package/src/cli/index.ts +0 -6
- package/src/containers/CommunityRegistry/CommunityRegistry.tsx +0 -181
- package/src/containers/CommunityRegistry/index.ts +0 -5
- package/src/containers/PluginRegistry/PluginRegistry.tsx +0 -98
- package/src/containers/PluginRegistry/index.ts +0 -5
- package/src/hooks/use-community-plugins.ts +0 -35
- /package/dist/types/src/{cli/commands → commands}/index.d.ts +0 -0
- /package/dist/types/src/{cli/commands → commands}/util.d.ts +0 -0
- /package/src/{cli/commands → commands}/disable.ts +0 -0
- /package/src/{cli/commands → commands}/enable.ts +0 -0
- /package/src/{cli/commands → commands}/index.ts +0 -0
- /package/src/{cli/commands → commands}/list.ts +0 -0
- /package/src/{cli/commands → commands}/util.ts +0 -0
- /package/src/{cli/storage.ts → storage.ts} +0 -0
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { type Plugin } from '@dxos/app-framework';
|
|
8
|
-
import { Button, Icon, Input, Link, ScrollArea, useTranslation } from '@dxos/react-ui';
|
|
7
|
+
import { type Registry, type Plugin, type PluginManager } from '@dxos/app-framework';
|
|
8
|
+
import { Button, Icon, Input, Link, ScrollArea, Select, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { composable, composableProps, getStyles, mx } from '@dxos/ui-theme';
|
|
10
10
|
|
|
11
11
|
import { meta } from '#meta';
|
|
12
12
|
|
|
13
|
+
import { PluginFailureBadge } from '../PluginFailureBadge';
|
|
14
|
+
|
|
13
15
|
export type PluginDetailProps = {
|
|
14
16
|
plugin: Plugin.Plugin;
|
|
15
17
|
enabled?: boolean;
|
|
@@ -25,10 +27,54 @@ export type PluginDetailProps = {
|
|
|
25
27
|
*/
|
|
26
28
|
onInstall?: () => void;
|
|
27
29
|
installing?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* When provided and the plugin is installed with a newer catalog version available,
|
|
32
|
+
* an Update button is shown in place of the enable Switch.
|
|
33
|
+
*/
|
|
34
|
+
onUpdate?: () => void;
|
|
35
|
+
/** True when the catalog has a newer version than the one installed. */
|
|
36
|
+
hasUpdate?: boolean;
|
|
37
|
+
/** True while an in-flight update is running. Forces the Update button disabled state. */
|
|
38
|
+
updating?: boolean;
|
|
39
|
+
/** Available versions of this plugin from the catalog. When non-empty, a version picker is shown. */
|
|
40
|
+
versions?: readonly Registry.PluginVersion[];
|
|
41
|
+
/** Currently selected version tag in the picker. */
|
|
42
|
+
selectedVersionTag?: string;
|
|
43
|
+
/** Called when the user selects a different version in the picker. */
|
|
44
|
+
onVersionChange?: (tag: string) => void;
|
|
45
|
+
/** Called when the user clicks Install on the version picker. */
|
|
46
|
+
onInstallVersion?: () => void;
|
|
47
|
+
/** Currently installed version tag (used to mark the matching select option). */
|
|
48
|
+
installedVersionTag?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Failure record for this plugin, if any. When present a warning badge is
|
|
51
|
+
* rendered next to the plugin name in the header.
|
|
52
|
+
*/
|
|
53
|
+
failure?: PluginManager.PluginFailure;
|
|
28
54
|
};
|
|
29
55
|
|
|
30
56
|
export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
|
|
31
|
-
(
|
|
57
|
+
(
|
|
58
|
+
{
|
|
59
|
+
plugin,
|
|
60
|
+
enabled,
|
|
61
|
+
onEnabledChange,
|
|
62
|
+
onUninstall,
|
|
63
|
+
onInstall,
|
|
64
|
+
installing,
|
|
65
|
+
onUpdate,
|
|
66
|
+
hasUpdate,
|
|
67
|
+
updating,
|
|
68
|
+
versions,
|
|
69
|
+
selectedVersionTag,
|
|
70
|
+
onVersionChange,
|
|
71
|
+
onInstallVersion,
|
|
72
|
+
installedVersionTag,
|
|
73
|
+
failure,
|
|
74
|
+
...props
|
|
75
|
+
},
|
|
76
|
+
forwardedRef,
|
|
77
|
+
) => {
|
|
32
78
|
const { t } = useTranslation(meta.id);
|
|
33
79
|
const {
|
|
34
80
|
id,
|
|
@@ -45,13 +91,16 @@ export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
|
|
|
45
91
|
return (
|
|
46
92
|
<ScrollArea.Root {...composableProps(props)} orientation='vertical' ref={forwardedRef}>
|
|
47
93
|
<ScrollArea.Viewport>
|
|
48
|
-
<div
|
|
49
|
-
<div
|
|
50
|
-
<Icon classNames={mx('p-1 rounded-md', styles.fill, styles.
|
|
94
|
+
<div className='dx-document grid grid-cols-[min-content_1fr] gap-4 p-4'>
|
|
95
|
+
<div>
|
|
96
|
+
<Icon classNames={mx('p-1 rounded-md', styles.fill, styles.foreground)} icon={icon} size={14} />
|
|
51
97
|
</div>
|
|
52
|
-
<div
|
|
53
|
-
<div
|
|
54
|
-
<
|
|
98
|
+
<div className='flex flex-col gap-6'>
|
|
99
|
+
<div className='grid grid-cols-[1fr_min-content] gap-x-3 w-full pt-1'>
|
|
100
|
+
<div className='flex items-center gap-2'>
|
|
101
|
+
<h2 className='text-xl'>{name}</h2>
|
|
102
|
+
{failure && <PluginFailureBadge failure={failure} size={5} />}
|
|
103
|
+
</div>
|
|
55
104
|
{onInstall ? (
|
|
56
105
|
<Button density='fine' variant='primary' disabled={installing} onClick={onInstall}>
|
|
57
106
|
{installing ? t('installing.label') : t('install.label')}
|
|
@@ -63,16 +112,16 @@ export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
|
|
|
63
112
|
)}
|
|
64
113
|
<p className='pt-0.5 text-sm text-description'>{id}</p>
|
|
65
114
|
</div>
|
|
66
|
-
<div
|
|
115
|
+
<div>
|
|
67
116
|
<p className='text-description'>{description}</p>
|
|
68
117
|
</div>
|
|
69
118
|
{screenshots && screenshots.length > 0 && (
|
|
70
|
-
<div
|
|
119
|
+
<div className='flex flex-col gap-2'>
|
|
71
120
|
<h2>Preview</h2>
|
|
72
121
|
<img src={screenshots[0]} alt={name} className='aspect-video object-fit' />
|
|
73
122
|
</div>
|
|
74
123
|
)}
|
|
75
|
-
<div
|
|
124
|
+
<div className='flex flex-col gap-2'>
|
|
76
125
|
<h2>Resources</h2>
|
|
77
126
|
<div className='flex gap-2'>
|
|
78
127
|
{homePage && (
|
|
@@ -90,9 +139,50 @@ export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
|
|
|
90
139
|
)}
|
|
91
140
|
</div>
|
|
92
141
|
</div>
|
|
93
|
-
{
|
|
94
|
-
<div
|
|
95
|
-
<
|
|
142
|
+
{versions && versions.length > 0 && (
|
|
143
|
+
<div className='flex flex-col gap-2'>
|
|
144
|
+
<h2>{t('versions.label')}</h2>
|
|
145
|
+
<div className='flex gap-2 items-center'>
|
|
146
|
+
<Select.Root value={selectedVersionTag} onValueChange={onVersionChange}>
|
|
147
|
+
<Select.TriggerButton classNames='min-w-32' />
|
|
148
|
+
<Select.Portal>
|
|
149
|
+
<Select.Content>
|
|
150
|
+
<Select.Viewport>
|
|
151
|
+
{versions.map((versionEntry) => (
|
|
152
|
+
<Select.Option key={versionEntry.tag} value={versionEntry.tag}>
|
|
153
|
+
{versionEntry.tag}
|
|
154
|
+
{installedVersionTag === versionEntry.tag ? ` (${t('installed.label')})` : ''}
|
|
155
|
+
</Select.Option>
|
|
156
|
+
))}
|
|
157
|
+
</Select.Viewport>
|
|
158
|
+
</Select.Content>
|
|
159
|
+
</Select.Portal>
|
|
160
|
+
</Select.Root>
|
|
161
|
+
{onInstallVersion && (
|
|
162
|
+
<Button
|
|
163
|
+
density='fine'
|
|
164
|
+
variant='primary'
|
|
165
|
+
disabled={installing || selectedVersionTag === installedVersionTag}
|
|
166
|
+
onClick={onInstallVersion}
|
|
167
|
+
>
|
|
168
|
+
{installing ? t('installing.label') : t('install-version.label')}
|
|
169
|
+
</Button>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
{(onUninstall || (hasUpdate && onUpdate) || updating) && (
|
|
175
|
+
<div className='flex gap-2'>
|
|
176
|
+
{updating ? (
|
|
177
|
+
<Button variant='primary' disabled>
|
|
178
|
+
{t('updating.label')}
|
|
179
|
+
</Button>
|
|
180
|
+
) : hasUpdate && onUpdate ? (
|
|
181
|
+
<Button variant='primary' onClick={onUpdate}>
|
|
182
|
+
{t('update.label')}
|
|
183
|
+
</Button>
|
|
184
|
+
) : null}
|
|
185
|
+
{onUninstall && <Button onClick={onUninstall}>{t('uninstall.label')}</Button>}
|
|
96
186
|
</div>
|
|
97
187
|
)}
|
|
98
188
|
</div>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type PluginManager } from '@dxos/app-framework';
|
|
8
|
+
import { IconButton, Popover, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { meta } from '#meta';
|
|
11
|
+
|
|
12
|
+
// Mirrors react-ui `Icon`'s `size` prop literal subset; kept inline so we don't
|
|
13
|
+
// have to add `@dxos/ui-types` as a dep just for one type alias.
|
|
14
|
+
type IconSize = 4 | 5 | 6 | 8;
|
|
15
|
+
|
|
16
|
+
export type PluginFailureBadgeProps = {
|
|
17
|
+
failure: PluginManager.PluginFailure;
|
|
18
|
+
/** Optional override for the trigger icon's tailwind classes. */
|
|
19
|
+
iconClassNames?: string;
|
|
20
|
+
/** Visual size of the warning icon (passed through to react-ui `<Icon />`). */
|
|
21
|
+
size?: IconSize;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compact warning glyph rendered next to a plugin's name when it has failed
|
|
26
|
+
* to load or activate. Clicking the icon opens a popover that names the phase
|
|
27
|
+
* (load / activation), the reason (timeout / error), and the underlying error
|
|
28
|
+
* message — enough for an operator to tell "remote host is offline" apart
|
|
29
|
+
* from "the plugin crashed".
|
|
30
|
+
*/
|
|
31
|
+
export const PluginFailureBadge = ({ failure, iconClassNames, size = 4 }: PluginFailureBadgeProps) => {
|
|
32
|
+
const { t } = useTranslation(meta.id);
|
|
33
|
+
|
|
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
|
+
return (
|
|
39
|
+
<Popover.Root>
|
|
40
|
+
<Popover.Trigger asChild>
|
|
41
|
+
<IconButton
|
|
42
|
+
variant='ghost'
|
|
43
|
+
iconOnly
|
|
44
|
+
noTooltip
|
|
45
|
+
icon='ph--warning--bold'
|
|
46
|
+
size={size}
|
|
47
|
+
iconClassNames={iconClassNames}
|
|
48
|
+
label={t('failure-badge.label')}
|
|
49
|
+
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
|
+
onClick={(event) => event.stopPropagation()}
|
|
53
|
+
/>
|
|
54
|
+
</Popover.Trigger>
|
|
55
|
+
<Popover.Content>
|
|
56
|
+
<Popover.Viewport>
|
|
57
|
+
<div className='px-3 py-2 min-w-[18rem] max-w-[28rem] flex flex-col gap-1'>
|
|
58
|
+
<p className='font-medium text-sm'>
|
|
59
|
+
{t('failure-title.label', { phase: phaseLabel, reason: reasonLabel })}
|
|
60
|
+
</p>
|
|
61
|
+
<p className='text-description text-sm break-words'>{failure.error.message}</p>
|
|
62
|
+
</div>
|
|
63
|
+
</Popover.Viewport>
|
|
64
|
+
<Popover.Arrow />
|
|
65
|
+
</Popover.Content>
|
|
66
|
+
</Popover.Root>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type MouseEvent, useCallback, useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type Plugin } from '@dxos/app-framework';
|
|
7
|
+
import { type Plugin, type PluginManager } from '@dxos/app-framework';
|
|
8
8
|
import {
|
|
9
9
|
Button,
|
|
10
10
|
type ChromaticPalette,
|
|
@@ -23,6 +23,8 @@ import { getStyles } from '@dxos/ui-theme';
|
|
|
23
23
|
import { meta } from '#meta';
|
|
24
24
|
import { type RegistryTagType } from '#types';
|
|
25
25
|
|
|
26
|
+
import { PluginFailureBadge } from '../PluginFailureBadge';
|
|
27
|
+
|
|
26
28
|
export type PluginItemProps = {
|
|
27
29
|
plugin: Plugin.Plugin;
|
|
28
30
|
/** Ids of plugins currently installed (loaded into the manager). */
|
|
@@ -31,7 +33,7 @@ export type PluginItemProps = {
|
|
|
31
33
|
installing?: readonly string[];
|
|
32
34
|
enabled?: readonly string[];
|
|
33
35
|
/**
|
|
34
|
-
* Derived tags (e.g. `
|
|
36
|
+
* Derived tags (e.g. `registry`, `local`) to display alongside the plugin's own meta.tags.
|
|
35
37
|
* Not persisted to plugin meta; computed per-render by the container.
|
|
36
38
|
*/
|
|
37
39
|
extraTags?: readonly string[];
|
|
@@ -42,8 +44,22 @@ export type PluginItemProps = {
|
|
|
42
44
|
* is rendered in place of the enable switch.
|
|
43
45
|
*/
|
|
44
46
|
onInstall?: (id: string) => void;
|
|
47
|
+
/**
|
|
48
|
+
* When true and plugin is installed, shows an Update button instead of the enable switch.
|
|
49
|
+
*/
|
|
50
|
+
hasUpdate?: boolean;
|
|
51
|
+
/** Called when the user clicks the Update button. */
|
|
52
|
+
onUpdate?: (id: string) => void;
|
|
53
|
+
/** Ids of plugins whose update is currently in flight. */
|
|
54
|
+
updating?: readonly string[];
|
|
45
55
|
hasSettings?: (id: string) => boolean;
|
|
46
56
|
onSettings?: (id: string) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Failure record for this plugin, if any. When present a warning badge is
|
|
59
|
+
* rendered next to the plugin name; clicking it opens a popover with the
|
|
60
|
+
* phase, reason, and error message.
|
|
61
|
+
*/
|
|
62
|
+
failure?: PluginManager.PluginFailure;
|
|
47
63
|
};
|
|
48
64
|
|
|
49
65
|
export const PluginItem = ({
|
|
@@ -55,8 +71,12 @@ export const PluginItem = ({
|
|
|
55
71
|
onClick,
|
|
56
72
|
onChange,
|
|
57
73
|
onInstall,
|
|
74
|
+
hasUpdate,
|
|
75
|
+
onUpdate,
|
|
76
|
+
updating,
|
|
58
77
|
hasSettings: hasSettingsProp,
|
|
59
78
|
onSettings,
|
|
79
|
+
failure,
|
|
60
80
|
}: PluginItemProps) => {
|
|
61
81
|
const { t } = useTranslation(meta.id);
|
|
62
82
|
const { id, name, description, tags, icon = 'ph--circle--regular', iconHue = 'neutral' } = plugin.meta;
|
|
@@ -73,7 +93,9 @@ export const PluginItem = ({
|
|
|
73
93
|
const isEnabled = enabled.includes(id);
|
|
74
94
|
const isInstalled = installed ? installed.includes(id) : true;
|
|
75
95
|
const isInstalling = installing?.includes(id) ?? false;
|
|
96
|
+
const isUpdating = updating?.includes(id) ?? false;
|
|
76
97
|
const showInstallButton = !!onInstall && !isInstalled;
|
|
98
|
+
const showUpdateButton = !!onUpdate && isInstalled && !!hasUpdate;
|
|
77
99
|
const inputId = `${id}-input`;
|
|
78
100
|
const labelId = `${id}-label`;
|
|
79
101
|
const descriptionId = `${id}-description`;
|
|
@@ -95,6 +117,14 @@ export const PluginItem = ({
|
|
|
95
117
|
[id, onInstall],
|
|
96
118
|
);
|
|
97
119
|
|
|
120
|
+
const handleUpdate = useCallback(
|
|
121
|
+
(event: MouseEvent) => {
|
|
122
|
+
event.stopPropagation();
|
|
123
|
+
onUpdate?.(id);
|
|
124
|
+
},
|
|
125
|
+
[id, onUpdate],
|
|
126
|
+
);
|
|
127
|
+
|
|
98
128
|
const hasSettings = hasSettingsProp?.(id) ?? false;
|
|
99
129
|
const handleSettings = useCallback(() => onSettings?.(id), [id, onSettings]);
|
|
100
130
|
const styles = getStyles(iconHue);
|
|
@@ -111,7 +141,7 @@ export const PluginItem = ({
|
|
|
111
141
|
>
|
|
112
142
|
<div className={mx(gridRows, 'justify-center rounded-l-md', styles.surface)}>
|
|
113
143
|
<Icon
|
|
114
|
-
classNames={mx('row-start-2 cursor-pointer', styles.
|
|
144
|
+
classNames={mx('row-start-2 cursor-pointer', styles.foreground)}
|
|
115
145
|
icon={icon}
|
|
116
146
|
size={14}
|
|
117
147
|
onClick={handleClick}
|
|
@@ -119,8 +149,9 @@ export const PluginItem = ({
|
|
|
119
149
|
</div>
|
|
120
150
|
|
|
121
151
|
<div className={mx(gridRows)}>
|
|
122
|
-
<div className='flex items-center overflow-hidden cursor-pointer' onClick={handleClick}>
|
|
152
|
+
<div className='flex items-center gap-2 overflow-hidden cursor-pointer' onClick={handleClick}>
|
|
123
153
|
<span className='text-lg truncate'>{name ?? id}</span>
|
|
154
|
+
{failure && <PluginFailureBadge failure={failure} />}
|
|
124
155
|
</div>
|
|
125
156
|
|
|
126
157
|
<div>
|
|
@@ -140,7 +171,7 @@ export const PluginItem = ({
|
|
|
140
171
|
aria-describedby={descriptionId}
|
|
141
172
|
classNames='cursor-pointer'
|
|
142
173
|
icon='ph--gear--regular'
|
|
143
|
-
label={t('settings.label')}
|
|
174
|
+
label={t('plugin-settings.label')}
|
|
144
175
|
iconOnly
|
|
145
176
|
size={4}
|
|
146
177
|
onClick={handleSettings}
|
|
@@ -153,7 +184,15 @@ export const PluginItem = ({
|
|
|
153
184
|
|
|
154
185
|
<div className='grow' />
|
|
155
186
|
<div className='pe-1'>
|
|
156
|
-
{
|
|
187
|
+
{isUpdating ? (
|
|
188
|
+
<Button aria-describedby={descriptionId} density='fine' variant='primary' disabled>
|
|
189
|
+
{t('updating.label')}
|
|
190
|
+
</Button>
|
|
191
|
+
) : showUpdateButton ? (
|
|
192
|
+
<Button aria-describedby={descriptionId} density='fine' variant='primary' onClick={handleUpdate}>
|
|
193
|
+
{t('update.label')}
|
|
194
|
+
</Button>
|
|
195
|
+
) : showInstallButton ? (
|
|
157
196
|
<Button
|
|
158
197
|
aria-describedby={descriptionId}
|
|
159
198
|
density='fine'
|
|
@@ -182,6 +221,6 @@ const tagColors: Record<RegistryTagType, ChromaticPalette | NeutralPalette> = {
|
|
|
182
221
|
popular: 'green',
|
|
183
222
|
featured: 'pink',
|
|
184
223
|
experimental: 'amber',
|
|
185
|
-
|
|
224
|
+
registry: 'indigo',
|
|
186
225
|
local: 'neutral',
|
|
187
226
|
};
|
|
@@ -11,9 +11,9 @@ import { ScrollArea } from '@dxos/react-ui';
|
|
|
11
11
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
12
12
|
import { getHashHue } from '@dxos/ui-theme';
|
|
13
13
|
|
|
14
|
+
import { translations } from '#translations';
|
|
14
15
|
import { RegistryTagType } from '#types';
|
|
15
16
|
|
|
16
|
-
import { translations } from '../../translations';
|
|
17
17
|
import { PluginList } from './PluginList';
|
|
18
18
|
|
|
19
19
|
random.seed(1);
|
|
@@ -4,25 +4,48 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { type Plugin } from '@dxos/app-framework';
|
|
7
|
+
import { type Plugin, type PluginManager } from '@dxos/app-framework';
|
|
8
8
|
import { List } from '@dxos/react-ui';
|
|
9
9
|
|
|
10
10
|
import { PluginItem, type PluginItemProps } from './PluginItem';
|
|
11
11
|
|
|
12
|
-
export type PluginListProps = Omit<PluginItemProps, 'plugin' | 'extraTags'> & {
|
|
12
|
+
export type PluginListProps = Omit<PluginItemProps, 'plugin' | 'extraTags' | 'hasUpdate' | 'failure'> & {
|
|
13
13
|
plugins?: readonly Plugin.Plugin[];
|
|
14
14
|
/**
|
|
15
|
-
* Map from plugin id → extra tags to display (e.g. `
|
|
15
|
+
* Map from plugin id → extra tags to display (e.g. `registry`, `local`).
|
|
16
16
|
* Computed by the container; not persisted to plugin meta.
|
|
17
17
|
*/
|
|
18
18
|
extraTagsById?: Record<string, readonly string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Set of plugin ids for which a newer version is available in the catalog.
|
|
21
|
+
* Causes each matching item to render an Update button instead of the enable switch.
|
|
22
|
+
*/
|
|
23
|
+
updateAvailableIds?: ReadonlySet<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Map from plugin id → most recent failure record. Used to render a warning
|
|
26
|
+
* badge next to the plugin name. Sourced from `PluginManager.failed`.
|
|
27
|
+
*/
|
|
28
|
+
failuresById?: Record<string, PluginManager.PluginFailure>;
|
|
19
29
|
};
|
|
20
30
|
|
|
21
|
-
export const PluginList = ({
|
|
31
|
+
export const PluginList = ({
|
|
32
|
+
plugins = [],
|
|
33
|
+
extraTagsById,
|
|
34
|
+
updateAvailableIds,
|
|
35
|
+
failuresById,
|
|
36
|
+
...props
|
|
37
|
+
}: PluginListProps) => {
|
|
22
38
|
return (
|
|
23
39
|
<List classNames='grid grid-cols-[repeat(auto-fill,minmax(18rem,1fr))] auto-rows-[max-content] gap-4 p-4'>
|
|
24
40
|
{plugins.map((plugin) => (
|
|
25
|
-
<PluginItem
|
|
41
|
+
<PluginItem
|
|
42
|
+
key={plugin.meta.id}
|
|
43
|
+
plugin={plugin}
|
|
44
|
+
extraTags={extraTagsById?.[plugin.meta.id]}
|
|
45
|
+
hasUpdate={updateAvailableIds?.has(plugin.meta.id)}
|
|
46
|
+
failure={failuresById?.[plugin.meta.id]}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
26
49
|
))}
|
|
27
50
|
</List>
|
|
28
51
|
);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
import { Button, Input, Message, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { Settings as SettingsForm } from '@dxos/react-ui-form';
|
|
11
|
+
|
|
12
|
+
import { meta } from '#meta';
|
|
13
|
+
|
|
14
|
+
import { type RegistrySettings as RegistrySettingsType } from '../../types';
|
|
15
|
+
|
|
16
|
+
export type RegistrySettingsProps = AppSurface.SettingsArticleProps<
|
|
17
|
+
RegistrySettingsType,
|
|
18
|
+
{
|
|
19
|
+
activeDevPluginIds: readonly string[];
|
|
20
|
+
onEnableDev: (url: string) => Promise<void>;
|
|
21
|
+
onDisableDev: (id: string) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Settings panel for `@dxos/plugin-registry`. The dev-plugin section lets a
|
|
27
|
+
* plugin author point Composer at a local Vite dev server (defaults to
|
|
28
|
+
* `localhost:3967`) and toggle the dev plugin on/off persistently — boot will
|
|
29
|
+
* re-attach it on the next reload, surviving HMR-triggered reloads. If the
|
|
30
|
+
* dev server is offline at boot, the toggle stays on and a warning is logged
|
|
31
|
+
* (the manager's `failed` atom also surfaces a badge on the plugin list).
|
|
32
|
+
*/
|
|
33
|
+
export const RegistrySettings = ({
|
|
34
|
+
settings,
|
|
35
|
+
onSettingsChange,
|
|
36
|
+
activeDevPluginIds,
|
|
37
|
+
onEnableDev,
|
|
38
|
+
onDisableDev,
|
|
39
|
+
}: RegistrySettingsProps) => {
|
|
40
|
+
const { t } = useTranslation(meta.id);
|
|
41
|
+
const [busy, setBusy] = useState(false);
|
|
42
|
+
const enabled = !!settings.devPluginEnabled;
|
|
43
|
+
const url = settings.devPluginUrl ?? '';
|
|
44
|
+
const trimmedUrl = url.trim();
|
|
45
|
+
const loadedDevId = activeDevPluginIds[0];
|
|
46
|
+
|
|
47
|
+
const handleToggle = useCallback(async () => {
|
|
48
|
+
if (busy || !onSettingsChange) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
setBusy(true);
|
|
52
|
+
try {
|
|
53
|
+
if (enabled && loadedDevId) {
|
|
54
|
+
await onDisableDev(loadedDevId);
|
|
55
|
+
onSettingsChange((current) => ({ ...current, devPluginEnabled: false }));
|
|
56
|
+
} else if (enabled) {
|
|
57
|
+
// Toggle is on but no dev plugin is currently loaded (boot-time load
|
|
58
|
+
// failed or hasn't finished). Treat the click as "turn it off."
|
|
59
|
+
onSettingsChange((current) => ({ ...current, devPluginEnabled: false }));
|
|
60
|
+
} else {
|
|
61
|
+
if (!trimmedUrl) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Optimistically persist intent so the next reload retries even if
|
|
65
|
+
// the page closes mid-load.
|
|
66
|
+
onSettingsChange((current) => ({ ...current, devPluginEnabled: true }));
|
|
67
|
+
try {
|
|
68
|
+
await onEnableDev(trimmedUrl);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Per product direction: leave the intent on, log the failure. The
|
|
71
|
+
// user explicitly turns it off if they're done iterating.
|
|
72
|
+
log.warn('dev plugin enable failed', { url: trimmedUrl, error });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
setBusy(false);
|
|
77
|
+
}
|
|
78
|
+
}, [busy, enabled, loadedDevId, onSettingsChange, onDisableDev, onEnableDev, trimmedUrl]);
|
|
79
|
+
|
|
80
|
+
// Button label tracks the persisted intent, not the current load state, so
|
|
81
|
+
// a transient "intent on but not loaded" combination still reads as
|
|
82
|
+
// "Disable Dev Plugin" — clicking it turns the intent off cleanly.
|
|
83
|
+
const buttonLabel = busy
|
|
84
|
+
? t('dev-plugin.busy.label')
|
|
85
|
+
: enabled
|
|
86
|
+
? t('dev-plugin.disable.label')
|
|
87
|
+
: t('dev-plugin.enable.label');
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<SettingsForm.Viewport>
|
|
91
|
+
<SettingsForm.Section title={t('dev-plugin.section.title')}>
|
|
92
|
+
<Message.Root valence='neutral'>
|
|
93
|
+
<Message.Content>{t('dev-plugin.description')}</Message.Content>
|
|
94
|
+
</Message.Root>
|
|
95
|
+
<SettingsForm.Item title={t('dev-plugin.url.label')} description={t('dev-plugin.url.description')}>
|
|
96
|
+
<Input.TextInput
|
|
97
|
+
classNames='ms-2'
|
|
98
|
+
disabled={!onSettingsChange || enabled || busy}
|
|
99
|
+
value={url}
|
|
100
|
+
onChange={(event) => onSettingsChange?.((current) => ({ ...current, devPluginUrl: event.target.value }))}
|
|
101
|
+
/>
|
|
102
|
+
</SettingsForm.Item>
|
|
103
|
+
<SettingsForm.Item title={t('dev-plugin.toggle.label')} description={t('dev-plugin.toggle.description')}>
|
|
104
|
+
<Button
|
|
105
|
+
variant={enabled ? undefined : 'primary'}
|
|
106
|
+
disabled={busy || (!enabled && !trimmedUrl)}
|
|
107
|
+
onClick={() => void handleToggle()}
|
|
108
|
+
>
|
|
109
|
+
{buttonLabel}
|
|
110
|
+
</Button>
|
|
111
|
+
</SettingsForm.Item>
|
|
112
|
+
{enabled && !loadedDevId && !busy && (
|
|
113
|
+
<Message.Root valence='warning'>
|
|
114
|
+
<Message.Content>{t('dev-plugin.not-loaded.message')}</Message.Content>
|
|
115
|
+
</Message.Root>
|
|
116
|
+
)}
|
|
117
|
+
</SettingsForm.Section>
|
|
118
|
+
</SettingsForm.Viewport>
|
|
119
|
+
);
|
|
120
|
+
};
|