mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Refactor ExcalidrawElement (#874)
* Get rid of isSelected, canvas, canvasZoom, canvasOffsetX and canvasOffsetY on ExcalidrawElement. * Fix most unit tests. Fix cmd a. Fix alt drag * Focus on paste * shift select should include previously selected items * Fix last test * Move this.shape out of ExcalidrawElement and into a WeakMap
This commit is contained in:
parent
8ecb4201db
commit
ccbbdb75a6
39 changed files with 416 additions and 306 deletions
|
@ -2,6 +2,7 @@ import { ExcalidrawElement } from "./types";
|
|||
import { rotate } from "../math";
|
||||
import { Drawable } from "roughjs/bin/core";
|
||||
import { Point } from "roughjs/bin/geometry";
|
||||
import { getShapeForElement } from "../renderer/renderElement";
|
||||
|
||||
// If the element is created from right to left, the width is going to be negative
|
||||
// This set of functions retrieves the absolute position of the 4 points.
|
||||
|
@ -33,7 +34,7 @@ export function getDiamondPoints(element: ExcalidrawElement) {
|
|||
}
|
||||
|
||||
export function getLinearElementAbsoluteBounds(element: ExcalidrawElement) {
|
||||
if (element.points.length < 2 || !element.shape) {
|
||||
if (element.points.length < 2 || !getShapeForElement(element)) {
|
||||
const { minX, minY, maxX, maxY } = element.points.reduce(
|
||||
(limits, [x, y]) => {
|
||||
limits.minY = Math.min(limits.minY, y);
|
||||
|
@ -54,7 +55,7 @@ export function getLinearElementAbsoluteBounds(element: ExcalidrawElement) {
|
|||
];
|
||||
}
|
||||
|
||||
const shape = element.shape as Drawable[];
|
||||
const shape = getShapeForElement(element) as Drawable[];
|
||||
|
||||
// first element is always the curve
|
||||
const ops = shape[0].sets[0].ops;
|
||||
|
@ -118,8 +119,7 @@ export function getLinearElementAbsoluteBounds(element: ExcalidrawElement) {
|
|||
];
|
||||
}
|
||||
|
||||
export function getArrowPoints(element: ExcalidrawElement) {
|
||||
const shape = element.shape as Drawable[];
|
||||
export function getArrowPoints(element: ExcalidrawElement, shape: Drawable[]) {
|
||||
const ops = shape[0].sets[0].ops;
|
||||
|
||||
const data = ops[ops.length - 1].data;
|
||||
|
|
|
@ -9,13 +9,22 @@ import {
|
|||
} from "./bounds";
|
||||
import { Point } from "roughjs/bin/geometry";
|
||||
import { Drawable, OpSet } from "roughjs/bin/core";
|
||||
import { AppState } from "../types";
|
||||
import { getShapeForElement } from "../renderer/renderElement";
|
||||
|
||||
function isElementDraggableFromInside(element: ExcalidrawElement): boolean {
|
||||
return element.backgroundColor !== "transparent" || element.isSelected;
|
||||
function isElementDraggableFromInside(
|
||||
element: ExcalidrawElement,
|
||||
appState: AppState,
|
||||
): boolean {
|
||||
return (
|
||||
element.backgroundColor !== "transparent" ||
|
||||
appState.selectedElementIds[element.id]
|
||||
);
|
||||
}
|
||||
|
||||
export function hitTest(
|
||||
element: ExcalidrawElement,
|
||||
appState: AppState,
|
||||
x: number,
|
||||
y: number,
|
||||
zoom: number,
|
||||
|
@ -58,7 +67,7 @@ export function hitTest(
|
|||
ty /= t;
|
||||
});
|
||||
|
||||
if (isElementDraggableFromInside(element)) {
|
||||
if (isElementDraggableFromInside(element, appState)) {
|
||||
return (
|
||||
a * tx - (px - lineThreshold) >= 0 && b * ty - (py - lineThreshold) >= 0
|
||||
);
|
||||
|
@ -67,7 +76,7 @@ export function hitTest(
|
|||
} else if (element.type === "rectangle") {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
|
||||
if (isElementDraggableFromInside(element)) {
|
||||
if (isElementDraggableFromInside(element, appState)) {
|
||||
return (
|
||||
x > x1 - lineThreshold &&
|
||||
x < x2 + lineThreshold &&
|
||||
|
@ -99,7 +108,7 @@ export function hitTest(
|
|||
leftY,
|
||||
] = getDiamondPoints(element);
|
||||
|
||||
if (isElementDraggableFromInside(element)) {
|
||||
if (isElementDraggableFromInside(element, appState)) {
|
||||
// TODO: remove this when we normalize coordinates globally
|
||||
if (topY > bottomY) {
|
||||
[bottomY, topY] = [topY, bottomY];
|
||||
|
@ -150,10 +159,10 @@ export function hitTest(
|
|||
lineThreshold
|
||||
);
|
||||
} else if (element.type === "arrow" || element.type === "line") {
|
||||
if (!element.shape) {
|
||||
if (!getShapeForElement(element)) {
|
||||
return false;
|
||||
}
|
||||
const shape = element.shape as Drawable[];
|
||||
const shape = getShapeForElement(element) as Drawable[];
|
||||
|
||||
const [x1, y1, x2, y2] = getLinearElementAbsoluteBounds(element);
|
||||
if (x < x1 || y < y1 - 10 || x > x2 || y > y2 + 10) {
|
||||
|
|
|
@ -54,8 +54,6 @@ it("clones arrow element", () => {
|
|||
...element,
|
||||
id: copy.id,
|
||||
seed: copy.seed,
|
||||
shape: undefined,
|
||||
canvas: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { randomSeed } from "roughjs/bin/math";
|
||||
import nanoid from "nanoid";
|
||||
import { Drawable } from "roughjs/bin/core";
|
||||
import { Point } from "roughjs/bin/geometry";
|
||||
|
||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||
|
@ -32,14 +31,8 @@ export function newElement(
|
|||
strokeWidth,
|
||||
roughness,
|
||||
opacity,
|
||||
isSelected: false,
|
||||
seed: randomSeed(),
|
||||
shape: null as Drawable | Drawable[] | null,
|
||||
points: [] as Point[],
|
||||
canvas: null as HTMLCanvasElement | null,
|
||||
canvasZoom: 1, // The zoom level used to render the cached canvas
|
||||
canvasOffsetX: 0,
|
||||
canvasOffsetY: 0,
|
||||
};
|
||||
return element;
|
||||
}
|
||||
|
@ -52,7 +45,6 @@ export function newTextElement(
|
|||
const metrics = measureText(text, font);
|
||||
const textElement: ExcalidrawTextElement = {
|
||||
...element,
|
||||
shape: null,
|
||||
type: "text",
|
||||
text: text,
|
||||
font: font,
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import { ExcalidrawElement, PointerType } from "./types";
|
||||
|
||||
import { handlerRectangles } from "./handlerRectangles";
|
||||
import { AppState } from "../types";
|
||||
|
||||
type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
|
||||
|
||||
export function resizeTest(
|
||||
element: ExcalidrawElement,
|
||||
appState: AppState,
|
||||
x: number,
|
||||
y: number,
|
||||
zoom: number,
|
||||
pointerType: PointerType,
|
||||
): HandlerRectanglesRet | false {
|
||||
if (!element.isSelected || element.type === "text") {
|
||||
if (!appState.selectedElementIds[element.id] || element.type === "text") {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -40,6 +42,7 @@ export function resizeTest(
|
|||
|
||||
export function getElementWithResizeHandler(
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
{ x, y }: { x: number; y: number },
|
||||
zoom: number,
|
||||
pointerType: PointerType,
|
||||
|
@ -48,7 +51,7 @@ export function getElementWithResizeHandler(
|
|||
if (result) {
|
||||
return result;
|
||||
}
|
||||
const resizeHandle = resizeTest(element, x, y, zoom, pointerType);
|
||||
const resizeHandle = resizeTest(element, appState, x, y, zoom, pointerType);
|
||||
return resizeHandle ? { element, resizeHandle } : null;
|
||||
}, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@ export const showSelectedShapeActions = (
|
|||
) =>
|
||||
Boolean(
|
||||
appState.editingElement ||
|
||||
getSelectedElements(elements).length ||
|
||||
getSelectedElements(elements, appState).length ||
|
||||
appState.elementType !== "selection",
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ExcalidrawElement } from "./types";
|
||||
import { invalidateShapeForElement } from "../renderer/renderElement";
|
||||
|
||||
export function isInvisiblySmallElement(element: ExcalidrawElement): boolean {
|
||||
if (element.type === "arrow" || element.type === "line") {
|
||||
|
@ -86,7 +87,7 @@ export function normalizeDimensions(
|
|||
element.y -= element.height;
|
||||
}
|
||||
|
||||
element.shape = null;
|
||||
invalidateShapeForElement(element);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { newElement } from "./newElement";
|
||||
|
||||
/**
|
||||
* ExcalidrawElement should be JSON serializable and (eventually) contain
|
||||
* no computed data. The list of all ExcalidrawElements should be shareable
|
||||
* between peers and contain no state local to the peer.
|
||||
*/
|
||||
export type ExcalidrawElement = ReturnType<typeof newElement>;
|
||||
export type ExcalidrawTextElement = ExcalidrawElement & {
|
||||
type: "text";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue