@dxos/plugin-registry 0.7.5-main.9d2a38b → 0.7.5-main.ff8607b

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.
Files changed (62) hide show
  1. package/dist/lib/browser/app-graph-builder-PPWF5KP5.mjs +164 -0
  2. package/dist/lib/browser/app-graph-builder-PPWF5KP5.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-CMDU7LPH.mjs → chunk-RGSIE33X.mjs} +5 -1
  4. package/dist/lib/browser/chunk-RGSIE33X.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +32 -12
  6. package/dist/lib/browser/index.mjs.map +3 -3
  7. package/dist/lib/browser/intent-resolver-2JQUQJI5.mjs +23 -0
  8. package/dist/lib/browser/intent-resolver-2JQUQJI5.mjs.map +7 -0
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/react-surface-NRETIUCH.mjs +295 -0
  11. package/dist/lib/browser/react-surface-NRETIUCH.mjs.map +7 -0
  12. package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
  13. package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
  14. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  15. package/dist/types/src/capabilities/index.d.ts +179 -2
  16. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  17. package/dist/types/src/capabilities/{settings.d.ts → intent-resolver.d.ts} +2 -2
  18. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  19. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  20. package/dist/types/src/components/PluginDetail.d.ts +9 -0
  21. package/dist/types/src/components/PluginDetail.d.ts.map +1 -0
  22. package/dist/types/src/components/PluginDetail.stories.d.ts +8 -0
  23. package/dist/types/src/components/PluginDetail.stories.d.ts.map +1 -0
  24. package/dist/types/src/components/PluginItem.d.ts +11 -0
  25. package/dist/types/src/components/PluginItem.d.ts.map +1 -0
  26. package/dist/types/src/components/PluginList.d.ts +5 -8
  27. package/dist/types/src/components/PluginList.d.ts.map +1 -1
  28. package/dist/types/src/components/PluginList.stories.d.ts +6 -1
  29. package/dist/types/src/components/PluginList.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/RegistryContainer.d.ts +7 -0
  31. package/dist/types/src/components/RegistryContainer.d.ts.map +1 -0
  32. package/dist/types/src/components/index.d.ts +2 -1
  33. package/dist/types/src/components/index.d.ts.map +1 -1
  34. package/dist/types/src/meta.d.ts +2 -0
  35. package/dist/types/src/meta.d.ts.map +1 -1
  36. package/dist/types/src/translations.d.ts +10 -0
  37. package/dist/types/src/translations.d.ts.map +1 -1
  38. package/package.json +16 -15
  39. package/src/RegistryPlugin.tsx +12 -7
  40. package/src/capabilities/app-graph-builder.ts +126 -0
  41. package/src/capabilities/index.ts +2 -1
  42. package/src/capabilities/intent-resolver.ts +27 -0
  43. package/src/capabilities/react-surface.tsx +76 -10
  44. package/src/components/PluginDetail.stories.tsx +45 -0
  45. package/src/components/PluginDetail.tsx +63 -0
  46. package/src/components/PluginItem.tsx +105 -0
  47. package/src/components/PluginList.stories.tsx +30 -21
  48. package/src/components/PluginList.tsx +13 -90
  49. package/src/components/RegistryContainer.tsx +51 -0
  50. package/src/components/index.ts +2 -1
  51. package/src/meta.ts +4 -0
  52. package/src/translations.ts +12 -2
  53. package/dist/lib/browser/chunk-CMDU7LPH.mjs.map +0 -7
  54. package/dist/lib/browser/react-surface-HKWZP7OW.mjs +0 -179
  55. package/dist/lib/browser/react-surface-HKWZP7OW.mjs.map +0 -7
  56. package/dist/lib/browser/settings-7LOHS2IV.mjs +0 -27
  57. package/dist/lib/browser/settings-7LOHS2IV.mjs.map +0 -7
  58. package/dist/types/src/capabilities/settings.d.ts.map +0 -1
  59. package/dist/types/src/components/PluginSettings.d.ts +0 -6
  60. package/dist/types/src/components/PluginSettings.d.ts.map +0 -1
  61. package/src/capabilities/settings.ts +0 -19
  62. package/src/components/PluginSettings.tsx +0 -112
