user can select 'plant' from dropdown menu
This commit is contained in:
parent
0da2c793cd
commit
dfbcca5c14
5 changed files with 77 additions and 9 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { Dispatch, ReactElement, SetStateAction, useEffect, useRef, useState } from "react";
|
import { createElement, Dispatch, ReactElement, SetStateAction, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { emptyStatechart, Statechart } from "../statecharts/abstract_syntax";
|
import { emptyStatechart, Statechart } from "../statecharts/abstract_syntax";
|
||||||
import { handleInputEvent, initialize } from "../statecharts/interpreter";
|
import { handleInputEvent, initialize } from "../statecharts/interpreter";
|
||||||
|
|
@ -18,8 +18,11 @@ import { TraceableError } from "../statecharts/parser";
|
||||||
import { getKeyHandler } from "./shortcut_handler";
|
import { getKeyHandler } from "./shortcut_handler";
|
||||||
import { BottomPanel } from "./BottomPanel";
|
import { BottomPanel } from "./BottomPanel";
|
||||||
import { emptyState } from "@/statecharts/concrete_syntax";
|
import { emptyState } from "@/statecharts/concrete_syntax";
|
||||||
import { usePersistentState } from "@/util/persistent_state";
|
|
||||||
import { PersistentDetails } from "./PersistentDetails";
|
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 = {
|
type EditHistory = {
|
||||||
current: VisualEditorState,
|
current: VisualEditorState,
|
||||||
|
|
@ -27,6 +30,11 @@ type EditHistory = {
|
||||||
future: VisualEditorState[],
|
future: VisualEditorState[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const plants: [string, Plant<any>][] = [
|
||||||
|
["dummy", DummyPlant],
|
||||||
|
["digital watch", DigitalWatchPlant],
|
||||||
|
]
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [mode, setMode] = useState<InsertMode>("and");
|
const [mode, setMode] = useState<InsertMode>("and");
|
||||||
const [historyState, setHistoryState] = useState<EditHistory>({current: emptyState, history: [], future: []});
|
const [historyState, setHistoryState] = useState<EditHistory>({current: emptyState, history: [], future: []});
|
||||||
|
|
@ -36,6 +44,9 @@ export function App() {
|
||||||
const [rtIdx, setRTIdx] = useState<number|undefined>();
|
const [rtIdx, setRTIdx] = useState<number|undefined>();
|
||||||
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
||||||
const [modal, setModal] = useState<ReactElement|null>(null);
|
const [modal, setModal] = useState<ReactElement|null>(null);
|
||||||
|
const [plantName, setPlantName] = usePersistentState("plant", "dummy");
|
||||||
|
|
||||||
|
const plant = plants.find(([pn, p]) => pn === plantName)![1];
|
||||||
|
|
||||||
const editorState = historyState.current;
|
const editorState = historyState.current;
|
||||||
const setEditorState = (cb: (value: VisualEditorState) => VisualEditorState) => {
|
const setEditorState = (cb: (value: VisualEditorState) => VisualEditorState) => {
|
||||||
|
|
@ -192,6 +203,19 @@ export function App() {
|
||||||
|
|
||||||
const highlightTransitions = (rtIdx === undefined) ? [] : rt[rtIdx].firedTransitions;
|
const highlightTransitions = (rtIdx === undefined) ? [] : rt[rtIdx].firedTransitions;
|
||||||
|
|
||||||
|
|
||||||
|
const plantStates = [];
|
||||||
|
let ps = plant.initial(e => {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
for (let i=0; i<rt.length; i++) {
|
||||||
|
const r = rt[i];
|
||||||
|
for (const o of r.outputEvents) {
|
||||||
|
ps = plant.reducer(o, ps);
|
||||||
|
}
|
||||||
|
plantStates.push(ps);
|
||||||
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
|
||||||
{/* Modal dialog */}
|
{/* Modal dialog */}
|
||||||
|
|
@ -270,7 +294,8 @@ export function App() {
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
flexGrow:1,
|
flexGrow:1,
|
||||||
overflow:'auto',
|
overflow:'auto',
|
||||||
minHeight: '75%', // <-- allows us to always scroll down the sidebar far enough such that the execution history is enough in view
|
minHeight: 400,
|
||||||
|
// minHeight: '75%', // <-- allows us to always scroll down the sidebar far enough such that the execution history is enough in view
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{ height: '100%'}}>
|
<Box sx={{ height: '100%'}}>
|
||||||
<div ref={refRightSideBar}>
|
<div ref={refRightSideBar}>
|
||||||
|
|
@ -278,6 +303,20 @@ export function App() {
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box sx={{flex: '0 0 content'}}>
|
||||||
|
<PersistentDetails localStorageKey="showPlant" initiallyOpen={true}>
|
||||||
|
<summary>plant</summary>
|
||||||
|
<select
|
||||||
|
value={plantName}
|
||||||
|
onChange={e => setPlantName(() => e.target.value)}>
|
||||||
|
{plants.map(([plantName, p]) =>
|
||||||
|
<option>{plantName}</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
{rtIdx!==undefined && <plant.render {...plantStates[rtIdx]}/>}
|
||||||
|
{/* <DigitalWatch alarm={true} light={true} h={12} m={30} s={33}/> */}
|
||||||
|
</PersistentDetails>
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import "./BottomPanel.css";
|
||||||
|
|
||||||
import head from "../head.svg" ;
|
import head from "../head.svg" ;
|
||||||
import { PersistentDetails } from "./PersistentDetails";
|
import { PersistentDetails } from "./PersistentDetails";
|
||||||
import { DigitalWatch } from "@/Plant/DigitalWatch/DigitalWatch";
|
|
||||||
|
|
||||||
export function BottomPanel(props: {errors: TraceableError[]}) {
|
export function BottomPanel(props: {errors: TraceableError[]}) {
|
||||||
const [greeting, setGreeting] = useState(<><b><img src={head} style={{transform: "scaleX(-1)"}}/> "Welcome to StateBuddy, buddy!"</b><br/></>);
|
const [greeting, setGreeting] = useState(<><b><img src={head} style={{transform: "scaleX(-1)"}}/> "Welcome to StateBuddy, buddy!"</b><br/></>);
|
||||||
|
|
@ -18,7 +17,6 @@ export function BottomPanel(props: {errors: TraceableError[]}) {
|
||||||
|
|
||||||
return <div className="toolbar bottom">
|
return <div className="toolbar bottom">
|
||||||
{greeting}
|
{greeting}
|
||||||
<DigitalWatch alarm={true} light={true} h={12} m={30} s={33}/>
|
|
||||||
{props.errors.length > 0 &&
|
{props.errors.length > 0 &&
|
||||||
<div className="errorStatus">
|
<div className="errorStatus">
|
||||||
<PersistentDetails initiallyOpen={false} localStorageKey="errorsExpanded">
|
<PersistentDetails initiallyOpen={false} localStorageKey="errorsExpanded">
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,15 @@ import digitalFont from "./digital-font.ttf";
|
||||||
import { Plant } from "../Plant";
|
import { Plant } from "../Plant";
|
||||||
import { RaisedEvent } from "@/statecharts/runtime_types";
|
import { RaisedEvent } from "@/statecharts/runtime_types";
|
||||||
|
|
||||||
type DigitalWatchProps = {
|
type DigitalWatchState = {
|
||||||
light: boolean;
|
light: boolean;
|
||||||
h: number;
|
h: number;
|
||||||
m: number;
|
m: number;
|
||||||
s: number;
|
s: number;
|
||||||
alarm: boolean;
|
alarm: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DigitalWatchProps = DigitalWatchState & {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
onTopLeftPressed: () => void;
|
onTopLeftPressed: () => void;
|
||||||
onTopRightPressed: () => void;
|
onTopRightPressed: () => void;
|
||||||
|
|
@ -26,7 +29,7 @@ type DigitalWatchProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DigitalWatch({light, h, m, s, alarm, callbacks}: DigitalWatchProps) {
|
export function DigitalWatch({light, h, m, s, alarm, callbacks}: DigitalWatchProps) {
|
||||||
const refText = useRef(null);
|
console.log(light, h, m);
|
||||||
const twoDigits = (n: number) => ("0"+n.toString()).slice(-2);
|
const twoDigits = (n: number) => ("0"+n.toString()).slice(-2);
|
||||||
const hhmmss = `${twoDigits(h)}:${twoDigits(m)}:${twoDigits(s)}`;
|
const hhmmss = `${twoDigits(h)}:${twoDigits(m)}:${twoDigits(s)}`;
|
||||||
|
|
||||||
|
|
@ -43,7 +46,7 @@ export function DigitalWatch({light, h, m, s, alarm, callbacks}: DigitalWatchPro
|
||||||
: <image width="222" height="236" xlinkHref={imgWatch}/>
|
: <image width="222" height="236" xlinkHref={imgWatch}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<text ref={refText} x="111" y="126" dominant-baseline="middle" text-anchor="middle" fontFamily="digital-font" fontSize={28}>{hhmmss}</text>
|
<text x="111" y="126" dominant-baseline="middle" text-anchor="middle" fontFamily="digital-font" fontSize={28}>{hhmmss}</text>
|
||||||
|
|
||||||
<rect x="0" y="59" width="16" height="16" fill="#fff" fill-opacity="0"
|
<rect x="0" y="59" width="16" height="16" fill="#fff" fill-opacity="0"
|
||||||
onMouseDown={() => callbacks.onTopLeftPressed()}
|
onMouseDown={() => callbacks.onTopLeftPressed()}
|
||||||
|
|
@ -87,6 +90,23 @@ export const DigitalWatchPlant: Plant<DigitalWatchProps> = {
|
||||||
{ kind: "event", event: "bottomRightReleased" },
|
{ kind: "event", event: "bottomRightReleased" },
|
||||||
{ kind: "event", event: "bottomLeftReleased" },
|
{ kind: "event", event: "bottomLeftReleased" },
|
||||||
],
|
],
|
||||||
|
initial: (raise: (event: RaisedEvent) => void) => ({
|
||||||
|
light: false,
|
||||||
|
alarm: false,
|
||||||
|
h: 12,
|
||||||
|
m: 0,
|
||||||
|
s: 0,
|
||||||
|
callbacks: {
|
||||||
|
onTopLeftPressed: () => raise({ name: "topLeftPressed" }),
|
||||||
|
onTopRightPressed: () => raise({ name: "topRightPressed" }),
|
||||||
|
onBottomRightPressed: () => raise({ name: "bottomRightPressed" }),
|
||||||
|
onBottomLeftPressed: () => raise({ name: "bottomLeftPressed" }),
|
||||||
|
onTopLeftReleased: () => raise({ name: "topLeftReleased" }),
|
||||||
|
onTopRightReleased: () => raise({ name: "topRightReleased" }),
|
||||||
|
onBottomRightReleased: () => raise({ name: "bottomRightReleased" }),
|
||||||
|
onBottomLeftReleased: () => raise({ name: "bottomLeftReleased" }),
|
||||||
|
},
|
||||||
|
}),
|
||||||
reducer: (inputEvent: RaisedEvent, state: DigitalWatchProps) => {
|
reducer: (inputEvent: RaisedEvent, state: DigitalWatchProps) => {
|
||||||
if (inputEvent.name === "setH") {
|
if (inputEvent.name === "setH") {
|
||||||
return { ...state, h: inputEvent.param };
|
return { ...state, h: inputEvent.param };
|
||||||
|
|
|
||||||
10
src/Plant/Dummy/Dummy.tsx
Normal file
10
src/Plant/Dummy/Dummy.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { RaisedEvent } from "@/statecharts/runtime_types";
|
||||||
|
import { Plant } from "../Plant";
|
||||||
|
|
||||||
|
export const DummyPlant: Plant<{}> = {
|
||||||
|
inputEvents: [],
|
||||||
|
outputEvents: [],
|
||||||
|
initial: () => ({}),
|
||||||
|
reducer: (_inputEvent: RaisedEvent, _state: {}) => ({}),
|
||||||
|
render: (_state: {}) => <></>,
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ export type Plant<StateType> = {
|
||||||
inputEvents: EventTrigger[];
|
inputEvents: EventTrigger[];
|
||||||
outputEvents: EventTrigger[];
|
outputEvents: EventTrigger[];
|
||||||
|
|
||||||
|
initial: (raise: (event: RaisedEvent) => void) => StateType;
|
||||||
reducer: (inputEvent: RaisedEvent, state: StateType) => StateType;
|
reducer: (inputEvent: RaisedEvent, state: StateType) => StateType;
|
||||||
render: (state: StateType) => ReactElement;
|
render: (state: StateType) => ReactElement;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue