import { useMemo } from "react"; import { analyze, autoLayout, compile, tidyLayout } from "./api/client"; import { CanvasArea } from "./components/CanvasArea"; import { LeftPanel } from "./components/LeftPanel"; import { RightInspector } from "./components/RightInspector"; import { TopBar } from "./components/TopBar"; import type { TopBarStatusTone } from "./components/TopBar"; import sampleFixture from "./fixtures/sample.schemeta.json"; import { useSchemetaActions, useSchemetaSelector } from "./hooks"; import { createSchemetaStore } from "./state"; import type { SchemetaModel } from "./state"; type EndpointActionId = "compile" | "analyze" | "layout-auto" | "layout-tidy"; type LocalActionId = "load-sample" | "reset-sample"; type WorkflowActionId = EndpointActionId | LocalActionId; type WorkflowAction = { id: WorkflowActionId; label: string; }; const SAMPLE_PAYLOAD = sampleFixture as SchemetaModel; function getPayloadOrThrow(payload: unknown): SchemetaModel { if (typeof payload !== "object" || payload === null) { throw new Error("No model loaded. Run 'Load Sample JSON' first."); } return payload as SchemetaModel; } function toCompileFromAnalyze(result: Awaited>): Record { return { api_version: result.api_version, schema_version: result.schema_version, request_id: result.request_id, ok: result.ok ?? true, errors: Array.isArray(result.errors) ? result.errors : [], warnings: Array.isArray(result.warnings) ? result.warnings : [], topology: result.topology ?? {} }; } export function App() { const store = useMemo(() => createSchemetaStore({ model: SAMPLE_PAYLOAD }), []); const actionsApi = useSchemetaActions(store); const model = useSchemetaSelector(store, (state) => state.model); const selection = useSchemetaSelector(store, (state) => state.selection); const compileResult = useSchemetaSelector(store, (state) => state.compileResult); const lifecycle = useSchemetaSelector(store, (state) => state.lifecycle); const jsonError = useSchemetaSelector(store, (state) => state.uiFlags.jsonError); const lastApiVersion = useSchemetaSelector(store, (state) => { const apiVersion = state.compileResult && typeof state.compileResult.api_version === "string" ? state.compileResult.api_version : null; return apiVersion ?? "-"; }); const actions = useMemo( () => [ { id: "load-sample", label: "Load Sample JSON" }, { id: "compile", label: "Compile" }, { id: "analyze", label: "Analyze" }, { id: "layout-auto", label: "Auto Layout" }, { id: "layout-tidy", label: "Auto Tidy" }, { id: "reset-sample", label: "Reset Sample" } ], [] ); const status = jsonError ? `JSON parse failed: ${jsonError}` : lifecycle.isCompiling ? `${lifecycle.lastAction ?? "Working"}...` : lifecycle.lastError ? `${lifecycle.lastAction ?? "Action"} failed: ${lifecycle.lastError}` : lifecycle.lastAction ? `${lifecycle.lastAction} complete` : "Idle"; const statusTone: TopBarStatusTone = jsonError ? "error" : lifecycle.isCompiling ? "busy" : lifecycle.lastError ? "error" : lifecycle.lastAction ? "success" : "idle"; async function runAction(actionId: WorkflowActionId) { const action = actions.find((item) => item.id === actionId); if (!action) { return; } actionsApi.beginCompile(action.label); try { if (action.id === "load-sample" || action.id === "reset-sample") { const parsed = actionsApi.applyJsonText(JSON.stringify(SAMPLE_PAYLOAD)); if (!parsed.ok) { throw new Error(parsed.error); } actionsApi.setCompileResult(null); actionsApi.setSelection({ selectedRefs: [], selectedNet: null, selectedPin: null }); actionsApi.setViewport({ scale: 1, panX: 40, panY: 40 }); actionsApi.completeCompile(action.label); return; } const payload = getPayloadOrThrow(store.getState().model); switch (action.id) { case "compile": { const result = await compile(payload); actionsApi.setCompileResult(result); } break; case "analyze": { const result = await analyze(payload); actionsApi.setCompileResult(toCompileFromAnalyze(result)); } break; case "layout-auto": { const result = await autoLayout(payload); if (result.model) { actionsApi.setModel(result.model); } if (result.compile) { actionsApi.setCompileResult(result.compile); } } break; case "layout-tidy": { const result = await tidyLayout(payload); if (result.model) { actionsApi.setModel(result.model); } if (result.compile) { actionsApi.setCompileResult(result.compile); } } break; } actionsApi.completeCompile(action.label); } catch (err) { actionsApi.failCompile(action.label, err); } } return (
({ id: action.id, label: action.label, onClick: () => { void runAction(action.id); }, disabled: lifecycle.isCompiling }))} statusMessage={status} statusTone={statusTone} apiVersion={lastApiVersion} />
actionsApi.setSelection({ selectedRefs: selection.selectedRefs.includes(ref) ? [] : [ref], selectedNet: null, selectedPin: null }) } onSelectNet={(name) => actionsApi.setSelection({ selectedRefs: [], selectedNet: selection.selectedNet === name ? null : name, selectedPin: null }) } />
); }