cache linear when panel shows up

This commit is contained in:
Ryan Di 2025-04-23 21:01:09 +10:00
parent 7541fadf9c
commit de6acc4bad
2 changed files with 146 additions and 19 deletions

View file

@ -326,7 +326,6 @@ import type {
MagicGenerationData,
ExcalidrawNonSelectionElement,
ExcalidrawArrowElement,
ConvertibleGenericTypes,
} from "@excalidraw/element/types";
import type { ValueOf } from "@excalidraw/common/utility-types";
@ -465,7 +464,6 @@ import { isMaybeMermaidDefinition } from "../mermaid";
import ShapeSwitch, {
getSwitchableTypeFromElements,
shapeSwitchAtom,
shapeSwitchFontSizeAtom,
switchShapes,
} from "./ShapeSwitch";
@ -4182,23 +4180,6 @@ class App extends React.Component<AppProps, AppState> {
editorJotaiStore.set(shapeSwitchAtom, {
type: "panel",
});
if (!editorJotaiStore.get(shapeSwitchFontSizeAtom)) {
selectedElements.forEach((element) => {
const boundText = getBoundTextElement(
element,
this.scene.getNonDeletedElementsMap(),
);
if (boundText && generic && element) {
editorJotaiStore.set(shapeSwitchFontSizeAtom, {
...editorJotaiStore.get(shapeSwitchFontSizeAtom),
[element.id]: {
fontSize: boundText.fontSize,
elementType: element.type as ConvertibleGenericTypes,
},
});
}
});
}
}
}
@ -6470,6 +6451,10 @@ class App extends React.Component<AppProps, AppState> {
editorJotaiStore.set(searchItemInFocusAtom, null);
}
if (editorJotaiStore.get(shapeSwitchAtom)) {
editorJotaiStore.set(shapeSwitchAtom, null);
}
// since contextMenu options are potentially evaluated on each render,
// and an contextMenu action may depend on selection state, we must
// close the contextMenu before we update the selection on pointerDown

View file

@ -44,7 +44,9 @@ import type {
ConvertibleGenericTypes,
ConvertibleLinearTypes,
ElementsMap,
ExcalidrawArrowElement,
ExcalidrawDiamondElement,
ExcalidrawElbowArrowElement,
ExcalidrawElement,
ExcalidrawEllipseElement,
ExcalidrawLinearElement,
@ -87,9 +89,19 @@ export const shapeSwitchFontSizeAtom = atom<{
};
} | null>(null);
export const shapeSwitchLinearAtom = atom<{
[id: string]: {
properties:
| Partial<ExcalidrawLinearElement>
| Partial<ExcalidrawElbowArrowElement>;
initialType: ConvertibleLinearTypes;
};
} | null>(null);
const ShapeSwitch = ({ app }: { app: App }) => {
const [shapeSwitch, setShapeSwitch] = useAtom(shapeSwitchAtom);
const [, setShapeSwitchFontSize] = useAtom(shapeSwitchFontSizeAtom);
const [, setShapeSwitchLinear] = useAtom(shapeSwitchLinearAtom);
const selectedElements = useMemo(
() => getSelectedElements(app.scene.getNonDeletedElementsMap(), app.state),
@ -117,6 +129,7 @@ const ShapeSwitch = ({ app }: { app: App }) => {
// clear if not active
if (!shapeSwitch) {
setShapeSwitchFontSize(null);
setShapeSwitchLinear(null);
return null;
}
@ -197,6 +210,56 @@ const Panel = ({
setPanelPosition({ x, y });
}, [genericElements, linearElements, app.scene, app.state]);
useEffect(() => {
if (editorJotaiStore.get(shapeSwitchLinearAtom)) {
return;
}
for (const linearElement of linearElements) {
const initialType = getArrowType(linearElement);
const cachedProperties =
initialType === "line"
? getLineProperties(linearElement)
: initialType === "sharpArrow"
? getSharpArrowProperties(linearElement)
: initialType === "curvedArrow"
? getCurvedArrowProperties(linearElement)
: initialType === "elbowArrow"
? getElbowArrowProperties(linearElement)
: {};
editorJotaiStore.set(shapeSwitchLinearAtom, {
...editorJotaiStore.get(shapeSwitchLinearAtom),
[linearElement.id]: {
properties: cachedProperties,
initialType,
},
});
}
}, [linearElements]);
useEffect(() => {
if (editorJotaiStore.get(shapeSwitchFontSizeAtom)) {
return;
}
for (const element of genericElements) {
const boundText = getBoundTextElement(
element,
app.scene.getNonDeletedElementsMap(),
);
if (boundText) {
editorJotaiStore.set(shapeSwitchFontSizeAtom, {
...editorJotaiStore.get(shapeSwitchFontSizeAtom),
[element.id]: {
fontSize: boundText.fontSize,
elementType: element.type as ConvertibleGenericTypes,
},
});
}
}
}, [genericElements, app.scene]);
const SHAPES: [string, ReactNode][] = linear
? [
["line", LineIcon],
@ -429,6 +492,19 @@ export const switchShapes = (
for (const element of selectedLinearSwitchableElements) {
convertElementType(element, nextType, app, false);
const cachedLinear = editorJotaiStore.get(shapeSwitchLinearAtom)?.[
element.id
];
if (cachedLinear) {
const { properties, initialType } = cachedLinear;
if (initialType === nextType) {
mutateElement(element, properties, false);
continue;
}
}
if (isElbowArrow(element)) {
const nextPoints = convertLineToElbow(element);
@ -557,6 +633,72 @@ const getArrowType = (element: ExcalidrawLinearElement) => {
return "line";
};
const getLineProperties = (
element: ExcalidrawLinearElement,
): Partial<ExcalidrawLinearElement> => {
if (element.type === "line") {
return {
points: element.points,
roundness: element.roundness,
};
}
return {};
};
const getSharpArrowProperties = (
element: ExcalidrawLinearElement,
): Partial<ExcalidrawArrowElement> => {
if (isSharpArrow(element)) {
return {
points: element.points,
startArrowhead: element.startArrowhead,
endArrowhead: element.endArrowhead,
startBinding: element.startBinding,
endBinding: element.endBinding,
roundness: null,
};
}
return {};
};
const getCurvedArrowProperties = (
element: ExcalidrawLinearElement,
): Partial<ExcalidrawArrowElement> => {
if (isCurvedArrow(element)) {
return {
points: element.points,
startArrowhead: element.startArrowhead,
endArrowhead: element.endArrowhead,
startBinding: element.startBinding,
endBinding: element.endBinding,
roundness: element.roundness,
};
}
return {};
};
const getElbowArrowProperties = (
element: ExcalidrawLinearElement,
): Partial<ExcalidrawElbowArrowElement> => {
if (isElbowArrow(element)) {
return {
points: element.points,
startArrowhead: element.startArrowhead,
endArrowhead: element.endArrowhead,
startBinding: element.startBinding,
endBinding: element.endBinding,
roundness: element.roundness,
fixedSegments: element.fixedSegments,
startIsSpecial: element.startIsSpecial,
endIsSpecial: element.endIsSpecial,
};
}
return {};
};
const getGenericSwitchableElements = (elements: ExcalidrawElement[]) =>
elements.filter((element) => isConvertibleGenericType(element.type)) as Array<
| ExcalidrawRectangleElement