import { useEffect, useMemo, useState } from "react"; import catalogJson from "./rtcp-program-manifest.json"; import { DraggableDialog } from "./DraggableDialog"; export type RtcpProgram = { id: string; group: string; label: string; path: string; coordinates: string[]; role: "main" | "support" | "reference"; features: string[]; note: string; }; type Catalog = { schema: string; total: number; groups: Array<{ id: string; label: string; count: number }>; programs: RtcpProgram[]; }; export const RTCP_PROGRAM_CATALOG = catalogJson as Catalog; const roleLabel = (role: RtcpProgram["role"]) => role === "main" ? "主程序" : role === "support" ? "支持/子程序" : "参考程序"; const assetUrl = (path: string) => `${import.meta.env.BASE_URL}${path}`; const machineLabel = (coordinates: string) => coordinates === "XYZAC" ? "AC(XYZAC)" : coordinates === "XYZBC" ? "BC(XYZBC)" : coordinates; export function RtcpProgramLibrary({ coordinates, disabled, disabledReason, onLoad }: { coordinates: string; disabled: boolean; disabledReason: string; onLoad: (program: RtcpProgram, source: string) => Promise; }) { const defaultProgram = RTCP_PROGRAM_CATALOG.programs.find((program) => program.role === "main" && program.coordinates.includes(coordinates)) ?? RTCP_PROGRAM_CATALOG.programs[0]; const [open, setOpen] = useState(false); const [scope, setScope] = useState<"machine" | "all">("machine"); const [selectedId, setSelectedId] = useState(defaultProgram.id); const [source, setSource] = useState(""); const [sourcePath, setSourcePath] = useState(""); const [message, setMessage] = useState(""); const [pending, setPending] = useState(false); const selected = RTCP_PROGRAM_CATALOG.programs.find((program) => program.id === selectedId) ?? defaultProgram; const compatible = selected.role === "main" && selected.coordinates.includes(coordinates); const machinePrograms = useMemo(() => RTCP_PROGRAM_CATALOG.programs.filter((program) => program.coordinates.includes(coordinates)), [coordinates]); const visiblePrograms = scope === "machine" ? machinePrograms : RTCP_PROGRAM_CATALOG.programs; const grouped = RTCP_PROGRAM_CATALOG.groups.map((group) => ({ ...group, programs: visiblePrograms.filter((program) => program.group === group.id) })).filter((group) => group.programs.length > 0); useEffect(() => { setScope("machine"); setSelectedId(defaultProgram.id); setSource(""); setSourcePath(""); setMessage(""); }, [coordinates, defaultProgram.id]); const readSource = async () => { if (sourcePath === selected.path && source) return source; const response = await fetch(assetUrl(selected.path)); if (!response.ok) throw new Error(`程序资源读取失败: HTTP ${response.status}`); const text = (await response.text()).replace(/\r\n/g, "\n").replace(/\r/g, "\n"); if (!text.trim()) throw new Error("程序资源为空"); setSource(text); setSourcePath(selected.path); return text; }; const run = async (action: "source" | "load") => { setPending(true); setMessage(action === "source" ? "正在读取 LinuxCNC 源程序…" : "正在交给 LinuxCNC WASM 解析…"); try { const text = await readSource(); if (action === "load") { await onLoad(selected, text); setMessage("程序已加载到 LinuxCNC WASM 控制器"); } else setMessage(`已读取 ${text.split("\n").length} 行`); } catch (error) { setMessage(error instanceof Error ? error.message : String(error)); } finally { setPending(false); } }; const compatibility = compatible ? `${coordinates} 当前机型可加载;运动由 LinuxCNC WASM 解释器与运动学执行。` : selected.role !== "main" ? `${roleLabel(selected.role)}用于 remap、O-call 或算法查阅,不作为独立主程序加载。` : `需要 ${selected.coordinates.join("/")} 运动学;当前机型为 ${coordinates}。`; return
setOpen(false)}>
{machineLabel(coordinates)} 程序当前分类:{machinePrograms.length} 个可直接加载:{machinePrograms.filter((program) => program.role === "main").length}完整源程序:{RTCP_PROGRAM_CATALOG.total} 个
类型
{roleLabel(selected.role)}
机型
{selected.coordinates.join(" / ")}
特点
{selected.features.join(" · ")}
源路径
{selected.path}

{selected.note}

{compatibility}

下载 .ngc
{message} {source && sourcePath === selected.path &&