Normalize dimensions (#527)
* normalize dimensions of non-linear elements * fix element type check regression
This commit is contained in:
parent
d65e90209c
commit
afb1d6725f
@ -8,7 +8,11 @@ export {
|
|||||||
|
|
||||||
export { handlerRectangles } from "./handlerRectangles";
|
export { handlerRectangles } from "./handlerRectangles";
|
||||||
export { hitTest } from "./collision";
|
export { hitTest } from "./collision";
|
||||||
export { resizeTest, getCursorForResizingElement } from "./resizeTest";
|
export {
|
||||||
|
resizeTest,
|
||||||
|
getCursorForResizingElement,
|
||||||
|
normalizeResizeHandle,
|
||||||
|
} from "./resizeTest";
|
||||||
export { isTextElement } from "./typeChecks";
|
export { isTextElement } from "./typeChecks";
|
||||||
export { textWysiwyg } from "./textWysiwyg";
|
export { textWysiwyg } from "./textWysiwyg";
|
||||||
export { redrawTextBoundingBox } from "./textElement";
|
export { redrawTextBoundingBox } from "./textElement";
|
||||||
@ -16,4 +20,5 @@ export {
|
|||||||
getPerfectElementSize,
|
getPerfectElementSize,
|
||||||
isInvisiblySmallElement,
|
isInvisiblySmallElement,
|
||||||
resizePerfectLineForNWHandler,
|
resizePerfectLineForNWHandler,
|
||||||
|
normalizeDimensions,
|
||||||
} from "./sizeHelpers";
|
} from "./sizeHelpers";
|
||||||
|
@ -91,3 +91,61 @@ export function getCursorForResizingElement(resizingElement: {
|
|||||||
|
|
||||||
return cursor ? `${cursor}-resize` : "";
|
return cursor ? `${cursor}-resize` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeResizeHandle(
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
resizeHandle: HandlerRectanglesRet,
|
||||||
|
): HandlerRectanglesRet {
|
||||||
|
if (
|
||||||
|
(element.width >= 0 && element.height >= 0) ||
|
||||||
|
element.type === "line" ||
|
||||||
|
element.type === "arrow"
|
||||||
|
) {
|
||||||
|
return resizeHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.width < 0 && element.height < 0) {
|
||||||
|
switch (resizeHandle) {
|
||||||
|
case "nw":
|
||||||
|
return "se";
|
||||||
|
case "ne":
|
||||||
|
return "sw";
|
||||||
|
case "se":
|
||||||
|
return "nw";
|
||||||
|
case "sw":
|
||||||
|
return "ne";
|
||||||
|
}
|
||||||
|
} else if (element.width < 0) {
|
||||||
|
switch (resizeHandle) {
|
||||||
|
case "nw":
|
||||||
|
return "ne";
|
||||||
|
case "ne":
|
||||||
|
return "nw";
|
||||||
|
case "se":
|
||||||
|
return "sw";
|
||||||
|
case "sw":
|
||||||
|
return "se";
|
||||||
|
case "e":
|
||||||
|
return "w";
|
||||||
|
case "w":
|
||||||
|
return "e";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (resizeHandle) {
|
||||||
|
case "nw":
|
||||||
|
return "sw";
|
||||||
|
case "ne":
|
||||||
|
return "se";
|
||||||
|
case "se":
|
||||||
|
return "ne";
|
||||||
|
case "sw":
|
||||||
|
return "nw";
|
||||||
|
case "n":
|
||||||
|
return "s";
|
||||||
|
case "s":
|
||||||
|
return "n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resizeHandle;
|
||||||
|
}
|
||||||
|
@ -57,3 +57,33 @@ export function resizePerfectLineForNWHandler(
|
|||||||
element.y = anchorY - element.height;
|
element.y = anchorY - element.height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} whether element was normalized
|
||||||
|
*/
|
||||||
|
export function normalizeDimensions(
|
||||||
|
element: ExcalidrawElement | null,
|
||||||
|
): element is ExcalidrawElement {
|
||||||
|
if (
|
||||||
|
!element ||
|
||||||
|
(element.width >= 0 && element.height >= 0) ||
|
||||||
|
element.type === "line" ||
|
||||||
|
element.type === "arrow"
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.width < 0) {
|
||||||
|
element.width = Math.abs(element.width);
|
||||||
|
element.x -= element.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.height < 0) {
|
||||||
|
element.height = Math.abs(element.height);
|
||||||
|
element.y -= element.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.shape = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
newTextElement,
|
newTextElement,
|
||||||
duplicateElement,
|
duplicateElement,
|
||||||
resizeTest,
|
resizeTest,
|
||||||
|
normalizeResizeHandle,
|
||||||
isInvisiblySmallElement,
|
isInvisiblySmallElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
textWysiwyg,
|
textWysiwyg,
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
getPerfectElementSize,
|
getPerfectElementSize,
|
||||||
resizePerfectLineForNWHandler,
|
resizePerfectLineForNWHandler,
|
||||||
|
normalizeDimensions,
|
||||||
} from "./element";
|
} from "./element";
|
||||||
import {
|
import {
|
||||||
clearSelection,
|
clearSelection,
|
||||||
@ -38,7 +40,7 @@ import { renderScene } from "./renderer";
|
|||||||
import { AppState } from "./types";
|
import { AppState } from "./types";
|
||||||
import { ExcalidrawElement } from "./element/types";
|
import { ExcalidrawElement } from "./element/types";
|
||||||
|
|
||||||
import { isInputLike, debounce, capitalizeString } from "./utils";
|
import { isInputLike, debounce, capitalizeString, distance } from "./utils";
|
||||||
import { KEYS, isArrowKey } from "./keys";
|
import { KEYS, isArrowKey } from "./keys";
|
||||||
|
|
||||||
import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
|
import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
|
||||||
@ -776,6 +778,9 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
||||||
|
|
||||||
|
const originX = x;
|
||||||
|
const originY = y;
|
||||||
|
|
||||||
let element = newElement(
|
let element = newElement(
|
||||||
this.state.elementType,
|
this.state.elementType,
|
||||||
x,
|
x,
|
||||||
@ -945,16 +950,15 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const deltaX = x - lastX;
|
const deltaX = x - lastX;
|
||||||
const deltaY = y - lastY;
|
const deltaY = y - lastY;
|
||||||
const element = selectedElements[0];
|
const element = selectedElements[0];
|
||||||
|
const isLinear =
|
||||||
|
element.type === "line" || element.type === "arrow";
|
||||||
switch (resizeHandle) {
|
switch (resizeHandle) {
|
||||||
case "nw":
|
case "nw":
|
||||||
element.width -= deltaX;
|
element.width -= deltaX;
|
||||||
element.x += deltaX;
|
element.x += deltaX;
|
||||||
|
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
if (
|
if (isLinear) {
|
||||||
element.type === "arrow" ||
|
|
||||||
element.type === "line"
|
|
||||||
) {
|
|
||||||
resizePerfectLineForNWHandler(element, x, y);
|
resizePerfectLineForNWHandler(element, x, y);
|
||||||
} else {
|
} else {
|
||||||
element.y += element.height - element.width;
|
element.y += element.height - element.width;
|
||||||
@ -986,10 +990,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
break;
|
break;
|
||||||
case "se":
|
case "se":
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
if (
|
if (isLinear) {
|
||||||
element.type === "arrow" ||
|
|
||||||
element.type === "line"
|
|
||||||
) {
|
|
||||||
const { width, height } = getPerfectElementSize(
|
const { width, height } = getPerfectElementSize(
|
||||||
element.type,
|
element.type,
|
||||||
x - element.x,
|
x - element.x,
|
||||||
@ -1022,6 +1023,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resizeHandle) {
|
||||||
|
resizeHandle = normalizeResizeHandle(element, resizeHandle);
|
||||||
|
}
|
||||||
|
normalizeDimensions(element);
|
||||||
|
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
{ element, resizeHandle },
|
{ element, resizeHandle },
|
||||||
);
|
);
|
||||||
@ -1064,33 +1070,35 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const draggingElement = this.state.draggingElement;
|
const draggingElement = this.state.draggingElement;
|
||||||
if (!draggingElement) return;
|
if (!draggingElement) return;
|
||||||
|
|
||||||
let width =
|
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
||||||
e.clientX -
|
|
||||||
CANVAS_WINDOW_OFFSET_LEFT -
|
let width = distance(originX, x);
|
||||||
draggingElement.x -
|
let height = distance(originY, y);
|
||||||
this.state.scrollX;
|
|
||||||
let height =
|
const isLinear =
|
||||||
e.clientY -
|
this.state.elementType === "line" ||
|
||||||
CANVAS_WINDOW_OFFSET_TOP -
|
this.state.elementType === "arrow";
|
||||||
draggingElement.y -
|
|
||||||
this.state.scrollY;
|
if (isLinear && x < originX) width = -width;
|
||||||
|
if (isLinear && y < originY) height = -height;
|
||||||
|
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
let {
|
({ width, height } = getPerfectElementSize(
|
||||||
width: newWidth,
|
|
||||||
height: newHeight,
|
|
||||||
} = getPerfectElementSize(
|
|
||||||
this.state.elementType,
|
this.state.elementType,
|
||||||
width,
|
width,
|
||||||
height,
|
!isLinear && y < originY ? -height : height,
|
||||||
);
|
));
|
||||||
draggingElement.width = newWidth;
|
|
||||||
draggingElement.height = newHeight;
|
if (!isLinear && height < 0) height = -height;
|
||||||
} else {
|
|
||||||
draggingElement.width = width;
|
|
||||||
draggingElement.height = height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isLinear) {
|
||||||
|
draggingElement.x = x < originX ? originX - width : originX;
|
||||||
|
draggingElement.y = y < originY ? originY - height : originY;
|
||||||
|
}
|
||||||
|
|
||||||
|
draggingElement.width = width;
|
||||||
|
draggingElement.height = height;
|
||||||
draggingElement.shape = null;
|
draggingElement.shape = null;
|
||||||
|
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
@ -1136,6 +1144,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (normalizeDimensions(draggingElement)) {
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
||||||
elements = elements.filter(el => el.id !== resizingElement.id);
|
elements = elements.filter(el => el.id !== resizingElement.id);
|
||||||
}
|
}
|
||||||
@ -1349,10 +1361,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const minX = Math.min(...parsedElements.map(element => element.x));
|
const minX = Math.min(...parsedElements.map(element => element.x));
|
||||||
const minY = Math.min(...parsedElements.map(element => element.y));
|
const minY = Math.min(...parsedElements.map(element => element.y));
|
||||||
|
|
||||||
const distance = (x: number, y: number) => {
|
|
||||||
return Math.abs(x > y ? x - y : y - x);
|
|
||||||
};
|
|
||||||
|
|
||||||
parsedElements.forEach(parsedElement => {
|
parsedElements.forEach(parsedElement => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(parsedElement);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(parsedElement);
|
||||||
subCanvasX1 = Math.min(subCanvasX1, x1);
|
subCanvasX1 = Math.min(subCanvasX1, x1);
|
||||||
|
@ -2,6 +2,7 @@ import rough from "roughjs/bin/rough";
|
|||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { getElementAbsoluteCoords } from "../element/bounds";
|
import { getElementAbsoluteCoords } from "../element/bounds";
|
||||||
import { renderScene } from "../renderer/renderScene";
|
import { renderScene } from "../renderer/renderScene";
|
||||||
|
import { distance } from "../utils";
|
||||||
|
|
||||||
export function getExportCanvasPreview(
|
export function getExportCanvasPreview(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@ -40,10 +41,6 @@ export function getExportCanvasPreview(
|
|||||||
subCanvasY2 = Math.max(subCanvasY2, y2);
|
subCanvasY2 = Math.max(subCanvasY2, y2);
|
||||||
});
|
});
|
||||||
|
|
||||||
function distance(x: number, y: number) {
|
|
||||||
return Math.abs(x > y ? x - y : y - x);
|
|
||||||
}
|
|
||||||
|
|
||||||
const width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
|
const width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
|
||||||
const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
|
const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
|
||||||
const tempCanvas: any = createCanvas(width, height);
|
const tempCanvas: any = createCanvas(width, height);
|
||||||
|
@ -86,3 +86,7 @@ export function removeSelection() {
|
|||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function distance(x: number, y: number) {
|
||||||
|
return Math.abs(x > y ? x - y : y - x);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user