137 lines
No EOL
5.4 KiB
TypeScript
137 lines
No EOL
5.4 KiB
TypeScript
import { Arrow, Diamond, Rountangle, Text, History } from "@/statecharts/concrete_syntax";
|
|
import { ClipboardEvent, Dispatch, SetStateAction, useCallback, useEffect } from "react";
|
|
import { Selection, VisualEditorState } from "../VisualEditor";
|
|
import { addV2D } from "@/util/geometry";
|
|
|
|
// const offset = {x: 40, y: 40};
|
|
const offset = {x: 0, y: 0};
|
|
|
|
export function useCopyPaste(makeCheckPoint: () => void, state: VisualEditorState, setState: Dispatch<(v:VisualEditorState) => VisualEditorState>, selection: Selection) {
|
|
const onPaste = useCallback((e: ClipboardEvent) => {
|
|
const data = e.clipboardData?.getData("text/plain");
|
|
if (data) {
|
|
try {
|
|
const parsed = JSON.parse(data);
|
|
setState(state => {
|
|
try {
|
|
let nextID = state.nextID;
|
|
const copiedRountangles: Rountangle[] = parsed.rountangles.map((r: Rountangle) => ({
|
|
...r,
|
|
uid: (nextID++).toString(),
|
|
topLeft: addV2D(r.topLeft, offset),
|
|
} as Rountangle));
|
|
const copiedDiamonds: Diamond[] = parsed.diamonds.map((r: Diamond) => ({
|
|
...r,
|
|
uid: (nextID++).toString(),
|
|
topLeft: addV2D(r.topLeft, offset),
|
|
} as Diamond));
|
|
const copiedArrows: Arrow[] = parsed.arrows.map((a: Arrow) => ({
|
|
...a,
|
|
uid: (nextID++).toString(),
|
|
start: addV2D(a.start, offset),
|
|
end: addV2D(a.end, offset),
|
|
} as Arrow));
|
|
const copiedTexts: Text[] = parsed.texts.map((t: Text) => ({
|
|
...t,
|
|
uid: (nextID++).toString(),
|
|
topLeft: addV2D(t.topLeft, offset),
|
|
} as Text));
|
|
const copiedHistories: History[] = parsed.history.map((h: History) => ({
|
|
...h,
|
|
uid: (nextID++).toString(),
|
|
topLeft: addV2D(h.topLeft, offset),
|
|
}))
|
|
// @ts-ignore
|
|
const newSelection: Selection = [
|
|
...copiedRountangles.map(r => ({uid: r.uid, parts: ["left", "top", "right", "bottom"]})),
|
|
...copiedDiamonds.map(d => ({uid: d.uid, parts: ["left", "top", "right", "bottom"]})),
|
|
...copiedArrows.map(a => ({uid: a.uid, parts: ["start", "end"]})),
|
|
...copiedTexts.map(t => ({uid: t.uid, parts: ["text"]})),
|
|
...copiedHistories.map(h => ({uid: h.uid, parts: ["history"]})),
|
|
];
|
|
makeCheckPoint();
|
|
return {
|
|
...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,
|
|
};
|
|
}
|
|
catch (e) {
|
|
console.warn("error pasting data. most likely you're tying to paste nonsense. ", e);
|
|
return state;
|
|
}
|
|
});
|
|
}
|
|
catch (e) {
|
|
console.warn("error pasting data. most likely you're tying to paste nonsense. ", e);
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
}, [setState]);
|
|
|
|
const copyInternal = useCallback((state: VisualEditorState, selection: Selection, e: ClipboardEvent) => {
|
|
const uidsToCopy = new Set(selection.map(shape => shape.uid));
|
|
const rountanglesToCopy = state.rountangles.filter(r => uidsToCopy.has(r.uid));
|
|
const diamondsToCopy = state.diamonds.filter(d => uidsToCopy.has(d.uid));
|
|
const historiesToCopy = state.history.filter(h => uidsToCopy.has(h.uid));
|
|
const arrowsToCopy = state.arrows.filter(a => uidsToCopy.has(a.uid));
|
|
const textsToCopy = state.texts.filter(t => uidsToCopy.has(t.uid));
|
|
e.clipboardData?.setData("text/plain", JSON.stringify({
|
|
rountangles: rountanglesToCopy,
|
|
diamonds: diamondsToCopy,
|
|
history: historiesToCopy,
|
|
arrows: arrowsToCopy,
|
|
texts: textsToCopy,
|
|
}));
|
|
}, []);
|
|
|
|
const onCopy = useCallback((e: ClipboardEvent) => {
|
|
if (selection.length > 0) {
|
|
e.preventDefault();
|
|
copyInternal(state, selection, e);
|
|
}
|
|
}, [state, selection]);
|
|
|
|
const onCut = useCallback((e: ClipboardEvent) => {
|
|
if (selection.length > 0) {
|
|
copyInternal(state, selection, e);
|
|
deleteSelection();
|
|
e.preventDefault();
|
|
}
|
|
}, [state, selection]);
|
|
|
|
const deleteSelection = useCallback(() => {
|
|
setState(state => ({
|
|
...state,
|
|
rountangles: state.rountangles.filter(r => !state.selection.some(rs => rs.uid === r.uid)),
|
|
diamonds: state.diamonds.filter(d => !state.selection.some(ds => ds.uid === d.uid)),
|
|
history: state.history.filter(h => !state.selection.some(hs => hs.uid === h.uid)),
|
|
arrows: state.arrows.filter(a => !state.selection.some(as => as.uid === a.uid)),
|
|
texts: state.texts.filter(t => !state.selection.some(ts => ts.uid === t.uid)),
|
|
selection: [],
|
|
}));
|
|
}, [setState]);
|
|
|
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
// @ts-ignore
|
|
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
|
if (e.key === "Delete") {
|
|
// delete selection
|
|
makeCheckPoint();
|
|
deleteSelection();
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
window.addEventListener("keydown", onKeyDown);
|
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
})
|
|
|
|
return {onCopy, onPaste, onCut, deleteSelection};
|
|
} |