toolbar button to undo last step
This commit is contained in:
parent
59d5e9913a
commit
5e60b3dc95
8 changed files with 99 additions and 31 deletions
|
|
@ -56,6 +56,28 @@ export function App() {
|
|||
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;
|
||||
|
|
@ -128,15 +150,21 @@ export function App() {
|
|||
borderBottom: 1,
|
||||
borderColor: "divider",
|
||||
alignItems: 'center',
|
||||
flex: '0 0 content',
|
||||
}}>
|
||||
<TopPanel
|
||||
rt={rtIdx === undefined ? undefined : rt[rtIdx]}
|
||||
{...{ast, time, setTime, onInit, onClear, onRaise, mode, setMode}}
|
||||
{...{rtIdx, ast, time, setTime, onInit, onClear, onRaise, onBack, mode, setMode}}
|
||||
/>
|
||||
</Box>
|
||||
<Stack direction="row" sx={{height:'calc(100vh - 64px)'}}>
|
||||
<Stack direction="row" sx={{
|
||||
overflow: 'auto',
|
||||
}}>
|
||||
{/* main */}
|
||||
<Box sx={{flexGrow:1, overflow:'auto'}}>
|
||||
<Box sx={{
|
||||
flexGrow:1,
|
||||
overflow:'auto',
|
||||
}}>
|
||||
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors, mode, highlightActive, highlightTransitions}}/>
|
||||
</Box>
|
||||
{/* right sidebar */}
|
||||
|
|
@ -145,7 +173,6 @@ export function App() {
|
|||
borderLeft: 1,
|
||||
borderColor: "divider",
|
||||
flex: '0 0 content',
|
||||
height: 'calc(100vh-32px)',
|
||||
overflow: "auto",
|
||||
}}>
|
||||
<ShowAST {...{...ast, rt: rt.at(rtIdx!), highlightActive}}/>
|
||||
|
|
@ -155,7 +182,9 @@ export function App() {
|
|||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Box sx={{
|
||||
flex: '0 0 content',
|
||||
}}>
|
||||
<BottomPanel {...{errors}}/>
|
||||
</Box>
|
||||
</Stack>;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
.errorStatus {
|
||||
color: rgb(230,0,0);
|
||||
/* background-color: rgb(230,0,0); */
|
||||
background-color: var(--error-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
background-color: lightyellow;
|
||||
}
|
||||
|
|
@ -1,10 +1,22 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { TraceableError } from "../statecharts/parser";
|
||||
|
||||
import "./BottomPanel.css";
|
||||
|
||||
export function BottomPanel(props: {errors: TraceableError[]}) {
|
||||
return <div className="toolbar">
|
||||
<div className="errorStatus">{
|
||||
props.errors.length>0 && <>{props.errors.length} errors {props.errors.map(({message})=>message).join(',')}</>}</div>
|
||||
const [greeting, setGreeting] = useState("Welcome to StateBuddy, buddy!");
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setGreeting("");
|
||||
}, 2000);
|
||||
}, []);
|
||||
|
||||
return <div className="toolbar bottom">
|
||||
<>{greeting}</>
|
||||
{props.errors.length > 0 &&
|
||||
<div className="errorStatus">
|
||||
{props.errors.length>0 && <>{props.errors.length} errors: {props.errors.map(({message})=>message).join(', ')}</>}
|
||||
</div>}
|
||||
</div>;
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ import { Stack } from "@mui/material";
|
|||
|
||||
export function KeyInfoVisible(props: {keyInfo, children}) {
|
||||
return <Stack style={{display: "inline-block"}}>
|
||||
<div style={{fontSize:11, height: 16, textAlign:"center"}}>
|
||||
<div style={{fontSize:11, height: 18, textAlign:"center", paddingLeft: 3, paddingRight: 3}}>
|
||||
{props.keyInfo}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{textAlign:"center"}}>
|
||||
{props.children}
|
||||
</div>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ 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 TrendingFlatIcon from '@mui/icons-material/TrendingFlat';
|
||||
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 UndoIcon from '@mui/icons-material/Undo';
|
||||
|
|
@ -20,11 +20,13 @@ import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
|||
|
||||
export type TopPanelProps = {
|
||||
rt?: BigStep,
|
||||
rtIdx?: number,
|
||||
time: TimeMode,
|
||||
setTime: Dispatch<SetStateAction<TimeMode>>,
|
||||
onInit: () => void,
|
||||
onClear: () => void,
|
||||
onRaise: (e: string, p: any) => void,
|
||||
onBack: () => void,
|
||||
ast: Statechart,
|
||||
mode: InsertMode,
|
||||
setMode: Dispatch<SetStateAction<InsertMode>>,
|
||||
|
|
@ -61,7 +63,7 @@ function HistoryIcon(props: {kind: "shallow"|"deep"}) {
|
|||
}
|
||||
|
||||
|
||||
export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode, setMode}: TopPanelProps) {
|
||||
export function TopPanel({rt, rtIdx, time, setTime, onInit, onClear, onRaise, onBack, ast, mode, setMode}: TopPanelProps) {
|
||||
const [displayTime, setDisplayTime] = useState("0.000");
|
||||
const [timescale, setTimescale] = useState(1);
|
||||
const [showKeys, setShowKeys] = useState(true);
|
||||
|
|
@ -99,6 +101,10 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
|
|||
e.preventDefault();
|
||||
setShowKeys(show => !show);
|
||||
}
|
||||
if (e.key === "Backspace") {
|
||||
e.preventDefault();
|
||||
onBack();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
|
|
@ -186,9 +192,20 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
|
|||
return <>
|
||||
<div className="toolbar">
|
||||
|
||||
<div style={{display:'inline-block'}}>
|
||||
<div style={{display:'inline-block'}}>
|
||||
|
||||
<div style={{display:'inline-block'}}>
|
||||
<KeyInfo keyInfo={<><kbd>Ctrl</kbd>+<kbd>Z</kbd></>}>
|
||||
<button title="undo"><UndoIcon fontSize="small"/></button>
|
||||
</KeyInfo>
|
||||
<KeyInfo keyInfo={<><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd></>}>
|
||||
<button title="redo"><RedoIcon fontSize="small"/></button>
|
||||
</KeyInfo>
|
||||
</div>
|
||||
|
||||
 
|
||||
|
||||
<div style={{display:'inline-block'}}>
|
||||
{([
|
||||
["and", "AND-states", <RountangleIcon kind="and"/>, <kbd>A</kbd>],
|
||||
["or", "OR-states", <RountangleIcon kind="or"/>, <kbd>O</kbd>],
|
||||
|
|
@ -205,7 +222,6 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
|
|||
className={mode===m ? "active":""}
|
||||
onClick={() => setMode(m)}
|
||||
>{buttonTxt}</button></KeyInfo>)}
|
||||
|
||||
</div>
|
||||
|
||||
 
|
||||
|
|
@ -221,7 +237,7 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
|
|||
|
||||
 
|
||||
|
||||
<KeyInfo keyInfo={<kbd>Space</kbd>}>
|
||||
<KeyInfo keyInfo={<><kbd>Space</kbd> toggles</>}>
|
||||
<button title="pause the simulation" disabled={!rt || time.kind==="paused"} className={(rt && time.kind==="paused") ? "active":""} onClick={() => onChangePaused(true, performance.now())}><PauseIcon fontSize="small"/></button>
|
||||
<button title="run the simulation in real time" disabled={!rt || time.kind==="realtime"} className={(rt && time.kind==="realtime") ? "active":""} onClick={() => onChangePaused(false, performance.now())}><PlayArrowIcon fontSize="small"/></button>
|
||||
</KeyInfo>
|
||||
|
|
@ -253,6 +269,12 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
|
|||
<button title="advance time just enough for the next timer to elapse" disabled={nextTimedTransition===undefined} onClick={onSkip}><SkipNextIcon fontSize="small"/><AccessAlarmIcon fontSize="small"/></button>
|
||||
</KeyInfo>
|
||||
|
||||
 
|
||||
<KeyInfo keyInfo={<kbd>Backspace</kbd>}>
|
||||
<button title="undo last step (go back in time)"
|
||||
disabled={rtIdx===undefined || rtIdx===0} onClick={onBack}><SkipPreviousIcon fontSize="small"/></button>
|
||||
</KeyInfo>
|
||||
|
||||
</div>
|
||||
|
||||
 
|
||||
|
|
@ -291,7 +313,7 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
|
|||
<div style={{display:"inline-block"}}>
|
||||
<KeyInfo keyInfo={<kbd>~</kbd>}>
|
||||
<input id="checkbox-keys" type="checkbox" checked={showKeys} onChange={e => setShowKeys(e.target.checked)}></input>
|
||||
<label for="checkbox-keys">shortcuts</label>
|
||||
<label for="checkbox-keys">see shortcuts</label>
|
||||
</KeyInfo>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -39,19 +39,12 @@
|
|||
/* fill: rgba(0, 0, 255, 0.2); */
|
||||
}
|
||||
.rountangle.error {
|
||||
stroke: rgb(230,0,0);
|
||||
stroke: var(--error-color);
|
||||
}
|
||||
.rountangle.active {
|
||||
/* fill: rgb(255, 140, 0); */
|
||||
/* fill-opacity: 0.2; */
|
||||
/* stroke: rgb(100, 149, 237); */
|
||||
/* stroke: */
|
||||
stroke: rgb(192, 125, 0);
|
||||
fill:rgb(255, 251, 244);
|
||||
/* fill: lightgrey; */
|
||||
/* color: white; */
|
||||
filter: drop-shadow( 0px 0px 3px rgba(192, 125, 0, 0.856));
|
||||
/* stroke-width: 3px; */
|
||||
filter: drop-shadow( 0px 0px 3px rgba(192, 125, 0, 0.85));
|
||||
}
|
||||
|
||||
.selected:hover:not(:active) {
|
||||
|
|
@ -166,7 +159,7 @@ text.helper:hover {
|
|||
}
|
||||
|
||||
.arrow.error {
|
||||
stroke: rgb(230,0,0);
|
||||
stroke: var(--error-color);
|
||||
}
|
||||
.arrow.fired {
|
||||
stroke: rgb(192, 125, 0);
|
||||
|
|
@ -174,7 +167,7 @@ text.helper:hover {
|
|||
}
|
||||
|
||||
text.error, tspan.error {
|
||||
fill: rgb(230,0,0);
|
||||
fill: var(--error-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -314,7 +314,8 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh
|
|||
const onMouseMove = (e: {pageX: number, pageY: number}) => {
|
||||
const currentPointer = getCurrentPointer(e);
|
||||
if (dragging) {
|
||||
const pointerDelta = subtractV2D(currentPointer, dragging.lastMousePos);
|
||||
// const pointerDelta = subtractV2D(currentPointer, dragging.lastMousePos);
|
||||
const pointerDelta = {x: e.movementX, y: e.movementY};
|
||||
setState(state => ({
|
||||
...state,
|
||||
rountangles: state.rountangles.map(r => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ html, body {
|
|||
font-family: Roboto, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
/* --error-color: darkred; */
|
||||
--error-color: rgb(163, 0, 0);
|
||||
}
|
||||
|
||||
div#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -18,7 +23,7 @@ kbd {
|
|||
border: 0.8px solid #aaa;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(#ebebeb, #fff);
|
||||
box-shadow: inset 0 -2px 0 #aaa;
|
||||
box-shadow: inset 0 -1.5px 0 #aaa;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -38,4 +43,4 @@ input {
|
|||
|
||||
label {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue