import { Dispatch, memo, ReactElement, SetStateAction, useEffect, useState } from "react"; import { BigStep, TimerElapseEvent, Timers } from "../statecharts/runtime_types"; import { getSimTime, setPaused, setRealtime, TimeMode } from "../statecharts/time"; import { Statechart } from "../statecharts/abstract_syntax"; import CachedIcon from '@mui/icons-material/Cached'; import PauseIcon from '@mui/icons-material/Pause'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import BoltIcon from '@mui/icons-material/Bolt'; import SkipNextIcon from '@mui/icons-material/SkipNext'; import SkipPreviousIcon from '@mui/icons-material/SkipPrevious';import TrendingFlatIcon from '@mui/icons-material/TrendingFlat'; import AccessAlarmIcon from '@mui/icons-material/AccessAlarm'; import StopIcon from '@mui/icons-material/Stop'; import InfoOutlineIcon from '@mui/icons-material/InfoOutline'; import KeyboardIcon from '@mui/icons-material/Keyboard'; import { formatTime } from "./util"; import { InsertMode } from "../VisualEditor/VisualEditor"; import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo"; import { About } from "./About"; import { RountangleIcon, PseudoStateIcon, HistoryIcon } from "./Icons"; import { EditHistory, TraceState } from "./App"; import { ZoomButtons } from "./TopPanel/ZoomButtons"; import { UndoRedoButtons } from "./TopPanel/UndoRedoButtons"; export type TopPanelProps = { trace: TraceState | null, time: TimeMode, setTime: Dispatch>, onUndo: () => void, onRedo: () => void, onInit: () => void, onClear: () => void, // onRaise: (e: string, p: any) => void, onBack: () => void, // ast: Statechart, insertMode: InsertMode, setInsertMode: Dispatch>, setModal: Dispatch>, zoom: number, setZoom: Dispatch>, showKeys: boolean, setShowKeys: Dispatch>, history: EditHistory, } const ShortCutShowKeys = ~; const insertModes: [InsertMode, string, ReactElement, ReactElement][] = [ ["and", "AND-states", , A], ["or", "OR-states", , O], ["pseudo", "pseudo-states", , P], ["shallow", "shallow history", , H], ["deep", "deep history", , <>], ["transition", "transitions", , T], ["text", "text", <> T , X], ]; export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, history}: TopPanelProps) { const [displayTime, setDisplayTime] = useState("0.000"); const [timescale, setTimescale] = useState(1); const config = trace && trace.trace[trace.idx]; const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden; useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (!e.ctrlKey) { if (e.key === " ") { e.preventDefault(); if (config) { onChangePaused(time.kind !== "paused", Math.round(performance.now())); } }; if (e.key === "i") { e.preventDefault(); onInit(); } if (e.key === "c") { e.preventDefault(); onClear(); } if (e.key === "Tab") { if (trace === null) { onInit(); } else { onSkip(); } e.preventDefault(); } if (e.key === "s") { e.preventDefault(); onSlower(); } if (e.key === "f") { e.preventDefault(); onFaster(); } if (e.key === "`") { e.preventDefault(); setShowKeys(show => !show); } if (e.key === "Backspace") { e.preventDefault(); onBack(); } } else { // ctrl is down if (e.key === "z") { e.preventDefault(); onUndo(); } if (e.key === "Z") { e.preventDefault(); onRedo(); } } }; window.addEventListener("keydown", onKeyDown); return () => { window.removeEventListener("keydown", onKeyDown); }; }, [time, onInit, timescale]); function updateDisplayedTime() { const now = Math.round(performance.now()); const timeMs = getSimTime(time, now); setDisplayTime(formatTime(timeMs)); } useEffect(() => { // This has no effect on statechart execution. In between events, the statechart is doing nothing. However, by updating the displayed time, we give the illusion of continuous progress. const interval = setInterval(() => { updateDisplayedTime(); }, 43); // every X ms -> we want a value that makes the numbers 'dance' while not using too much CPU return () => { clearInterval(interval); } }, [time]); function onChangePaused(paused: boolean, wallclktime: number) { setTime(time => { if (paused) { return setPaused(time, wallclktime); } else { return setRealtime(time, timescale, wallclktime); } }); updateDisplayedTime(); } function onTimeScaleChange(newValue: string, wallclktime: number) { const asFloat = parseFloat(newValue); if (Number.isNaN(asFloat)) { return; } const maxed = Math.min(asFloat, 64); const mined = Math.max(maxed, 1/64); setTimescale(mined); setTime(time => { if (time.kind === "paused") { return time; } else { return setRealtime(time, mined, wallclktime); } }); } // timestamp of next timed transition, in simulated time const timers: Timers = config?.kind === "bigstep" && config.environment.get("_timers") || []; const nextTimedTransition: [number, TimerElapseEvent] | undefined = timers[0]; function onSkip() { const now = Math.round(performance.now()); if (nextTimedTransition) { setTime(time => { if (time.kind === "paused") { return {kind: "paused", simtime: nextTimedTransition[0]}; } else { return {kind: "realtime", scale: time.scale, since: {simtime: nextTimedTransition[0], wallclktime: now}}; } }); } } function onSlower() { onTimeScaleChange((timescale/2).toString(), Math.round(performance.now())); } function onFaster() { onTimeScaleChange((timescale*2).toString(), Math.round(performance.now())); } return
{/* shortcuts / about */}
{/* zoom */}
{/* undo / redo */}
{/* insert rountangle / arrow / ... */}
{insertModes.map(([m, hint, buttonTxt, keyInfo]) => )}  
{/* execution */}
{/* init / clear / pause / real time */}
I}> C}> Space toggles}>
{/* speed */}
  S}> onTimeScaleChange(e.target.value, Math.round(performance.now()))}/> F}>
{/* time, next */}
 
  Tab}>
{/* input events */} {/*
{ast.inputEvents && <> {ast.inputEvents.map(({event, paramName}) =>
{paramName && <> }  
)} }
*/}
; });