editor: shift or ctrl key to append selection
This commit is contained in:
parent
848a13e875
commit
9646d716c6
2 changed files with 118 additions and 89 deletions
|
|
@ -30,21 +30,19 @@ export type VisualEditorState = ConcreteSyntax & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RountangleSelectable = {
|
export type RountangleSelectable = {
|
||||||
// kind: "rountangle";
|
part: RectSide;
|
||||||
parts: RectSide[];
|
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
type ArrowSelectable = {
|
type ArrowSelectable = {
|
||||||
// kind: "arrow";
|
part: ArrowPart;
|
||||||
parts: ArrowPart[];
|
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
type TextSelectable = {
|
type TextSelectable = {
|
||||||
parts: ["text"];
|
part: "text";
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
type HistorySelectable = {
|
type HistorySelectable = {
|
||||||
parts: ["history"];
|
part: "history";
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
type Selectable = RountangleSelectable | ArrowSelectable | TextSelectable | HistorySelectable;
|
type Selectable = RountangleSelectable | ArrowSelectable | TextSelectable | HistorySelectable;
|
||||||
|
|
@ -113,12 +111,10 @@ export const VisualEditor = memo(function VisualEditor({state, setState, conns,
|
||||||
for (const textUid of texts) {
|
for (const textUid of texts) {
|
||||||
textsToHighlight[textUid] = true;
|
textsToHighlight[textUid] = true;
|
||||||
}
|
}
|
||||||
for (const part of selected.parts) {
|
const arrows = conns.side2ArrowMap.get(selected.uid + '/' + selected.part) || [];
|
||||||
const arrows = conns.side2ArrowMap.get(selected.uid + '/' + part) || [];
|
if (arrows) {
|
||||||
if (arrows) {
|
for (const [arrowPart, arrowUid] of arrows) {
|
||||||
for (const [arrowPart, arrowUid] of arrows) {
|
arrowsToHighlight[arrowUid] = true;
|
||||||
arrowsToHighlight[arrowUid] = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const arrow2 = conns.text2ArrowMap.get(selected.uid);
|
const arrow2 = conns.text2ArrowMap.get(selected.uid);
|
||||||
|
|
@ -229,7 +225,7 @@ export const VisualEditor = memo(function VisualEditor({state, setState, conns,
|
||||||
return <ArrowSVG
|
return <ArrowSVG
|
||||||
key={arrow.uid}
|
key={arrow.uid}
|
||||||
arrow={arrow}
|
arrow={arrow}
|
||||||
selected={selection.find(a => a.uid === arrow.uid)?.parts as ArrowPart[] || []}
|
selected={selection.filter(a => a.uid === arrow.uid).map(({part})=> part as ArrowPart)}
|
||||||
error={errors
|
error={errors
|
||||||
.filter(({shapeUid}) => shapeUid === arrow.uid)
|
.filter(({shapeUid}) => shapeUid === arrow.uid)
|
||||||
.map(({message}) => message).join(', ')}
|
.map(({message}) => message).join(', ')}
|
||||||
|
|
@ -252,7 +248,7 @@ const Rountangles = memo(function Rountangles({rountangles, selection, sidesToHi
|
||||||
return <RountangleSVG
|
return <RountangleSVG
|
||||||
key={rountangle.uid}
|
key={rountangle.uid}
|
||||||
rountangle={rountangle}
|
rountangle={rountangle}
|
||||||
selected={selection.find(r => r.uid === rountangle.uid)?.parts as RectSide[] || []}
|
selected={selection.filter(r => r.uid === rountangle.uid).map(({part}) => part as RectSide)}
|
||||||
highlight={[...(sidesToHighlight[rountangle.uid] || []), ...(rountanglesToHighlight[rountangle.uid]?["left","right","top","bottom"]:[]) as RectSide[]]}
|
highlight={[...(sidesToHighlight[rountangle.uid] || []), ...(rountanglesToHighlight[rountangle.uid]?["left","right","top","bottom"]:[]) as RectSide[]]}
|
||||||
error={errors
|
error={errors
|
||||||
.filter(({shapeUid}) => shapeUid === rountangle.uid)
|
.filter(({shapeUid}) => shapeUid === rountangle.uid)
|
||||||
|
|
@ -273,7 +269,7 @@ const Diamonds = memo(function Diamonds({diamonds, selection, sidesToHighlight,
|
||||||
<DiamondSVG
|
<DiamondSVG
|
||||||
key={diamond.uid}
|
key={diamond.uid}
|
||||||
diamond={diamond}
|
diamond={diamond}
|
||||||
selected={selection.find(r => r.uid === diamond.uid)?.parts as RectSide[] || []}
|
selected={selection.filter(r => r.uid === diamond.uid).map(({part})=>part as RectSide)}
|
||||||
highlight={[...(sidesToHighlight[diamond.uid] || []), ...(rountanglesToHighlight[diamond.uid]?["left","right","top","bottom"]:[]) as RectSide[]]}
|
highlight={[...(sidesToHighlight[diamond.uid] || []), ...(rountanglesToHighlight[diamond.uid]?["left","right","top","bottom"]:[]) as RectSide[]]}
|
||||||
error={errors
|
error={errors
|
||||||
.filter(({shapeUid}) => shapeUid === diamond.uid)
|
.filter(({shapeUid}) => shapeUid === diamond.uid)
|
||||||
|
|
@ -294,7 +290,7 @@ const Texts = memo(function Texts({texts, selection, textsToHighlight, errors, o
|
||||||
key={txt.uid}
|
key={txt.uid}
|
||||||
error={errors.find(({shapeUid}) => txt.uid === shapeUid)}
|
error={errors.find(({shapeUid}) => txt.uid === shapeUid)}
|
||||||
text={txt}
|
text={txt}
|
||||||
selected={Boolean(selection.find(s => s.uid === txt.uid)?.parts?.length)}
|
selected={Boolean(selection.filter(s => s.uid === txt.uid).length)}
|
||||||
highlight={textsToHighlight.hasOwnProperty(txt.uid)}
|
highlight={textsToHighlight.hasOwnProperty(txt.uid)}
|
||||||
onEdit={onEditText}
|
onEdit={onEditText}
|
||||||
setModal={setModal}
|
setModal={setModal}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import { Selection, VisualEditorState } from "../VisualEditor";
|
||||||
|
|
||||||
export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoom: number, refSVG: {current: SVGSVGElement|null}, state: VisualEditorState, setState: Dispatch<(v: VisualEditorState) => VisualEditorState>, deleteSelection: () => void) {
|
export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoom: number, refSVG: {current: SVGSVGElement|null}, state: VisualEditorState, setState: Dispatch<(v: VisualEditorState) => VisualEditorState>, deleteSelection: () => void) {
|
||||||
const [dragging, setDragging] = useState(false);
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const [shiftOrCtrlPressed, setShiftOrCtrlPressed] = useState(false);
|
||||||
|
|
||||||
|
console.log(shiftOrCtrlPressed);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
@ -29,7 +32,7 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
const currentPointer = getCurrentPointer(e);
|
const currentPointer = getCurrentPointer(e);
|
||||||
if (e.button === 2) {
|
if (e.button === 2) {
|
||||||
makeCheckPoint();
|
makeCheckPoint();
|
||||||
// ignore selection, middle mouse button always inserts
|
// ignore selection, right mouse button always inserts
|
||||||
setState(state => {
|
setState(state => {
|
||||||
const newID = state.nextID.toString();
|
const newID = state.nextID.toString();
|
||||||
if (insertMode === "and" || insertMode === "or") {
|
if (insertMode === "and" || insertMode === "or") {
|
||||||
|
|
@ -43,7 +46,7 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
kind: insertMode,
|
kind: insertMode,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
selection: [{uid: newID, parts: ["bottom", "right"]}],
|
selection: [{uid: newID, part: "bottom"}, {uid: newID, part: "right"}],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (insertMode === "pseudo") {
|
else if (insertMode === "pseudo") {
|
||||||
|
|
@ -55,7 +58,7 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
size: MIN_ROUNTANGLE_SIZE,
|
size: MIN_ROUNTANGLE_SIZE,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
selection: [{uid: newID, parts: ["bottom", "right"]}],
|
selection: [{uid: newID, part: "bottom"}, {uid: newID, part: "right"}],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (insertMode === "shallow" || insertMode === "deep") {
|
else if (insertMode === "shallow" || insertMode === "deep") {
|
||||||
|
|
@ -67,7 +70,7 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
selection: [{uid: newID, parts: ["history"]}],
|
selection: [{uid: newID, part: "history"}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (insertMode === "transition") {
|
else if (insertMode === "transition") {
|
||||||
|
|
@ -79,7 +82,7 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
end: currentPointer,
|
end: currentPointer,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
selection: [{uid: newID, parts: ["end"]}],
|
selection: [{uid: newID, part: "end"}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (insertMode === "text") {
|
else if (insertMode === "text") {
|
||||||
|
|
@ -91,7 +94,7 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
selection: [{uid: newID, parts: ["text"]}],
|
selection: [{uid: newID, part: "text"}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error("unreachable, mode=" + insertMode); // shut up typescript
|
throw new Error("unreachable, mode=" + insertMode); // shut up typescript
|
||||||
|
|
@ -101,38 +104,40 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
if (!shiftOrCtrlPressed) {
|
||||||
const uid = e.target?.dataset.uid;
|
// 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.
|
||||||
const parts: string[] = e.target?.dataset.parts?.split(' ').filter((p:string) => p!=="") || [];
|
const uid = e.target?.dataset.uid;
|
||||||
if (uid && parts.length > 0) {
|
const parts: string[] = e.target?.dataset.parts?.split(' ').filter((p:string) => p!=="") || [];
|
||||||
makeCheckPoint();
|
if (uid && parts.length > 0) {
|
||||||
|
makeCheckPoint();
|
||||||
|
|
||||||
// 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
|
||||||
let allPartsInSelection = true;
|
let allPartsInSelection = true;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
if (!(selection.find(s => s.uid === uid)?.parts || [] as string[]).includes(part)) {
|
// is there anything in our existing selection that is not under the cursor?
|
||||||
allPartsInSelection = false;
|
if (!(selection.some(s => (s.uid === uid) && (s.part === part)))) {
|
||||||
break;
|
allPartsInSelection = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (!allPartsInSelection) {
|
||||||
|
if (e.target.classList.contains("helper")) {
|
||||||
|
setSelection(() => parts.map(part => ({uid, part})) as Selection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setDragging(false);
|
||||||
|
setSelectingState({
|
||||||
|
topLeft: currentPointer,
|
||||||
|
size: {x: 0, y: 0},
|
||||||
|
});
|
||||||
|
setSelection(() => []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// start dragging
|
||||||
|
setDragging(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!allPartsInSelection) {
|
|
||||||
if (e.target.classList.contains("helper")) {
|
|
||||||
setSelection(() => [{uid, parts}] as Selection);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setDragging(false);
|
|
||||||
setSelectingState({
|
|
||||||
topLeft: currentPointer,
|
|
||||||
size: {x: 0, y: 0},
|
|
||||||
});
|
|
||||||
setSelection(() => []);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start dragging
|
|
||||||
setDragging(true);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,40 +147,45 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
size: {x: 0, y: 0},
|
size: {x: 0, y: 0},
|
||||||
});
|
});
|
||||||
setSelection(() => []);
|
if (!shiftOrCtrlPressed) {
|
||||||
}, [getCurrentPointer, makeCheckPoint, insertMode, selection]);
|
setSelection(() => []);
|
||||||
|
}
|
||||||
|
}, [getCurrentPointer, makeCheckPoint, insertMode, selection, shiftOrCtrlPressed]);
|
||||||
|
|
||||||
const onMouseMove = useCallback((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);
|
const currentPointer = getCurrentPointer(e);
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
// const pointerDelta = subtractV2D(currentPointer, dragging.lastMousePos);
|
// const pointerDelta = subtractV2D(currentPointer, dragging.lastMousePos);
|
||||||
const pointerDelta = {x: e.movementX/zoom, y: e.movementY/zoom};
|
const pointerDelta = {x: e.movementX/zoom, y: e.movementY/zoom};
|
||||||
|
const getParts = (uid: string) => {
|
||||||
|
return state.selection.filter(s => s.uid === uid).map(s => s.part);
|
||||||
|
}
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
...state,
|
...state,
|
||||||
rountangles: state.rountangles.map(r => {
|
rountangles: state.rountangles.map(r => {
|
||||||
const parts = state.selection.find(selected => selected.uid === r.uid)?.parts || [];
|
const selectedParts = getParts(r.uid);
|
||||||
if (parts.length === 0) {
|
if (selectedParts.length === 0) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...r,
|
...r,
|
||||||
...transformRect(r, parts, pointerDelta),
|
...transformRect(r, selectedParts, pointerDelta),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.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 => {
|
diamonds: state.diamonds.map(d => {
|
||||||
const parts = state.selection.find(selected => selected.uid === d.uid)?.parts || [];
|
const selectedParts = getParts(d.uid);
|
||||||
if (parts.length === 0) {
|
if (selectedParts.length === 0) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...d,
|
...d,
|
||||||
...transformRect(d, parts, pointerDelta),
|
...transformRect(d, selectedParts, pointerDelta),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
history: state.history.map(h => {
|
history: state.history.map(h => {
|
||||||
const parts = state.selection.find(selected => selected.uid === h.uid)?.parts || [];
|
const selectedParts = getParts(h.uid);
|
||||||
if (parts.length === 0) {
|
if (selectedParts.length === 0) {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -184,18 +194,18 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
arrows: state.arrows.map(a => {
|
arrows: state.arrows.map(a => {
|
||||||
const parts = state.selection.find(selected => selected.uid === a.uid)?.parts || [];
|
const selectedParts = getParts(a.uid);
|
||||||
if (parts.length === 0) {
|
if (selectedParts.length === 0) {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...a,
|
...a,
|
||||||
...transformLine(a, parts, pointerDelta),
|
...transformLine(a, selectedParts, pointerDelta),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
texts: state.texts.map(t => {
|
texts: state.texts.map(t => {
|
||||||
const parts = state.selection.find(selected => selected.uid === t.uid)?.parts || [];
|
const selectedParts = getParts(t.uid);
|
||||||
if (parts.length === 0) {
|
if (selectedParts.length === 0) {
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -239,12 +249,12 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
if (selectingState.size.x === 0 && selectingState.size.y === 0) {
|
if (selectingState.size.x === 0 && selectingState.size.y === 0) {
|
||||||
const uid = e.target?.dataset.uid;
|
const uid = e.target?.dataset.uid;
|
||||||
if (uid) {
|
if (uid) {
|
||||||
const parts = e.target?.dataset.parts.split(' ').filter((p: string) => p!=="");
|
const parts = e.target?.dataset.parts.split(' ').filter((p: string) => p!=="") || [];
|
||||||
if (uid) {
|
if (uid) {
|
||||||
setSelection(() => [{
|
setSelection(oldSelection => [
|
||||||
uid,
|
...oldSelection,
|
||||||
parts,
|
...parts.map((part: string) => ({uid, part})),
|
||||||
}]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -261,26 +271,45 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
return isEntirelyWithin(scaledBBox, normalizedSS);
|
return isEntirelyWithin(scaledBBox, normalizedSS);
|
||||||
}).filter(el => !el.classList.contains("corner"));
|
}).filter(el => !el.classList.contains("corner"));
|
||||||
|
|
||||||
const uidToParts = new Map();
|
// @ts-ignore
|
||||||
for (const shape of shapesInSelection) {
|
setSelection(oldSelection => {
|
||||||
const uid = shape.dataset.uid;
|
const newSelection = [];
|
||||||
if (uid) {
|
const common = [];
|
||||||
const parts: Set<string> = uidToParts.get(uid) || new Set();
|
for (const shape of shapesInSelection) {
|
||||||
for (const part of shape.dataset.parts?.split(' ') || []) {
|
const uid = shape.dataset.uid;
|
||||||
parts.add(part);
|
if (uid) {
|
||||||
|
const parts = shape.dataset.parts?.split(' ') || [];
|
||||||
|
for (const part of parts) {
|
||||||
|
if (oldSelection.some(({uid: oldUid, part: oldPart}) =>
|
||||||
|
uid === oldUid && part === oldPart)) {
|
||||||
|
common.push({uid, part});
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newSelection.push({uid, part});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uidToParts.set(uid, parts);
|
|
||||||
}
|
}
|
||||||
}
|
return [...oldSelection, ...newSelection];
|
||||||
setSelection(() => [...uidToParts.entries()].map(([uid,parts]) => ({
|
})
|
||||||
uid,
|
|
||||||
parts: [...parts],
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSelectingState(null); // no longer making a selection
|
setSelectingState(null); // no longer making a selection
|
||||||
}, [dragging, selectingState, refSVG.current]);
|
}, [dragging, selectingState, refSVG.current]);
|
||||||
|
|
||||||
|
const trackShiftKey = useCallback((e: KeyboardEvent) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||||
|
|
||||||
|
if (e.shiftKey || e.ctrlKey) {
|
||||||
|
setShiftOrCtrlPressed(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setShiftOrCtrlPressed(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
// don't capture keyboard events when focused on an input element:
|
// don't capture keyboard events when focused on an input element:
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -318,11 +347,11 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
...state,
|
...state,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
selection: [
|
selection: [
|
||||||
...state.rountangles.map(r => ({uid: r.uid, parts: ["left", "top", "right", "bottom"]})),
|
...state.rountangles.map(r => ["left", "top", "right", "bottom"].map(part => ({uid: r.uid, part}))),
|
||||||
...state.diamonds.map(d => ({uid: d.uid, parts: ["left", "top", "right", "bottom"]})),
|
...state.diamonds.map(d => ["left", "top", "right", "bottom"].map(part => ({uid: d.uid, part}))),
|
||||||
...state.arrows.map(a => ({uid: a.uid, parts: ["start", "end"]})),
|
...state.arrows.map(a => ["start", "end"].map(part => ({uid: a.uid, part}))),
|
||||||
...state.texts.map(t => ({uid: t.uid, parts: ["text"]})),
|
...state.texts.map(t => ({uid: t.uid, part: "text"})),
|
||||||
...state.history.map(h => ({uid: h.uid, parts: ["history"]})),
|
...state.history.map(h => ({uid: h.uid, part: "history"})),
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
@ -334,10 +363,14 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
|
||||||
window.addEventListener("mouseup", onMouseUp);
|
window.addEventListener("mouseup", onMouseUp);
|
||||||
window.addEventListener("mousemove", onMouseMove);
|
window.addEventListener("mousemove", onMouseMove);
|
||||||
window.addEventListener("keydown", onKeyDown);
|
window.addEventListener("keydown", onKeyDown);
|
||||||
|
window.addEventListener("keydown", trackShiftKey);
|
||||||
|
window.addEventListener("keyup", trackShiftKey);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("mousemove", onMouseMove);
|
window.removeEventListener("mousemove", onMouseMove);
|
||||||
window.removeEventListener("mouseup", onMouseUp);
|
window.removeEventListener("mouseup", onMouseUp);
|
||||||
window.removeEventListener("keydown", onKeyDown);
|
window.removeEventListener("keydown", onKeyDown);
|
||||||
|
window.removeEventListener("keydown", trackShiftKey);
|
||||||
|
window.removeEventListener("keyup", trackShiftKey);
|
||||||
};
|
};
|
||||||
}, [selectingState, dragging]);
|
}, [selectingState, dragging]);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue