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, MagicGenerationData,
ExcalidrawNonSelectionElement, ExcalidrawNonSelectionElement,
ExcalidrawArrowElement, ExcalidrawArrowElement,
ConvertibleGenericTypes,
} from "@excalidraw/element/types"; } from "@excalidraw/element/types";
import type { ValueOf } from "@excalidraw/common/utility-types"; import type { ValueOf } from "@excalidraw/common/utility-types";
@ -465,7 +464,6 @@ import { isMaybeMermaidDefinition } from "../mermaid";
import ShapeSwitch, { import ShapeSwitch, {
getSwitchableTypeFromElements, getSwitchableTypeFromElements,
shapeSwitchAtom, shapeSwitchAtom,
shapeSwitchFontSizeAtom,
switchShapes, switchShapes,
} from "./ShapeSwitch"; } from "./ShapeSwitch";
@ -4182,23 +4180,6 @@ class App extends React.Component<AppProps, AppState> {
editorJotaiStore.set(shapeSwitchAtom, { editorJotaiStore.set(shapeSwitchAtom, {
type: "panel", 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); editorJotaiStore.set(searchItemInFocusAtom, null);
} }
if (editorJotaiStore.get(shapeSwitchAtom)) {
editorJotaiStore.set(shapeSwitchAtom, null);
}
// since contextMenu options are potentially evaluated on each render, // since contextMenu options are potentially evaluated on each render,
// and an contextMenu action may depend on selection state, we must // and an contextMenu action may depend on selection state, we must
// close the contextMenu before we update the selection on pointerDown // close the contextMenu before we update the selection on pointerDown

View file

@ -44,7 +44,9 @@ import type {
ConvertibleGenericTypes, ConvertibleGenericTypes,
ConvertibleLinearTypes, ConvertibleLinearTypes,
ElementsMap, ElementsMap,
ExcalidrawArrowElement,
ExcalidrawDiamondElement, ExcalidrawDiamondElement,
ExcalidrawElbowArrowElement,
ExcalidrawElement, ExcalidrawElement,
ExcalidrawEllipseElement, ExcalidrawEllipseElement,
ExcalidrawLinearElement, ExcalidrawLinearElement,
@ -87,9 +89,19 @@ export const shapeSwitchFontSizeAtom = atom<{
}; };
} | null>(null); } | null>(null);
export const shapeSwitchLinearAtom = atom<{
[id: string]: {
properties:
| Partial<ExcalidrawLinearElement>
| Partial<ExcalidrawElbowArrowElement>;
initialType: ConvertibleLinearTypes;
};
} | null>(null);
const ShapeSwitch = ({ app }: { app: App }) => { const ShapeSwitch = ({ app }: { app: App }) => {
const [shapeSwitch, setShapeSwitch] = useAtom(shapeSwitchAtom); const [shapeSwitch, setShapeSwitch] = useAtom(shapeSwitchAtom);
const [, setShapeSwitchFontSize] = useAtom(shapeSwitchFontSizeAtom); const [, setShapeSwitchFontSize] = useAtom(shapeSwitchFontSizeAtom);
const [, setShapeSwitchLinear] = useAtom(shapeSwitchLinearAtom);
const selectedElements = useMemo( const selectedElements = useMemo(
() => getSelectedElements(app.scene.getNonDeletedElementsMap(), app.state), () => getSelectedElements(app.scene.getNonDeletedElementsMap(), app.state),
@ -117,6 +129,7 @@ const ShapeSwitch = ({ app }: { app: App }) => {
// clear if not active // clear if not active
if (!shapeSwitch) { if (!shapeSwitch) {
setShapeSwitchFontSize(null); setShapeSwitchFontSize(null);
setShapeSwitchLinear(null);
return null; return null;
} }
@ -197,6 +210,56 @@ const Panel = ({
setPanelPosition({ x, y }); setPanelPosition({ x, y });
}, [genericElements, linearElements, app.scene, app.state]); }, [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 const SHAPES: [string, ReactNode][] = linear
? [ ? [
["line", LineIcon], ["line", LineIcon],
@ -429,6 +492,19 @@ export const switchShapes = (
for (const element of selectedLinearSwitchableElements) { for (const element of selectedLinearSwitchableElements) {
convertElementType(element, nextType, app, false); 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)) { if (isElbowArrow(element)) {
const nextPoints = convertLineToElbow(element); const nextPoints = convertLineToElbow(element);
@ -557,6 +633,72 @@ const getArrowType = (element: ExcalidrawLinearElement) => {
return "line"; 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[]) => const getGenericSwitchableElements = (elements: ExcalidrawElement[]) =>
elements.filter((element) => isConvertibleGenericType(element.type)) as Array< elements.filter((element) => isConvertibleGenericType(element.type)) as Array<
| ExcalidrawRectangleElement | ExcalidrawRectangleElement