@dxos/plugin-registry 0.7.5-main.9d26e3a → 0.7.5-main.e9bb01b
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/app-graph-builder-PPWF5KP5.mjs +164 -0
- package/dist/lib/browser/app-graph-builder-PPWF5KP5.mjs.map +7 -0
- package/dist/lib/browser/chunk-RGSIE33X.mjs +16 -0
- package/dist/lib/browser/chunk-RGSIE33X.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +49 -189
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/intent-resolver-2JQUQJI5.mjs +23 -0
- package/dist/lib/browser/intent-resolver-2JQUQJI5.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-surface-NRETIUCH.mjs +295 -0
- package/dist/lib/browser/react-surface-NRETIUCH.mjs.map +7 -0
- package/dist/types/src/RegistryPlugin.d.ts +1 -6
- package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +180 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/intent-resolver.d.ts +4 -0
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +4 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail.d.ts +9 -0
- package/dist/types/src/components/PluginDetail.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail.stories.d.ts +8 -0
- package/dist/types/src/components/PluginDetail.stories.d.ts.map +1 -0
- package/dist/types/src/components/PluginItem.d.ts +11 -0
- package/dist/types/src/components/PluginItem.d.ts.map +1 -0
- package/dist/types/src/components/PluginList.d.ts +5 -8
- package/dist/types/src/components/PluginList.d.ts.map +1 -1
- package/dist/types/src/components/PluginList.stories.d.ts +6 -1
- package/dist/types/src/components/PluginList.stories.d.ts.map +1 -1
- package/dist/types/src/components/RegistryContainer.d.ts +7 -0
- package/dist/types/src/components/RegistryContainer.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/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +3 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +10 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +6 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -23
- package/src/RegistryPlugin.tsx +26 -41
- package/src/capabilities/app-graph-builder.ts +126 -0
- package/src/capabilities/index.ts +9 -0
- package/src/capabilities/intent-resolver.ts +27 -0
- package/src/capabilities/react-surface.tsx +90 -0
- package/src/components/PluginDetail.stories.tsx +45 -0
- package/src/components/PluginDetail.tsx +63 -0
- package/src/components/PluginItem.tsx +105 -0
- package/src/components/PluginList.stories.tsx +30 -21
- package/src/components/PluginList.tsx +13 -90
- package/src/components/RegistryContainer.tsx +51 -0
- package/src/components/index.ts +2 -1
- package/src/index.ts +1 -4
- package/src/meta.ts +5 -1
- package/src/translations.ts +12 -2
- package/src/types.ts +13 -0
- package/dist/lib/browser/chunk-NEMFDAOB.mjs +0 -12
- package/dist/lib/browser/chunk-NEMFDAOB.mjs.map +0 -7
- package/dist/lib/browser/meta.mjs +0 -9
- package/dist/lib/browser/meta.mjs.map +0 -7
- package/dist/types/src/components/PluginSettings.d.ts +0 -6
- package/dist/types/src/components/PluginSettings.d.ts.map +0 -1
- package/src/components/PluginSettings.tsx +0 -108
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities, contributes, createIntent, SettingsAction, type PluginsContext } from '@dxos/app-framework';
|
|
6
|
+
import { createExtension, type Node } from '@dxos/plugin-graph';
|
|
7
|
+
|
|
8
|
+
import { REGISTRY_ID, REGISTRY_KEY, REGISTRY_PLUGIN } from '../meta';
|
|
9
|
+
|
|
10
|
+
export default (context: PluginsContext) =>
|
|
11
|
+
contributes(Capabilities.AppGraphBuilder, [
|
|
12
|
+
createExtension({
|
|
13
|
+
id: REGISTRY_PLUGIN,
|
|
14
|
+
filter: (node): node is Node<null> => node.id === 'root',
|
|
15
|
+
actions: () => [
|
|
16
|
+
{
|
|
17
|
+
id: REGISTRY_PLUGIN,
|
|
18
|
+
data: async () => {
|
|
19
|
+
const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
|
|
20
|
+
await dispatch(createIntent(SettingsAction.OpenPluginRegistry));
|
|
21
|
+
},
|
|
22
|
+
properties: {
|
|
23
|
+
label: ['open plugin registry label', { ns: REGISTRY_PLUGIN }],
|
|
24
|
+
icon: 'ph--squares-four--regular',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}),
|
|
29
|
+
createExtension({
|
|
30
|
+
id: REGISTRY_PLUGIN,
|
|
31
|
+
filter: (node): node is Node<null> => node.id === 'root',
|
|
32
|
+
connector: () => [
|
|
33
|
+
{
|
|
34
|
+
id: REGISTRY_ID,
|
|
35
|
+
type: REGISTRY_PLUGIN,
|
|
36
|
+
properties: {
|
|
37
|
+
label: ['plugin registry label', { ns: REGISTRY_PLUGIN }],
|
|
38
|
+
icon: 'ph--squares-four--regular',
|
|
39
|
+
disposition: 'pin-end',
|
|
40
|
+
testId: 'treeView.pluginRegistry',
|
|
41
|
+
},
|
|
42
|
+
nodes: [
|
|
43
|
+
{
|
|
44
|
+
id: `${REGISTRY_KEY}:all`,
|
|
45
|
+
type: 'category',
|
|
46
|
+
data: `${REGISTRY_KEY}:all`,
|
|
47
|
+
properties: {
|
|
48
|
+
label: ['all plugins label', { ns: REGISTRY_PLUGIN }],
|
|
49
|
+
icon: 'ph--squares-four--regular',
|
|
50
|
+
key: REGISTRY_KEY,
|
|
51
|
+
testId: 'pluginRegistry.all',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: `${REGISTRY_KEY}:installed`,
|
|
56
|
+
type: 'category',
|
|
57
|
+
data: `${REGISTRY_KEY}:installed`,
|
|
58
|
+
properties: {
|
|
59
|
+
label: ['installed plugins label', { ns: REGISTRY_PLUGIN }],
|
|
60
|
+
icon: 'ph--check--regular',
|
|
61
|
+
key: REGISTRY_KEY,
|
|
62
|
+
testId: 'pluginRegistry.installed',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: `${REGISTRY_KEY}:recommended`,
|
|
67
|
+
type: 'category',
|
|
68
|
+
data: `${REGISTRY_KEY}:recommended`,
|
|
69
|
+
properties: {
|
|
70
|
+
label: ['recommended plugins label', { ns: REGISTRY_PLUGIN }],
|
|
71
|
+
icon: 'ph--star--regular',
|
|
72
|
+
key: REGISTRY_KEY,
|
|
73
|
+
testId: 'pluginRegistry.recommended',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: `${REGISTRY_KEY}:experimental`,
|
|
78
|
+
type: 'category',
|
|
79
|
+
data: `${REGISTRY_KEY}:experimental`,
|
|
80
|
+
properties: {
|
|
81
|
+
label: ['experimental plugins label', { ns: REGISTRY_PLUGIN }],
|
|
82
|
+
icon: 'ph--flask--regular',
|
|
83
|
+
key: REGISTRY_KEY,
|
|
84
|
+
testId: 'pluginRegistry.experimental',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
}),
|
|
91
|
+
createExtension({
|
|
92
|
+
id: `${REGISTRY_PLUGIN}/actions`,
|
|
93
|
+
filter: (node): node is Node<null> => node.id === REGISTRY_ID,
|
|
94
|
+
actions: () => [
|
|
95
|
+
{
|
|
96
|
+
id: `${REGISTRY_PLUGIN}/load-by-url`,
|
|
97
|
+
data: async () => {},
|
|
98
|
+
properties: {
|
|
99
|
+
label: ['load by url label', { ns: REGISTRY_PLUGIN }],
|
|
100
|
+
icon: 'ph--cloud-arrow-down--regular',
|
|
101
|
+
disabled: true,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
}),
|
|
106
|
+
createExtension({
|
|
107
|
+
id: `${REGISTRY_PLUGIN}/plugins`,
|
|
108
|
+
resolver: ({ id }) => {
|
|
109
|
+
const manager = context.requestCapability(Capabilities.PluginManager);
|
|
110
|
+
const plugin = manager.plugins.find((plugin) => plugin.meta.id === id.replaceAll(':', '/'));
|
|
111
|
+
if (!plugin) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
id,
|
|
117
|
+
type: 'dxos.org/plugin',
|
|
118
|
+
data: plugin,
|
|
119
|
+
properties: {
|
|
120
|
+
label: plugin.meta.name ?? plugin.meta.id,
|
|
121
|
+
icon: plugin.meta.icon ?? 'ph--circle--regular',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { lazy } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
export const AppGraphBuilder = lazy(() => import('./app-graph-builder'));
|
|
8
|
+
export const IntentResolver = lazy(() => import('./intent-resolver'));
|
|
9
|
+
export const ReactSurface = lazy(() => import('./react-surface'));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
contributes,
|
|
7
|
+
SettingsAction,
|
|
8
|
+
createResolver,
|
|
9
|
+
LayoutAction,
|
|
10
|
+
Capabilities,
|
|
11
|
+
createIntent,
|
|
12
|
+
} from '@dxos/app-framework';
|
|
13
|
+
|
|
14
|
+
import { REGISTRY_ID } from '../meta';
|
|
15
|
+
|
|
16
|
+
export default () =>
|
|
17
|
+
contributes(
|
|
18
|
+
Capabilities.IntentResolver,
|
|
19
|
+
createResolver({
|
|
20
|
+
intent: SettingsAction.OpenPluginRegistry,
|
|
21
|
+
resolve: () => {
|
|
22
|
+
return {
|
|
23
|
+
intents: [createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: REGISTRY_ID })],
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Capabilities, contributes, createSurface, Plugin, usePluginManager } from '@dxos/app-framework';
|
|
8
|
+
import { StackItem } from '@dxos/react-ui-stack';
|
|
9
|
+
|
|
10
|
+
import { PluginDetail, RegistryContainer } from '../components';
|
|
11
|
+
import { REGISTRY_KEY, REGISTRY_PLUGIN } from '../meta';
|
|
12
|
+
|
|
13
|
+
export default () =>
|
|
14
|
+
contributes(Capabilities.ReactSurface, [
|
|
15
|
+
createSurface({
|
|
16
|
+
id: `${REGISTRY_PLUGIN}/all`,
|
|
17
|
+
role: 'article',
|
|
18
|
+
filter: (data): data is any => data.subject === `${REGISTRY_KEY}:all`,
|
|
19
|
+
component: () => {
|
|
20
|
+
const manager = usePluginManager();
|
|
21
|
+
const filtered = useMemo(() => manager.plugins.filter(({ meta }) => !manager.core.includes(meta.id)), []);
|
|
22
|
+
|
|
23
|
+
return <RegistryContainer id={`${REGISTRY_KEY}:all`} plugins={filtered} />;
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
createSurface({
|
|
27
|
+
id: `${REGISTRY_PLUGIN}/installed`,
|
|
28
|
+
role: 'article',
|
|
29
|
+
filter: (data): data is any => data.subject === `${REGISTRY_KEY}:installed`,
|
|
30
|
+
component: () => {
|
|
31
|
+
const manager = usePluginManager();
|
|
32
|
+
const filtered = useMemo(
|
|
33
|
+
() =>
|
|
34
|
+
manager.plugins
|
|
35
|
+
.filter(({ meta }) => !manager.core.includes(meta.id))
|
|
36
|
+
.filter(({ meta }) => manager.enabled.includes(meta.id)),
|
|
37
|
+
[],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return <RegistryContainer id={`${REGISTRY_KEY}:installed`} plugins={filtered} />;
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
createSurface({
|
|
44
|
+
id: `${REGISTRY_PLUGIN}/recommended`,
|
|
45
|
+
role: 'article',
|
|
46
|
+
filter: (data): data is any => data.subject === `${REGISTRY_KEY}:recommended`,
|
|
47
|
+
component: () => {
|
|
48
|
+
const manager = usePluginManager();
|
|
49
|
+
const filtered = useMemo(
|
|
50
|
+
() =>
|
|
51
|
+
manager.plugins
|
|
52
|
+
.filter(({ meta }) => !manager.core.includes(meta.id))
|
|
53
|
+
.filter(({ meta }) => !meta.tags?.includes('experimental')),
|
|
54
|
+
[],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return <RegistryContainer id={`${REGISTRY_KEY}:recommended`} plugins={filtered} />;
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
createSurface({
|
|
61
|
+
id: `${REGISTRY_PLUGIN}/experimental`,
|
|
62
|
+
role: 'article',
|
|
63
|
+
filter: (data): data is any => data.subject === `${REGISTRY_KEY}:experimental`,
|
|
64
|
+
component: () => {
|
|
65
|
+
const manager = usePluginManager();
|
|
66
|
+
const filtered = useMemo(() => manager.plugins.filter(({ meta }) => meta.tags?.includes('experimental')), []);
|
|
67
|
+
|
|
68
|
+
return <RegistryContainer id={`${REGISTRY_KEY}:experimental`} plugins={filtered} />;
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
createSurface({
|
|
72
|
+
id: `${REGISTRY_PLUGIN}/plugin-details`,
|
|
73
|
+
role: 'article',
|
|
74
|
+
filter: (data): data is { subject: Plugin } => data.subject instanceof Plugin,
|
|
75
|
+
component: ({ data: { subject } }) => {
|
|
76
|
+
const manager = usePluginManager();
|
|
77
|
+
const enabled = manager.enabled.includes(subject.meta.id);
|
|
78
|
+
const handleEnable = useCallback(
|
|
79
|
+
() => (enabled ? manager.disable(subject.meta.id) : manager.enable(subject.meta.id)),
|
|
80
|
+
[manager, subject.meta.id, enabled],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<StackItem.Content role='article' toolbar={false}>
|
|
85
|
+
<PluginDetail plugin={subject} enabled={enabled} onEnable={handleEnable} />
|
|
86
|
+
</StackItem.Content>
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
]);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
+
|
|
9
|
+
import { faker } from '@dxos/random';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
11
|
+
|
|
12
|
+
import { PluginDetail, type PluginDetailProps } from './PluginDetail';
|
|
13
|
+
import translations from '../translations';
|
|
14
|
+
|
|
15
|
+
const meta: Meta<PluginDetailProps> = {
|
|
16
|
+
title: 'plugins/plugin-registry/PluginDetail',
|
|
17
|
+
component: PluginDetail,
|
|
18
|
+
decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'justify-center' })],
|
|
19
|
+
parameters: {
|
|
20
|
+
translations,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<PluginDetailProps>;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
plugin: {
|
|
31
|
+
meta: {
|
|
32
|
+
id: 'example.com/plugin/test-plugin',
|
|
33
|
+
name: 'Test Plugin',
|
|
34
|
+
description: faker.lorem.paragraphs(2),
|
|
35
|
+
icon: 'ph--bug--regular',
|
|
36
|
+
homePage: 'https://example.com',
|
|
37
|
+
source: 'https://github.com/example/test-plugin',
|
|
38
|
+
screenshots: [
|
|
39
|
+
'https://media.gcflearnfree.org/content/55e073de7dd48174331f51b3_01_17_2014/getting_started_interactive2.png',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
modules: [],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
8
|
+
import { Icon, Input, Link, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { DeprecatedFormContainer } from '@dxos/react-ui-form';
|
|
10
|
+
|
|
11
|
+
import { REGISTRY_PLUGIN } from '../meta';
|
|
12
|
+
|
|
13
|
+
export type PluginDetailProps = {
|
|
14
|
+
plugin: Plugin;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
onEnable?: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const PluginDetail = ({ plugin, enabled, onEnable }: PluginDetailProps) => {
|
|
20
|
+
const { t } = useTranslation(REGISTRY_PLUGIN);
|
|
21
|
+
const { id, name, description, homePage, source, screenshots } = plugin.meta;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<DeprecatedFormContainer classNames='gap-6'>
|
|
25
|
+
<div role='none' className='flex justify-between items-center'>
|
|
26
|
+
<p>{id}</p>
|
|
27
|
+
<Input.Root>
|
|
28
|
+
<Input.Switch classNames='self-center' checked={enabled} onClick={onEnable} />
|
|
29
|
+
</Input.Root>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{screenshots && (
|
|
33
|
+
<div role='none'>
|
|
34
|
+
<h1 className='text-lg mb-2'>Preview</h1>
|
|
35
|
+
<img src={screenshots[0]} alt={name} className='aspect-video object-fit' />
|
|
36
|
+
</div>
|
|
37
|
+
)}
|
|
38
|
+
|
|
39
|
+
<div role='none'>
|
|
40
|
+
<p>{description}</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div role='none'>
|
|
44
|
+
<h1 className='text-lg mb-2'>Resources</h1>
|
|
45
|
+
<div className='flex gap-2'>
|
|
46
|
+
{homePage && (
|
|
47
|
+
<Link href={homePage} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
|
|
48
|
+
{t('home page label')}
|
|
49
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mli-1' />
|
|
50
|
+
</Link>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
{source && (
|
|
54
|
+
<Link href={source} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
|
|
55
|
+
{t('source label')}
|
|
56
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mli-1' />
|
|
57
|
+
</Link>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</DeprecatedFormContainer>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
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 { Icon, Input, Link, ListItem, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
10
|
+
|
|
11
|
+
import { REGISTRY_PLUGIN } from '../meta';
|
|
12
|
+
|
|
13
|
+
export type PluginItemProps = {
|
|
14
|
+
plugin: Plugin;
|
|
15
|
+
installed?: readonly string[];
|
|
16
|
+
enabled?: readonly string[];
|
|
17
|
+
onClick?: (id: string) => void;
|
|
18
|
+
onChange?: (id: string, enabled: boolean) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const PluginItem = ({ plugin, enabled = [], onClick, onChange }: PluginItemProps) => {
|
|
22
|
+
const { t } = useTranslation(REGISTRY_PLUGIN);
|
|
23
|
+
const { id, name, description, homePage, source, icon = 'ph--circle--regular' } = plugin.meta;
|
|
24
|
+
const isEnabled = enabled.includes(id);
|
|
25
|
+
const inputId = `${id}-input`;
|
|
26
|
+
const labelId = `${id}-label`;
|
|
27
|
+
const descriptionId = `${id}-description`;
|
|
28
|
+
const handleClick = useCallback(() => onClick?.(id), [id, onClick]);
|
|
29
|
+
const handleChange = useCallback(
|
|
30
|
+
(event: React.MouseEvent) => {
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
onChange?.(id, !isEnabled);
|
|
33
|
+
},
|
|
34
|
+
[id, isEnabled, onChange],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<ListItem.Root
|
|
39
|
+
key={id}
|
|
40
|
+
labelId={labelId}
|
|
41
|
+
data-testid={`pluginList.${id}`}
|
|
42
|
+
aria-describedby={descriptionId}
|
|
43
|
+
classNames='w-full h-full grid grid-cols-[48px_1fr_48px] grid-rows-[40px_1fr] p-1 rounded-md border border-separator'
|
|
44
|
+
>
|
|
45
|
+
<div className='flex grow justify-center items-center'>
|
|
46
|
+
<Icon icon={icon} size={6} onClick={handleClick} classNames='text-subdued cursor-pointer' />
|
|
47
|
+
</div>
|
|
48
|
+
<div className='flex grow items-center truncate cursor-pointer' onClick={handleClick}>
|
|
49
|
+
{name ?? id}
|
|
50
|
+
</div>
|
|
51
|
+
<div className='flex grow justify-center items-center'>
|
|
52
|
+
<Input.Root id={inputId}>
|
|
53
|
+
<Input.Switch classNames='self-center' checked={isEnabled} onClick={handleChange} />
|
|
54
|
+
</Input.Root>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div />
|
|
58
|
+
{(description || homePage || source) && (
|
|
59
|
+
<div id={descriptionId} className='col-span-2 flex flex-col w-full gap-2 pb-3 overflow-y-scroll'>
|
|
60
|
+
<div className='grow'>
|
|
61
|
+
<p className={mx(descriptionText, 'line-clamp-3 min-w-0 pie-4')}>{description}</p>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div className='flex gap-2 items-center'>
|
|
65
|
+
<Link
|
|
66
|
+
aria-describedby={descriptionId}
|
|
67
|
+
classNames='text-sm text-description cursor-pointer'
|
|
68
|
+
onClick={handleClick}
|
|
69
|
+
>
|
|
70
|
+
{t('details label')}
|
|
71
|
+
</Link>
|
|
72
|
+
|
|
73
|
+
{homePage && (
|
|
74
|
+
<Link
|
|
75
|
+
href={homePage}
|
|
76
|
+
target='_blank'
|
|
77
|
+
rel='noreferrer'
|
|
78
|
+
aria-describedby={descriptionId}
|
|
79
|
+
classNames='text-sm text-description'
|
|
80
|
+
onClick={(ev) => ev.stopPropagation()}
|
|
81
|
+
>
|
|
82
|
+
{t('home page label')}
|
|
83
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mli-1' />
|
|
84
|
+
</Link>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
{source && (
|
|
88
|
+
<Link
|
|
89
|
+
href={source}
|
|
90
|
+
target='_blank'
|
|
91
|
+
rel='noreferrer'
|
|
92
|
+
aria-describedby={descriptionId}
|
|
93
|
+
classNames='text-sm text-description'
|
|
94
|
+
onClick={(ev) => ev.stopPropagation()}
|
|
95
|
+
>
|
|
96
|
+
{t('source label')}
|
|
97
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mli-1' />
|
|
98
|
+
</Link>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</ListItem.Root>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
@@ -7,9 +7,11 @@ import '@dxos-theme';
|
|
|
7
7
|
import { type Meta } from '@storybook/react';
|
|
8
8
|
import React, { useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import { type
|
|
10
|
+
import { definePlugin, type Plugin } from '@dxos/app-framework';
|
|
11
11
|
import { faker } from '@dxos/random';
|
|
12
|
-
import {
|
|
12
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
13
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
14
|
+
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
13
15
|
|
|
14
16
|
import { PluginList } from './PluginList';
|
|
15
17
|
|
|
@@ -24,18 +26,22 @@ const icons = [
|
|
|
24
26
|
'ph--github-logo--regular',
|
|
25
27
|
];
|
|
26
28
|
|
|
27
|
-
const
|
|
28
|
-
const [plugins] = useState<
|
|
29
|
+
const Render = ({ classNames }: ThemedClassName<{}>) => {
|
|
30
|
+
const [plugins] = useState<Plugin[]>(
|
|
29
31
|
faker.helpers.multiple(
|
|
30
|
-
() =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
() =>
|
|
33
|
+
definePlugin(
|
|
34
|
+
{
|
|
35
|
+
id: `dxos.org/plugin/plugin-${faker.string.uuid()}`,
|
|
36
|
+
name: `${faker.commerce.productName()}`,
|
|
37
|
+
description: faker.lorem.sentences(Math.ceil(Math.random() * 3)),
|
|
38
|
+
tags: faker.datatype.boolean({ probability: 0.6 })
|
|
39
|
+
? [faker.helpers.arrayElement(['experimental', 'beta', 'alpha', 'stable', 'new', '新発売'])]
|
|
40
|
+
: undefined,
|
|
41
|
+
icon: faker.helpers.arrayElement(icons),
|
|
42
|
+
},
|
|
43
|
+
[],
|
|
44
|
+
),
|
|
39
45
|
{ count: 16 },
|
|
40
46
|
),
|
|
41
47
|
);
|
|
@@ -46,22 +52,25 @@ const DefaultStory = () => {
|
|
|
46
52
|
};
|
|
47
53
|
|
|
48
54
|
return (
|
|
49
|
-
<div className={'flex
|
|
55
|
+
<div className={mx('flex overflow-hidden', classNames)}>
|
|
50
56
|
<PluginList plugins={plugins} enabled={enabled} onChange={handleChange} />
|
|
51
57
|
</div>
|
|
52
58
|
);
|
|
53
59
|
};
|
|
54
60
|
|
|
55
|
-
export const Default = {};
|
|
56
|
-
|
|
57
61
|
const meta: Meta = {
|
|
58
62
|
title: 'plugins/plugin-registry/PluginList',
|
|
59
63
|
component: PluginList,
|
|
60
|
-
render:
|
|
61
|
-
decorators: [withTheme],
|
|
62
|
-
parameters: {
|
|
63
|
-
layout: 'centered',
|
|
64
|
-
},
|
|
64
|
+
render: Render,
|
|
65
|
+
decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'justify-center' })],
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
export default meta;
|
|
69
|
+
|
|
70
|
+
export const Default = {};
|
|
71
|
+
|
|
72
|
+
export const Column = {
|
|
73
|
+
args: {
|
|
74
|
+
classNames: 'w-[400px]',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -2,102 +2,25 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React
|
|
5
|
+
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
8
|
-
import {
|
|
9
|
-
import { descriptionText, fineBlockSize, mx } from '@dxos/react-ui-theme';
|
|
7
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
8
|
+
import { List } from '@dxos/react-ui';
|
|
10
9
|
|
|
11
|
-
import {
|
|
10
|
+
import { PluginItem, type PluginItemProps } from './PluginItem';
|
|
12
11
|
|
|
13
|
-
export type PluginListProps = {
|
|
14
|
-
plugins?:
|
|
15
|
-
loaded?: string[];
|
|
16
|
-
enabled?: string[];
|
|
17
|
-
onChange?: (id: string, enabled: boolean) => void;
|
|
18
|
-
onReload?: () => void;
|
|
12
|
+
export type PluginListProps = Omit<PluginItemProps, 'plugin'> & {
|
|
13
|
+
plugins?: readonly Plugin[];
|
|
19
14
|
};
|
|
20
15
|
|
|
21
|
-
export const PluginList = ({ plugins = [],
|
|
22
|
-
const { t } = useTranslation(REGISTRY_PLUGIN);
|
|
23
|
-
|
|
16
|
+
export const PluginList = ({ plugins = [], ...props }: PluginListProps) => {
|
|
24
17
|
return (
|
|
25
|
-
<List classNames='
|
|
26
|
-
{plugins.map((
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const labelId = `${id}-label`;
|
|
32
|
-
const descriptionId = `${id}-description`;
|
|
33
|
-
const handleChange = useCallback(() => onChange?.(id, !isEnabled), [id, isEnabled, onChange]);
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<Input.Root key={id} id={inputId}>
|
|
37
|
-
<ListItem.Root
|
|
38
|
-
labelId={labelId}
|
|
39
|
-
data-testid={`pluginList.${id}`}
|
|
40
|
-
aria-describedby={descriptionId}
|
|
41
|
-
classNames='flex gap-2 plb-2 pli-2 -mli-2 rounded'
|
|
42
|
-
>
|
|
43
|
-
<Icon icon={icon} size={6} classNames='shrink-0 mbs-1' />
|
|
44
|
-
<div role='none' className={mx(fineBlockSize, 'grow pbs-1 pl-1')}>
|
|
45
|
-
<label htmlFor={inputId} id={labelId} className='truncate'>
|
|
46
|
-
{name ?? id}
|
|
47
|
-
</label>
|
|
48
|
-
{(description || reloadRequired || homePage || source) && (
|
|
49
|
-
<>
|
|
50
|
-
<div id={descriptionId} className='space-b-1 pbs-1 pbe-1'>
|
|
51
|
-
<p className={descriptionText}>{description}</p>
|
|
52
|
-
{homePage && (
|
|
53
|
-
<Link
|
|
54
|
-
href={homePage}
|
|
55
|
-
target='_blank'
|
|
56
|
-
rel='noreferrer'
|
|
57
|
-
aria-describedby={descriptionId}
|
|
58
|
-
classNames='text-xs text-description'
|
|
59
|
-
>
|
|
60
|
-
{t('home page label')}
|
|
61
|
-
<Icon
|
|
62
|
-
icon='ph--arrow-square-out--bold'
|
|
63
|
-
size={3}
|
|
64
|
-
classNames='inline-block leading-none mli-1'
|
|
65
|
-
/>
|
|
66
|
-
</Link>
|
|
67
|
-
)}
|
|
68
|
-
{source && (
|
|
69
|
-
<Link
|
|
70
|
-
href={source}
|
|
71
|
-
target='_blank'
|
|
72
|
-
rel='noreferrer'
|
|
73
|
-
aria-describedby={descriptionId}
|
|
74
|
-
classNames='text-xs text-description'
|
|
75
|
-
>
|
|
76
|
-
{t('source label')}
|
|
77
|
-
<Icon
|
|
78
|
-
icon='ph--arrow-square-out--bold'
|
|
79
|
-
size={3}
|
|
80
|
-
classNames='inline-block leading-none mli-1'
|
|
81
|
-
/>
|
|
82
|
-
</Link>
|
|
83
|
-
)}
|
|
84
|
-
</div>
|
|
85
|
-
{reloadRequired && (
|
|
86
|
-
<Button variant='ghost' classNames='p-0 gap-2' onClick={onReload}>
|
|
87
|
-
<Icon size={4} icon='ph--arrow-clockwise--regular' />
|
|
88
|
-
<p className='text-sm font-medium'>{t('reload required message')}</p>
|
|
89
|
-
</Button>
|
|
90
|
-
)}
|
|
91
|
-
</>
|
|
92
|
-
)}
|
|
93
|
-
</div>
|
|
94
|
-
<div className='pbs-1'>
|
|
95
|
-
<Input.Switch classNames='self-center' checked={isEnabled} onClick={handleChange} />
|
|
96
|
-
</div>
|
|
97
|
-
</ListItem.Root>
|
|
98
|
-
</Input.Root>
|
|
99
|
-
);
|
|
100
|
-
})}
|
|
18
|
+
<List classNames='grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] auto-rows-[10rem] gap-3 p-3 overflow-y-auto scrollbar-thin'>
|
|
19
|
+
{plugins.map((plugin) => (
|
|
20
|
+
<div key={plugin.meta.id} className='flex'>
|
|
21
|
+
<PluginItem plugin={plugin} {...props} />
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
101
24
|
</List>
|
|
102
25
|
);
|
|
103
26
|
};
|