@dxos/plugin-registry 0.9.0 → 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 +709 -18
- package/dist/lib/neutral/RegistryPlugin.node.mjs.map +4 -4
- package/dist/lib/neutral/chunk-ZY37XLVF.mjs +46 -0
- package/dist/lib/neutral/chunk-ZY37XLVF.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +1 -1
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/{open-plugin-registry-II735N76.mjs → open-plugin-registry-ID7LV3SF.mjs} +3 -3
- package/dist/lib/neutral/open-plugin-registry-ID7LV3SF.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +2 -2
- package/dist/lib/neutral/translations.mjs +6 -6
- package/dist/lib/neutral/translations.mjs.map +3 -3
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- 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/index.d.ts +4 -12
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +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/categories.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +2 -2
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/plugin/disable.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/enable.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/index.d.ts +3 -0
- package/dist/types/src/commands/plugin/index.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/list.d.ts.map +1 -0
- package/dist/types/src/commands/plugin/util.d.ts.map +1 -0
- package/dist/types/src/commands/registry/index.d.ts +16 -0
- package/dist/types/src/commands/registry/index.d.ts.map +1 -0
- package/dist/types/src/commands/registry/publish-package.d.ts +29 -0
- package/dist/types/src/commands/registry/publish-package.d.ts.map +1 -0
- package/dist/types/src/commands/registry/publish-publisher.d.ts +19 -0
- package/dist/types/src/commands/registry/publish-publisher.d.ts.map +1 -0
- package/dist/types/src/commands/registry/publish.d.ts +25 -0
- package/dist/types/src/commands/registry/publish.d.ts.map +1 -0
- package/dist/types/src/commands/registry/records.d.ts +14 -0
- package/dist/types/src/commands/registry/records.d.ts.map +1 -0
- package/dist/types/src/commands/registry/unpublish.d.ts +15 -0
- package/dist/types/src/commands/registry/unpublish.d.ts.map +1 -0
- package/dist/types/src/commands/registry/util.d.ts +113 -0
- package/dist/types/src/commands/registry/util.d.ts.map +1 -0
- package/dist/types/src/commands/registry/verify.d.ts +20 -0
- package/dist/types/src/commands/registry/verify.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +2 -2
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
- 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/PluginList/PluginList.stories.d.ts.map +1 -1
- 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/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.map +1 -1
- 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/hooks/useAutoTags.d.ts +2 -2
- package/dist/types/src/hooks/useAutoTags.d.ts.map +1 -1
- package/dist/types/src/hooks/useCatalogEntry.d.ts +4 -5
- package/dist/types/src/hooks/useCatalogEntry.d.ts.map +1 -1
- package/dist/types/src/hooks/usePluginActions.d.ts +3 -3
- package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -1
- package/dist/types/src/hooks/useUpdateAvailable.d.ts +2 -2
- package/dist/types/src/hooks/useUpdateAvailable.d.ts.map +1 -1
- package/dist/types/src/hooks/useVersionPicker.d.ts +7 -8
- package/dist/types/src/hooks/useVersionPicker.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +32 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/open-plugin-registry.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/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +20 -0
- package/package.json +40 -33
- package/src/RegistryPlugin.node.ts +2 -2
- package/src/RegistryPlugin.test.ts +1 -1
- package/src/capabilities/app-graph-builder.ts +46 -41
- package/src/capabilities/dev-plugin-loader.ts +1 -1
- package/src/capabilities/react-surface.tsx +1 -4
- package/src/capabilities/settings.ts +2 -2
- package/src/categories.ts +5 -4
- package/src/commands/index.ts +2 -10
- package/src/commands/{disable.ts → plugin/disable.ts} +3 -3
- package/src/commands/{enable.ts → plugin/enable.ts} +3 -3
- package/src/commands/plugin/index.ts +14 -0
- package/src/commands/{list.ts → plugin/list.ts} +4 -4
- package/src/commands/registry/index.ts +30 -0
- package/src/commands/registry/publish-package.ts +119 -0
- package/src/commands/registry/publish-publisher.ts +73 -0
- package/src/commands/registry/publish.ts +278 -0
- package/src/commands/registry/records.ts +73 -0
- package/src/commands/registry/unpublish.ts +70 -0
- package/src/commands/registry/util.ts +395 -0
- package/src/commands/registry/verify.ts +76 -0
- package/src/components/DisableDependentsAlert/DisableDependentsAlert.tsx +1 -1
- package/src/components/PluginDetail/PluginDetail.stories.tsx +31 -15
- package/src/components/PluginDetail/PluginDetail.tsx +21 -23
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +21 -24
- package/src/components/PluginList/PluginItem.tsx +16 -11
- package/src/components/PluginList/PluginList.stories.tsx +1 -2
- package/src/components/PluginList/PluginList.tsx +18 -13
- package/src/components/RegistrySettings/RegistrySettings.stories.tsx +45 -0
- package/src/components/RegistrySettings/RegistrySettings.tsx +44 -33
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +2 -2
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +3 -3
- package/src/containers/PluginArticle/PluginArticle.tsx +8 -6
- package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +35 -28
- package/src/containers/PublicRegistryArticle/index.ts +1 -1
- package/src/containers/RegistryArticle/RegistryArticle.tsx +1 -1
- package/src/containers/RegistryArticle/index.ts +1 -1
- package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +2 -2
- package/src/hooks/useAutoTags.ts +3 -3
- package/src/hooks/useCatalogEntry.ts +6 -7
- package/src/hooks/useDisableConfirmation.ts +1 -1
- package/src/hooks/usePluginActions.ts +13 -11
- package/src/hooks/useUpdateAvailable.ts +5 -5
- package/src/hooks/useVersionPicker.ts +22 -30
- package/src/meta.ts +6 -15
- package/src/operations/open-plugin-registry.ts +2 -2
- package/src/translations.ts +6 -6
- package/src/types.ts +3 -1
- package/dist/lib/neutral/chunk-GRQI6MLI.mjs +0 -36
- package/dist/lib/neutral/chunk-GRQI6MLI.mjs.map +0 -7
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +0 -7
- package/dist/types/src/commands/disable.d.ts.map +0 -1
- package/dist/types/src/commands/enable.d.ts.map +0 -1
- package/dist/types/src/commands/list.d.ts.map +0 -1
- package/dist/types/src/commands/util.d.ts.map +0 -1
- /package/dist/types/src/commands/{disable.d.ts → plugin/disable.d.ts} +0 -0
- /package/dist/types/src/commands/{enable.d.ts → plugin/enable.d.ts} +0 -0
- /package/dist/types/src/commands/{list.d.ts → plugin/list.d.ts} +0 -0
- /package/dist/types/src/commands/{util.d.ts → plugin/util.d.ts} +0 -0
- /package/src/commands/{util.ts → plugin/util.ts} +0 -0
|
@@ -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,
|
|
32
|
-
const { t } = useTranslation(meta.
|
|
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');
|
|
29
|
+
export const PluginFailureBadge = ({ failure, size }: PluginFailureBadgeProps) => {
|
|
30
|
+
const { t } = useTranslation(meta.profile.key);
|
|
37
31
|
|
|
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
|
|
|
@@ -78,8 +78,10 @@ export const PluginItem = ({
|
|
|
78
78
|
onSettings,
|
|
79
79
|
failure,
|
|
80
80
|
}: PluginItemProps) => {
|
|
81
|
-
const { t } = useTranslation(meta.
|
|
82
|
-
const { id, name, description, tags, icon
|
|
81
|
+
const { t } = useTranslation(meta.profile.key);
|
|
82
|
+
const { key: id, name, description, tags, icon: rawIcon } = plugin.meta.profile;
|
|
83
|
+
const icon = rawIcon?.key ?? 'ph--circle--regular';
|
|
84
|
+
const iconHue = rawIcon?.hue ?? 'neutral';
|
|
83
85
|
const displayTags = useMemo(() => {
|
|
84
86
|
if (!extraTags || extraTags.length === 0) {
|
|
85
87
|
return tags ?? [];
|
|
@@ -97,7 +99,6 @@ export const PluginItem = ({
|
|
|
97
99
|
const showInstallButton = !!onInstall && !isInstalled;
|
|
98
100
|
const showUpdateButton = !!onUpdate && isInstalled && !!hasUpdate;
|
|
99
101
|
const inputId = `${id}-input`;
|
|
100
|
-
const labelId = `${id}-label`;
|
|
101
102
|
const descriptionId = `${id}-description`;
|
|
102
103
|
const handleClick = useCallback(() => onClick?.(id), [id, onClick]);
|
|
103
104
|
|
|
@@ -132,12 +133,16 @@ export const PluginItem = ({
|
|
|
132
133
|
const gridRows = 'grid grid-cols-1 grid-rows-[40px_1fr_min-content_40px]';
|
|
133
134
|
|
|
134
135
|
return (
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
labelId={labelId}
|
|
136
|
+
<Listbox.Item
|
|
137
|
+
id={id}
|
|
138
138
|
data-testid={`pluginList.${id}`}
|
|
139
139
|
aria-describedby={descriptionId}
|
|
140
|
-
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
|
+
)}
|
|
141
146
|
>
|
|
142
147
|
<div className={mx(gridRows, 'rounded-l-md', styles.surface)}>
|
|
143
148
|
<div className='flex justify-center row-start-2 cursor-pointer' onClick={handleClick}>
|
|
@@ -156,8 +161,8 @@ export const PluginItem = ({
|
|
|
156
161
|
</div>
|
|
157
162
|
|
|
158
163
|
<div className='flex -ms-0.5 overflow-x-auto scrollbar-none'>
|
|
159
|
-
{displayTags.map((tag) => (
|
|
160
|
-
<Tag key={tag}
|
|
164
|
+
{displayTags.map((tag: string) => (
|
|
165
|
+
<Tag key={tag} hue={tagColors[tag as RegistryTagType]} classNames='text-xs uppercase'>
|
|
161
166
|
{tag}
|
|
162
167
|
</Tag>
|
|
163
168
|
))}
|
|
@@ -207,7 +212,7 @@ export const PluginItem = ({
|
|
|
207
212
|
</div>
|
|
208
213
|
</div>
|
|
209
214
|
</div>
|
|
210
|
-
</
|
|
215
|
+
</Listbox.Item>
|
|
211
216
|
);
|
|
212
217
|
};
|
|
213
218
|
|
|
@@ -38,8 +38,7 @@ const DefaultStory = () => {
|
|
|
38
38
|
name: `${random.commerce.productName()}`,
|
|
39
39
|
description: random.lorem.sentences(Math.ceil(Math.random() * 3)),
|
|
40
40
|
tags: random.helpers.uniqueArray(RegistryTagType.literals as any, Math.floor(Math.random() * 3)),
|
|
41
|
-
icon: random.helpers.arrayElement(icons),
|
|
42
|
-
iconHue: getHashHue(random.string.uuid()),
|
|
41
|
+
icon: { key: random.helpers.arrayElement(icons), hue: getHashHue(random.string.uuid()) },
|
|
43
42
|
homePage: random.datatype.boolean({ probability: 0.5 }) ? random.internet.url() : undefined,
|
|
44
43
|
source: random.internet.url(),
|
|
45
44
|
}),
|
|
@@ -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,
|
|
@@ -37,7 +41,7 @@ export const RegistrySettings = ({
|
|
|
37
41
|
onEnableDev,
|
|
38
42
|
onDisableDev,
|
|
39
43
|
}: RegistrySettingsProps) => {
|
|
40
|
-
const { t } = useTranslation(meta.
|
|
44
|
+
const { t } = useTranslation(meta.profile.key);
|
|
41
45
|
const [busy, setBusy] = useState(false);
|
|
42
46
|
const enabled = !!settings.devPluginEnabled;
|
|
43
47
|
const url = settings.devPluginUrl ?? '';
|
|
@@ -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
|
};
|
|
@@ -20,7 +20,7 @@ import { getPluginPath, meta } from '#meta';
|
|
|
20
20
|
import { useDisableConfirmation } from '../../hooks';
|
|
21
21
|
|
|
22
22
|
const matchesFilter = (plugin: Plugin.Plugin, query: string) => {
|
|
23
|
-
const haystack = `${plugin.meta.name ?? ''} ${plugin.meta.
|
|
23
|
+
const haystack = `${plugin.meta.profile.name ?? ''} ${plugin.meta.profile.key}`.toLowerCase();
|
|
24
24
|
return haystack.includes(query);
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -67,7 +67,7 @@ export const BaseRegistryArticle = composable<HTMLDivElement, BaseRegistryArticl
|
|
|
67
67
|
},
|
|
68
68
|
forwardedRef,
|
|
69
69
|
) => {
|
|
70
|
-
const { t } = useTranslation(meta.
|
|
70
|
+
const { t } = useTranslation(meta.profile.key);
|
|
71
71
|
const manager = usePluginManager();
|
|
72
72
|
const { invoke, invokePromise } = useOperationInvoker();
|
|
73
73
|
const allSettings = useCapabilities(AppCapabilities.Settings);
|
|
@@ -12,11 +12,11 @@ import { Button, Dialog, Input, useTranslation } from '@dxos/react-ui';
|
|
|
12
12
|
|
|
13
13
|
import { meta } from '../../meta';
|
|
14
14
|
|
|
15
|
-
export const LOAD_PLUGIN_DIALOG = DXN.make(`${meta.
|
|
15
|
+
export const LOAD_PLUGIN_DIALOG = DXN.make(`${meta.profile.key}.loadPluginDialog`);
|
|
16
16
|
|
|
17
17
|
export const LoadPluginDialog = () => {
|
|
18
18
|
const manager = usePluginManager();
|
|
19
|
-
const { t } = useTranslation(meta.
|
|
19
|
+
const { t } = useTranslation(meta.profile.key);
|
|
20
20
|
const [url, setUrl] = useState('');
|
|
21
21
|
const [loading, setLoading] = useState(false);
|
|
22
22
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -33,7 +33,7 @@ export const LoadPluginDialog = () => {
|
|
|
33
33
|
|
|
34
34
|
void Effect.gen(function* () {
|
|
35
35
|
const plugin = yield* manager.add(trimmed);
|
|
36
|
-
yield* manager.enable(plugin.meta.
|
|
36
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
37
37
|
closeRef.current?.click();
|
|
38
38
|
}).pipe(
|
|
39
39
|
Effect.catchAll((err) =>
|
|
@@ -24,19 +24,18 @@ import {
|
|
|
24
24
|
export type PluginArticleProps = { subject: Plugin.Plugin };
|
|
25
25
|
|
|
26
26
|
export const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {
|
|
27
|
-
const pluginId = plugin.meta.
|
|
27
|
+
const pluginId = plugin.meta.profile.key;
|
|
28
28
|
const manager = usePluginManager();
|
|
29
29
|
const plugins = useAtomValue(manager.plugins);
|
|
30
30
|
const remotePluginIds = useRemotePluginIds();
|
|
31
31
|
const provider = useRegistryPluginProvider();
|
|
32
32
|
const { invokePromise } = useOperationInvoker();
|
|
33
33
|
|
|
34
|
-
const { catalogEntry, moduleUrl
|
|
34
|
+
const { catalogEntry, moduleUrl } = useCatalogEntry(pluginId);
|
|
35
35
|
const { installedVersionTag, syncInstalledVersion } = useInstalledVersionTag(pluginId, plugins);
|
|
36
36
|
const { pickerVersions, selectedVersionTag, setSelectedVersionTag } = useVersionPicker({
|
|
37
37
|
provider,
|
|
38
38
|
pluginId,
|
|
39
|
-
repo,
|
|
40
39
|
moduleUrl,
|
|
41
40
|
installedVersionTag,
|
|
42
41
|
});
|
|
@@ -44,11 +43,14 @@ export const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {
|
|
|
44
43
|
const enabled = manager.getEnabled().includes(pluginId);
|
|
45
44
|
const failed = useAtomValue(manager.failed);
|
|
46
45
|
const failure = useMemo(() => failed.find((entry) => entry.id === pluginId), [failed, pluginId]);
|
|
47
|
-
const isInstalled = useMemo(
|
|
46
|
+
const isInstalled = useMemo(
|
|
47
|
+
() => plugins.some((candidate) => candidate.meta.profile.key === pluginId),
|
|
48
|
+
[plugins, pluginId],
|
|
49
|
+
);
|
|
48
50
|
const isCore = manager.getCore().includes(pluginId);
|
|
49
51
|
const canUninstall = isInstalled && !isCore && remotePluginIds.has(pluginId);
|
|
50
52
|
const hasUpdate =
|
|
51
|
-
isInstalled && !!catalogEntry && !!installedVersionTag && installedVersionTag !== catalogEntry.version;
|
|
53
|
+
isInstalled && !!catalogEntry && !!installedVersionTag && installedVersionTag !== catalogEntry.release?.version;
|
|
52
54
|
|
|
53
55
|
// Recompute graph slices whenever the plugin list changes, so installs /
|
|
54
56
|
// removals through other surfaces (or this article's own actions) keep the
|
|
@@ -66,7 +68,7 @@ export const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {
|
|
|
66
68
|
// only loaded when it is activated, so the always-available `meta.name`
|
|
67
69
|
// from the registered plugin set is the right source for chip labels.
|
|
68
70
|
const handleResolvePluginName = useCallback(
|
|
69
|
-
(id: string): string => plugins.find((candidate) => candidate.meta.
|
|
71
|
+
(id: string): string => plugins.find((candidate) => candidate.meta.profile.key === id)?.meta.profile.name ?? id,
|
|
70
72
|
[plugins],
|
|
71
73
|
);
|
|
72
74
|
|
|
@@ -6,7 +6,7 @@ import { useAtomValue } from '@effect-atom/atom-react';
|
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Plugin, UrlLoader } from '@dxos/app-framework';
|
|
10
10
|
import { useOperationInvoker, usePluginManager } from '@dxos/app-framework/ui';
|
|
11
11
|
import { EffectEx } from '@dxos/effect';
|
|
12
12
|
import { DXN } from '@dxos/keys';
|
|
@@ -19,29 +19,30 @@ import { meta } from '#meta';
|
|
|
19
19
|
import { useAutoTags, useRegistryPlugins, useUpdateAvailableIds } from '../../hooks';
|
|
20
20
|
import { BaseRegistryArticle } from '../BaseRegistryArticle';
|
|
21
21
|
|
|
22
|
-
const sortEntries = (a:
|
|
22
|
+
const sortEntries = (a: Plugin.Meta, b: Plugin.Meta) =>
|
|
23
|
+
(a.profile.name ?? a.profile.key).localeCompare(b.profile.name ?? b.profile.key);
|
|
23
24
|
|
|
24
25
|
const sortPlugins = (a: Plugin.Plugin, b: Plugin.Plugin) =>
|
|
25
|
-
(a.meta.name ?? a.meta.
|
|
26
|
+
(a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Turns a registry catalog entry into a minimal Plugin object so we can reuse
|
|
29
30
|
* {@link BaseRegistryArticle} for rendering. The synthesized plugin has no
|
|
30
31
|
* modules — it exists only for display until the user installs it.
|
|
31
32
|
*/
|
|
32
|
-
const toDisplayPlugin = (plugin:
|
|
33
|
+
const toDisplayPlugin = (plugin: Plugin.Meta): Plugin.Plugin =>
|
|
33
34
|
({
|
|
34
35
|
[Plugin.PluginTypeId]: Plugin.PluginTypeId,
|
|
35
36
|
meta: Plugin.makeMeta({
|
|
36
|
-
key: DXN.make(plugin.
|
|
37
|
-
name: plugin.name,
|
|
38
|
-
description: plugin.description,
|
|
39
|
-
homePage: plugin.homePage,
|
|
40
|
-
source: plugin.source,
|
|
41
|
-
screenshots: plugin.screenshots,
|
|
42
|
-
tags: plugin.tags,
|
|
43
|
-
icon: plugin.icon,
|
|
44
|
-
|
|
37
|
+
key: DXN.make(plugin.profile.key),
|
|
38
|
+
name: plugin.profile.name,
|
|
39
|
+
description: plugin.profile.description,
|
|
40
|
+
homePage: plugin.profile.homePage,
|
|
41
|
+
source: plugin.profile.source,
|
|
42
|
+
screenshots: plugin.profile.screenshots,
|
|
43
|
+
tags: plugin.profile.tags,
|
|
44
|
+
icon: plugin.profile.icon,
|
|
45
|
+
author: plugin.profile.author,
|
|
45
46
|
}),
|
|
46
47
|
modules: [],
|
|
47
48
|
}) as Plugin.Plugin;
|
|
@@ -52,18 +53,18 @@ export type PublicRegistryArticleProps = {
|
|
|
52
53
|
|
|
53
54
|
export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryArticleProps>(
|
|
54
55
|
({ id, ...props }, forwardedRef) => {
|
|
55
|
-
const { t } = useTranslation(meta.
|
|
56
|
+
const { t } = useTranslation(meta.profile.key);
|
|
56
57
|
const manager = usePluginManager();
|
|
57
58
|
const { invoke } = useOperationInvoker();
|
|
58
59
|
const { entries, loading, error } = useRegistryPlugins();
|
|
59
60
|
const plugins = useAtomValue(manager.plugins);
|
|
60
|
-
const installedIds = useMemo(() => plugins.map((plugin) => plugin.meta.
|
|
61
|
+
const installedIds = useMemo(() => plugins.map((plugin) => plugin.meta.profile.key), [plugins]);
|
|
61
62
|
const extraTagsById = useAutoTags(entries);
|
|
62
63
|
|
|
63
64
|
// Snapshot of installed plugin ids at mount time. Used to sort installed
|
|
64
65
|
// plugins to the top without having newly-installed rows jump up mid-session.
|
|
65
66
|
const [installedSnapshot] = useState<ReadonlySet<string>>(
|
|
66
|
-
() => new Set(manager.getPlugins().map((plugin) => plugin.meta.
|
|
67
|
+
() => new Set(manager.getPlugins().map((plugin) => plugin.meta.profile.key)),
|
|
67
68
|
);
|
|
68
69
|
|
|
69
70
|
const [installingIds, setInstallingIds] = useState<readonly string[]>([]);
|
|
@@ -74,7 +75,9 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
74
75
|
const moduleUrlById = useMemo(() => {
|
|
75
76
|
const map: Record<string, string> = {};
|
|
76
77
|
for (const entry of sortedEntries) {
|
|
77
|
-
|
|
78
|
+
if (entry.release?.moduleUrl) {
|
|
79
|
+
map[entry.profile.key] = entry.release.moduleUrl;
|
|
80
|
+
}
|
|
78
81
|
}
|
|
79
82
|
return map;
|
|
80
83
|
}, [sortedEntries]);
|
|
@@ -83,7 +86,9 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
83
86
|
const versionById = useMemo(() => {
|
|
84
87
|
const map: Record<string, string> = {};
|
|
85
88
|
for (const entry of sortedEntries) {
|
|
86
|
-
|
|
89
|
+
if (entry.release?.version) {
|
|
90
|
+
map[entry.profile.key] = entry.release.version;
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
return map;
|
|
89
94
|
}, [sortedEntries]);
|
|
@@ -92,13 +97,15 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
92
97
|
// is based on the mount-time snapshot so rows don't jump as the user
|
|
93
98
|
// installs plugins during the session.
|
|
94
99
|
const items = useMemo(() => {
|
|
95
|
-
const catalogIds = new Set(sortedEntries.map((entry) => entry.
|
|
100
|
+
const catalogIds = new Set(sortedEntries.map((entry) => entry.profile.key));
|
|
96
101
|
const remoteIds = new Set(UrlLoader.getRemoteEntries().map((entry) => entry.id));
|
|
97
102
|
const fromCatalog = sortedEntries.map(toDisplayPlugin);
|
|
98
|
-
const fromUrlOnly = plugins.filter(
|
|
103
|
+
const fromUrlOnly = plugins.filter(
|
|
104
|
+
(plugin) => remoteIds.has(plugin.meta.profile.key) && !catalogIds.has(plugin.meta.profile.key),
|
|
105
|
+
);
|
|
99
106
|
const installedFirst = [...fromCatalog, ...fromUrlOnly].sort((a, b) => {
|
|
100
|
-
const aInstalled = installedSnapshot.has(a.meta.
|
|
101
|
-
const bInstalled = installedSnapshot.has(b.meta.
|
|
107
|
+
const aInstalled = installedSnapshot.has(a.meta.profile.key);
|
|
108
|
+
const bInstalled = installedSnapshot.has(b.meta.profile.key);
|
|
102
109
|
if (aInstalled !== bInstalled) {
|
|
103
110
|
return aInstalled ? -1 : 1;
|
|
104
111
|
}
|
|
@@ -117,14 +124,14 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
117
124
|
setInstallingIds((prev) => (prev.includes(pluginId) ? prev : [...prev, pluginId]));
|
|
118
125
|
void Effect.gen(function* () {
|
|
119
126
|
const plugin = yield* manager.add(moduleUrl);
|
|
120
|
-
yield* manager.enable(plugin.meta.
|
|
127
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
121
128
|
// Persist the installed version for future update detection.
|
|
122
129
|
if (version) {
|
|
123
|
-
UrlLoader.setInstalledVersion(plugin.meta.
|
|
130
|
+
UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);
|
|
124
131
|
}
|
|
125
132
|
yield* invoke(ObservabilityOperation.SendEvent, {
|
|
126
133
|
name: 'plugins.install',
|
|
127
|
-
properties: { plugin: plugin.meta.
|
|
134
|
+
properties: { plugin: plugin.meta.profile.key, source: 'registry' },
|
|
128
135
|
});
|
|
129
136
|
}).pipe(
|
|
130
137
|
Effect.ensuring(Effect.sync(() => setInstallingIds((prev) => prev.filter((pid) => pid !== pluginId)))),
|
|
@@ -146,13 +153,13 @@ export const PublicRegistryArticle = composable<HTMLDivElement, PublicRegistryAr
|
|
|
146
153
|
// Unload the old version then re-install from the new URL.
|
|
147
154
|
yield* manager.remove(pluginId);
|
|
148
155
|
const plugin = yield* manager.add(moduleUrl);
|
|
149
|
-
yield* manager.enable(plugin.meta.
|
|
156
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
150
157
|
if (version) {
|
|
151
|
-
UrlLoader.setInstalledVersion(plugin.meta.
|
|
158
|
+
UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);
|
|
152
159
|
}
|
|
153
160
|
yield* invoke(ObservabilityOperation.SendEvent, {
|
|
154
161
|
name: 'plugins.update',
|
|
155
|
-
properties: { plugin: plugin.meta.
|
|
162
|
+
properties: { plugin: plugin.meta.profile.key, source: 'registry' },
|
|
156
163
|
});
|
|
157
164
|
}).pipe(
|
|
158
165
|
Effect.ensuring(Effect.sync(() => setUpdatingIds((prev) => prev.filter((pid) => pid !== pluginId)))),
|
|
@@ -12,7 +12,7 @@ import { composable } from '@dxos/react-ui';
|
|
|
12
12
|
import { BaseRegistryArticle } from '../BaseRegistryArticle';
|
|
13
13
|
|
|
14
14
|
const sortByPluginMeta = (a: Plugin.Plugin, b: Plugin.Plugin) =>
|
|
15
|
-
(a.meta.name ?? a.meta.
|
|
15
|
+
(a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);
|
|
16
16
|
|
|
17
17
|
export type RegistryArticleProps = {
|
|
18
18
|
id: string;
|
|
@@ -6,7 +6,7 @@ import { useAtomValue } from '@effect-atom/atom-react';
|
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
import React, { useCallback } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { usePluginManager, useSettingsState } from '@dxos/app-framework/ui';
|
|
10
10
|
import { type AppCapabilities } from '@dxos/app-toolkit';
|
|
11
11
|
import { EffectEx } from '@dxos/effect';
|
|
12
12
|
|
|
@@ -33,7 +33,7 @@ export const RegistrySettingsContainer = ({ subject }: RegistrySettingsContainer
|
|
|
33
33
|
await EffectEx.runAndForwardErrors(
|
|
34
34
|
Effect.gen(function* () {
|
|
35
35
|
const plugin = yield* manager.add(url);
|
|
36
|
-
yield* manager.enable(plugin.meta.
|
|
36
|
+
yield* manager.enable(plugin.meta.profile.key);
|
|
37
37
|
}),
|
|
38
38
|
);
|
|
39
39
|
},
|