mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: add support for regular polygon shape with customizable sides
This commit is contained in:
parent
2a0d15799c
commit
c8d38e87b0
24 changed files with 329 additions and 67 deletions
|
@ -432,12 +432,12 @@ export const TOOL_TYPE = {
|
||||||
freedraw: "freedraw",
|
freedraw: "freedraw",
|
||||||
text: "text",
|
text: "text",
|
||||||
image: "image",
|
image: "image",
|
||||||
|
regularPolygon: "regularPolygon",
|
||||||
eraser: "eraser",
|
eraser: "eraser",
|
||||||
hand: "hand",
|
hand: "hand",
|
||||||
|
laser: "laser",
|
||||||
frame: "frame",
|
frame: "frame",
|
||||||
magicframe: "magicframe",
|
magicframe: "magicframe",
|
||||||
embeddable: "embeddable",
|
|
||||||
laser: "laser",
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const EDITOR_LS_KEYS = {
|
export const EDITOR_LS_KEYS = {
|
||||||
|
@ -473,4 +473,4 @@ export enum UserIdleState {
|
||||||
ACTIVE = "active",
|
ACTIVE = "active",
|
||||||
AWAY = "away",
|
AWAY = "away",
|
||||||
IDLE = "idle",
|
IDLE = "idle",
|
||||||
}
|
}
|
|
@ -28,6 +28,7 @@ import type {
|
||||||
ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
Arrowhead,
|
Arrowhead,
|
||||||
|
ExcalidrawRegularPolygonElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
import type { Drawable, Options } from "roughjs/bin/core";
|
import type { Drawable, Options } from "roughjs/bin/core";
|
||||||
|
@ -108,6 +109,14 @@ export const generateRoughOptions = (
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
case "regularPolygon": {
|
||||||
|
options.fillStyle = element.fillStyle;
|
||||||
|
options.fill = isTransparent(element.backgroundColor)
|
||||||
|
? undefined
|
||||||
|
: element.backgroundColor;
|
||||||
|
// Add any specific options for polygons if needed, otherwise just return
|
||||||
|
return options;
|
||||||
|
}
|
||||||
case "line":
|
case "line":
|
||||||
case "freedraw": {
|
case "freedraw": {
|
||||||
if (isPathALoop(element.points)) {
|
if (isPathALoop(element.points)) {
|
||||||
|
@ -127,6 +136,50 @@ export const generateRoughOptions = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the points for a regular polygon with the specified number of sides,
|
||||||
|
* centered within the element's bounds.
|
||||||
|
*/
|
||||||
|
export const getRegularPolygonPoints = (
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
sides: number = 6
|
||||||
|
): [number, number][] => {
|
||||||
|
// Minimum number of sides for a polygon is 3
|
||||||
|
if (sides < 3) {
|
||||||
|
sides = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = element.width;
|
||||||
|
const height = element.height;
|
||||||
|
|
||||||
|
// Center of the element
|
||||||
|
const cx = width / 2;
|
||||||
|
const cy = height / 2;
|
||||||
|
|
||||||
|
// Use the smaller dimension to ensure polygon fits within the element bounds
|
||||||
|
const radius = Math.min(width, height) / 2;
|
||||||
|
|
||||||
|
// Calculate points for the regular polygon
|
||||||
|
const points: [number, number][] = [];
|
||||||
|
|
||||||
|
// For regular polygons, we want to start from the top (angle = -π/2)
|
||||||
|
// so that polygons like hexagons have a flat top
|
||||||
|
const startAngle = -Math.PI / 2;
|
||||||
|
|
||||||
|
for (let i = 0; i < sides; i++) {
|
||||||
|
// Calculate angle for this vertex
|
||||||
|
const angle = startAngle + (2 * Math.PI * i) / sides;
|
||||||
|
|
||||||
|
// Calculate x and y for this vertex
|
||||||
|
const x = cx + radius * Math.cos(angle);
|
||||||
|
const y = cy + radius * Math.sin(angle);
|
||||||
|
|
||||||
|
points.push([x, y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return points;
|
||||||
|
};
|
||||||
|
|
||||||
const modifyIframeLikeForRoughOptions = (
|
const modifyIframeLikeForRoughOptions = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
isExporting: boolean,
|
isExporting: boolean,
|
||||||
|
@ -537,6 +590,75 @@ export const _generateElementShape = (
|
||||||
// `element.canvas` on rerenders
|
// `element.canvas` on rerenders
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
case "regularPolygon": {
|
||||||
|
let shape: ElementShapes[typeof element.type];
|
||||||
|
|
||||||
|
const points = getRegularPolygonPoints(
|
||||||
|
element,
|
||||||
|
(element as ExcalidrawRegularPolygonElement).sides
|
||||||
|
);
|
||||||
|
|
||||||
|
if (element.roundness) {
|
||||||
|
// For rounded corners, we create a path with smooth corners
|
||||||
|
// using quadratic Bézier curves, similar to the diamond shape
|
||||||
|
const options = generateRoughOptions(element, true);
|
||||||
|
|
||||||
|
// Calculate appropriate corner radius based on element size
|
||||||
|
const radius = getCornerRadius(
|
||||||
|
Math.min(element.width, element.height) / 4,
|
||||||
|
element
|
||||||
|
);
|
||||||
|
|
||||||
|
const pathData: string[] = [];
|
||||||
|
|
||||||
|
// Process each vertex to create rounded corners between edges
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const current = points[i];
|
||||||
|
const next = points[(i + 1) % points.length];
|
||||||
|
const prev = points[(i - 1 + points.length) % points.length];
|
||||||
|
|
||||||
|
// Calculate vectors to previous and next points
|
||||||
|
const toPrev = [prev[0] - current[0], prev[1] - current[1]];
|
||||||
|
const toNext = [next[0] - current[0], next[1] - current[1]];
|
||||||
|
|
||||||
|
// Normalize vectors and calculate corner points
|
||||||
|
const toPrevLength = Math.sqrt(toPrev[0] * toPrev[0] + toPrev[1] * toPrev[1]);
|
||||||
|
const toNextLength = Math.sqrt(toNext[0] * toNext[0] + toNext[1] * toNext[1]);
|
||||||
|
|
||||||
|
// Move inward from vertex toward previous point (limited by half the distance)
|
||||||
|
const prevCorner = [
|
||||||
|
current[0] + (toPrev[0] / toPrevLength) * Math.min(radius, toPrevLength / 2),
|
||||||
|
current[1] + (toPrev[1] / toPrevLength) * Math.min(radius, toPrevLength / 2)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Move inward from vertex toward next point (limited by half the distance)
|
||||||
|
const nextCorner = [
|
||||||
|
current[0] + (toNext[0] / toNextLength) * Math.min(radius, toNextLength / 2),
|
||||||
|
current[1] + (toNext[1] / toNextLength) * Math.min(radius, toNextLength / 2)
|
||||||
|
];
|
||||||
|
|
||||||
|
// First point needs a move command, others need line commands
|
||||||
|
if (i === 0) {
|
||||||
|
pathData.push(`M ${nextCorner[0]} ${nextCorner[1]}`);
|
||||||
|
} else {
|
||||||
|
// Draw line to the corner coming from previous point
|
||||||
|
pathData.push(`L ${prevCorner[0]} ${prevCorner[1]}`);
|
||||||
|
// Draw a quadratic curve around the current vertex to the corner going to next point
|
||||||
|
pathData.push(`Q ${current[0]} ${current[1]}, ${nextCorner[0]} ${nextCorner[1]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the path to create a complete shape
|
||||||
|
pathData.push("Z");
|
||||||
|
|
||||||
|
shape = generator.path(pathData.join(" "), options);
|
||||||
|
} else {
|
||||||
|
// For non-rounded corners, use the simple polygon generator
|
||||||
|
shape = generator.polygon(points, generateRoughOptions(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
assertNever(
|
assertNever(
|
||||||
element,
|
element,
|
||||||
|
@ -610,4 +732,4 @@ const generateElbowArrowShape = (
|
||||||
d.push(`L ${points[points.length - 1][0]} ${points[points.length - 1][1]}`);
|
d.push(`L ${points[points.length - 1][0]} ${points[points.length - 1][1]}`);
|
||||||
|
|
||||||
return d.join(" ");
|
return d.join(" ");
|
||||||
};
|
};
|
|
@ -1,11 +1,6 @@
|
||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
|
|
||||||
import {
|
import { rescalePoints, arrayToMap, invariant } from "@excalidraw/common";
|
||||||
rescalePoints,
|
|
||||||
arrayToMap,
|
|
||||||
invariant,
|
|
||||||
sizeOf,
|
|
||||||
} from "@excalidraw/common";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
|
@ -62,7 +57,7 @@ import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawRectanguloidElement,
|
ExcalidrawRectanguloidElement,
|
||||||
ExcalidrawEllipseElement,
|
ExcalidrawEllipseElement,
|
||||||
ElementsMapOrArray,
|
ExcalidrawRegularPolygonElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import type { Drawable, Op } from "roughjs/bin/core";
|
import type { Drawable, Op } from "roughjs/bin/core";
|
||||||
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
||||||
|
@ -944,10 +939,10 @@ export const getElementBounds = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCommonBounds = (
|
export const getCommonBounds = (
|
||||||
elements: ElementsMapOrArray,
|
elements: readonly ExcalidrawElement[],
|
||||||
elementsMap?: ElementsMap,
|
elementsMap?: ElementsMap,
|
||||||
): Bounds => {
|
): Bounds => {
|
||||||
if (!sizeOf(elements)) {
|
if (!elements.length) {
|
||||||
return [0, 0, 0, 0];
|
return [0, 0, 0, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,4 +1140,4 @@ export const doBoundsIntersect = (
|
||||||
const [minX2, minY2, maxX2, maxY2] = bounds2;
|
const [minX2, minY2, maxX2, maxY2] = bounds2;
|
||||||
|
|
||||||
return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
|
return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
|
||||||
};
|
};
|
|
@ -39,7 +39,11 @@ export const distanceToBindableElement = (
|
||||||
return distanceToDiamondElement(element, p);
|
return distanceToDiamondElement(element, p);
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
return distanceToEllipseElement(element, p);
|
return distanceToEllipseElement(element, p);
|
||||||
|
case "regularPolygon":
|
||||||
|
// For regularPolygon, use the same distance calculation as rectangle
|
||||||
|
return distanceToRectanguloidElement(element, p);
|
||||||
}
|
}
|
||||||
|
return Infinity;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,4 +120,4 @@ const distanceToEllipseElement = (
|
||||||
pointRotateRads(p, center, -element.angle as Radians),
|
pointRotateRads(p, center, -element.angle as Radians),
|
||||||
ellipse(center, element.width / 2, element.height / 2),
|
ellipse(center, element.width / 2, element.height / 2),
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -46,6 +46,7 @@ import type {
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
FixedSegment,
|
FixedSegment,
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
|
ExcalidrawRegularPolygonElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export type ElementConstructorOpts = MarkOptional<
|
export type ElementConstructorOpts = MarkOptional<
|
||||||
|
@ -471,6 +472,28 @@ export const newLinearElement = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const newRegularPolygonElement = (
|
||||||
|
opts: {
|
||||||
|
type: "regularPolygon";
|
||||||
|
sides?: number;
|
||||||
|
} & ElementConstructorOpts,
|
||||||
|
): NonDeleted<ExcalidrawRegularPolygonElement> => {
|
||||||
|
// create base element
|
||||||
|
const base = _newElementBase<ExcalidrawRegularPolygonElement>(
|
||||||
|
"regularPolygon",
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
// set default size if none provided
|
||||||
|
const width = opts.width ?? 100;
|
||||||
|
const height = opts.height ?? 100;
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
sides: opts.sides ?? 6, // default to hexagon
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const newArrowElement = <T extends boolean>(
|
export const newArrowElement = <T extends boolean>(
|
||||||
opts: {
|
opts: {
|
||||||
type: ExcalidrawArrowElement["type"];
|
type: ExcalidrawArrowElement["type"];
|
||||||
|
@ -532,4 +555,4 @@ export const newImageElement = (
|
||||||
scale: opts.scale ?? [1, 1],
|
scale: opts.scale ?? [1, 1],
|
||||||
crop: opts.crop ?? null,
|
crop: opts.crop ?? null,
|
||||||
};
|
};
|
||||||
};
|
};
|
|
@ -394,6 +394,7 @@ const drawElementOnCanvas = (
|
||||||
appState: StaticCanvasAppState,
|
appState: StaticCanvasAppState,
|
||||||
) => {
|
) => {
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
|
case "regularPolygon":
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
case "iframe":
|
case "iframe":
|
||||||
case "embeddable":
|
case "embeddable":
|
||||||
|
@ -806,6 +807,7 @@ export const renderElement = (
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "regularPolygon":
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
case "diamond":
|
case "diamond":
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
|
@ -1077,4 +1079,4 @@ function getSvgPathFromStroke(points: number[][]): string {
|
||||||
)
|
)
|
||||||
.join(" ")
|
.join(" ")
|
||||||
.replace(TO_FIXED_PRECISION, "$1");
|
.replace(TO_FIXED_PRECISION, "$1");
|
||||||
}
|
}
|
|
@ -40,6 +40,7 @@ import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
|
ExcalidrawRegularPolygonElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,7 +96,12 @@ export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
shouldTestInside(element),
|
shouldTestInside(element),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
case "regularPolygon":
|
||||||
|
return getPolygonShape(element as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this throw to fix the "no return statement" error
|
||||||
|
throw new Error(`Unsupported element type: ${(element as any).type}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
|
@ -282,6 +288,16 @@ export const mapIntervalToBezierT = <P extends GlobalPoint | LocalPoint>(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns geometric shape for regular polygon
|
||||||
|
*/
|
||||||
|
export const getRegularPolygonShapePoints = <Point extends GlobalPoint | LocalPoint>(
|
||||||
|
element: ExcalidrawRegularPolygonElement,
|
||||||
|
): GeometricShape<Point> => {
|
||||||
|
// We'll use the same shape calculation as other polygon-like elements
|
||||||
|
return getPolygonShape<Point>(element as any);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the axis-aligned bounding box for a given element
|
* Get the axis-aligned bounding box for a given element
|
||||||
*/
|
*/
|
||||||
|
@ -395,4 +411,4 @@ export const isPathALoop = (
|
||||||
return distance <= LINE_CONFIRM_THRESHOLD / zoomValue;
|
return distance <= LINE_CONFIRM_THRESHOLD / zoomValue;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
|
@ -28,6 +28,8 @@ import type {
|
||||||
PointBinding,
|
PointBinding,
|
||||||
FixedPointBinding,
|
FixedPointBinding,
|
||||||
ExcalidrawFlowchartNodeElement,
|
ExcalidrawFlowchartNodeElement,
|
||||||
|
ExcalidrawRegularPolygonElement,
|
||||||
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export const isInitializedImageElement = (
|
export const isInitializedImageElement = (
|
||||||
|
@ -159,6 +161,7 @@ export const isBindableElement = (
|
||||||
element.type === "embeddable" ||
|
element.type === "embeddable" ||
|
||||||
element.type === "frame" ||
|
element.type === "frame" ||
|
||||||
element.type === "magicframe" ||
|
element.type === "magicframe" ||
|
||||||
|
element.type === "regularPolygon" ||
|
||||||
(element.type === "text" && !element.containerId))
|
(element.type === "text" && !element.containerId))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -175,10 +178,17 @@ export const isRectanguloidElement = (
|
||||||
element.type === "embeddable" ||
|
element.type === "embeddable" ||
|
||||||
element.type === "frame" ||
|
element.type === "frame" ||
|
||||||
element.type === "magicframe" ||
|
element.type === "magicframe" ||
|
||||||
|
element.type === "regularPolygon" ||
|
||||||
(element.type === "text" && !element.containerId))
|
(element.type === "text" && !element.containerId))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isRegularPolygonElement = (
|
||||||
|
element: unknown
|
||||||
|
): element is ExcalidrawRegularPolygonElement => {
|
||||||
|
return element != null && (element as any).type === "regularPolygon";
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Remove this when proper distance calculation is introduced
|
// TODO: Remove this when proper distance calculation is introduced
|
||||||
// @see binding.ts:distanceToBindableElement()
|
// @see binding.ts:distanceToBindableElement()
|
||||||
export const isRectangularElement = (
|
export const isRectangularElement = (
|
||||||
|
@ -231,7 +241,8 @@ export const isExcalidrawElement = (
|
||||||
case "frame":
|
case "frame":
|
||||||
case "magicframe":
|
case "magicframe":
|
||||||
case "image":
|
case "image":
|
||||||
case "selection": {
|
case "selection":
|
||||||
|
case "regularPolygon": {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -337,4 +348,4 @@ export const isBounds = (box: unknown): box is Bounds =>
|
||||||
typeof box[0] === "number" &&
|
typeof box[0] === "number" &&
|
||||||
typeof box[1] === "number" &&
|
typeof box[1] === "number" &&
|
||||||
typeof box[2] === "number" &&
|
typeof box[2] === "number" &&
|
||||||
typeof box[3] === "number";
|
typeof box[3] === "number";
|
|
@ -165,6 +165,12 @@ export type ExcalidrawFrameElement = _ExcalidrawElementBase & {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ExcalidrawRegularPolygonElement = _ExcalidrawElementBase & {
|
||||||
|
type: "regularPolygon";
|
||||||
|
/** Number of sides in the regular polygon */
|
||||||
|
sides: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ExcalidrawMagicFrameElement = _ExcalidrawElementBase & {
|
export type ExcalidrawMagicFrameElement = _ExcalidrawElementBase & {
|
||||||
type: "magicframe";
|
type: "magicframe";
|
||||||
name: string | null;
|
name: string | null;
|
||||||
|
@ -195,7 +201,8 @@ export type ExcalidrawRectanguloidElement =
|
||||||
| ExcalidrawFreeDrawElement
|
| ExcalidrawFreeDrawElement
|
||||||
| ExcalidrawIframeLikeElement
|
| ExcalidrawIframeLikeElement
|
||||||
| ExcalidrawFrameLikeElement
|
| ExcalidrawFrameLikeElement
|
||||||
| ExcalidrawEmbeddableElement;
|
| ExcalidrawEmbeddableElement
|
||||||
|
| ExcalidrawRegularPolygonElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExcalidrawElement should be JSON serializable and (eventually) contain
|
* ExcalidrawElement should be JSON serializable and (eventually) contain
|
||||||
|
@ -212,7 +219,8 @@ export type ExcalidrawElement =
|
||||||
| ExcalidrawFrameElement
|
| ExcalidrawFrameElement
|
||||||
| ExcalidrawMagicFrameElement
|
| ExcalidrawMagicFrameElement
|
||||||
| ExcalidrawIframeElement
|
| ExcalidrawIframeElement
|
||||||
| ExcalidrawEmbeddableElement;
|
| ExcalidrawEmbeddableElement
|
||||||
|
| ExcalidrawRegularPolygonElement;
|
||||||
|
|
||||||
export type ExcalidrawNonSelectionElement = Exclude<
|
export type ExcalidrawNonSelectionElement = Exclude<
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
@ -264,7 +272,8 @@ export type ExcalidrawBindableElement =
|
||||||
| ExcalidrawIframeElement
|
| ExcalidrawIframeElement
|
||||||
| ExcalidrawEmbeddableElement
|
| ExcalidrawEmbeddableElement
|
||||||
| ExcalidrawFrameElement
|
| ExcalidrawFrameElement
|
||||||
| ExcalidrawMagicFrameElement;
|
| ExcalidrawMagicFrameElement
|
||||||
|
| ExcalidrawRegularPolygonElement;
|
||||||
|
|
||||||
export type ExcalidrawTextContainer =
|
export type ExcalidrawTextContainer =
|
||||||
| ExcalidrawRectangleElement
|
| ExcalidrawRectangleElement
|
||||||
|
@ -411,4 +420,4 @@ export type NonDeletedSceneElementsMap = Map<
|
||||||
|
|
||||||
export type ElementsMapOrArray =
|
export type ElementsMapOrArray =
|
||||||
| readonly ExcalidrawElement[]
|
| readonly ExcalidrawElement[]
|
||||||
| Readonly<ElementsMap>;
|
| Readonly<ElementsMap>;
|
|
@ -2,8 +2,6 @@ import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||||
|
|
||||||
import { isElbowArrow, isLinearElement } from "@excalidraw/element/typeChecks";
|
import { isElbowArrow, isLinearElement } from "@excalidraw/element/typeChecks";
|
||||||
|
|
||||||
import { arrayToMap } from "@excalidraw/common";
|
|
||||||
|
|
||||||
import type { ExcalidrawLinearElement } from "@excalidraw/element/types";
|
import type { ExcalidrawLinearElement } from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette";
|
import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette";
|
||||||
|
@ -52,7 +50,7 @@ export const actionToggleLinearEditor = register({
|
||||||
const editingLinearElement =
|
const editingLinearElement =
|
||||||
appState.editingLinearElement?.elementId === selectedElement.id
|
appState.editingLinearElement?.elementId === selectedElement.id
|
||||||
? null
|
? null
|
||||||
: new LinearElementEditor(selectedElement, arrayToMap(elements));
|
: new LinearElementEditor(selectedElement);
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
|
@ -82,3 +80,39 @@ export const actionToggleLinearEditor = register({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
export const actionChangeRegularPolygonSides = register({
|
||||||
|
name: "changeRegularPolygonSides",
|
||||||
|
label: "labels.regularPolygonSides",
|
||||||
|
trackEvent: false,
|
||||||
|
perform: (elements, appState, value) => {
|
||||||
|
return {
|
||||||
|
elements: elements.map((el) =>
|
||||||
|
el.type === "regularPolygon"
|
||||||
|
? { ...el, sides: value }
|
||||||
|
: el
|
||||||
|
),
|
||||||
|
appState,
|
||||||
|
commitToHistory: true,
|
||||||
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ elements, updateData }) => {
|
||||||
|
const selected = elements.find((el) => el.type === "regularPolygon");
|
||||||
|
if (!selected) return null;
|
||||||
|
// Type guard for ExcalidrawRegularPolygonElement
|
||||||
|
if (selected.type !== "regularPolygon") return null;
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
<legend>{t("labels.regularPolygonSides") /* Add this key to your translation files, e.g., "Number of sides" */}</legend>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={3}
|
||||||
|
max={12}
|
||||||
|
value={selected.sides}
|
||||||
|
onChange={(e) => updateData(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
<span>{selected.sides}</span>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -140,7 +140,8 @@ export type ActionName =
|
||||||
| "linkToElement"
|
| "linkToElement"
|
||||||
| "cropEditor"
|
| "cropEditor"
|
||||||
| "wrapSelectionInFrame"
|
| "wrapSelectionInFrame"
|
||||||
| "toggleLassoTool";
|
| "toggleLassoTool"
|
||||||
|
| "changeRegularPolygonSides";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
@ -206,4 +207,4 @@ export interface Action {
|
||||||
/** if set to `true`, allow action to be performed in viewMode.
|
/** if set to `true`, allow action to be performed in viewMode.
|
||||||
* Defaults to `false` */
|
* Defaults to `false` */
|
||||||
viewMode?: boolean;
|
viewMode?: boolean;
|
||||||
}
|
}
|
|
@ -136,6 +136,7 @@ import {
|
||||||
newLinearElement,
|
newLinearElement,
|
||||||
newTextElement,
|
newTextElement,
|
||||||
refreshTextDimensions,
|
refreshTextDimensions,
|
||||||
|
newRegularPolygonElement,
|
||||||
} from "@excalidraw/element/newElement";
|
} from "@excalidraw/element/newElement";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -6622,7 +6623,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
) {
|
) {
|
||||||
this.createFrameElementOnPointerDown(
|
this.createFrameElementOnPointerDown(
|
||||||
pointerDownState,
|
pointerDownState,
|
||||||
this.state.activeTool.type,
|
// Ensure only frame or magicframe types are passed
|
||||||
|
this.state.activeTool.type as "frame" | "magicframe",
|
||||||
);
|
);
|
||||||
} else if (this.state.activeTool.type === "laser") {
|
} else if (this.state.activeTool.type === "laser") {
|
||||||
this.laserTrails.startPath(
|
this.laserTrails.startPath(
|
||||||
|
@ -6630,11 +6632,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
pointerDownState.lastCoords.y,
|
pointerDownState.lastCoords.y,
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
this.state.activeTool.type !== "eraser" &&
|
["selection", "rectangle", "diamond", "ellipse", "embeddable", "regularPolygon", "text", "image", "lasso"].includes(
|
||||||
this.state.activeTool.type !== "hand"
|
this.state.activeTool.type
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
this.createGenericElementOnPointerDown(
|
this.createGenericElementOnPointerDown(
|
||||||
this.state.activeTool.type,
|
this.state.activeTool.type as "selection" | "rectangle" | "diamond" | "ellipse" | "embeddable" | "regularPolygon",
|
||||||
pointerDownState,
|
pointerDownState,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7820,7 +7823,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
| "diamond"
|
| "diamond"
|
||||||
| "ellipse"
|
| "ellipse"
|
||||||
| "iframe"
|
| "iframe"
|
||||||
| "embeddable",
|
| "embeddable"
|
||||||
|
| "regularPolygon",
|
||||||
|
specificType?: string,
|
||||||
|
simulatePressure?: boolean
|
||||||
) {
|
) {
|
||||||
return this.state.currentItemRoundness === "round"
|
return this.state.currentItemRoundness === "round"
|
||||||
? {
|
? {
|
||||||
|
@ -7832,22 +7838,15 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createGenericElementOnPointerDown = (
|
private createGenericElementOnPointerDown = (
|
||||||
elementType: ExcalidrawGenericElement["type"] | "embeddable",
|
elementType: ExcalidrawGenericElement["type"] | "embeddable" | "regularPolygon",
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
): void => {
|
): void => {
|
||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
this.lastPointerDownEvent?.[KEYS.CTRL_OR_CMD]
|
this.getEffectiveGridSize(),
|
||||||
? null
|
|
||||||
: this.getEffectiveGridSize(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
|
||||||
x: gridX,
|
|
||||||
y: gridY,
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseElementAttributes = {
|
const baseElementAttributes = {
|
||||||
x: gridX,
|
x: gridX,
|
||||||
y: gridY,
|
y: gridY,
|
||||||
|
@ -7858,10 +7857,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
roundness: this.getCurrentItemRoundness(elementType),
|
roundness: this.state.currentItemRoundness
|
||||||
|
? {
|
||||||
|
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
};
|
||||||
} as const;
|
|
||||||
|
|
||||||
let element;
|
let element;
|
||||||
if (elementType === "embeddable") {
|
if (elementType === "embeddable") {
|
||||||
|
@ -7869,6 +7871,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
type: "embeddable",
|
type: "embeddable",
|
||||||
...baseElementAttributes,
|
...baseElementAttributes,
|
||||||
});
|
});
|
||||||
|
} else if (elementType === "regularPolygon") {
|
||||||
|
element = newRegularPolygonElement({
|
||||||
|
type: "regularPolygon",
|
||||||
|
...baseElementAttributes,
|
||||||
|
sides: 6, // Default to hexagon
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
element = newElement({
|
element = newElement({
|
||||||
type: elementType,
|
type: elementType,
|
||||||
|
|
|
@ -484,7 +484,7 @@ function CommandPaletteInner({
|
||||||
const command: CommandPaletteItem = {
|
const command: CommandPaletteItem = {
|
||||||
label: t(`toolBar.${value}`),
|
label: t(`toolBar.${value}`),
|
||||||
category: DEFAULT_CATEGORIES.tools,
|
category: DEFAULT_CATEGORIES.tools,
|
||||||
shortcut,
|
shortcut: shortcut === null ? undefined : String(shortcut),
|
||||||
icon,
|
icon,
|
||||||
keywords: ["toolbar"],
|
keywords: ["toolbar"],
|
||||||
viewMode: false,
|
viewMode: false,
|
||||||
|
@ -961,4 +961,4 @@ const CommandItem = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -151,6 +151,10 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||||
label={t("toolBar.rectangle")}
|
label={t("toolBar.rectangle")}
|
||||||
shortcuts={[KEYS.R, KEYS["2"]]}
|
shortcuts={[KEYS.R, KEYS["2"]]}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("toolBar.regularPolygon")}
|
||||||
|
shortcuts={["6"]}
|
||||||
|
/>
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("toolBar.diamond")}
|
label={t("toolBar.diamond")}
|
||||||
shortcuts={[KEYS.D, KEYS["3"]]}
|
shortcuts={[KEYS.D, KEYS["3"]]}
|
||||||
|
@ -505,4 +509,4 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -296,7 +296,9 @@ export const StatsInner = memo(
|
||||||
>
|
>
|
||||||
{appState.croppingElementId
|
{appState.croppingElementId
|
||||||
? t("labels.imageCropping")
|
? t("labels.imageCropping")
|
||||||
: t(`element.${singleElement.type}`)}
|
: singleElement && singleElement.type
|
||||||
|
? singleElement.type.charAt(0).toUpperCase() + singleElement.type.slice(1)
|
||||||
|
: ""}
|
||||||
</StatsRow>
|
</StatsRow>
|
||||||
|
|
||||||
<StatsRow>
|
<StatsRow>
|
||||||
|
@ -442,4 +444,4 @@ export const StatsInner = memo(
|
||||||
prev.appState.croppingElementId === next.appState.croppingElementId
|
prev.appState.croppingElementId === next.appState.croppingElementId
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
|
@ -308,6 +308,17 @@ export const DiamondIcon = createIcon(
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const RegularPolygonIcon = createIcon(
|
||||||
|
// Hexagon points - centered in a 24x24 viewport
|
||||||
|
<polygon
|
||||||
|
points="12,3 20.8,7.5 20.8,16.5 12,21 3.2,16.5 3.2,7.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
fill="none"
|
||||||
|
/>,
|
||||||
|
tablerIconProps, // Use tablerIconProps for consistency
|
||||||
|
);
|
||||||
|
|
||||||
// tabler-icons: circle
|
// tabler-icons: circle
|
||||||
export const EllipseIcon = createIcon(
|
export const EllipseIcon = createIcon(
|
||||||
<g strokeWidth="1.5">
|
<g strokeWidth="1.5">
|
||||||
|
@ -920,7 +931,7 @@ export const shield = createIcon(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const file = createIcon(
|
export const file = createIcon(
|
||||||
"M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm32-48h224V288l-23.5-23.5c-4.7-4.7-12.3-4.7-17 0L176 352l-39.5-39.5c-4.7-4.7-12.3-4.7-17 0L80 352v64zm48-240c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48z",
|
"M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm48-48h224V288l-23.5-23.5c-4.7-4.7-12.3-4.7-17 0L176 352l-39.5-39.5c-4.7-4.7-12.3-4.7-17 0L80 352v64zm48-240c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48z",
|
||||||
{ width: 384, height: 512 },
|
{ width: 384, height: 512 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2235,4 +2246,4 @@ export const elementLinkIcon = createIcon(
|
||||||
<path d="M19 7l0 10" />
|
<path d="M19 7l0 10" />
|
||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
|
@ -11,6 +11,7 @@ import {
|
||||||
TextIcon,
|
TextIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
EraserIcon,
|
EraserIcon,
|
||||||
|
RegularPolygonIcon,
|
||||||
} from "./icons";
|
} from "./icons";
|
||||||
|
|
||||||
export const SHAPES = [
|
export const SHAPES = [
|
||||||
|
@ -77,6 +78,13 @@ export const SHAPES = [
|
||||||
numericKey: KEYS["9"],
|
numericKey: KEYS["9"],
|
||||||
fillable: false,
|
fillable: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: RegularPolygonIcon,
|
||||||
|
value: "regularPolygon",
|
||||||
|
key: null, // TODO: Assign a unique letter key if needed
|
||||||
|
numericKey: null, // Removed conflicting key
|
||||||
|
fillable: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: EraserIcon,
|
icon: EraserIcon,
|
||||||
value: "eraser",
|
value: "eraser",
|
||||||
|
@ -97,4 +105,4 @@ export const findShapeByKey = (key: string) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return shape?.value || null;
|
return shape?.value || null;
|
||||||
};
|
};
|
|
@ -102,6 +102,7 @@ export const AllowedExcalidrawActiveTools: Record<
|
||||||
hand: true,
|
hand: true,
|
||||||
laser: false,
|
laser: false,
|
||||||
magicframe: false,
|
magicframe: false,
|
||||||
|
regularPolygon: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RestoredDataState = {
|
export type RestoredDataState = {
|
||||||
|
@ -831,4 +832,4 @@ export const restoreLibraryItems = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return restoredItems;
|
return restoredItems;
|
||||||
};
|
};
|
|
@ -126,6 +126,7 @@
|
||||||
"increaseFontSize": "Increase font size",
|
"increaseFontSize": "Increase font size",
|
||||||
"unbindText": "Unbind text",
|
"unbindText": "Unbind text",
|
||||||
"bindText": "Bind text to the container",
|
"bindText": "Bind text to the container",
|
||||||
|
"regularPolygonSides": "Number of sides",
|
||||||
"createContainerFromText": "Wrap text in a container",
|
"createContainerFromText": "Wrap text in a container",
|
||||||
"link": {
|
"link": {
|
||||||
"edit": "Edit link",
|
"edit": "Edit link",
|
||||||
|
@ -283,6 +284,7 @@
|
||||||
"ellipse": "Ellipse",
|
"ellipse": "Ellipse",
|
||||||
"arrow": "Arrow",
|
"arrow": "Arrow",
|
||||||
"line": "Line",
|
"line": "Line",
|
||||||
|
"regularPolygon": "Regular Polygon",
|
||||||
"freedraw": "Draw",
|
"freedraw": "Draw",
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
"library": "Library",
|
"library": "Library",
|
||||||
|
@ -631,4 +633,4 @@
|
||||||
"itemNotAvailable": "Command is not available...",
|
"itemNotAvailable": "Command is not available...",
|
||||||
"shortcutHint": "For Command palette, use {{shortcut}}"
|
"shortcutHint": "For Command palette, use {{shortcut}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -150,6 +150,7 @@ const renderElementToSvg = (
|
||||||
// this should not happen
|
// this should not happen
|
||||||
throw new Error("Selection rendering is not supported for SVG");
|
throw new Error("Selection rendering is not supported for SVG");
|
||||||
}
|
}
|
||||||
|
case "regularPolygon": // <-- Add this line
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
case "diamond":
|
case "diamond":
|
||||||
case "ellipse": {
|
case "ellipse": {
|
||||||
|
@ -754,4 +755,4 @@ export const renderSceneToSvg = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
|
@ -130,14 +130,12 @@ export type ScrollBars = {
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
deltaMultiplier: number;
|
|
||||||
} | null;
|
} | null;
|
||||||
vertical: {
|
vertical: {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
deltaMultiplier: number;
|
|
||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -156,4 +154,5 @@ export type ElementShapes = {
|
||||||
image: null;
|
image: null;
|
||||||
frame: null;
|
frame: null;
|
||||||
magicframe: null;
|
magicframe: null;
|
||||||
};
|
regularPolygon: Drawable;
|
||||||
|
};
|
|
@ -17,6 +17,7 @@ import {
|
||||||
newLinearElement,
|
newLinearElement,
|
||||||
newMagicFrameElement,
|
newMagicFrameElement,
|
||||||
newTextElement,
|
newTextElement,
|
||||||
|
newRegularPolygonElement,
|
||||||
} from "@excalidraw/element/newElement";
|
} from "@excalidraw/element/newElement";
|
||||||
|
|
||||||
import { isLinearElementType } from "@excalidraw/element/typeChecks";
|
import { isLinearElementType } from "@excalidraw/element/typeChecks";
|
||||||
|
@ -361,10 +362,19 @@ export class API {
|
||||||
case "magicframe":
|
case "magicframe":
|
||||||
element = newMagicFrameElement({ ...base, width, height });
|
element = newMagicFrameElement({ ...base, width, height });
|
||||||
break;
|
break;
|
||||||
|
case "regularPolygon":
|
||||||
|
element = newRegularPolygonElement({
|
||||||
|
type: type as "regularPolygon",
|
||||||
|
...base,
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
// This check ensures all TOOL_TYPE values are handled.
|
||||||
|
// If a new tool type is added without updating this switch,
|
||||||
|
// TypeScript will error here.
|
||||||
assertNever(
|
assertNever(
|
||||||
type,
|
type,
|
||||||
`API.createElement: unimplemented element type ${type}}`,
|
`API.createElement: unimplemented element type ${type as string}}`,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { TOOL_TYPE } from "@excalidraw/common";
|
||||||
import type { ToolType } from "@excalidraw/excalidraw/types";
|
import type { ToolType } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
const _getAllByToolName = (container: HTMLElement, tool: ToolType | "lock") => {
|
const _getAllByToolName = (container: HTMLElement, tool: ToolType | "lock") => {
|
||||||
const toolTitle = tool === "lock" ? "lock" : TOOL_TYPE[tool];
|
const toolTitle = tool === "lock" ? "lock" : TOOL_TYPE[tool as keyof typeof TOOL_TYPE];
|
||||||
return queries.getAllByTestId(container, `toolbar-${toolTitle}`);
|
return queries.getAllByTestId(container, `toolbar-${toolTitle}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,4 +24,4 @@ export const [
|
||||||
_getAllByToolName,
|
_getAllByToolName,
|
||||||
getMultipleError,
|
getMultipleError,
|
||||||
getMissingError,
|
getMissingError,
|
||||||
);
|
);
|
|
@ -138,6 +138,7 @@ export type ToolType =
|
||||||
| "selection"
|
| "selection"
|
||||||
| "lasso"
|
| "lasso"
|
||||||
| "rectangle"
|
| "rectangle"
|
||||||
|
| "regularPolygon"
|
||||||
| "diamond"
|
| "diamond"
|
||||||
| "ellipse"
|
| "ellipse"
|
||||||
| "arrow"
|
| "arrow"
|
||||||
|
@ -601,7 +602,6 @@ export interface ExcalidrawProps {
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
aiEnabled?: boolean;
|
aiEnabled?: boolean;
|
||||||
showDeprecatedFonts?: boolean;
|
showDeprecatedFonts?: boolean;
|
||||||
renderScrollbars?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SceneData = {
|
export type SceneData = {
|
||||||
|
@ -784,7 +784,6 @@ export type UnsubscribeCallback = () => void;
|
||||||
|
|
||||||
export interface ExcalidrawImperativeAPI {
|
export interface ExcalidrawImperativeAPI {
|
||||||
updateScene: InstanceType<typeof App>["updateScene"];
|
updateScene: InstanceType<typeof App>["updateScene"];
|
||||||
mutateElement: InstanceType<typeof App>["mutateElement"];
|
|
||||||
updateLibrary: InstanceType<typeof Library>["updateLibrary"];
|
updateLibrary: InstanceType<typeof Library>["updateLibrary"];
|
||||||
resetScene: InstanceType<typeof App>["resetScene"];
|
resetScene: InstanceType<typeof App>["resetScene"];
|
||||||
getSceneElementsIncludingDeleted: InstanceType<
|
getSceneElementsIncludingDeleted: InstanceType<
|
||||||
|
@ -917,4 +916,4 @@ export type Offsets = Partial<{
|
||||||
right: number;
|
right: number;
|
||||||
bottom: number;
|
bottom: number;
|
||||||
left: number;
|
left: number;
|
||||||
}>;
|
}>;
|
Loading…
Add table
Add a link
Reference in a new issue