import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Check, Copy, Download, LayoutTemplate, Palette, History, Power, RefreshCw, RotateCcw, TriangleAlert, } from "lucide-react"; import { Button } from "@/components/button"; import { ListRow } from "@/components/list-kit"; import { InlineView, SurfaceScope } from "@/components/surface/surface-scope"; import { useTheme } from "@/components/theme-provider"; import { usePermissions } from "@/hooks/use-permissions"; import { PluginConsentPrompt } from "@/plugins/PluginConsentPrompt"; import { usePluginsManager } from "@/plugins/use-plugins-manager"; import { PluginIconBox } from "@/plugins/plugin-bits"; import { getPluginRecord } from "@/plugin-host/plugin-store"; import { restoreBundledPlugin } from "@/api/plugins-api"; import { ShellConfirmCapture } from "@/shell/controller/ShellControllerProvider"; import { chooseDefaultLook, chooseLayout, chooseThemeForAccount, } from "./look-store"; import { layoutLabel, useLayouts } from "./layout-registry"; import { themeLabel, useThemes } from "./theme-registry"; import { useChooseTheme } from "./use-look-theme"; import type { LayoutStatus } from "./layout-status"; type RepairStatus = Extract; const LOOK_CAPABILITIES = ["ui:layout", "ui:theme"]; /** * What an admin can do about a broken layout: update or disable its plugin, * and install another layout or theme. Uses the Plugins tab's own flows. */ function AdminTools({ status, installOpen, setInstallOpen, onRetry, }: { status: RepairStatus; installOpen: boolean; setInstallOpen: (open: boolean) => void; onRetry: () => void; }) { const { t } = useTranslation(); const manager = usePluginsManager(); const entry = status.pluginId ? manager.plugins.find((plugin) => plugin.id === status.pluginId) : undefined; const installable = manager.plugins.filter( (plugin) => !plugin.installed && plugin.versions[0]?.capabilities?.some((capability) => LOOK_CAPABILITIES.includes(capability), ), ); return ( <> {entry?.installed && entry.updateAvailable && entry.latestVersion && ( )} {entry?.installed && entry.enabled && !entry.managedByServer && ( )} {entry?.installed && entry.bundled && !entry.managedByServer && ( )} } >
{manager.loading && (

{t("common.loading")}

)} {!manager.loading && installable.length === 0 && (

{manager.registryError ? t("looks.repair.registryUnreachable") : t("looks.repair.nothingToInstall")}

)} {installable.map((plugin, index) => ( } title={plugin.name} meta={plugin.description} trailing={ } /> ))}
manager.setConsent(null)} onConfirm={() => void manager.confirmConsent()} /> ); } /** * Shown instead of a layout that cannot be drawn. Small on purpose: it has * to work when everything else is broken, and gets people back to a working * look in one click. */ export function RepairView({ status, onRetry, }: { status: RepairStatus; onRetry: () => void; }) { const { t } = useTranslation(); const { isAdmin } = usePermissions(); const { theme } = useTheme(); const chooseTheme = useChooseTheme(); const layouts = useLayouts(); const themes = useThemes(); const [copied, setCopied] = useState(false); const [installOpen, setInstallOpen] = useState(false); const record = status.pluginId ? getPluginRecord(status.pluginId) : null; const layoutName = layouts.find((layout) => layout.id === status.layoutId) ?? null; const name = layoutName ? layoutLabel(layoutName, t) : status.layoutId || t("looks.repair.noLayout"); const details = [ `${t("looks.repair.layout")}: ${name} (${status.layoutId})`, status.pluginId ? `${t("looks.repair.plugin")}: ${record?.summary.name ?? status.pluginId} ${status.version ?? ""}`.trim() : null, `${t("looks.repair.reason")}: ${t(`looks.repair.reasons.${status.reason}`)}`, status.error ? `${t("looks.repair.error")}: ${status.error}` : null, ] .filter(Boolean) .join("\n"); const copy = async () => { try { await navigator.clipboard.writeText(details); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { toast.error(t("looks.repair.copyFailed")); } }; const run = (action: () => Promise) => () => void action().catch(() => toast.error(t("looks.repair.saveFailed"))); const resetOwn = run(async () => { try { localStorage.removeItem("vite-ui-theme"); } catch { // Private mode. } await chooseThemeForAccount(null); await chooseLayout(null); }); const resetForEveryone = run(() => chooseDefaultLook({ layout: null, theme: null }), ); const safe = status.reason === "safe-url" || status.reason === "safe-env"; return (

{safe ? t("looks.repair.safeTitle") : t("looks.repair.title")}

{t(`looks.repair.reasons.${status.reason}`)}

{name} {status.pluginId && ( {" | "} {record?.summary.name ?? status.pluginId} {status.version ? ` ${status.version}` : ""} )}
              {status.error ?? t("looks.repair.noError")}
            
{!safe && ( )} {isAdmin && ( )} {isAdmin && ( )}
{!isAdmin && (

{t("looks.repair.askAdmin")}

)}

{t("looks.repair.switchLayout")}

{layouts.map((layout, index) => ( chooseLayout(layout.id))} /> ))}

{t("looks.repair.switchTheme")}

{themes.map((entry) => ( ))}
); }