further reduce unnecessary re-renders
This commit is contained in:
parent
2ca2ba5d1b
commit
87ceaa1220
5 changed files with 40 additions and 26 deletions
|
|
@ -18,9 +18,7 @@ import { formatTime } from "./util";
|
||||||
import { InsertMode } from "../VisualEditor/VisualEditor";
|
import { InsertMode } from "../VisualEditor/VisualEditor";
|
||||||
import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
|
||||||
import { About } from "./About";
|
import { About } from "./About";
|
||||||
import { usePersistentState } from "@/util/persistent_state";
|
|
||||||
import { RountangleIcon, PseudoStateIcon, HistoryIcon } from "./Icons";
|
import { RountangleIcon, PseudoStateIcon, HistoryIcon } from "./Icons";
|
||||||
import { ZOOM_MAX, ZOOM_MIN, ZOOM_STEP } from "@/VisualEditor/parameters";
|
|
||||||
import { EditHistory, TraceState } from "./App";
|
import { EditHistory, TraceState } from "./App";
|
||||||
import { ZoomButtons } from "./TopPanel/ZoomButtons";
|
import { ZoomButtons } from "./TopPanel/ZoomButtons";
|
||||||
import { UndoRedoButtons } from "./TopPanel/UndoRedoButtons";
|
import { UndoRedoButtons } from "./TopPanel/UndoRedoButtons";
|
||||||
|
|
@ -46,6 +44,18 @@ export type TopPanelProps = {
|
||||||
history: EditHistory,
|
history: EditHistory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ShortCutShowKeys = <kbd>~</kbd>;
|
||||||
|
|
||||||
|
const insertModes: [InsertMode, string, ReactElement, ReactElement][] = [
|
||||||
|
["and", "AND-states", <RountangleIcon kind="and"/>, <kbd>A</kbd>],
|
||||||
|
["or", "OR-states", <RountangleIcon kind="or"/>, <kbd>O</kbd>],
|
||||||
|
["pseudo", "pseudo-states", <PseudoStateIcon/>, <kbd>P</kbd>],
|
||||||
|
["shallow", "shallow history", <HistoryIcon kind="shallow"/>, <kbd>H</kbd>],
|
||||||
|
["deep", "deep history", <HistoryIcon kind="deep"/>, <></>],
|
||||||
|
["transition", "transitions", <TrendingFlatIcon fontSize="small"/>, <kbd>T</kbd>],
|
||||||
|
["text", "text", <> T </>, <kbd>X</kbd>],
|
||||||
|
];
|
||||||
|
|
||||||
export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onInit, onClear, onBack, mode, setMode, setModal, zoom, setZoom, showKeys, setShowKeys, history}: TopPanelProps) {
|
export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onInit, onClear, onBack, mode, setMode, setModal, zoom, setZoom, showKeys, setShowKeys, history}: TopPanelProps) {
|
||||||
const [displayTime, setDisplayTime] = useState("0.000");
|
const [displayTime, setDisplayTime] = useState("0.000");
|
||||||
const [timescale, setTimescale] = useState(1);
|
const [timescale, setTimescale] = useState(1);
|
||||||
|
|
@ -191,7 +201,7 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
|
||||||
|
|
||||||
{/* shortcuts / about */}
|
{/* shortcuts / about */}
|
||||||
<div className="toolbarGroup">
|
<div className="toolbarGroup">
|
||||||
<KeyInfo keyInfo={<kbd>~</kbd>}>
|
<KeyInfo keyInfo={ShortCutShowKeys}>
|
||||||
<button title="show/hide keyboard shortcuts" className={showKeys?"active":""} onClick={() => setShowKeys(s => !s)}><KeyboardIcon fontSize="small"/></button>
|
<button title="show/hide keyboard shortcuts" className={showKeys?"active":""} onClick={() => setShowKeys(s => !s)}><KeyboardIcon fontSize="small"/></button>
|
||||||
</KeyInfo>
|
</KeyInfo>
|
||||||
<button title="about StateBuddy" onClick={() => setModal(<About setModal={setModal}/>)}><InfoOutlineIcon fontSize="small"/></button>
|
<button title="about StateBuddy" onClick={() => setModal(<About setModal={setModal}/>)}><InfoOutlineIcon fontSize="small"/></button>
|
||||||
|
|
@ -212,15 +222,7 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
|
||||||
|
|
||||||
{/* insert rountangle / arrow / ... */}
|
{/* insert rountangle / arrow / ... */}
|
||||||
<div className="toolbarGroup">
|
<div className="toolbarGroup">
|
||||||
{([
|
{insertModes.map(([m, hint, buttonTxt, keyInfo]) =>
|
||||||
["and", "AND-states", <RountangleIcon kind="and"/>, <kbd>A</kbd>],
|
|
||||||
["or", "OR-states", <RountangleIcon kind="or"/>, <kbd>O</kbd>],
|
|
||||||
["pseudo", "pseudo-states", <PseudoStateIcon/>, <kbd>P</kbd>],
|
|
||||||
["shallow", "shallow history", <HistoryIcon kind="shallow"/>, <kbd>H</kbd>],
|
|
||||||
["deep", "deep history", <HistoryIcon kind="deep"/>, <></>],
|
|
||||||
["transition", "transitions", <TrendingFlatIcon fontSize="small"/>, <kbd>T</kbd>],
|
|
||||||
["text", "text", <> T </>, <kbd>X</kbd>],
|
|
||||||
] as [InsertMode, string, ReactElement, ReactElement][]).map(([m, hint, buttonTxt, keyInfo]) =>
|
|
||||||
<KeyInfo key={m} keyInfo={keyInfo}>
|
<KeyInfo key={m} keyInfo={keyInfo}>
|
||||||
<button
|
<button
|
||||||
title={"insert "+hint}
|
title={"insert "+hint}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import { KeyInfoHidden, KeyInfoVisible } from "../KeyInfo";
|
||||||
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
||||||
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
||||||
|
|
||||||
|
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>>}) {
|
export const ZoomButtons = memo(function ZoomButtons({showKeys, zoom, setZoom}: {showKeys: boolean, zoom: number, setZoom: Dispatch<SetStateAction<number>>}) {
|
||||||
|
|
||||||
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
|
||||||
|
|
@ -36,11 +39,11 @@ export const ZoomButtons = memo(function ZoomButtons({showKeys, zoom, setZoom}:
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<KeyInfo keyInfo={<><kbd>Ctrl</kbd>+<kbd>-</kbd></>}>
|
<KeyInfo keyInfo={shortcutZoomOut}>
|
||||||
<button title="zoom out" onClick={onZoomOut} disabled={zoom <= ZOOM_MIN}><ZoomOutIcon fontSize="small"/></button>
|
<button title="zoom out" onClick={onZoomOut} disabled={zoom <= ZOOM_MIN}><ZoomOutIcon fontSize="small"/></button>
|
||||||
</KeyInfo>
|
</KeyInfo>
|
||||||
<input title="current zoom level" value={zoom.toFixed(3)} style={{width:40}} readOnly/>
|
<input title="current zoom level" value={zoom.toFixed(3)} style={{width:40}} readOnly/>
|
||||||
<KeyInfo keyInfo={<><kbd>Ctrl</kbd>+<kbd>+</kbd></>}>
|
<KeyInfo keyInfo={shortcutZoomIn}>
|
||||||
<button title="zoom in" onClick={onZoomIn} disabled={zoom >= ZOOM_MAX}><ZoomInIcon fontSize="small"/></button>
|
<button title="zoom in" onClick={onZoomIn} disabled={zoom >= ZOOM_MAX}><ZoomInIcon fontSize="small"/></button>
|
||||||
</KeyInfo>
|
</KeyInfo>
|
||||||
</>;
|
</>;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Arrow } from "../statecharts/concrete_syntax";
|
import { Arrow, ArrowPart } from "../statecharts/concrete_syntax";
|
||||||
import { ArcDirection, euclideanDistance } from "./geometry";
|
import { ArcDirection, euclideanDistance } from "./geometry";
|
||||||
import { CORNER_HELPER_RADIUS } from "./parameters";
|
import { CORNER_HELPER_RADIUS } from "./parameters";
|
||||||
|
import { arraysEqual } from "@/App/util";
|
||||||
|
|
||||||
|
|
||||||
export const ArrowSVG = memo(function(props: { arrow: Arrow; selected: string[]; errors: string[]; highlight: boolean; fired: boolean; arc: ArcDirection; initialMarker: boolean }) {
|
export const ArrowSVG = memo(function(props: { arrow: Arrow; selected: ArrowPart[]; error: string; highlight: boolean; fired: boolean; arc: ArcDirection; initialMarker: boolean }) {
|
||||||
const { start, end, uid } = props.arrow;
|
const { start, end, uid } = props.arrow;
|
||||||
const radius = euclideanDistance(start, end) / 1.6;
|
const radius = euclideanDistance(start, end) / 1.6;
|
||||||
let largeArc = "1";
|
let largeArc = "1";
|
||||||
|
|
@ -18,7 +19,7 @@ export const ArrowSVG = memo(function(props: { arrow: Arrow; selected: string[];
|
||||||
<path
|
<path
|
||||||
className={"arrow"
|
className={"arrow"
|
||||||
+ (props.selected.length === 2 ? " selected" : "")
|
+ (props.selected.length === 2 ? " selected" : "")
|
||||||
+ (props.errors.length > 0 ? " error" : "")
|
+ (props.error ? " error" : "")
|
||||||
+ (props.highlight ? " highlight" : "")
|
+ (props.highlight ? " highlight" : "")
|
||||||
+ (props.fired ? " fired" : "")
|
+ (props.fired ? " fired" : "")
|
||||||
}
|
}
|
||||||
|
|
@ -30,13 +31,13 @@ export const ArrowSVG = memo(function(props: { arrow: Arrow; selected: string[];
|
||||||
data-uid={uid}
|
data-uid={uid}
|
||||||
data-parts="start end" />
|
data-parts="start end" />
|
||||||
|
|
||||||
{props.errors.length > 0 && <text
|
{props.error && <text
|
||||||
className="error"
|
className="error"
|
||||||
x={(start.x + end.x) / 2 + 5}
|
x={(start.x + end.x) / 2 + 5}
|
||||||
y={(start.y + end.y) / 2}
|
y={(start.y + end.y) / 2}
|
||||||
textAnchor="middle"
|
textAnchor="middle"
|
||||||
data-uid={uid}
|
data-uid={uid}
|
||||||
data-parts="start end">{props.errors.join(', ')}</text>}
|
data-parts="start end">{props.error}</text>}
|
||||||
|
|
||||||
<path
|
<path
|
||||||
className="helper"
|
className="helper"
|
||||||
|
|
@ -79,4 +80,12 @@ export const ArrowSVG = memo(function(props: { arrow: Arrow; selected: string[];
|
||||||
data-parts="end" />}
|
data-parts="end" />}
|
||||||
|
|
||||||
</g>;
|
</g>;
|
||||||
});
|
}, (prevProps, nextProps) => {
|
||||||
|
return prevProps.arrow === nextProps.arrow
|
||||||
|
&& arraysEqual(prevProps.selected, nextProps.selected)
|
||||||
|
&& prevProps.highlight === nextProps.highlight
|
||||||
|
&& prevProps.error === nextProps.error
|
||||||
|
&& prevProps.fired === nextProps.fired
|
||||||
|
&& prevProps.arc === nextProps.arc
|
||||||
|
&& prevProps.initialMarker === nextProps.initialMarker
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -755,10 +755,10 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
||||||
return <ArrowSVG
|
return <ArrowSVG
|
||||||
key={arrow.uid}
|
key={arrow.uid}
|
||||||
arrow={arrow}
|
arrow={arrow}
|
||||||
selected={selection.find(a => a.uid === arrow.uid)?.parts || []}
|
selected={selection.find(a => a.uid === arrow.uid)?.parts as ArrowPart[] || []}
|
||||||
errors={errors
|
error={errors
|
||||||
.filter(({shapeUid}) => shapeUid === arrow.uid)
|
.filter(({shapeUid}) => shapeUid === arrow.uid)
|
||||||
.map(({message}) => message)}
|
.map(({message}) => message).join(', ')}
|
||||||
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
||||||
fired={highlightTransitions.includes(arrow.uid)}
|
fired={highlightTransitions.includes(arrow.uid)}
|
||||||
arc={arc}
|
arc={arc}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useCallback, useState } from "react";
|
||||||
|
|
||||||
// like useState, but it is persisted in localStorage
|
// like useState, but it is persisted in localStorage
|
||||||
// important: values must be JSON-(de-)serializable
|
// important: values must be JSON-(de-)serializable
|
||||||
|
|
@ -18,7 +18,7 @@ export function usePersistentState<T>(key: string, initial: T): [T, Dispatch<Set
|
||||||
return initial;
|
return initial;
|
||||||
});
|
});
|
||||||
|
|
||||||
function setStateWrapped(val: SetStateAction<T>) {
|
const setStateWrapped = useCallback((val: SetStateAction<T>) => {
|
||||||
setState((oldState: T) => {
|
setState((oldState: T) => {
|
||||||
let newVal;
|
let newVal;
|
||||||
if (typeof val === 'function') {
|
if (typeof val === 'function') {
|
||||||
|
|
@ -32,7 +32,7 @@ export function usePersistentState<T>(key: string, initial: T): [T, Dispatch<Set
|
||||||
localStorage.setItem(key, serialized);
|
localStorage.setItem(key, serialized);
|
||||||
return newVal;
|
return newVal;
|
||||||
});
|
});
|
||||||
}
|
}, [setState]);
|
||||||
|
|
||||||
return [state, setStateWrapped];
|
return [state, setStateWrapped];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue