move the parsing from VisualEditor to App component
This commit is contained in:
parent
65b6a343d1
commit
4f9a546fd1
11 changed files with 107 additions and 116 deletions
|
|
@ -1,8 +1,7 @@
|
|||
import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { emptyStatechart, Statechart, Transition } from "../statecharts/abstract_syntax";
|
||||
import { handleInputEvent, initialize, RuntimeError } from "../statecharts/interpreter";
|
||||
import { BigStep, BigStepOutput, RT_Event } from "../statecharts/runtime_types";
|
||||
import { BigStep, RT_Event } from "../statecharts/runtime_types";
|
||||
import { InsertMode, VisualEditor, VisualEditorState } from "../VisualEditor/VisualEditor";
|
||||
import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
|
||||
|
||||
|
|
@ -13,7 +12,7 @@ import Stack from "@mui/material/Stack";
|
|||
import Box from "@mui/material/Box";
|
||||
import { TopPanel } from "./TopPanel";
|
||||
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST";
|
||||
import { TraceableError } from "../statecharts/parser";
|
||||
import { parseStatechart } from "../statecharts/parser";
|
||||
import { getKeyHandler } from "./shortcut_handler";
|
||||
import { BottomPanel } from "./BottomPanel";
|
||||
import { emptyState } from "@/statecharts/concrete_syntax";
|
||||
|
|
@ -23,6 +22,7 @@ import { DummyPlant } from "@/Plant/Dummy/Dummy";
|
|||
import { Plant } from "@/Plant/Plant";
|
||||
import { usePersistentState } from "@/util/persistent_state";
|
||||
import { RTHistory } from "./RTHistory";
|
||||
import { detectConnections } from "@/statecharts/detect_connections";
|
||||
|
||||
export type EditHistory = {
|
||||
current: VisualEditorState,
|
||||
|
|
@ -70,10 +70,8 @@ function getPlantState<T>(plant: Plant<T>, trace: TraceItem[], idx: number): T |
|
|||
}
|
||||
|
||||
export function App() {
|
||||
const [mode, setMode] = useState<InsertMode>("and");
|
||||
const [insertMode, setInsertMode] = useState<InsertMode>("and");
|
||||
const [historyState, setHistoryState] = useState<EditHistory>({current: emptyState, history: [], future: []});
|
||||
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
||||
const [errors, setErrors] = useState<TraceableError[]>([]);
|
||||
const [trace, setTrace] = useState<TraceState|null>(null);
|
||||
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
||||
const [modal, setModal] = useState<ReactElement|null>(null);
|
||||
|
|
@ -91,6 +89,10 @@ export function App() {
|
|||
|
||||
const refRightSideBar = useRef<HTMLDivElement>(null);
|
||||
|
||||
// parse concrete syntax always:
|
||||
const conns = useMemo(() => detectConnections(editorState), [editorState]);
|
||||
const [ast, syntaxErrors] = useMemo(() => parseStatechart(editorState, conns), [editorState, conns]);
|
||||
|
||||
// append editor state to undo history
|
||||
const makeCheckPoint = useCallback(() => {
|
||||
setHistoryState(historyState => ({
|
||||
|
|
@ -261,7 +263,7 @@ export function App() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = getKeyHandler(setMode);
|
||||
const onKeyDown = getKeyHandler(setInsertMode);
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
|
|
@ -322,12 +324,12 @@ export function App() {
|
|||
}}
|
||||
>
|
||||
<TopPanel
|
||||
{...{trace, ast, time, setTime, onUndo, onRedo, onInit, onClear, onRaise, onBack, mode, setMode, setModal, zoom, setZoom, showKeys, setShowKeys, history: historyState}}
|
||||
{...{trace, ast, time, setTime, onUndo, onRedo, onInit, onClear, onRaise, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, history: historyState}}
|
||||
/>
|
||||
</Box>
|
||||
{/* Below the top bar: Editor */}
|
||||
<Box sx={{flexGrow:1, overflow: "auto"}}>
|
||||
<VisualEditor {...{state: editorState, setState: setEditorState, ast, setAST, trace, setTrace, errors, setErrors, mode, highlightActive, highlightTransitions, setModal, makeCheckPoint, zoom}}/>
|
||||
<VisualEditor {...{state: editorState, setState: setEditorState, conns, trace, setTrace, syntaxErrors, insertMode, highlightActive, highlightTransitions, setModal, makeCheckPoint, zoom}}/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
|
@ -408,7 +410,7 @@ export function App() {
|
|||
|
||||
{/* Bottom panel */}
|
||||
<Box sx={{flex: '0 0 content'}}>
|
||||
<BottomPanel {...{errors}}/>
|
||||
<BottomPanel {...{errors: syntaxErrors}}/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</>;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Dispatch, Ref, SetStateAction } from "react";
|
||||
import { Dispatch, memo, Ref, SetStateAction, useCallback } from "react";
|
||||
import { Statechart, stateDescription } from "../statecharts/abstract_syntax";
|
||||
import { BigStep, Environment, Mode, RaisedEvent, RT_Event } from "../statecharts/runtime_types";
|
||||
import { formatTime } from "./util";
|
||||
import { TimeMode } from "../statecharts/time";
|
||||
import { TraceState } from "./App";
|
||||
import { TraceItem, TraceState } from "./App";
|
||||
|
||||
type RTHistoryProps = {
|
||||
trace: TraceState|null,
|
||||
|
|
@ -13,24 +13,29 @@ type RTHistoryProps = {
|
|||
}
|
||||
|
||||
export function RTHistory({trace, setTrace, ast, setTime}: RTHistoryProps) {
|
||||
function gotoRt(idx: number, timestamp: number) {
|
||||
const onMouseDown = useCallback((idx: number, timestamp: number) => {
|
||||
setTrace(trace => trace && {
|
||||
...trace,
|
||||
idx,
|
||||
});
|
||||
setTime({kind: "paused", simtime: timestamp});
|
||||
}
|
||||
}, [setTrace, setTime]);
|
||||
|
||||
if (trace === null) {
|
||||
return <></>;
|
||||
}
|
||||
return <div>
|
||||
{trace.trace.map((item, i) => {
|
||||
{trace.trace.map((item, i) => <RTHistoryItem ast={ast} idx={i} item={item} prevItem={trace.trace[i-1]} active={i === trace.idx} onMouseDown={onMouseDown}/>)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
export const RTHistoryItem = memo(function RTHistoryItem({ast, idx, item, prevItem, active, onMouseDown}: {idx: number, ast: Statechart, item: TraceItem, prevItem?: TraceItem, active: boolean, onMouseDown: (idx: number, timestamp: number) => void}) {
|
||||
if (item.kind === "bigstep") {
|
||||
const newStates = item.mode.difference(trace.trace[i-1]?.mode || new Set());
|
||||
// @ts-ignore
|
||||
const newStates = item.mode.difference(prevItem?.mode || new Set());
|
||||
return <div
|
||||
className={"runtimeState" + (i === trace.idx ? " active" : "")}
|
||||
onMouseDown={() => gotoRt(i, item.simtime)}>
|
||||
className={"runtimeState" + (active ? " active" : "")}
|
||||
onMouseDown={useCallback(() => onMouseDown(idx, item.simtime), [idx, item.simtime])}>
|
||||
<div>
|
||||
{formatTime(item.simtime)}
|
||||
 
|
||||
|
|
@ -55,9 +60,7 @@ export function RTHistory({trace, setTrace, ast, setTime}: RTHistoryProps) {
|
|||
</div>
|
||||
</div>;
|
||||
}
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function ShowEnvironment(props: {environment: Environment}) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export function ShowAction(props: {action: Action}) {
|
|||
}
|
||||
}
|
||||
|
||||
export const ShowAST = memo(function ShowAST(props: {root: ConcreteState | PseudoState, transitions: Map<string, Transition[]>, trace: TraceState | null, highlightActive: Set<string>}) {
|
||||
export const ShowAST = memo(function ShowASTx(props: {root: ConcreteState | PseudoState}) {
|
||||
const description = stateDescription(props.root);
|
||||
// const outgoing = props.transitions.get(props.root.uid) || [];
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ export const ShowAST = memo(function ShowAST(props: {root: ConcreteState | Pseud
|
|||
{props.root.kind !== "pseudo" && props.root.children.length>0 &&
|
||||
<ul>
|
||||
{props.root.children.map(child =>
|
||||
<ShowAST key={child.uid} root={child} transitions={props.transitions} trace={props.trace} highlightActive={props.highlightActive} />
|
||||
<ShowAST key={child.uid} root={child} />
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ export type TopPanelProps = {
|
|||
// onRaise: (e: string, p: any) => void,
|
||||
onBack: () => void,
|
||||
// ast: Statechart,
|
||||
mode: InsertMode,
|
||||
setMode: Dispatch<SetStateAction<InsertMode>>,
|
||||
insertMode: InsertMode,
|
||||
setInsertMode: Dispatch<SetStateAction<InsertMode>>,
|
||||
setModal: Dispatch<SetStateAction<ReactElement|null>>,
|
||||
zoom: number,
|
||||
setZoom: Dispatch<SetStateAction<number>>,
|
||||
|
|
@ -56,7 +56,7 @@ const insertModes: [InsertMode, string, ReactElement, ReactElement][] = [
|
|||
["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, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, history}: TopPanelProps) {
|
||||
const [displayTime, setDisplayTime] = useState("0.000");
|
||||
const [timescale, setTimescale] = useState(1);
|
||||
|
||||
|
|
@ -226,9 +226,9 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
|
|||
<KeyInfo key={m} keyInfo={keyInfo}>
|
||||
<button
|
||||
title={"insert "+hint}
|
||||
disabled={mode===m}
|
||||
className={mode===m ? "active":""}
|
||||
onClick={() => setMode(m)}
|
||||
disabled={insertMode===m}
|
||||
className={insertMode===m ? "active":""}
|
||||
onClick={() => setInsertMode(m)}
|
||||
>{buttonTxt}</button></KeyInfo>)}
|
||||
 
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Diamond, RountanglePart } from "@/statecharts/concrete_syntax";
|
||||
import { Diamond, RectSide } from "@/statecharts/concrete_syntax";
|
||||
import { rountangleMinSize } from "./VisualEditor";
|
||||
import { Vec2D } from "./geometry";
|
||||
import { RectHelper } from "./RectHelpers";
|
||||
|
|
@ -21,8 +21,7 @@ export const DiamondShape = memo(function DiamondShape(props: {size: Vec2D, extr
|
|||
/>;
|
||||
});
|
||||
|
||||
export const DiamondSVG = memo(function DiamondSVG(props: { diamond: Diamond; selected: RountanglePart[]; highlight: RountanglePart[]; error?: string; active: boolean; }) {
|
||||
console.log('render diamond', props.diamond.uid);
|
||||
export const DiamondSVG = memo(function DiamondSVG(props: { diamond: Diamond; selected: RectSide[]; highlight: RectSide[]; error?: string; active: boolean; }) {
|
||||
const minSize = rountangleMinSize(props.diamond.size);
|
||||
const extraAttrs = {
|
||||
className: ''
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { memo } from "react";
|
||||
import { RountanglePart } from "../statecharts/concrete_syntax";
|
||||
import { RectSide } from "../statecharts/concrete_syntax";
|
||||
import { Vec2D } from "./geometry";
|
||||
import { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS } from "./parameters";
|
||||
|
||||
function lineGeometryProps(size: Vec2D): [RountanglePart, object][] {
|
||||
function lineGeometryProps(size: Vec2D): [RectSide, object][] {
|
||||
return [
|
||||
["top", {x1: 0, y1: 0, x2: size.x, y2: 0 }],
|
||||
["right", {x1: size.x, y1: 0, x2: size.x, y2: size.y}],
|
||||
|
|
@ -13,7 +13,7 @@ function lineGeometryProps(size: Vec2D): [RountanglePart, object][] {
|
|||
}
|
||||
|
||||
// no need to memo() this component, the parent component is already memoized
|
||||
export const RectHelper = function RectHelper(props: { uid: string, size: Vec2D, selected: RountanglePart[], highlight: string[] }) {
|
||||
export const RectHelper = function RectHelper(props: { uid: string, size: Vec2D, selected: RectSide[], highlight: string[] }) {
|
||||
const geomProps = lineGeometryProps(props.size);
|
||||
return <>
|
||||
{geomProps.map(([side, ps]) => <g key={side}>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { memo } from "react";
|
||||
import { Rountangle, RountanglePart } from "../statecharts/concrete_syntax";
|
||||
import { Rountangle, RectSide } from "../statecharts/concrete_syntax";
|
||||
import { ROUNTANGLE_RADIUS } from "./parameters";
|
||||
import { RectHelper } from "./RectHelpers";
|
||||
import { rountangleMinSize } from "./VisualEditor";
|
||||
import { arraysEqual } from "@/App/util";
|
||||
|
||||
|
||||
export const RountangleSVG = memo(function RountangleSVG(props: {rountangle: Rountangle; selected: RountanglePart[]; highlight: RountanglePart[]; error?: string; active: boolean; }) {
|
||||
console.log('render rountangle', props.rountangle.uid);
|
||||
|
||||
export const RountangleSVG = memo(function RountangleSVG(props: {rountangle: Rountangle; selected: RectSide[]; highlight: RectSide[]; error?: string; active: boolean; }) {
|
||||
const { topLeft, size, uid } = props.rountangle;
|
||||
// always draw a rountangle with a minimum size
|
||||
// during resizing, rountangle can be smaller than this size and even have a negative size, but we don't show it
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ClipboardEvent, Dispatch, memo, ReactElement, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { Statechart } from "../statecharts/abstract_syntax";
|
||||
import { Arrow, ArrowPart, Diamond, History, Rountangle, RountanglePart, Text } from "../statecharts/concrete_syntax";
|
||||
import { Arrow, ArrowPart, Diamond, History, Rountangle, RectSide, Text } from "../statecharts/concrete_syntax";
|
||||
import { parseStatechart, TraceableError } from "../statecharts/parser";
|
||||
import { ArcDirection, Line2D, Rect2D, Vec2D, addV2D, arcDirection, area, getBottomSide, getLeftSide, getRightSide, getTopSide, isEntirelyWithin, normalizeRect, scaleV2D, subtractV2D, transformLine, transformRect } from "./geometry";
|
||||
import { MIN_ROUNTANGLE_SIZE } from "./parameters";
|
||||
|
|
@ -11,7 +11,7 @@ import { RountangleSVG } from "./RountangleSVG";
|
|||
import { TextSVG } from "./TextSVG";
|
||||
import { DiamondSVG } from "./DiamondSVG";
|
||||
import { HistorySVG } from "./HistorySVG";
|
||||
import { detectConnections } from "../statecharts/detect_connections";
|
||||
import { Connections, detectConnections } from "../statecharts/detect_connections";
|
||||
|
||||
import "./VisualEditor.css";
|
||||
import { TraceState } from "@/App/App";
|
||||
|
|
@ -30,7 +30,7 @@ type SelectingState = Rect2D | null;
|
|||
|
||||
export type RountangleSelectable = {
|
||||
// kind: "rountangle";
|
||||
parts: RountanglePart[];
|
||||
parts: RectSide[];
|
||||
uid: string;
|
||||
}
|
||||
type ArrowSelectable = {
|
||||
|
|
@ -51,7 +51,7 @@ type Selectable = RountangleSelectable | ArrowSelectable | TextSelectable | Hist
|
|||
type Selection = Selectable[];
|
||||
|
||||
|
||||
export const sides: [RountanglePart, (r:Rect2D)=>Line2D][] = [
|
||||
export const sides: [RectSide, (r:Rect2D)=>Line2D][] = [
|
||||
["left", getLeftSide],
|
||||
["top", getTopSide],
|
||||
["right", getRightSide],
|
||||
|
|
@ -63,12 +63,10 @@ export type InsertMode = "and"|"or"|"pseudo"|"shallow"|"deep"|"transition"|"text
|
|||
type VisualEditorProps = {
|
||||
state: VisualEditorState,
|
||||
setState: Dispatch<(v:VisualEditorState) => VisualEditorState>,
|
||||
// ast: Statechart,
|
||||
setAST: Dispatch<SetStateAction<Statechart>>,
|
||||
conns: Connections,
|
||||
syntaxErrors: TraceableError[],
|
||||
trace: TraceState | null,
|
||||
errors: TraceableError[],
|
||||
setErrors: Dispatch<SetStateAction<TraceableError[]>>,
|
||||
mode: InsertMode,
|
||||
insertMode: InsertMode,
|
||||
highlightActive: Set<string>,
|
||||
highlightTransitions: string[],
|
||||
setModal: Dispatch<SetStateAction<ReactElement|null>>,
|
||||
|
|
@ -76,7 +74,7 @@ type VisualEditorProps = {
|
|||
zoom: number;
|
||||
};
|
||||
|
||||
export const VisualEditor = memo(function VisualEditor({state, setState, setAST, trace, errors, setErrors, mode, highlightActive, highlightTransitions, setModal, makeCheckPoint, zoom}: VisualEditorProps) {
|
||||
export const VisualEditor = memo(function VisualEditor({state, setState, trace, conns, syntaxErrors: errors, insertMode, highlightActive, highlightTransitions, setModal, makeCheckPoint, zoom}: VisualEditorProps) {
|
||||
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
|
|
@ -153,31 +151,22 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
return () => clearTimeout(timeout);
|
||||
}, [state]);
|
||||
|
||||
const conns = useMemo(() => detectConnections(state), [state]);
|
||||
|
||||
useEffect(() => {
|
||||
const [statechart, errors] = parseStatechart(state, conns);
|
||||
setErrors(errors);
|
||||
setAST(statechart);
|
||||
}, [state])
|
||||
|
||||
function getCurrentPointer(e: {pageX: number, pageY: number}) {
|
||||
const getCurrentPointer = useCallback((e: {pageX: number, pageY: number}) => {
|
||||
const bbox = refSVG.current!.getBoundingClientRect();
|
||||
return {
|
||||
x: (e.pageX - bbox.left)/zoom,
|
||||
y: (e.pageY - bbox.top)/zoom,
|
||||
}
|
||||
}
|
||||
}, [refSVG.current]);
|
||||
|
||||
const onMouseDown = (e: {button: number, target: any, pageX: number, pageY: number}) => {
|
||||
const onMouseDown = useCallback((e: {button: number, target: any, pageX: number, pageY: number}) => {
|
||||
const currentPointer = getCurrentPointer(e);
|
||||
|
||||
if (e.button === 2) {
|
||||
makeCheckPoint();
|
||||
// ignore selection, middle mouse button always inserts
|
||||
setState(state => {
|
||||
const newID = state.nextID.toString();
|
||||
if (mode === "and" || mode === "or") {
|
||||
if (insertMode === "and" || insertMode === "or") {
|
||||
// insert rountangle
|
||||
return {
|
||||
...state,
|
||||
|
|
@ -185,13 +174,13 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
uid: newID,
|
||||
topLeft: currentPointer,
|
||||
size: MIN_ROUNTANGLE_SIZE,
|
||||
kind: mode,
|
||||
kind: insertMode,
|
||||
}],
|
||||
nextID: state.nextID+1,
|
||||
selection: [{uid: newID, parts: ["bottom", "right"]}],
|
||||
};
|
||||
}
|
||||
else if (mode === "pseudo") {
|
||||
else if (insertMode === "pseudo") {
|
||||
return {
|
||||
...state,
|
||||
diamonds: [...state.diamonds, {
|
||||
|
|
@ -203,19 +192,19 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
selection: [{uid: newID, parts: ["bottom", "right"]}],
|
||||
};
|
||||
}
|
||||
else if (mode === "shallow" || mode === "deep") {
|
||||
else if (insertMode === "shallow" || insertMode === "deep") {
|
||||
return {
|
||||
...state,
|
||||
history: [...state.history, {
|
||||
uid: newID,
|
||||
kind: mode,
|
||||
kind: insertMode,
|
||||
topLeft: currentPointer,
|
||||
}],
|
||||
nextID: state.nextID+1,
|
||||
selection: [{uid: newID, parts: ["history"]}],
|
||||
}
|
||||
}
|
||||
else if (mode === "transition") {
|
||||
else if (insertMode === "transition") {
|
||||
return {
|
||||
...state,
|
||||
arrows: [...state.arrows, {
|
||||
|
|
@ -227,7 +216,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
selection: [{uid: newID, parts: ["end"]}],
|
||||
}
|
||||
}
|
||||
else if (mode === "text") {
|
||||
else if (insertMode === "text") {
|
||||
return {
|
||||
...state,
|
||||
texts: [...state.texts, {
|
||||
|
|
@ -239,7 +228,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
selection: [{uid: newID, parts: ["text"]}],
|
||||
}
|
||||
}
|
||||
throw new Error("unreachable, mode=" + mode); // shut up typescript
|
||||
throw new Error("unreachable, mode=" + insertMode); // shut up typescript
|
||||
});
|
||||
setDragging(true);
|
||||
return;
|
||||
|
|
@ -288,9 +277,9 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
size: {x: 0, y: 0},
|
||||
});
|
||||
setSelection(() => []);
|
||||
};
|
||||
}, [getCurrentPointer, makeCheckPoint, insertMode, selection]);
|
||||
|
||||
const onMouseMove = (e: {pageX: number, pageY: number, movementX: number, movementY: number}) => {
|
||||
const onMouseMove = useCallback((e: {pageX: number, pageY: number, movementX: number, movementY: number}) => {
|
||||
const currentPointer = getCurrentPointer(e);
|
||||
if (dragging) {
|
||||
// const pointerDelta = subtractV2D(currentPointer, dragging.lastMousePos);
|
||||
|
|
@ -298,7 +287,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
setState(state => ({
|
||||
...state,
|
||||
rountangles: state.rountangles.map(r => {
|
||||
const parts = selection.find(selected => selected.uid === r.uid)?.parts || [];
|
||||
const parts = state.selection.find(selected => selected.uid === r.uid)?.parts || [];
|
||||
if (parts.length === 0) {
|
||||
return r;
|
||||
}
|
||||
|
|
@ -309,7 +298,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
})
|
||||
.toSorted((a,b) => area(b) - area(a)), // sort: smaller rountangles are drawn on top
|
||||
diamonds: state.diamonds.map(d => {
|
||||
const parts = selection.find(selected => selected.uid === d.uid)?.parts || [];
|
||||
const parts = state.selection.find(selected => selected.uid === d.uid)?.parts || [];
|
||||
if (parts.length === 0) {
|
||||
return d;
|
||||
}
|
||||
|
|
@ -319,7 +308,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
}
|
||||
}),
|
||||
history: state.history.map(h => {
|
||||
const parts = selection.find(selected => selected.uid === h.uid)?.parts || [];
|
||||
const parts = state.selection.find(selected => selected.uid === h.uid)?.parts || [];
|
||||
if (parts.length === 0) {
|
||||
return h;
|
||||
}
|
||||
|
|
@ -329,7 +318,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
}
|
||||
}),
|
||||
arrows: state.arrows.map(a => {
|
||||
const parts = selection.find(selected => selected.uid === a.uid)?.parts || [];
|
||||
const parts = state.selection.find(selected => selected.uid === a.uid)?.parts || [];
|
||||
if (parts.length === 0) {
|
||||
return a;
|
||||
}
|
||||
|
|
@ -339,7 +328,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
}
|
||||
}),
|
||||
texts: state.texts.map(t => {
|
||||
const parts = selection.find(selected => selected.uid === t.uid)?.parts || [];
|
||||
const parts = state.selection.find(selected => selected.uid === t.uid)?.parts || [];
|
||||
if (parts.length === 0) {
|
||||
return t;
|
||||
}
|
||||
|
|
@ -360,9 +349,9 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [getCurrentPointer, selectingState, dragging]);
|
||||
|
||||
const onMouseUp = (e: {target: any, pageX: number, pageY: number}) => {
|
||||
const onMouseUp = useCallback((e: {target: any, pageX: number, pageY: number}) => {
|
||||
if (dragging) {
|
||||
setDragging(false);
|
||||
// do not persist sizes smaller than 40x40
|
||||
|
|
@ -424,7 +413,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
}
|
||||
}
|
||||
setSelectingState(null); // no longer making a selection
|
||||
};
|
||||
}, [dragging, selectingState, refSVG.current]);
|
||||
|
||||
function deleteSelection() {
|
||||
setState(state => ({
|
||||
|
|
@ -499,7 +488,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
}, [selectingState, dragging]);
|
||||
|
||||
// for visual feedback, when selecting/moving one thing, we also highlight (in green) all the things that belong to the thing we selected.
|
||||
const sidesToHighlight: {[key: string]: RountanglePart[]} = {};
|
||||
const sidesToHighlight: {[key: string]: RectSide[]} = {};
|
||||
const arrowsToHighlight: {[key: string]: boolean} = {};
|
||||
const textsToHighlight: {[key: string]: boolean} = {};
|
||||
const rountanglesToHighlight: {[key: string]: boolean} = {};
|
||||
|
|
@ -716,8 +705,8 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
return <RountangleSVG
|
||||
key={rountangle.uid}
|
||||
rountangle={rountangle}
|
||||
selected={selection.find(r => r.uid === rountangle.uid)?.parts || []}
|
||||
highlight={[...(sidesToHighlight[rountangle.uid] || []), ...(rountanglesToHighlight[rountangle.uid]?["left","right","top","bottom"]:[]) as RountanglePart[]]}
|
||||
selected={selection.find(r => r.uid === rountangle.uid)?.parts as RectSide[] || []}
|
||||
highlight={[...(sidesToHighlight[rountangle.uid] || []), ...(rountanglesToHighlight[rountangle.uid]?["left","right","top","bottom"]:[]) as RectSide[]]}
|
||||
error={errors
|
||||
.filter(({shapeUid}) => shapeUid === rountangle.uid)
|
||||
.map(({message}) => message).join(', ')}
|
||||
|
|
@ -728,8 +717,8 @@ export const VisualEditor = memo(function VisualEditor({state, setState, setAST,
|
|||
<DiamondSVG
|
||||
key={diamond.uid}
|
||||
diamond={diamond}
|
||||
selected={selection.find(r => r.uid === diamond.uid)?.parts || []}
|
||||
highlight={[...(sidesToHighlight[diamond.uid] || []), ...(rountanglesToHighlight[diamond.uid]?["left","right","top","bottom"]:[]) as RountanglePart[]]}
|
||||
selected={selection.find(r => r.uid === diamond.uid)?.parts as RectSide[] || []}
|
||||
highlight={[...(sidesToHighlight[diamond.uid] || []), ...(rountanglesToHighlight[diamond.uid]?["left","right","top","bottom"]:[]) as RectSide[]]}
|
||||
error={errors
|
||||
.filter(({shapeUid}) => shapeUid === diamond.uid)
|
||||
.map(({message}) => message).join(', ')}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { RountanglePart } from "../statecharts/concrete_syntax";
|
||||
import { RectSide } from "../statecharts/concrete_syntax";
|
||||
|
||||
export type Vec2D = {
|
||||
x: number;
|
||||
|
|
@ -166,7 +166,7 @@ export function getBottomSide(rect: Rect2D): Line2D {
|
|||
|
||||
export type ArcDirection = "no" | "cw" | "ccw";
|
||||
|
||||
export function arcDirection(start: RountanglePart, end: RountanglePart): ArcDirection {
|
||||
export function arcDirection(start: RectSide, end: RectSide): ArcDirection {
|
||||
if (start === end) {
|
||||
if (start === "left" || start === "top") {
|
||||
return "ccw";
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export type History = {
|
|||
};
|
||||
|
||||
// independently moveable parts of our shapes:
|
||||
export type RountanglePart = "left" | "top" | "right" | "bottom";
|
||||
export type RectSide = "left" | "top" | "right" | "bottom";
|
||||
export type ArrowPart = "start" | "end";
|
||||
|
||||
export const emptyState: VisualEditorState = {
|
||||
|
|
@ -36,9 +36,9 @@ export const emptyState: VisualEditorState = {
|
|||
};
|
||||
|
||||
// used to find which rountangle an arrow connects to (src/tgt)
|
||||
export function findNearestSide(arrow: Line2D, arrowPart: "start" | "end", candidates: (Rountangle|Diamond)[]): {uid: string, part: RountanglePart} | undefined {
|
||||
export function findNearestSide(arrow: Line2D, arrowPart: "start" | "end", candidates: (Rountangle|Diamond)[]): {uid: string, part: RectSide} | undefined {
|
||||
let best = Infinity;
|
||||
let bestSide: undefined | {uid: string, part: RountanglePart};
|
||||
let bestSide: undefined | {uid: string, part: RectSide};
|
||||
for (const rountangle of candidates) {
|
||||
for (const [side, getSide] of sides) {
|
||||
const asLine = getSide(rountangle);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { VisualEditorState } from "@/VisualEditor/VisualEditor";
|
||||
import { findNearestArrow, findNearestHistory, findNearestSide, findRountangle, RountanglePart } from "./concrete_syntax";
|
||||
import { findNearestArrow, findNearestHistory, findNearestSide, findRountangle, RectSide } from "./concrete_syntax";
|
||||
|
||||
export type Connections = {
|
||||
arrow2SideMap: Map<string,[{ uid: string; part: RountanglePart; } | undefined, { uid: string; part: RountanglePart; } | undefined]>,
|
||||
arrow2SideMap: Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>,
|
||||
side2ArrowMap: Map<string, Set<["start"|"end", string]>>,
|
||||
text2ArrowMap: Map<string,string>,
|
||||
arrow2TextMap: Map<string,string[]>,
|
||||
|
|
@ -14,7 +14,7 @@ export type Connections = {
|
|||
|
||||
export function detectConnections(state: VisualEditorState): Connections {
|
||||
// detect what is 'connected'
|
||||
const arrow2SideMap = new Map<string,[{ uid: string; part: RountanglePart; } | undefined, { uid: string; part: RountanglePart; } | undefined]>();
|
||||
const arrow2SideMap = new Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>();
|
||||
const side2ArrowMap = new Map<string, Set<["start"|"end", string]>>();
|
||||
const text2ArrowMap = new Map<string,string>();
|
||||
const arrow2TextMap = new Map<string,string[]>();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue