import type { ReactNode } from "react"; import { type XyzacOperationTrace, type XyzacPanelView } from "./xyzac-panel-contract"; import type { SimSnapshotV1 } from "./protocol"; import { DEFAULT_MACHINE } from "../../workspace/machine-definitions.mjs"; type Command = (kind: string, payload?: Record, action?: string) => void; export function XyzacKinematicsControl({ view, command, loading = false }: { view: XyzacPanelView; command: Command; loading?: boolean }) { const active = view.kinematics?.active_type; const machine = view.machine ?? DEFAULT_MACHINE; const modes = [{ type: 0, action: "switchkinsIdentity", label: "Identity", mcode: "M429" }, { type: 1, action: "switchkinsXyzac", label: `TCP: ${machine.coordinates}`, mcode: "M428" }, { type: 2, action: "switchkinsUserk", label: "User K", mcode: "M430" }]; return
{machine.label}{view.kinematics?.native_type ?? "Unavailable"}
{modes.map((item) => { const gate = view.gates[item.action]; const disabled = loading || view.status !== "ready" || gate?.enabled === false; return ; })}
; } export function XyzacManualPanel({ view }: { view: XyzacPanelView }) { const pose = view.pose; const machine = view.machine ?? DEFAULT_MACHINE; const axes = machine.axes; const offsets = view.kinematics?.offsets; const offsetRows = machine.coordinates === "XYZBC" ? [`X offset ${offsets ? Number(offsets.x_offset ?? 0).toFixed(3) : "-"}`, `Z offset ${offsets ? Number(offsets.z_offset).toFixed(3) : "-"}`, `Tool ${offsets ? Number(offsets.tool_offset).toFixed(3) : "-"}`] : [`Y offset ${offsets ? Number(offsets.y_offset).toFixed(3) : "-"}`, `Z offset ${offsets ? Number(offsets.z_offset).toFixed(3) : "-"}`, `Tool ${offsets ? Number(offsets.tool_offset).toFixed(3) : "-"}`]; return
{machine.coordinates} authority{view.kinematics?.linear_units ?? "mm"} / {view.kinematics?.angular_units ?? "deg"}
{axes.map((axis, index) =>
{axis.toUpperCase()}{pose ? Number(pose[axis as keyof typeof pose] ?? Number.NaN).toFixed(["a", "b", "c"].includes(axis) ? 3 : 4) : "-"}J{index} {view.joints.length === machine.joint_count ? Number(view.joints[index]).toFixed(4) : "-"}
)}
{offsetRows.map((row) => {row})}
; } export function XyzacProgramPane({ status, currentLine, sourceHash, children }: { status: string; currentLine: number | null; sourceHash?: string | null; children: ReactNode }) { return
TRT Program{status}Line {currentLine ?? "-"}{sourceHash?.slice(0, 18) ?? "no source"}
{children}
; } export function XyzacLiveParameters({ snapshot }: { snapshot: SimSnapshotV1 | null }) { const ready = snapshot?.motion_authority === "wasm" && Boolean(snapshot?.task_authority); const motion = (snapshot?.motion ?? {}) as Record; const program = snapshot?.program ?? null; const number = (value: unknown, digits = 2, unit = "") => ready && typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(digits)}${unit ? ` ${unit}` : ""}` : "-"; const integer = (value: unknown) => ready && Number.isSafeInteger(value) ? String(value) : "-"; const wasmState = (snapshot?.wasm_motion as { state?: Record } | undefined)?.state; return
Live parameters{ready ? "LinuxCNC WASM" : "WASM PENDING"}
Spindle{number(motion.spindle_speed_rpm, 1, "rpm")}Cmd {number(motion.spindle_command_rpm, 1, "rpm")} / S {number(program?.speed, 1, "rpm")}
Feed actual{number(motion.velocity_mm_min, 1, "mm/min")}Program {number(motion.feed_command_mm_min, 1, "mm/min")}
TP velocity{number(motion.velocity, 3, "mm/s")}Limit {number(motion.max_velocity, 3, "mm/s")}
OverridesF {number(typeof motion.feed_override === "number" ? motion.feed_override * 100 : Number.NaN, 0, "%")}S {number(typeof motion.spindle_override === "number" ? motion.spindle_override * 100 : Number.NaN, 0, "%")}
Planner{integer(wasmState?.queue_depth)} queuedSegment {integer(wasmState?.active_id)}
ExecutionLine {integer(program?.execution_line ?? program?.current_line)}{ready ? String(program?.program_status ?? "-") : "-"}
; } export function XyzacDiagnostics({ view, operations, alarms, error }: { view: XyzacPanelView; operations: XyzacOperationTrace[]; alarms: Array>; error: { code: string; message?: string } | null }) { const disabled = Object.entries(view.gates).filter(([, gate]) => gate.enabled === false); return
{view.status.toUpperCase()}Tick {view.tick}{view.stateHash || "no hash"}
{error &&
{error.code}{error.message}
}

Operation trace

{operations.length ? operations.slice(-8).reverse().map((item, index) =>
{item.operation_id ?? "-"}{item.kind}{item.phase}{item.reason ?? item.response_kind}
) : No operations}

Alarms / recovery

{alarms.length ? alarms.slice(-5).map((alarm, index) =>
{String(alarm.code ?? "alarm")}{String(alarm.severity ?? "error")}
) : No alarms}
recovery{view.recovery ? JSON.stringify(view.recovery) : "idle"}

Gate reasons

{disabled.length ? disabled.slice(0, 10).map(([name, gate]) =>
{name}{gate.reason}
) : All {view.machine.coordinates} gates enabled}
; } export function XyzacTrtPanel({ view, command, loading = false, machineOptions = [], selectedMachine, onMachineChange }: { view: XyzacPanelView; command: Command; loading?: boolean; machineOptions?: readonly { entrypoint: string; label: string }[]; selectedMachine?: string; onMachineChange?: (entrypoint: string) => void }) { return
{machineOptions.length > 1 && }
; }