getting rid of some code duplication
This commit is contained in:
parent
0266675f29
commit
970b9d850e
21 changed files with 325 additions and 302 deletions
|
|
@ -3,6 +3,7 @@ import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
|||
import { HistoryIcon, PseudoStateIcon, RountangleIcon } from "./Icons";
|
||||
|
||||
import TrendingFlatIcon from '@mui/icons-material/TrendingFlat';
|
||||
import { useShortcuts } from "@/hooks/useShortcuts";
|
||||
|
||||
export type InsertMode = "and" | "or" | "pseudo" | "shallow" | "deep" | "transition" | "text";
|
||||
|
||||
|
|
@ -18,45 +19,14 @@ const insertModes: [InsertMode, string, ReactElement, ReactElement][] = [
|
|||
|
||||
export const InsertModes = memo(function InsertModes({showKeys, insertMode, setInsertMode}: {showKeys: boolean, insertMode: InsertMode, setInsertMode: Dispatch<SetStateAction<InsertMode>>}) {
|
||||
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
// @ts-ignore
|
||||
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||
|
||||
if (!e.ctrlKey) {
|
||||
if (e.key === "a") {
|
||||
e.preventDefault();
|
||||
setInsertMode("and");
|
||||
}
|
||||
if (e.key === "o") {
|
||||
e.preventDefault();
|
||||
setInsertMode("or");
|
||||
}
|
||||
if (e.key === "p") {
|
||||
e.preventDefault();
|
||||
setInsertMode("pseudo");
|
||||
}
|
||||
if (e.key === "t") {
|
||||
e.preventDefault();
|
||||
setInsertMode("transition");
|
||||
}
|
||||
if (e.key === "x") {
|
||||
e.preventDefault();
|
||||
setInsertMode("text");
|
||||
}
|
||||
if (e.key === "h") {
|
||||
e.preventDefault();
|
||||
setInsertMode(oldMode => {
|
||||
if (oldMode === "shallow") return "deep";
|
||||
return "shallow";
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [setInsertMode]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
() => window.removeEventListener("keydown", onKeyDown);
|
||||
}, [onKeyDown]);
|
||||
useShortcuts([
|
||||
{keys: ["a"], action: () => setInsertMode("and")},
|
||||
{keys: ["o"], action: () => setInsertMode("or")},
|
||||
{keys: ["p"], action: () => setInsertMode("pseudo")},
|
||||
{keys: ["t"], action: () => setInsertMode("transition")},
|
||||
{keys: ["x"], action: () => setInsertMode("text")},
|
||||
{keys: ["h"], action: () => setInsertMode(mode => mode === "shallow" ? "deep" : "shallow")},
|
||||
]);
|
||||
|
||||
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
||||
return <>{insertModes.map(([m, hint, buttonTxt, keyInfo]) => <KeyInfo key={m} keyInfo={keyInfo}>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
|||
import { setRealtime, TimeMode } from "@/statecharts/time";
|
||||
|
||||
import SpeedIcon from '@mui/icons-material/Speed';
|
||||
import { useShortcuts } from "@/hooks/useShortcuts";
|
||||
|
||||
export const SpeedControl = memo(function SpeedControl({showKeys, timescale, setTimescale, setTime}: {showKeys: boolean, timescale: number, setTimescale: Dispatch<SetStateAction<number>>, setTime: Dispatch<SetStateAction<TimeMode>>}) {
|
||||
|
||||
|
|
@ -31,25 +32,10 @@ export const SpeedControl = memo(function SpeedControl({showKeys, timescale, set
|
|||
onTimeScaleChange((timescale*2).toString(), Math.round(performance.now()));
|
||||
}, [onTimeScaleChange, timescale]);
|
||||
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
// @ts-ignore
|
||||
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||
if (!e.ctrlKey) {
|
||||
if (e.key === "s") {
|
||||
e.preventDefault();
|
||||
onSlower();
|
||||
}
|
||||
if (e.key === "f") {
|
||||
e.preventDefault();
|
||||
onFaster();
|
||||
}
|
||||
}
|
||||
}, [onSlower, onFaster])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}, [onKeyDown])
|
||||
useShortcuts([
|
||||
{keys: ["s"], action: onSlower},
|
||||
{keys: ["f"], action: onFaster},
|
||||
]);
|
||||
|
||||
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
||||
return <>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Dispatch, memo, ReactElement, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { TimerElapseEvent, Timers } from "../../statecharts/runtime_types";
|
||||
import { getSimTime, setPaused, setRealtime, TimeMode } from "../../statecharts/time";
|
||||
import { InsertMode } from "./InsertModes";
|
||||
import { About } from "../Modals/About";
|
||||
import { EditHistory, LightMode } from "../App";
|
||||
import { AppState, EditHistory, LightMode } from "../App";
|
||||
import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
||||
import { UndoRedoButtons } from "./UndoRedoButtons";
|
||||
import { ZoomButtons } from "./ZoomButtons";
|
||||
|
|
@ -15,6 +14,8 @@ import BrightnessAutoIcon from '@mui/icons-material/BrightnessAuto';
|
|||
|
||||
import SpeedIcon from '@mui/icons-material/Speed';
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import FindInPageIcon from '@mui/icons-material/FindInPage';
|
||||
import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined';
|
||||
|
||||
import AccessAlarmIcon from '@mui/icons-material/AccessAlarm';
|
||||
import CachedIcon from '@mui/icons-material/Cached';
|
||||
|
|
@ -29,10 +30,16 @@ import { usePersistentState } from "@/hooks/usePersistentState";
|
|||
import { RotateButtons } from "./RotateButtons";
|
||||
import { SpeedControl } from "./SpeedControl";
|
||||
import { TraceState } from "../hooks/useSimulator";
|
||||
import { FindReplace } from "../BottomPanel/FindReplace";
|
||||
import { VisualEditorState } from "../VisualEditor/VisualEditor";
|
||||
import { Setters } from "../makePartialSetter";
|
||||
import { TwoStateButton } from "../Components/TwoStateButton";
|
||||
import { useShortcuts } from "@/hooks/useShortcuts";
|
||||
|
||||
export type TopPanelProps = {
|
||||
trace: TraceState | null,
|
||||
time: TimeMode,
|
||||
|
||||
setTime: Dispatch<SetStateAction<TimeMode>>,
|
||||
onUndo: () => void,
|
||||
onRedo: () => void,
|
||||
|
|
@ -40,28 +47,32 @@ export type TopPanelProps = {
|
|||
onInit: () => void,
|
||||
onClear: () => void,
|
||||
onBack: () => void,
|
||||
|
||||
// lightMode: LightMode,
|
||||
// setLightMode: Dispatch<SetStateAction<LightMode>>,
|
||||
insertMode: InsertMode,
|
||||
setInsertMode: Dispatch<SetStateAction<InsertMode>>,
|
||||
// insertMode: InsertMode,
|
||||
// setInsertMode: Dispatch<SetStateAction<InsertMode>>,
|
||||
setModal: Dispatch<SetStateAction<ReactElement|null>>,
|
||||
zoom: number,
|
||||
setZoom: Dispatch<SetStateAction<number>>,
|
||||
showKeys: boolean,
|
||||
setShowKeys: Dispatch<SetStateAction<boolean>>,
|
||||
// zoom: number,
|
||||
// setZoom: Dispatch<SetStateAction<number>>,
|
||||
// showKeys: boolean,
|
||||
// setShowKeys: Dispatch<SetStateAction<boolean>>,
|
||||
editHistory: EditHistory,
|
||||
}
|
||||
setEditorState: Dispatch<(oldState: VisualEditorState) => VisualEditorState>,
|
||||
} & AppState & Setters<AppState>
|
||||
|
||||
const ShortCutShowKeys = <kbd>~</kbd>;
|
||||
|
||||
export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onRotate, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory}: TopPanelProps) {
|
||||
function toggle(booleanSetter: Dispatch<(state: boolean) => boolean>) {
|
||||
return () => booleanSetter(x => !x);
|
||||
}
|
||||
|
||||
export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onRotate, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory, showFindReplace, setShowFindReplace, setEditorState}: TopPanelProps) {
|
||||
const [displayTime, setDisplayTime] = useState(0);
|
||||
const [timescale, setTimescale] = usePersistentState("timescale", 1);
|
||||
|
||||
const config = trace && trace.trace[trace.idx];
|
||||
|
||||
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
||||
|
||||
const updateDisplayedTime = useCallback(() => {
|
||||
const now = Math.round(performance.now());
|
||||
const timeMs = getSimTime(time, now);
|
||||
|
|
@ -69,12 +80,8 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
|
|||
}, [time, setDisplayTime]);
|
||||
|
||||
const formattedDisplayTime = useMemo(() => formatTime(displayTime), [displayTime]);
|
||||
|
||||
// const lastSimTime = useMemo(() => time.kind === "realtime" ? time.since.simtime : time.simtime, [time]);
|
||||
|
||||
const lastSimTime = config?.simtime || 0;
|
||||
|
||||
|
||||
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(() => {
|
||||
|
|
@ -115,54 +122,18 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
|
|||
}
|
||||
}, [nextTimedTransition, setTime]);
|
||||
|
||||
useShortcuts([
|
||||
{keys: ["`"], action: toggle(setShowKeys)},
|
||||
{keys: ["Ctrl", "Shift", "F"], action: toggle(setShowFindReplace)},
|
||||
{keys: ["i"], action: onInit},
|
||||
{keys: ["c"], action: onClear},
|
||||
{keys: ["Tab"], action: config && onSkip || onInit},
|
||||
{keys: ["Backspace"], action: onBack},
|
||||
{keys: ["Shift", "Tab"], action: onBack},
|
||||
{keys: [" "], action: () => config && onChangePaused(time.kind !== "paused", Math.round(performance.now()))},
|
||||
]);
|
||||
|
||||
console.log({lastSimTime, displayTime, nxt: nextTimedTransition?.[0]});
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
// don't capture keyboard events when focused on an input element:
|
||||
// @ts-ignore
|
||||
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||
|
||||
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 (config === null) {
|
||||
onInit();
|
||||
}
|
||||
else {
|
||||
onSkip();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === "`") {
|
||||
e.preventDefault();
|
||||
setShowKeys(show => !show);
|
||||
}
|
||||
if (e.key === "Backspace") {
|
||||
e.preventDefault();
|
||||
onBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [config, time, onInit, onChangePaused, setShowKeys, onSkip, onBack, onClear]);
|
||||
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
||||
|
||||
return <div className="toolbar">
|
||||
|
||||
|
|
@ -207,11 +178,26 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
|
|||
 
|
||||
</div>
|
||||
|
||||
{/* rotate */}
|
||||
<div className="toolbarGroup">
|
||||
<RotateButtons selection={editHistory.current.selection} onRotate={onRotate}/>
|
||||
 
|
||||
</div>
|
||||
|
||||
{/* find, replace */}
|
||||
<div className="toolbarGroup">
|
||||
<KeyInfo keyInfo={<><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>F</kbd></>}>
|
||||
<TwoStateButton
|
||||
title="show find & replace"
|
||||
active={showFindReplace}
|
||||
onClick={() => setShowFindReplace(x => !x)}
|
||||
>
|
||||
<FindInPageOutlinedIcon fontSize="small"/>
|
||||
</TwoStateButton>
|
||||
</KeyInfo>
|
||||
 
|
||||
</div>
|
||||
|
||||
{/* execution */}
|
||||
<div className="toolbarGroup">
|
||||
|
||||
|
|
|
|||
|
|
@ -3,27 +3,14 @@ import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
|||
|
||||
import UndoIcon from '@mui/icons-material/Undo';
|
||||
import RedoIcon from '@mui/icons-material/Redo';
|
||||
import { useShortcuts } from "@/hooks/useShortcuts";
|
||||
|
||||
export const UndoRedoButtons = memo(function UndoRedoButtons({showKeys, onUndo, onRedo, historyLength, futureLength}: {showKeys: boolean, onUndo: () => void, onRedo: () => void, historyLength: number, futureLength: number}) {
|
||||
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
if (e.ctrlKey) {
|
||||
// ctrl is down
|
||||
if (e.key === "z") {
|
||||
e.preventDefault();
|
||||
onUndo();
|
||||
}
|
||||
if (e.key === "Z") {
|
||||
e.preventDefault();
|
||||
onRedo();
|
||||
}
|
||||
}
|
||||
}, [onUndo, onRedo]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}, [onKeyDown]);
|
||||
useShortcuts([
|
||||
{keys: ["Ctrl", "z"], action: onUndo},
|
||||
{keys: ["Ctrl", "Shift", "Z"], action: onRedo},
|
||||
])
|
||||
|
||||
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
||||
return <>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,20 @@ import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
|||
|
||||
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
||||
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
||||
import { useShortcuts } from "@/hooks/useShortcuts";
|
||||
|
||||
const shortcutZoomIn = <><kbd>Ctrl</kbd>+<kbd>-</kbd></>;
|
||||
const shortcutZoomOut = <><kbd>Ctrl</kbd>+<kbd>+</kbd></>;
|
||||
|
||||
export const ZoomButtons = memo(function ZoomButtons({showKeys, zoom, setZoom}: {showKeys: boolean, zoom: number, setZoom: Dispatch<SetStateAction<number>>}) {
|
||||
|
||||
useShortcuts([
|
||||
{keys: ["Ctrl", "+"], action: onZoomIn}, // plus on numerical keypad
|
||||
{keys: ["Ctrl", "Shift", "+"], action: onZoomIn}, // plus on normal keyboard requires Shift key
|
||||
{keys: ["Ctrl", "="], action: onZoomIn}, // most browsers also bind this shortcut so it would be confusing if we also did not override it
|
||||
{keys: ["Ctrl", "-"], action: onZoomOut},
|
||||
]);
|
||||
|
||||
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
||||
|
||||
function onZoomIn() {
|
||||
|
|
@ -19,27 +27,6 @@ export const ZoomButtons = memo(function ZoomButtons({showKeys, zoom, setZoom}:
|
|||
setZoom(zoom => Math.max(zoom / ZOOM_STEP, ZOOM_MIN));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey) {
|
||||
if (e.key === "+" || e.key === "=") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onZoomIn();
|
||||
}
|
||||
if (e.key === "-") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onZoomOut();
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
<KeyInfo keyInfo={shortcutZoomOut}>
|
||||
<button title="zoom out" onClick={onZoomOut} disabled={zoom <= ZOOM_MIN}><ZoomOutIcon fontSize="small"/></button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue