import { createElement, Dispatch, ReactElement, SetStateAction, useEffect, useRef, useState } from "react"; import { emptyStatechart, Statechart } from "../statecharts/abstract_syntax"; import { handleInputEvent, initialize } from "../statecharts/interpreter"; import { BigStep, BigStepOutput } from "../statecharts/runtime_types"; import { InsertMode, VisualEditor, VisualEditorState } from "../VisualEditor/VisualEditor"; import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time"; import "../index.css"; import "./App.css"; import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import { TopPanel } from "./TopPanel"; import { RTHistory } from "./RTHistory"; import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST"; import { TraceableError } from "../statecharts/parser"; import { getKeyHandler } from "./shortcut_handler"; import { BottomPanel } from "./BottomPanel"; import { emptyState } from "@/statecharts/concrete_syntax"; import { PersistentDetails } from "./PersistentDetails"; import { DigitalWatch, DigitalWatchPlant } from "@/Plant/DigitalWatch/DigitalWatch"; import { DummyPlant } from "@/Plant/Dummy/Dummy"; import { Plant } from "@/Plant/Plant"; import { usePersistentState } from "@/util/persistent_state"; type EditHistory = { current: VisualEditorState, history: VisualEditorState[], future: VisualEditorState[], } const plants: [string, Plant][] = [ ["dummy", DummyPlant], ["digital watch", DigitalWatchPlant], ] export function App() { const [mode, setMode] = useState("and"); const [historyState, setHistoryState] = useState({current: emptyState, history: [], future: []}); const [ast, setAST] = useState(emptyStatechart); const [errors, setErrors] = useState([]); const [rt, setRT] = useState([]); const [rtIdx, setRTIdx] = useState(); const [time, setTime] = useState({kind: "paused", simtime: 0}); const [modal, setModal] = useState(null); const [plantName, setPlantName] = usePersistentState("plant", "dummy"); const plant = plants.find(([pn, p]) => pn === plantName)![1]; const editorState = historyState.current; const setEditorState = (cb: (value: VisualEditorState) => VisualEditorState) => { setHistoryState(historyState => ({...historyState, current: cb(historyState.current)})); } const refRightSideBar = useRef(null); function makeCheckPoint() { setHistoryState(historyState => ({ ...historyState, history: [...historyState.history, historyState.current], future: [], })); } function onUndo() { setHistoryState(historyState => { if (historyState.history.length === 0) { return historyState; // no change } return { current: historyState.history.at(-1)!, history: historyState.history.slice(0,-1), future: [...historyState.future, historyState.current], } }) } function onRedo() { setHistoryState(historyState => { if (historyState.future.length === 0) { return historyState; // no change } return { current: historyState.future.at(-1)!, history: [...historyState.history, historyState.current], future: historyState.future.slice(0,-1), } }); } function onInit() { const config = initialize(ast); setRT([{inputEvent: null, simtime: 0, ...config}]); setRTIdx(0); setTime({kind: "paused", simtime: 0}); scrollDownSidebar(); } function onClear() { setRT([]); setRTIdx(undefined); setTime({kind: "paused", simtime: 0}); } function onRaise(inputEvent: string, param: any) { if (rt.length>0 && rtIdx!==undefined && ast.inputEvents.some(e => e.event === inputEvent)) { const simtime = getSimTime(time, Math.round(performance.now())); const nextConfig = handleInputEvent(simtime, {kind: "input", name: inputEvent, param}, ast, rt[rtIdx]!); appendNewConfig(inputEvent, simtime, nextConfig); } } function appendNewConfig(inputEvent: string, simtime: number, config: BigStepOutput) { setRT([...rt.slice(0, rtIdx!+1), {inputEvent, simtime, ...config}]); setRTIdx(rtIdx!+1); // console.log('new config:', config); scrollDownSidebar(); } function onBack() { setTime(() => { if (rtIdx !== undefined) { if (rtIdx > 0) return { kind: "paused", simtime: rt[rtIdx-1].simtime, } } return { kind: "paused", simtime: 0 }; }); setRTIdx(rtIdx => { if (rtIdx !== undefined) { if (rtIdx > 0) return rtIdx - 1; else return 0; } else return undefined; }) } function scrollDownSidebar() { if (refRightSideBar.current) { const el = refRightSideBar.current; // hack: we want to scroll to the new element, but we have to wait until it is rendered... setTimeout(() => { el.scrollIntoView({block: "end", behavior: "smooth"}); }, 50); } } useEffect(() => { console.log("Welcome to StateBuddy!"); () => { console.log("Goodbye!"); } }, []); useEffect(() => { let timeout: NodeJS.Timeout | undefined; if (rtIdx !== undefined) { const currentRt = rt[rtIdx]!; const timers = currentRt.environment.get("_timers") || []; if (timers.length > 0) { const [nextInterrupt, timeElapsedEvent] = timers[0]; const raiseTimeEvent = () => { const nextConfig = handleInputEvent(nextInterrupt, timeElapsedEvent, ast, currentRt); appendNewConfig('', nextInterrupt, nextConfig); } if (time.kind === "realtime") { const wallclkDelay = getWallClkDelay(time, nextInterrupt, Math.round(performance.now())); // console.log('scheduling timeout after', wallclkDelay); timeout = setTimeout(raiseTimeEvent, wallclkDelay); } else if (time.kind === "paused") { if (nextInterrupt <= time.simtime) { raiseTimeEvent(); } } } } return () => { if (timeout) clearTimeout(timeout); } }, [time, rtIdx]); useEffect(() => { const onKeyDown = getKeyHandler(setMode); window.addEventListener("keydown", onKeyDown); return () => { window.removeEventListener("keydown", onKeyDown); }; }, []); // const highlightActive = (rtIdx !== undefined) && new Set([...rt[rtIdx].mode].filter(uid => { // const state = ast.uid2State.get(uid); // return state && state.parent?.kind !== "and"; // })) || new Set(); const highlightActive: Set = (rtIdx === undefined) ? new Set() : rt[rtIdx].mode; const highlightTransitions = (rtIdx === undefined) ? [] : rt[rtIdx].firedTransitions; const plantStates = []; let ps = plant.initial(e => { onRaise(e.name, e.param); }); for (let i=0; i {/* Modal dialog */} {modal &&
setModal(null)}>
e.stopPropagation()}> {modal}
} {/* Left: top bar and main editor */} {/* Top bar */} {/* Below the top bar: Editor */} {/* Right: sidebar */} state tree
input events internal events output events
plant {rtIdx!==undefined && } {/* */}
{/* Bottom panel */}
; } export default App;