import { useEffect, useMemo, useRef, useState } from "react"; import { readTrtGeometry, writeTrtGeometry, TRT_GEOMETRY_KEYS } from "../../workspace/trt-geometry.mjs"; import { DraggableDialog } from "./DraggableDialog"; import { axisIcon } from "./sourceAssets"; type Bundle = { entrypoint: string; files: Record; source_ini?: string }; type Props = { bundle: Bundle | null; disabled: boolean; busy: boolean; onApply: (bundle: Bundle) => Promise; onReload: () => Promise }; export function TrtGeometryPanel({ bundle, disabled, busy, onApply, onReload }: Props) { const [open, setOpen] = useState(false); const [values, setValues] = useState>({}); const [message, setMessage] = useState(""); const [pending, setPending] = useState(false); const file = useRef(null); const filePickerOpen = useRef(false); const openFilePicker = () => { if (locked || !file.current) return; filePickerOpen.current = true; file.current.click(); const clear = () => { window.removeEventListener("focus", clear); setTimeout(() => { filePickerOpen.current = false; }, 0); }; window.addEventListener("focus", clear, { once: true }); }; const geometry = useMemo(() => bundle ? readTrtGeometry(bundle) : null, [bundle]); useEffect(() => { setValues(Object.fromEntries(TRT_GEOMETRY_KEYS.map((key: string) => [key, String(geometry?.offsets[key] ?? 0)]))); setMessage(""); }, [bundle]); const locked = disabled || busy || pending || !bundle; const dirty = TRT_GEOMETRY_KEYS.some((key: string) => values[key] !== String(geometry?.offsets[key] ?? 0)); const run = async (action: () => Promise, success: string) => { setPending(true); setMessage(""); try { await action(); setMessage(success); } catch (error) { setMessage(error instanceof Error ? error.message : String(error)); } finally { setPending(false); } }; const edited = () => { if (!bundle) throw new Error("INI unavailable"); if (Object.values(geometry?.sources ?? {}).some((source) => source.path !== bundle.entrypoint)) throw new Error("参数位于包含文件,请修改源文件后重新读取 INI"); const next = writeTrtGeometry(bundle, values); // Export the native INI, retaining LIB:basic_sim.tcl instead of the Web fixture. next.source_ini = writeTrtGeometry({ ...bundle, files: { ...bundle.files, [bundle.entrypoint]: bundle.source_ini ?? bundle.files[bundle.entrypoint] } }, values).files[bundle.entrypoint]; return next; }; const exportIni = async () => { const next = edited(); const content = next.source_ini ?? next.files[next.entrypoint]; const name = next.entrypoint.split("/").at(-1)!; const picker = (window as unknown as { showSaveFilePicker?: (options: unknown) => Promise<{ createWritable: () => Promise<{ write: (data: string) => Promise; close: () => Promise }> }> }).showSaveFilePicker; if (picker) { const handle = await picker({ suggestedName: name, types: [{ description: "LinuxCNC INI", accept: { "text/plain": [".ini"] } }] }); const stream = await handle.createWritable(); await stream.write(content); await stream.close(); } else { const url = URL.createObjectURL(new Blob([content], { type: "text/plain" })); const link = document.createElement("a"); link.href = url; link.download = name; link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); } }; const tool = (id: string, label: string, asset: string, action: () => void, off = locked) => ; return
filePickerOpen.current} onClose={() => setOpen(false)}>
{bundle?.entrypoint.split("/").at(-1) ?? "INI"}mm{dirty ? "待应用" : "已生效"}
{["旋转中心", "轴间偏置"].map((title, index) =>
{title}{TRT_GEOMETRY_KEYS.slice(index * 3, index * 3 + 3).map((key: string) => )}
)}
{tool("reload", "重新读取 INI", "tool_reload.gif", () => void run(onReload, "已重新读取 INI"))} {tool("import", "导入 INI 参数", "tool_open.gif", openFilePicker)} {message || (disabled ? "关机且程序空闲后可修改" : "")}
{ const selected = event.target.files?.[0]; event.target.value = ""; if (!selected || !bundle || locked) return; void run(async () => { if (selected.name !== bundle.entrypoint.split("/").at(-1)) throw new Error("INI 文件名与机型不一致"); const imported = readTrtGeometry({ ...bundle, files: { ...bundle.files, [bundle.entrypoint]: await selected.text() } }); setValues(Object.fromEntries(TRT_GEOMETRY_KEYS.map((key: string) => [key, String(imported.offsets[key])]))); }, "INI 参数已导入,待应用"); }} />
; }