can draw history states
This commit is contained in:
parent
e8fda9bdf0
commit
28071eb1f3
8 changed files with 166 additions and 63 deletions
|
|
@ -28,31 +28,47 @@ export function ArrowSVG(props: { arrow: Arrow; selected: string[]; errors: stri
|
||||||
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.errors.join(', ')}</text>}
|
||||||
|
|
||||||
<path
|
<path
|
||||||
className="pathHelper helper"
|
className="helper"
|
||||||
d={`M ${start.x} ${start.y}
|
d={`M ${start.x} ${start.y}
|
||||||
${arcOrLine}
|
${arcOrLine}
|
||||||
${end.x} ${end.y}`}
|
${end.x} ${end.y}`}
|
||||||
data-uid={uid}
|
data-uid={uid}
|
||||||
data-parts="start end" />
|
data-parts="start end" />
|
||||||
|
|
||||||
|
{/* selection helper circles */}
|
||||||
<circle
|
<circle
|
||||||
className={"circleHelper helper"
|
className="helper"
|
||||||
+ (props.selected.includes("start") ? " selected" : "")}
|
|
||||||
cx={start.x}
|
cx={start.x}
|
||||||
cy={start.y}
|
cy={start.y}
|
||||||
r={CORNER_HELPER_RADIUS}
|
r={CORNER_HELPER_RADIUS}
|
||||||
data-uid={uid}
|
data-uid={uid}
|
||||||
data-parts="start" />
|
data-parts="start" />
|
||||||
<circle
|
<circle
|
||||||
className={"circleHelper helper"
|
className="helper"
|
||||||
+ (props.selected.includes("end") ? " selected" : "")}
|
|
||||||
cx={end.x}
|
cx={end.x}
|
||||||
cy={end.y}
|
cy={end.y}
|
||||||
r={CORNER_HELPER_RADIUS}
|
r={CORNER_HELPER_RADIUS}
|
||||||
data-uid={uid}
|
data-uid={uid}
|
||||||
data-parts="end" />
|
data-parts="end" />
|
||||||
|
|
||||||
|
{/* selection indicator circles */}
|
||||||
|
{props.selected.includes("start") && <circle
|
||||||
|
className="selected"
|
||||||
|
cx={start.x}
|
||||||
|
cy={start.y}
|
||||||
|
r={CORNER_HELPER_RADIUS}
|
||||||
|
data-uid={uid}
|
||||||
|
data-parts="start" />}
|
||||||
|
{props.selected.includes("end") && <circle
|
||||||
|
className="selected"
|
||||||
|
cx={end.x}
|
||||||
|
cy={end.y}
|
||||||
|
r={CORNER_HELPER_RADIUS}
|
||||||
|
data-uid={uid}
|
||||||
|
data-parts="end" />}
|
||||||
|
|
||||||
</g>;
|
</g>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,10 @@ export function DiamondSVG(props: { diamond: Diamond; selected: string[]; highli
|
||||||
return <g transform={`translate(${props.diamond.topLeft.x} ${props.diamond.topLeft.y})`}>
|
return <g transform={`translate(${props.diamond.topLeft.x} ${props.diamond.topLeft.y})`}>
|
||||||
<DiamondShape size={minSize} extraAttrs={extraAttrs}/>
|
<DiamondShape size={minSize} extraAttrs={extraAttrs}/>
|
||||||
|
|
||||||
<RectHelper uid={props.diamond.uid} size={minSize} highlight={props.highlight} selected={props.selected} />
|
|
||||||
|
|
||||||
<text x={minSize.x/2} y={minSize.y/2}
|
<text x={minSize.x/2} y={minSize.y/2}
|
||||||
className="uid"
|
className="uid"
|
||||||
textAnchor="middle"
|
textAnchor="middle">{props.diamond.uid}</text>
|
||||||
data-uid={props.diamond.uid}>{props.diamond.uid}</text>
|
|
||||||
|
|
||||||
|
<RectHelper uid={props.diamond.uid} size={minSize} highlight={props.highlight} selected={props.selected} />
|
||||||
</g>;
|
</g>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,33 @@
|
||||||
export function ShallowHistorySVG() {
|
import { Vec2D } from "./geometry";
|
||||||
|
import { HISTORY_RADIUS } from "./parameters";
|
||||||
|
|
||||||
|
export function HistorySVG(props: {uid: string, topLeft: Vec2D, kind: "shallow"|"deep", selected: boolean}) {
|
||||||
|
const text = props.kind === "shallow" ? "H" : "H*";
|
||||||
|
return <>
|
||||||
|
<circle
|
||||||
|
className={props.selected ? "selected":""}
|
||||||
|
cx={props.topLeft.x+HISTORY_RADIUS}
|
||||||
|
cy={props.topLeft.y+HISTORY_RADIUS}
|
||||||
|
r={HISTORY_RADIUS}
|
||||||
|
fill="white"
|
||||||
|
stroke="black"
|
||||||
|
strokeWidth={2}
|
||||||
|
data-uid={props.uid}
|
||||||
|
data-parts="history"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
x={props.topLeft.x+HISTORY_RADIUS}
|
||||||
|
y={props.topLeft.y+HISTORY_RADIUS+4}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontWeight={500}
|
||||||
|
>{text}</text>
|
||||||
|
<circle
|
||||||
|
className="helper"
|
||||||
|
cx={props.topLeft.x+HISTORY_RADIUS}
|
||||||
|
cy={props.topLeft.y+HISTORY_RADIUS}
|
||||||
|
r={HISTORY_RADIUS}
|
||||||
|
data-uid={props.uid}
|
||||||
|
data-parts="history"
|
||||||
|
/>
|
||||||
|
</>;
|
||||||
}
|
}
|
||||||
|
|
@ -23,29 +23,30 @@ export function RectHelper(props: { uid: string, size: Vec2D, selected: string[]
|
||||||
<line className="helper" {...ps} data-uid={props.uid} data-parts={side}/>
|
<line className="helper" {...ps} data-uid={props.uid} data-parts={side}/>
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
|
{/* The corner-helpers have the DOM class 'corner' added to them, because we ignore them when the user is making a selection. Only if the user clicks directly on them, do we select their respective parts. */}
|
||||||
<circle
|
<circle
|
||||||
className="helper"
|
className="helper corner"
|
||||||
cx={CORNER_HELPER_OFFSET}
|
cx={CORNER_HELPER_OFFSET}
|
||||||
cy={CORNER_HELPER_OFFSET}
|
cy={CORNER_HELPER_OFFSET}
|
||||||
r={CORNER_HELPER_RADIUS}
|
r={CORNER_HELPER_RADIUS}
|
||||||
data-uid={props.uid}
|
data-uid={props.uid}
|
||||||
data-parts="top left" />
|
data-parts="top left" />
|
||||||
<circle
|
<circle
|
||||||
className="helper"
|
className="helper corner"
|
||||||
cx={props.size.x - CORNER_HELPER_OFFSET}
|
cx={props.size.x - CORNER_HELPER_OFFSET}
|
||||||
cy={CORNER_HELPER_OFFSET}
|
cy={CORNER_HELPER_OFFSET}
|
||||||
r={CORNER_HELPER_RADIUS}
|
r={CORNER_HELPER_RADIUS}
|
||||||
data-uid={props.uid}
|
data-uid={props.uid}
|
||||||
data-parts="top right" />
|
data-parts="top right" />
|
||||||
<circle
|
<circle
|
||||||
className="helper"
|
className="helper corner"
|
||||||
cx={props.size.x - CORNER_HELPER_OFFSET}
|
cx={props.size.x - CORNER_HELPER_OFFSET}
|
||||||
cy={props.size.y - CORNER_HELPER_OFFSET}
|
cy={props.size.y - CORNER_HELPER_OFFSET}
|
||||||
r={CORNER_HELPER_RADIUS}
|
r={CORNER_HELPER_RADIUS}
|
||||||
data-uid={props.uid}
|
data-uid={props.uid}
|
||||||
data-parts="bottom right" />
|
data-parts="bottom right" />
|
||||||
<circle
|
<circle
|
||||||
className="helper"
|
className="helper corner"
|
||||||
cx={CORNER_HELPER_OFFSET}
|
cx={CORNER_HELPER_OFFSET}
|
||||||
cy={props.size.y - CORNER_HELPER_OFFSET}
|
cy={props.size.y - CORNER_HELPER_OFFSET}
|
||||||
r={CORNER_HELPER_RADIUS}
|
r={CORNER_HELPER_RADIUS}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ export function RountangleSVG(props: { rountangle: Rountangle; selected: string[
|
||||||
{...extraAttrs}
|
{...extraAttrs}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<text x={10} y={20} className="uid">{props.rountangle.uid}</text>
|
||||||
|
|
||||||
{(props.errors.length > 0) &&
|
{(props.errors.length > 0) &&
|
||||||
<text className="error" x={10} y={40} data-uid={uid} data-parts="left top right bottom">{props.errors.join(' ')}</text>}
|
<text className="error" x={10} y={40} data-uid={uid} data-parts="left top right bottom">{props.errors.join(' ')}</text>}
|
||||||
|
|
||||||
|
|
@ -35,9 +37,6 @@ export function RountangleSVG(props: { rountangle: Rountangle; selected: string[
|
||||||
selected={props.selected}
|
selected={props.selected}
|
||||||
highlight={props.highlight} />
|
highlight={props.highlight} />
|
||||||
|
|
||||||
<text x={10} y={20}
|
|
||||||
className="uid"
|
|
||||||
data-uid={props.rountangle.uid}>{props.rountangle.uid}</text>
|
|
||||||
|
|
||||||
</g>;
|
</g>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@
|
||||||
background-color: rgb(255, 140, 0, 0.2);
|
background-color: rgb(255, 140, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.svgCanvas text {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* rectangle drawn while a selection is being made */
|
/* rectangle drawn while a selection is being made */
|
||||||
.selecting {
|
.selecting {
|
||||||
fill: blue;
|
fill: blue;
|
||||||
|
|
@ -32,7 +36,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.rountangle.selected {
|
.rountangle.selected {
|
||||||
fill: rgba(0, 0, 255, 0.2);
|
/* fill: rgba(0, 0, 255, 0.2); */
|
||||||
}
|
}
|
||||||
.rountangle.error {
|
.rountangle.error {
|
||||||
stroke: rgb(230,0,0);
|
stroke: rgb(230,0,0);
|
||||||
|
|
@ -127,7 +131,6 @@ text.helper:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggableText, .draggableText.highlight {
|
.draggableText, .draggableText.highlight {
|
||||||
user-select: none;
|
|
||||||
/* text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; */
|
/* text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; */
|
||||||
/* -webkit-text-stroke: 4px white; */
|
/* -webkit-text-stroke: 4px white; */
|
||||||
paint-order: stroke;
|
paint-order: stroke;
|
||||||
|
|
@ -154,7 +157,7 @@ text.helper:hover {
|
||||||
.arrow.error {
|
.arrow.error {
|
||||||
stroke: rgb(230,0,0);
|
stroke: rgb(230,0,0);
|
||||||
}
|
}
|
||||||
.draggableText.error, tspan.error {
|
text.error, tspan.error {
|
||||||
fill: rgb(230,0,0);
|
fill: rgb(230,0,0);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { ArrowSVG } from "./ArrowSVG";
|
||||||
import { RountangleSVG } from "./RountangleSVG";
|
import { RountangleSVG } from "./RountangleSVG";
|
||||||
import { TextSVG } from "./TextSVG";
|
import { TextSVG } from "./TextSVG";
|
||||||
import { DiamondSVG } from "./DiamondSVG";
|
import { DiamondSVG } from "./DiamondSVG";
|
||||||
|
import { HistorySVG } from "./HistorySVG";
|
||||||
|
|
||||||
|
|
||||||
type DraggingState = {
|
type DraggingState = {
|
||||||
|
|
@ -36,7 +37,11 @@ type TextSelectable = {
|
||||||
parts: ["text"];
|
parts: ["text"];
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
type Selectable = RountangleSelectable | ArrowSelectable | TextSelectable;
|
type HistorySelectable = {
|
||||||
|
parts: ["history"];
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
type Selectable = RountangleSelectable | ArrowSelectable | TextSelectable | HistorySelectable;
|
||||||
type Selection = Selectable[];
|
type Selection = Selectable[];
|
||||||
|
|
||||||
type HistoryState = {
|
type HistoryState = {
|
||||||
|
|
@ -52,7 +57,7 @@ export const sides: [RountanglePart, (r:Rect2D)=>Line2D][] = [
|
||||||
["bottom", getBottomSide],
|
["bottom", getBottomSide],
|
||||||
];
|
];
|
||||||
|
|
||||||
export type InsertMode = "and"|"or"|"pseudo"|"transition"|"text";
|
export type InsertMode = "and"|"or"|"pseudo"|"shallow"|"deep"|"transition"|"text";
|
||||||
|
|
||||||
type VisualEditorProps = {
|
type VisualEditorProps = {
|
||||||
setAST: Dispatch<SetStateAction<Statechart>>,
|
setAST: Dispatch<SetStateAction<Statechart>>,
|
||||||
|
|
@ -65,8 +70,6 @@ type VisualEditorProps = {
|
||||||
export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditorProps) {
|
export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditorProps) {
|
||||||
const [historyState, setHistoryState] = useState<HistoryState>({current: emptyState, history: [], future: []});
|
const [historyState, setHistoryState] = useState<HistoryState>({current: emptyState, history: [], future: []});
|
||||||
|
|
||||||
const [clipboard, setClipboard] = useState<Set<string>>(new Set());
|
|
||||||
|
|
||||||
const state = historyState.current;
|
const state = historyState.current;
|
||||||
const setState = (s: SetStateAction<VisualEditorState>) => {
|
const setState = (s: SetStateAction<VisualEditorState>) => {
|
||||||
setHistoryState(historyState => {
|
setHistoryState(historyState => {
|
||||||
|
|
@ -170,7 +173,7 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMouseDown = (e: MouseEvent) => {
|
const onMouseDown = (e: {button: number, target: any, pageX: number, pageY: number}) => {
|
||||||
const currentPointer = getCurrentPointer(e);
|
const currentPointer = getCurrentPointer(e);
|
||||||
|
|
||||||
if (e.button === 2) {
|
if (e.button === 2) {
|
||||||
|
|
@ -204,6 +207,18 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
else if (mode === "shallow" || mode === "deep") {
|
||||||
|
setSelection([{uid: newID, parts: ["history"]}]);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
history: [...state.history, {
|
||||||
|
uid: newID,
|
||||||
|
kind: mode,
|
||||||
|
topLeft: currentPointer,
|
||||||
|
}],
|
||||||
|
nextID: state.nextID+1,
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (mode === "transition") {
|
else if (mode === "transition") {
|
||||||
setSelection([{uid: newID, parts: ["end"]}]);
|
setSelection([{uid: newID, parts: ["end"]}]);
|
||||||
return {
|
return {
|
||||||
|
|
@ -238,11 +253,10 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
|
|
||||||
if (e.button === 0) {
|
if (e.button === 0) {
|
||||||
// left mouse button on a shape will drag that shape (and everything else that's selected). if the shape under the pointer was not in the selection then the selection is reset to contain only that shape.
|
// left mouse button on a shape will drag that shape (and everything else that's selected). if the shape under the pointer was not in the selection then the selection is reset to contain only that shape.
|
||||||
// @ts-ignore
|
|
||||||
const uid = e.target?.dataset.uid;
|
const uid = e.target?.dataset.uid;
|
||||||
// @ts-ignore
|
const parts: string[] = e.target?.dataset.parts?.split(' ').filter((p:string) => p!=="") || [];
|
||||||
const parts: string[] = e.target?.dataset.parts?.split(' ') || [];
|
if (uid && parts.length > 0) {
|
||||||
if (uid) {
|
console.log('start drag');
|
||||||
checkPoint();
|
checkPoint();
|
||||||
|
|
||||||
// if the mouse button is pressed outside of the current selection, we reset the selection to whatever shape the mouse is on
|
// if the mouse button is pressed outside of the current selection, we reset the selection to whatever shape the mouse is on
|
||||||
|
|
@ -254,8 +268,19 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!allPartsInSelection) {
|
if (!allPartsInSelection) {
|
||||||
|
if (e.target.classList.contains("helper")) {
|
||||||
setSelection([{uid, parts}] as Selection);
|
setSelection([{uid, parts}] as Selection);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
setDragging(null);
|
||||||
|
setSelectingState({
|
||||||
|
topLeft: currentPointer,
|
||||||
|
size: {x: 0, y: 0},
|
||||||
|
});
|
||||||
|
setSelection([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// start dragging
|
// start dragging
|
||||||
setDragging({
|
setDragging({
|
||||||
|
|
@ -291,6 +316,26 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.toSorted((a,b) => area(b) - area(a)), // sort: smaller rountangles are drawn on top
|
.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 || [];
|
||||||
|
if (parts.length === 0) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
...transformRect(d, parts, pointerDelta),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
history: state.history.map(h => {
|
||||||
|
const parts = selection.find(selected => selected.uid === h.uid)?.parts || [];
|
||||||
|
if (parts.length === 0) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...h,
|
||||||
|
topLeft: addV2D(h.topLeft, pointerDelta),
|
||||||
|
}
|
||||||
|
}),
|
||||||
arrows: state.arrows.map(a => {
|
arrows: state.arrows.map(a => {
|
||||||
const parts = selection.find(selected => selected.uid === a.uid)?.parts || [];
|
const parts = selection.find(selected => selected.uid === a.uid)?.parts || [];
|
||||||
if (parts.length === 0) {
|
if (parts.length === 0) {
|
||||||
|
|
@ -311,16 +356,6 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
topLeft: addV2D(t.topLeft, pointerDelta),
|
topLeft: addV2D(t.topLeft, pointerDelta),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
diamonds: state.diamonds.map(d => {
|
|
||||||
const parts = selection.find(selected => selected.uid === d.uid)?.parts || [];
|
|
||||||
if (parts.length === 0) {
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...d,
|
|
||||||
...transformRect(d, parts, pointerDelta),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}));
|
}));
|
||||||
setDragging({lastMousePos: currentPointer});
|
setDragging({lastMousePos: currentPointer});
|
||||||
}
|
}
|
||||||
|
|
@ -335,7 +370,7 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseUp = (e: {pageX: number, pageY: number}) => {
|
const onMouseUp = (e: {target: any, pageX: number, pageY: number}) => {
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
setDragging(null);
|
setDragging(null);
|
||||||
// do not persist sizes smaller than 40x40
|
// do not persist sizes smaller than 40x40
|
||||||
|
|
@ -354,6 +389,19 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (selectingState) {
|
if (selectingState) {
|
||||||
|
if (selectingState.size.x === 0 && selectingState.size.y === 0) {
|
||||||
|
const uid = e.target?.dataset.uid;
|
||||||
|
if (uid) {
|
||||||
|
const parts = e.target?.dataset.parts.split(' ').filter((p: string) => p!=="");
|
||||||
|
if (uid) {
|
||||||
|
setSelection(() => [{
|
||||||
|
uid,
|
||||||
|
parts,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
// we were making a selection
|
// we were making a selection
|
||||||
const normalizedSS = normalizeRect(selectingState);
|
const normalizedSS = normalizeRect(selectingState);
|
||||||
const shapes = Array.from(refSVG.current?.querySelectorAll("rect, line, circle, text") || []) as SVGGraphicsElement[];
|
const shapes = Array.from(refSVG.current?.querySelectorAll("rect, line, circle, text") || []) as SVGGraphicsElement[];
|
||||||
|
|
@ -378,6 +426,7 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
parts: [...parts],
|
parts: [...parts],
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
setSelectingState(null); // no longer making a selection
|
setSelectingState(null); // no longer making a selection
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -385,9 +434,10 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
...state,
|
...state,
|
||||||
rountangles: state.rountangles.filter(r => !selection.some(rs => rs.uid === r.uid)),
|
rountangles: state.rountangles.filter(r => !selection.some(rs => rs.uid === r.uid)),
|
||||||
|
diamonds: state.diamonds.filter(d => !selection.some(ds => ds.uid === d.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)),
|
||||||
diamonds: state.diamonds.filter(d => !selection.some(ds => ds.uid === d.uid)),
|
|
||||||
}));
|
}));
|
||||||
setSelection([]);
|
setSelection([]);
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +512,7 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
window.removeEventListener("mousemove", onMouseMove);
|
window.removeEventListener("mousemove", onMouseMove);
|
||||||
window.removeEventListener("mouseup", onMouseUp);
|
window.removeEventListener("mouseup", onMouseUp);
|
||||||
};
|
};
|
||||||
}, [selectingState, dragging, clipboard]);
|
}, [selectingState, dragging]);
|
||||||
|
|
||||||
// detect what is 'connected'
|
// 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: RountanglePart; } | undefined, { uid: string; part: RountanglePart; } | undefined]>();
|
||||||
|
|
@ -712,6 +762,10 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
|
||||||
active={active.has(diamond.uid)}/>
|
active={active.has(diamond.uid)}/>
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
|
{state.history.map(history => <>
|
||||||
|
<HistorySVG {...history} selected={selection.find(h => h.uid === history.uid)} />
|
||||||
|
</>)}
|
||||||
|
|
||||||
{state.arrows.map(arrow => {
|
{state.arrows.map(arrow => {
|
||||||
const sides = arrow2SideMap.get(arrow.uid);
|
const sides = arrow2SideMap.get(arrow.uid);
|
||||||
let arc = "no" as ArcDirection;
|
let arc = "no" as ArcDirection;
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,5 @@ export const MIN_ROUNTANGLE_SIZE = { x: ROUNTANGLE_RADIUS*2, y: ROUNTANGLE_RADIU
|
||||||
// those hoverable green transparent circles in the corners of rountangles:
|
// those hoverable green transparent circles in the corners of rountangles:
|
||||||
export const CORNER_HELPER_OFFSET = 4;
|
export const CORNER_HELPER_OFFSET = 4;
|
||||||
export const CORNER_HELPER_RADIUS = 16;
|
export const CORNER_HELPER_RADIUS = 16;
|
||||||
|
|
||||||
|
export const HISTORY_RADIUS = 20;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue