@dxos/plugin-registry 0.8.4-main.b97322e → 0.8.4-main.bcb3aa67d6
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/browser/cli/index.mjs +219 -0
- package/dist/lib/browser/cli/index.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +52 -52
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/cli/index.mjs +221 -0
- package/dist/lib/node-esm/cli/index.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +73 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/types/src/RegistryPlugin.d.ts +2 -1
- package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
- 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/index.d.ts +5 -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/cli/commands/disable.d.ts +11 -0
- package/dist/types/src/cli/commands/disable.d.ts.map +1 -0
- package/dist/types/src/cli/commands/enable.d.ts +11 -0
- package/dist/types/src/cli/commands/enable.d.ts.map +1 -0
- package/dist/types/src/cli/commands/index.d.ts +3 -0
- package/dist/types/src/cli/commands/index.d.ts.map +1 -0
- package/dist/types/src/cli/commands/list.d.ts +7 -0
- package/dist/types/src/cli/commands/list.d.ts.map +1 -0
- package/dist/types/src/cli/commands/util.d.ts +11 -0
- package/dist/types/src/cli/commands/util.d.ts.map +1 -0
- package/dist/types/src/cli/index.d.ts +3 -0
- package/dist/types/src/cli/index.d.ts.map +1 -0
- package/dist/types/src/cli/plugin.d.ts +3 -0
- package/dist/types/src/cli/plugin.d.ts.map +1 -0
- package/dist/types/src/cli/storage.d.ts +18 -0
- package/dist/types/src/cli/storage.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +15 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +43 -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/{PluginItem.d.ts → PluginList/PluginItem.d.ts} +2 -2
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -0
- package/dist/types/src/components/{PluginList.d.ts → PluginList/PluginList.d.ts} +1 -1
- package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts +38 -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/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- 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 +3 -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 +3 -0
- package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts +14 -0
- package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts.map +1 -0
- package/dist/types/src/containers/PluginRegistry/index.d.ts +3 -0
- package/dist/types/src/containers/PluginRegistry/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +6 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- 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/translations.d.ts +19 -15
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +3 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +59 -34
- package/src/RegistryPlugin.tsx +12 -26
- package/src/capabilities/app-graph-builder.ts +128 -135
- package/src/capabilities/index.ts +8 -4
- package/src/capabilities/operation-handler.ts +16 -0
- package/src/capabilities/react-surface.tsx +83 -79
- package/src/cli/commands/disable.ts +47 -0
- package/src/cli/commands/enable.ts +44 -0
- package/src/cli/commands/index.ts +14 -0
- package/src/cli/commands/list.ts +41 -0
- package/src/cli/commands/util.ts +24 -0
- package/src/cli/index.ts +6 -0
- package/src/cli/plugin.ts +17 -0
- package/src/cli/storage.ts +50 -0
- package/src/components/PluginDetail/PluginDetail.stories.tsx +44 -0
- package/src/components/PluginDetail/PluginDetail.tsx +84 -0
- package/src/components/PluginDetail/index.ts +5 -0
- package/src/components/PluginList/PluginItem.tsx +124 -0
- package/src/components/PluginList/PluginList.stories.tsx +87 -0
- package/src/components/{PluginList.tsx → PluginList/PluginList.tsx} +3 -5
- package/src/components/PluginList/index.ts +5 -0
- package/src/components/index.ts +0 -1
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +84 -0
- package/src/containers/LoadPluginDialog/index.ts +7 -0
- package/src/containers/PluginArticle/PluginArticle.tsx +28 -0
- package/src/containers/PluginArticle/index.ts +7 -0
- package/src/containers/PluginRegistry/PluginRegistry.tsx +94 -0
- package/src/containers/PluginRegistry/index.ts +7 -0
- package/src/containers/index.ts +10 -0
- package/src/meta.ts +19 -9
- package/src/operations/index.ts +7 -0
- package/src/operations/open-plugin-registry.ts +21 -0
- package/src/translations.ts +19 -15
- package/src/types.ts +5 -1
- package/dist/lib/browser/app-graph-builder-LKQBQDUA.mjs +0 -161
- package/dist/lib/browser/app-graph-builder-LKQBQDUA.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/intent-resolver-W67CIJI3.mjs +0 -23
- package/dist/lib/browser/intent-resolver-W67CIJI3.mjs.map +0 -7
- package/dist/lib/browser/react-surface-3GNB7AOY.mjs +0 -333
- package/dist/lib/browser/react-surface-3GNB7AOY.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 -6
- 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 -8
- package/dist/types/src/components/PluginDetail.stories.d.ts.map +0 -1
- package/dist/types/src/components/PluginItem.d.ts.map +0 -1
- package/dist/types/src/components/PluginList.d.ts.map +0 -1
- package/dist/types/src/components/PluginList.stories.d.ts +0 -11
- 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 -108
- package/src/components/PluginDetail.stories.tsx +0 -45
- package/src/components/PluginDetail.tsx +0 -63
- package/src/components/PluginItem.tsx +0 -119
- package/src/components/PluginList.stories.tsx +0 -82
- package/src/components/RegistryContainer.tsx +0 -84
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type MouseEvent, useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
8
|
+
import { type ChromaticPalette, Icon, IconButton, Input, Link, ListItem, Tag, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { mx } from '@dxos/ui-theme';
|
|
10
|
+
import { getStyles } from '@dxos/ui-theme';
|
|
11
|
+
|
|
12
|
+
import { meta } from '#meta';
|
|
13
|
+
import { type RegistryTagType } from '#types';
|
|
14
|
+
|
|
15
|
+
export type PluginItemProps = {
|
|
16
|
+
plugin: Plugin.Plugin;
|
|
17
|
+
installed?: readonly string[];
|
|
18
|
+
enabled?: readonly string[];
|
|
19
|
+
onClick?: (id: string) => void;
|
|
20
|
+
onChange?: (id: string, enabled: boolean) => void;
|
|
21
|
+
hasSettings?: (id: string) => boolean;
|
|
22
|
+
onSettings?: (id: string) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const PluginItem = ({
|
|
26
|
+
plugin,
|
|
27
|
+
enabled = [],
|
|
28
|
+
onClick,
|
|
29
|
+
onChange,
|
|
30
|
+
hasSettings: hasSettingsProp,
|
|
31
|
+
onSettings,
|
|
32
|
+
}: PluginItemProps) => {
|
|
33
|
+
const { t } = useTranslation(meta.id);
|
|
34
|
+
const { id, name, description, tags, icon = 'ph--circle--regular', iconHue = 'neutral' } = plugin.meta;
|
|
35
|
+
const isEnabled = enabled.includes(id);
|
|
36
|
+
const inputId = `${id}-input`;
|
|
37
|
+
const labelId = `${id}-label`;
|
|
38
|
+
const descriptionId = `${id}-description`;
|
|
39
|
+
const handleClick = useCallback(() => onClick?.(id), [id, onClick]);
|
|
40
|
+
|
|
41
|
+
const handleChange = useCallback(
|
|
42
|
+
(event: MouseEvent) => {
|
|
43
|
+
event.stopPropagation();
|
|
44
|
+
onChange?.(id, !isEnabled);
|
|
45
|
+
},
|
|
46
|
+
[id, isEnabled, onChange],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const hasSettings = hasSettingsProp?.(id) ?? false;
|
|
50
|
+
const handleSettings = useCallback(() => onSettings?.(id), [id, onSettings]);
|
|
51
|
+
const styles = getStyles(iconHue);
|
|
52
|
+
const gridCols = 'grid grid-cols-[5rem_1fr]';
|
|
53
|
+
const gridRows = 'grid grid-rows-[40px_1fr_min-content_40px]';
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<ListItem.Root
|
|
57
|
+
key={id}
|
|
58
|
+
labelId={labelId}
|
|
59
|
+
data-testid={`pluginList.${id}`}
|
|
60
|
+
aria-describedby={descriptionId}
|
|
61
|
+
classNames={mx(gridCols, 'h-[14rem] w-full gap-3 pe-2 bg-modal-surface rounded-md overflow-hidden')}
|
|
62
|
+
>
|
|
63
|
+
<div className={mx(gridRows, 'justify-center rounded-l-md', styles.surface)}>
|
|
64
|
+
<Icon
|
|
65
|
+
classNames={mx('row-start-2 cursor-pointer', styles.surfaceText)}
|
|
66
|
+
icon={icon}
|
|
67
|
+
size={14}
|
|
68
|
+
onClick={handleClick}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className={mx(gridRows)}>
|
|
73
|
+
<div className='flex items-center overflow-hidden cursor-pointer' onClick={handleClick}>
|
|
74
|
+
<span className='text-lg truncate'>{name ?? id}</span>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div>
|
|
78
|
+
<p className={mx('text-description', 'line-clamp-4 min-w-0')}>{description}</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className='flex -ms-0.5 overflow-x-auto scrollbar-none'>
|
|
82
|
+
{tags?.map((tag) => (
|
|
83
|
+
<Tag key={tag} palette={tagColors[tag as RegistryTagType]} classNames='text-xs uppercase font-thin'>
|
|
84
|
+
{tag}
|
|
85
|
+
</Tag>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className='flex gap-2 items-center text-sm'>
|
|
90
|
+
<IconButton
|
|
91
|
+
aria-describedby={descriptionId}
|
|
92
|
+
classNames='cursor-pointer'
|
|
93
|
+
icon='ph--gear--regular'
|
|
94
|
+
label={t('settings.label')}
|
|
95
|
+
iconOnly
|
|
96
|
+
size={4}
|
|
97
|
+
onClick={handleSettings}
|
|
98
|
+
disabled={!hasSettings}
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<Link aria-describedby={descriptionId} classNames='text-description cursor-pointer' onClick={handleClick}>
|
|
102
|
+
{t('details.label')}
|
|
103
|
+
</Link>
|
|
104
|
+
|
|
105
|
+
<div className='grow' />
|
|
106
|
+
<div className='pe-1'>
|
|
107
|
+
<Input.Root id={inputId}>
|
|
108
|
+
<Input.Switch classNames='self-center' checked={isEnabled} onClick={handleChange} />
|
|
109
|
+
</Input.Root>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</ListItem.Root>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const tagColors: Record<RegistryTagType, ChromaticPalette> = {
|
|
118
|
+
new: 'rose',
|
|
119
|
+
beta: 'teal',
|
|
120
|
+
labs: 'blue',
|
|
121
|
+
popular: 'green',
|
|
122
|
+
featured: 'pink',
|
|
123
|
+
experimental: 'amber',
|
|
124
|
+
};
|
|
@@ -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 { faker } 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
|
+
faker.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
|
+
faker.helpers.multiple(
|
|
33
|
+
() =>
|
|
34
|
+
PluginNS.define({
|
|
35
|
+
id: `org.dxos.plugin.plugin-${faker.string.uuid()}`,
|
|
36
|
+
name: `${faker.commerce.productName()}`,
|
|
37
|
+
description: faker.lorem.sentences(Math.ceil(Math.random() * 3)),
|
|
38
|
+
tags: faker.helpers.uniqueArray(RegistryTagType.literals as any, Math.floor(Math.random() * 3)),
|
|
39
|
+
icon: faker.helpers.arrayElement(icons),
|
|
40
|
+
iconHue: getHashHue(faker.string.uuid()),
|
|
41
|
+
homePage: faker.datatype.boolean({ probability: 0.5 }) ? faker.internet.url() : undefined,
|
|
42
|
+
source: faker.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
|
+
};
|
|
@@ -10,16 +10,14 @@ import { List } from '@dxos/react-ui';
|
|
|
10
10
|
import { PluginItem, type PluginItemProps } from './PluginItem';
|
|
11
11
|
|
|
12
12
|
export type PluginListProps = Omit<PluginItemProps, 'plugin'> & {
|
|
13
|
-
plugins?: readonly Plugin[];
|
|
13
|
+
plugins?: readonly Plugin.Plugin[];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export const PluginList = ({ plugins = [], ...props }: PluginListProps) => {
|
|
17
17
|
return (
|
|
18
|
-
<List classNames='grid grid-cols-[repeat(auto-fill,minmax(
|
|
18
|
+
<List classNames='grid grid-cols-[repeat(auto-fill,minmax(18rem,1fr))] auto-rows-[max-content] gap-4 p-4'>
|
|
19
19
|
{plugins.map((plugin) => (
|
|
20
|
-
<
|
|
21
|
-
<PluginItem plugin={plugin} {...props} />
|
|
22
|
-
</div>
|
|
20
|
+
<PluginItem key={plugin.meta.id} plugin={plugin} {...props} />
|
|
23
21
|
))}
|
|
24
22
|
</List>
|
|
25
23
|
);
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { usePluginManager } from '@dxos/app-framework/ui';
|
|
9
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
10
|
+
import { Button, Dialog, Input, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
|
|
12
|
+
import { meta } from '../../meta';
|
|
13
|
+
|
|
14
|
+
export const LOAD_PLUGIN_DIALOG = `${meta.id}.LoadPluginDialog`;
|
|
15
|
+
|
|
16
|
+
export const LoadPluginDialog = () => {
|
|
17
|
+
const manager = usePluginManager();
|
|
18
|
+
const { t } = useTranslation(meta.id);
|
|
19
|
+
const [url, setUrl] = useState('');
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [error, setError] = useState<string | null>(null);
|
|
22
|
+
const closeRef = useRef<HTMLButtonElement | null>(null);
|
|
23
|
+
|
|
24
|
+
const handleLoad = useCallback(() => {
|
|
25
|
+
if (!url.trim()) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setLoading(true);
|
|
30
|
+
setError(null);
|
|
31
|
+
|
|
32
|
+
void Effect.gen(function* () {
|
|
33
|
+
yield* manager.add(url.trim());
|
|
34
|
+
closeRef.current?.click();
|
|
35
|
+
}).pipe(
|
|
36
|
+
Effect.catchAll((err) =>
|
|
37
|
+
Effect.sync(() => {
|
|
38
|
+
setError(String(err));
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
Effect.tap(() => Effect.sync(() => setLoading(false))),
|
|
42
|
+
runAndForwardErrors,
|
|
43
|
+
);
|
|
44
|
+
}, [url, manager]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Dialog.Content>
|
|
48
|
+
<Dialog.Header>
|
|
49
|
+
<Dialog.Title>{t('load-by-url-dialog.title')}</Dialog.Title>
|
|
50
|
+
<Dialog.Close asChild>
|
|
51
|
+
<Dialog.CloseIconButton ref={closeRef} />
|
|
52
|
+
</Dialog.Close>
|
|
53
|
+
</Dialog.Header>
|
|
54
|
+
<Dialog.Body>
|
|
55
|
+
<div className='flex flex-col gap-4'>
|
|
56
|
+
<Input.Root validationValence={error ? 'error' : undefined}>
|
|
57
|
+
<Input.Label>{t('plugin-url.label')}</Input.Label>
|
|
58
|
+
<Input.TextInput
|
|
59
|
+
placeholder='https://example.com/plugin.mjs'
|
|
60
|
+
value={url}
|
|
61
|
+
onChange={(event) => {
|
|
62
|
+
setUrl(event.target.value);
|
|
63
|
+
setError(null);
|
|
64
|
+
}}
|
|
65
|
+
onKeyDown={(event) => {
|
|
66
|
+
if (event.key === 'Enter') {
|
|
67
|
+
handleLoad();
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
70
|
+
disabled={loading}
|
|
71
|
+
autoFocus
|
|
72
|
+
/>
|
|
73
|
+
{error && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
74
|
+
</Input.Root>
|
|
75
|
+
<div className='flex justify-end'>
|
|
76
|
+
<Button variant='primary' disabled={!url.trim() || loading} onClick={handleLoad}>
|
|
77
|
+
{loading ? t('loading.label') : t('load-plugin.label')}
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</Dialog.Body>
|
|
82
|
+
</Dialog.Content>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
8
|
+
import { usePluginManager } from '@dxos/app-framework/ui';
|
|
9
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
10
|
+
|
|
11
|
+
import { PluginDetail } from '#components';
|
|
12
|
+
|
|
13
|
+
// TODO(burdon): Convert to ECHO type.
|
|
14
|
+
export type PluginArticleProps = { subject: Plugin.Plugin };
|
|
15
|
+
|
|
16
|
+
export const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {
|
|
17
|
+
const manager = usePluginManager();
|
|
18
|
+
const enabled = manager.getEnabled().includes(plugin.meta.id);
|
|
19
|
+
const handleEnableChange = useCallback(
|
|
20
|
+
(enabled: boolean) =>
|
|
21
|
+
enabled
|
|
22
|
+
? runAndForwardErrors(manager.enable(plugin.meta.id))
|
|
23
|
+
: runAndForwardErrors(manager.disable(plugin.meta.id)),
|
|
24
|
+
[manager, plugin.meta.id],
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return <PluginDetail plugin={plugin} enabled={enabled} onEnabledChange={handleEnableChange} />;
|
|
28
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import React, { useCallback, useMemo } from 'react';
|
|
8
|
+
|
|
9
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
10
|
+
import { useCapabilities, useOperationInvoker, usePluginManager } from '@dxos/app-framework/ui';
|
|
11
|
+
import { AppCapabilities, LayoutOperation, SettingsOperation } from '@dxos/app-toolkit';
|
|
12
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
13
|
+
import { ObservabilityOperation } from '@dxos/plugin-observability/operations';
|
|
14
|
+
import { ScrollArea } from '@dxos/react-ui';
|
|
15
|
+
import { composable, composableProps } from '@dxos/ui-theme';
|
|
16
|
+
|
|
17
|
+
import { PluginList } from '#components';
|
|
18
|
+
import { getPluginPath } from '#meta';
|
|
19
|
+
|
|
20
|
+
const sortByPluginMeta = ({ meta: { name: a = '' } }: Plugin.Plugin, { meta: { name: b = '' } }: Plugin.Plugin) =>
|
|
21
|
+
a.localeCompare(b);
|
|
22
|
+
|
|
23
|
+
export type PluginRegistryProps = {
|
|
24
|
+
id: string;
|
|
25
|
+
plugins: Plugin.Plugin[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const PluginRegistry = composable<HTMLDivElement, PluginRegistryProps>(
|
|
29
|
+
({ id, plugins: pluginsProp, ...props }, forwardedRef) => {
|
|
30
|
+
const manager = usePluginManager();
|
|
31
|
+
const { invoke, invokePromise } = useOperationInvoker();
|
|
32
|
+
const plugins = useMemo(() => pluginsProp.sort(sortByPluginMeta), [pluginsProp]);
|
|
33
|
+
const enabled = useAtomValue(manager.enabled);
|
|
34
|
+
const allSettings = useCapabilities(AppCapabilities.Settings);
|
|
35
|
+
|
|
36
|
+
// TODO(wittjosiah): Factor out to an intent?
|
|
37
|
+
const handleChange = useCallback(
|
|
38
|
+
(id: string, enabled: boolean) =>
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
if (enabled) {
|
|
41
|
+
yield* manager.enable(id);
|
|
42
|
+
} else {
|
|
43
|
+
yield* manager.disable(id);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
yield* invoke(ObservabilityOperation.SendEvent, {
|
|
47
|
+
name: 'plugins.toggle',
|
|
48
|
+
properties: {
|
|
49
|
+
plugin: id,
|
|
50
|
+
enabled,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}).pipe(runAndForwardErrors),
|
|
54
|
+
[invoke, manager],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const handleClick = useCallback(
|
|
58
|
+
(pluginId: string) =>
|
|
59
|
+
invokePromise(LayoutOperation.Open, {
|
|
60
|
+
subject: [getPluginPath(pluginId)],
|
|
61
|
+
pivotId: id,
|
|
62
|
+
positioning: 'end',
|
|
63
|
+
}),
|
|
64
|
+
[invokePromise, id],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const hasSettings = useCallback(
|
|
68
|
+
(pluginId: string) => allSettings.some((s) => s.prefix === pluginId),
|
|
69
|
+
[allSettings],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleSettings = useCallback(
|
|
73
|
+
(pluginId: string) => invokePromise(SettingsOperation.Open, { plugin: pluginId }),
|
|
74
|
+
[invokePromise],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<ScrollArea.Root {...composableProps(props)} orientation='vertical' ref={forwardedRef}>
|
|
79
|
+
<ScrollArea.Viewport>
|
|
80
|
+
<PluginList
|
|
81
|
+
plugins={plugins}
|
|
82
|
+
enabled={enabled}
|
|
83
|
+
onClick={handleClick}
|
|
84
|
+
onChange={handleChange}
|
|
85
|
+
hasSettings={hasSettings}
|
|
86
|
+
onSettings={handleSettings}
|
|
87
|
+
/>
|
|
88
|
+
</ScrollArea.Viewport>
|
|
89
|
+
</ScrollArea.Root>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
PluginRegistry.displayName = 'PluginRegistry';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type ComponentType, lazy } from 'react';
|
|
6
|
+
|
|
7
|
+
export { LOAD_PLUGIN_DIALOG } from './LoadPluginDialog/LoadPluginDialog';
|
|
8
|
+
export const LoadPluginDialog: ComponentType<any> = lazy(() => import('./LoadPluginDialog'));
|
|
9
|
+
export const PluginArticle: ComponentType<any> = lazy(() => import('./PluginArticle'));
|
|
10
|
+
export const PluginRegistry: ComponentType<any> = lazy(() => import('./PluginRegistry'));
|
package/src/meta.ts
CHANGED
|
@@ -2,17 +2,27 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
6
|
+
import { pinnedWorkspaceId } from '@dxos/app-toolkit';
|
|
7
|
+
import { trim } from '@dxos/util';
|
|
6
8
|
|
|
7
|
-
export const
|
|
8
|
-
|
|
9
|
-
// TODO(wittjosiah): Deck does not currently support `/` in ids.
|
|
10
|
-
// TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
|
|
11
|
-
// Ideally this should be worked into the data model in a generic way.
|
|
12
|
-
export const REGISTRY_ID = '!dxos:plugin-registry';
|
|
9
|
+
export const REGISTRY_ID = pinnedWorkspaceId('dxos:plugin-registry');
|
|
13
10
|
export const REGISTRY_KEY = 'plugin-registry';
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
// TODO(wittjosiah): Should this be a special separator or use the standard path separator?
|
|
13
|
+
const CATEGORY_SEPARATOR = '>';
|
|
14
|
+
|
|
15
|
+
/** Build a registry category node ID. */
|
|
16
|
+
export const registryCategoryId = (category: string): string => `${REGISTRY_KEY}${CATEGORY_SEPARATOR}${category}`;
|
|
17
|
+
|
|
18
|
+
/** Qualified graph path to a specific plugin node. */
|
|
19
|
+
export const getPluginPath = (pluginId: string): string => `root/${REGISTRY_ID}/${pluginId}`;
|
|
20
|
+
|
|
21
|
+
export const meta: Plugin.Meta = {
|
|
22
|
+
id: 'org.dxos.plugin.registry',
|
|
17
23
|
name: 'Plugins',
|
|
24
|
+
description: trim`
|
|
25
|
+
Plugin management system for discovering, installing, and configuring workspace extensions.
|
|
26
|
+
Browse available plugins and customize your workspace capabilities.
|
|
27
|
+
`,
|
|
18
28
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { LayoutOperation, SettingsOperation, getSpacePath } from '@dxos/app-toolkit';
|
|
8
|
+
import { Operation } from '@dxos/operation';
|
|
9
|
+
|
|
10
|
+
import { REGISTRY_ID } from '#meta';
|
|
11
|
+
|
|
12
|
+
const handler: Operation.WithHandler<typeof SettingsOperation.OpenPluginRegistry> =
|
|
13
|
+
SettingsOperation.OpenPluginRegistry.pipe(
|
|
14
|
+
Operation.withHandler(
|
|
15
|
+
Effect.fnUntraced(function* () {
|
|
16
|
+
yield* Operation.invoke(LayoutOperation.SwitchWorkspace, { subject: getSpacePath(REGISTRY_ID) });
|
|
17
|
+
}),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export default handler;
|
package/src/translations.ts
CHANGED
|
@@ -4,26 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Resource } from '@dxos/react-ui';
|
|
6
6
|
|
|
7
|
-
import { meta } from '
|
|
7
|
+
import { meta } from '#meta';
|
|
8
8
|
|
|
9
9
|
export const translations = [
|
|
10
10
|
{
|
|
11
11
|
'en-US': {
|
|
12
12
|
[meta.id]: {
|
|
13
|
-
'plugin
|
|
14
|
-
'details
|
|
15
|
-
'home
|
|
16
|
-
'source
|
|
17
|
-
'plugin
|
|
18
|
-
'all
|
|
19
|
-
'installed
|
|
20
|
-
'recommended
|
|
21
|
-
'labs
|
|
22
|
-
'community
|
|
23
|
-
'load
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
13
|
+
'plugin.name': 'Registry',
|
|
14
|
+
'details.label': 'Details',
|
|
15
|
+
'home-page.label': 'Website',
|
|
16
|
+
'source.label': 'Source',
|
|
17
|
+
'plugin-registry.label': 'Plugin Registry',
|
|
18
|
+
'all-plugins.label': 'All',
|
|
19
|
+
'installed-plugins.label': 'Enabled',
|
|
20
|
+
'recommended-plugins.label': 'Recommended',
|
|
21
|
+
'labs-plugins.label': 'Labs',
|
|
22
|
+
'community-plugins.label': 'Community',
|
|
23
|
+
'load-by-url.label': 'Load from URL',
|
|
24
|
+
'load-by-url-dialog.title': 'Load Plugin from URL',
|
|
25
|
+
'plugin-url.label': 'Plugin URL',
|
|
26
|
+
'load-plugin.label': 'Load Plugin',
|
|
27
|
+
'loading.label': 'Loading...',
|
|
28
|
+
'coming-soon.label': 'Coming soon!',
|
|
29
|
+
'open-plugin-registry.label': 'Open plugin registry',
|
|
30
|
+
'settings.label': 'Settings',
|
|
27
31
|
},
|
|
28
32
|
},
|
|
29
33
|
},
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
|
|
7
7
|
export const RegistrySettingsSchema = Schema.mutable(
|
|
8
8
|
Schema.Struct({
|
|
@@ -11,3 +11,7 @@ export const RegistrySettingsSchema = Schema.mutable(
|
|
|
11
11
|
);
|
|
12
12
|
|
|
13
13
|
export type RegistrySettings = Schema.Schema.Type<typeof RegistrySettingsSchema>;
|
|
14
|
+
|
|
15
|
+
export const RegistryTagType = Schema.Literal('new', 'beta', 'labs', 'popular', 'featured', 'experimental');
|
|
16
|
+
|
|
17
|
+
export type RegistryTagType = Schema.Schema.Type<typeof RegistryTagType>;
|