@dxos/plugin-registry 0.13.0 → 0.14.0
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/chunk-BaseRegistryArticle.mjs +6 -7
- package/dist/lib/chunk-BaseRegistryArticle.mjs.map +1 -1
- package/dist/lib/chunk-PluginArticle.mjs +2 -2
- package/dist/lib/chunk-PluginArticle.mjs.map +1 -1
- package/dist/lib/chunk-PublicRegistryArticle.mjs +2 -2
- package/dist/lib/chunk-PublicRegistryArticle.mjs.map +1 -1
- package/dist/lib/chunk-RegistryArticle.mjs +2 -2
- package/dist/lib/chunk-RegistryArticle.mjs.map +1 -1
- package/dist/lib/chunk-react-surface.mjs +19 -7
- package/dist/lib/chunk-react-surface.mjs.map +1 -1
- package/dist/lib/components.mjs +31 -19
- package/dist/lib/components.mjs.map +1 -1
- package/dist/types/src/capabilities/RegistryCategoryArticle.d.ts +2 -1
- package/dist/types/src/capabilities/RegistryCategoryArticle.d.ts.map +1 -1
- package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -1
- package/dist/types/src/components/PluginList/PluginItem.d.ts +2 -1
- package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +1 -2
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -1
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +1 -1
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -30
- package/src/capabilities/RegistryCategoryArticle.tsx +8 -2
- package/src/capabilities/react-surface.ts +5 -5
- package/src/components/PluginDetail/PluginDetail.tsx +17 -15
- package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +3 -2
- package/src/components/PluginList/PluginItem.tsx +7 -3
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +4 -13
- package/src/containers/PluginArticle/PluginArticle.tsx +1 -1
- package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +3 -3
- package/src/containers/RegistryArticle/RegistryArticle.tsx +3 -3
- package/src/hooks/usePluginActions.ts +1 -1
|
@@ -5,13 +5,13 @@ import { meta } from "#meta";
|
|
|
5
5
|
import { useCallback, useMemo, useState } from "react";
|
|
6
6
|
import * as UiHooks from "@dxos/react-ui/Hooks";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import * as ToolkitHooks from "@dxos/app-toolkit/Hooks";
|
|
8
9
|
import * as Input from "@dxos/react-ui/Input";
|
|
9
10
|
import * as Layout from "@dxos/react-ui/Layout";
|
|
10
11
|
import * as ScrollArea from "@dxos/react-ui/ScrollArea";
|
|
11
12
|
import * as Util from "@dxos/react-ui/Util";
|
|
12
13
|
import * as SettingsOperation from "@dxos/app-toolkit/SettingsOperation";
|
|
13
14
|
import * as AppCapabilities from "@dxos/app-toolkit/AppCapabilities";
|
|
14
|
-
import * as LayoutOperation from "@dxos/app-toolkit/LayoutOperation";
|
|
15
15
|
import { PluginList } from "#components";
|
|
16
16
|
import * as PluginManagerProvider from "@dxos/app-framework/PluginManagerProvider";
|
|
17
17
|
import { useAtomValue } from "@effect/atom-react/Hooks";
|
|
@@ -25,7 +25,7 @@ import * as Toolbar from "@dxos/react-ui/Toolbar";
|
|
|
25
25
|
var matchesFilter = (plugin, query) => {
|
|
26
26
|
return `${plugin.meta.profile.name ?? ""} ${plugin.meta.profile.key}`.toLowerCase().includes(query);
|
|
27
27
|
};
|
|
28
|
-
var BaseRegistryArticle = Util.composable(({
|
|
28
|
+
var BaseRegistryArticle = Util.composable(({ contextId, plugins, source, empty, installed, installing, updating, updateAvailableIds, extraTagsById, failuresById, deviceOnlyIds, onInstall, onUpdate, ...props }, forwardedRef) => {
|
|
29
29
|
const { t } = UiHooks.useTranslation(meta.profile.key);
|
|
30
30
|
const manager = PluginManagerProvider.usePluginManager();
|
|
31
31
|
const { invoke, invokePromise } = Hooks.useOperationInvoker();
|
|
@@ -64,11 +64,10 @@ var BaseRegistryArticle = Util.composable(({ id, plugins, source, empty, install
|
|
|
64
64
|
}
|
|
65
65
|
requestDisable(pluginId);
|
|
66
66
|
}, [dispatchToggle, requestDisable]);
|
|
67
|
-
const handleClick =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}), [invokePromise, id]);
|
|
67
|
+
const handleClick = ToolkitHooks.useDetailNavigation({
|
|
68
|
+
contextId,
|
|
69
|
+
getPath: getPluginPath
|
|
70
|
+
});
|
|
72
71
|
const hasSettings = useCallback((pluginId) => allSettings.some((setting) => setting.prefix === pluginId), [allSettings]);
|
|
73
72
|
const handleSettings = useCallback((pluginId) => invokePromise(SettingsOperation.Open, { plugin: pluginId }), [invokePromise]);
|
|
74
73
|
return /* @__PURE__ */ jsxs(Panel.Root, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-BaseRegistryArticle.mjs","names":[],"sources":["../../src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Effect from 'effect/Effect';\nimport React, { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\n// Loaded only through the lazy registry containers, so Next's CSS stays out of the boot graph.\nimport '@dxos/react-ui/theme.css';\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';\nimport * as
|
|
1
|
+
{"version":3,"file":"chunk-BaseRegistryArticle.mjs","names":[],"sources":["../../src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Effect from 'effect/Effect';\nimport React, { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\n// Loaded only through the lazy registry containers, so Next's CSS stays out of the boot graph.\nimport '@dxos/react-ui/theme.css';\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';\nimport * as ToolkitHooks from '@dxos/app-toolkit/Hooks';\nimport * as SettingsOperation from '@dxos/app-toolkit/SettingsOperation';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport * as ObservabilityOperation from '@dxos/plugin-observability/ObservabilityOperation';\nimport * as UiHooks from '@dxos/react-ui/Hooks';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Panel from '@dxos/react-ui/Panel';\nimport * as ScrollArea from '@dxos/react-ui/ScrollArea';\nimport * as Toolbar from '@dxos/react-ui/Toolbar';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { PluginList, type PluginListProps } from '#components';\nimport { meta } from '#meta';\n\nimport { useDisableConfirmation } from '../../hooks/index.ts';\nimport { getPluginPath } from '../../paths.ts';\n\nconst matchesFilter = (plugin: Plugin.Plugin, query: string) => {\n const haystack = `${plugin.meta.profile.name ?? ''} ${plugin.meta.profile.key}`.toLowerCase();\n return haystack.includes(query);\n};\n\nexport type BaseRegistryArticleProps = {\n contextId: string;\n /** Plugins to display, pre-sorted by the caller. */\n plugins: readonly Plugin.Plugin[];\n /**\n * Distinguishes the source of toggle events for observability\n * (e.g. `'registry'` for the public registry surface).\n */\n source?: string;\n /** Rendered in place of the list when no plugins match the current filter. */\n empty?: ReactNode;\n} & Pick<\n PluginListProps,\n | 'installed'\n | 'installing'\n | 'updating'\n | 'updateAvailableIds'\n | 'extraTagsById'\n | 'failuresById'\n | 'deviceOnlyIds'\n | 'onInstall'\n | 'onUpdate'\n>;\n\nexport const BaseRegistryArticle = Util.composable<HTMLDivElement, BaseRegistryArticleProps>(\n (\n {\n contextId,\n plugins,\n source,\n empty,\n installed,\n installing,\n updating,\n updateAvailableIds,\n extraTagsById,\n failuresById,\n deviceOnlyIds,\n onInstall,\n onUpdate,\n ...props\n },\n forwardedRef,\n ) => {\n const { t } = UiHooks.useTranslation(meta.profile.key);\n const manager = PluginManagerProvider.usePluginManager();\n const { invoke, invokePromise } = Hooks.useOperationInvoker();\n const allSettings = Hooks.useCapabilities(AppCapabilities.Settings);\n const enabled = useAtomValue(manager.enabled);\n const settingsSync = Hooks.useOptionalCapability(AppCapabilities.SettingsSync);\n const [filter, setFilter] = useState('');\n\n const filtered = useMemo(() => {\n const query = filter.trim().toLowerCase();\n return query.length === 0 ? plugins : plugins.filter((plugin) => matchesFilter(plugin, query));\n }, [plugins, filter]);\n\n const dispatchToggle = useCallback(\n (pluginId: string, nextEnabled: boolean) =>\n Effect.gen(function* () {\n if (nextEnabled) {\n yield* manager.enable(pluginId);\n } else {\n yield* manager.disable(pluginId);\n }\n yield* invoke(ObservabilityOperation.SendEvent, {\n name: 'plugins.toggle',\n properties: source\n ? { plugin: pluginId, enabled: nextEnabled, source }\n : { plugin: pluginId, enabled: nextEnabled },\n });\n }).pipe(EffectEx.runAndForwardErrors),\n [invoke, manager, source],\n );\n\n const requestDisable = useDisableConfirmation(manager, (id) => void dispatchToggle(id, false));\n\n const handleChange = useCallback(\n (pluginId: string, nextEnabled: boolean) => {\n if (nextEnabled) {\n void dispatchToggle(pluginId, true);\n return;\n }\n requestDisable(pluginId);\n },\n [dispatchToggle, requestDisable],\n );\n\n const handleClick = ToolkitHooks.useDetailNavigation({ contextId, getPath: getPluginPath });\n\n const hasSettings = useCallback(\n (pluginId: string) => allSettings.some((setting) => setting.prefix === pluginId),\n [allSettings],\n );\n\n const handleSettings = useCallback(\n (pluginId: string) => invokePromise(SettingsOperation.Open, { plugin: pluginId }),\n [invokePromise],\n );\n\n return (\n <Panel.Root {...Util.composableProps(props)} ref={forwardedRef}>\n <Panel.Header>\n <Toolbar.Root>\n <Input.Root\n aria-label={t('filter.label')}\n placeholder={t('filter.placeholder')}\n value={filter}\n onChange={(event) => setFilter(event.target.value)}\n />\n </Toolbar.Root>\n </Panel.Header>\n <Panel.Body asChild>\n <ScrollArea.Root>\n <ScrollArea.Viewport asChild>\n <Layout.Container gutter='md' padBlock>\n {filtered.length > 0 ? (\n <PluginList\n plugins={filtered}\n enabled={enabled}\n installed={installed}\n installing={installing}\n updating={updating}\n updateAvailableIds={updateAvailableIds}\n extraTagsById={extraTagsById}\n failuresById={failuresById}\n deviceOnlyIds={deviceOnlyIds}\n onClick={handleClick}\n readOnly={settingsSync === undefined}\n onChange={handleChange}\n onInstall={onInstall}\n onUpdate={onUpdate}\n hasSettings={hasSettings}\n onSettings={handleSettings}\n />\n ) : (\n empty\n )}\n </Layout.Container>\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n </Panel.Body>\n </Panel.Root>\n );\n },\n);\n\nBaseRegistryArticle.displayName = 'BaseRegistryArticle';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAM,iBAAiB,QAAuB,UAAkB;CAE9D,OADiB,GAAG,OAAO,KAAK,QAAQ,QAAQ,GAAG,GAAG,OAAO,KAAK,QAAQ,MAAM,YACzE,CAAA,CAAS,SAAS,KAAK;AAChC;AA0BA,IAAa,sBAAsB,KAAK,YAEpC,EACE,WACA,SACA,QACA,OACA,WACA,YACA,UACA,oBACA,eACA,cACA,eACA,WACA,UACA,GAAG,SAEL,iBACG;CACH,MAAM,EAAE,MAAM,QAAQ,eAAe,KAAK,QAAQ,GAAG;CACrD,MAAM,UAAU,sBAAsB,iBAAiB;CACvD,MAAM,EAAE,QAAQ,kBAAkB,MAAM,oBAAoB;CAC5D,MAAM,cAAc,MAAM,gBAAgB,gBAAgB,QAAQ;CAClE,MAAM,UAAU,aAAa,QAAQ,OAAO;CAC5C,MAAM,eAAe,MAAM,sBAAsB,gBAAgB,YAAY;CAC7E,MAAM,CAAC,QAAQ,aAAa,SAAS,EAAE;CAEvC,MAAM,WAAW,cAAc;EAC7B,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC,YAAY;EACxC,OAAO,MAAM,WAAW,IAAI,UAAU,QAAQ,QAAQ,WAAW,cAAc,QAAQ,KAAK,CAAC;CAC/F,GAAG,CAAC,SAAS,MAAM,CAAC;CAEpB,MAAM,iBAAiB,aACpB,UAAkB,gBACjB,OAAO,IAAI,aAAa;EACtB,IAAI,aACF,OAAO,QAAQ,OAAO,QAAQ;OAE9B,OAAO,QAAQ,QAAQ,QAAQ;EAEjC,OAAO,OAAO,uBAAuB,WAAW;GAC9C,MAAM;GACN,YAAY,SACR;IAAE,QAAQ;IAAU,SAAS;IAAa;GAAO,IACjD;IAAE,QAAQ;IAAU,SAAS;GAAY;EAC/C,CAAC;CACH,CAAC,CAAC,CAAC,KAAK,SAAS,mBAAmB,GACtC;EAAC;EAAQ;EAAS;CAAM,CAC1B;CAEA,MAAM,iBAAiB,uBAAuB,UAAU,OAAO,KAAK,eAAe,IAAI,KAAK,CAAC;CAE7F,MAAM,eAAe,aAClB,UAAkB,gBAAyB;EAC1C,IAAI,aAAa;GACf,eAAoB,UAAU,IAAI;GAClC;EACF;EACA,eAAe,QAAQ;CACzB,GACA,CAAC,gBAAgB,cAAc,CACjC;CAEA,MAAM,cAAc,aAAa,oBAAoB;EAAE;EAAW,SAAS;CAAc,CAAC;CAE1F,MAAM,cAAc,aACjB,aAAqB,YAAY,MAAM,YAAY,QAAQ,WAAW,QAAQ,GAC/E,CAAC,WAAW,CACd;CAEA,MAAM,iBAAiB,aACpB,aAAqB,cAAc,kBAAkB,MAAM,EAAE,QAAQ,SAAS,CAAC,GAChF,CAAC,aAAa,CAChB;CAEA,OACE,qBAAC,MAAM,MAAP;EAAY,GAAI,KAAK,gBAAgB,KAAK;EAAG,KAAK;EAAlD,UAAA,CACE,oBAAC,MAAM,QAAP,EAAA,UACE,oBAAC,QAAQ,MAAT,EAAA,UACE,oBAAC,MAAM,MAAP;GACE,cAAY,EAAE,cAAc;GAC5B,aAAa,EAAE,oBAAoB;GACnC,OAAO;GACP,WAAW,UAAU,UAAU,MAAM,OAAO,KAAK;EAClD,CAAA,EACW,CAAA,EACF,CAAA,GACd,oBAAC,MAAM,MAAP;GAAY,SAAA;GACV,UAAA,oBAAC,WAAW,MAAZ,EAAA,UACE,oBAAC,WAAW,UAAZ;IAAqB,SAAA;IACnB,UAAA,oBAAC,OAAO,WAAR;KAAkB,QAAO;KAAK,UAAA;KAC3B,UAAA,SAAS,SAAS,IACjB,oBAAC,YAAD;MACE,SAAS;MACA;MACE;MACC;MACF;MACU;MACL;MACD;MACC;MACf,SAAS;MACT,UAAU,iBAAiB,KAAA;MAC3B,UAAU;MACC;MACD;MACG;MACb,YAAY;KACb,CAAA,IAED;IAEc,CAAA;GACC,CAAA,EACN,CAAA;EACP,CAAA,CACF;;AAEhB,CACF;AAEA,oBAAoB,cAAc"}
|
|
@@ -81,7 +81,7 @@ var usePluginActions = ({ manager, pluginId, isInstalled, moduleUrl, catalogEntr
|
|
|
81
81
|
invokePromise(LayoutOperation.Open, {
|
|
82
82
|
subject: [specPath],
|
|
83
83
|
pivotId: getPluginPath(pluginId),
|
|
84
|
-
disposition: "
|
|
84
|
+
disposition: "detail"
|
|
85
85
|
});
|
|
86
86
|
}, [
|
|
87
87
|
invokePromise,
|
|
@@ -266,7 +266,7 @@ var PluginArticle = ({ subject: plugin }) => {
|
|
|
266
266
|
invokePromise(LayoutOperation.Open, {
|
|
267
267
|
subject: [getPluginPath(targetId)],
|
|
268
268
|
pivotId: getPluginPath(pluginId),
|
|
269
|
-
disposition: "
|
|
269
|
+
disposition: "detail"
|
|
270
270
|
});
|
|
271
271
|
}, [invokePromise, pluginId]);
|
|
272
272
|
const actions = usePluginActions({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-PluginArticle.mjs","names":[],"sources":["../../src/hooks/useCatalogEntry.ts","../../src/hooks/useInstalledVersionTag.ts","../../src/hooks/usePluginActions.ts","../../src/hooks/useVersionPicker.ts","../../src/containers/PluginArticle/PluginArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { useMemo } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\n\nimport { useRegistryPlugins } from './useRegistryPlugins.ts';\n\n/**\n * Resolves the registry catalog entry for a plugin by id, plus the `moduleUrl`\n * the install/update actions need.\n */\nexport const useCatalogEntry = (\n pluginId: string,\n): {\n catalogEntry: Plugin.Meta | undefined;\n moduleUrl: string | undefined;\n} => {\n const { entries } = useRegistryPlugins();\n const catalogEntry = useMemo(() => entries.find((entry) => entry.profile.key === pluginId), [entries, pluginId]);\n return { catalogEntry, moduleUrl: catalogEntry?.release?.moduleUrl };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useCallback, useEffect, useState } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\n\n/**\n * Tracks the per-plugin installed version (sourced from `UrlLoader` localStorage)\n * as React state so post-install/update re-renders are deterministic — the value\n * lives in component state rather than being read inline on every render.\n *\n * - Initialised from localStorage at mount.\n * - Auto-resyncs when the plugin manager's plugin list changes (catches external\n * installs / removes triggered by the list view).\n * - `syncInstalledVersion()` is the explicit handle install/update operations\n * call inside their `Effect.ensuring` so the post-mutation render sees both\n * the new version and the cleared `installing`/`updating` flag together.\n */\nexport const useInstalledVersionTag = (\n pluginId: string,\n plugins: readonly Plugin.Plugin[],\n): {\n installedVersionTag: string | undefined;\n syncInstalledVersion: () => void;\n} => {\n const [installedVersionTag, setInstalledVersionTag] = useState<string | undefined>(() =>\n UrlLoader.getInstalledVersion(pluginId),\n );\n\n const syncInstalledVersion = useCallback(() => {\n setInstalledVersionTag(UrlLoader.getInstalledVersion(pluginId));\n }, [pluginId]);\n\n useEffect(() => {\n syncInstalledVersion();\n }, [plugins, syncInstalledVersion]);\n\n return { installedVersionTag, syncInstalledVersion };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport { useCallback, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport type * as PluginManager from '@dxos/app-framework/PluginManager';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\nimport * as ToolkitHooks from '@dxos/app-toolkit/Hooks';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport * as GraphHooks from '@dxos/plugin-graph/Hooks';\n\nimport { getPluginPath, getPluginSpecPath } from '../paths.ts';\nimport { useDisableConfirmation } from './useDisableConfirmation.ts';\n\nexport type PluginActionsProps = {\n manager: PluginManager.PluginManager;\n pluginId: string;\n isInstalled: boolean;\n moduleUrl: string | undefined;\n catalogEntry: Plugin.Meta | undefined;\n pickerVersions: readonly Plugin.Release[];\n selectedVersionTag: string | undefined;\n syncInstalledVersion: () => void;\n};\n\nexport type PluginActions = {\n installing: boolean;\n updating: boolean;\n handleEnableChange: (enabled: boolean) => void;\n handleUninstall: () => void;\n handleInstall: () => void;\n handleUpdate: () => void;\n handleInstallVersion: () => void;\n handleOpenSpec?: () => void;\n};\n\n/**\n * The user-facing actions the article exposes — enable/disable, uninstall,\n * install (latest), update (to latest catalog version), and install-from-picker —\n * plus the `installing`/`updating` flags they drive. Each handler ends with a\n * `syncInstalledVersion()` call inside its `Effect.ensuring` so the cached\n * installed version snaps to the freshly-written localStorage value in the same\n * render that clears the in-flight flag.\n *\n * `handleOpenSpec` is included only when a `spec` child graph node exists for\n * this plugin — contributed by whichever plugin can render MDL (today\n * `plugin-code`). When that contributor isn't enabled, the action is absent\n * and the detail view hides its \"Open Specification\" chip.\n */\nexport const usePluginActions = ({\n manager,\n pluginId,\n isInstalled,\n moduleUrl,\n catalogEntry,\n pickerVersions,\n selectedVersionTag,\n syncInstalledVersion,\n}: PluginActionsProps): PluginActions => {\n const { invokePromise } = Hooks.useOperationInvoker();\n const { graph } = ToolkitHooks.useAppGraph();\n\n const [installing, setInstalling] = useState(false);\n const [updating, setUpdating] = useState(false);\n const requestDisable = useDisableConfirmation(\n manager,\n (id) => void EffectEx.runAndForwardErrors(manager.disable(id)),\n );\n\n const specPath = getPluginSpecPath(pluginId);\n const hasSpecNode = !!GraphHooks.useNode(graph, specPath);\n const handleOpenSpec = useCallback(() => {\n void invokePromise(LayoutOperation.Open, {\n subject: [specPath],\n pivotId: getPluginPath(pluginId),\n disposition: 'add',\n });\n }, [invokePromise, specPath, pluginId]);\n\n const handleEnableChange = useCallback(\n (enabled: boolean) => {\n if (enabled) {\n void EffectEx.runAndForwardErrors(manager.enable(pluginId));\n return;\n }\n\n requestDisable(pluginId);\n },\n [manager, pluginId, requestDisable],\n );\n\n const handleUninstall = useCallback(() => {\n void EffectEx.runAndForwardErrors(manager.remove(pluginId));\n }, [manager, pluginId]);\n\n const handleInstall = useCallback(() => {\n if (!moduleUrl) {\n return;\n }\n\n setInstalling(true);\n void Effect.gen(function* () {\n const added = yield* manager.add(moduleUrl);\n yield* manager.enable(added.meta.profile.key);\n if (catalogEntry?.release?.version) {\n UrlLoader.setInstalledVersion(added.meta.profile.key, catalogEntry.release.version);\n }\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n setInstalling(false);\n syncInstalledVersion();\n }),\n ),\n EffectEx.runAndForwardErrors,\n );\n }, [manager, moduleUrl, catalogEntry, syncInstalledVersion]);\n\n const handleUpdate = useCallback(() => {\n if (!moduleUrl || !catalogEntry) {\n return;\n }\n\n setUpdating(true);\n void Effect.gen(function* () {\n yield* manager.remove(pluginId);\n const added = yield* manager.add(moduleUrl);\n yield* manager.enable(added.meta.profile.key);\n if (catalogEntry.release?.version) {\n UrlLoader.setInstalledVersion(added.meta.profile.key, catalogEntry.release.version);\n }\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n setUpdating(false);\n syncInstalledVersion();\n }),\n ),\n EffectEx.runAndForwardErrors,\n );\n }, [manager, pluginId, moduleUrl, catalogEntry, syncInstalledVersion]);\n\n const handleInstallVersion = useCallback(() => {\n const version = pickerVersions.find((candidate) => candidate.version === selectedVersionTag);\n if (!version || !version.moduleUrl) {\n return;\n }\n\n setInstalling(true);\n void Effect.gen(function* () {\n // Remove existing version before installing a different one.\n if (isInstalled) {\n yield* manager.remove(pluginId);\n }\n const added = yield* manager.add(version.moduleUrl);\n yield* manager.enable(added.meta.profile.key);\n UrlLoader.setInstalledVersion(added.meta.profile.key, version.version);\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n setInstalling(false);\n syncInstalledVersion();\n }),\n ),\n EffectEx.runAndForwardErrors,\n );\n }, [manager, pluginId, isInstalled, selectedVersionTag, pickerVersions, syncInstalledVersion]);\n\n return {\n installing,\n updating,\n handleEnableChange,\n handleUninstall,\n handleInstall,\n handleUpdate,\n handleInstallVersion,\n handleOpenSpec: hasSpecNode ? handleOpenSpec : undefined,\n };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport type * as Registry from '@dxos/app-framework/Registry';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\nimport * as EffectEx from '@dxos/effect/EffectEx';\n\n/**\n * Owns the version picker's state machine: fetches the available versions list\n * from the provider (served from the inlined `releases` array on each entry —\n * no separate endpoint needed) and keeps the selection clamped to whatever's\n * currently in the list so an external update doesn't strand the trigger on a\n * tag that's no longer available.\n */\nexport const useVersionPicker = ({\n provider,\n pluginId,\n moduleUrl,\n installedVersionTag,\n}: {\n provider: Registry.Manager;\n pluginId: string;\n moduleUrl: string | undefined;\n installedVersionTag: string | undefined;\n}): {\n pickerVersions: readonly Plugin.Release[];\n selectedVersionTag: string | undefined;\n setSelectedVersionTag: Dispatch<SetStateAction<string | undefined>>;\n} => {\n const [versions, setVersions] = useState<readonly Plugin.Release[]>([]);\n const [selectedVersionTag, setSelectedVersionTag] = useState<string | undefined>();\n\n // Load version list whenever pluginId changes. The `cancelled` flag guards\n // against a stale in-flight response overwriting newer state when the user\n // navigates between plugins quickly.\n useEffect(() => {\n setVersions([]);\n setSelectedVersionTag(undefined);\n if (!pluginId) {\n return;\n }\n let cancelled = false;\n void provider.listVersions(pluginId).pipe(\n Effect.match({\n onSuccess: (vs) => {\n if (cancelled) {\n return;\n }\n setVersions(vs);\n // Default selection: the currently installed version, or the latest.\n const installedVersion = UrlLoader.getInstalledVersion(pluginId);\n setSelectedVersionTag(installedVersion ?? vs[0]?.version);\n },\n onFailure: () => {\n if (cancelled) {\n return;\n }\n setVersions([]);\n setSelectedVersionTag(undefined);\n },\n }),\n EffectEx.runAndForwardErrors,\n );\n return () => {\n cancelled = true;\n };\n }, [provider, pluginId]);\n\n // Make sure the picker always lists the installed version, even if the catalog\n // hasn't surfaced it (the current `listVersions` stub only returns latest).\n const pickerVersions = useMemo<readonly Plugin.Release[]>(() => {\n if (!installedVersionTag) {\n return versions;\n }\n if (versions.some((entry) => entry.version === installedVersionTag)) {\n return versions;\n }\n const installedEntry: Plugin.Release = {\n version: installedVersionTag,\n // Picker won't be re-installing this entry unless the user selects + clicks Install,\n // and the catalog moduleUrl is the closest stand-in we have.\n moduleUrl: moduleUrl ?? '',\n };\n return [installedEntry, ...versions];\n }, [versions, installedVersionTag, moduleUrl]);\n\n // Keep `selectedVersionTag` in sync when picker contents change (e.g. the user\n // updated the plugin from the list view — installed version moved, picker\n // shrunk, prior selection is no longer in the list).\n useEffect(() => {\n if (pickerVersions.length === 0) {\n return;\n }\n if (selectedVersionTag && pickerVersions.some((entry) => entry.version === selectedVersionTag)) {\n return;\n }\n setSelectedVersionTag(installedVersionTag ?? pickerVersions[0]?.version);\n }, [pickerVersions, selectedVersionTag, installedVersionTag]);\n\n return { pickerVersions, selectedVersionTag, setSelectedVersionTag };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport React, { useCallback, useMemo } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as AppSettings from '@dxos/app-toolkit/AppSettings';\nimport * as ToolkitHooks from '@dxos/app-toolkit/Hooks';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\n\nimport { PluginDetail, PluginScope } from '#components';\n\nimport {\n useCatalogEntry,\n useInstalledVersionTag,\n usePluginActions,\n useRegistryPluginProvider,\n useRemotePluginIds,\n useVersionPicker,\n} from '../../hooks/index.ts';\nimport { getPluginPath } from '../../paths.ts';\n\nexport type PluginArticleProps = { subject: Plugin.Plugin };\n\nexport const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {\n const pluginId = plugin.meta.profile.key;\n const manager = PluginManagerProvider.usePluginManager();\n const plugins = useAtomValue(manager.plugins);\n const remotePluginIds = useRemotePluginIds();\n const provider = useRegistryPluginProvider();\n const { invokePromise } = Hooks.useOperationInvoker();\n\n const { catalogEntry, moduleUrl } = useCatalogEntry(pluginId);\n const { installedVersionTag, syncInstalledVersion } = useInstalledVersionTag(pluginId, plugins);\n const { pickerVersions, selectedVersionTag, setSelectedVersionTag } = useVersionPicker({\n provider,\n pluginId,\n moduleUrl,\n installedVersionTag,\n });\n\n const enabled = manager.getEnabled().includes(pluginId);\n const scope = ToolkitHooks.useSettingsKeyScope(AppSettings.PLUGINS_NAMESPACE, pluginId);\n const failed = useAtomValue(manager.failed);\n const failure = useMemo(() => failed.find((entry) => entry.id === pluginId), [failed, pluginId]);\n const isInstalled = useMemo(\n () => plugins.some((candidate) => candidate.meta.profile.key === pluginId),\n [plugins, pluginId],\n );\n const isCore = manager.getCore().includes(pluginId);\n\n const canUninstall = isInstalled && !isCore && remotePluginIds.has(pluginId);\n const hasUpdate =\n isInstalled && !!catalogEntry && !!installedVersionTag && installedVersionTag !== catalogEntry.release?.version;\n\n // Recompute graph slices whenever the plugin list changes, so installs /\n // removals through other surfaces (or this article's own actions) keep the\n // detail view in sync.\n const dependencies = useMemo(\n () => manager.getDependencies(pluginId, { transitive: false }),\n [manager, pluginId, plugins],\n );\n const dependents = useMemo(\n () => manager.getDependents(pluginId, { transitive: false }),\n [manager, pluginId, plugins],\n );\n\n // Resolves a plugin id to its display name. A plugin's translations are\n // only loaded when it is activated, so the always-available `meta.name`\n // from the registered plugin set is the right source for chip labels.\n const handleResolvePluginName = useCallback(\n (id: string): string => plugins.find((candidate) => candidate.meta.profile.key === id)?.meta.profile.name ?? id,\n [plugins],\n );\n\n const handleNavigateToPlugin = useCallback(\n (targetId: string) => {\n void invokePromise(LayoutOperation.Open, {\n subject: [getPluginPath(targetId)],\n pivotId: getPluginPath(pluginId),\n disposition: 'add',\n });\n },\n [invokePromise, pluginId],\n );\n\n const actions = usePluginActions({\n manager,\n pluginId,\n isInstalled,\n moduleUrl,\n catalogEntry,\n pickerVersions,\n selectedVersionTag,\n syncInstalledVersion,\n });\n\n return (\n <PluginDetail\n plugin={plugin}\n scope={\n scope.available ? <PluginScope synced={scope.synced} onPin={scope.pin} onUnpin={scope.unpin} /> : undefined\n }\n enabled={enabled}\n installing={actions.installing}\n updating={actions.updating}\n hasUpdate={hasUpdate}\n installedVersionTag={installedVersionTag}\n selectedVersionTag={selectedVersionTag}\n versions={pickerVersions}\n dependencies={dependencies}\n dependents={dependents}\n failure={failure}\n onOpenSpec={actions.handleOpenSpec}\n onEnabledChange={actions.handleEnableChange}\n onInstall={!isInstalled && moduleUrl ? actions.handleInstall : undefined}\n onInstallVersion={pickerVersions.length > 0 ? actions.handleInstallVersion : undefined}\n onNavigateToPlugin={handleNavigateToPlugin}\n onResolvePluginName={handleResolvePluginName}\n onUninstall={canUninstall ? actions.handleUninstall : undefined}\n onUpdate={hasUpdate ? actions.handleUpdate : undefined}\n onVersionChange={setSelectedVersionTag}\n />\n );\n};\n\nPluginArticle.displayName = 'PluginArticle';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chunk-PluginArticle.mjs","names":[],"sources":["../../src/hooks/useCatalogEntry.ts","../../src/hooks/useInstalledVersionTag.ts","../../src/hooks/usePluginActions.ts","../../src/hooks/useVersionPicker.ts","../../src/containers/PluginArticle/PluginArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { useMemo } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\n\nimport { useRegistryPlugins } from './useRegistryPlugins.ts';\n\n/**\n * Resolves the registry catalog entry for a plugin by id, plus the `moduleUrl`\n * the install/update actions need.\n */\nexport const useCatalogEntry = (\n pluginId: string,\n): {\n catalogEntry: Plugin.Meta | undefined;\n moduleUrl: string | undefined;\n} => {\n const { entries } = useRegistryPlugins();\n const catalogEntry = useMemo(() => entries.find((entry) => entry.profile.key === pluginId), [entries, pluginId]);\n return { catalogEntry, moduleUrl: catalogEntry?.release?.moduleUrl };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useCallback, useEffect, useState } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\n\n/**\n * Tracks the per-plugin installed version (sourced from `UrlLoader` localStorage)\n * as React state so post-install/update re-renders are deterministic — the value\n * lives in component state rather than being read inline on every render.\n *\n * - Initialised from localStorage at mount.\n * - Auto-resyncs when the plugin manager's plugin list changes (catches external\n * installs / removes triggered by the list view).\n * - `syncInstalledVersion()` is the explicit handle install/update operations\n * call inside their `Effect.ensuring` so the post-mutation render sees both\n * the new version and the cleared `installing`/`updating` flag together.\n */\nexport const useInstalledVersionTag = (\n pluginId: string,\n plugins: readonly Plugin.Plugin[],\n): {\n installedVersionTag: string | undefined;\n syncInstalledVersion: () => void;\n} => {\n const [installedVersionTag, setInstalledVersionTag] = useState<string | undefined>(() =>\n UrlLoader.getInstalledVersion(pluginId),\n );\n\n const syncInstalledVersion = useCallback(() => {\n setInstalledVersionTag(UrlLoader.getInstalledVersion(pluginId));\n }, [pluginId]);\n\n useEffect(() => {\n syncInstalledVersion();\n }, [plugins, syncInstalledVersion]);\n\n return { installedVersionTag, syncInstalledVersion };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport { useCallback, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport type * as PluginManager from '@dxos/app-framework/PluginManager';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\nimport * as ToolkitHooks from '@dxos/app-toolkit/Hooks';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport * as GraphHooks from '@dxos/plugin-graph/Hooks';\n\nimport { getPluginPath, getPluginSpecPath } from '../paths.ts';\nimport { useDisableConfirmation } from './useDisableConfirmation.ts';\n\nexport type PluginActionsProps = {\n manager: PluginManager.PluginManager;\n pluginId: string;\n isInstalled: boolean;\n moduleUrl: string | undefined;\n catalogEntry: Plugin.Meta | undefined;\n pickerVersions: readonly Plugin.Release[];\n selectedVersionTag: string | undefined;\n syncInstalledVersion: () => void;\n};\n\nexport type PluginActions = {\n installing: boolean;\n updating: boolean;\n handleEnableChange: (enabled: boolean) => void;\n handleUninstall: () => void;\n handleInstall: () => void;\n handleUpdate: () => void;\n handleInstallVersion: () => void;\n handleOpenSpec?: () => void;\n};\n\n/**\n * The user-facing actions the article exposes — enable/disable, uninstall,\n * install (latest), update (to latest catalog version), and install-from-picker —\n * plus the `installing`/`updating` flags they drive. Each handler ends with a\n * `syncInstalledVersion()` call inside its `Effect.ensuring` so the cached\n * installed version snaps to the freshly-written localStorage value in the same\n * render that clears the in-flight flag.\n *\n * `handleOpenSpec` is included only when a `spec` child graph node exists for\n * this plugin — contributed by whichever plugin can render MDL (today\n * `plugin-code`). When that contributor isn't enabled, the action is absent\n * and the detail view hides its \"Open Specification\" chip.\n */\nexport const usePluginActions = ({\n manager,\n pluginId,\n isInstalled,\n moduleUrl,\n catalogEntry,\n pickerVersions,\n selectedVersionTag,\n syncInstalledVersion,\n}: PluginActionsProps): PluginActions => {\n const { invokePromise } = Hooks.useOperationInvoker();\n const { graph } = ToolkitHooks.useAppGraph();\n\n const [installing, setInstalling] = useState(false);\n const [updating, setUpdating] = useState(false);\n const requestDisable = useDisableConfirmation(\n manager,\n (id) => void EffectEx.runAndForwardErrors(manager.disable(id)),\n );\n\n const specPath = getPluginSpecPath(pluginId);\n const hasSpecNode = !!GraphHooks.useNode(graph, specPath);\n const handleOpenSpec = useCallback(() => {\n void invokePromise(LayoutOperation.Open, {\n subject: [specPath],\n pivotId: getPluginPath(pluginId),\n disposition: 'detail',\n });\n }, [invokePromise, specPath, pluginId]);\n\n const handleEnableChange = useCallback(\n (enabled: boolean) => {\n if (enabled) {\n void EffectEx.runAndForwardErrors(manager.enable(pluginId));\n return;\n }\n\n requestDisable(pluginId);\n },\n [manager, pluginId, requestDisable],\n );\n\n const handleUninstall = useCallback(() => {\n void EffectEx.runAndForwardErrors(manager.remove(pluginId));\n }, [manager, pluginId]);\n\n const handleInstall = useCallback(() => {\n if (!moduleUrl) {\n return;\n }\n\n setInstalling(true);\n void Effect.gen(function* () {\n const added = yield* manager.add(moduleUrl);\n yield* manager.enable(added.meta.profile.key);\n if (catalogEntry?.release?.version) {\n UrlLoader.setInstalledVersion(added.meta.profile.key, catalogEntry.release.version);\n }\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n setInstalling(false);\n syncInstalledVersion();\n }),\n ),\n EffectEx.runAndForwardErrors,\n );\n }, [manager, moduleUrl, catalogEntry, syncInstalledVersion]);\n\n const handleUpdate = useCallback(() => {\n if (!moduleUrl || !catalogEntry) {\n return;\n }\n\n setUpdating(true);\n void Effect.gen(function* () {\n yield* manager.remove(pluginId);\n const added = yield* manager.add(moduleUrl);\n yield* manager.enable(added.meta.profile.key);\n if (catalogEntry.release?.version) {\n UrlLoader.setInstalledVersion(added.meta.profile.key, catalogEntry.release.version);\n }\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n setUpdating(false);\n syncInstalledVersion();\n }),\n ),\n EffectEx.runAndForwardErrors,\n );\n }, [manager, pluginId, moduleUrl, catalogEntry, syncInstalledVersion]);\n\n const handleInstallVersion = useCallback(() => {\n const version = pickerVersions.find((candidate) => candidate.version === selectedVersionTag);\n if (!version || !version.moduleUrl) {\n return;\n }\n\n setInstalling(true);\n void Effect.gen(function* () {\n // Remove existing version before installing a different one.\n if (isInstalled) {\n yield* manager.remove(pluginId);\n }\n const added = yield* manager.add(version.moduleUrl);\n yield* manager.enable(added.meta.profile.key);\n UrlLoader.setInstalledVersion(added.meta.profile.key, version.version);\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n setInstalling(false);\n syncInstalledVersion();\n }),\n ),\n EffectEx.runAndForwardErrors,\n );\n }, [manager, pluginId, isInstalled, selectedVersionTag, pickerVersions, syncInstalledVersion]);\n\n return {\n installing,\n updating,\n handleEnableChange,\n handleUninstall,\n handleInstall,\n handleUpdate,\n handleInstallVersion,\n handleOpenSpec: hasSpecNode ? handleOpenSpec : undefined,\n };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport type * as Registry from '@dxos/app-framework/Registry';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\nimport * as EffectEx from '@dxos/effect/EffectEx';\n\n/**\n * Owns the version picker's state machine: fetches the available versions list\n * from the provider (served from the inlined `releases` array on each entry —\n * no separate endpoint needed) and keeps the selection clamped to whatever's\n * currently in the list so an external update doesn't strand the trigger on a\n * tag that's no longer available.\n */\nexport const useVersionPicker = ({\n provider,\n pluginId,\n moduleUrl,\n installedVersionTag,\n}: {\n provider: Registry.Manager;\n pluginId: string;\n moduleUrl: string | undefined;\n installedVersionTag: string | undefined;\n}): {\n pickerVersions: readonly Plugin.Release[];\n selectedVersionTag: string | undefined;\n setSelectedVersionTag: Dispatch<SetStateAction<string | undefined>>;\n} => {\n const [versions, setVersions] = useState<readonly Plugin.Release[]>([]);\n const [selectedVersionTag, setSelectedVersionTag] = useState<string | undefined>();\n\n // Load version list whenever pluginId changes. The `cancelled` flag guards\n // against a stale in-flight response overwriting newer state when the user\n // navigates between plugins quickly.\n useEffect(() => {\n setVersions([]);\n setSelectedVersionTag(undefined);\n if (!pluginId) {\n return;\n }\n let cancelled = false;\n void provider.listVersions(pluginId).pipe(\n Effect.match({\n onSuccess: (vs) => {\n if (cancelled) {\n return;\n }\n setVersions(vs);\n // Default selection: the currently installed version, or the latest.\n const installedVersion = UrlLoader.getInstalledVersion(pluginId);\n setSelectedVersionTag(installedVersion ?? vs[0]?.version);\n },\n onFailure: () => {\n if (cancelled) {\n return;\n }\n setVersions([]);\n setSelectedVersionTag(undefined);\n },\n }),\n EffectEx.runAndForwardErrors,\n );\n return () => {\n cancelled = true;\n };\n }, [provider, pluginId]);\n\n // Make sure the picker always lists the installed version, even if the catalog\n // hasn't surfaced it (the current `listVersions` stub only returns latest).\n const pickerVersions = useMemo<readonly Plugin.Release[]>(() => {\n if (!installedVersionTag) {\n return versions;\n }\n if (versions.some((entry) => entry.version === installedVersionTag)) {\n return versions;\n }\n const installedEntry: Plugin.Release = {\n version: installedVersionTag,\n // Picker won't be re-installing this entry unless the user selects + clicks Install,\n // and the catalog moduleUrl is the closest stand-in we have.\n moduleUrl: moduleUrl ?? '',\n };\n return [installedEntry, ...versions];\n }, [versions, installedVersionTag, moduleUrl]);\n\n // Keep `selectedVersionTag` in sync when picker contents change (e.g. the user\n // updated the plugin from the list view — installed version moved, picker\n // shrunk, prior selection is no longer in the list).\n useEffect(() => {\n if (pickerVersions.length === 0) {\n return;\n }\n if (selectedVersionTag && pickerVersions.some((entry) => entry.version === selectedVersionTag)) {\n return;\n }\n setSelectedVersionTag(installedVersionTag ?? pickerVersions[0]?.version);\n }, [pickerVersions, selectedVersionTag, installedVersionTag]);\n\n return { pickerVersions, selectedVersionTag, setSelectedVersionTag };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport React, { useCallback, useMemo } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as AppSettings from '@dxos/app-toolkit/AppSettings';\nimport * as ToolkitHooks from '@dxos/app-toolkit/Hooks';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\n\nimport { PluginDetail, PluginScope } from '#components';\n\nimport {\n useCatalogEntry,\n useInstalledVersionTag,\n usePluginActions,\n useRegistryPluginProvider,\n useRemotePluginIds,\n useVersionPicker,\n} from '../../hooks/index.ts';\nimport { getPluginPath } from '../../paths.ts';\n\nexport type PluginArticleProps = { subject: Plugin.Plugin };\n\nexport const PluginArticle = ({ subject: plugin }: PluginArticleProps) => {\n const pluginId = plugin.meta.profile.key;\n const manager = PluginManagerProvider.usePluginManager();\n const plugins = useAtomValue(manager.plugins);\n const remotePluginIds = useRemotePluginIds();\n const provider = useRegistryPluginProvider();\n const { invokePromise } = Hooks.useOperationInvoker();\n\n const { catalogEntry, moduleUrl } = useCatalogEntry(pluginId);\n const { installedVersionTag, syncInstalledVersion } = useInstalledVersionTag(pluginId, plugins);\n const { pickerVersions, selectedVersionTag, setSelectedVersionTag } = useVersionPicker({\n provider,\n pluginId,\n moduleUrl,\n installedVersionTag,\n });\n\n const enabled = manager.getEnabled().includes(pluginId);\n const scope = ToolkitHooks.useSettingsKeyScope(AppSettings.PLUGINS_NAMESPACE, pluginId);\n const failed = useAtomValue(manager.failed);\n const failure = useMemo(() => failed.find((entry) => entry.id === pluginId), [failed, pluginId]);\n const isInstalled = useMemo(\n () => plugins.some((candidate) => candidate.meta.profile.key === pluginId),\n [plugins, pluginId],\n );\n const isCore = manager.getCore().includes(pluginId);\n\n const canUninstall = isInstalled && !isCore && remotePluginIds.has(pluginId);\n const hasUpdate =\n isInstalled && !!catalogEntry && !!installedVersionTag && installedVersionTag !== catalogEntry.release?.version;\n\n // Recompute graph slices whenever the plugin list changes, so installs /\n // removals through other surfaces (or this article's own actions) keep the\n // detail view in sync.\n const dependencies = useMemo(\n () => manager.getDependencies(pluginId, { transitive: false }),\n [manager, pluginId, plugins],\n );\n const dependents = useMemo(\n () => manager.getDependents(pluginId, { transitive: false }),\n [manager, pluginId, plugins],\n );\n\n // Resolves a plugin id to its display name. A plugin's translations are\n // only loaded when it is activated, so the always-available `meta.name`\n // from the registered plugin set is the right source for chip labels.\n const handleResolvePluginName = useCallback(\n (id: string): string => plugins.find((candidate) => candidate.meta.profile.key === id)?.meta.profile.name ?? id,\n [plugins],\n );\n\n const handleNavigateToPlugin = useCallback(\n (targetId: string) => {\n void invokePromise(LayoutOperation.Open, {\n subject: [getPluginPath(targetId)],\n pivotId: getPluginPath(pluginId),\n disposition: 'detail',\n });\n },\n [invokePromise, pluginId],\n );\n\n const actions = usePluginActions({\n manager,\n pluginId,\n isInstalled,\n moduleUrl,\n catalogEntry,\n pickerVersions,\n selectedVersionTag,\n syncInstalledVersion,\n });\n\n return (\n <PluginDetail\n plugin={plugin}\n scope={\n scope.available ? <PluginScope synced={scope.synced} onPin={scope.pin} onUnpin={scope.unpin} /> : undefined\n }\n enabled={enabled}\n installing={actions.installing}\n updating={actions.updating}\n hasUpdate={hasUpdate}\n installedVersionTag={installedVersionTag}\n selectedVersionTag={selectedVersionTag}\n versions={pickerVersions}\n dependencies={dependencies}\n dependents={dependents}\n failure={failure}\n onOpenSpec={actions.handleOpenSpec}\n onEnabledChange={actions.handleEnableChange}\n onInstall={!isInstalled && moduleUrl ? actions.handleInstall : undefined}\n onInstallVersion={pickerVersions.length > 0 ? actions.handleInstallVersion : undefined}\n onNavigateToPlugin={handleNavigateToPlugin}\n onResolvePluginName={handleResolvePluginName}\n onUninstall={canUninstall ? actions.handleUninstall : undefined}\n onUpdate={hasUpdate ? actions.handleUpdate : undefined}\n onVersionChange={setSelectedVersionTag}\n />\n );\n};\n\nPluginArticle.displayName = 'PluginArticle';\n"],"mappings":"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"}
|
|
@@ -64,7 +64,7 @@ var toDisplayPlugin = (plugin) => ({
|
|
|
64
64
|
}),
|
|
65
65
|
modules: []
|
|
66
66
|
});
|
|
67
|
-
var PublicRegistryArticle = Util.composable(({
|
|
67
|
+
var PublicRegistryArticle = Util.composable(({ contextId, ...props }, forwardedRef) => {
|
|
68
68
|
const { t } = UiHooks.useTranslation(meta.profile.key);
|
|
69
69
|
const manager = PluginManagerProvider.usePluginManager();
|
|
70
70
|
const { invoke } = Hooks.useOperationInvoker();
|
|
@@ -163,7 +163,7 @@ var PublicRegistryArticle = Util.composable(({ id, ...props }, forwardedRef) =>
|
|
|
163
163
|
});
|
|
164
164
|
return /* @__PURE__ */ jsx(BaseRegistryArticle, {
|
|
165
165
|
...props,
|
|
166
|
-
|
|
166
|
+
contextId,
|
|
167
167
|
source: "registry",
|
|
168
168
|
plugins: items,
|
|
169
169
|
installed: installedIds,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-PublicRegistryArticle.mjs","names":[],"sources":["../../src/hooks/useUpdateAvailable.ts","../../src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\n\n/**\n * Returns a set of plugin ids for which the registry has a newer version\n * than what was recorded at install time.\n *\n * Only flags plugins where a version was recorded at install time AND it differs\n * from the catalog's current version. Legacy installs (no version stored) are ignored.\n *\n * NOTE: this is intentionally NOT memoised. The installed-version side of the\n * comparison lives in localStorage and is mutated by `manager.add` /\n * `manager.remove` / `UrlLoader.setInstalledVersion` outside React's view, so a\n * memo keyed on `entries` would happily return stale data for the rest of the\n * session after an update completes (the bug that produced \"Update button\n * reappears after Updating…\"). The loop is O(catalog size) sync reads — cheap\n * enough to do every render.\n */\nexport const useUpdateAvailableIds = (entries: readonly Plugin.Meta[]): ReadonlySet<string> => {\n const ids = new Set<string>();\n for (const entry of entries) {\n const installedVersion = UrlLoader.getInstalledVersion(entry.profile.key);\n if (installedVersion !== undefined && installedVersion !== entry.release?.version) {\n ids.add(entry.profile.key);\n }\n }\n return ids;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Effect from 'effect/Effect';\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport * as Plugin from '@dxos/app-framework/Plugin';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\nimport * as AppSettings from '@dxos/app-toolkit/AppSettings';\nimport * as ToolkitHooks from '@dxos/app-toolkit/Hooks';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { DXN } from '@dxos/keys';\nimport * as ObservabilityOperation from '@dxos/plugin-observability/ObservabilityOperation';\nimport * as UiHooks from '@dxos/react-ui/Hooks';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { meta } from '#meta';\n\nimport { useAutoTags, useRegistryPlugins, useUpdateAvailableIds } from '../../hooks/index.ts';\nimport { BaseRegistryArticle } from '../BaseRegistryArticle/index.ts';\n\nconst sortEntries = (a: Plugin.Meta, b: Plugin.Meta) =>\n (a.profile.name ?? a.profile.key).localeCompare(b.profile.name ?? b.profile.key);\n\nconst sortPlugins = (a: Plugin.Plugin, b: Plugin.Plugin) =>\n (a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);\n\n/**\n * Turns a registry catalog entry into a minimal Plugin object so we can reuse\n * {@link BaseRegistryArticle} for rendering. The synthesized plugin has no\n * modules — it exists only for display until the user installs it.\n */\nconst toDisplayPlugin = (plugin: Plugin.Meta): Plugin.Plugin =>\n ({\n [Plugin.PluginTypeId]: Plugin.PluginTypeId,\n meta: Plugin.makeMeta({\n key: DXN.make(plugin.profile.key),\n name: plugin.profile.name,\n description: plugin.profile.description,\n homePage: plugin.profile.homePage,\n source: plugin.profile.source,\n screenshots: plugin.profile.screenshots,\n tags: plugin.profile.tags,\n icon: plugin.profile.icon,\n author: plugin.profile.author,\n }),\n modules: [],\n }) as Plugin.Plugin;\n\nexport type PublicRegistryArticleProps = {\n id: string;\n};\n\nexport const PublicRegistryArticle = Util.composable<HTMLDivElement, PublicRegistryArticleProps>(\n ({ id, ...props }, forwardedRef) => {\n const { t } = UiHooks.useTranslation(meta.profile.key);\n const manager = PluginManagerProvider.usePluginManager();\n const { invoke } = Hooks.useOperationInvoker();\n const { entries, loading, error } = useRegistryPlugins();\n // Reloaded on every visit, so a plugin published since boot (a private one in particular) shows up.\n useEffect(() => manager.pluginRegistry.refresh(), [manager]);\n const plugins = useAtomValue(manager.plugins);\n const installedIds = useMemo(() => plugins.map((plugin) => plugin.meta.profile.key), [plugins]);\n const extraTagsById = useAutoTags(entries);\n const deviceOnlyIds = ToolkitHooks.useSettingsDivergedKeys(AppSettings.PLUGINS_NAMESPACE);\n\n // Snapshot of installed plugin ids at mount time. Used to sort installed\n // plugins to the top without having newly-installed rows jump up mid-session.\n const [installedSnapshot] = useState<ReadonlySet<string>>(\n () => new Set(manager.getPlugins().map((plugin) => plugin.meta.profile.key)),\n );\n\n const [installingIds, setInstallingIds] = useState<readonly string[]>([]);\n const [updatingIds, setUpdatingIds] = useState<readonly string[]>([]);\n const updateAvailableIds = useUpdateAvailableIds(entries);\n\n const sortedEntries = useMemo(() => [...entries].sort(sortEntries), [entries]);\n const moduleUrlById = useMemo(() => {\n const map: Record<string, string> = {};\n for (const entry of sortedEntries) {\n if (entry.release?.moduleUrl) {\n map[entry.profile.key] = entry.release.moduleUrl;\n }\n }\n return map;\n }, [sortedEntries]);\n\n // Maps plugin id → current registry version (i.e. plugin.version from the catalog).\n const versionById = useMemo(() => {\n const map: Record<string, string> = {};\n for (const entry of sortedEntries) {\n if (entry.release?.version) {\n map[entry.profile.key] = entry.release.version;\n }\n }\n return map;\n }, [sortedEntries]);\n\n // Single list with installed-at-mount plugins sorted to the top. Sort order\n // is based on the mount-time snapshot so rows don't jump as the user\n // installs plugins during the session.\n const items = useMemo(() => {\n const catalogIds = new Set(sortedEntries.map((entry) => entry.profile.key));\n const remoteIds = new Set(UrlLoader.getRemoteEntries().map((entry) => entry.id));\n const fromCatalog = sortedEntries.map(toDisplayPlugin);\n const fromUrlOnly = plugins.filter(\n (plugin) => remoteIds.has(plugin.meta.profile.key) && !catalogIds.has(plugin.meta.profile.key),\n );\n const installedFirst = [...fromCatalog, ...fromUrlOnly].sort((a, b) => {\n const aInstalled = installedSnapshot.has(a.meta.profile.key);\n const bInstalled = installedSnapshot.has(b.meta.profile.key);\n if (aInstalled !== bInstalled) {\n return aInstalled ? -1 : 1;\n }\n return sortPlugins(a, b);\n });\n return installedFirst;\n }, [sortedEntries, installedSnapshot, plugins]);\n\n const handleInstall = useCallback(\n (pluginId: string) => {\n const moduleUrl = moduleUrlById[pluginId];\n const version = versionById[pluginId];\n if (!moduleUrl) {\n return;\n }\n setInstallingIds((prev) => (prev.includes(pluginId) ? prev : [...prev, pluginId]));\n void Effect.gen(function* () {\n const plugin = yield* manager.add(moduleUrl);\n yield* manager.enable(plugin.meta.profile.key);\n // Persist the installed version for future update detection.\n if (version) {\n UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);\n }\n yield* invoke(ObservabilityOperation.SendEvent, {\n name: 'plugins.install',\n properties: { plugin: plugin.meta.profile.key, source: 'registry' },\n });\n }).pipe(\n Effect.ensuring(Effect.sync(() => setInstallingIds((prev) => prev.filter((pid) => pid !== pluginId)))),\n EffectEx.runAndForwardErrors,\n );\n },\n [invoke, manager, moduleUrlById, versionById],\n );\n\n const handleUpdate = useCallback(\n (pluginId: string) => {\n const moduleUrl = moduleUrlById[pluginId];\n const version = versionById[pluginId];\n if (!moduleUrl) {\n return;\n }\n setUpdatingIds((prev) => (prev.includes(pluginId) ? prev : [...prev, pluginId]));\n void Effect.gen(function* () {\n // Unload the old version then re-install from the new URL.\n yield* manager.remove(pluginId);\n const plugin = yield* manager.add(moduleUrl);\n yield* manager.enable(plugin.meta.profile.key);\n if (version) {\n UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);\n }\n yield* invoke(ObservabilityOperation.SendEvent, {\n name: 'plugins.update',\n properties: { plugin: plugin.meta.profile.key, source: 'registry' },\n });\n }).pipe(\n Effect.ensuring(Effect.sync(() => setUpdatingIds((prev) => prev.filter((pid) => pid !== pluginId)))),\n EffectEx.runAndForwardErrors,\n );\n },\n [invoke, manager, moduleUrlById, versionById],\n );\n\n const empty = error ? (\n <div className='p-4 text-fg-muted'>{t('registry.error.label', { message: error.message })}</div>\n ) : loading ? (\n <div className='p-4 text-fg-muted'>{t('registry.loading.label')}</div>\n ) : (\n <div className='p-4 text-fg-muted'>{t('registry.empty.label')}</div>\n );\n\n return (\n <BaseRegistryArticle\n {...props}\n id={id}\n source='registry'\n plugins={items}\n installed={installedIds}\n installing={installingIds}\n updating={updatingIds}\n updateAvailableIds={updateAvailableIds}\n extraTagsById={extraTagsById}\n deviceOnlyIds={deviceOnlyIds}\n onInstall={handleInstall}\n onUpdate={handleUpdate}\n empty={empty}\n ref={forwardedRef}\n />\n );\n },\n);\n\nPublicRegistryArticle.displayName = 'PublicRegistryArticle';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chunk-PublicRegistryArticle.mjs","names":[],"sources":["../../src/hooks/useUpdateAvailable.ts","../../src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\n\n/**\n * Returns a set of plugin ids for which the registry has a newer version\n * than what was recorded at install time.\n *\n * Only flags plugins where a version was recorded at install time AND it differs\n * from the catalog's current version. Legacy installs (no version stored) are ignored.\n *\n * NOTE: this is intentionally NOT memoised. The installed-version side of the\n * comparison lives in localStorage and is mutated by `manager.add` /\n * `manager.remove` / `UrlLoader.setInstalledVersion` outside React's view, so a\n * memo keyed on `entries` would happily return stale data for the rest of the\n * session after an update completes (the bug that produced \"Update button\n * reappears after Updating…\"). The loop is O(catalog size) sync reads — cheap\n * enough to do every render.\n */\nexport const useUpdateAvailableIds = (entries: readonly Plugin.Meta[]): ReadonlySet<string> => {\n const ids = new Set<string>();\n for (const entry of entries) {\n const installedVersion = UrlLoader.getInstalledVersion(entry.profile.key);\n if (installedVersion !== undefined && installedVersion !== entry.release?.version) {\n ids.add(entry.profile.key);\n }\n }\n return ids;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Effect from 'effect/Effect';\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport * as Plugin from '@dxos/app-framework/Plugin';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as UrlLoader from '@dxos/app-framework/UrlLoader';\nimport * as AppSettings from '@dxos/app-toolkit/AppSettings';\nimport * as ToolkitHooks from '@dxos/app-toolkit/Hooks';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { DXN } from '@dxos/keys';\nimport * as ObservabilityOperation from '@dxos/plugin-observability/ObservabilityOperation';\nimport * as UiHooks from '@dxos/react-ui/Hooks';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { meta } from '#meta';\n\nimport { useAutoTags, useRegistryPlugins, useUpdateAvailableIds } from '../../hooks/index.ts';\nimport { BaseRegistryArticle } from '../BaseRegistryArticle/index.ts';\n\nconst sortEntries = (a: Plugin.Meta, b: Plugin.Meta) =>\n (a.profile.name ?? a.profile.key).localeCompare(b.profile.name ?? b.profile.key);\n\nconst sortPlugins = (a: Plugin.Plugin, b: Plugin.Plugin) =>\n (a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);\n\n/**\n * Turns a registry catalog entry into a minimal Plugin object so we can reuse\n * {@link BaseRegistryArticle} for rendering. The synthesized plugin has no\n * modules — it exists only for display until the user installs it.\n */\nconst toDisplayPlugin = (plugin: Plugin.Meta): Plugin.Plugin =>\n ({\n [Plugin.PluginTypeId]: Plugin.PluginTypeId,\n meta: Plugin.makeMeta({\n key: DXN.make(plugin.profile.key),\n name: plugin.profile.name,\n description: plugin.profile.description,\n homePage: plugin.profile.homePage,\n source: plugin.profile.source,\n screenshots: plugin.profile.screenshots,\n tags: plugin.profile.tags,\n icon: plugin.profile.icon,\n author: plugin.profile.author,\n }),\n modules: [],\n }) as Plugin.Plugin;\n\nexport type PublicRegistryArticleProps = {\n contextId: string;\n};\n\nexport const PublicRegistryArticle = Util.composable<HTMLDivElement, PublicRegistryArticleProps>(\n ({ contextId, ...props }, forwardedRef) => {\n const { t } = UiHooks.useTranslation(meta.profile.key);\n const manager = PluginManagerProvider.usePluginManager();\n const { invoke } = Hooks.useOperationInvoker();\n const { entries, loading, error } = useRegistryPlugins();\n // Reloaded on every visit, so a plugin published since boot (a private one in particular) shows up.\n useEffect(() => manager.pluginRegistry.refresh(), [manager]);\n const plugins = useAtomValue(manager.plugins);\n const installedIds = useMemo(() => plugins.map((plugin) => plugin.meta.profile.key), [plugins]);\n const extraTagsById = useAutoTags(entries);\n const deviceOnlyIds = ToolkitHooks.useSettingsDivergedKeys(AppSettings.PLUGINS_NAMESPACE);\n\n // Snapshot of installed plugin ids at mount time. Used to sort installed\n // plugins to the top without having newly-installed rows jump up mid-session.\n const [installedSnapshot] = useState<ReadonlySet<string>>(\n () => new Set(manager.getPlugins().map((plugin) => plugin.meta.profile.key)),\n );\n\n const [installingIds, setInstallingIds] = useState<readonly string[]>([]);\n const [updatingIds, setUpdatingIds] = useState<readonly string[]>([]);\n const updateAvailableIds = useUpdateAvailableIds(entries);\n\n const sortedEntries = useMemo(() => [...entries].sort(sortEntries), [entries]);\n const moduleUrlById = useMemo(() => {\n const map: Record<string, string> = {};\n for (const entry of sortedEntries) {\n if (entry.release?.moduleUrl) {\n map[entry.profile.key] = entry.release.moduleUrl;\n }\n }\n return map;\n }, [sortedEntries]);\n\n // Maps plugin id → current registry version (i.e. plugin.version from the catalog).\n const versionById = useMemo(() => {\n const map: Record<string, string> = {};\n for (const entry of sortedEntries) {\n if (entry.release?.version) {\n map[entry.profile.key] = entry.release.version;\n }\n }\n return map;\n }, [sortedEntries]);\n\n // Single list with installed-at-mount plugins sorted to the top. Sort order\n // is based on the mount-time snapshot so rows don't jump as the user\n // installs plugins during the session.\n const items = useMemo(() => {\n const catalogIds = new Set(sortedEntries.map((entry) => entry.profile.key));\n const remoteIds = new Set(UrlLoader.getRemoteEntries().map((entry) => entry.id));\n const fromCatalog = sortedEntries.map(toDisplayPlugin);\n const fromUrlOnly = plugins.filter(\n (plugin) => remoteIds.has(plugin.meta.profile.key) && !catalogIds.has(plugin.meta.profile.key),\n );\n const installedFirst = [...fromCatalog, ...fromUrlOnly].sort((a, b) => {\n const aInstalled = installedSnapshot.has(a.meta.profile.key);\n const bInstalled = installedSnapshot.has(b.meta.profile.key);\n if (aInstalled !== bInstalled) {\n return aInstalled ? -1 : 1;\n }\n return sortPlugins(a, b);\n });\n return installedFirst;\n }, [sortedEntries, installedSnapshot, plugins]);\n\n const handleInstall = useCallback(\n (pluginId: string) => {\n const moduleUrl = moduleUrlById[pluginId];\n const version = versionById[pluginId];\n if (!moduleUrl) {\n return;\n }\n setInstallingIds((prev) => (prev.includes(pluginId) ? prev : [...prev, pluginId]));\n void Effect.gen(function* () {\n const plugin = yield* manager.add(moduleUrl);\n yield* manager.enable(plugin.meta.profile.key);\n // Persist the installed version for future update detection.\n if (version) {\n UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);\n }\n yield* invoke(ObservabilityOperation.SendEvent, {\n name: 'plugins.install',\n properties: { plugin: plugin.meta.profile.key, source: 'registry' },\n });\n }).pipe(\n Effect.ensuring(Effect.sync(() => setInstallingIds((prev) => prev.filter((pid) => pid !== pluginId)))),\n EffectEx.runAndForwardErrors,\n );\n },\n [invoke, manager, moduleUrlById, versionById],\n );\n\n const handleUpdate = useCallback(\n (pluginId: string) => {\n const moduleUrl = moduleUrlById[pluginId];\n const version = versionById[pluginId];\n if (!moduleUrl) {\n return;\n }\n setUpdatingIds((prev) => (prev.includes(pluginId) ? prev : [...prev, pluginId]));\n void Effect.gen(function* () {\n // Unload the old version then re-install from the new URL.\n yield* manager.remove(pluginId);\n const plugin = yield* manager.add(moduleUrl);\n yield* manager.enable(plugin.meta.profile.key);\n if (version) {\n UrlLoader.setInstalledVersion(plugin.meta.profile.key, version);\n }\n yield* invoke(ObservabilityOperation.SendEvent, {\n name: 'plugins.update',\n properties: { plugin: plugin.meta.profile.key, source: 'registry' },\n });\n }).pipe(\n Effect.ensuring(Effect.sync(() => setUpdatingIds((prev) => prev.filter((pid) => pid !== pluginId)))),\n EffectEx.runAndForwardErrors,\n );\n },\n [invoke, manager, moduleUrlById, versionById],\n );\n\n const empty = error ? (\n <div className='p-4 text-fg-muted'>{t('registry.error.label', { message: error.message })}</div>\n ) : loading ? (\n <div className='p-4 text-fg-muted'>{t('registry.loading.label')}</div>\n ) : (\n <div className='p-4 text-fg-muted'>{t('registry.empty.label')}</div>\n );\n\n return (\n <BaseRegistryArticle\n {...props}\n contextId={contextId}\n source='registry'\n plugins={items}\n installed={installedIds}\n installing={installingIds}\n updating={updatingIds}\n updateAvailableIds={updateAvailableIds}\n extraTagsById={extraTagsById}\n deviceOnlyIds={deviceOnlyIds}\n onInstall={handleInstall}\n onUpdate={handleUpdate}\n empty={empty}\n ref={forwardedRef}\n />\n );\n },\n);\n\nPublicRegistryArticle.displayName = 'PublicRegistryArticle';\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import * as PluginManagerProvider from "@dxos/app-framework/PluginManagerProvide
|
|
|
6
6
|
import { useAtomValue } from "@effect/atom-react/Hooks";
|
|
7
7
|
//#region src/containers/RegistryArticle/RegistryArticle.tsx
|
|
8
8
|
var sortByPluginMeta = (a, b) => (a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);
|
|
9
|
-
var RegistryArticle = Util.composable(({
|
|
9
|
+
var RegistryArticle = Util.composable(({ contextId, plugins: pluginsProp, extraTagsById, ...props }, forwardedRef) => {
|
|
10
10
|
const manager = PluginManagerProvider.usePluginManager();
|
|
11
11
|
const failed = useAtomValue(manager.failed);
|
|
12
12
|
const plugins = useMemo(() => [...pluginsProp].sort(sortByPluginMeta), [pluginsProp]);
|
|
@@ -16,7 +16,7 @@ var RegistryArticle = Util.composable(({ id, plugins: pluginsProp, extraTagsById
|
|
|
16
16
|
}, {}), [failed]);
|
|
17
17
|
return /* @__PURE__ */ jsx(BaseRegistryArticle, {
|
|
18
18
|
...props,
|
|
19
|
-
|
|
19
|
+
contextId,
|
|
20
20
|
plugins,
|
|
21
21
|
extraTagsById,
|
|
22
22
|
failuresById,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-RegistryArticle.mjs","names":[],"sources":["../../src/containers/RegistryArticle/RegistryArticle.tsx"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport React, { useMemo } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport type * as PluginManager from '@dxos/app-framework/PluginManager';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { BaseRegistryArticle } from '../BaseRegistryArticle/index.ts';\n\nconst sortByPluginMeta = (a: Plugin.Plugin, b: Plugin.Plugin) =>\n (a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);\n\nexport type RegistryArticleProps = {\n
|
|
1
|
+
{"version":3,"file":"chunk-RegistryArticle.mjs","names":[],"sources":["../../src/containers/RegistryArticle/RegistryArticle.tsx"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport React, { useMemo } from 'react';\n\nimport type * as Plugin from '@dxos/app-framework/Plugin';\nimport type * as PluginManager from '@dxos/app-framework/PluginManager';\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as Util from '@dxos/react-ui/Util';\n\nimport { BaseRegistryArticle } from '../BaseRegistryArticle/index.ts';\n\nconst sortByPluginMeta = (a: Plugin.Plugin, b: Plugin.Plugin) =>\n (a.meta.profile.name ?? a.meta.profile.key).localeCompare(b.meta.profile.name ?? b.meta.profile.key);\n\nexport type RegistryArticleProps = {\n contextId: string;\n plugins: Plugin.Plugin[];\n /**\n * Map from plugin id → display-only tags (e.g. `registry`, `local`) computed by the caller.\n */\n extraTagsById?: Record<string, readonly string[]>;\n};\n\nexport const RegistryArticle = Util.composable<HTMLDivElement, RegistryArticleProps>(\n ({ contextId, plugins: pluginsProp, extraTagsById, ...props }, forwardedRef) => {\n const manager = PluginManagerProvider.usePluginManager();\n const failed = useAtomValue(manager.failed);\n const plugins = useMemo(() => [...pluginsProp].sort(sortByPluginMeta), [pluginsProp]);\n const failuresById = useMemo(\n () =>\n failed.reduce<Record<string, PluginManager.PluginFailure>>((acc, failure) => {\n acc[failure.id] = failure;\n return acc;\n }, {}),\n [failed],\n );\n\n return (\n <BaseRegistryArticle\n {...props}\n contextId={contextId}\n plugins={plugins}\n extraTagsById={extraTagsById}\n failuresById={failuresById}\n ref={forwardedRef}\n />\n );\n },\n);\n\nRegistryArticle.displayName = 'RegistryArticle';\n"],"mappings":";;;;;;;AAcA,IAAM,oBAAoB,GAAkB,OACzC,EAAE,KAAK,QAAQ,QAAQ,EAAE,KAAK,QAAQ,IAAA,CAAK,cAAc,EAAE,KAAK,QAAQ,QAAQ,EAAE,KAAK,QAAQ,GAAG;AAWrG,IAAa,kBAAkB,KAAK,YACjC,EAAE,WAAW,SAAS,aAAa,eAAe,GAAG,SAAS,iBAAiB;CAC9E,MAAM,UAAU,sBAAsB,iBAAiB;CACvD,MAAM,SAAS,aAAa,QAAQ,MAAM;CAC1C,MAAM,UAAU,cAAc,CAAC,GAAG,WAAW,CAAC,CAAC,KAAK,gBAAgB,GAAG,CAAC,WAAW,CAAC;CACpF,MAAM,eAAe,cAEjB,OAAO,QAAqD,KAAK,YAAY;EAC3E,IAAI,QAAQ,MAAM;EAClB,OAAO;CACT,GAAG,CAAC,CAAC,GACP,CAAC,MAAM,CACT;CAEA,OACE,oBAAC,qBAAD;EACE,GAAI;EACO;EACF;EACM;EACD;EACd,KAAK;CACN,CAAA;AAEL,CACF;AAEA,gBAAgB,cAAc"}
|
|
@@ -42,7 +42,7 @@ var useCategoryPredicate = (category) => {
|
|
|
42
42
|
* `usePluginManager` + `useRegistryPlugins` + `useAutoTags` wiring shared by every category surface,
|
|
43
43
|
* none of which can live in a surface's `props` mapper.
|
|
44
44
|
*/
|
|
45
|
-
var RegistryCategoryArticle = ({ category }) => {
|
|
45
|
+
var RegistryCategoryArticle = ({ contextId, category }) => {
|
|
46
46
|
const manager = PluginManagerProvider.usePluginManager();
|
|
47
47
|
const filter = useCategoryPredicate(category);
|
|
48
48
|
const { entries } = useRegistryPlugins();
|
|
@@ -50,7 +50,7 @@ var RegistryCategoryArticle = ({ category }) => {
|
|
|
50
50
|
const deviceOnlyIds = Hooks.useSettingsDivergedKeys(AppSettings.PLUGINS_NAMESPACE);
|
|
51
51
|
const filtered = useMemo(() => manager.getPlugins().filter(filter), [manager, filter]);
|
|
52
52
|
return /* @__PURE__ */ jsx(RegistryArticle, {
|
|
53
|
-
|
|
53
|
+
contextId,
|
|
54
54
|
plugins: filtered,
|
|
55
55
|
extraTagsById,
|
|
56
56
|
deviceOnlyIds
|
|
@@ -64,7 +64,7 @@ var react_surface_default = Capability.makeModule(({ externalPlugins = true } =
|
|
|
64
64
|
id: "registry",
|
|
65
65
|
filter: AppSurface.literal(AppSurface.Article, "registry"),
|
|
66
66
|
component: PublicRegistryArticle,
|
|
67
|
-
props: () => ({
|
|
67
|
+
props: ({ data: { attendableId } }) => ({ contextId: attendableId })
|
|
68
68
|
}),
|
|
69
69
|
Surface.create({
|
|
70
70
|
id: LOAD_PLUGIN_DIALOG,
|
|
@@ -83,25 +83,37 @@ var react_surface_default = Capability.makeModule(({ externalPlugins = true } =
|
|
|
83
83
|
id: "bundled",
|
|
84
84
|
filter: AppSurface.literal(AppSurface.Article, "bundled"),
|
|
85
85
|
component: RegistryCategoryArticle,
|
|
86
|
-
props: (
|
|
86
|
+
props: ({ data: { attendableId } }) => ({
|
|
87
|
+
contextId: attendableId,
|
|
88
|
+
category: "bundled"
|
|
89
|
+
})
|
|
87
90
|
}),
|
|
88
91
|
Surface.create({
|
|
89
92
|
id: "installed",
|
|
90
93
|
filter: AppSurface.literal(AppSurface.Article, "installed"),
|
|
91
94
|
component: RegistryCategoryArticle,
|
|
92
|
-
props: (
|
|
95
|
+
props: ({ data: { attendableId } }) => ({
|
|
96
|
+
contextId: attendableId,
|
|
97
|
+
category: "installed"
|
|
98
|
+
})
|
|
93
99
|
}),
|
|
94
100
|
Surface.create({
|
|
95
101
|
id: "recommended",
|
|
96
102
|
filter: AppSurface.literal(AppSurface.Article, "recommended"),
|
|
97
103
|
component: RegistryCategoryArticle,
|
|
98
|
-
props: (
|
|
104
|
+
props: ({ data: { attendableId } }) => ({
|
|
105
|
+
contextId: attendableId,
|
|
106
|
+
category: "recommended"
|
|
107
|
+
})
|
|
99
108
|
}),
|
|
100
109
|
Surface.create({
|
|
101
110
|
id: "labs",
|
|
102
111
|
filter: AppSurface.literal(AppSurface.Article, "labs"),
|
|
103
112
|
component: RegistryCategoryArticle,
|
|
104
|
-
props: (
|
|
113
|
+
props: ({ data: { attendableId } }) => ({
|
|
114
|
+
contextId: attendableId,
|
|
115
|
+
category: "labs"
|
|
116
|
+
})
|
|
105
117
|
}),
|
|
106
118
|
Surface.create({
|
|
107
119
|
id: "pluginDetails",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-react-surface.mjs","names":[],"sources":["../../src/capabilities/RegistryCategoryArticle.tsx","../../src/capabilities/react-surface.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useMemo } from 'react';\n\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as AppSettings from '@dxos/app-toolkit/AppSettings';\nimport * as Hooks from '@dxos/app-toolkit/Hooks';\n\nimport { RegistryArticle } from '#containers';\n\nimport { type PluginPredicate, getCategoryPredicate } from '../categories.ts';\nimport { useAutoTags, useRegistryPlugins, useRemotePluginIds } from '../hooks/index.ts';\n\n/**\n * Resolves the {@link PluginPredicate} for a registry category against the live plugin list.\n * Shared with the graph builder via {@link getCategoryPredicate} so the category lists and their counts agree.\n */\nconst useCategoryPredicate = (category: string): PluginPredicate => {\n const manager = PluginManagerProvider.usePluginManager();\n const remoteIds = useRemotePluginIds();\n const core = useMemo(() => manager.getCore(), [manager]);\n const enabled = useMemo(() => manager.getEnabled(), [manager]);\n return useMemo(\n () => getCategoryPredicate(category, { core, enabled, remoteIds }),\n [category, core, enabled, remoteIds],\n );\n};\n\nexport type RegistryCategoryArticleProps = {\n category: string;\n};\n\n/**\n * Renders the {@link RegistryArticle} surface for one registry category. Centralises the\n * `usePluginManager` + `useRegistryPlugins` + `useAutoTags` wiring shared by every category surface,\n * none of which can live in a surface's `props` mapper.\n */\nexport const RegistryCategoryArticle = ({ category }: RegistryCategoryArticleProps) => {\n const manager = PluginManagerProvider.usePluginManager();\n const filter = useCategoryPredicate(category);\n const { entries } = useRegistryPlugins();\n const extraTagsById = useAutoTags(entries);\n const deviceOnlyIds = Hooks.useSettingsDivergedKeys(AppSettings.PLUGINS_NAMESPACE);\n const filtered = useMemo(() => manager.getPlugins().filter(filter), [manager, filter]);\n\n return (\n <RegistryArticle
|
|
1
|
+
{"version":3,"file":"chunk-react-surface.mjs","names":[],"sources":["../../src/capabilities/RegistryCategoryArticle.tsx","../../src/capabilities/react-surface.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useMemo } from 'react';\n\nimport * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';\nimport * as AppSettings from '@dxos/app-toolkit/AppSettings';\nimport * as Hooks from '@dxos/app-toolkit/Hooks';\n\nimport { RegistryArticle } from '#containers';\n\nimport { type PluginPredicate, getCategoryPredicate } from '../categories.ts';\nimport { useAutoTags, useRegistryPlugins, useRemotePluginIds } from '../hooks/index.ts';\n\n/**\n * Resolves the {@link PluginPredicate} for a registry category against the live plugin list.\n * Shared with the graph builder via {@link getCategoryPredicate} so the category lists and their counts agree.\n */\nconst useCategoryPredicate = (category: string): PluginPredicate => {\n const manager = PluginManagerProvider.usePluginManager();\n const remoteIds = useRemotePluginIds();\n const core = useMemo(() => manager.getCore(), [manager]);\n const enabled = useMemo(() => manager.getEnabled(), [manager]);\n return useMemo(\n () => getCategoryPredicate(category, { core, enabled, remoteIds }),\n [category, core, enabled, remoteIds],\n );\n};\n\nexport type RegistryCategoryArticleProps = {\n contextId: string;\n category: string;\n};\n\n/**\n * Renders the {@link RegistryArticle} surface for one registry category. Centralises the\n * `usePluginManager` + `useRegistryPlugins` + `useAutoTags` wiring shared by every category surface,\n * none of which can live in a surface's `props` mapper.\n */\nexport const RegistryCategoryArticle = ({ contextId, category }: RegistryCategoryArticleProps) => {\n const manager = PluginManagerProvider.usePluginManager();\n const filter = useCategoryPredicate(category);\n const { entries } = useRegistryPlugins();\n const extraTagsById = useAutoTags(entries);\n const deviceOnlyIds = Hooks.useSettingsDivergedKeys(AppSettings.PLUGINS_NAMESPACE);\n const filtered = useMemo(() => manager.getPlugins().filter(filter), [manager, filter]);\n\n return (\n <RegistryArticle\n contextId={contextId}\n plugins={filtered}\n extraTagsById={extraTagsById}\n deviceOnlyIds={deviceOnlyIds}\n />\n );\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport { type ComponentProps } from 'react';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as Plugin from '@dxos/app-framework/Plugin';\nimport * as Surface from '@dxos/app-framework/Surface';\nimport * as AppSurface from '@dxos/app-toolkit/AppSurface';\n\nimport { DisableDependentsAlert } from '#components';\nimport { LoadPluginDialog, PluginArticle, PublicRegistryArticle, RegistrySettingsContainer } from '#containers';\nimport { meta } from '#meta';\nimport { LOAD_PLUGIN_DIALOG, type RegistryPluginOptions } from '#types';\n\nimport { DISABLE_DEPENDENTS_DIALOG } from '../constants.ts';\nimport { RegistryCategoryArticle } from './RegistryCategoryArticle.tsx';\n\nexport default Capability.makeModule(({ externalPlugins = true }: RegistryPluginOptions = {}) => {\n const externalPluginSurfaces = externalPlugins\n ? [\n Surface.create({\n id: 'registry',\n filter: AppSurface.literal(AppSurface.Article, 'registry'),\n component: PublicRegistryArticle,\n props: ({ data: { attendableId } }) => ({ contextId: attendableId }),\n }),\n Surface.create({\n id: LOAD_PLUGIN_DIALOG,\n filter: AppSurface.component(AppSurface.Dialog, LOAD_PLUGIN_DIALOG),\n component: LoadPluginDialog,\n }),\n Surface.create({\n id: 'pluginSettings',\n filter: AppSurface.settings(AppSurface.Article, meta.profile.key),\n component: RegistrySettingsContainer,\n props: ({ data: { subject } }) => ({ subject }),\n }),\n ]\n : [];\n\n return Effect.succeed(\n Capability.contribute(Capabilities.ReactSurface, [\n Surface.create({\n id: 'bundled',\n filter: AppSurface.literal(AppSurface.Article, 'bundled'),\n component: RegistryCategoryArticle,\n props: ({ data: { attendableId } }) => ({ contextId: attendableId, category: 'bundled' }),\n }),\n Surface.create({\n id: 'installed',\n filter: AppSurface.literal(AppSurface.Article, 'installed'),\n component: RegistryCategoryArticle,\n props: ({ data: { attendableId } }) => ({ contextId: attendableId, category: 'installed' }),\n }),\n Surface.create({\n id: 'recommended',\n filter: AppSurface.literal(AppSurface.Article, 'recommended'),\n component: RegistryCategoryArticle,\n props: ({ data: { attendableId } }) => ({ contextId: attendableId, category: 'recommended' }),\n }),\n Surface.create({\n id: 'labs',\n filter: AppSurface.literal(AppSurface.Article, 'labs'),\n component: RegistryCategoryArticle,\n props: ({ data: { attendableId } }) => ({ contextId: attendableId, category: 'labs' }),\n }),\n Surface.create({\n id: 'pluginDetails',\n filter: AppSurface.subject(AppSurface.Article, Plugin.isPlugin),\n component: PluginArticle,\n props: ({ data: { subject } }) => ({ subject }),\n }),\n Surface.create({\n id: DISABLE_DEPENDENTS_DIALOG,\n filter: AppSurface.component<ComponentProps<typeof DisableDependentsAlert>>(\n AppSurface.Dialog,\n DISABLE_DEPENDENTS_DIALOG,\n ),\n component: DisableDependentsAlert,\n props: ({ data: { props } }) => ({ ...props }),\n }),\n ...externalPluginSurfaces,\n ]),\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmBA,IAAM,wBAAwB,aAAsC;CAClE,MAAM,UAAU,sBAAsB,iBAAiB;CACvD,MAAM,YAAY,mBAAmB;CACrC,MAAM,OAAO,cAAc,QAAQ,QAAQ,GAAG,CAAC,OAAO,CAAC;CACvD,MAAM,UAAU,cAAc,QAAQ,WAAW,GAAG,CAAC,OAAO,CAAC;CAC7D,OAAO,cACC,qBAAqB,UAAU;EAAE;EAAM;EAAS;CAAU,CAAC,GACjE;EAAC;EAAU;EAAM;EAAS;CAAS,CACrC;AACF;;;;;;AAYA,IAAa,2BAA2B,EAAE,WAAW,eAA6C;CAChG,MAAM,UAAU,sBAAsB,iBAAiB;CACvD,MAAM,SAAS,qBAAqB,QAAQ;CAC5C,MAAM,EAAE,YAAY,mBAAmB;CACvC,MAAM,gBAAgB,YAAY,OAAO;CACzC,MAAM,gBAAgB,MAAM,wBAAwB,YAAY,iBAAiB;CACjF,MAAM,WAAW,cAAc,QAAQ,WAAW,CAAC,CAAC,OAAO,MAAM,GAAG,CAAC,SAAS,MAAM,CAAC;CAErF,OACE,oBAAC,iBAAD;EACa;EACX,SAAS;EACM;EACA;CAChB,CAAA;AAEL;;;ACnCA,IAAA,wBAAe,WAAW,YAAY,EAAE,kBAAkB,SAAgC,CAAC,MAAM;CAC/F,MAAM,yBAAyB,kBAC3B;EACE,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,QAAQ,WAAW,SAAS,UAAU;GACzD,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,sBAAsB,EAAE,WAAW,aAAa;EACpE,CAAC;EACD,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,UAAU,WAAW,QAAQ,kBAAkB;GAClE,WAAW;EACb,CAAC;EACD,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,SAAS,WAAW,SAAS,KAAK,QAAQ,GAAG;GAChE,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,QAAQ;EAC/C,CAAC;CACH,IACA,CAAC;CAEL,OAAO,OAAO,QACZ,WAAW,WAAW,aAAa,cAAc;EAC/C,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,QAAQ,WAAW,SAAS,SAAS;GACxD,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,sBAAsB;IAAE,WAAW;IAAc,UAAU;GAAU;EACzF,CAAC;EACD,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,QAAQ,WAAW,SAAS,WAAW;GAC1D,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,sBAAsB;IAAE,WAAW;IAAc,UAAU;GAAY;EAC3F,CAAC;EACD,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,QAAQ,WAAW,SAAS,aAAa;GAC5D,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,sBAAsB;IAAE,WAAW;IAAc,UAAU;GAAc;EAC7F,CAAC;EACD,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,QAAQ,WAAW,SAAS,MAAM;GACrD,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,sBAAsB;IAAE,WAAW;IAAc,UAAU;GAAO;EACtF,CAAC;EACD,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,QAAQ,WAAW,SAAS,OAAO,QAAQ;GAC9D,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,QAAQ;EAC/C,CAAC;EACD,QAAQ,OAAO;GACb,IAAI;GACJ,QAAQ,WAAW,UACjB,WAAW,QACX,yBACF;GACA,WAAW;GACX,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM;EAC9C,CAAC;EACD,GAAG;CACL,CAAC,CACH;AACF,CAAC"}
|
package/dist/lib/components.mjs
CHANGED
|
@@ -79,8 +79,10 @@ var PluginFailureBadge = ({ failure, size }) => {
|
|
|
79
79
|
"data-testid": `pluginFailureBadge.${failure.id}`,
|
|
80
80
|
onClick: (event) => event.stopPropagation()
|
|
81
81
|
})
|
|
82
|
-
}), /* @__PURE__ */ jsx(Popover.Content, { children: /* @__PURE__ */ jsx(Popover.Body, { children: /* @__PURE__ */ jsxs(
|
|
83
|
-
|
|
82
|
+
}), /* @__PURE__ */ jsx(Popover.Content, { children: /* @__PURE__ */ jsx(Popover.Body, { children: /* @__PURE__ */ jsxs(Layout.Flex, {
|
|
83
|
+
column: true,
|
|
84
|
+
gap: "xs",
|
|
85
|
+
classNames: "px-3 py-2 min-w-[18rem] max-w-[28rem]",
|
|
84
86
|
children: [/* @__PURE__ */ jsx("p", {
|
|
85
87
|
className: "font-medium text-sm",
|
|
86
88
|
children: t("failure-title.label", {
|
|
@@ -137,8 +139,9 @@ var PluginDetail = Util.composable(({ plugin, scope, enabled, installing, updati
|
|
|
137
139
|
cols: ["fill", "min"],
|
|
138
140
|
classNames: "row-start-1 col-start-2 col-span-2 gap-x-3 w-full pt-1",
|
|
139
141
|
children: [
|
|
140
|
-
/* @__PURE__ */ jsxs(
|
|
141
|
-
|
|
142
|
+
/* @__PURE__ */ jsxs(Layout.Flex, {
|
|
143
|
+
align: "center",
|
|
144
|
+
gap: "sm",
|
|
142
145
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
143
146
|
className: "text-xl",
|
|
144
147
|
children: name
|
|
@@ -158,8 +161,10 @@ var PluginDetail = Util.composable(({ plugin, scope, enabled, installing, updati
|
|
|
158
161
|
checked: enabled,
|
|
159
162
|
onCheckedChange: ({ checked }) => onEnabledChange?.(checked)
|
|
160
163
|
}) }),
|
|
161
|
-
/* @__PURE__ */ jsxs(
|
|
162
|
-
|
|
164
|
+
/* @__PURE__ */ jsxs(Layout.Flex, {
|
|
165
|
+
align: "center",
|
|
166
|
+
gap: "xs",
|
|
167
|
+
classNames: "pt-0.5 text-sm text-fg-muted",
|
|
163
168
|
children: [slug, author && /* @__PURE__ */ jsx("span", {
|
|
164
169
|
className: "dx-tag dx-tag-inline",
|
|
165
170
|
"data-hue": "info",
|
|
@@ -181,8 +186,9 @@ var PluginDetail = Util.composable(({ plugin, scope, enabled, installing, updati
|
|
|
181
186
|
alt: name
|
|
182
187
|
}, src)) }), /* @__PURE__ */ jsx(Carousel.IndicatorGroup, {})]
|
|
183
188
|
}) })] }),
|
|
184
|
-
/* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("resources.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsxs(
|
|
185
|
-
|
|
189
|
+
/* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("resources.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsxs(Layout.Flex, {
|
|
190
|
+
gap: "md",
|
|
191
|
+
align: "center",
|
|
186
192
|
children: [
|
|
187
193
|
homePage && /* @__PURE__ */ jsxs(Typography.Link, {
|
|
188
194
|
href: homePage,
|
|
@@ -209,24 +215,27 @@ var PluginDetail = Util.composable(({ plugin, scope, enabled, installing, updati
|
|
|
209
215
|
})
|
|
210
216
|
]
|
|
211
217
|
}) })] }),
|
|
212
|
-
dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("dependencies.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsx(
|
|
213
|
-
|
|
218
|
+
dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("dependencies.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsx(Layout.Flex, {
|
|
219
|
+
wrap: true,
|
|
220
|
+
gap: "xs",
|
|
214
221
|
children: dependencies.map((depId) => /* @__PURE__ */ jsx(Chip, {
|
|
215
222
|
id: depId,
|
|
216
223
|
name: onResolvePluginName?.(depId) ?? depId,
|
|
217
224
|
onClick: onNavigateToPlugin
|
|
218
225
|
}, depId))
|
|
219
226
|
}) })] }),
|
|
220
|
-
dependents && dependents.length > 0 && /* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("dependents.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsx(
|
|
221
|
-
|
|
227
|
+
dependents && dependents.length > 0 && /* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("dependents.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsx(Layout.Flex, {
|
|
228
|
+
wrap: true,
|
|
229
|
+
gap: "xs",
|
|
222
230
|
children: dependents.map((dependentId) => /* @__PURE__ */ jsx(Chip, {
|
|
223
231
|
id: dependentId,
|
|
224
232
|
name: onResolvePluginName?.(dependentId) ?? dependentId,
|
|
225
233
|
onClick: onNavigateToPlugin
|
|
226
234
|
}, dependentId))
|
|
227
235
|
}) })] }),
|
|
228
|
-
versions && versions.length > 0 && /* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("versions.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsxs(
|
|
229
|
-
|
|
236
|
+
versions && versions.length > 0 && /* @__PURE__ */ jsxs(Section.Root, { children: [/* @__PURE__ */ jsx(Section.Heading, { title: t("versions.label") }), /* @__PURE__ */ jsx(Section.Body, { children: /* @__PURE__ */ jsxs(Layout.Flex, {
|
|
237
|
+
gap: "sm",
|
|
238
|
+
align: "center",
|
|
230
239
|
children: [/* @__PURE__ */ jsxs(Select.Root, {
|
|
231
240
|
items: versionItems,
|
|
232
241
|
value: selectedVersionTag ? [selectedVersionTag] : [],
|
|
@@ -240,8 +249,9 @@ var PluginDetail = Util.composable(({ plugin, scope, enabled, installing, updati
|
|
|
240
249
|
children: installing ? t("installing.label") : t("install-version.label")
|
|
241
250
|
})]
|
|
242
251
|
}) })] }),
|
|
243
|
-
(onUninstall || hasUpdate && onUpdate || updating) && /* @__PURE__ */ jsxs(
|
|
244
|
-
|
|
252
|
+
(onUninstall || hasUpdate && onUpdate || updating) && /* @__PURE__ */ jsxs(Layout.Flex, {
|
|
253
|
+
gap: "sm",
|
|
254
|
+
classNames: "col-start-2 col-span-2",
|
|
245
255
|
children: [updating ? /* @__PURE__ */ jsx(Button.Root, {
|
|
246
256
|
variant: "primary",
|
|
247
257
|
disabled: true,
|
|
@@ -265,8 +275,10 @@ var SectionHeading = ({ title }) => /* @__PURE__ */ jsx("h2", {
|
|
|
265
275
|
className: "col-start-2 col-span-2 pt-6 pb-2 uppercase text-sm font-medium text-fg-subtle",
|
|
266
276
|
children: title
|
|
267
277
|
});
|
|
268
|
-
var SectionBody = ({ classNames, children }) => /* @__PURE__ */ jsx(
|
|
269
|
-
|
|
278
|
+
var SectionBody = ({ classNames, children }) => /* @__PURE__ */ jsx(Layout.Flex, {
|
|
279
|
+
column: true,
|
|
280
|
+
gap: "sm",
|
|
281
|
+
classNames: ["col-start-2", classNames],
|
|
270
282
|
children
|
|
271
283
|
});
|
|
272
284
|
var Section = {
|
|
@@ -301,7 +313,7 @@ var PluginItem = ({ plugin, installed, installing, enabled = [], extraTags, devi
|
|
|
301
313
|
const showUpdateButton = !!onUpdate && isInstalled && !!hasUpdate;
|
|
302
314
|
const hasSettings = hasSettingsProp?.(id) ?? false;
|
|
303
315
|
const titleId = `${id}-title`;
|
|
304
|
-
const handleClick = useCallback(() => onClick?.(id), [id, onClick]);
|
|
316
|
+
const handleClick = useCallback((event) => onClick?.(id, { modified: event.metaKey || event.ctrlKey }), [id, onClick]);
|
|
305
317
|
const handleSettings = useCallback(() => onSettings?.(id), [id, onSettings]);
|
|
306
318
|
const handleInstall = useCallback(() => onInstall?.(id), [id, onInstall]);
|
|
307
319
|
const handleUpdate = useCallback(() => onUpdate?.(id), [id, onUpdate]);
|