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,
|
||||
} from "./typeChecks";
|
||||
|
||||
import { newArrowElement, newElement, newLinearElement } from "./newElement";
|
||||
|
||||
import type {
|
||||
ConvertibleGenericTypes,
|
||||
ConvertibleLinearTypes,
|
||||
ExcalidrawArrowElement,
|
||||
ExcalidrawDiamondElement,
|
||||
ExcalidrawElbowArrowElement,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawEllipseElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawRectangleElement,
|
||||
ExcalidrawSelectionElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
|
@ -241,17 +240,6 @@ export const CONVERTIBLE_GENERIC_TYPES: readonly ConvertibleGenericTypes[] = [
|
|||
"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[] = [
|
||||
"line",
|
||||
"sharpArrow",
|
||||
|
@ -313,106 +301,82 @@ export const convertElementType = <
|
|||
|
||||
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 (
|
||||
isConvertibleGenericType(startType) &&
|
||||
isConvertibleGenericType(newType)
|
||||
) {
|
||||
(element as any).type = newType;
|
||||
|
||||
if (newType === "diamond" && element.roundness) {
|
||||
(element as any).roundness = {
|
||||
const nextElement = bumpVersion(
|
||||
newElement({
|
||||
...element,
|
||||
type: newType,
|
||||
roundness:
|
||||
newType === "diamond" && element.roundness
|
||||
? {
|
||||
type: isUsingAdaptiveRadius(newType)
|
||||
? ROUNDNESS.ADAPTIVE_RADIUS
|
||||
: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||
};
|
||||
}
|
||||
: element.roundness,
|
||||
}),
|
||||
);
|
||||
|
||||
update();
|
||||
|
||||
switch (element.type) {
|
||||
switch (nextElement.type) {
|
||||
case "rectangle":
|
||||
return element as ExcalidrawRectangleElement;
|
||||
return nextElement as ExcalidrawRectangleElement;
|
||||
case "diamond":
|
||||
return element as ExcalidrawDiamondElement;
|
||||
return nextElement as ExcalidrawDiamondElement;
|
||||
case "ellipse":
|
||||
return element as ExcalidrawEllipseElement;
|
||||
return nextElement as ExcalidrawEllipseElement;
|
||||
}
|
||||
}
|
||||
|
||||
if (isConvertibleLinearType(element.type)) {
|
||||
if (newType === "line") {
|
||||
for (const key of ELBOW_ARROW_SPECIFIC_PROPERTIES) {
|
||||
delete (element as any)[key];
|
||||
}
|
||||
for (const key of ARROW_TO_LINE_CLEAR_PROPERTIES) {
|
||||
if (key in element) {
|
||||
(element as any)[key] = null;
|
||||
}
|
||||
}
|
||||
const nextElement = newLinearElement({
|
||||
...element,
|
||||
type: "line",
|
||||
});
|
||||
|
||||
(element as any).type = newType;
|
||||
return bumpVersion(nextElement);
|
||||
}
|
||||
|
||||
if (newType === "sharpArrow") {
|
||||
if (startType === "elbowArrow") {
|
||||
// drop elbow arrow specific properties
|
||||
for (const key of ELBOW_ARROW_SPECIFIC_PROPERTIES) {
|
||||
delete (element as any)[key];
|
||||
}
|
||||
}
|
||||
const nextElement = newArrowElement({
|
||||
...element,
|
||||
type: "arrow",
|
||||
elbowed: false,
|
||||
roundness: null,
|
||||
startArrowhead: app.state.currentItemStartArrowhead,
|
||||
endArrowhead: app.state.currentItemEndArrowhead,
|
||||
});
|
||||
|
||||
(element as any).type = "arrow";
|
||||
(element as any).elbowed = false;
|
||||
(element as any).roundness = null;
|
||||
(element as any).startArrowhead = app.state.currentItemStartArrowhead;
|
||||
(element as any).endArrowhead = app.state.currentItemEndArrowhead;
|
||||
return bumpVersion(nextElement);
|
||||
}
|
||||
|
||||
if (newType === "curvedArrow") {
|
||||
if (startType === "elbowArrow") {
|
||||
// drop elbow arrow specific properties
|
||||
for (const key of ELBOW_ARROW_SPECIFIC_PROPERTIES) {
|
||||
delete (element as any)[key];
|
||||
}
|
||||
}
|
||||
(element as any).type = "arrow";
|
||||
(element as any).elbowed = false;
|
||||
(element as any).roundness = {
|
||||
const nextElement = newArrowElement({
|
||||
...element,
|
||||
type: "arrow",
|
||||
elbowed: false,
|
||||
roundness: {
|
||||
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||
};
|
||||
(element as any).startArrowhead = app.state.currentItemStartArrowhead;
|
||||
(element as any).endArrowhead = app.state.currentItemEndArrowhead;
|
||||
},
|
||||
startArrowhead: app.state.currentItemStartArrowhead,
|
||||
endArrowhead: app.state.currentItemEndArrowhead,
|
||||
});
|
||||
|
||||
return bumpVersion(nextElement);
|
||||
}
|
||||
|
||||
if (newType === "elbowArrow") {
|
||||
(element as any).type = "arrow";
|
||||
(element as any).elbowed = true;
|
||||
(element as any).fixedSegments = null;
|
||||
(element as any).startIsSpecial = null;
|
||||
(element as any).endIsSpecial = null;
|
||||
}
|
||||
const nextElement = newArrowElement({
|
||||
...element,
|
||||
type: "arrow",
|
||||
elbowed: true,
|
||||
fixedSegments: null,
|
||||
});
|
||||
|
||||
update();
|
||||
|
||||
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;
|
||||
return bumpVersion(nextElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ import type {
|
|||
} from "@excalidraw/element/types";
|
||||
|
||||
import { mutateElement, sceneCoordsToViewportCoords } from "..";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { atom, editorJotaiStore, useAtom } from "../editor-jotai";
|
||||
|
||||
|
@ -104,7 +103,7 @@ const ShapeSwitch = ({ app }: { app: App }) => {
|
|||
const [, setShapeSwitchLinear] = useAtom(shapeSwitchLinearAtom);
|
||||
|
||||
const selectedElements = useMemo(
|
||||
() => getSelectedElements(app.scene.getNonDeletedElementsMap(), app.state),
|
||||
() => app.scene.getSelectedElements(app.state),
|
||||
[app.scene, app.state],
|
||||
);
|
||||
const selectedElementsTypeRef = useRef<"generic" | "linear">(null);
|
||||
|
@ -394,10 +393,7 @@ export const switchShapes = (
|
|||
return false;
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
app.state,
|
||||
);
|
||||
const selectedElements = app.scene.getSelectedElements(app.state);
|
||||
|
||||
const selectedElementIds = selectedElements.reduce(
|
||||
(acc, element) => ({ ...acc, [element.id]: true }),
|
||||
|
@ -428,9 +424,31 @@ export const switchShapes = (
|
|||
];
|
||||
|
||||
if (nextType && isConvertibleGenericType(nextType)) {
|
||||
for (const element of selectedGenericSwitchableElements) {
|
||||
convertElementType(element, nextType, app, false);
|
||||
const convertedElements: Record<string, ExcalidrawElement> = {};
|
||||
|
||||
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(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
|
@ -489,9 +507,25 @@ export const switchShapes = (
|
|||
];
|
||||
|
||||
if (nextType && isConvertibleLinearType(nextType)) {
|
||||
const convertedElements: Record<string, ExcalidrawElement> = {};
|
||||
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)?.[
|
||||
element.id
|
||||
];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue