"use client"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { IconRefresh } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Switch } from "@kandev/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@kandev/ui/tabs"; import { SettingsPageTemplate } from "@/components/settings/settings-page-template"; import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint"; import { useAutoUpdateSettings } from "@/hooks/domains/plugins/use-auto-update-settings"; import { useIsAdmin } from "@/hooks/domains/auth/use-is-admin"; import { usePlugins } from "@/hooks/domains/plugins/use-plugins"; import { usePluginSetupStatus } from "@/hooks/domains/plugins/use-plugin-setup-status"; import { usePluginUpdates } from "@/hooks/domains/plugins/use-plugin-updates"; import { InstallPluginDialog } from "./install-plugin-dialog"; import { MarketplaceBrowser } from "./marketplace-browser"; import { PluginRow, type PluginRowUpdateState } from "./plugin-row"; import { PluginUpdateStatus } from "./plugin-update-status"; import { usePluginActions } from "./use-plugin-actions"; import { usePluginUpdateAction } from "./use-plugin-update-action"; import { settingsActionClassName } from "@/components/settings/settings-control"; /** * Operator UI to browse, install, enable, disable, uninstall, and update kandev * plugins (docs/specs/plugins/requirements/marketplace.md). Gated on the `plugins` feature * flag by the page-level default export. */ export function PluginsSettings() { const { t } = useTranslation(); const canManage = useIsAdmin(); const { isFinePointer } = useResponsiveBreakpoint(); const list = usePlugins(); const actions = usePluginActions(); const autoUpdate = useAutoUpdateSettings(); const updates = usePluginUpdates(); const installedIds = useMemo(() => new Set(list.items.map((p) => p.id)), [list.items]); const updateAction = usePluginUpdateAction( actions.marketplaceInstall, updates.reload, installedIds, updates.markUpdated, ); const handleMarketplaceInstall = async (url: string) => { const result = await actions.marketplaceInstall(url); if (result.ok) await updates.reload(result.pluginId); return result; }; return ( undefined} showSaveButton={false} > {t("plugins:tabInstalled")} {t("plugins:tabBrowse")} {canManage && ( )} ); } type InstalledTabProps = { list: ReturnType; actions: ReturnType; autoUpdate: ReturnType; canManage: boolean; updates: ReturnType; updateAction: ReturnType; isFinePointer: boolean; }; /** The Installed tab: auto-update toggle, sync/install toolbar, update status, sync errors, and the plugin list. */ function InstalledTab({ list, actions, autoUpdate, updates, canManage, updateAction, isFinePointer, }: InstalledTabProps) { const { t } = useTranslation(); return ( <> {canManage && }
{t("plugins:installedPlugins")}
{canManage && (
)}
{canManage && actions.syncErrors.length > 0 && (
{actions.syncErrors.map((err) => (
{err.path}: {err.reason}
))}
)} ); } /** * The instance-wide "Automatically update plugins" switch. When on, every * installed plugin without its own per-row override is auto-updated in the * background. Individual rows can still override this either way. */ function GlobalAutoUpdateToggle({ settings, }: { settings: ReturnType; }) { const { t } = useTranslation(); return (

{t("plugins:autoUpdateDescription")}

); } type PluginListProps = { list: ReturnType; actions: ReturnType; autoUpdateDefault: boolean; canManage: boolean; updates: ReturnType; updateAction: ReturnType; isFinePointer: boolean; }; function PluginList({ list, actions, autoUpdateDefault, updates, canManage, updateAction, isFinePointer, }: PluginListProps) { const { t } = useTranslation(); const { items, loaded, loading, error } = list; const needsSetup = usePluginSetupStatus(items); if (error) { return (
{error}
); } if (!loaded && loading) { return (
{t("plugins:loadingPlugins")}
); } if (loaded && items.length === 0) { return (
{t("plugins:noPluginsYet")}
); } return (
{items.map((plugin) => { const rowUpdate: PluginRowUpdateState = { latest: updates.latestById.get(plugin.id), hasUpdate: updates.updates.has(plugin.id), checked: updates.checked, sourcesDegraded: updates.sourcesDegraded, busy: updateAction.updatingIds.has(plugin.id), error: updateAction.errorsById.get(plugin.id), }; return ( { await actions.confirmUninstall(target); }} onUpdate={updateAction.runUpdate} onSetAutoUpdate={actions.handleSetAutoUpdate} /> ); })}
); }