grid support (1st iteration) (#1788)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
b6bf011d0d
commit
baa8fb6c14
@ -53,6 +53,7 @@ export const getDefaultAppState = (): AppState => {
|
|||||||
shouldCacheIgnoreZoom: false,
|
shouldCacheIgnoreZoom: false,
|
||||||
showShortcutsDialog: false,
|
showShortcutsDialog: false,
|
||||||
zenModeEnabled: false,
|
zenModeEnabled: false,
|
||||||
|
gridSize: null,
|
||||||
editingGroupId: null,
|
editingGroupId: null,
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
};
|
};
|
||||||
@ -81,5 +82,6 @@ export const clearAppStateForLocalStorage = (appState: AppState) => {
|
|||||||
export const cleanAppStateForExport = (appState: AppState) => {
|
export const cleanAppStateForExport = (appState: AppState) => {
|
||||||
return {
|
return {
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
|
gridSize: appState.gridSize,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -27,6 +27,9 @@ import {
|
|||||||
getResizeArrowDirection,
|
getResizeArrowDirection,
|
||||||
getResizeHandlerFromCoords,
|
getResizeHandlerFromCoords,
|
||||||
isNonDeletedElement,
|
isNonDeletedElement,
|
||||||
|
dragSelectedElements,
|
||||||
|
getDragOffsetXY,
|
||||||
|
dragNewElement,
|
||||||
} from "../element";
|
} from "../element";
|
||||||
import {
|
import {
|
||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
@ -54,7 +57,7 @@ import { renderScene } from "../renderer";
|
|||||||
import { AppState, GestureEvent, Gesture } from "../types";
|
import { AppState, GestureEvent, Gesture } from "../types";
|
||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||||
|
|
||||||
import { distance2d, isPathALoop } from "../math";
|
import { distance2d, isPathALoop, getGridPoint } from "../math";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isWritableElement,
|
isWritableElement,
|
||||||
@ -72,6 +75,7 @@ import {
|
|||||||
isArrowKey,
|
isArrowKey,
|
||||||
getResizeCenterPointKey,
|
getResizeCenterPointKey,
|
||||||
getResizeWithSidesSameLengthKey,
|
getResizeWithSidesSameLengthKey,
|
||||||
|
getRotateWithDiscreteAngleKey,
|
||||||
} from "../keys";
|
} from "../keys";
|
||||||
|
|
||||||
import { findShapeByKey, shapesShortcutKeys } from "../shapes";
|
import { findShapeByKey, shapesShortcutKeys } from "../shapes";
|
||||||
@ -109,6 +113,7 @@ import {
|
|||||||
EVENT,
|
EVENT,
|
||||||
ENV,
|
ENV,
|
||||||
CANVAS_ONLY_ACTIONS,
|
CANVAS_ONLY_ACTIONS,
|
||||||
|
GRID_SIZE,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import {
|
import {
|
||||||
INITAL_SCENE_UPDATE_TIMEOUT,
|
INITAL_SCENE_UPDATE_TIMEOUT,
|
||||||
@ -834,6 +839,12 @@ class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleGridMode = () => {
|
||||||
|
this.setState({
|
||||||
|
gridSize: this.state.gridSize ? null : GRID_SIZE,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private destroySocketClient = () => {
|
private destroySocketClient = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isCollaborating: false,
|
isCollaborating: false,
|
||||||
@ -1173,6 +1184,10 @@ class App extends React.Component<any, AppState> {
|
|||||||
this.toggleZenMode();
|
this.toggleZenMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event[KEYS.CTRL_OR_CMD] && event.keyCode === KEYS.GRID_KEY_CODE) {
|
||||||
|
this.toggleGridMode();
|
||||||
|
}
|
||||||
|
|
||||||
if (event.code === "KeyC" && event.altKey && event.shiftKey) {
|
if (event.code === "KeyC" && event.altKey && event.shiftKey) {
|
||||||
this.copyToClipboardAsPng();
|
this.copyToClipboardAsPng();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -1186,9 +1201,12 @@ class App extends React.Component<any, AppState> {
|
|||||||
const shape = findShapeByKey(event.key);
|
const shape = findShapeByKey(event.key);
|
||||||
|
|
||||||
if (isArrowKey(event.key)) {
|
if (isArrowKey(event.key)) {
|
||||||
const step = event.shiftKey
|
const step =
|
||||||
|
(this.state.gridSize &&
|
||||||
|
(event.shiftKey ? ELEMENT_TRANSLATE_AMOUNT : this.state.gridSize)) ||
|
||||||
|
(event.shiftKey
|
||||||
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
||||||
: ELEMENT_TRANSLATE_AMOUNT;
|
: ELEMENT_TRANSLATE_AMOUNT);
|
||||||
globalSceneState.replaceAllElements(
|
globalSceneState.replaceAllElements(
|
||||||
globalSceneState.getElementsIncludingDeleted().map((el) => {
|
globalSceneState.getElementsIncludingDeleted().map((el) => {
|
||||||
if (this.state.selectedElementIds[el.id]) {
|
if (this.state.selectedElementIds[el.id]) {
|
||||||
@ -2013,6 +2031,11 @@ class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
const originX = x;
|
const originX = x;
|
||||||
const originY = y;
|
const originY = y;
|
||||||
|
const [originGridX, originGridY] = getGridPoint(
|
||||||
|
originX,
|
||||||
|
originY,
|
||||||
|
this.state.gridSize,
|
||||||
|
);
|
||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
let resizeHandle: ResizeTestType = false;
|
let resizeHandle: ResizeTestType = false;
|
||||||
@ -2023,6 +2046,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
let resizeArrowDirection: "origin" | "end" = "origin";
|
let resizeArrowDirection: "origin" | "end" = "origin";
|
||||||
let isResizingElements = false;
|
let isResizingElements = false;
|
||||||
let draggingOccurred = false;
|
let draggingOccurred = false;
|
||||||
|
let dragOffsetXY: [number, number] = [0, 0];
|
||||||
let hitElement: ExcalidrawElement | null = null;
|
let hitElement: ExcalidrawElement | null = null;
|
||||||
let hitElementWasAddedToSelection = false;
|
let hitElementWasAddedToSelection = false;
|
||||||
|
|
||||||
@ -2106,6 +2130,20 @@ class App extends React.Component<any, AppState> {
|
|||||||
hitElement ||
|
hitElement ||
|
||||||
getElementAtPosition(elements, this.state, x, y, this.state.zoom);
|
getElementAtPosition(elements, this.state, x, y, this.state.zoom);
|
||||||
|
|
||||||
|
if (hitElement && isNonDeletedElement(hitElement)) {
|
||||||
|
if (this.state.selectedElementIds[hitElement.id]) {
|
||||||
|
dragOffsetXY = getDragOffsetXY(selectedElements, x, y);
|
||||||
|
} else if (event.shiftKey) {
|
||||||
|
dragOffsetXY = getDragOffsetXY(
|
||||||
|
[...selectedElements, hitElement],
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dragOffsetXY = getDragOffsetXY([hitElement], x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// clear selection if shift is not clicked
|
// clear selection if shift is not clicked
|
||||||
if (
|
if (
|
||||||
!(hitElement && this.state.selectedElementIds[hitElement.id]) &&
|
!(hitElement && this.state.selectedElementIds[hitElement.id]) &&
|
||||||
@ -2260,10 +2298,15 @@ class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
||||||
} else {
|
} else {
|
||||||
|
const [gridX, gridY] = getGridPoint(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
this.state.elementType === "draw" ? null : this.state.gridSize,
|
||||||
|
);
|
||||||
const element = newLinearElement({
|
const element = newLinearElement({
|
||||||
type: this.state.elementType,
|
type: this.state.elementType,
|
||||||
x: x,
|
x: gridX,
|
||||||
y: y,
|
y: gridY,
|
||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
@ -2291,10 +2334,11 @@ class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const [gridX, gridY] = getGridPoint(x, y, this.state.gridSize);
|
||||||
const element = newElement({
|
const element = newElement({
|
||||||
type: this.state.elementType,
|
type: this.state.elementType,
|
||||||
x: x,
|
x: gridX,
|
||||||
y: y,
|
y: gridY,
|
||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
@ -2356,6 +2400,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
this.canvas,
|
this.canvas,
|
||||||
window.devicePixelRatio,
|
window.devicePixelRatio,
|
||||||
);
|
);
|
||||||
|
const [gridX, gridY] = getGridPoint(x, y, this.state.gridSize);
|
||||||
|
|
||||||
// for arrows/lines, don't start dragging until a given threshold
|
// for arrows/lines, don't start dragging until a given threshold
|
||||||
// to ensure we don't create a 2-point arrow by mistake when
|
// to ensure we don't create a 2-point arrow by mistake when
|
||||||
@ -2380,15 +2425,22 @@ class App extends React.Component<any, AppState> {
|
|||||||
isResizing: resizeHandle && resizeHandle !== "rotation",
|
isResizing: resizeHandle && resizeHandle !== "rotation",
|
||||||
isRotating: resizeHandle === "rotation",
|
isRotating: resizeHandle === "rotation",
|
||||||
});
|
});
|
||||||
|
const [resizeX, resizeY] = getGridPoint(
|
||||||
|
x - resizeOffsetXY[0],
|
||||||
|
y - resizeOffsetXY[1],
|
||||||
|
this.state.gridSize,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
resizeElements(
|
resizeElements(
|
||||||
resizeHandle,
|
resizeHandle,
|
||||||
setResizeHandle,
|
setResizeHandle,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
resizeArrowDirection,
|
resizeArrowDirection,
|
||||||
event,
|
getRotateWithDiscreteAngleKey(event),
|
||||||
x - resizeOffsetXY[0],
|
getResizeWithSidesSameLengthKey(event),
|
||||||
y - resizeOffsetXY[1],
|
getResizeCenterPointKey(event),
|
||||||
|
resizeX,
|
||||||
|
resizeY,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@ -2421,21 +2473,12 @@ class App extends React.Component<any, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
if (selectedElements.length > 0) {
|
if (selectedElements.length > 0) {
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const [dragX, dragY] = getGridPoint(
|
||||||
event,
|
x - dragOffsetXY[0],
|
||||||
this.state,
|
y - dragOffsetXY[1],
|
||||||
this.canvas,
|
this.state.gridSize,
|
||||||
window.devicePixelRatio,
|
|
||||||
);
|
);
|
||||||
|
dragSelectedElements(selectedElements, dragX, dragY);
|
||||||
selectedElements.forEach((element) => {
|
|
||||||
mutateElement(element, {
|
|
||||||
x: element.x + x - lastX,
|
|
||||||
y: element.y + y - lastY,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
lastX = x;
|
|
||||||
lastY = y;
|
|
||||||
|
|
||||||
// We duplicate the selected element if alt is pressed on pointer move
|
// We duplicate the selected element if alt is pressed on pointer move
|
||||||
if (event.altKey && !selectedElementWasDuplicated) {
|
if (event.altKey && !selectedElementWasDuplicated) {
|
||||||
@ -2460,9 +2503,14 @@ class App extends React.Component<any, AppState> {
|
|||||||
groupIdMap,
|
groupIdMap,
|
||||||
element,
|
element,
|
||||||
);
|
);
|
||||||
|
const [originDragX, originDragY] = getGridPoint(
|
||||||
|
originX - dragOffsetXY[0],
|
||||||
|
originY - dragOffsetXY[1],
|
||||||
|
this.state.gridSize,
|
||||||
|
);
|
||||||
mutateElement(duplicatedElement, {
|
mutateElement(duplicatedElement, {
|
||||||
x: duplicatedElement.x + (originX - lastX),
|
x: duplicatedElement.x + (originDragX - dragX),
|
||||||
y: duplicatedElement.y + (originY - lastY),
|
y: duplicatedElement.y + (originDragY - dragY),
|
||||||
});
|
});
|
||||||
nextElements.push(duplicatedElement);
|
nextElements.push(duplicatedElement);
|
||||||
elementsToAppend.push(element);
|
elementsToAppend.push(element);
|
||||||
@ -2486,16 +2534,20 @@ class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let width = distance(originX, x);
|
|
||||||
let height = distance(originY, y);
|
|
||||||
|
|
||||||
if (isLinearElement(draggingElement)) {
|
if (isLinearElement(draggingElement)) {
|
||||||
draggingOccurred = true;
|
draggingOccurred = true;
|
||||||
const points = draggingElement.points;
|
const points = draggingElement.points;
|
||||||
let dx = x - draggingElement.x;
|
let dx: number;
|
||||||
let dy = y - draggingElement.y;
|
let dy: number;
|
||||||
|
if (draggingElement.type === "draw") {
|
||||||
|
dx = x - draggingElement.x;
|
||||||
|
dy = y - draggingElement.y;
|
||||||
|
} else {
|
||||||
|
dx = gridX - draggingElement.x;
|
||||||
|
dy = gridY - draggingElement.y;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.shiftKey && points.length === 2) {
|
if (getRotateWithDiscreteAngleKey(event) && points.length === 2) {
|
||||||
({ width: dx, height: dy } = getPerfectElementSize(
|
({ width: dx, height: dy } = getPerfectElementSize(
|
||||||
this.state.elementType,
|
this.state.elementType,
|
||||||
dx,
|
dx,
|
||||||
@ -2516,35 +2568,32 @@ class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (draggingElement.type === "selection") {
|
||||||
if (getResizeWithSidesSameLengthKey(event)) {
|
dragNewElement(
|
||||||
({ width, height } = getPerfectElementSize(
|
draggingElement,
|
||||||
this.state.elementType,
|
this.state.elementType,
|
||||||
width,
|
originX,
|
||||||
y < originY ? -height : height,
|
originY,
|
||||||
));
|
x,
|
||||||
|
y,
|
||||||
if (height < 0) {
|
distance(originX, x),
|
||||||
height = -height;
|
distance(originY, y),
|
||||||
}
|
getResizeWithSidesSameLengthKey(event),
|
||||||
}
|
getResizeCenterPointKey(event),
|
||||||
|
);
|
||||||
let newX = x < originX ? originX - width : originX;
|
} else {
|
||||||
let newY = y < originY ? originY - height : originY;
|
dragNewElement(
|
||||||
|
draggingElement,
|
||||||
if (getResizeCenterPointKey(event)) {
|
this.state.elementType,
|
||||||
width += width;
|
originGridX,
|
||||||
height += height;
|
originGridY,
|
||||||
newX = originX - width / 2;
|
gridX,
|
||||||
newY = originY - height / 2;
|
gridY,
|
||||||
}
|
distance(originGridX, gridX),
|
||||||
|
distance(originGridY, gridY),
|
||||||
mutateElement(draggingElement, {
|
getResizeWithSidesSameLengthKey(event),
|
||||||
x: newX,
|
getResizeCenterPointKey(event),
|
||||||
y: newY,
|
);
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
@ -2857,6 +2906,10 @@ class App extends React.Component<any, AppState> {
|
|||||||
...this.actionManager.getContextMenuItems((action) =>
|
...this.actionManager.getContextMenuItems((action) =>
|
||||||
CANVAS_ONLY_ACTIONS.includes(action.name),
|
CANVAS_ONLY_ACTIONS.includes(action.name),
|
||||||
),
|
),
|
||||||
|
{
|
||||||
|
label: t("labels.toggleGridMode"),
|
||||||
|
action: this.toggleGridMode,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
top: event.clientY,
|
top: event.clientY,
|
||||||
left: event.clientX,
|
left: event.clientX,
|
||||||
|
@ -247,6 +247,10 @@ export const ShortcutsDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
label={t("buttons.toggleZenMode")}
|
label={t("buttons.toggleZenMode")}
|
||||||
shortcuts={[getShortcutKey("Alt+Z")]}
|
shortcuts={[getShortcutKey("Alt+Z")]}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("buttons.toggleGridMode")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
|
||||||
|
/>
|
||||||
</ShortcutIsland>
|
</ShortcutIsland>
|
||||||
</Column>
|
</Column>
|
||||||
<Column>
|
<Column>
|
||||||
|
@ -68,3 +68,5 @@ export const FONT_FAMILY = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const CANVAS_ONLY_ACTIONS = ["selectAll"];
|
export const CANVAS_ONLY_ACTIONS = ["selectAll"];
|
||||||
|
|
||||||
|
export const GRID_SIZE = 20; // TODO make it configurable?
|
||||||
|
72
src/element/dragElements.ts
Normal file
72
src/element/dragElements.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { NonDeletedExcalidrawElement } from "./types";
|
||||||
|
import { getCommonBounds } from "./bounds";
|
||||||
|
import { mutateElement } from "./mutateElement";
|
||||||
|
import { SHAPES } from "../shapes";
|
||||||
|
import { getPerfectElementSize } from "./sizeHelpers";
|
||||||
|
|
||||||
|
export const dragSelectedElements = (
|
||||||
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
|
pointerX: number,
|
||||||
|
pointerY: number,
|
||||||
|
) => {
|
||||||
|
const [x1, y1] = getCommonBounds(selectedElements);
|
||||||
|
selectedElements.forEach((element) => {
|
||||||
|
mutateElement(element, {
|
||||||
|
x: pointerX + element.x - x1,
|
||||||
|
y: pointerY + element.y - y1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDragOffsetXY = (
|
||||||
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
): [number, number] => {
|
||||||
|
const [x1, y1] = getCommonBounds(selectedElements);
|
||||||
|
return [x - x1, y - y1];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dragNewElement = (
|
||||||
|
draggingElement: NonDeletedExcalidrawElement,
|
||||||
|
elementType: typeof SHAPES[number]["value"],
|
||||||
|
originX: number,
|
||||||
|
originY: number,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
isResizeWithSidesSameLength: boolean,
|
||||||
|
isResizeCenterPoint: boolean,
|
||||||
|
) => {
|
||||||
|
if (isResizeWithSidesSameLength) {
|
||||||
|
({ width, height } = getPerfectElementSize(
|
||||||
|
elementType,
|
||||||
|
width,
|
||||||
|
y < originY ? -height : height,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (height < 0) {
|
||||||
|
height = -height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newX = x < originX ? originX - width : originX;
|
||||||
|
let newY = y < originY ? originY - height : originY;
|
||||||
|
|
||||||
|
if (isResizeCenterPoint) {
|
||||||
|
width += width;
|
||||||
|
height += height;
|
||||||
|
newX = originX - width / 2;
|
||||||
|
newY = originY - height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width !== 0 && height !== 0) {
|
||||||
|
mutateElement(draggingElement, {
|
||||||
|
x: newX,
|
||||||
|
y: newY,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -38,6 +38,11 @@ export {
|
|||||||
getResizeOffsetXY,
|
getResizeOffsetXY,
|
||||||
getResizeArrowDirection,
|
getResizeArrowDirection,
|
||||||
} from "./resizeElements";
|
} from "./resizeElements";
|
||||||
|
export {
|
||||||
|
dragSelectedElements,
|
||||||
|
getDragOffsetXY,
|
||||||
|
dragNewElement,
|
||||||
|
} from "./dragElements";
|
||||||
export { isTextElement, isExcalidrawElement } from "./typeChecks";
|
export { isTextElement, isExcalidrawElement } from "./typeChecks";
|
||||||
export { textWysiwyg } from "./textWysiwyg";
|
export { textWysiwyg } from "./textWysiwyg";
|
||||||
export { redrawTextBoundingBox } from "./textElement";
|
export { redrawTextBoundingBox } from "./textElement";
|
||||||
|
@ -21,10 +21,6 @@ import {
|
|||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
normalizeResizeHandle,
|
normalizeResizeHandle,
|
||||||
} from "./resizeTest";
|
} from "./resizeTest";
|
||||||
import {
|
|
||||||
getResizeCenterPointKey,
|
|
||||||
getResizeWithSidesSameLengthKey,
|
|
||||||
} from "../keys";
|
|
||||||
import { measureText, getFontString } from "../utils";
|
import { measureText, getFontString } from "../utils";
|
||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
@ -34,14 +30,21 @@ export const resizeElements = (
|
|||||||
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
||||||
selectedElements: NonDeletedExcalidrawElement[],
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
resizeArrowDirection: "origin" | "end",
|
resizeArrowDirection: "origin" | "end",
|
||||||
event: PointerEvent, // XXX we want to make it independent?
|
isRotateWithDiscreteAngle: boolean,
|
||||||
|
isResizeWithSidesSameLength: boolean,
|
||||||
|
isResizeCenterPoint: boolean,
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
) => {
|
) => {
|
||||||
if (selectedElements.length === 1) {
|
if (selectedElements.length === 1) {
|
||||||
const [element] = selectedElements;
|
const [element] = selectedElements;
|
||||||
if (resizeHandle === "rotation") {
|
if (resizeHandle === "rotation") {
|
||||||
rotateSingleElement(element, pointerX, pointerY, event.shiftKey);
|
rotateSingleElement(
|
||||||
|
element,
|
||||||
|
pointerX,
|
||||||
|
pointerY,
|
||||||
|
isRotateWithDiscreteAngle,
|
||||||
|
);
|
||||||
} else if (
|
} else if (
|
||||||
isLinearElement(element) &&
|
isLinearElement(element) &&
|
||||||
element.points.length === 2 &&
|
element.points.length === 2 &&
|
||||||
@ -53,7 +56,7 @@ export const resizeElements = (
|
|||||||
resizeSingleTwoPointElement(
|
resizeSingleTwoPointElement(
|
||||||
element,
|
element,
|
||||||
resizeArrowDirection,
|
resizeArrowDirection,
|
||||||
event.shiftKey,
|
isRotateWithDiscreteAngle,
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
);
|
);
|
||||||
@ -67,7 +70,7 @@ export const resizeElements = (
|
|||||||
resizeSingleTextElement(
|
resizeSingleTextElement(
|
||||||
element,
|
element,
|
||||||
resizeHandle,
|
resizeHandle,
|
||||||
getResizeCenterPointKey(event),
|
isResizeCenterPoint,
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
);
|
);
|
||||||
@ -75,8 +78,8 @@ export const resizeElements = (
|
|||||||
resizeSingleElement(
|
resizeSingleElement(
|
||||||
element,
|
element,
|
||||||
resizeHandle,
|
resizeHandle,
|
||||||
getResizeWithSidesSameLengthKey(event),
|
isResizeWithSidesSameLength,
|
||||||
getResizeCenterPointKey(event),
|
isResizeCenterPoint,
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
);
|
);
|
||||||
@ -114,13 +117,13 @@ const rotateSingleElement = (
|
|||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
isAngleLocking: boolean,
|
isRotateWithDiscreteAngle: boolean,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
let angle = (5 * Math.PI) / 2 + Math.atan2(pointerY - cy, pointerX - cx);
|
let angle = (5 * Math.PI) / 2 + Math.atan2(pointerY - cy, pointerX - cx);
|
||||||
if (isAngleLocking) {
|
if (isRotateWithDiscreteAngle) {
|
||||||
angle += SHIFT_LOCKING_ANGLE / 2;
|
angle += SHIFT_LOCKING_ANGLE / 2;
|
||||||
angle -= angle % SHIFT_LOCKING_ANGLE;
|
angle -= angle % SHIFT_LOCKING_ANGLE;
|
||||||
}
|
}
|
||||||
@ -133,14 +136,14 @@ const rotateSingleElement = (
|
|||||||
const resizeSingleTwoPointElement = (
|
const resizeSingleTwoPointElement = (
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
resizeArrowDirection: "origin" | "end",
|
resizeArrowDirection: "origin" | "end",
|
||||||
isAngleLocking: boolean,
|
isRotateWithDiscreteAngle: boolean,
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
) => {
|
) => {
|
||||||
const pointOrigin = element.points[0]; // can assume always [0, 0]?
|
const pointOrigin = element.points[0]; // can assume always [0, 0]?
|
||||||
const pointEnd = element.points[1];
|
const pointEnd = element.points[1];
|
||||||
if (resizeArrowDirection === "end") {
|
if (resizeArrowDirection === "end") {
|
||||||
if (isAngleLocking) {
|
if (isRotateWithDiscreteAngle) {
|
||||||
const { width, height } = getPerfectElementSize(
|
const { width, height } = getPerfectElementSize(
|
||||||
element.type,
|
element.type,
|
||||||
pointerX - element.x,
|
pointerX - element.x,
|
||||||
@ -162,7 +165,7 @@ const resizeSingleTwoPointElement = (
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// resizeArrowDirection === "origin"
|
// resizeArrowDirection === "origin"
|
||||||
if (isAngleLocking) {
|
if (isRotateWithDiscreteAngle) {
|
||||||
const { width, height } = getPerfectElementSize(
|
const { width, height } = getPerfectElementSize(
|
||||||
element.type,
|
element.type,
|
||||||
element.x + pointEnd[0] - pointOrigin[0] - pointerX,
|
element.x + pointEnd[0] - pointOrigin[0] - pointerX,
|
||||||
@ -232,6 +235,16 @@ const measureFontSizeFromWH = (
|
|||||||
if (metrics.width - nextWidth < 1 && metrics.height - nextHeight < 1) {
|
if (metrics.width - nextWidth < 1 && metrics.height - nextHeight < 1) {
|
||||||
return { size: nextFontSize, baseline: metrics.baseline };
|
return { size: nextFontSize, baseline: metrics.baseline };
|
||||||
}
|
}
|
||||||
|
// third measurement
|
||||||
|
scale *= 0.99; // just heuristics
|
||||||
|
nextFontSize = element.fontSize * scale;
|
||||||
|
metrics = measureText(
|
||||||
|
element.text,
|
||||||
|
getFontString({ fontSize: nextFontSize, fontFamily: element.fontFamily }),
|
||||||
|
);
|
||||||
|
if (metrics.width - nextWidth < 1 && metrics.height - nextHeight < 1) {
|
||||||
|
return { size: nextFontSize, baseline: metrics.baseline };
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ export const KEYS = {
|
|||||||
F_KEY_CODE: 70,
|
F_KEY_CODE: 70,
|
||||||
ALT_KEY_CODE: 18,
|
ALT_KEY_CODE: 18,
|
||||||
Z_KEY_CODE: 90,
|
Z_KEY_CODE: 90,
|
||||||
|
GRID_KEY_CODE: 222,
|
||||||
G_KEY_CODE: 71,
|
G_KEY_CODE: 71,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -32,3 +33,6 @@ export const getResizeCenterPointKey = (event: MouseEvent | KeyboardEvent) =>
|
|||||||
|
|
||||||
export const getResizeWithSidesSameLengthKey = (event: MouseEvent) =>
|
export const getResizeWithSidesSameLengthKey = (event: MouseEvent) =>
|
||||||
event.shiftKey;
|
event.shiftKey;
|
||||||
|
|
||||||
|
export const getRotateWithDiscreteAngleKey = (event: MouseEvent) =>
|
||||||
|
event.shiftKey;
|
||||||
|
@ -63,7 +63,8 @@
|
|||||||
"madeWithExcalidraw": "Made with Excalidraw",
|
"madeWithExcalidraw": "Made with Excalidraw",
|
||||||
"group": "Group selection",
|
"group": "Group selection",
|
||||||
"ungroup": "Ungroup selection",
|
"ungroup": "Ungroup selection",
|
||||||
"collaborators": "Collaborators"
|
"collaborators": "Collaborators",
|
||||||
|
"toggleGridMode": "Toggle grid mode"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Reset the canvas",
|
"clearReset": "Reset the canvas",
|
||||||
@ -91,7 +92,8 @@
|
|||||||
"createNewRoom": "Create new room",
|
"createNewRoom": "Create new room",
|
||||||
"toggleFullScreen": "Toggle full screen",
|
"toggleFullScreen": "Toggle full screen",
|
||||||
"toggleZenMode": "Toggle zen mode",
|
"toggleZenMode": "Toggle zen mode",
|
||||||
"exitZenMode": "Exit zen mode"
|
"exitZenMode": "Exit zen mode",
|
||||||
|
"toggleGridMode": "Toggle grid mode"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "This will clear the whole canvas. Are you sure?",
|
"clearReset": "This will clear the whole canvas. Are you sure?",
|
||||||
|
14
src/math.ts
14
src/math.ts
@ -340,3 +340,17 @@ const doIntersect = (p1: Point, q1: Point, p2: Point, q2: Point) => {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGridPoint = (
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
gridSize: number | null,
|
||||||
|
): [number, number] => {
|
||||||
|
if (gridSize) {
|
||||||
|
return [
|
||||||
|
Math.round(x / gridSize) * gridSize,
|
||||||
|
Math.round(y / gridSize) * gridSize,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [x, y];
|
||||||
|
};
|
||||||
|
@ -74,6 +74,29 @@ const strokeCircle = (
|
|||||||
context.stroke();
|
context.stroke();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderGrid = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
gridSize: number,
|
||||||
|
offsetX: number,
|
||||||
|
offsetY: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
) => {
|
||||||
|
const origStrokeStyle = context.strokeStyle;
|
||||||
|
context.strokeStyle = "rgba(0,0,0,0.1)";
|
||||||
|
context.beginPath();
|
||||||
|
for (let x = offsetX; x < offsetX + width + gridSize * 2; x += gridSize) {
|
||||||
|
context.moveTo(x, offsetY - gridSize);
|
||||||
|
context.lineTo(x, offsetY + height + gridSize * 2);
|
||||||
|
}
|
||||||
|
for (let y = offsetY; y < offsetY + height + gridSize * 2; y += gridSize) {
|
||||||
|
context.moveTo(offsetX - gridSize, y);
|
||||||
|
context.lineTo(offsetX + width + gridSize * 2, y);
|
||||||
|
}
|
||||||
|
context.stroke();
|
||||||
|
context.strokeStyle = origStrokeStyle;
|
||||||
|
};
|
||||||
|
|
||||||
const renderLinearPointHandles = (
|
const renderLinearPointHandles = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@ -167,6 +190,22 @@ export const renderScene = (
|
|||||||
context.translate(zoomTranslationX, zoomTranslationY);
|
context.translate(zoomTranslationX, zoomTranslationY);
|
||||||
context.scale(sceneState.zoom, sceneState.zoom);
|
context.scale(sceneState.zoom, sceneState.zoom);
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
if (appState.gridSize) {
|
||||||
|
renderGrid(
|
||||||
|
context,
|
||||||
|
appState.gridSize,
|
||||||
|
-Math.ceil(zoomTranslationX / sceneState.zoom / appState.gridSize) *
|
||||||
|
appState.gridSize +
|
||||||
|
(sceneState.scrollX % appState.gridSize),
|
||||||
|
-Math.ceil(zoomTranslationY / sceneState.zoom / appState.gridSize) *
|
||||||
|
appState.gridSize +
|
||||||
|
(sceneState.scrollY % appState.gridSize),
|
||||||
|
normalizedCanvasWidth / sceneState.zoom,
|
||||||
|
normalizedCanvasHeight / sceneState.zoom,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Paint visible elements
|
// Paint visible elements
|
||||||
const visibleElements = elements.filter((element) =>
|
const visibleElements = elements.filter((element) =>
|
||||||
isVisibleElement(
|
isVisibleElement(
|
||||||
|
@ -24,6 +24,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -421,6 +422,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -627,6 +629,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -750,6 +753,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1009,6 +1013,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1170,6 +1175,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1369,6 +1375,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1574,6 +1581,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -1880,6 +1888,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -2272,6 +2281,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4057,6 +4067,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4180,6 +4191,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4303,6 +4315,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4426,6 +4439,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4571,6 +4585,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4716,6 +4731,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -4861,6 +4877,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5006,6 +5023,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5129,6 +5147,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5252,6 +5271,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5397,6 +5417,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5520,6 +5541,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -5665,6 +5687,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -6302,6 +6325,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -6508,6 +6532,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -6572,6 +6597,7 @@ Object {
|
|||||||
"elementType": "rectangle",
|
"elementType": "rectangle",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -6634,6 +6660,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -7453,6 +7480,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -7849,6 +7877,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -8162,6 +8191,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -8396,6 +8426,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -8555,6 +8586,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -9323,6 +9355,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -9992,6 +10025,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -10566,6 +10600,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11049,6 +11084,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11488,6 +11524,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -11842,6 +11879,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -12115,6 +12153,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -12311,6 +12350,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -13130,6 +13170,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -13848,6 +13889,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -14469,6 +14511,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -14997,6 +15040,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -15267,6 +15311,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -15329,6 +15374,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -15452,6 +15498,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -15514,6 +15561,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -16165,6 +16213,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -16229,6 +16278,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -16654,6 +16704,7 @@ Object {
|
|||||||
"elementType": "text",
|
"elementType": "text",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
@ -16727,6 +16778,7 @@ Object {
|
|||||||
"elementType": "selection",
|
"elementType": "selection",
|
||||||
"errorMessage": null,
|
"errorMessage": null,
|
||||||
"exportBackground": true,
|
"exportBackground": true,
|
||||||
|
"gridSize": null,
|
||||||
"isCollaborating": false,
|
"isCollaborating": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
|
@ -859,10 +859,10 @@ describe("regression tests", () => {
|
|||||||
fireEvent.contextMenu(canvas, { button: 2, clientX: 1, clientY: 1 });
|
fireEvent.contextMenu(canvas, { button: 2, clientX: 1, clientY: 1 });
|
||||||
const contextMenu = document.querySelector(".context-menu");
|
const contextMenu = document.querySelector(".context-menu");
|
||||||
const options = contextMenu?.querySelectorAll(".context-menu-option");
|
const options = contextMenu?.querySelectorAll(".context-menu-option");
|
||||||
const expectedOptions = ["Select all"];
|
const expectedOptions = ["Select all", "Toggle grid mode"];
|
||||||
|
|
||||||
expect(contextMenu).not.toBeNull();
|
expect(contextMenu).not.toBeNull();
|
||||||
expect(options?.length).toBe(1);
|
expect(options?.length).toBe(2);
|
||||||
expect(options?.item(0).textContent).toBe(expectedOptions[0]);
|
expect(options?.item(0).textContent).toBe(expectedOptions[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,6 +72,7 @@ export type AppState = {
|
|||||||
shouldCacheIgnoreZoom: boolean;
|
shouldCacheIgnoreZoom: boolean;
|
||||||
showShortcutsDialog: boolean;
|
showShortcutsDialog: boolean;
|
||||||
zenModeEnabled: boolean;
|
zenModeEnabled: boolean;
|
||||||
|
gridSize: number | null;
|
||||||
|
|
||||||
/** top-most selected groups (i.e. does not include nested groups) */
|
/** top-most selected groups (i.e. does not include nested groups) */
|
||||||
selectedGroupIds: { [groupId: string]: boolean };
|
selectedGroupIds: { [groupId: string]: boolean };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user