@dxos/plugin-registry 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29
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/neutral/RegistryPlugin.node.mjs +153 -0
- 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 +3 -1
- package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
- package/dist/types/src/RegistryPlugin.node.d.ts +4 -0
- 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 +4 -2
- 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 +15 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +3 -2
- 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/commands/disable.d.ts +11 -0
- package/dist/types/src/commands/disable.d.ts.map +1 -0
- package/dist/types/src/commands/enable.d.ts +11 -0
- package/dist/types/src/commands/enable.d.ts.map +1 -0
- package/dist/types/src/commands/index.d.ts +3 -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 +11 -0
- package/dist/types/src/commands/util.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +46 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +68 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/index.d.ts +2 -0
- package/dist/types/src/components/PluginDetail/index.d.ts.map +1 -0
- 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 +40 -0
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginList.d.ts +23 -0
- package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts +67 -0
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/index.d.ts +2 -0
- package/dist/types/src/components/PluginList/index.d.ts.map +1 -0
- 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 -1
- 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 +4 -0
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
- package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
- package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts +7 -0
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -0
- package/dist/types/src/containers/PluginArticle/index.d.ts +2 -0
- package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -0
- 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 +8 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/use-auto-tags.d.ts +20 -0
- package/dist/types/src/hooks/use-auto-tags.d.ts.map +1 -0
- 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 +7 -4
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
- package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/storage.d.ts +18 -0
- package/dist/types/src/storage.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +48 -16
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +32 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +73 -36
- package/src/RegistryPlugin.node.ts +19 -0
- package/src/RegistryPlugin.test.ts +24 -0
- package/src/RegistryPlugin.tsx +20 -27
- package/src/capabilities/app-graph-builder.ts +180 -135
- package/src/capabilities/dev-plugin-loader.ts +39 -0
- package/src/capabilities/index.ts +10 -4
- package/src/capabilities/operation-handler.ts +16 -0
- package/src/capabilities/react-surface.tsx +124 -73
- package/src/capabilities/settings.ts +34 -0
- package/src/commands/disable.ts +47 -0
- package/src/commands/enable.ts +44 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/list.ts +41 -0
- package/src/commands/util.ts +24 -0
- package/src/components/PluginDetail/PluginDetail.stories.tsx +44 -0
- package/src/components/PluginDetail/PluginDetail.tsx +196 -0
- package/src/components/PluginDetail/index.ts +5 -0
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
- package/src/components/PluginFailureBadge/index.ts +5 -0
- package/src/components/PluginList/PluginItem.tsx +226 -0
- package/src/components/PluginList/PluginList.stories.tsx +87 -0
- package/src/components/PluginList/PluginList.tsx +52 -0
- package/src/components/PluginList/index.ts +5 -0
- package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
- package/src/components/RegistrySettings/index.ts +5 -0
- package/src/components/index.ts +2 -1
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +163 -0
- package/src/containers/BaseRegistryArticle/index.ts +5 -0
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +87 -0
- package/src/containers/LoadPluginDialog/index.ts +5 -0
- package/src/containers/PluginArticle/PluginArticle.tsx +336 -0
- package/src/containers/PluginArticle/index.ts +5 -0
- 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 +12 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/use-auto-tags.ts +51 -0
- 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 +21 -9
- package/src/operations/index.ts +7 -0
- package/src/operations/open-plugin-registry.ts +21 -0
- package/src/plugin.ts +11 -0
- package/src/storage.ts +50 -0
- package/src/translations.ts +52 -15
- package/src/types.ts +37 -1
- package/dist/lib/browser/app-graph-builder-6JQNRTEQ.mjs +0 -161
- package/dist/lib/browser/app-graph-builder-6JQNRTEQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-AEBASNOI.mjs +0 -16
- package/dist/lib/browser/chunk-AEBASNOI.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -71
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-UGDFPMAG.mjs +0 -23
- package/dist/lib/browser/intent-resolver-UGDFPMAG.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/react-surface-YATPY47L.mjs +0 -335
- package/dist/lib/browser/react-surface-YATPY47L.mjs.map +0 -7
- package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
- package/dist/types/src/components/Icon.stories.d.ts +0 -14
- package/dist/types/src/components/Icon.stories.d.ts.map +0 -1
- package/dist/types/src/components/PluginDetail.d.ts +0 -9
- package/dist/types/src/components/PluginDetail.d.ts.map +0 -1
- package/dist/types/src/components/PluginDetail.stories.d.ts +0 -33
- package/dist/types/src/components/PluginDetail.stories.d.ts.map +0 -1
- package/dist/types/src/components/PluginItem.d.ts +0 -13
- package/dist/types/src/components/PluginItem.d.ts.map +0 -1
- package/dist/types/src/components/PluginList.d.ts +0 -8
- package/dist/types/src/components/PluginList.d.ts.map +0 -1
- package/dist/types/src/components/PluginList.stories.d.ts +0 -37
- package/dist/types/src/components/PluginList.stories.d.ts.map +0 -1
- package/dist/types/src/components/RegistryContainer.d.ts +0 -7
- package/dist/types/src/components/RegistryContainer.d.ts.map +0 -1
- package/src/capabilities/intent-resolver.ts +0 -27
- package/src/components/Icon.stories.tsx +0 -110
- package/src/components/PluginDetail.stories.tsx +0 -46
- package/src/components/PluginDetail.tsx +0 -65
- package/src/components/PluginItem.tsx +0 -119
- package/src/components/PluginList.stories.tsx +0 -85
- package/src/components/PluginList.tsx +0 -26
- package/src/components/RegistryContainer.tsx +0 -84
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type MouseEvent, useCallback, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Plugin, type PluginManager } from '@dxos/app-framework';
|
|
8
|
+
import {
|
|
9
|
+
Button,
|
|
10
|
+
type ChromaticPalette,
|
|
11
|
+
Icon,
|
|
12
|
+
IconButton,
|
|
13
|
+
Input,
|
|
14
|
+
Link,
|
|
15
|
+
ListItem,
|
|
16
|
+
type NeutralPalette,
|
|
17
|
+
Tag,
|
|
18
|
+
useTranslation,
|
|
19
|
+
} from '@dxos/react-ui';
|
|
20
|
+
import { mx } from '@dxos/ui-theme';
|
|
21
|
+
import { getStyles } from '@dxos/ui-theme';
|
|
22
|
+
|
|
23
|
+
import { meta } from '#meta';
|
|
24
|
+
import { type RegistryTagType } from '#types';
|
|
25
|
+
|
|
26
|
+
import { PluginFailureBadge } from '../PluginFailureBadge';
|
|
27
|
+
|
|
28
|
+
export type PluginItemProps = {
|
|
29
|
+
plugin: Plugin.Plugin;
|
|
30
|
+
/** Ids of plugins currently installed (loaded into the manager). */
|
|
31
|
+
installed?: readonly string[];
|
|
32
|
+
/** Ids of plugins whose install is in flight. */
|
|
33
|
+
installing?: readonly string[];
|
|
34
|
+
enabled?: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* Derived tags (e.g. `registry`, `local`) to display alongside the plugin's own meta.tags.
|
|
37
|
+
* Not persisted to plugin meta; computed per-render by the container.
|
|
38
|
+
*/
|
|
39
|
+
extraTags?: readonly string[];
|
|
40
|
+
onClick?: (id: string) => void;
|
|
41
|
+
onChange?: (id: string, enabled: boolean) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Install handler. When provided and the plugin is not installed, an Install button
|
|
44
|
+
* is rendered in place of the enable switch.
|
|
45
|
+
*/
|
|
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[];
|
|
55
|
+
hasSettings?: (id: string) => boolean;
|
|
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;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const PluginItem = ({
|
|
66
|
+
plugin,
|
|
67
|
+
installed,
|
|
68
|
+
installing,
|
|
69
|
+
enabled = [],
|
|
70
|
+
extraTags,
|
|
71
|
+
onClick,
|
|
72
|
+
onChange,
|
|
73
|
+
onInstall,
|
|
74
|
+
hasUpdate,
|
|
75
|
+
onUpdate,
|
|
76
|
+
updating,
|
|
77
|
+
hasSettings: hasSettingsProp,
|
|
78
|
+
onSettings,
|
|
79
|
+
failure,
|
|
80
|
+
}: PluginItemProps) => {
|
|
81
|
+
const { t } = useTranslation(meta.id);
|
|
82
|
+
const { id, name, description, tags, icon = 'ph--circle--regular', iconHue = 'neutral' } = plugin.meta;
|
|
83
|
+
const displayTags = useMemo(() => {
|
|
84
|
+
if (!extraTags || extraTags.length === 0) {
|
|
85
|
+
return tags ?? [];
|
|
86
|
+
}
|
|
87
|
+
const set = new Set<string>(tags ?? []);
|
|
88
|
+
for (const tag of extraTags) {
|
|
89
|
+
set.add(tag);
|
|
90
|
+
}
|
|
91
|
+
return Array.from(set);
|
|
92
|
+
}, [tags, extraTags]);
|
|
93
|
+
const isEnabled = enabled.includes(id);
|
|
94
|
+
const isInstalled = installed ? installed.includes(id) : true;
|
|
95
|
+
const isInstalling = installing?.includes(id) ?? false;
|
|
96
|
+
const isUpdating = updating?.includes(id) ?? false;
|
|
97
|
+
const showInstallButton = !!onInstall && !isInstalled;
|
|
98
|
+
const showUpdateButton = !!onUpdate && isInstalled && !!hasUpdate;
|
|
99
|
+
const inputId = `${id}-input`;
|
|
100
|
+
const labelId = `${id}-label`;
|
|
101
|
+
const descriptionId = `${id}-description`;
|
|
102
|
+
const handleClick = useCallback(() => onClick?.(id), [id, onClick]);
|
|
103
|
+
|
|
104
|
+
const handleChange = useCallback(
|
|
105
|
+
(event: MouseEvent) => {
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
onChange?.(id, !isEnabled);
|
|
108
|
+
},
|
|
109
|
+
[id, isEnabled, onChange],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const handleInstall = useCallback(
|
|
113
|
+
(event: MouseEvent) => {
|
|
114
|
+
event.stopPropagation();
|
|
115
|
+
onInstall?.(id);
|
|
116
|
+
},
|
|
117
|
+
[id, onInstall],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const handleUpdate = useCallback(
|
|
121
|
+
(event: MouseEvent) => {
|
|
122
|
+
event.stopPropagation();
|
|
123
|
+
onUpdate?.(id);
|
|
124
|
+
},
|
|
125
|
+
[id, onUpdate],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const hasSettings = hasSettingsProp?.(id) ?? false;
|
|
129
|
+
const handleSettings = useCallback(() => onSettings?.(id), [id, onSettings]);
|
|
130
|
+
const styles = getStyles(iconHue);
|
|
131
|
+
const gridCols = 'grid grid-cols-[5rem_1fr]';
|
|
132
|
+
const gridRows = 'grid grid-rows-[40px_1fr_min-content_40px]';
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<ListItem.Root
|
|
136
|
+
key={id}
|
|
137
|
+
labelId={labelId}
|
|
138
|
+
data-testid={`pluginList.${id}`}
|
|
139
|
+
aria-describedby={descriptionId}
|
|
140
|
+
classNames={mx(gridCols, 'h-[14rem] w-full gap-3 pe-2 bg-modal-surface rounded-md overflow-hidden')}
|
|
141
|
+
>
|
|
142
|
+
<div className={mx(gridRows, 'justify-center rounded-l-md', styles.surface)}>
|
|
143
|
+
<Icon
|
|
144
|
+
classNames={mx('row-start-2 cursor-pointer', styles.surfaceText)}
|
|
145
|
+
icon={icon}
|
|
146
|
+
size={14}
|
|
147
|
+
onClick={handleClick}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div className={mx(gridRows)}>
|
|
152
|
+
<div className='flex items-center gap-2 overflow-hidden cursor-pointer' onClick={handleClick}>
|
|
153
|
+
<span className='text-lg truncate'>{name ?? id}</span>
|
|
154
|
+
{failure && <PluginFailureBadge failure={failure} />}
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div>
|
|
158
|
+
<p className={mx('text-description', 'line-clamp-4 min-w-0')}>{description}</p>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div className='flex -ms-0.5 overflow-x-auto scrollbar-none'>
|
|
162
|
+
{displayTags.map((tag) => (
|
|
163
|
+
<Tag key={tag} palette={tagColors[tag as RegistryTagType]} classNames='text-xs uppercase font-thin'>
|
|
164
|
+
{tag}
|
|
165
|
+
</Tag>
|
|
166
|
+
))}
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div className='flex gap-2 items-center text-sm'>
|
|
170
|
+
<IconButton
|
|
171
|
+
aria-describedby={descriptionId}
|
|
172
|
+
classNames='cursor-pointer'
|
|
173
|
+
icon='ph--gear--regular'
|
|
174
|
+
label={t('plugin-settings.label')}
|
|
175
|
+
iconOnly
|
|
176
|
+
size={4}
|
|
177
|
+
onClick={handleSettings}
|
|
178
|
+
disabled={!hasSettings}
|
|
179
|
+
/>
|
|
180
|
+
|
|
181
|
+
<Link aria-describedby={descriptionId} classNames='text-description cursor-pointer' onClick={handleClick}>
|
|
182
|
+
{t('details.label')}
|
|
183
|
+
</Link>
|
|
184
|
+
|
|
185
|
+
<div className='grow' />
|
|
186
|
+
<div className='pe-1'>
|
|
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 ? (
|
|
196
|
+
<Button
|
|
197
|
+
aria-describedby={descriptionId}
|
|
198
|
+
density='fine'
|
|
199
|
+
variant='primary'
|
|
200
|
+
disabled={isInstalling}
|
|
201
|
+
onClick={handleInstall}
|
|
202
|
+
>
|
|
203
|
+
{isInstalling ? t('installing.label') : t('install.label')}
|
|
204
|
+
</Button>
|
|
205
|
+
) : (
|
|
206
|
+
<Input.Root id={inputId}>
|
|
207
|
+
<Input.Switch classNames='self-center' checked={isEnabled} onClick={handleChange} />
|
|
208
|
+
</Input.Root>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</ListItem.Root>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const tagColors: Record<RegistryTagType, ChromaticPalette | NeutralPalette> = {
|
|
218
|
+
new: 'rose',
|
|
219
|
+
beta: 'teal',
|
|
220
|
+
labs: 'blue',
|
|
221
|
+
popular: 'green',
|
|
222
|
+
featured: 'pink',
|
|
223
|
+
experimental: 'amber',
|
|
224
|
+
registry: 'indigo',
|
|
225
|
+
local: 'neutral',
|
|
226
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type Plugin, Plugin as PluginNS } from '@dxos/app-framework';
|
|
9
|
+
import { random } from '@dxos/random';
|
|
10
|
+
import { ScrollArea } from '@dxos/react-ui';
|
|
11
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
12
|
+
import { getHashHue } from '@dxos/ui-theme';
|
|
13
|
+
|
|
14
|
+
import { translations } from '#translations';
|
|
15
|
+
import { RegistryTagType } from '#types';
|
|
16
|
+
|
|
17
|
+
import { PluginList } from './PluginList';
|
|
18
|
+
|
|
19
|
+
random.seed(1);
|
|
20
|
+
|
|
21
|
+
const icons = [
|
|
22
|
+
'ph--bug--regular',
|
|
23
|
+
'ph--compass--regular',
|
|
24
|
+
'ph--kanban--regular',
|
|
25
|
+
'ph--table--regular',
|
|
26
|
+
'ph--gear--regular',
|
|
27
|
+
'ph--github-logo--regular',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const DefaultStory = () => {
|
|
31
|
+
const [plugins] = useState<Plugin.Plugin[]>(
|
|
32
|
+
random.helpers.multiple(
|
|
33
|
+
() =>
|
|
34
|
+
PluginNS.define({
|
|
35
|
+
id: `org.dxos.plugin.plugin-${random.string.uuid()}`,
|
|
36
|
+
name: `${random.commerce.productName()}`,
|
|
37
|
+
description: random.lorem.sentences(Math.ceil(Math.random() * 3)),
|
|
38
|
+
tags: random.helpers.uniqueArray(RegistryTagType.literals as any, Math.floor(Math.random() * 3)),
|
|
39
|
+
icon: random.helpers.arrayElement(icons),
|
|
40
|
+
iconHue: getHashHue(random.string.uuid()),
|
|
41
|
+
homePage: random.datatype.boolean({ probability: 0.5 }) ? random.internet.url() : undefined,
|
|
42
|
+
source: random.internet.url(),
|
|
43
|
+
}).pipe(PluginNS.make)(),
|
|
44
|
+
{ count: 32 },
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
const [enabled, setEnabled] = useState<string[]>([]);
|
|
48
|
+
|
|
49
|
+
const handleChange = (id: string, enabled: boolean) => {
|
|
50
|
+
setEnabled((plugins) => (enabled ? [...plugins, id] : plugins.filter((plugin) => plugin === id)));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<ScrollArea.Root orientation='vertical'>
|
|
55
|
+
<ScrollArea.Viewport>
|
|
56
|
+
<PluginList plugins={plugins} enabled={enabled} onChange={handleChange} hasSettings={() => true} />
|
|
57
|
+
</ScrollArea.Viewport>
|
|
58
|
+
</ScrollArea.Root>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const meta = {
|
|
63
|
+
title: 'plugins/plugin-registry/components/PluginList',
|
|
64
|
+
component: PluginList,
|
|
65
|
+
render: DefaultStory,
|
|
66
|
+
parameters: {
|
|
67
|
+
translations,
|
|
68
|
+
},
|
|
69
|
+
} satisfies Meta<typeof PluginList>;
|
|
70
|
+
|
|
71
|
+
export default meta;
|
|
72
|
+
|
|
73
|
+
type Story = StoryObj<typeof meta>;
|
|
74
|
+
|
|
75
|
+
export const Default: Story = {
|
|
76
|
+
decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'bg-deck-surface' })],
|
|
77
|
+
parameters: {
|
|
78
|
+
layout: 'fullscreen',
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const FullScreen: Story = {
|
|
83
|
+
decorators: [withTheme(), withLayout({ scroll: true })],
|
|
84
|
+
parameters: {
|
|
85
|
+
layout: 'fullscreen',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Plugin, type PluginManager } from '@dxos/app-framework';
|
|
8
|
+
import { List } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { PluginItem, type PluginItemProps } from './PluginItem';
|
|
11
|
+
|
|
12
|
+
export type PluginListProps = Omit<PluginItemProps, 'plugin' | 'extraTags' | 'hasUpdate' | 'failure'> & {
|
|
13
|
+
plugins?: readonly Plugin.Plugin[];
|
|
14
|
+
/**
|
|
15
|
+
* Map from plugin id → extra tags to display (e.g. `registry`, `local`).
|
|
16
|
+
* Computed by the container; not persisted to plugin meta.
|
|
17
|
+
*/
|
|
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>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const PluginList = ({
|
|
32
|
+
plugins = [],
|
|
33
|
+
extraTagsById,
|
|
34
|
+
updateAvailableIds,
|
|
35
|
+
failuresById,
|
|
36
|
+
...props
|
|
37
|
+
}: PluginListProps) => {
|
|
38
|
+
return (
|
|
39
|
+
<List classNames='grid grid-cols-[repeat(auto-fill,minmax(18rem,1fr))] auto-rows-[max-content] gap-4 p-4'>
|
|
40
|
+
{plugins.map((plugin) => (
|
|
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
|
+
/>
|
|
49
|
+
))}
|
|
50
|
+
</List>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -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
|
+
};
|