import { Boxes, Eye, Filter, PlugZap, Search, Sparkles } from "lucide-react"; import { type ChangeEvent, useCallback, useEffect, useMemo, useState } from "react"; import { platformApiClient } from "../api/client"; import type { GamePluginStatus, MarketplacePluginFilterRequest, MarketplacePluginResponse, MarketplacePluginStateAction } from "../api/types"; import { ManagementDialog } from "../components/OperationControls"; import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews"; import { PageFrame } from "../components/PageFrame"; import type { PageComponentProps } from "../contracts/page"; import { cx } from "../utils/classes"; type ListState = "loading" | "ready" | "error"; type StatusFilter = GamePluginStatus | "all"; interface PluginsPageInitialState { listState?: ListState; listError?: string; plugins?: MarketplacePluginResponse[]; selectedId?: string; detail?: MarketplacePluginResponse | null; usingFallback?: boolean; actionResult?: { status: "succeeded" | "failed"; label: string } | null; } interface PluginsPageProps extends Partial { initialState?: PluginsPageInitialState; } const statusFilters: Array<{ id: StatusFilter; label: string }> = [ { id: "all", label: "全部" }, { id: "installed", label: "已安装" }, { id: "disabled", label: "停用" }, { id: "invalid", label: "异常" }, { id: "updating", label: "更新中" } ]; export function PluginsPage({ initialState }: PluginsPageProps = {}) { const [listState, setListState] = useState(initialState?.listState ?? "loading"); const [listError, setListError] = useState(initialState?.listError ?? ""); const [plugins, setPlugins] = useState(initialState?.plugins ?? []); const [selectedId, setSelectedId] = useState(initialState?.selectedId ?? ""); const [detail, setDetail] = useState(initialState?.detail ?? null); const [detailPending, setDetailPending] = useState(false); const [detailError, setDetailError] = useState(""); const [keyword, setKeyword] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [serverType, setServerType] = useState(""); const [capability, setCapability] = useState(""); const [actionPending, setActionPending] = useState(null); const [actionResult, setActionResult] = useState<{ status: "succeeded" | "failed"; label: string } | null>(initialState?.actionResult ?? null); const [usingFallback, setUsingFallback] = useState(initialState?.usingFallback ?? false); const refresh = useCallback(async () => { setListState("loading"); setListError(""); const filter: MarketplacePluginFilterRequest = { keyword: keyword.trim() || undefined, status: statusFilter, serverType: serverType.trim() || undefined, capability: capability.trim() || undefined }; if (initialState?.plugins) { setListState(initialState.listState ?? "ready"); setListError(initialState.listError ?? ""); setPlugins(initialState.plugins); setSelectedId(initialState.selectedId ?? ""); setDetail(initialState.detail ?? null); setUsingFallback(initialState.usingFallback ?? false); return; } try { const response = await platformApiClient.listMarketplacePlugins(filter); setPlugins(response.items); setSelectedId((current) => (current && response.items.some((plugin) => plugin.id === current) ? current : "")); setUsingFallback(false); setListState("ready"); } catch (error) { setListState("error"); setListError(error instanceof Error ? error.message : "插件市场加载失败"); setPlugins([]); setDetail(null); } }, [capability, initialState, keyword, serverType, statusFilter]); useEffect(() => { void refresh(); }, [refresh]); useEffect(() => { if (!selectedId) { setDetail(null); setDetailError(""); return; } const selectedSummary = plugins.find((plugin) => plugin.id === selectedId) ?? null; if (usingFallback) { setDetail(selectedSummary); setDetailError(""); return; } setDetailPending(true); setDetailError(""); platformApiClient .getMarketplacePlugin(selectedId) .then((plugin) => setDetail(plugin)) .catch((error: unknown) => { setDetail(selectedSummary); setDetailError(error instanceof Error ? error.message : "插件详情加载失败"); }) .finally(() => setDetailPending(false)); }, [plugins, selectedId, usingFallback]); const serverTypes = useMemo(() => unique(plugins.map((plugin) => plugin.serverType)), [plugins]); const capabilities = useMemo(() => unique(plugins.flatMap((plugin) => [...plugin.capabilities, ...plugin.bridgeActions])), [plugins]); const installedCount = plugins.filter((plugin) => plugin.status === "installed").length; const bridgeActionCount = plugins.reduce((sum, plugin) => sum + plugin.bridgeActions.length, 0); const invalidCount = plugins.filter((plugin) => plugin.status === "invalid").length; function updateSelect(event: ChangeEvent) { const { name, value } = event.target; if (name === "serverType") { setServerType(value); } else if (name === "capability") { setCapability(value); } } async function changeState(action: MarketplacePluginStateAction) { if (!detail || usingFallback) { return; } setActionPending(action); setActionResult(null); try { const updated = await platformApiClient.setMarketplacePluginState(detail.id, { action }); setDetail(updated); setPlugins((current) => current.map((plugin) => (plugin.id === updated.id ? updated : plugin))); setActionResult({ status: "succeeded", label: `${updated.name} 已${stateActionLabel(action)}` }); } catch (error) { setActionResult({ status: "failed", label: error instanceof Error ? error.message : "状态更新失败" }); } finally { setActionPending(null); } } return (
0 ? "warning" : "success" } ]} />
{usingFallback && } {actionResult && } {listState === "loading" && } {listState === "error" && void refresh()} />} {listState === "ready" && plugins.length === 0 && ( } title="暂无匹配插件" description="调整搜索、状态、服务器类型或能力筛选后再试。" actionLabel="清除筛选" onAction={() => { setKeyword(""); setStatusFilter("all"); setServerType(""); setCapability(""); }} /> )} {plugins.length > 0 && (
{plugins.map((plugin) => (

{plugin.name}

{plugin.id}
{statusLabel(plugin.status)}

{plugin.description || plugin.serverDisplayName || plugin.serverType}

版本
{plugin.version}
服务器类型
{plugin.serverType}
能力
{plugin.capabilities.slice(0, 3).join(", ") || "--"}
桥接
{plugin.bridgeActions.slice(0, 3).join(", ") || "--"}
))}
)} { setSelectedId(""); setDetail(null); setDetailError(""); }}>
{detailPending && } {detailError && } {detail && void changeState(action)} />}
); } interface PluginDetailProps { plugin: MarketplacePluginResponse; actionPending: MarketplacePluginStateAction | null; actionsDisabled: boolean; onAction: (action: MarketplacePluginStateAction) => void; } function PluginDetail({ plugin, actionPending, actionsDisabled, onAction }: PluginDetailProps) { return (

{plugin.name}

{plugin.manifestRef}
{statusLabel(plugin.status)}
能力
{plugin.capabilities.join(", ") || "--"}
桥接动作
{plugin.bridgeActions.join(", ") || "--"}
插件页面
{plugin.pages.map((page) => `${page.title}(${page.key})`).join(", ") || "--"}
校验
{plugin.validationViolations?.length ? plugin.validationViolations.join(", ") : "manifest validated"}
); } function DetailStat({ label, value }: { label: string; value: string }) { return ( {label} {value} ); } function unique(values: string[]): string[] { return Array.from(new Set(values.filter(Boolean))).sort((left, right) => left.localeCompare(right)); } function statusClass(status: GamePluginStatus): string { if (status === "installed") { return "status-active"; } if (status === "invalid") { return "status-error"; } return "status-disabled"; } function statusLabel(status: string): string { return status === "installed" ? "已安装" : status === "disabled" ? "停用" : status === "invalid" ? "异常" : status === "updating" ? "更新中" : status; } function stateActionLabel(action: MarketplacePluginStateAction): string { return action === "disable" ? "停用" : "启用"; }