mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
type safe element conversion
This commit is contained in:
parent
de6acc4bad
commit
37e12ec201
2 changed files with 96 additions and 98 deletions
|
@ -27,15 +27,14 @@ import {
|
||||||
isUsingAdaptiveRadius,
|
isUsingAdaptiveRadius,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
|
import { newArrowElement, newElement, newLinearElement } from "./newElement";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ConvertibleGenericTypes,
|
ConvertibleGenericTypes,
|
||||||
ConvertibleLinearTypes,
|
ConvertibleLinearTypes,
|
||||||
ExcalidrawArrowElement,
|
|
||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
ExcalidrawElbowArrowElement,
|
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawEllipseElement,
|
ExcalidrawEllipseElement,
|
||||||
ExcalidrawLinearElement,
|
|
||||||
ExcalidrawRectangleElement,
|
ExcalidrawRectangleElement,
|
||||||
ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
|
@ -241,17 +240,6 @@ export const CONVERTIBLE_GENERIC_TYPES: readonly ConvertibleGenericTypes[] = [
|
||||||
"ellipse",
|
"ellipse",
|
||||||
];
|
];
|
||||||
|
|
||||||
const ELBOW_ARROW_SPECIFIC_PROPERTIES: Array<
|
|
||||||
keyof ExcalidrawElbowArrowElement
|
|
||||||
> = ["elbowed", "fixedSegments", "startIsSpecial", "endIsSpecial"];
|
|
||||||
|
|
||||||
const ARROW_TO_LINE_CLEAR_PROPERTIES: Array<keyof ExcalidrawArrowElement> = [
|
|
||||||
"startArrowhead",
|
|
||||||
"endArrowhead",
|
|
||||||
"startBinding",
|
|
||||||
"endBinding",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const CONVERTIBLE_LINEAR_TYPES: readonly ConvertibleLinearTypes[] = [
|
export const CONVERTIBLE_LINEAR_TYPES: readonly ConvertibleLinearTypes[] = [
|
||||||
"line",
|
"line",
|
||||||
"sharpArrow",
|
"sharpArrow",
|
||||||
|
@ -313,106 +301,82 @@ export const convertElementType = <
|
||||||
|
|
||||||
ShapeCache.delete(element);
|
ShapeCache.delete(element);
|
||||||
|
|
||||||
const update = () => {
|
|
||||||
(element as any).version++;
|
|
||||||
(element as any).versionNonce = randomInteger();
|
|
||||||
(element as any).updated = getUpdatedTimestamp();
|
|
||||||
|
|
||||||
if (informMutation) {
|
|
||||||
app.scene.triggerUpdate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isConvertibleGenericType(startType) &&
|
isConvertibleGenericType(startType) &&
|
||||||
isConvertibleGenericType(newType)
|
isConvertibleGenericType(newType)
|
||||||
) {
|
) {
|
||||||
(element as any).type = newType;
|
const nextElement = bumpVersion(
|
||||||
|
newElement({
|
||||||
if (newType === "diamond" && element.roundness) {
|
...element,
|
||||||
(element as any).roundness = {
|
type: newType,
|
||||||
|
roundness:
|
||||||
|
newType === "diamond" && element.roundness
|
||||||
|
? {
|
||||||
type: isUsingAdaptiveRadius(newType)
|
type: isUsingAdaptiveRadius(newType)
|
||||||
? ROUNDNESS.ADAPTIVE_RADIUS
|
? ROUNDNESS.ADAPTIVE_RADIUS
|
||||||
: ROUNDNESS.PROPORTIONAL_RADIUS,
|
: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
: element.roundness,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
update();
|
switch (nextElement.type) {
|
||||||
|
|
||||||
switch (element.type) {
|
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
return element as ExcalidrawRectangleElement;
|
return nextElement as ExcalidrawRectangleElement;
|
||||||
case "diamond":
|
case "diamond":
|
||||||
return element as ExcalidrawDiamondElement;
|
return nextElement as ExcalidrawDiamondElement;
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
return element as ExcalidrawEllipseElement;
|
return nextElement as ExcalidrawEllipseElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isConvertibleLinearType(element.type)) {
|
if (isConvertibleLinearType(element.type)) {
|
||||||
if (newType === "line") {
|
if (newType === "line") {
|
||||||
for (const key of ELBOW_ARROW_SPECIFIC_PROPERTIES) {
|
const nextElement = newLinearElement({
|
||||||
delete (element as any)[key];
|
...element,
|
||||||
}
|
type: "line",
|
||||||
for (const key of ARROW_TO_LINE_CLEAR_PROPERTIES) {
|
});
|
||||||
if (key in element) {
|
|
||||||
(element as any)[key] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(element as any).type = newType;
|
return bumpVersion(nextElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newType === "sharpArrow") {
|
if (newType === "sharpArrow") {
|
||||||
if (startType === "elbowArrow") {
|
const nextElement = newArrowElement({
|
||||||
// drop elbow arrow specific properties
|
...element,
|
||||||
for (const key of ELBOW_ARROW_SPECIFIC_PROPERTIES) {
|
type: "arrow",
|
||||||
delete (element as any)[key];
|
elbowed: false,
|
||||||
}
|
roundness: null,
|
||||||
}
|
startArrowhead: app.state.currentItemStartArrowhead,
|
||||||
|
endArrowhead: app.state.currentItemEndArrowhead,
|
||||||
|
});
|
||||||
|
|
||||||
(element as any).type = "arrow";
|
return bumpVersion(nextElement);
|
||||||
(element as any).elbowed = false;
|
|
||||||
(element as any).roundness = null;
|
|
||||||
(element as any).startArrowhead = app.state.currentItemStartArrowhead;
|
|
||||||
(element as any).endArrowhead = app.state.currentItemEndArrowhead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newType === "curvedArrow") {
|
if (newType === "curvedArrow") {
|
||||||
if (startType === "elbowArrow") {
|
const nextElement = newArrowElement({
|
||||||
// drop elbow arrow specific properties
|
...element,
|
||||||
for (const key of ELBOW_ARROW_SPECIFIC_PROPERTIES) {
|
type: "arrow",
|
||||||
delete (element as any)[key];
|
elbowed: false,
|
||||||
}
|
roundness: {
|
||||||
}
|
|
||||||
(element as any).type = "arrow";
|
|
||||||
(element as any).elbowed = false;
|
|
||||||
(element as any).roundness = {
|
|
||||||
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
};
|
},
|
||||||
(element as any).startArrowhead = app.state.currentItemStartArrowhead;
|
startArrowhead: app.state.currentItemStartArrowhead,
|
||||||
(element as any).endArrowhead = app.state.currentItemEndArrowhead;
|
endArrowhead: app.state.currentItemEndArrowhead,
|
||||||
|
});
|
||||||
|
|
||||||
|
return bumpVersion(nextElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newType === "elbowArrow") {
|
if (newType === "elbowArrow") {
|
||||||
(element as any).type = "arrow";
|
const nextElement = newArrowElement({
|
||||||
(element as any).elbowed = true;
|
...element,
|
||||||
(element as any).fixedSegments = null;
|
type: "arrow",
|
||||||
(element as any).startIsSpecial = null;
|
elbowed: true,
|
||||||
(element as any).endIsSpecial = null;
|
fixedSegments: null,
|
||||||
}
|
});
|
||||||
|
|
||||||
update();
|
return bumpVersion(nextElement);
|
||||||
|
|
||||||
switch (newType) {
|
|
||||||
case "line":
|
|
||||||
return element as ExcalidrawLinearElement;
|
|
||||||
case "sharpArrow":
|
|
||||||
return element as ExcalidrawArrowElement;
|
|
||||||
case "curvedArrow":
|
|
||||||
return element as ExcalidrawArrowElement;
|
|
||||||
case "elbowArrow":
|
|
||||||
return element as ExcalidrawElbowArrowElement;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,6 @@ import type {
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { mutateElement, sceneCoordsToViewportCoords } from "..";
|
import { mutateElement, sceneCoordsToViewportCoords } from "..";
|
||||||
import { getSelectedElements } from "../scene";
|
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { atom, editorJotaiStore, useAtom } from "../editor-jotai";
|
import { atom, editorJotaiStore, useAtom } from "../editor-jotai";
|
||||||
|
|
||||||
|
@ -104,7 +103,7 @@ const ShapeSwitch = ({ app }: { app: App }) => {
|
||||||
const [, setShapeSwitchLinear] = useAtom(shapeSwitchLinearAtom);
|
const [, setShapeSwitchLinear] = useAtom(shapeSwitchLinearAtom);
|
||||||
|
|
||||||
const selectedElements = useMemo(
|
const selectedElements = useMemo(
|
||||||
() => getSelectedElements(app.scene.getNonDeletedElementsMap(), app.state),
|
() => app.scene.getSelectedElements(app.state),
|
||||||
[app.scene, app.state],
|
[app.scene, app.state],
|
||||||
);
|
);
|
||||||
const selectedElementsTypeRef = useRef<"generic" | "linear">(null);
|
const selectedElementsTypeRef = useRef<"generic" | "linear">(null);
|
||||||
|
@ -394,10 +393,7 @@ export const switchShapes = (
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = app.scene.getSelectedElements(app.state);
|
||||||
app.scene.getNonDeletedElementsMap(),
|
|
||||||
app.state,
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedElementIds = selectedElements.reduce(
|
const selectedElementIds = selectedElements.reduce(
|
||||||
(acc, element) => ({ ...acc, [element.id]: true }),
|
(acc, element) => ({ ...acc, [element.id]: true }),
|
||||||
|
@ -428,9 +424,31 @@ export const switchShapes = (
|
||||||
];
|
];
|
||||||
|
|
||||||
if (nextType && isConvertibleGenericType(nextType)) {
|
if (nextType && isConvertibleGenericType(nextType)) {
|
||||||
for (const element of selectedGenericSwitchableElements) {
|
const convertedElements: Record<string, ExcalidrawElement> = {};
|
||||||
convertElementType(element, nextType, app, false);
|
|
||||||
|
|
||||||
|
for (const element of selectedGenericSwitchableElements) {
|
||||||
|
const convertedElement = convertElementType(
|
||||||
|
element,
|
||||||
|
nextType,
|
||||||
|
app,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
convertedElements[convertedElement.id] = convertedElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextElements = [];
|
||||||
|
|
||||||
|
for (const element of app.scene.getElementsIncludingDeleted()) {
|
||||||
|
if (convertedElements[element.id]) {
|
||||||
|
nextElements.push(convertedElements[element.id]);
|
||||||
|
} else {
|
||||||
|
nextElements.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.scene.replaceAllElements(nextElements);
|
||||||
|
|
||||||
|
for (const element of Object.values(convertedElements)) {
|
||||||
const boundText = getBoundTextElement(
|
const boundText = getBoundTextElement(
|
||||||
element,
|
element,
|
||||||
app.scene.getNonDeletedElementsMap(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
@ -489,9 +507,25 @@ export const switchShapes = (
|
||||||
];
|
];
|
||||||
|
|
||||||
if (nextType && isConvertibleLinearType(nextType)) {
|
if (nextType && isConvertibleLinearType(nextType)) {
|
||||||
|
const convertedElements: Record<string, ExcalidrawElement> = {};
|
||||||
for (const element of selectedLinearSwitchableElements) {
|
for (const element of selectedLinearSwitchableElements) {
|
||||||
convertElementType(element, nextType, app, false);
|
const converted = convertElementType(element, nextType, app, false);
|
||||||
|
convertedElements[converted.id] = converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextElements = [];
|
||||||
|
|
||||||
|
for (const element of app.scene.getElementsIncludingDeleted()) {
|
||||||
|
if (convertedElements[element.id]) {
|
||||||
|
nextElements.push(convertedElements[element.id]);
|
||||||
|
} else {
|
||||||
|
nextElements.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.scene.replaceAllElements(nextElements);
|
||||||
|
|
||||||
|
for (const element of Object.values(convertedElements)) {
|
||||||
const cachedLinear = editorJotaiStore.get(shapeSwitchLinearAtom)?.[
|
const cachedLinear = editorJotaiStore.get(shapeSwitchLinearAtom)?.[
|
||||||
element.id
|
element.id
|
||||||
];
|
];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue