feat: switch between basic shapes (#9270)
Some checks failed
Auto release excalidraw next / Auto-release-excalidraw-next (push) Failing after 47s
Build Docker image / build-docker (push) Successful in 2m4s
Cancel previous runs / cancel (push) Failing after 3s
Publish Docker / publish-docker (push) Failing after 1m42s
New Sentry production release / sentry (push) Failing after 12s

* feat: switch between basic shapes

* add tab for testing

* style tweaks

* only show hint when a new node is created

* fix panel state

* refactor

* combine captures into one

* keep original font size

* switch multi

* switch different types altogether

* use tab only

* fix font size atom

* do not switch from active tool change

* prefer generic when mixed

* provide an optional direction when shape switching

* adjust panel bg & shadow

* redraw to correctly position text

* remove redundant code

* only tab to switch if focusing on app container

* limit which linear elements can be switched

* add shape switch to command palette

* remove hint

* cache initial panel position

* bend line to elbow if needed

* remove debug logic

* clean switch of arrows using app state

* safe conversion between line, sharp, curved, and elbow

* cache linear when panel shows up

* type safe element conversion

* rename type

* respect initial type when switching between linears

* fix elbow segment indexing

* use latest linear

* merge converted elbow points if too close

* focus on panel after click

* set roudness to null to fix drag points offset for elbows

* remove Mutable

* add arrowBoundToElement check

* make it dependent on one signle state

* unmount when not showing

* simpler types, tidy up code

* can change linear when it's linear + non-generic

* fix popup component lifecycle

* move constant to CLASSES

* DRY out type detection

* file & variable renaming

* refactor

* throw in not-prod instead

* simplify

* semi-fix bindings on `generic` type conversion

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di 2025-05-01 02:07:31 +10:00 committed by GitHub
parent 4a60fe3d22
commit 195a743874
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1258 additions and 47 deletions

View file

@ -81,7 +81,6 @@ import type {
NonDeletedSceneElementsMap,
ExcalidrawTextElement,
ExcalidrawArrowElement,
OrderedExcalidrawElement,
ExcalidrawElbowArrowElement,
FixedPoint,
FixedPointBinding,
@ -710,29 +709,32 @@ const calculateFocusAndGap = (
// Supports translating, rotating and scaling `changedElement` with bound
// linear elements.
// Because scaling involves moving the focus points as well, it is
// done before the `changedElement` is updated, and the `newSize` is passed
// in explicitly.
export const updateBoundElements = (
changedElement: NonDeletedExcalidrawElement,
scene: Scene,
options?: {
simultaneouslyUpdated?: readonly ExcalidrawElement[];
newSize?: { width: number; height: number };
changedElements?: Map<string, OrderedExcalidrawElement>;
changedElements?: Map<string, ExcalidrawElement>;
},
) => {
if (!isBindableElement(changedElement)) {
return;
}
const { newSize, simultaneouslyUpdated } = options ?? {};
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
simultaneouslyUpdated,
);
if (!isBindableElement(changedElement)) {
return;
let elementsMap: ElementsMap = scene.getNonDeletedElementsMap();
if (options?.changedElements) {
elementsMap = new Map(elementsMap) as typeof elementsMap;
options.changedElements.forEach((element) => {
elementsMap.set(element.id, element);
});
}
const elementsMap = scene.getNonDeletedElementsMap();
boundElementsVisitor(elementsMap, changedElement, (element) => {
if (!isLinearElement(element) || element.isDeleted) {
return;
@ -836,6 +838,25 @@ export const updateBoundElements = (
});
};
export const updateBindings = (
latestElement: ExcalidrawElement,
scene: Scene,
options?: {
simultaneouslyUpdated?: readonly ExcalidrawElement[];
newSize?: { width: number; height: number };
zoom?: AppState["zoom"];
},
) => {
if (isLinearElement(latestElement)) {
bindOrUnbindLinearElements([latestElement], true, [], scene, options?.zoom);
} else {
updateBoundElements(latestElement, scene, {
...options,
changedElements: new Map([[latestElement.id, latestElement]]),
});
}
};
const doesNeedUpdate = (
boundElement: NonDeleted<ExcalidrawLinearElement>,
changedElement: ExcalidrawBindableElement,

View file

@ -44,7 +44,6 @@ import type {
ExcalidrawIframeElement,
ElementsMap,
ExcalidrawArrowElement,
FixedSegment,
ExcalidrawElbowArrowElement,
} from "./types";
@ -478,7 +477,7 @@ export const newArrowElement = <T extends boolean>(
endArrowhead?: Arrowhead | null;
points?: ExcalidrawArrowElement["points"];
elbowed?: T;
fixedSegments?: FixedSegment[] | null;
fixedSegments?: ExcalidrawElbowArrowElement["fixedSegments"] | null;
} & ElementConstructorOpts,
): T extends true
? NonDeleted<ExcalidrawElbowArrowElement>

View file

@ -119,6 +119,20 @@ export const isElbowArrow = (
return isArrowElement(element) && element.elbowed;
};
export const isSharpArrow = (
element?: ExcalidrawElement,
): element is ExcalidrawArrowElement => {
return isArrowElement(element) && !element.elbowed && !element.roundness;
};
export const isCurvedArrow = (
element?: ExcalidrawElement,
): element is ExcalidrawArrowElement => {
return (
isArrowElement(element) && !element.elbowed && element.roundness !== null
);
};
export const isLinearElementType = (
elementType: ElementOrToolType,
): boolean => {
@ -271,6 +285,10 @@ export const isBoundToContainer = (
);
};
export const isArrowBoundToElement = (element: ExcalidrawArrowElement) => {
return !!element.startBinding || !!element.endBinding;
};
export const isUsingAdaptiveRadius = (type: string) =>
type === "rectangle" ||
type === "embeddable" ||

View file

@ -412,3 +412,11 @@ export type NonDeletedSceneElementsMap = Map<
export type ElementsMapOrArray =
| readonly ExcalidrawElement[]
| Readonly<ElementsMap>;
export type ConvertibleGenericTypes = "rectangle" | "diamond" | "ellipse";
export type ConvertibleLinearTypes =
| "line"
| "sharpArrow"
| "curvedArrow"
| "elbowArrow";
export type ConvertibleTypes = ConvertibleGenericTypes | ConvertibleLinearTypes;