simpler types, tidy up code

This commit is contained in:
Ryan Di 2025-04-30 20:04:03 +10:00
parent d890ed090c
commit 721f5ec0f4
4 changed files with 57 additions and 98 deletions

View file

@ -419,3 +419,4 @@ export type ConvertibleLinearTypes =
| "sharpArrow" | "sharpArrow"
| "curvedArrow" | "curvedArrow"
| "elbowArrow"; | "elbowArrow";
export type ConvertibleTypes = ConvertibleGenericTypes | ConvertibleLinearTypes;

View file

@ -1,7 +1,7 @@
import type { ExcalidrawElement } from "@excalidraw/element/types"; import type { ExcalidrawElement } from "@excalidraw/element/types";
import { import {
getSwitchableTypeFromElements, getSwitchCategoryFromElements,
shapeSwitchAtom, shapeSwitchAtom,
} from "../components/ShapeSwitch"; } from "../components/ShapeSwitch";
import { editorJotaiStore } from "../editor-jotai"; import { editorJotaiStore } from "../editor-jotai";
@ -31,11 +31,6 @@ export const actionToggleShapeSwitch = register({
}; };
}, },
checked: (appState) => appState.gridModeEnabled, checked: (appState) => appState.gridModeEnabled,
predicate: (elements, appState, props) => { predicate: (elements, appState, props) =>
const { generic, linear } = getSwitchableTypeFromElements( getSwitchCategoryFromElements(elements as ExcalidrawElement[]) !== null,
elements as ExcalidrawElement[],
);
return generic || linear;
},
}); });

View file

