@dxos/plugin-registry 0.8.4-main.72ec0f3 → 0.8.4-main.8360d9e660
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/PluginArticle-N2ITBUGD.mjs +29 -0
- package/dist/lib/browser/PluginArticle-N2ITBUGD.mjs.map +7 -0
- package/dist/lib/browser/PluginRegistry-DKO4UEYH.mjs +78 -0
- package/dist/lib/browser/PluginRegistry-DKO4UEYH.mjs.map +7 -0
- package/dist/lib/browser/app-graph-builder-AHS5BI4Y.mjs +167 -0
- package/dist/lib/browser/app-graph-builder-AHS5BI4Y.mjs.map +7 -0
- package/dist/lib/browser/chunk-2YWVCU2W.mjs +191 -0
- package/dist/lib/browser/chunk-2YWVCU2W.mjs.map +7 -0
- package/dist/lib/browser/{chunk-XPEFEID2.mjs → chunk-KMUDYITM.mjs} +4 -2
- package/dist/lib/browser/chunk-KMUDYITM.mjs.map +7 -0
- package/dist/lib/browser/cli/index.mjs +222 -0
- package/dist/lib/browser/cli/index.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +25 -29
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/operation-resolver-PK4B2BZQ.mjs +25 -0
- package/dist/lib/browser/operation-resolver-PK4B2BZQ.mjs.map +7 -0
- package/dist/lib/browser/react-surface-ZBP7YXKO.mjs +85 -0
- package/dist/lib/browser/react-surface-ZBP7YXKO.mjs.map +7 -0
- package/dist/lib/node-esm/PluginArticle-3NEYDKN5.mjs +30 -0
- package/dist/lib/node-esm/PluginArticle-3NEYDKN5.mjs.map +7 -0
- package/dist/lib/node-esm/PluginRegistry-BYKWC732.mjs +79 -0
- package/dist/lib/node-esm/PluginRegistry-BYKWC732.mjs.map +7 -0
- package/dist/lib/node-esm/app-graph-builder-DBDL5WWF.mjs +168 -0
- package/dist/lib/node-esm/app-graph-builder-DBDL5WWF.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-BOCFM775.mjs +192 -0
- package/dist/lib/node-esm/chunk-BOCFM775.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ENSNUFQK.mjs +23 -0
- package/dist/lib/node-esm/chunk-ENSNUFQK.mjs.map +7 -0
- package/dist/lib/node-esm/cli/index.mjs +223 -0
- package/dist/lib/node-esm/cli/index.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +66 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/operation-resolver-CCHN4NBP.mjs +26 -0
- package/dist/lib/node-esm/operation-resolver-CCHN4NBP.mjs.map +7 -0
- package/dist/lib/node-esm/react-surface-FJHMLGZF.mjs +86 -0
- package/dist/lib/node-esm/react-surface-FJHMLGZF.mjs.map +7 -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/app-graph-builder.d.ts +6 -0
- package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +3 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
- 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 +9 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -0
- package/dist/types/src/components/{PluginDetail.stories.d.ts → PluginDetail/PluginDetail.stories.d.ts} +2 -1
- 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.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/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 +8 -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 +4 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +4 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -32
- package/src/RegistryPlugin.tsx +10 -24
- package/src/capabilities/app-graph-builder/app-graph-builder.ts +137 -0
- package/src/capabilities/app-graph-builder/index.ts +7 -0
- package/src/capabilities/index.ts +3 -5
- package/src/capabilities/operation-resolver/index.ts +7 -0
- package/src/capabilities/operation-resolver/operation-resolver.ts +24 -0
- package/src/capabilities/react-surface/index.ts +7 -0
- package/src/capabilities/react-surface/react-surface.tsx +88 -0
- 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 +81 -0
- package/src/components/PluginDetail/index.ts +5 -0
- package/src/components/{PluginItem.tsx → PluginList/PluginItem.tsx} +18 -14
- package/src/components/PluginList/PluginList.stories.tsx +84 -0
- package/src/components/{PluginList.tsx → PluginList/PluginList.tsx} +2 -2
- package/src/components/PluginList/index.ts +5 -0
- package/src/components/index.ts +0 -1
- package/src/containers/PluginArticle/PluginArticle.tsx +28 -0
- package/src/containers/PluginArticle/index.ts +7 -0
- package/src/containers/PluginRegistry/PluginRegistry.tsx +86 -0
- package/src/containers/PluginRegistry/index.ts +7 -0
- package/src/containers/index.ts +8 -0
- package/src/meta.ts +6 -6
- package/dist/lib/browser/app-graph-builder-6CNBP3A4.mjs +0 -162
- package/dist/lib/browser/app-graph-builder-6CNBP3A4.mjs.map +0 -7
- package/dist/lib/browser/chunk-XPEFEID2.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-LKNUYBTN.mjs +0 -23
- package/dist/lib/browser/intent-resolver-LKNUYBTN.mjs.map +0 -7
- package/dist/lib/browser/react-surface-IGCXOVE2.mjs +0 -359
- package/dist/lib/browser/react-surface-IGCXOVE2.mjs.map +0 -7
- package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -4
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
- 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/capabilities/react-surface.d.ts +0 -4
- package/dist/types/src/capabilities/react-surface.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.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.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/app-graph-builder.ts +0 -156
- package/src/capabilities/intent-resolver.ts +0 -27
- package/src/capabilities/react-surface.tsx +0 -86
- package/src/components/PluginDetail.stories.tsx +0 -45
- package/src/components/PluginDetail.tsx +0 -82
- package/src/components/PluginList.stories.tsx +0 -80
- package/src/components/RegistryContainer.tsx +0 -76
- /package/dist/types/src/components/{PluginList.stories.d.ts → PluginList/PluginList.stories.d.ts} +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as FileSystem from '@effect/platform/FileSystem';
|
|
6
|
+
import * as Path from '@effect/platform/Path';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
|
+
import * as Yaml from 'yaml';
|
|
10
|
+
|
|
11
|
+
import { DX_CONFIG } from '@dxos/client-protocol';
|
|
12
|
+
|
|
13
|
+
// TODO(wittjosiah): Factor out to app-framework?
|
|
14
|
+
|
|
15
|
+
const PluginsSchema = Schema.Array(Schema.String);
|
|
16
|
+
|
|
17
|
+
/** CLI-only: path to plugins/<profile>.yml (sibling to profile/). */
|
|
18
|
+
const getPluginsConfigPath = (profile: string) => `${DX_CONFIG}/plugins/${profile}.yml`;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load enabled plugins from plugins/<profile>.yml (sibling to profile/).
|
|
22
|
+
* Returns the list of enabled plugin IDs, or an empty array if the file doesn't exist.
|
|
23
|
+
*/
|
|
24
|
+
export const loadEnabledPlugins = Effect.fn(function* ({ profile }: { profile: string }) {
|
|
25
|
+
const fs = yield* FileSystem.FileSystem;
|
|
26
|
+
const pluginsPath = getPluginsConfigPath(profile);
|
|
27
|
+
|
|
28
|
+
const content = yield* fs
|
|
29
|
+
.readFileString(pluginsPath)
|
|
30
|
+
.pipe(Effect.catchTag('SystemError', () => Effect.succeed('[]')));
|
|
31
|
+
|
|
32
|
+
const raw = Yaml.parse(content);
|
|
33
|
+
const parsed = yield* Schema.decodeUnknown(PluginsSchema)(raw ?? []).pipe(
|
|
34
|
+
Effect.catchAll(() => Effect.succeed([] as string[])),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return parsed;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Save enabled plugins to plugins/<profile>.yml.
|
|
42
|
+
*/
|
|
43
|
+
export const saveEnabledPlugins = Effect.fn(function* ({ profile, enabled }: { profile: string; enabled: string[] }) {
|
|
44
|
+
const fs = yield* FileSystem.FileSystem;
|
|
45
|
+
const path = yield* Path.Path;
|
|
46
|
+
const pluginsPath = getPluginsConfigPath(profile);
|
|
47
|
+
yield* fs.makeDirectory(path.dirname(pluginsPath), { recursive: true });
|
|
48
|
+
const encoded = Yaml.stringify(enabled);
|
|
49
|
+
yield* fs.writeFileString(pluginsPath, encoded);
|
|
50
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
|
|
7
|
+
import { Plugin } from '@dxos/app-framework';
|
|
8
|
+
import { faker } from '@dxos/random';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
|
|
11
|
+
import { translations } from '../../translations';
|
|
12
|
+
|
|
13
|
+
import { PluginDetail } from './PluginDetail';
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: 'plugins/plugin-registry/components/PluginDetail',
|
|
17
|
+
component: PluginDetail,
|
|
18
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
19
|
+
parameters: {
|
|
20
|
+
layout: 'fullscreen',
|
|
21
|
+
translations,
|
|
22
|
+
},
|
|
23
|
+
} satisfies Meta<typeof PluginDetail>;
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
plugin: Plugin.define({
|
|
32
|
+
id: 'example.com/plugin/test-plugin',
|
|
33
|
+
name: 'Test Plugin',
|
|
34
|
+
description: faker.lorem.paragraphs(2),
|
|
35
|
+
icon: 'ph--bug--regular',
|
|
36
|
+
iconHue: 'sky',
|
|
37
|
+
homePage: 'https://example.com',
|
|
38
|
+
source: 'https://github.com/example/test-plugin',
|
|
39
|
+
screenshots: [
|
|
40
|
+
'https://media.gcflearnfree.org/content/55e073de7dd48174331f51b3_01_17_2014/getting_started_interactive2.png',
|
|
41
|
+
],
|
|
42
|
+
}).pipe(Plugin.make)(),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { ScrollArea } from '@dxos/react-ui';
|
|
10
|
+
import { getStyles, mx } from '@dxos/ui-theme';
|
|
11
|
+
|
|
12
|
+
import { meta } from '../../meta';
|
|
13
|
+
|
|
14
|
+
export type PluginDetailProps = {
|
|
15
|
+
plugin: Plugin.Plugin;
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
onEnabledChange?: (enabled: boolean) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const PluginDetail = ({ plugin, enabled, onEnabledChange }: PluginDetailProps) => {
|
|
21
|
+
const { t } = useTranslation(meta.id);
|
|
22
|
+
const {
|
|
23
|
+
id,
|
|
24
|
+
name,
|
|
25
|
+
description,
|
|
26
|
+
homePage,
|
|
27
|
+
source,
|
|
28
|
+
screenshots,
|
|
29
|
+
icon = 'ph--circle--regular',
|
|
30
|
+
iconHue = 'neutral',
|
|
31
|
+
} = plugin.meta;
|
|
32
|
+
const styles = getStyles(iconHue);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ScrollArea.Root orientation='vertical'>
|
|
36
|
+
<ScrollArea.Viewport>
|
|
37
|
+
<div role='none' className='dx-document grid grid-cols-[min-content_1fr] gap-4 p-4'>
|
|
38
|
+
<div role='none'>
|
|
39
|
+
<Icon classNames={mx('p-1 rounded-md', styles.fill, styles.surfaceText)} icon={icon} size={14} />
|
|
40
|
+
</div>
|
|
41
|
+
<div role='none' className='flex flex-col gap-6'>
|
|
42
|
+
<div role='none' className='grid grid-cols-[1fr_min-content] gap-x-3 w-full pt-1'>
|
|
43
|
+
<h2 className='text-xl'>{name}</h2>
|
|
44
|
+
<Input.Root>
|
|
45
|
+
<Input.Switch classNames='self-center' checked={enabled} onCheckedChange={onEnabledChange} />
|
|
46
|
+
</Input.Root>
|
|
47
|
+
<p className='pt-0.5 text-sm text-description'>{id}</p>
|
|
48
|
+
</div>
|
|
49
|
+
<div role='none'>
|
|
50
|
+
<p className='text-description'>{description}</p>
|
|
51
|
+
</div>
|
|
52
|
+
{screenshots && screenshots.length > 0 && (
|
|
53
|
+
<div role='none' className='flex flex-col gap-2'>
|
|
54
|
+
<h2>Preview</h2>
|
|
55
|
+
<img src={screenshots[0]} alt={name} className='aspect-video object-fit' />
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
<div role='none' className='flex flex-col gap-2'>
|
|
59
|
+
<h2>Resources</h2>
|
|
60
|
+
<div className='flex gap-2'>
|
|
61
|
+
{homePage && (
|
|
62
|
+
<Link href={homePage} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
|
|
63
|
+
{t('home page label')}
|
|
64
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mx-1' />
|
|
65
|
+
</Link>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{source && (
|
|
69
|
+
<Link href={source} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
|
|
70
|
+
{t('source label')}
|
|
71
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mx-1' />
|
|
72
|
+
</Link>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</ScrollArea.Viewport>
|
|
79
|
+
</ScrollArea.Root>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -6,14 +6,14 @@ import React, { type MouseEvent, useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { type Plugin } from '@dxos/app-framework';
|
|
8
8
|
import { type ChromaticPalette, Icon, IconButton, Input, Link, ListItem, Tag, useTranslation } from '@dxos/react-ui';
|
|
9
|
-
import {
|
|
10
|
-
import { getStyles } from '@dxos/
|
|
9
|
+
import { mx } from '@dxos/ui-theme';
|
|
10
|
+
import { getStyles } from '@dxos/ui-theme';
|
|
11
11
|
|
|
12
|
-
import { meta } from '
|
|
13
|
-
import { type RegistryTagType } from '
|
|
12
|
+
import { meta } from '../../meta';
|
|
13
|
+
import { type RegistryTagType } from '../../types';
|
|
14
14
|
|
|
15
15
|
export type PluginItemProps = {
|
|
16
|
-
plugin: Plugin;
|
|
16
|
+
plugin: Plugin.Plugin;
|
|
17
17
|
installed?: readonly string[];
|
|
18
18
|
enabled?: readonly string[];
|
|
19
19
|
onClick?: (id: string) => void;
|
|
@@ -27,7 +27,7 @@ export const PluginItem = ({
|
|
|
27
27
|
enabled = [],
|
|
28
28
|
onClick,
|
|
29
29
|
onChange,
|
|
30
|
-
hasSettings:
|
|
30
|
+
hasSettings: hasSettingsProp,
|
|
31
31
|
onSettings,
|
|
32
32
|
}: PluginItemProps) => {
|
|
33
33
|
const { t } = useTranslation(meta.id);
|
|
@@ -46,7 +46,7 @@ export const PluginItem = ({
|
|
|
46
46
|
[id, isEnabled, onChange],
|
|
47
47
|
);
|
|
48
48
|
|
|
49
|
-
const hasSettings =
|
|
49
|
+
const hasSettings = hasSettingsProp?.(id) ?? false;
|
|
50
50
|
const handleSettings = useCallback(() => onSettings?.(id), [id, onSettings]);
|
|
51
51
|
const styles = getStyles(iconHue);
|
|
52
52
|
const gridCols = 'grid grid-cols-[5rem_1fr]';
|
|
@@ -58,11 +58,15 @@ export const PluginItem = ({
|
|
|
58
58
|
labelId={labelId}
|
|
59
59
|
data-testid={`pluginList.${id}`}
|
|
60
60
|
aria-describedby={descriptionId}
|
|
61
|
-
classNames={mx(gridCols, '
|
|
61
|
+
classNames={mx(gridCols, 'h-[12rem] w-full gap-3 pe-2 bg-modal-surface rounded-md overflow-hidden')}
|
|
62
62
|
>
|
|
63
|
-
<div className={mx(gridRows, 'justify-center rounded-l-md', styles.
|
|
64
|
-
<
|
|
65
|
-
|
|
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
|
+
/>
|
|
66
70
|
</div>
|
|
67
71
|
|
|
68
72
|
<div className={mx(gridRows)}>
|
|
@@ -71,10 +75,10 @@ export const PluginItem = ({
|
|
|
71
75
|
</div>
|
|
72
76
|
|
|
73
77
|
<div>
|
|
74
|
-
<p className={mx(
|
|
78
|
+
<p className={mx('text-description', 'line-clamp-4 min-w-0')}>{description}</p>
|
|
75
79
|
</div>
|
|
76
80
|
|
|
77
|
-
<div className='flex -
|
|
81
|
+
<div className='flex -ms-0.5 overflow-x-auto scrollbar-none'>
|
|
78
82
|
{tags?.map((tag) => (
|
|
79
83
|
<Tag key={tag} palette={tagColors[tag as RegistryTagType]} classNames='text-xs uppercase font-thin'>
|
|
80
84
|
{tag}
|
|
@@ -99,7 +103,7 @@ export const PluginItem = ({
|
|
|
99
103
|
</Link>
|
|
100
104
|
|
|
101
105
|
<div className='grow' />
|
|
102
|
-
<div className='
|
|
106
|
+
<div className='pe-1'>
|
|
103
107
|
<Input.Root id={inputId}>
|
|
104
108
|
<Input.Switch classNames='self-center' checked={isEnabled} onClick={handleChange} />
|
|
105
109
|
</Input.Root>
|
|
@@ -0,0 +1,84 @@
|
|
|
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' })],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const FullScreen: Story = {
|
|
80
|
+
decorators: [withTheme(), withLayout({ scroll: true })],
|
|
81
|
+
parameters: {
|
|
82
|
+
layout: 'fullscreen',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
@@ -10,12 +10,12 @@ 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(18rem,1fr))] gap-4 p-4'>
|
|
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
20
|
<PluginItem key={plugin.meta.id} plugin={plugin} {...props} />
|
|
21
21
|
))}
|
package/src/components/index.ts
CHANGED
|
@@ -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,86 @@
|
|
|
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/types';
|
|
14
|
+
import { ScrollArea } from '@dxos/react-ui';
|
|
15
|
+
|
|
16
|
+
import { PluginList } from '../../components';
|
|
17
|
+
import { getPluginPath } from '../../meta';
|
|
18
|
+
|
|
19
|
+
const sortByPluginMeta = ({ meta: { name: a = '' } }: Plugin.Plugin, { meta: { name: b = '' } }: Plugin.Plugin) =>
|
|
20
|
+
a.localeCompare(b);
|
|
21
|
+
|
|
22
|
+
export type PluginRegistryProps = {
|
|
23
|
+
id: string;
|
|
24
|
+
plugins: Plugin.Plugin[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const PluginRegistry = ({ id, plugins: pluginsProp }: PluginRegistryProps) => {
|
|
28
|
+
const manager = usePluginManager();
|
|
29
|
+
const { invoke, invokePromise } = useOperationInvoker();
|
|
30
|
+
const plugins = useMemo(() => pluginsProp.sort(sortByPluginMeta), [pluginsProp]);
|
|
31
|
+
const enabled = useAtomValue(manager.enabled);
|
|
32
|
+
const allSettings = useCapabilities(AppCapabilities.Settings);
|
|
33
|
+
|
|
34
|
+
// TODO(wittjosiah): Factor out to an intent?
|
|
35
|
+
const handleChange = useCallback(
|
|
36
|
+
(id: string, enabled: boolean) =>
|
|
37
|
+
Effect.gen(function* () {
|
|
38
|
+
if (enabled) {
|
|
39
|
+
yield* manager.enable(id);
|
|
40
|
+
} else {
|
|
41
|
+
yield* manager.disable(id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
yield* invoke(ObservabilityOperation.SendEvent, {
|
|
45
|
+
name: 'plugins.toggle',
|
|
46
|
+
properties: {
|
|
47
|
+
plugin: id,
|
|
48
|
+
enabled,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}).pipe(runAndForwardErrors),
|
|
52
|
+
[invoke, manager],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const handleClick = useCallback(
|
|
56
|
+
(pluginId: string) =>
|
|
57
|
+
invokePromise(LayoutOperation.Open, {
|
|
58
|
+
subject: [getPluginPath(pluginId)],
|
|
59
|
+
pivotId: id,
|
|
60
|
+
positioning: 'end',
|
|
61
|
+
}),
|
|
62
|
+
[invokePromise, id],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const hasSettings = useCallback((pluginId: string) => allSettings.some((s) => s.prefix === pluginId), [allSettings]);
|
|
66
|
+
|
|
67
|
+
const handleSettings = useCallback(
|
|
68
|
+
(pluginId: string) => invokePromise(SettingsOperation.Open, { plugin: pluginId }),
|
|
69
|
+
[invokePromise],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<ScrollArea.Root orientation='vertical'>
|
|
74
|
+
<ScrollArea.Viewport>
|
|
75
|
+
<PluginList
|
|
76
|
+
plugins={plugins}
|
|
77
|
+
enabled={enabled}
|
|
78
|
+
onClick={handleClick}
|
|
79
|
+
onChange={handleChange}
|
|
80
|
+
hasSettings={hasSettings}
|
|
81
|
+
onSettings={handleSettings}
|
|
82
|
+
/>
|
|
83
|
+
</ScrollArea.Viewport>
|
|
84
|
+
</ScrollArea.Root>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type ComponentType, lazy } from 'react';
|
|
6
|
+
|
|
7
|
+
export const PluginArticle: ComponentType<any> = lazy(() => import('./PluginArticle'));
|
|
8
|
+
export const PluginRegistry: ComponentType<any> = lazy(() => import('./PluginRegistry'));
|
package/src/meta.ts
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type Plugin } from '@dxos/app-framework';
|
|
6
6
|
import { trim } from '@dxos/util';
|
|
7
7
|
|
|
8
|
-
// TODO(wittjosiah): Deck does not currently support `/` in ids.
|
|
9
|
-
// TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
|
|
10
|
-
// Ideally this should be worked into the data model in a generic way.
|
|
11
8
|
export const REGISTRY_ID = '!dxos:plugin-registry';
|
|
12
9
|
export const REGISTRY_KEY = 'plugin-registry';
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
/** Qualified graph path to a specific plugin node. */
|
|
12
|
+
export const getPluginPath = (pluginId: string): string => `root/${REGISTRY_ID}/${pluginId}`;
|
|
13
|
+
|
|
14
|
+
export const meta: Plugin.Meta = {
|
|
15
|
+
id: 'org.dxos.plugin.registry',
|
|
16
16
|
name: 'Plugins',
|
|
17
17
|
description: trim`
|
|
18
18
|
Plugin management system for discovering, installing, and configuring workspace extensions.
|