@@ -2,23 +2,89 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useCallback, useMemo } from 'react';
6
6
 
7
- import { Capabilities, contributes, createSurface, useCapability } from '@dxos/app-framework';
7
+ import { Capabilities, contributes, createSurface, Plugin, usePluginManager } from '@dxos/app-framework';
8
+ import { StackItem } from '@dxos/react-ui-stack';
8
9
 
9
- import { PluginSettings } from '../components';
10
- import { meta, REGISTRY_PLUGIN } from '../meta';
11
- import { type RegistrySettings } from '../types';
10
+ import { PluginDetail, RegistryContainer } from '../components';
11
+ import { REGISTRY_KEY, REGISTRY_PLUGIN } from '../meta';
12
12
 
13
13
  export default () =>
14
14
  contributes(Capabilities.ReactSurface, [
15
15
  createSurface({
16
- id: meta.id,
17
- role: 'settings',
18
- filter: (data): data is any => data.subject === REGISTRY_PLUGIN,
16
+ id: `${REGISTRY_PLUGIN}/all`,
17
+ role: 'article',
18
+ filter: (data): data is any => data.subject === `${REGISTRY_KEY}:all`,
19
19
  component: () => {
20
- const settings = useCapability(Capabilities.SettingsStore).getStore<RegistrySettings>(REGISTRY_PLUGIN)!.value;
21
- return <PluginSettings settings={settings} />;
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
+ );
22
88
  },
23
89
  }),
24
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 PluginMeta } from '@dxos/app-framework';
10
+ import { definePlugin, type Plugin } from '@dxos/app-framework';
11
11
  import { faker } from '@dxos/random';