@ -468,7 +468,7 @@ import { LassoTrail } from "../lasso";
import { EraserTrail } from "../eraser"; import { EraserTrail } from "../eraser";
import ShapeSwitch, { import ShapeSwitch, {
getSwitchableTypeFromElements, getSwitchCategoryFromElements,
SHAPE_SWITCH_PANEL_CLASSNAME, SHAPE_SWITCH_PANEL_CLASSNAME,
shapeSwitchAtom, shapeSwitchAtom,
switchShapes, switchShapes,
@ -4179,21 +4179,20 @@ class App extends React.Component<AppProps, AppState> {
) { ) {
event.preventDefault(); event.preventDefault();
const { generic, linear } = const switchCategory =
getSwitchableTypeFromElements(selectedElements); getSwitchCategoryFromElements(selectedElements);
if (editorJotaiStore.get(shapeSwitchAtom)?.type === "panel") { if (editorJotaiStore.get(shapeSwitchAtom)?.type === "panel") {
if ( if (
switchShapes(this, { switchShapes(this, {
generic, switchCategory,
linear,
direction: event.shiftKey ? "left" : "right", direction: event.shiftKey ? "left" : "right",
}) })
) { ) {
this.store.shouldCaptureIncrement(); this.store.shouldCaptureIncrement();
} }
} }
if (generic || linear) { if (switchCategory) {
editorJotaiStore.set(shapeSwitchAtom, { editorJotaiStore.set(shapeSwitchAtom, {
type: "panel", type: "panel",
}); });

View file

@ -5,6 +5,7 @@ import { updateElbowArrowPoints } from "@excalidraw/element/elbowArrow";
import { pointFrom, pointRotateRads, type LocalPoint } from "@excalidraw/math"; import { pointFrom, pointRotateRads, type LocalPoint } from "@excalidraw/math";
import { import {
isArrowBoundToElement,
isArrowElement, isArrowElement,
isCurvedArrow, isCurvedArrow,
isElbowArrow, isElbowArrow,
@ -44,6 +45,7 @@ import { ShapeCache } from "@excalidraw/element/ShapeCache";
import type { import type {
ConvertibleGenericTypes, ConvertibleGenericTypes,
ConvertibleLinearTypes, ConvertibleLinearTypes,
ConvertibleTypes,
ExcalidrawArrowElement, ExcalidrawArrowElement,
ExcalidrawDiamondElement, ExcalidrawDiamondElement,
ExcalidrawElbowArrowElement, ExcalidrawElbowArrowElement,
@ -117,22 +119,21 @@ const ShapeSwitch = ({ app }: { app: App }) => {
() => app.scene.getSelectedElements(app.state), () => app.scene.getSelectedElements(app.state),
[app.scene, app.state], [app.scene, app.state],
); );
const selectedElementsTypeRef = useRef<"generic" | "linear">(null); const elementsCategoryRef = useRef<SwitchShapeCategory>(null);
// close shape switch panel if selecting different "types" of elements // close shape switch panel if selecting different "types" of elements
useEffect(() => { useEffect(() => {
const { generic, linear } = getSwitchableTypeFromElements(selectedElements); const switchCategory = getSwitchCategoryFromElements(selectedElements);
const type = generic ? "generic" : linear ? "linear" : null;
if (type && !selectedElementsTypeRef.current) { if (switchCategory && !elementsCategoryRef.current) {
selectedElementsTypeRef.current = type; elementsCategoryRef.current = switchCategory;
} else if ( } else if (
(selectedElementsTypeRef.current && !type) || (elementsCategoryRef.current && !switchCategory) ||
(selectedElementsTypeRef.current && (elementsCategoryRef.current &&
type !== selectedElementsTypeRef.current) switchCategory !== elementsCategoryRef.current)
) { ) {
setShapeSwitch(null); setShapeSwitch(null);
selectedElementsTypeRef.current = null; elementsCategoryRef.current = null;
} }
}, [selectedElements, app.state.selectedElementIds, setShapeSwitch]); }, [selectedElements, app.state.selectedElementIds, setShapeSwitch]);
@ -158,7 +159,9 @@ const Panel = ({
app: App; app: App;
elements: ExcalidrawElement[]; elements: ExcalidrawElement[];
}) => { }) => {
const { generic, linear } = getSwitchableTypeFromElements(elements); const switchCategory = getSwitchCategoryFromElements(elements);
const generic = switchCategory === "generic";
const linear = switchCategory === "linear";
const genericElements = useMemo(() => { const genericElements = useMemo(() => {
return generic ? getGenericSwitchableElements(elements) : []; return generic ? getGenericSwitchableElements(elements) : [];
@ -325,8 +328,7 @@ const Panel = ({
trackEvent("shape-switch", type, "ui"); trackEvent("shape-switch", type, "ui");
} }
switchShapes(app, { switchShapes(app, {
generic, switchCategory,
linear,
nextType: type as nextType: type as
| ConvertibleGenericTypes | ConvertibleGenericTypes
| ConvertibleLinearTypes, | ConvertibleLinearTypes,
@ -386,21 +388,21 @@ export const adjustBoundTextSize = (
redrawTextBoundingBox(boundText, container, scene); redrawTextBoundingBox(boundText, container, scene);
}; };
type SwitchShapeCategory = "generic" | "linear" | null;
export const switchShapes = ( export const switchShapes = (
app: App, app: App,
{ {
generic, switchCategory,
linear,
nextType, nextType,
direction = "right", direction = "right",
}: { }: {
generic?: boolean; switchCategory: SwitchShapeCategory;
linear?: boolean; nextType?: ConvertibleTypes;
nextType?: ConvertibleGenericTypes | ConvertibleLinearTypes;
direction?: "left" | "right"; direction?: "left" | "right";
} = {}, },
): boolean => { ): boolean => {
if (!generic && !linear) { if (!switchCategory) {
return false; return false;
} }
@ -413,7 +415,7 @@ export const switchShapes = (
const advancement = direction === "right" ? 1 : -1; const advancement = direction === "right" ? 1 : -1;
if (generic) { if (switchCategory === "generic") {
const selectedGenericSwitchableElements = const selectedGenericSwitchableElements =
getGenericSwitchableElements(selectedElements); getGenericSwitchableElements(selectedElements);
@ -490,7 +492,7 @@ export const switchShapes = (
} }
} }
if (linear) { if (switchCategory === "linear") {
const selectedLinearSwitchableElements = getLinearSwitchableElements( const selectedLinearSwitchableElements = getLinearSwitchableElements(
selectedElements, selectedElements,
) as ExcalidrawLinearElement[]; ) as ExcalidrawLinearElement[];
@ -634,77 +636,47 @@ export const switchShapes = (
return true; return true;
}; };
export const getSwitchableTypeFromElements = ( export const getSwitchCategoryFromElements = (
elements: ExcalidrawElement[], elements: ExcalidrawElement[],
): ): SwitchShapeCategory => {
| {
generic: true;
linear: false;
}
| {
linear: true;
generic: false;
}
| {
generic: false;
linear: false;
} => {
if (elements.length === 0) { if (elements.length === 0) {
return { return null;
generic: false,
linear: false,
};
} }
let onlyLinear = true; let onlyLinear = true;
let canBeLinear = false;
for (const element of elements) { for (const element of elements) {
if ( if (
element.type === "rectangle" || element.type === "rectangle" ||
element.type === "ellipse" || element.type === "ellipse" ||
element.type === "diamond" element.type === "diamond"
) { ) {
return { return "generic";
generic: true,
linear: false,
};
} }
if (element.type !== "arrow" && element.type !== "line") { if (element.type !== "arrow" && element.type !== "line") {
onlyLinear = false; onlyLinear = false;
} canBeLinear = false;
} }
if (onlyLinear) { if (onlyLinear) {
// check at least some linear element is switchable if (isLinearElement(element) && isLinearElementElligible(element)) {
// for a linear to be swtichable: canBeLinear = true;
// - no labels }
// - not bound to anything
let linear = true;
for (const element of elements) {
if (
isArrowElement(element) &&
(element.startBinding !== null || element.endBinding !== null)
) {
linear = false;
} else if (element.boundElements && element.boundElements.length > 0) {
linear = false;
} else {
linear = true;
break;
} }
} }
return { if (canBeLinear) {
linear, return "linear";
generic: false, }
return null;
}; };
}
return { const isLinearElementElligible = (linear: ExcalidrawLinearElement) => {
generic: false, return (
linear: false, !(isArrowElement(linear) && isArrowBoundToElement(linear)) &&
}; !(linear.boundElements && linear.boundElements.length > 0)
);
}; };
const getArrowType = (element: ExcalidrawLinearElement) => { const getArrowType = (element: ExcalidrawLinearElement) => {
@ -795,13 +767,7 @@ const getGenericSwitchableElements = (elements: ExcalidrawElement[]) =>
const getLinearSwitchableElements = (elements: ExcalidrawElement[]) => const getLinearSwitchableElements = (elements: ExcalidrawElement[]) =>
elements.filter( elements.filter(
(element) => (element) => isLinearElement(element) && isLinearElementElligible(element),
isLinearElement(element) &&
!(
isArrowElement(element) &&
(element.startBinding !== null || element.endBinding !== null)
) &&
(!element.boundElements || element.boundElements.length === 0),
) as ExcalidrawLinearElement[]; ) as ExcalidrawLinearElement[];
const THRESHOLD = 20; const THRESHOLD = 20;
@ -935,8 +901,6 @@ const CONVERTIBLE_LINEAR_TYPES: readonly ConvertibleLinearTypes[] = [
"elbowArrow", "elbowArrow",
]; ];
type ConvertibleType = ConvertibleGenericTypes | ConvertibleLinearTypes;
const isConvertibleGenericType = ( const isConvertibleGenericType = (
elementType: string, elementType: string,
): elementType is ConvertibleGenericTypes => ): elementType is ConvertibleGenericTypes =>
@ -962,7 +926,7 @@ export const convertElementType = <
TElement extends Exclude<ExcalidrawElement, ExcalidrawSelectionElement>, TElement extends Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
>( >(
element: TElement, element: TElement,
newType: ConvertibleType, newType: ConvertibleTypes,
app: AppClassProperties, app: AppClassProperties,
): ExcalidrawElement => { ): ExcalidrawElement => {
if (!isValidConversion(element.type, newType)) { if (!isValidConversion(element.type, newType)) {
@ -1071,8 +1035,8 @@ export const convertElementType = <
const isValidConversion = ( const isValidConversion = (
startType: string, startType: string,
targetType: ConvertibleType, targetType: ConvertibleTypes,
): startType is ConvertibleType => { ): startType is ConvertibleTypes => {
if ( if (
isConvertibleGenericType(startType) && isConvertibleGenericType(startType) &&
isConvertibleGenericType(targetType) isConvertibleGenericType(targetType)