diff --git a/src/App/App.css b/src/App/App.css index 76f1272..51defe8 100644 --- a/src/App/App.css +++ b/src/App/App.css @@ -33,6 +33,9 @@ details:has(+ details) { background-color: rgba(0,0,255,0.2); border: solid blue 1px; } +.runtimeState.plantStep:not(.active) { + background-color: #f7f7f7; +} .runtimeState.plantStep * { color: grey; } diff --git a/src/App/App.tsx b/src/App/App.tsx index 4e9ff1c..b18d96a 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -5,10 +5,10 @@ import { Dispatch, ReactElement, SetStateAction, useCallback, useEffect, useMemo import AddIcon from '@mui/icons-material/Add'; import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; -import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; -import VisibilityIcon from '@mui/icons-material/Visibility'; -import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; import CachedOutlinedIcon from '@mui/icons-material/CachedOutlined'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; +import VisibilityIcon from '@mui/icons-material/Visibility'; import { Statechart } from "@/statecharts/abstract_syntax"; import { detectConnections } from "@/statecharts/detect_connections"; @@ -18,7 +18,7 @@ import { parseStatechart } from "../statecharts/parser"; import { BigStep, RaisedEvent } from "../statecharts/runtime_types"; import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time"; import { BottomPanel } from "./BottomPanel"; -import { PersistentDetails } from "./PersistentDetails"; +import { PersistentDetails, PersistentDetailsLocalStorage } from "./PersistentDetails"; import { digitalWatchPlant } from "./Plant/DigitalWatch/DigitalWatch"; import { dummyPlant } from "./Plant/Dummy/Dummy"; import { microwavePlant } from "./Plant/Microwave/Microwave"; @@ -26,14 +26,11 @@ import { Plant } from "./Plant/Plant"; import { trafficLightPlant } from "./Plant/TrafficLight/TrafficLight"; import { RTHistory } from "./RTHistory"; import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST"; -import { InsertMode } from "./TopPanel/InsertModes"; import { TopPanel } from "./TopPanel/TopPanel"; import { VisualEditor, VisualEditorState } from "./VisualEditor/VisualEditor"; import { checkProperty, PropertyCheckResult } from "./check_property"; -import { usePersistentState } from "./persistent_state"; import { useEditor } from "./useEditor"; import { useUrlHashState } from "./useUrlHashState"; -import { formatTime } from "@/util/util"; export type EditHistory = { current: VisualEditorState, @@ -112,6 +109,10 @@ export function App() { setInsertMode, plantName, setPlantName, + showConnections, + setShowConnections, + showProperties, + setShowProperties, showExecutionTrace, setShowExecutionTrace, showPlantTrace, @@ -417,33 +418,33 @@ export function App() { style={{flex: '0 0 content', backgroundColor: ''}} > {/* State tree */} - + state tree {ast && } - + {/* Input events */} - + input events {ast && onRaise("debug."+e,p)} disabled={trace===null || trace.trace[trace.idx].kind === "error"} showKeys={showKeys}/>} - + {/* Internal events */} - + internal events {ast && } - + {/* Output events */} - + output events {ast && } - + {/* Plant */} - + plant onRaise("plant.ui."+e.name, e.param)} />} - + {/* Connections */} - + connections setAutoConnect(c => !c)}> @@ -469,8 +470,13 @@ export function App() { {ast && ConnEditor(ast, plant, plantConns, setPlantConns)} {/* Properties */} - + setShowProperties(e.newState === "open")}> properties + {plant && + available signals: + + {plant.signals.join(', ')} + } {properties.map((property, i) => { const result = propertyResults && propertyResults[i]; let violated = null, propertyError = null; @@ -495,7 +501,7 @@ export function App() { add property - + {/* Traces */} setShowExecutionTrace(e.newState === "open")}>execution trace @@ -517,9 +523,9 @@ export function App() { setShowPlantTrace(e.target.checked)}/> - show plant steps + show plant steps setAutoScroll(e.target.checked)}/> - auto-scroll + auto-scroll onSaveTrace()}> save trace @@ -617,9 +623,9 @@ function ConnEditor(ast: Statechart, plant: Plant, plantConns: Conns, )]} - {/* Plant UI events can go to SC or to Plant */} + {/* Plant UI events typically go to the Plant */} {plant.uiEvents.map(e => - ui.{e.event} → + ui.{e.event} → 0 && - + {props.errors.length} errors {props.errors.map(({message, shapeUid})=> @@ -32,7 +32,7 @@ export function BottomPanel(props: {errors: TraceableError[]}) { {shapeUid}: {message} )} - + } ; diff --git a/src/App/PersistentDetails.tsx b/src/App/PersistentDetails.tsx index 174ce71..4d24bb0 100644 --- a/src/App/PersistentDetails.tsx +++ b/src/App/PersistentDetails.tsx @@ -1,5 +1,5 @@ import { usePersistentState } from "@/App/persistent_state" -import { DetailsHTMLAttributes, PropsWithChildren } from "react"; +import { DetailsHTMLAttributes, Dispatch, PropsWithChildren, SetStateAction } from "react"; type Props = { localStorageKey: string, @@ -7,9 +7,15 @@ type Props = { } & DetailsHTMLAttributes; // A node that remembers whether it was open or closed by storing that state in localStorage. -export function PersistentDetails({localStorageKey, initiallyOpen, children, ...rest}: PropsWithChildren) { +export function PersistentDetailsLocalStorage({localStorageKey, initiallyOpen, children, ...rest}: PropsWithChildren) { const [open, setOpen] = usePersistentState(localStorageKey, initiallyOpen); return setOpen(e.newState === "open")} {...rest}> {children} ; } + +export function PersistentDetails({state, setState, children, ...rest}: PropsWithChildren<{state: boolean, setState: Dispatch>}>) { + return setState(e.newState === "open")} {...rest}> + {children} + ; +} diff --git a/src/App/Plant/DigitalWatch/DigitalWatch.tsx b/src/App/Plant/DigitalWatch/DigitalWatch.tsx index 88ab630..9b41f94 100644 --- a/src/App/Plant/DigitalWatch/DigitalWatch.tsx +++ b/src/App/Plant/DigitalWatch/DigitalWatch.tsx @@ -156,4 +156,21 @@ export const digitalWatchPlant = makeStatechartPlant({ { kind: "event", event: "bottomRightMouseUp" }, { kind: "event", event: "bottomLeftMouseUp" }, ], + signals: [ + "lightOn", + "beep", + "alarmOn", + "displayingTime", + "displayingAlarm", + "displayingChrono", + "hideH", + "hideM", + "hideS", + + // these properties are true for as long as the mouse button is down: + "topLeftPressed", + "topRightPressed", + "bottomRightPressed", + "bottomLeftPressed", + ], }); diff --git a/src/App/Plant/Dummy/Dummy.tsx b/src/App/Plant/Dummy/Dummy.tsx index 9620f7b..aff27ad 100644 --- a/src/App/Plant/Dummy/Dummy.tsx +++ b/src/App/Plant/Dummy/Dummy.tsx @@ -15,4 +15,5 @@ export const dummyPlant: Plant<{}, {}> = { execution: dummyExecution, cleanupState: ({}) => ({}), render: ({}) => <>>, + signals: [], }; diff --git a/src/App/Plant/Microwave/Microwave.tsx b/src/App/Plant/Microwave/Microwave.tsx index 5eee395..516b444 100644 --- a/src/App/Plant/Microwave/Microwave.tsx +++ b/src/App/Plant/Microwave/Microwave.tsx @@ -143,6 +143,16 @@ const microwavePlantSpec: StatechartPlantSpec = { {kind: "event", event: "incTimeMouseDown"}, {kind: "event", event: "incTimeMouseUp"}, ], + signals: [ + "bellRinging", + "magnetronRunning", + "doorOpen", + + // these booleans are true for as long as the respective button is pressed (i.e., mouse button is down) + "startPressed", + "stopPressed", + "incTimePressed", + ] } export const microwavePlant = makeStatechartPlant(microwavePlantSpec); diff --git a/src/App/Plant/Plant.ts b/src/App/Plant/Plant.ts index 5ddaf27..444c848 100644 --- a/src/App/Plant/Plant.ts +++ b/src/App/Plant/Plant.ts @@ -17,6 +17,8 @@ export type Plant = { inputEvents: EventTrigger[]; outputEvents: EventTrigger[]; + signals: string[]; // signal names. all signals are booleans. + execution: TimedReactive; cleanupState: (state: StateType) => CleanStateType; render: (props: PlantRenderProps) => ReactNode; @@ -59,9 +61,10 @@ export type StatechartPlantSpec = { ast: Statechart, cleanupState: (rtConfig: RT_Statechart) => CleanStateType, render: (props: PlantRenderProps) => ReactNode, + signals: string[], } -export function makeStatechartPlant({uiEvents, ast, cleanupState, render}: StatechartPlantSpec): Plant { +export function makeStatechartPlant({uiEvents, ast, cleanupState, render, signals}: StatechartPlantSpec): Plant { return { uiEvents, inputEvents: ast.inputEvents, @@ -69,6 +72,7 @@ export function makeStatechartPlant({uiEvents, ast, cleanupState execution: statechartExecution(ast), cleanupState, render, + signals, } } diff --git a/src/App/Plant/TrafficLight/TrafficLight.tsx b/src/App/Plant/TrafficLight/TrafficLight.tsx index cc78968..fb47f48 100644 --- a/src/App/Plant/TrafficLight/TrafficLight.tsx +++ b/src/App/Plant/TrafficLight/TrafficLight.tsx @@ -109,6 +109,13 @@ const trafficLightPlantSpec: StatechartPlantSpec = { uiEvents: [ {kind: "event", event: "policeInterrupt"}, ], + signals: [ + "redOn", + "yellowOn", + "greenOn", + "timerGreen", + "timerValue", + ], } export const trafficLightPlant = makeStatechartPlant(trafficLightPlantSpec); diff --git a/src/App/RTHistory.tsx b/src/App/RTHistory.tsx index d834399..938867d 100644 --- a/src/App/RTHistory.tsx +++ b/src/App/RTHistory.tsx @@ -90,6 +90,7 @@ function RTEventParam(props: {param?: any}) { export const RTHistoryItem = memo(function RTHistoryItem({ast, idx, item, prevItem, isPlantStep, active, onMouseDown, propertyStatus}: {idx: number, ast: Statechart, item: TraceItem, prevItem?: TraceItem, isPlantStep: boolean, active: boolean, onMouseDown: (idx: number, timestamp: number) => void, propertyStatus: PropertyStatus}) { if (item.kind === "bigstep") { + const outputEvents = isPlantStep ? item.state.plant.outputEvents : item.state.sc.outputEvents; // @ts-ignore const newStates = item.state.sc.mode.difference(prevItem?.state.sc.mode || new Set()); return - {item.state.sc.outputEvents.length>0 && <>^ - {item.state.sc.outputEvents.map((e:RaisedEvent) => {e.name})} + {outputEvents.length>0 && <>^ + {outputEvents.map((e:RaisedEvent) => {e.name})} >} ; } diff --git a/src/App/useUrlHashState.ts b/src/App/useUrlHashState.ts index d00a629..8a38759 100644 --- a/src/App/useUrlHashState.ts +++ b/src/App/useUrlHashState.ts @@ -8,7 +8,7 @@ import { Conns } from "@/statecharts/timed_reactive"; export function useUrlHashState(editorState: VisualEditorState | null, setEditHistory: Dispatch>) { // i should probably put all these things into a single object, the 'app state'... - const [autoScroll, setAutoScroll] = useState(true); + const [autoScroll, setAutoScroll] = useState(false); const [autoConnect, setAutoConnect] = useState(true); const [plantConns, setPlantConns] = useState({}); const [showKeys, setShowKeys] = useState(true); @@ -16,6 +16,8 @@ export function useUrlHashState(editorState: VisualEditorState | null, setEditHi const [insertMode, setInsertMode] = useState("and"); const [plantName, setPlantName] = useState("dummy"); + const [showConnections, setShowConnections] = useState(false); + const [showProperties, setShowProperties] = useState(false); const [showExecutionTrace, setShowExecutionTrace] = useState(true); const [showPlantTrace, setShowPlantTrace] = useState(false); const [properties, setProperties] = useState([]); @@ -82,6 +84,12 @@ export function useUrlHashState(editorState: VisualEditorState | null, setEditHi if (recoveredState.insertMode !== undefined) { setInsertMode(recoveredState.insertMode); } + if (recoveredState.showConnections !== undefined) { + setShowConnections(recoveredState.showConnections); + } + if (recoveredState.showProperties !== undefined) { + setShowProperties(recoveredState.showProperties); + } if (recoveredState.showExecutionTrace !== undefined) { setShowExecutionTrace(recoveredState.showExecutionTrace); } @@ -123,6 +131,8 @@ export function useUrlHashState(editorState: VisualEditorState | null, setEditHi insertMode, plantName, editorState, + showConnections, + showProperties, showExecutionTrace, showPlantTrace, properties, @@ -149,6 +159,8 @@ export function useUrlHashState(editorState: VisualEditorState | null, setEditHi zoom, insertMode, plantName, + showConnections, + showProperties, showExecutionTrace, showPlantTrace, properties, @@ -171,6 +183,10 @@ export function useUrlHashState(editorState: VisualEditorState | null, setEditHi setInsertMode, plantName, setPlantName, + showConnections, + setShowConnections, + showProperties, + setShowProperties, showExecutionTrace, setShowExecutionTrace, showPlantTrace,