move 'selection' part of state into same object as rest of editor state
This commit is contained in:
parent
9e79537b6e
commit
297905a4af
8 changed files with 66 additions and 58 deletions
|
|
@ -3,7 +3,7 @@ import { Dispatch, ReactElement, SetStateAction, useEffect, useRef, useState } f
|
||||||
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";
|
||||||
import { BigStep, BigStepOutput } from "../statecharts/runtime_types";
|
import { BigStep, BigStepOutput } from "../statecharts/runtime_types";
|
||||||
import { InsertMode, VisualEditor } from "../VisualEditor/VisualEditor";
|
import { InsertMode, VisualEditor, VisualEditorState } from "../VisualEditor/VisualEditor";
|
||||||
import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
|
import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
|
||||||
|
|
||||||
import "../index.css";
|
import "../index.css";
|
||||||
|
|
@ -17,7 +17,7 @@ import { ShowAST, ShowInputEvents, ShowOutputEvents } from "./ShowAST";
|
||||||
import { TraceableError } from "../statecharts/parser";
|
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, VisualEditorState } from "@/statecharts/concrete_syntax";
|
import { emptyState } from "@/statecharts/concrete_syntax";
|
||||||
import { usePersistentState } from "@/util/persistent_state";
|
import { usePersistentState } from "@/util/persistent_state";
|
||||||
|
|
||||||
type EditHistory = {
|
type EditHistory = {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ function ShowMode(props: {mode: Mode, statechart: Statechart}) {
|
||||||
|
|
||||||
function getActiveLeafs(mode: Mode, sc: Statechart) {
|
function getActiveLeafs(mode: Mode, sc: Statechart) {
|
||||||
return new Set([...mode].filter(uid =>
|
return new Set([...mode].filter(uid =>
|
||||||
|
// @ts-ignore
|
||||||
sc.uid2State.get(uid)?.children?.length === 0
|
sc.uid2State.get(uid)?.children?.length === 0
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export function ShowAST(props: {root: ConcreteState | PseudoState, transitions:
|
||||||
const description = stateDescription(props.root);
|
const description = stateDescription(props.root);
|
||||||
// const outgoing = props.transitions.get(props.root.uid) || [];
|
// const outgoing = props.transitions.get(props.root.uid) || [];
|
||||||
|
|
||||||
return <li>{props.root.kind}: {description}
|
return <li >{props.root.kind}: {description}
|
||||||
{props.root.kind !== "pseudo" && props.root.children.length>0 &&
|
{props.root.kind !== "pseudo" && props.root.children.length>0 &&
|
||||||
<ul>
|
<ul>
|
||||||
{props.root.children.map(child =>
|
{props.root.children.map(child =>
|
||||||
|
|
@ -46,29 +46,29 @@ export function ShowAST(props: {root: ConcreteState | PseudoState, transitions:
|
||||||
}
|
}
|
||||||
</li>;
|
</li>;
|
||||||
|
|
||||||
return <details open={true} className={"stateTree" + (props.highlightActive.has(props.root.uid) ? " active" : "")}>
|
// return <details open={true} className={"stateTree" + (props.highlightActive.has(props.root.uid) ? " active" : "")}>
|
||||||
<summary>{props.root.kind}: {description}</summary>
|
// <summary>{props.root.kind}: {description}</summary>
|
||||||
|
|
||||||
{/* {props.root.kind !== "pseudo" && props.root.entryActions.length>0 &&
|
// {/* {props.root.kind !== "pseudo" && props.root.entryActions.length>0 &&
|
||||||
props.root.entryActions.map(action =>
|
// props.root.entryActions.map(action =>
|
||||||
<div> entry / <ShowAction action={action}/></div>
|
// <div> entry / <ShowAction action={action}/></div>
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
{props.root.kind !== "pseudo" && props.root.exitActions.length>0 &&
|
// {props.root.kind !== "pseudo" && props.root.exitActions.length>0 &&
|
||||||
props.root.exitActions.map(action =>
|
// props.root.exitActions.map(action =>
|
||||||
<div> exit / <ShowAction action={action}/></div>
|
// <div> exit / <ShowAction action={action}/></div>
|
||||||
)
|
// )
|
||||||
} */}
|
// } */}
|
||||||
|
|
||||||
{props.root.kind !== "pseudo" && props.root.children.length>0 &&
|
// {props.root.kind !== "pseudo" && props.root.children.length>0 &&
|
||||||
props.root.children.map(child =>
|
// props.root.children.map(child =>
|
||||||
<ShowAST key={child.uid} root={child} transitions={props.transitions} rt={props.rt} highlightActive={props.highlightActive} />
|
// <ShowAST key={child.uid} root={child} transitions={props.transitions} rt={props.rt} highlightActive={props.highlightActive} />
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
{/* {outgoing.length>0 &&
|
// {/* {outgoing.length>0 &&
|
||||||
outgoing.map(transition => <> <ShowTransition transition={transition}/><br/></>)
|
// outgoing.map(transition => <> <ShowTransition transition={transition}/><br/></>)
|
||||||
} */}
|
// } */}
|
||||||
</details>;
|
// </details>;
|
||||||
}
|
}
|
||||||
|
|
||||||
import BoltIcon from '@mui/icons-material/Bolt';
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export function TextDialog(props: {setModal: Dispatch<SetStateAction<ReactElemen
|
||||||
|
|
||||||
return <div onKeyDown={onKeyDown} style={{padding: 4}}>
|
return <div onKeyDown={onKeyDown} style={{padding: 4}}>
|
||||||
Text label:<br/>
|
Text label:<br/>
|
||||||
<textarea autoFocus style={{fontFamily: 'Roboto', width:'calc(100%-10px)', height: 60}} onChange={e=>setText(e.target.value)}>{text}</textarea>
|
<textarea autoFocus style={{fontFamily: 'Roboto', width:'calc(100%-10px)', height: 60}} onChange={e=>setText(e.target.value)} value={text}/>
|
||||||
<br/>
|
<br/>
|
||||||
<span style={{color: 'var(--error-color)'}}>{error}</span><br/>
|
<span style={{color: 'var(--error-color)'}}>{error}</span><br/>
|
||||||
<p><kbd>Enter</kbd> to confirm. <kbd>Esc</kbd> to cancel.
|
<p><kbd>Enter</kbd> to confirm. <kbd>Esc</kbd> to cancel.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Dispatch, ReactElement, SetStateAction, useEffect, useMemo, useRef, useState } from "react";
|
import { Dispatch, ReactElement, SetStateAction, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import { Statechart } from "../statecharts/abstract_syntax";
|
import { Statechart } from "../statecharts/abstract_syntax";
|
||||||
import { Arrow, ArrowPart, Diamond, History, Rountangle, RountanglePart, Text, VisualEditorState, emptyState } from "../statecharts/concrete_syntax";
|
import { Arrow, ArrowPart, Diamond, History, Rountangle, RountanglePart, Text } from "../statecharts/concrete_syntax";
|
||||||
import { parseStatechart, TraceableError } from "../statecharts/parser";
|
import { parseStatechart, TraceableError } from "../statecharts/parser";
|
||||||
import { BigStep } from "../statecharts/runtime_types";
|
import { BigStep } from "../statecharts/runtime_types";
|
||||||
import { ArcDirection, Line2D, Rect2D, Vec2D, addV2D, arcDirection, area, getBottomSide, getLeftSide, getRightSide, getTopSide, isEntirelyWithin, normalizeRect, subtractV2D, transformLine, transformRect } from "./geometry";
|
import { ArcDirection, Line2D, Rect2D, Vec2D, addV2D, arcDirection, area, getBottomSide, getLeftSide, getRightSide, getTopSide, isEntirelyWithin, normalizeRect, subtractV2D, transformLine, transformRect } from "./geometry";
|
||||||
|
|
@ -16,6 +16,16 @@ import { detectConnections } from "../statecharts/detect_connections";
|
||||||
|
|
||||||
import "./VisualEditor.css";
|
import "./VisualEditor.css";
|
||||||
|
|
||||||
|
export type VisualEditorState = {
|
||||||
|
rountangles: Rountangle[];
|
||||||
|
texts: Text[];
|
||||||
|
arrows: Arrow[];
|
||||||
|
diamonds: Diamond[];
|
||||||
|
history: History[];
|
||||||
|
nextID: number;
|
||||||
|
selection: Selection;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
type DraggingState = {
|
type DraggingState = {
|
||||||
lastMousePos: Vec2D;
|
lastMousePos: Vec2D;
|
||||||
|
|
@ -42,6 +52,7 @@ type HistorySelectable = {
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
type Selectable = RountangleSelectable | ArrowSelectable | TextSelectable | HistorySelectable;
|
type Selectable = RountangleSelectable | ArrowSelectable | TextSelectable | HistorySelectable;
|
||||||
|
|
||||||
type Selection = Selectable[];
|
type Selection = Selectable[];
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -74,7 +85,10 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
const [dragging, setDragging] = useState<DraggingState>(null);
|
const [dragging, setDragging] = useState<DraggingState>(null);
|
||||||
|
|
||||||
// uid's of selected rountangles
|
// uid's of selected rountangles
|
||||||
const [selection, setSelection] = useState<Selection>([]);
|
// const [selection, setSelection] = useState<Selection>([]);
|
||||||
|
const selection = state.selection || [];
|
||||||
|
const setSelection = (cb: (oldSelection: Selection) => Selection) =>
|
||||||
|
setState(oldState => ({...oldState, selection: cb(oldState.selection)}));
|
||||||
|
|
||||||
// not null while the user is making a selection
|
// not null while the user is making a selection
|
||||||
const [selectingState, setSelectingState] = useState<SelectingState>(null);
|
const [selectingState, setSelectingState] = useState<SelectingState>(null);
|
||||||
|
|
@ -155,7 +169,6 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
const newID = state.nextID.toString();
|
const newID = state.nextID.toString();
|
||||||
if (mode === "and" || mode === "or") {
|
if (mode === "and" || mode === "or") {
|
||||||
// insert rountangle
|
// insert rountangle
|
||||||
setSelection([{uid: newID, parts: ["bottom", "right"]}]);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
rountangles: [...state.rountangles, {
|
rountangles: [...state.rountangles, {
|
||||||
|
|
@ -165,10 +178,10 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
kind: mode,
|
kind: mode,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
|
selection: [{uid: newID, parts: ["bottom", "right"]}],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (mode === "pseudo") {
|
else if (mode === "pseudo") {
|
||||||
setSelection([{uid: newID, parts: ["bottom", "right"]}]);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
diamonds: [...state.diamonds, {
|
diamonds: [...state.diamonds, {
|
||||||
|
|
@ -177,10 +190,10 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
size: MIN_ROUNTANGLE_SIZE,
|
size: MIN_ROUNTANGLE_SIZE,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
|
selection: [{uid: newID, parts: ["bottom", "right"]}],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (mode === "shallow" || mode === "deep") {
|
else if (mode === "shallow" || mode === "deep") {
|
||||||
setSelection([{uid: newID, parts: ["history"]}]);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
history: [...state.history, {
|
history: [...state.history, {
|
||||||
|
|
@ -189,10 +202,10 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
|
selection: [{uid: newID, parts: ["history"]}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode === "transition") {
|
else if (mode === "transition") {
|
||||||
setSelection([{uid: newID, parts: ["end"]}]);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
arrows: [...state.arrows, {
|
arrows: [...state.arrows, {
|
||||||
|
|
@ -201,10 +214,10 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
end: currentPointer,
|
end: currentPointer,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
|
selection: [{uid: newID, parts: ["end"]}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode === "text") {
|
else if (mode === "text") {
|
||||||
setSelection([{uid: newID, parts: ["text"]}]);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
texts: [...state.texts, {
|
texts: [...state.texts, {
|
||||||
|
|
@ -213,6 +226,7 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
|
selection: [{uid: newID, parts: ["text"]}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error("unreachable, mode=" + mode); // shut up typescript
|
throw new Error("unreachable, mode=" + mode); // shut up typescript
|
||||||
|
|
@ -240,7 +254,7 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
}
|
}
|
||||||
if (!allPartsInSelection) {
|
if (!allPartsInSelection) {
|
||||||
if (e.target.classList.contains("helper")) {
|
if (e.target.classList.contains("helper")) {
|
||||||
setSelection([{uid, parts}] as Selection);
|
setSelection(() => [{uid, parts}] as Selection);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setDragging(null);
|
setDragging(null);
|
||||||
|
|
@ -248,7 +262,7 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
size: {x: 0, y: 0},
|
size: {x: 0, y: 0},
|
||||||
});
|
});
|
||||||
setSelection([]);
|
setSelection(() => []);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +281,7 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
size: {x: 0, y: 0},
|
size: {x: 0, y: 0},
|
||||||
});
|
});
|
||||||
setSelection([]);
|
setSelection(() => []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseMove = (e: {pageX: number, pageY: number, movementX: number, movementY: number}) => {
|
const onMouseMove = (e: {pageX: number, pageY: number, movementX: number, movementY: number}) => {
|
||||||
|
|
@ -410,8 +424,8 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
history: state.history.filter(h => !selection.some(hs => hs.uid === h.uid)),
|
history: state.history.filter(h => !selection.some(hs => hs.uid === h.uid)),
|
||||||
arrows: state.arrows.filter(a => !selection.some(as => as.uid === a.uid)),
|
arrows: state.arrows.filter(a => !selection.some(as => as.uid === a.uid)),
|
||||||
texts: state.texts.filter(t => !selection.some(ts => ts.uid === t.uid)),
|
texts: state.texts.filter(t => !selection.some(ts => ts.uid === t.uid)),
|
||||||
|
selection: [],
|
||||||
}));
|
}));
|
||||||
setSelection([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
|
@ -564,15 +578,6 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
uid: (nextID++).toString(),
|
uid: (nextID++).toString(),
|
||||||
topLeft: addV2D(h.topLeft, offset),
|
topLeft: addV2D(h.topLeft, offset),
|
||||||
}))
|
}))
|
||||||
setState(state => ({
|
|
||||||
...state,
|
|
||||||
rountangles: [...state.rountangles, ...copiedRountangles],
|
|
||||||
diamonds: [...state.diamonds, ...copiedDiamonds],
|
|
||||||
arrows: [...state.arrows, ...copiedArrows],
|
|
||||||
texts: [...state.texts, ...copiedTexts],
|
|
||||||
history: [...state.history, ...copiedHistories],
|
|
||||||
nextID: nextID,
|
|
||||||
}));
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const newSelection: Selection = [
|
const newSelection: Selection = [
|
||||||
...copiedRountangles.map(r => ({uid: r.uid, parts: ["left", "top", "right", "bottom"]})),
|
...copiedRountangles.map(r => ({uid: r.uid, parts: ["left", "top", "right", "bottom"]})),
|
||||||
|
|
@ -581,7 +586,16 @@ export function VisualEditor({state, setState, ast, setAST, rt, errors, setError
|
||||||
...copiedTexts.map(t => ({uid: t.uid, parts: ["text"]})),
|
...copiedTexts.map(t => ({uid: t.uid, parts: ["text"]})),
|
||||||
...copiedHistories.map(h => ({uid: h.uid, parts: ["history"]})),
|
...copiedHistories.map(h => ({uid: h.uid, parts: ["history"]})),
|
||||||
];
|
];
|
||||||
setSelection(newSelection);
|
setState(state => ({
|
||||||
|
...state,
|
||||||
|
rountangles: [...state.rountangles, ...copiedRountangles],
|
||||||
|
diamonds: [...state.diamonds, ...copiedDiamonds],
|
||||||
|
arrows: [...state.arrows, ...copiedArrows],
|
||||||
|
texts: [...state.texts, ...copiedTexts],
|
||||||
|
history: [...state.history, ...copiedHistories],
|
||||||
|
nextID: nextID,
|
||||||
|
selection: newSelection,
|
||||||
|
}));
|
||||||
// copyInternal(newSelection, e); // doesn't work
|
// copyInternal(newSelection, e); // doesn't work
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Rect2D, Vec2D, Line2D, euclideanDistance, intersectLines, isWithin, lineBBox, subtractV2D } from "../VisualEditor/geometry";
|
import { Rect2D, Vec2D, Line2D, euclideanDistance, intersectLines, isWithin, lineBBox, subtractV2D } from "../VisualEditor/geometry";
|
||||||
import { ARROW_SNAP_THRESHOLD, HISTORY_RADIUS, TEXT_SNAP_THRESHOLD } from "../VisualEditor/parameters";
|
import { ARROW_SNAP_THRESHOLD, HISTORY_RADIUS, TEXT_SNAP_THRESHOLD } from "../VisualEditor/parameters";
|
||||||
import { sides } from "../VisualEditor/VisualEditor";
|
import { sides, VisualEditorState } from "../VisualEditor/VisualEditor";
|
||||||
|
|
||||||
export type Rountangle = {
|
export type Rountangle = {
|
||||||
uid: string;
|
uid: string;
|
||||||
|
|
@ -27,15 +27,6 @@ export type History = {
|
||||||
topLeft: Vec2D;
|
topLeft: Vec2D;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VisualEditorState = {
|
|
||||||
rountangles: Rountangle[];
|
|
||||||
texts: Text[];
|
|
||||||
arrows: Arrow[];
|
|
||||||
diamonds: Diamond[];
|
|
||||||
history: History[];
|
|
||||||
nextID: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
// independently moveable parts of our shapes:
|
// independently moveable parts of our shapes:
|
||||||
export type RountanglePart = "left" | "top" | "right" | "bottom";
|
export type RountanglePart = "left" | "top" | "right" | "bottom";
|
||||||
export type ArrowPart = "start" | "end";
|
export type ArrowPart = "start" | "end";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { findNearestArrow, findNearestHistory, findNearestSide, findRountangle, RountanglePart, VisualEditorState } from "./concrete_syntax";
|
import { VisualEditorState } from "@/VisualEditor/VisualEditor";
|
||||||
|
import { findNearestArrow, findNearestHistory, findNearestSide, findRountangle, RountanglePart } from "./concrete_syntax";
|
||||||
|
|
||||||
export type Connections = {
|
export type Connections = {
|
||||||
arrow2SideMap: Map<string,[{ uid: string; part: RountanglePart; } | undefined, { uid: string; part: RountanglePart; } | undefined]>,
|
arrow2SideMap: Map<string,[{ uid: string; part: RountanglePart; } | undefined, { uid: string; part: RountanglePart; } | undefined]>,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { ConcreteState, HistoryState, OrState, PseudoState, Statechart, Transition } from "./abstract_syntax";
|
import { ConcreteState, HistoryState, OrState, PseudoState, Statechart, Transition } from "./abstract_syntax";
|
||||||
import { Rountangle, VisualEditorState } from "./concrete_syntax";
|
import { Rountangle } from "./concrete_syntax";
|
||||||
import { isEntirelyWithin, Rect2D } from "../VisualEditor/geometry";
|
import { isEntirelyWithin, Rect2D } from "../VisualEditor/geometry";
|
||||||
import { Action, EventTrigger, Expression, ParsedText } from "./label_ast";
|
import { Action, EventTrigger, Expression, ParsedText } from "./label_ast";
|
||||||
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
||||||
import { Connections } from "./detect_connections";
|
import { Connections } from "./detect_connections";
|
||||||
import { HISTORY_RADIUS } from "../VisualEditor/parameters";
|
import { HISTORY_RADIUS } from "../VisualEditor/parameters";
|
||||||
|
import { VisualEditorState } from "@/VisualEditor/VisualEditor";
|
||||||
|
|
||||||
export type TraceableError = {
|
export type TraceableError = {
|
||||||
shapeUid: string;
|
shapeUid: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue