@dxos/plugin-registry 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/neutral/RegistryPlugin.mjs +27 -0
- package/dist/lib/neutral/RegistryPlugin.mjs.map +7 -0
- package/dist/lib/neutral/RegistryPlugin.node.mjs +153 -0
- package/dist/lib/neutral/RegistryPlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs +33 -0
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YV7A523U.mjs +29 -0
- package/dist/lib/neutral/chunk-YV7A523U.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +21 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs +15 -0
- package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +18 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +61 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/types/src/RegistryPlugin.d.ts +3 -1
- package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
- package/dist/types/src/RegistryPlugin.node.d.ts +4 -0
- package/dist/types/src/RegistryPlugin.node.d.ts.map +1 -0
- package/dist/types/src/RegistryPlugin.test.d.ts +2 -0
- package/dist/types/src/RegistryPlugin.test.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts +4 -2
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/dev-plugin-loader.d.ts +11 -0
- package/dist/types/src/capabilities/dev-plugin-loader.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +15 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +3 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/settings.d.ts +14 -0
- package/dist/types/src/capabilities/settings.d.ts.map +1 -0
- package/dist/types/src/commands/disable.d.ts +11 -0
- package/dist/types/src/commands/disable.d.ts.map +1 -0
- package/dist/types/src/commands/enable.d.ts +11 -0
- package/dist/types/src/commands/enable.d.ts.map +1 -0
- package/dist/types/src/commands/index.d.ts +3 -0
- package/dist/types/src/commands/index.d.ts.map +1 -0
- package/dist/types/src/commands/list.d.ts +7 -0
- package/dist/types/src/commands/list.d.ts.map +1 -0
- package/dist/types/src/commands/util.d.ts +11 -0
- package/dist/types/src/commands/util.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +46 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +68 -0
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -0
- package/dist/types/src/components/PluginDetail/index.d.ts +2 -0
- package/dist/types/src/components/PluginDetail/index.d.ts.map +1 -0
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
- package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
- package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginItem.d.ts +40 -0
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginList.d.ts +23 -0
- package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts +67 -0
- package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -0
- package/dist/types/src/components/PluginList/index.d.ts +2 -0
- package/dist/types/src/components/PluginList/index.d.ts.map +1 -0
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
- package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
- package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
- package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +4 -0
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
- package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
- package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts +7 -0
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -0
- package/dist/types/src/containers/PluginArticle/index.d.ts +2 -0
- package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
- package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
- package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +8 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/use-auto-tags.d.ts +20 -0
- package/dist/types/src/hooks/use-auto-tags.d.ts.map +1 -0
- package/dist/types/src/hooks/use-registry-plugins.d.ts +15 -0
- package/dist/types/src/hooks/use-registry-plugins.d.ts.map +1 -0
- package/dist/types/src/hooks/use-update-available.d.ts +18 -0
- package/dist/types/src/hooks/use-update-available.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +7 -4
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
- package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/storage.d.ts +18 -0
- package/dist/types/src/storage.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +48 -16
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +32 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +73 -36
- package/src/RegistryPlugin.node.ts +19 -0
- package/src/RegistryPlugin.test.ts +24 -0
- package/src/RegistryPlugin.tsx +20 -27
- package/src/capabilities/app-graph-builder.ts +180 -135
- package/src/capabilities/dev-plugin-loader.ts +39 -0
- package/src/capabilities/index.ts +10 -4
- package/src/capabilities/operation-handler.ts +16 -0
- package/src/capabilities/react-surface.tsx +124 -73
- package/src/capabilities/settings.ts +34 -0
- package/src/commands/disable.ts +47 -0
- package/src/commands/enable.ts +44 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/list.ts +41 -0
- package/src/commands/util.ts +24 -0
- package/src/components/PluginDetail/PluginDetail.stories.tsx +44 -0
- package/src/components/PluginDetail/PluginDetail.tsx +196 -0
- package/src/components/PluginDetail/index.ts +5 -0
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
- package/src/components/PluginFailureBadge/index.ts +5 -0
- package/src/components/PluginList/PluginItem.tsx +226 -0
- package/src/components/PluginList/PluginList.stories.tsx +87 -0
- package/src/components/PluginList/PluginList.tsx +52 -0
- package/src/components/PluginList/index.ts +5 -0
- package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
- package/src/components/RegistrySettings/index.ts +5 -0
- package/src/components/index.ts +2 -1
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +163 -0
- package/src/containers/BaseRegistryArticle/index.ts +5 -0
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +87 -0
- package/src/containers/LoadPluginDialog/index.ts +5 -0
- package/src/containers/PluginArticle/PluginArticle.tsx +336 -0
- package/src/containers/PluginArticle/index.ts +5 -0
- package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +192 -0
- package/src/containers/PublicRegistryArticle/index.ts +5 -0
- package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
- package/src/containers/RegistryArticle/index.ts +5 -0
- package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
- package/src/containers/RegistrySettingsContainer/index.ts +5 -0
- package/src/containers/index.ts +12 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/use-auto-tags.ts +51 -0
- package/src/hooks/use-registry-plugins.ts +29 -0
- package/src/hooks/use-update-available.ts +31 -0
- package/src/index.ts +1 -1
- package/src/meta.ts +21 -9
- package/src/operations/index.ts +7 -0
- package/src/operations/open-plugin-registry.ts +21 -0
- package/src/plugin.ts +11 -0
- package/src/storage.ts +50 -0
- package/src/translations.ts +52 -15
- package/src/types.ts +37 -1
- package/dist/lib/browser/app-graph-builder-6JQNRTEQ.mjs +0 -161
- package/dist/lib/browser/app-graph-builder-6JQNRTEQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-AEBASNOI.mjs +0 -16
- package/dist/lib/browser/chunk-AEBASNOI.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -71
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-UGDFPMAG.mjs +0 -23
- package/dist/lib/browser/intent-resolver-UGDFPMAG.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/react-surface-YATPY47L.mjs +0 -335
- package/dist/lib/browser/react-surface-YATPY47L.mjs.map +0 -7
- package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
- package/dist/types/src/components/Icon.stories.d.ts +0 -14
- package/dist/types/src/components/Icon.stories.d.ts.map +0 -1
- package/dist/types/src/components/PluginDetail.d.ts +0 -9
- package/dist/types/src/components/PluginDetail.d.ts.map +0 -1
- package/dist/types/src/components/PluginDetail.stories.d.ts +0 -33
- package/dist/types/src/components/PluginDetail.stories.d.ts.map +0 -1
- package/dist/types/src/components/PluginItem.d.ts +0 -13
- package/dist/types/src/components/PluginItem.d.ts.map +0 -1
- package/dist/types/src/components/PluginList.d.ts +0 -8
- package/dist/types/src/components/PluginList.d.ts.map +0 -1
- package/dist/types/src/components/PluginList.stories.d.ts +0 -37
- package/dist/types/src/components/PluginList.stories.d.ts.map +0 -1
- package/dist/types/src/components/RegistryContainer.d.ts +0 -7
- package/dist/types/src/components/RegistryContainer.d.ts.map +0 -1
- package/src/capabilities/intent-resolver.ts +0 -27
- package/src/components/Icon.stories.tsx +0 -110
- package/src/components/PluginDetail.stories.tsx +0 -46
- package/src/components/PluginDetail.tsx +0 -65
- package/src/components/PluginItem.tsx +0 -119
- package/src/components/PluginList.stories.tsx +0 -85
- package/src/components/PluginList.tsx +0 -26
- package/src/components/RegistryContainer.tsx +0 -84
|
@@ -2,84 +2,135 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
6
7
|
|
|
7
|
-
import { Capabilities,
|
|
8
|
+
import { Capabilities, Capability, Plugin } from '@dxos/app-framework';
|
|
9
|
+
import { Surface, usePluginManager } from '@dxos/app-framework/ui';
|
|
10
|
+
import { AppCapabilities } from '@dxos/app-toolkit';
|
|
11
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
8
12
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
13
|
+
import {
|
|
14
|
+
LOAD_PLUGIN_DIALOG,
|
|
15
|
+
LoadPluginDialog,
|
|
16
|
+
PluginArticle,
|
|
17
|
+
PublicRegistryArticle,
|
|
18
|
+
RegistryArticle,
|
|
19
|
+
RegistrySettingsContainer,
|
|
20
|
+
} from '#containers';
|
|
21
|
+
import { meta, registryCategoryId } from '#meta';
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
contributes(Capabilities.ReactSurface, [
|
|
14
|
-
createSurface({
|
|
15
|
-
id: `${REGISTRY_PLUGIN}/all`,
|
|
16
|
-
role: 'article',
|
|
17
|
-
filter: (data): data is any => data.subject === `${REGISTRY_KEY}+all`,
|
|
18
|
-
component: () => {
|
|
19
|
-
const manager = usePluginManager();
|
|
20
|
-
const filtered = useMemo(() => manager.plugins.filter(({ meta }) => !manager.core.includes(meta.id)), []);
|
|
23
|
+
import { useAutoTags, useRegistryPlugins, useRemotePluginIds } from '../hooks';
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
export default Capability.makeModule(() =>
|
|
26
|
+
Effect.succeed(
|
|
27
|
+
Capability.contributes(Capabilities.ReactSurface, [
|
|
28
|
+
Surface.create({
|
|
29
|
+
id: 'official',
|
|
30
|
+
filter: AppSurface.literal(AppSurface.Article, registryCategoryId('official')),
|
|
31
|
+
component: () => {
|
|
32
|
+
const manager = usePluginManager();
|
|
33
|
+
const remoteIds = useRemotePluginIds();
|
|
34
|
+
const core = useMemo(() => manager.getCore(), [manager]);
|
|
35
|
+
const predicate = useMemo<PluginPredicate>(
|
|
36
|
+
() =>
|
|
37
|
+
({ meta }) =>
|
|
38
|
+
!core.includes(meta.id) && !remoteIds.has(meta.id),
|
|
39
|
+
[core, remoteIds],
|
|
40
|
+
);
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
return <FilteredRegistryArticle id={registryCategoryId('official')} filter={predicate} />;
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
Surface.create({
|
|
46
|
+
id: 'installed',
|
|
47
|
+
filter: AppSurface.literal(AppSurface.Article, registryCategoryId('installed')),
|
|
48
|
+
component: () => {
|
|
49
|
+
const manager = usePluginManager();
|
|
50
|
+
const core = useMemo(() => manager.getCore(), [manager]);
|
|
51
|
+
const enabled = useMemo(() => manager.getEnabled(), [manager]);
|
|
52
|
+
const predicate = useMemo<PluginPredicate>(
|
|
53
|
+
() =>
|
|
54
|
+
({ meta }) =>
|
|
55
|
+
!core.includes(meta.id) && enabled.includes(meta.id),
|
|
56
|
+
[core, enabled],
|
|
57
|
+
);
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
return <FilteredRegistryArticle id={registryCategoryId('installed')} filter={predicate} />;
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
Surface.create({
|
|
63
|
+
id: 'recommended',
|
|
64
|
+
filter: AppSurface.literal(AppSurface.Article, registryCategoryId('recommended')),
|
|
65
|
+
component: () => {
|
|
66
|
+
const manager = usePluginManager();
|
|
67
|
+
const remoteIds = useRemotePluginIds();
|
|
68
|
+
const core = useMemo(() => manager.getCore(), [manager]);
|
|
69
|
+
const predicate = useMemo<PluginPredicate>(
|
|
70
|
+
() =>
|
|
71
|
+
({ meta }) =>
|
|
72
|
+
!core.includes(meta.id) && !remoteIds.has(meta.id) && !meta.tags?.includes('labs'),
|
|
73
|
+
[core, remoteIds],
|
|
74
|
+
);
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
);
|
|
76
|
+
return <FilteredRegistryArticle id={registryCategoryId('recommended')} filter={predicate} />;
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
Surface.create({
|
|
80
|
+
id: 'labs',
|
|
81
|
+
filter: AppSurface.literal(AppSurface.Article, registryCategoryId('labs')),
|
|
82
|
+
component: () => {
|
|
83
|
+
const predicate = useMemo<PluginPredicate>(
|
|
84
|
+
() =>
|
|
85
|
+
({ meta }) =>
|
|
86
|
+
meta.tags?.includes('labs') ?? false,
|
|
87
|
+
[],
|
|
88
|
+
);
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
return <FilteredRegistryArticle id={registryCategoryId('labs')} filter={predicate} />;
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
Surface.create({
|
|
94
|
+
id: 'registry',
|
|
95
|
+
filter: AppSurface.literal(AppSurface.Article, registryCategoryId('registry')),
|
|
96
|
+
component: () => <PublicRegistryArticle id={registryCategoryId('registry')} />,
|
|
97
|
+
}),
|
|
98
|
+
Surface.create({
|
|
99
|
+
id: 'plugin-details',
|
|
100
|
+
filter: AppSurface.subject(AppSurface.Article, Plugin.isPlugin),
|
|
101
|
+
component: ({ data: { subject } }) => {
|
|
102
|
+
return <PluginArticle subject={subject} />;
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
Surface.create({
|
|
106
|
+
id: LOAD_PLUGIN_DIALOG,
|
|
107
|
+
filter: AppSurface.component(AppSurface.Dialog, LOAD_PLUGIN_DIALOG),
|
|
108
|
+
component: () => <LoadPluginDialog />,
|
|
109
|
+
}),
|
|
110
|
+
Surface.create({
|
|
111
|
+
id: 'plugin-settings',
|
|
112
|
+
role: 'article',
|
|
113
|
+
filter: (data): data is { subject: AppCapabilities.Settings } =>
|
|
114
|
+
AppCapabilities.isSettings(data.subject) && data.subject.prefix === meta.id,
|
|
115
|
+
component: ({ data: { subject } }) => <RegistrySettingsContainer subject={subject} />,
|
|
116
|
+
}),
|
|
117
|
+
]),
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
type PluginPredicate = (plugin: Plugin.Plugin) => boolean;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Renders the {@link RegistryArticle} surface filtered by an arbitrary
|
|
125
|
+
* predicate computed against the live plugin list. Centralises the
|
|
126
|
+
* `usePluginManager` + `useRegistryPlugins` + `useAutoTags` wiring shared
|
|
127
|
+
* by every category surface.
|
|
128
|
+
*/
|
|
129
|
+
const FilteredRegistryArticle = ({ id, filter }: { id: string; filter: PluginPredicate }) => {
|
|
130
|
+
const manager = usePluginManager();
|
|
131
|
+
const { entries } = useRegistryPlugins();
|
|
132
|
+
const extraTagsById = useAutoTags(entries);
|
|
133
|
+
const filtered = useMemo(() => manager.getPlugins().filter(filter), [manager, filter]);
|
|
134
|
+
|
|
135
|
+
return <RegistryArticle id={id} plugins={filtered} extraTagsById={extraTagsById} />;
|
|
136
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { Capability, PLUGIN_DEV_SERVER_PORT } from '@dxos/app-framework';
|
|
8
|
+
import { AppCapabilities } from '@dxos/app-toolkit';
|
|
9
|
+
import { createKvsStore } from '@dxos/effect';
|
|
10
|
+
|
|
11
|
+
import { meta } from '#meta';
|
|
12
|
+
|
|
13
|
+
import { RegistryCapabilities, RegistrySettingsSchema } from '../types';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_DEV_PLUGIN_URL = `http://localhost:${PLUGIN_DEV_SERVER_PORT}/manifest.json`;
|
|
16
|
+
|
|
17
|
+
export default Capability.makeModule(() =>
|
|
18
|
+
Effect.sync(() => {
|
|
19
|
+
const settingsAtom = createKvsStore({
|
|
20
|
+
key: meta.id,
|
|
21
|
+
schema: RegistrySettingsSchema,
|
|
22
|
+
defaultValue: () => ({ devPluginUrl: DEFAULT_DEV_PLUGIN_URL }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
Capability.contributes(RegistryCapabilities.Settings, settingsAtom),
|
|
27
|
+
Capability.contributes(AppCapabilities.Settings, {
|
|
28
|
+
prefix: meta.id,
|
|
29
|
+
schema: RegistrySettingsSchema,
|
|
30
|
+
atom: settingsAtom,
|
|
31
|
+
}),
|
|
32
|
+
];
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Args from '@effect/cli/Args';
|
|
6
|
+
import * as Command from '@effect/cli/Command';
|
|
7
|
+
import * as Console from 'effect/Console';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
|
|
10
|
+
import { Plugin } from '@dxos/app-framework';
|
|
11
|
+
import { CommandConfig } from '@dxos/cli-util';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
|
|
14
|
+
import { saveEnabledPlugins } from '../storage';
|
|
15
|
+
|
|
16
|
+
export const handler = Effect.fn(function* ({ id }: { id: string }) {
|
|
17
|
+
const { json, profile } = yield* CommandConfig;
|
|
18
|
+
const manager = yield* Plugin.Service;
|
|
19
|
+
|
|
20
|
+
const plugins = manager.getPlugins();
|
|
21
|
+
const plugin = plugins.find((p: Plugin.Plugin) => p.meta.id === id);
|
|
22
|
+
invariant(plugin, `Plugin not found: ${id}`);
|
|
23
|
+
|
|
24
|
+
const core = manager.getCore();
|
|
25
|
+
invariant(!core.includes(id), `Cannot disable core plugin: ${id}`);
|
|
26
|
+
|
|
27
|
+
const disabled = yield* manager.disable(id);
|
|
28
|
+
invariant(disabled, `Failed to disable plugin: ${id}`);
|
|
29
|
+
|
|
30
|
+
// Save enabled plugins to storage
|
|
31
|
+
const enabledPlugins = manager.getEnabled();
|
|
32
|
+
yield* saveEnabledPlugins({ profile, enabled: [...enabledPlugins] });
|
|
33
|
+
|
|
34
|
+
if (json) {
|
|
35
|
+
yield* Console.log(JSON.stringify({ id, enabled: false }, null, 2));
|
|
36
|
+
} else {
|
|
37
|
+
yield* Console.log(`Plugin "${plugin.meta.name ?? id}" disabled.`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const disable = Command.make(
|
|
42
|
+
'disable',
|
|
43
|
+
{
|
|
44
|
+
id: Args.text({ name: 'id' }).pipe(Args.withDescription('The ID of the plugin to disable.')),
|
|
45
|
+
},
|
|
46
|
+
handler,
|
|
47
|
+
).pipe(Command.withDescription('Disable a plugin.'));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Args from '@effect/cli/Args';
|
|
6
|
+
import * as Command from '@effect/cli/Command';
|
|
7
|
+
import * as Console from 'effect/Console';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
|
|
10
|
+
import { Plugin } from '@dxos/app-framework';
|
|
11
|
+
import { CommandConfig } from '@dxos/cli-util';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
|
|
14
|
+
import { saveEnabledPlugins } from '../storage';
|
|
15
|
+
|
|
16
|
+
export const handler = Effect.fn(function* ({ id }: { id: string }) {
|
|
17
|
+
const { json, profile } = yield* CommandConfig;
|
|
18
|
+
const manager = yield* Plugin.Service;
|
|
19
|
+
|
|
20
|
+
const plugins = manager.getPlugins();
|
|
21
|
+
const plugin = plugins.find((p: Plugin.Plugin) => p.meta.id === id);
|
|
22
|
+
invariant(plugin, `Plugin not found: ${id}`);
|
|
23
|
+
|
|
24
|
+
const enabled = yield* manager.enable(id);
|
|
25
|
+
invariant(enabled, `Failed to enable plugin: ${id}`);
|
|
26
|
+
|
|
27
|
+
// Save enabled plugins to storage
|
|
28
|
+
const enabledPlugins = manager.getEnabled();
|
|
29
|
+
yield* saveEnabledPlugins({ profile, enabled: [...enabledPlugins] });
|
|
30
|
+
|
|
31
|
+
if (json) {
|
|
32
|
+
yield* Console.log(JSON.stringify({ id, enabled: true }, null, 2));
|
|
33
|
+
} else {
|
|
34
|
+
yield* Console.log(`Plugin "${plugin.meta.name ?? id}" enabled.`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const enable = Command.make(
|
|
39
|
+
'enable',
|
|
40
|
+
{
|
|
41
|
+
id: Args.text({ name: 'id' }).pipe(Args.withDescription('The ID of the plugin to enable.')),
|
|
42
|
+
},
|
|
43
|
+
handler,
|
|
44
|
+
).pipe(Command.withDescription('Enable a plugin.'));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Command from '@effect/cli/Command';
|
|
6
|
+
|
|
7
|
+
import { disable } from './disable';
|
|
8
|
+
import { enable } from './enable';
|
|
9
|
+
import { list } from './list';
|
|
10
|
+
|
|
11
|
+
export const plugin: Command.Command<any, any, any, any> = Command.make('plugin').pipe(
|
|
12
|
+
Command.withDescription('Manage plugins.'),
|
|
13
|
+
Command.withSubcommands([enable, disable, list]),
|
|
14
|
+
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Command from '@effect/cli/Command';
|
|
6
|
+
import * as Console from 'effect/Console';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
|
|
9
|
+
import { Plugin } from '@dxos/app-framework';
|
|
10
|
+
import { CommandConfig, printList } from '@dxos/cli-util';
|
|
11
|
+
|
|
12
|
+
import { type FormattedPlugin, printPlugin } from './util';
|
|
13
|
+
|
|
14
|
+
export const handler = Effect.fn(function* () {
|
|
15
|
+
const { json } = yield* CommandConfig;
|
|
16
|
+
const manager = yield* Plugin.Service;
|
|
17
|
+
|
|
18
|
+
const plugins = manager.getPlugins();
|
|
19
|
+
const enabled = manager.getEnabled();
|
|
20
|
+
const core = manager.getCore();
|
|
21
|
+
|
|
22
|
+
const formattedPlugins: FormattedPlugin[] = plugins.map((plugin: Plugin.Plugin) => {
|
|
23
|
+
const isEnabled = enabled.includes(plugin.meta.id);
|
|
24
|
+
const isCore = core.includes(plugin.meta.id);
|
|
25
|
+
return {
|
|
26
|
+
id: plugin.meta.id,
|
|
27
|
+
name: plugin.meta.name ?? plugin.meta.id,
|
|
28
|
+
enabled: isEnabled,
|
|
29
|
+
core: isCore,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (json) {
|
|
34
|
+
yield* Console.log(JSON.stringify(formattedPlugins, null, 2));
|
|
35
|
+
} else {
|
|
36
|
+
const formatted = formattedPlugins.map(printPlugin);
|
|
37
|
+
yield* Console.log(printList(formatted));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const list = Command.make('list', {}, handler).pipe(Command.withDescription('List all available plugins.'));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { FormBuilder } from '@dxos/cli-util';
|
|
6
|
+
|
|
7
|
+
export type FormattedPlugin = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
core: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pretty prints a plugin with ANSI colors.
|
|
16
|
+
*/
|
|
17
|
+
export const printPlugin = (plugin: FormattedPlugin) => {
|
|
18
|
+
const status = plugin.core ? 'core' : plugin.enabled ? 'enabled' : 'disabled';
|
|
19
|
+
return FormBuilder.make({ title: plugin.name }).pipe(
|
|
20
|
+
FormBuilder.set('id', plugin.id),
|
|
21
|
+
FormBuilder.set('status', status),
|
|
22
|
+
FormBuilder.build,
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -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 { random } 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: 'com.example.plugin.test',
|
|
33
|
+
name: 'Test Plugin',
|
|
34
|
+
description: random.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,196 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Registry, type Plugin, type PluginManager } from '@dxos/app-framework';
|
|
8
|
+
import { Button, Icon, Input, Link, ScrollArea, Select, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { composable, composableProps, getStyles, mx } from '@dxos/ui-theme';
|
|
10
|
+
|
|
11
|
+
import { meta } from '#meta';
|
|
12
|
+
|
|
13
|
+
import { PluginFailureBadge } from '../PluginFailureBadge';
|
|
14
|
+
|
|
15
|
+
export type PluginDetailProps = {
|
|
16
|
+
plugin: Plugin.Plugin;
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
onEnabledChange?: (enabled: boolean) => void;
|
|
19
|
+
/**
|
|
20
|
+
* When provided, an Uninstall button is rendered. Leave undefined for core
|
|
21
|
+
* or non-removable plugins.
|
|
22
|
+
*/
|
|
23
|
+
onUninstall?: () => void;
|
|
24
|
+
/**
|
|
25
|
+
* When provided, the plugin is not installed and an Install button is shown
|
|
26
|
+
* in place of the enable Switch.
|
|
27
|
+
*/
|
|
28
|
+
onInstall?: () => void;
|
|
29
|
+
installing?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* When provided and the plugin is installed with a newer catalog version available,
|
|
32
|
+
* an Update button is shown in place of the enable Switch.
|
|
33
|
+
*/
|
|
34
|
+
onUpdate?: () => void;
|
|
35
|
+
/** True when the catalog has a newer version than the one installed. */
|
|
36
|
+
hasUpdate?: boolean;
|
|
37
|
+
/** True while an in-flight update is running. Forces the Update button disabled state. */
|
|
38
|
+
updating?: boolean;
|
|
39
|
+
/** Available versions of this plugin from the catalog. When non-empty, a version picker is shown. */
|
|
40
|
+
versions?: readonly Registry.PluginVersion[];
|
|
41
|
+
/** Currently selected version tag in the picker. */
|
|
42
|
+
selectedVersionTag?: string;
|
|
43
|
+
/** Called when the user selects a different version in the picker. */
|
|
44
|
+
onVersionChange?: (tag: string) => void;
|
|
45
|
+
/** Called when the user clicks Install on the version picker. */
|
|
46
|
+
onInstallVersion?: () => void;
|
|
47
|
+
/** Currently installed version tag (used to mark the matching select option). */
|
|
48
|
+
installedVersionTag?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Failure record for this plugin, if any. When present a warning badge is
|
|
51
|
+
* rendered next to the plugin name in the header.
|
|
52
|
+
*/
|
|
53
|
+
failure?: PluginManager.PluginFailure;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
|
|
57
|
+
(
|
|
58
|
+
{
|
|
59
|
+
plugin,
|
|
60
|
+
enabled,
|
|
61
|
+
onEnabledChange,
|
|
62
|
+
onUninstall,
|
|
63
|
+
onInstall,
|
|
64
|
+
installing,
|
|
65
|
+
onUpdate,
|
|
66
|
+
hasUpdate,
|
|
67
|
+
updating,
|
|
68
|
+
versions,
|
|
69
|
+
selectedVersionTag,
|
|
70
|
+
onVersionChange,
|
|
71
|
+
onInstallVersion,
|
|
72
|
+
installedVersionTag,
|
|
73
|
+
failure,
|
|
74
|
+
...props
|
|
75
|
+
},
|
|
76
|
+
forwardedRef,
|
|
77
|
+
) => {
|
|
78
|
+
const { t } = useTranslation(meta.id);
|
|
79
|
+
const {
|
|
80
|
+
id,
|
|
81
|
+
name,
|
|
82
|
+
description,
|
|
83
|
+
homePage,
|
|
84
|
+
source,
|
|
85
|
+
screenshots,
|
|
86
|
+
icon = 'ph--circle--regular',
|
|
87
|
+
iconHue = 'neutral',
|
|
88
|
+
} = plugin.meta;
|
|
89
|
+
const styles = getStyles(iconHue);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<ScrollArea.Root {...composableProps(props)} orientation='vertical' ref={forwardedRef}>
|
|
93
|
+
<ScrollArea.Viewport>
|
|
94
|
+
<div className='dx-document grid grid-cols-[min-content_1fr] gap-4 p-4'>
|
|
95
|
+
<div>
|
|
96
|
+
<Icon classNames={mx('p-1 rounded-md', styles.fill, styles.surfaceText)} icon={icon} size={14} />
|
|
97
|
+
</div>
|
|
98
|
+
<div className='flex flex-col gap-6'>
|
|
99
|
+
<div className='grid grid-cols-[1fr_min-content] gap-x-3 w-full pt-1'>
|
|
100
|
+
<div className='flex items-center gap-2'>
|
|
101
|
+
<h2 className='text-xl'>{name}</h2>
|
|
102
|
+
{failure && <PluginFailureBadge failure={failure} size={5} />}
|
|
103
|
+
</div>
|
|
104
|
+
{onInstall ? (
|
|
105
|
+
<Button density='fine' variant='primary' disabled={installing} onClick={onInstall}>
|
|
106
|
+
{installing ? t('installing.label') : t('install.label')}
|
|
107
|
+
</Button>
|
|
108
|
+
) : (
|
|
109
|
+
<Input.Root>
|
|
110
|
+
<Input.Switch classNames='self-center' checked={enabled} onCheckedChange={onEnabledChange} />
|
|
111
|
+
</Input.Root>
|
|
112
|
+
)}
|
|
113
|
+
<p className='pt-0.5 text-sm text-description'>{id}</p>
|
|
114
|
+
</div>
|
|
115
|
+
<div>
|
|
116
|
+
<p className='text-description'>{description}</p>
|
|
117
|
+
</div>
|
|
118
|
+
{screenshots && screenshots.length > 0 && (
|
|
119
|
+
<div className='flex flex-col gap-2'>
|
|
120
|
+
<h2>Preview</h2>
|
|
121
|
+
<img src={screenshots[0]} alt={name} className='aspect-video object-fit' />
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
<div className='flex flex-col gap-2'>
|
|
125
|
+
<h2>Resources</h2>
|
|
126
|
+
<div className='flex gap-2'>
|
|
127
|
+
{homePage && (
|
|
128
|
+
<Link href={homePage} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
|
|
129
|
+
{t('home-page.label')}
|
|
130
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mx-1' />
|
|
131
|
+
</Link>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{source && (
|
|
135
|
+
<Link href={source} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
|
|
136
|
+
{t('source.label')}
|
|
137
|
+
<Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mx-1' />
|
|
138
|
+
</Link>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
{versions && versions.length > 0 && (
|
|
143
|
+
<div className='flex flex-col gap-2'>
|
|
144
|
+
<h2>{t('versions.label')}</h2>
|
|
145
|
+
<div className='flex gap-2 items-center'>
|
|
146
|
+
<Select.Root value={selectedVersionTag} onValueChange={onVersionChange}>
|
|
147
|
+
<Select.TriggerButton classNames='min-w-32' />
|
|
148
|
+
<Select.Portal>
|
|
149
|
+
<Select.Content>
|
|
150
|
+
<Select.Viewport>
|
|
151
|
+
{versions.map((versionEntry) => (
|
|
152
|
+
<Select.Option key={versionEntry.tag} value={versionEntry.tag}>
|
|
153
|
+
{versionEntry.tag}
|
|
154
|
+
{installedVersionTag === versionEntry.tag ? ` (${t('installed.label')})` : ''}
|
|
155
|
+
</Select.Option>
|
|
156
|
+
))}
|
|
157
|
+
</Select.Viewport>
|
|
158
|
+
</Select.Content>
|
|
159
|
+
</Select.Portal>
|
|
160
|
+
</Select.Root>
|
|
161
|
+
{onInstallVersion && (
|
|
162
|
+
<Button
|
|
163
|
+
density='fine'
|
|
164
|
+
variant='primary'
|
|
165
|
+
disabled={installing || selectedVersionTag === installedVersionTag}
|
|
166
|
+
onClick={onInstallVersion}
|
|
167
|
+
>
|
|
168
|
+
{installing ? t('installing.label') : t('install-version.label')}
|
|
169
|
+
</Button>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
{(onUninstall || (hasUpdate && onUpdate) || updating) && (
|
|
175
|
+
<div className='flex gap-2'>
|
|
176
|
+
{updating ? (
|
|
177
|
+
<Button variant='primary' disabled>
|
|
178
|
+
{t('updating.label')}
|
|
179
|
+
</Button>
|
|
180
|
+
) : hasUpdate && onUpdate ? (
|
|
181
|
+
<Button variant='primary' onClick={onUpdate}>
|
|
182
|
+
{t('update.label')}
|
|
183
|
+
</Button>
|
|
184
|
+
) : null}
|
|
185
|
+
{onUninstall && <Button onClick={onUninstall}>{t('uninstall.label')}</Button>}
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</ScrollArea.Viewport>
|
|
191
|
+
</ScrollArea.Root>
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
PluginDetail.displayName = 'PluginDetail';
|