You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
4.3 KiB
TypeScript
192 lines
4.3 KiB
TypeScript
import {
|
|
getClosedCurveShape,
|
|
getCurveShape,
|
|
getEllipseShape,
|
|
getFreedrawShape,
|
|
getPolygonShape,
|
|
type GeometricShape,
|
|
} from "../utils/geometry/shape";
|
|
import {
|
|
ArrowIcon,
|
|
DiamondIcon,
|
|
EllipseIcon,
|
|
EraserIcon,
|
|
FreedrawIcon,
|
|
ImageIcon,
|
|
LineIcon,
|
|
RectangleIcon,
|
|
SelectionIcon,
|
|
TextIcon,
|
|
} from "./components/icons";
|
|
import { getElementAbsoluteCoords } from "./element";
|
|
import { shouldTestInside } from "./element/collision";
|
|
import { LinearElementEditor } from "./element/linearElementEditor";
|
|
import { getBoundTextElement } from "./element/textElement";
|
|
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
|
import { KEYS } from "./keys";
|
|
import { ShapeCache } from "./scene/ShapeCache";
|
|
|
|
export const SHAPES = [
|
|
{
|
|
icon: SelectionIcon,
|
|
value: "selection",
|
|
key: KEYS.V,
|
|
numericKey: KEYS["1"],
|
|
fillable: true,
|
|
},
|
|
{
|
|
icon: RectangleIcon,
|
|
value: "rectangle",
|
|
key: KEYS.R,
|
|
numericKey: KEYS["2"],
|
|
fillable: true,
|
|
},
|
|
{
|
|
icon: DiamondIcon,
|
|
value: "diamond",
|
|
key: KEYS.D,
|
|
numericKey: KEYS["3"],
|
|
fillable: true,
|
|
},
|
|
{
|
|
icon: EllipseIcon,
|
|
value: "ellipse",
|
|
key: KEYS.O,
|
|
numericKey: KEYS["4"],
|
|
fillable: true,
|
|
},
|
|
{
|
|
icon: ArrowIcon,
|
|
value: "arrow",
|
|
key: KEYS.A,
|
|
numericKey: KEYS["5"],
|
|
fillable: true,
|
|
},
|
|
{
|
|
icon: LineIcon,
|
|
value: "line",
|
|
key: KEYS.L,
|
|
numericKey: KEYS["6"],
|
|
fillable: true,
|
|
},
|
|
{
|
|
icon: FreedrawIcon,
|
|
value: "freedraw",
|
|
key: [KEYS.P, KEYS.X],
|
|
numericKey: KEYS["7"],
|
|
fillable: false,
|
|
},
|
|
{
|
|
icon: TextIcon,
|
|
value: "text",
|
|
key: KEYS.T,
|
|
numericKey: KEYS["8"],
|
|
fillable: false,
|
|
},
|
|
{
|
|
icon: ImageIcon,
|
|
value: "image",
|
|
key: null,
|
|
numericKey: KEYS["9"],
|
|
fillable: false,
|
|
},
|
|
{
|
|
icon: EraserIcon,
|
|
value: "eraser",
|
|
key: KEYS.E,
|
|
numericKey: KEYS["0"],
|
|
fillable: false,
|
|
},
|
|
] as const;
|
|
|
|
export const findShapeByKey = (key: string) => {
|
|
const shape = SHAPES.find((shape, index) => {
|
|
return (
|
|
(shape.numericKey != null && key === shape.numericKey.toString()) ||
|
|
(shape.key &&
|
|
(typeof shape.key === "string"
|
|
? shape.key === key
|
|
: (shape.key as readonly string[]).includes(key)))
|
|
);
|
|
});
|
|
return shape?.value || null;
|
|
};
|
|
|
|
/**
|
|
* get the pure geometric shape of an excalidraw element
|
|
* which is then used for hit detection
|
|
*/
|
|
export const getElementShape = (
|
|
element: ExcalidrawElement,
|
|
elementsMap: ElementsMap,
|
|
): GeometricShape => {
|
|
switch (element.type) {
|
|
case "rectangle":
|
|
case "diamond":
|
|
case "frame":
|
|
case "magicframe":
|
|
case "embeddable":
|
|
case "image":
|
|
case "iframe":
|
|
case "text":
|
|
case "selection":
|
|
return getPolygonShape(element);
|
|
case "arrow":
|
|
case "line": {
|
|
const roughShape =
|
|
ShapeCache.get(element)?.[0] ??
|
|
ShapeCache.generateElementShape(element, null)[0];
|
|
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
|
|
|
return shouldTestInside(element)
|
|
? getClosedCurveShape(
|
|
element,
|
|
roughShape,
|
|
[element.x, element.y],
|
|
element.angle,
|
|
[cx, cy],
|
|
)
|
|
: getCurveShape(roughShape, [element.x, element.y], element.angle, [
|
|
cx,
|
|
cy,
|
|
]);
|
|
}
|
|
|
|
case "ellipse":
|
|
return getEllipseShape(element);
|
|
|
|
case "freedraw": {
|
|
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
|
return getFreedrawShape(element, [cx, cy], shouldTestInside(element));
|
|
}
|
|
}
|
|
};
|
|
|
|
export const getBoundTextShape = (
|
|
element: ExcalidrawElement,
|
|
elementsMap: ElementsMap,
|
|
): GeometricShape | null => {
|
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
|
|
|
if (boundTextElement) {
|
|
if (element.type === "arrow") {
|
|
return getElementShape(
|
|
{
|
|
...boundTextElement,
|
|
// arrow's bound text accurate position is not stored in the element's property
|
|
// but rather calculated and returned from the following static method
|
|
...LinearElementEditor.getBoundTextElementPosition(
|
|
element,
|
|
boundTextElement,
|
|
elementsMap,
|
|
),
|
|
},
|
|
elementsMap,
|
|
);
|
|
}
|
|
return getElementShape(boundTextElement, elementsMap);
|
|
}
|
|
|
|
return null;
|
|
};
|