12
- import { withTheme } from '@dxos/storybook-utils';
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 DefaultStory = () => {
28
- const [plugins] = useState<PluginMeta[]>(
29
+ const Render = ({ classNames }: ThemedClassName<{}>) => {
30
+ const [plugins] = useState<Plugin[]>(
29
31
  faker.helpers.multiple(
30
- () => ({
31
- id: `dxos.org/plugin/plugin-${faker.string.uuid()}`,
32
- name: `${faker.lorem.sentence(3)}`,
33
- description: faker.datatype.boolean() ? `${faker.lorem.sentences()}` : undefined,
34
- tags: faker.datatype.boolean({ probability: 0.6 })
35
- ? [faker.helpers.arrayElement(['experimental', 'beta', 'alpha', 'stable', 'new', '新発売'])]
36
- : undefined,
37
- icon: faker.helpers.arrayElement(icons),
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 w-[400px] overflow-hidden'}>
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: DefaultStory,
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, { useCallback } from 'react';
5
+ import React from 'react';
6
6
 
7
- import { type PluginMeta } from '@dxos/app-framework';
8
- import { Button, Icon, Input, Link, List, ListItem, useTranslation } from '@dxos/react-ui';
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 { REGISTRY_PLUGIN } from '../meta';
10
+ import { PluginItem, type PluginItemProps } from './PluginItem';
12
11
 
13
- export type PluginListProps = {
14
- plugins?: readonly PluginMeta[];
15
- installed?: readonly string[];
16
- enabled?: readonly 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 = [], installed = [], enabled = [], onChange, onReload }: PluginListProps) => {
22
- const { t } = useTranslation(REGISTRY_PLUGIN);
23
-
16
+ export const PluginList = ({ plugins = [], ...props }: PluginListProps) => {
24
17
  return (
25
- <List classNames='mb-4 select-none'>
26
- {plugins.map(({ id, name, description, homePage, source, icon = 'ph--circle--regular' }) => {
27
- const isEnabled = enabled.includes(id);
28
- const isInstalled = installed.includes(id);
29
- const reloadRequired = isEnabled !== isInstalled;
30
- const inputId = `${id}-input`;
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
  };
@@ -0,0 +1,51 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { useMemo } from 'react';
6
+
7
+ import { createIntent, LayoutAction, type Plugin, useIntentDispatcher, usePluginManager } from '@dxos/app-framework';
8
+ import { ObservabilityAction } from '@dxos/plugin-observability/types';
9
+ import { StackItem } from '@dxos/react-ui-stack';
10
+
11
+ import { PluginList } from './PluginList';
12
+
13
+ const sortByPluginMeta = ({ meta: { name: a = '' } }: Plugin, { meta: { name: b = '' } }: Plugin) => a.localeCompare(b);
14
+
15
+ export const RegistryContainer = ({ id, plugins: _plugins }: { id: string; plugins: Plugin[] }) => {
16
+ const manager = usePluginManager();
17
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
18
+ const plugins = useMemo(() => _plugins.sort(sortByPluginMeta), [_plugins]);
19
+
20
+ // TODO(wittjosiah): Factor out to an intent?
21
+ const handleChange = async (id: string, enabled: boolean) => {
22
+ if (enabled) {
23
+ await manager.enable(id);
24
+ } else {
25
+ await manager.disable(id);
26
+ }
27
+
28
+ await dispatch(
29
+ createIntent(ObservabilityAction.SendEvent, {
30
+ name: 'plugins.toggle',
31
+ properties: { plugin: id, enabled },
32
+ }),
33
+ );
34
+ };
35
+
36
+ const handleClick = (pluginId: string) =>
37
+ dispatch(
38
+ createIntent(LayoutAction.Open, {
39
+ part: 'main',
40
+ // TODO(wittjosiah): `/` currently is not supported in ids.
41
+ subject: [pluginId.replaceAll('/', ':')],
42
+ options: { pivotId: id, positioning: 'end' },
43
+ }),
44
+ );
45
+
46
+ return (
47
+ <StackItem.Content toolbar={false} role='article' classNames='overflow-x-hidden overflow-y-auto'>
48
+ <PluginList plugins={plugins} enabled={manager.enabled} onClick={handleClick} onChange={handleChange} />
49
+ </StackItem.Content>
50
+ );
51
+ };
@@ -2,5 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './PluginDetail';
5
6
  export * from './PluginList';
6
- export * from './PluginSettings';
7
+ export * from './RegistryContainer';
package/src/meta.ts CHANGED
@@ -6,6 +6,10 @@ import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
7
  export const REGISTRY_PLUGIN = 'dxos.org/plugin/registry';
8
8
 
9
+ // TODO(wittjosiah): Deck does not currently support `/` in ids.
10
+ export const REGISTRY_ID = 'dxos:plugin-registry';
11
+ export const REGISTRY_KEY = 'plugin-registry';
12
+
9
13
  export const meta = {
10
14
  id: REGISTRY_PLUGIN,
11
15
  name: 'Plugins',
@@ -11,11 +11,21 @@ export default [
11
11
  'plugin name': 'Registry',
12
12
  'reload required message': 'Reload required.',
13
13
  'settings show experimental label': 'Show experimental plugins',
14
- 'settings section installed label': 'Installed',
14
+ 'settings section installed label': 'Enabled',
15
15
  'settings section recommended label': 'Recommended',
16
16
  'settings section experimental label': 'Experimental',
17
- 'home page label': 'Home page',
17
+ 'details label': 'Details',
18
+ 'home page label': 'Website',
18
19
  'source label': 'Source',
20
+ 'plugin registry label': 'Plugin Registry',
21
+ 'all plugins label': 'All',
22
+ 'installed plugins label': 'Enabled',
23
+ 'recommended plugins label': 'Recommended',
24
+ 'experimental plugins label': 'Experimental',
25
+ 'community plugins label': 'Community',
26
+ 'load by url label': 'Load by URL (soon)',
27
+ 'coming soon label': 'Coming soon!',
28
+ 'open plugin registry label': 'Open plugin registry',
19
29
  },
20
30
  },
21
31
  },
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/meta.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { type PluginMeta } from '@dxos/app-framework';\n\nexport const REGISTRY_PLUGIN = 'dxos.org/plugin/registry';\n\nexport const meta = {\n id: REGISTRY_PLUGIN,\n name: 'Plugins',\n} satisfies PluginMeta;\n"],
5
- "mappings": ";AAMO,IAAMA,kBAAkB;AAExB,IAAMC,OAAO;EAClBC,IAAIF;EACJG,MAAM;AACR;",
6
- "names": ["REGISTRY_PLUGIN", "meta", "id", "name"]
7
- }