mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: improved freedraw (#3512)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
198800136e
commit
49c6bdd520
66 changed files with 786 additions and 247 deletions
|
@ -1,4 +1,9 @@
|
|||
import { ExcalidrawElement, ExcalidrawLinearElement, Arrowhead } from "./types";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
Arrowhead,
|
||||
ExcalidrawFreeDrawElement,
|
||||
} from "./types";
|
||||
import { distance2d, rotate } from "../math";
|
||||
import rough from "roughjs/bin/rough";
|
||||
import { Drawable, Op } from "roughjs/bin/core";
|
||||
|
@ -7,7 +12,7 @@ import {
|
|||
getShapeForElement,
|
||||
generateRoughOptions,
|
||||
} from "../renderer/renderElement";
|
||||
import { isLinearElement } from "./typeChecks";
|
||||
import { isFreeDrawElement, isLinearElement } from "./typeChecks";
|
||||
import { rescalePoints } from "../points";
|
||||
|
||||
// x and y position of top left corner, x and y position of bottom right corner
|
||||
|
@ -18,7 +23,9 @@ export type Bounds = readonly [number, number, number, number];
|
|||
export const getElementAbsoluteCoords = (
|
||||
element: ExcalidrawElement,
|
||||
): Bounds => {
|
||||
if (isLinearElement(element)) {
|
||||
if (isFreeDrawElement(element)) {
|
||||
return getFreeDrawElementAbsoluteCoords(element);
|
||||
} else if (isLinearElement(element)) {
|
||||
return getLinearElementAbsoluteCoords(element);
|
||||
}
|
||||
return [
|
||||
|
@ -120,9 +127,42 @@ const getMinMaxXYFromCurvePathOps = (
|
|||
return [minX, minY, maxX, maxY];
|
||||
};
|
||||
|
||||
const getBoundsFromPoints = (
|
||||
points: ExcalidrawFreeDrawElement["points"],
|
||||
): [number, number, number, number] => {
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const [x, y] of points) {
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
|
||||
return [minX, minY, maxX, maxY];
|
||||
};
|
||||
|
||||
const getFreeDrawElementAbsoluteCoords = (
|
||||
element: ExcalidrawFreeDrawElement,
|
||||
): [number, number, number, number] => {
|
||||
const [minX, minY, maxX, maxY] = getBoundsFromPoints(element.points);
|
||||
|
||||
return [
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
};
|
||||
|
||||
const getLinearElementAbsoluteCoords = (
|
||||
element: ExcalidrawLinearElement,
|
||||
): [number, number, number, number] => {
|
||||
let coords: [number, number, number, number];
|
||||
|
||||
if (element.points.length < 2 || !getShapeForElement(element)) {
|
||||
// XXX this is just a poor estimate and not very useful
|
||||
const { minX, minY, maxX, maxY } = element.points.reduce(
|
||||
|
@ -137,7 +177,21 @@ const getLinearElementAbsoluteCoords = (
|
|||
},
|
||||
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
|
||||
);
|
||||
return [
|
||||
coords = [
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
} else {
|
||||
const shape = getShapeForElement(element) as Drawable[];
|
||||
|
||||
// first element is always the curve
|
||||
const ops = getCurvePathOps(shape[0]);
|
||||
|
||||
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
|
||||
|
||||
coords = [
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
|
@ -145,19 +199,7 @@ const getLinearElementAbsoluteCoords = (
|
|||
];
|
||||
}
|
||||
|
||||
const shape = getShapeForElement(element) as Drawable[];
|
||||
|
||||
// first element is always the curve
|
||||
const ops = getCurvePathOps(shape[0]);
|
||||
|
||||
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
|
||||
|
||||
return [
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
return coords;
|
||||
};
|
||||
|
||||
export const getArrowheadPoints = (
|
||||
|
@ -231,7 +273,7 @@ export const getArrowheadPoints = (
|
|||
const ys = y2 - ny * minSize;
|
||||
|
||||
if (arrowhead === "dot") {
|
||||
const r = Math.hypot(ys - y2, xs - x2);
|
||||
const r = Math.hypot(ys - y2, xs - x2) + element.strokeWidth;
|
||||
return [x2, y2, r];
|
||||
}
|
||||
|
||||
|
@ -277,16 +319,31 @@ const getLinearElementRotatedBounds = (
|
|||
return getMinMaxXYFromCurvePathOps(ops, transformXY);
|
||||
};
|
||||
|
||||
// We could cache this stuff
|
||||
export const getElementBounds = (
|
||||
element: ExcalidrawElement,
|
||||
): [number, number, number, number] => {
|
||||
let bounds: [number, number, number, number];
|
||||
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
if (isLinearElement(element)) {
|
||||
return getLinearElementRotatedBounds(element, cx, cy);
|
||||
}
|
||||
if (element.type === "diamond") {
|
||||
if (isFreeDrawElement(element)) {
|
||||
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
||||
element.points.map(([x, y]) =>
|
||||
rotate(x, y, cx - element.x, cy - element.y, element.angle),
|
||||
),
|
||||
);
|
||||
|
||||
return [
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
maxX + element.x,
|
||||
maxY + element.y,
|
||||
];
|
||||
} else if (isLinearElement(element)) {
|
||||
bounds = getLinearElementRotatedBounds(element, cx, cy);
|
||||
} else if (element.type === "diamond") {
|
||||
const [x11, y11] = rotate(cx, y1, cx, cy, element.angle);
|
||||
const [x12, y12] = rotate(cx, y2, cx, cy, element.angle);
|
||||
const [x22, y22] = rotate(x1, cy, cx, cy, element.angle);
|
||||
|
@ -295,26 +352,28 @@ export const getElementBounds = (
|
|||
const minY = Math.min(y11, y12, y22, y21);
|
||||
const maxX = Math.max(x11, x12, x22, x21);
|
||||
const maxY = Math.max(y11, y12, y22, y21);
|
||||
return [minX, minY, maxX, maxY];
|
||||
}
|
||||
if (element.type === "ellipse") {
|
||||
bounds = [minX, minY, maxX, maxY];
|
||||
} else if (element.type === "ellipse") {
|
||||
const w = (x2 - x1) / 2;
|
||||
const h = (y2 - y1) / 2;
|
||||
const cos = Math.cos(element.angle);
|
||||
const sin = Math.sin(element.angle);
|
||||
const ww = Math.hypot(w * cos, h * sin);
|
||||
const hh = Math.hypot(h * cos, w * sin);
|
||||
return [cx - ww, cy - hh, cx + ww, cy + hh];
|
||||
bounds = [cx - ww, cy - hh, cx + ww, cy + hh];
|
||||
} else {
|
||||
const [x11, y11] = rotate(x1, y1, cx, cy, element.angle);
|
||||
const [x12, y12] = rotate(x1, y2, cx, cy, element.angle);
|
||||
const [x22, y22] = rotate(x2, y2, cx, cy, element.angle);
|
||||
const [x21, y21] = rotate(x2, y1, cx, cy, element.angle);
|
||||
const minX = Math.min(x11, x12, x22, x21);
|
||||
const minY = Math.min(y11, y12, y22, y21);
|
||||
const maxX = Math.max(x11, x12, x22, x21);
|
||||
const maxY = Math.max(y11, y12, y22, y21);
|
||||
bounds = [minX, minY, maxX, maxY];
|
||||
}
|
||||
const [x11, y11] = rotate(x1, y1, cx, cy, element.angle);
|
||||
const [x12, y12] = rotate(x1, y2, cx, cy, element.angle);
|
||||
const [x22, y22] = rotate(x2, y2, cx, cy, element.angle);
|
||||
const [x21, y21] = rotate(x2, y1, cx, cy, element.angle);
|
||||
const minX = Math.min(x11, x12, x22, x21);
|
||||
const minY = Math.min(y11, y12, y22, y21);
|
||||
const maxX = Math.max(x11, x12, x22, x21);
|
||||
const maxY = Math.max(y11, y12, y22, y21);
|
||||
return [minX, minY, maxX, maxY];
|
||||
|
||||
return bounds;
|
||||
};
|
||||
|
||||
export const getCommonBounds = (
|
||||
|
@ -345,7 +404,7 @@ export const getResizedElementAbsoluteCoords = (
|
|||
nextWidth: number,
|
||||
nextHeight: number,
|
||||
): [number, number, number, number] => {
|
||||
if (!isLinearElement(element)) {
|
||||
if (!(isLinearElement(element) || isFreeDrawElement(element))) {
|
||||
return [
|
||||
element.x,
|
||||
element.y,
|
||||
|
@ -360,16 +419,29 @@ export const getResizedElementAbsoluteCoords = (
|
|||
rescalePoints(1, nextHeight, element.points),
|
||||
);
|
||||
|
||||
const gen = rough.generator();
|
||||
const curve =
|
||||
element.strokeSharpness === "sharp"
|
||||
? gen.linearPath(
|
||||
points as [number, number][],
|
||||
generateRoughOptions(element),
|
||||
)
|
||||
: gen.curve(points as [number, number][], generateRoughOptions(element));
|
||||
const ops = getCurvePathOps(curve);
|
||||
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
|
||||
let bounds: [number, number, number, number];
|
||||
|
||||
if (isFreeDrawElement(element)) {
|
||||
// Free Draw
|
||||
bounds = getBoundsFromPoints(points);
|
||||
} else {
|
||||
// Line
|
||||
const gen = rough.generator();
|
||||
const curve =
|
||||
element.strokeSharpness === "sharp"
|
||||
? gen.linearPath(
|
||||
points as [number, number][],
|
||||
generateRoughOptions(element),
|
||||
)
|
||||
: gen.curve(
|
||||
points as [number, number][],
|
||||
generateRoughOptions(element),
|
||||
);
|
||||
const ops = getCurvePathOps(curve);
|
||||
bounds = getMinMaxXYFromCurvePathOps(ops);
|
||||
}
|
||||
|
||||
const [minX, minY, maxX, maxY] = bounds;
|
||||
return [
|
||||
minX + element.x,
|
||||
minY + element.y,
|
||||
|
|
|
@ -4,7 +4,13 @@ import * as GADirection from "../gadirections";
|
|||
import * as GALine from "../galines";
|
||||
import * as GATransform from "../gatransforms";
|
||||
|
||||
import { isPathALoop, isPointInPolygon, rotate } from "../math";
|
||||
import {
|
||||
distance2d,
|
||||
rotatePoint,
|
||||
isPathALoop,
|
||||
isPointInPolygon,
|
||||
rotate,
|
||||
} from "../math";
|
||||
import { pointsOnBezierCurves } from "points-on-curve";
|
||||
|
||||
import {
|
||||
|
@ -16,6 +22,7 @@ import {
|
|||
ExcalidrawTextElement,
|
||||
ExcalidrawEllipseElement,
|
||||
NonDeleted,
|
||||
ExcalidrawFreeDrawElement,
|
||||
} from "./types";
|
||||
|
||||
import { getElementAbsoluteCoords, getCurvePathOps, Bounds } from "./bounds";
|
||||
|
@ -30,10 +37,17 @@ const isElementDraggableFromInside = (
|
|||
if (element.type === "arrow") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (element.type === "freedraw") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isDraggableFromInside = element.backgroundColor !== "transparent";
|
||||
if (element.type === "line" || element.type === "draw") {
|
||||
|
||||
if (element.type === "line") {
|
||||
return isDraggableFromInside && isPathALoop(element.points);
|
||||
}
|
||||
|
||||
return isDraggableFromInside;
|
||||
};
|
||||
|
||||
|
@ -81,6 +95,7 @@ const isHittingElementNotConsideringBoundingBox = (
|
|||
: isElementDraggableFromInside(element)
|
||||
? isInsideCheck
|
||||
: isNearCheck;
|
||||
|
||||
return hitTestPointAgainstElement({ element, point, threshold, check });
|
||||
};
|
||||
|
||||
|
@ -151,6 +166,18 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
|||
case "ellipse":
|
||||
const distance = distanceToBindableElement(args.element, args.point);
|
||||
return args.check(distance, args.threshold);
|
||||
case "freedraw": {
|
||||
if (
|
||||
!args.check(
|
||||
distanceToRectangle(args.element, args.point),
|
||||
args.threshold,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hitTestFreeDrawElement(args.element, args.point, args.threshold);
|
||||
}
|
||||
case "arrow":
|
||||
case "line":
|
||||
case "draw":
|
||||
|
@ -195,7 +222,10 @@ const isOutsideCheck = (distance: number, threshold: number): boolean => {
|
|||
};
|
||||
|
||||
const distanceToRectangle = (
|
||||
element: ExcalidrawRectangleElement | ExcalidrawTextElement,
|
||||
element:
|
||||
| ExcalidrawRectangleElement
|
||||
| ExcalidrawTextElement
|
||||
| ExcalidrawFreeDrawElement,
|
||||
point: Point,
|
||||
): number => {
|
||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
||||
|
@ -267,6 +297,71 @@ const ellipseParamsForTest = (
|
|||
return [pointRel, tangent];
|
||||
};
|
||||
|
||||
const hitTestFreeDrawElement = (
|
||||
element: ExcalidrawFreeDrawElement,
|
||||
point: Point,
|
||||
threshold: number,
|
||||
): boolean => {
|
||||
// Check point-distance-to-line-segment for every segment in the
|
||||
// element's points (its input points, not its outline points).
|
||||
// This is... okay? It's plenty fast, but the GA library may
|
||||
// have a faster option.
|
||||
|
||||
let x: number;
|
||||
let y: number;
|
||||
|
||||
if (element.angle === 0) {
|
||||
x = point[0] - element.x;
|
||||
y = point[1] - element.y;
|
||||
} else {
|
||||
// Counter-rotate the point around center before testing
|
||||
const [minX, minY, maxX, maxY] = getElementAbsoluteCoords(element);
|
||||
const rotatedPoint = rotatePoint(
|
||||
point,
|
||||
[minX + (maxX - minX) / 2, minY + (maxY - minY) / 2],
|
||||
-element.angle,
|
||||
);
|
||||
x = rotatedPoint[0] - element.x;
|
||||
y = rotatedPoint[1] - element.y;
|
||||
}
|
||||
|
||||
let [A, B] = element.points;
|
||||
let P: readonly [number, number];
|
||||
|
||||
// For freedraw dots
|
||||
if (element.points.length === 2) {
|
||||
return (
|
||||
distance2d(A[0], A[1], x, y) < threshold ||
|
||||
distance2d(B[0], B[1], x, y) < threshold
|
||||
);
|
||||
}
|
||||
|
||||
// For freedraw lines
|
||||
for (let i = 1; i < element.points.length - 1; i++) {
|
||||
const delta = [B[0] - A[0], B[1] - A[1]];
|
||||
const length = Math.hypot(delta[1], delta[0]);
|
||||
|
||||
const U = [delta[0] / length, delta[1] / length];
|
||||
const C = [x - A[0], y - A[1]];
|
||||
const d = (C[0] * U[0] + C[1] * U[1]) / Math.hypot(U[1], U[0]);
|
||||
P = [A[0] + U[0] * d, A[1] + U[1] * d];
|
||||
|
||||
const da = distance2d(P[0], P[1], A[0], A[1]);
|
||||
const db = distance2d(P[0], P[1], B[0], B[1]);
|
||||
|
||||
P = db < da && da > length ? B : da < db && db > length ? A : P;
|
||||
|
||||
if (Math.hypot(y - P[1], x - P[0]) < threshold) {
|
||||
return true;
|
||||
}
|
||||
|
||||
A = B;
|
||||
B = element.points[i + 1];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const hitTestLinear = (args: HitTestArgs): boolean => {
|
||||
const { element, threshold } = args;
|
||||
if (!getShapeForElement(element)) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
GroupId,
|
||||
VerticalAlign,
|
||||
Arrowhead,
|
||||
ExcalidrawFreeDrawElement,
|
||||
} from "../element/types";
|
||||
import { measureText, getFontString } from "../utils";
|
||||
import { randomInteger, randomId } from "../random";
|
||||
|
@ -212,6 +213,22 @@ export const updateTextElement = (
|
|||
});
|
||||
};
|
||||
|
||||
export const newFreeDrawElement = (
|
||||
opts: {
|
||||
type: "freedraw";
|
||||
points?: ExcalidrawFreeDrawElement["points"];
|
||||
simulatePressure: boolean;
|
||||
} & ElementConstructorOpts,
|
||||
): NonDeleted<ExcalidrawFreeDrawElement> => {
|
||||
return {
|
||||
..._newElementBase<ExcalidrawFreeDrawElement>(opts.type, opts),
|
||||
points: opts.points || [],
|
||||
pressures: [],
|
||||
simulatePressure: opts.simulatePressure,
|
||||
lastCommittedPoint: null,
|
||||
};
|
||||
};
|
||||
|
||||
export const newLinearElement = (
|
||||
opts: {
|
||||
type: ExcalidrawLinearElement["type"];
|
||||
|
|
|
@ -18,7 +18,11 @@ import {
|
|||
getCommonBounds,
|
||||
getResizedElementAbsoluteCoords,
|
||||
} from "./bounds";
|
||||
import { isLinearElement, isTextElement } from "./typeChecks";
|
||||
import {
|
||||
isFreeDrawElement,
|
||||
isLinearElement,
|
||||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { getPerfectElementSize } from "./sizeHelpers";
|
||||
import { measureText, getFontString } from "../utils";
|
||||
|
@ -244,7 +248,7 @@ const rescalePointsInElement = (
|
|||
width: number,
|
||||
height: number,
|
||||
) =>
|
||||
isLinearElement(element)
|
||||
isLinearElement(element) || isFreeDrawElement(element)
|
||||
? {
|
||||
points: rescalePoints(
|
||||
0,
|
||||
|
@ -404,7 +408,7 @@ export const resizeSingleElement = (
|
|||
-stateAtResizeStart.angle,
|
||||
);
|
||||
|
||||
//Get bounds corners rendered on screen
|
||||
// Get bounds corners rendered on screen
|
||||
const [esx1, esy1, esx2, esy2] = getResizedElementAbsoluteCoords(
|
||||
element,
|
||||
element.width,
|
||||
|
@ -644,11 +648,14 @@ const resizeMultipleElements = (
|
|||
font = { fontSize: nextFont.size, baseline: nextFont.baseline };
|
||||
}
|
||||
const origCoords = getElementAbsoluteCoords(element);
|
||||
|
||||
const rescaledPoints = rescalePointsInElement(element, width, height);
|
||||
|
||||
updateBoundElements(element, {
|
||||
newSize: { width, height },
|
||||
simultaneouslyUpdated: elements,
|
||||
});
|
||||
|
||||
const finalCoords = getResizedElementAbsoluteCoords(
|
||||
{
|
||||
...element,
|
||||
|
@ -657,6 +664,7 @@ const resizeMultipleElements = (
|
|||
width,
|
||||
height,
|
||||
);
|
||||
|
||||
const { x, y } = getNextXY(element, origCoords, finalCoords);
|
||||
return [...prev, { width, height, x, y, ...rescaledPoints, ...font }];
|
||||
},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ExcalidrawElement } from "./types";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { isLinearElement } from "./typeChecks";
|
||||
import { isFreeDrawElement, isLinearElement } from "./typeChecks";
|
||||
import { SHIFT_LOCKING_ANGLE } from "../constants";
|
||||
|
||||
export const isInvisiblySmallElement = (
|
||||
element: ExcalidrawElement,
|
||||
): boolean => {
|
||||
if (isLinearElement(element)) {
|
||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||
return element.points.length < 2;
|
||||
}
|
||||
return element.width === 0 && element.height === 0;
|
||||
|
@ -26,7 +26,7 @@ export const getPerfectElementSize = (
|
|||
if (
|
||||
elementType === "line" ||
|
||||
elementType === "arrow" ||
|
||||
elementType === "draw"
|
||||
elementType === "freedraw"
|
||||
) {
|
||||
const lockedAngle =
|
||||
Math.round(Math.atan(absHeight / absWidth) / SHIFT_LOCKING_ANGLE) *
|
||||
|
|
|
@ -225,7 +225,7 @@ export const getTransformHandles = (
|
|||
if (
|
||||
element.type === "arrow" ||
|
||||
element.type === "line" ||
|
||||
element.type === "draw"
|
||||
element.type === "freedraw"
|
||||
) {
|
||||
if (element.points.length === 2) {
|
||||
// only check the last point because starting point is always (0,0)
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
ExcalidrawLinearElement,
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawGenericElement,
|
||||
ExcalidrawFreeDrawElement,
|
||||
} from "./types";
|
||||
|
||||
export const isGenericElement = (
|
||||
|
@ -24,6 +25,18 @@ export const isTextElement = (
|
|||
return element != null && element.type === "text";
|
||||
};
|
||||
|
||||
export const isFreeDrawElement = (
|
||||
element?: ExcalidrawElement | null,
|
||||
): element is ExcalidrawFreeDrawElement => {
|
||||
return element != null && isFreeDrawElementType(element.type);
|
||||
};
|
||||
|
||||
export const isFreeDrawElementType = (
|
||||
elementType: ExcalidrawElement["type"],
|
||||
): boolean => {
|
||||
return elementType === "freedraw";
|
||||
};
|
||||
|
||||
export const isLinearElement = (
|
||||
element?: ExcalidrawElement | null,
|
||||
): element is ExcalidrawLinearElement => {
|
||||
|
@ -34,7 +47,7 @@ export const isLinearElementType = (
|
|||
elementType: ExcalidrawElement["type"],
|
||||
): boolean => {
|
||||
return (
|
||||
elementType === "arrow" || elementType === "line" || elementType === "draw"
|
||||
elementType === "arrow" || elementType === "line" // || elementType === "freedraw"
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -69,7 +82,7 @@ export const isExcalidrawElement = (element: any): boolean => {
|
|||
element?.type === "rectangle" ||
|
||||
element?.type === "ellipse" ||
|
||||
element?.type === "arrow" ||
|
||||
element?.type === "draw" ||
|
||||
element?.type === "freedraw" ||
|
||||
element?.type === "line"
|
||||
);
|
||||
};
|
||||
|
|
|
@ -78,7 +78,8 @@ export type ExcalidrawGenericElement =
|
|||
export type ExcalidrawElement =
|
||||
| ExcalidrawGenericElement
|
||||
| ExcalidrawTextElement
|
||||
| ExcalidrawLinearElement;
|
||||
| ExcalidrawLinearElement
|
||||
| ExcalidrawFreeDrawElement;
|
||||
|
||||
export type NonDeleted<TElement extends ExcalidrawElement> = TElement & {
|
||||
isDeleted: false;
|
||||
|
@ -121,3 +122,12 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase &
|
|||
startArrowhead: Arrowhead | null;
|
||||
endArrowhead: Arrowhead | null;
|
||||
}>;
|
||||
|
||||
export type ExcalidrawFreeDrawElement = _ExcalidrawElementBase &
|
||||
Readonly<{
|
||||
type: "freedraw";
|
||||
points: readonly Point[];
|
||||
pressures: readonly number[];
|
||||
simulatePressure: boolean;
|
||||
lastCommittedPoint: Point | null;
|
||||
}>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue