Compare commits

..

4 Commits

Author SHA1 Message Date
Marcel Mraz
6bcd5b622c Coderabbit test 2025-05-22 13:18:12 +02:00
Marcel Mraz
41c036e1a5 chore: Add DeepWiki badge (#9559) 2025-05-22 13:05:56 +02:00
Márk Tolmács
91d36e9b81 fix: Linear to elbow conversion crash (#9556)
* Fix linear to elbow conversion

* Add invariant check

* Add dev invariant fix

* Add arrowhead
2025-05-22 12:34:15 +02:00
Kamil Wąż
27522110df fix: fix keybindings for arrowheads (#9557) 2025-05-22 09:47:41 +02:00
7 changed files with 35 additions and 49 deletions

View File

@@ -34,9 +34,13 @@
<a href="https://discord.gg/UexuTaE"> <a href="https://discord.gg/UexuTaE">
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/> <img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/>
</a> </a>
<a href="https://deepwiki.com/excalidraw/excalidraw">
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" />
</a>
<a href="https://twitter.com/excalidraw"> <a href="https://twitter.com/excalidraw">
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/> <img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/>
</a> </a>
<img alt"CodeRabbit Reviews" src="https://img.shields.io/coderabbit/prs/github/excalidraw/excalidraw?utm_source=oss&utm_medium=github&utm_campaign=excalidraw%2Fexcalidraw&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews"/>
</p> </p>
<div align="center"> <div align="center">

View File

@@ -974,6 +974,25 @@ export const updateElbowArrowPoints = (
), ),
"Elbow arrow segments must be either horizontal or vertical", "Elbow arrow segments must be either horizontal or vertical",
); );
invariant(
updates.fixedSegments?.find(
(segment) =>
segment.index === 1 &&
pointsEqual(segment.start, (updates.points ?? arrow.points)[0]),
) == null &&
updates.fixedSegments?.find(
(segment) =>
segment.index === (updates.points ?? arrow.points).length - 1 &&
pointsEqual(
segment.end,
(updates.points ?? arrow.points)[
(updates.points ?? arrow.points).length - 1
],
),
) == null,
"The first and last segments cannot be fixed",
);
} }
const fixedSegments = updates.fixedSegments ?? arrow.fixedSegments ?? []; const fixedSegments = updates.fixedSegments ?? arrow.fixedSegments ?? [];

View File

@@ -1483,13 +1483,13 @@ const getArrowheadOptions = (flip: boolean) => {
value: "crowfoot_one", value: "crowfoot_one",
text: t("labels.arrowhead_crowfoot_one"), text: t("labels.arrowhead_crowfoot_one"),
icon: <ArrowheadCrowfootOneIcon flip={flip} />, icon: <ArrowheadCrowfootOneIcon flip={flip} />,
keyBinding: "c", keyBinding: "x",
}, },
{ {
value: "crowfoot_many", value: "crowfoot_many",
text: t("labels.arrowhead_crowfoot_many"), text: t("labels.arrowhead_crowfoot_many"),
icon: <ArrowheadCrowfootIcon flip={flip} />, icon: <ArrowheadCrowfootIcon flip={flip} />,
keyBinding: "x", keyBinding: "c",
}, },
{ {
value: "crowfoot_one_or_many", value: "crowfoot_one_or_many",

View File

@@ -564,7 +564,7 @@ export const convertElementTypes = (
continue; continue;
} }
const fixedSegments: FixedSegment[] = []; const fixedSegments: FixedSegment[] = [];
for (let i = 0; i < nextPoints.length - 1; i++) { for (let i = 1; i < nextPoints.length - 2; i++) {
fixedSegments.push({ fixedSegments.push({
start: nextPoints[i], start: nextPoints[i],
end: nextPoints[i + 1], end: nextPoints[i + 1],
@@ -581,6 +581,7 @@ export const convertElementTypes = (
); );
mutateElement(element, app.scene.getNonDeletedElementsMap(), { mutateElement(element, app.scene.getNonDeletedElementsMap(), {
...updates, ...updates,
endArrowhead: "arrow",
}); });
} else { } else {
// if we're converting to non-elbow linear element, check if // if we're converting to non-elbow linear element, check if

View File

@@ -39,7 +39,6 @@ const handleDimensionChange: DragInputCallbackType<
shouldKeepAspectRatio, shouldKeepAspectRatio,
shouldChangeByStepSize, shouldChangeByStepSize,
nextValue, nextValue,
ratio,
property, property,
originalAppState, originalAppState,
instantChange, instantChange,
@@ -155,12 +154,6 @@ const handleDimensionChange: DragInputCallbackType<
} }
if (nextValue !== undefined) { if (nextValue !== undefined) {
if (ratio) {
ratio = property === "width" ? ratio : [ratio[1], ratio[0]];
nextValue = (origElement[property] / ratio[0]) * ratio[1];
property = property === "width" ? "height" : "width";
}
const nextWidth = Math.max( const nextWidth = Math.max(
property === "width" property === "width"
? nextValue ? nextValue

View File

@@ -33,7 +33,6 @@ export type DragInputCallbackType<
shouldChangeByStepSize: boolean; shouldChangeByStepSize: boolean;
scene: Scene; scene: Scene;
nextValue?: number; nextValue?: number;
ratio?: [number, number] | null;
property: P; property: P;
originalAppState: AppState; originalAppState: AppState;
setInputValue: (value: number) => void; setInputValue: (value: number) => void;
@@ -110,22 +109,11 @@ const StatsDragInput = <
} }
stateRef.current.updatePending = false; stateRef.current.updatePending = false;
const ratioMatch = updatedValue
.trim()
.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
let ratio: [number, number] | null = null;
if (ratioMatch) {
ratio = [Number(ratioMatch[1]), Number(ratioMatch[2])];
}
const parsed = Number(updatedValue); const parsed = Number(updatedValue);
if (isNaN(parsed)) { if (isNaN(parsed)) {
setInputValue(value.toString()); setInputValue(value.toString());
if (!ratio) {
return; return;
} }
}
const rounded = Number(parsed.toFixed(2)); const rounded = Number(parsed.toFixed(2));
const original = Number(value); const original = Number(value);
@@ -135,11 +123,7 @@ const StatsDragInput = <
// 2. original was not "Mixed" and the difference between a new value and previous value is greater // 2. original was not "Mixed" and the difference between a new value and previous value is greater
// than the smallest delta allowed, which is 0.01 // than the smallest delta allowed, which is 0.01
// reason: idempotent to avoid unnecessary // reason: idempotent to avoid unnecessary
if ( if (isNaN(original) || Math.abs(rounded - original) >= SMALLEST_DELTA) {
isNaN(original) ||
ratio ||
Math.abs(rounded - original) >= SMALLEST_DELTA
) {
stateRef.current.lastUpdatedValue = updatedValue; stateRef.current.lastUpdatedValue = updatedValue;
dragInputCallback({ dragInputCallback({
accumulatedChange: 0, accumulatedChange: 0,
@@ -150,7 +134,6 @@ const StatsDragInput = <
shouldChangeByStepSize: false, shouldChangeByStepSize: false,
scene, scene,
nextValue: rounded, nextValue: rounded,
ratio,
property, property,
originalAppState: appState, originalAppState: appState,
setInputValue: (value) => setInputValue(String(value)), setInputValue: (value) => setInputValue(String(value)),

View File

@@ -151,7 +151,6 @@ const handleDimensionChange: DragInputCallbackType<
originalAppState, originalAppState,
shouldChangeByStepSize, shouldChangeByStepSize,
nextValue, nextValue,
ratio,
scene, scene,
property, property,
}) => { }) => {
@@ -203,22 +202,9 @@ const handleDimensionChange: DragInputCallbackType<
origElement && origElement &&
isPropertyEditable(latestElement, property) isPropertyEditable(latestElement, property)
) { ) {
let _nextValue = nextValue;
let _property = property;
if (ratio) {
let _ratio = ratio;
if (ratio) {
_ratio = _property === "width" ? _ratio : [_ratio[1], _ratio[0]];
_nextValue = (origElement[_property] / _ratio[0]) * _ratio[1];
_property = _property === "width" ? "height" : "width";
}
}
let nextWidth = let nextWidth =
_property === "width" property === "width" ? Math.max(0, nextValue) : latestElement.width;
? Math.max(0, _nextValue) if (property === "width") {
: latestElement.width;
if (_property === "width") {
if (shouldChangeByStepSize) { if (shouldChangeByStepSize) {
nextWidth = getStepSizedValue(nextWidth, STEP_SIZE); nextWidth = getStepSizedValue(nextWidth, STEP_SIZE);
} else { } else {
@@ -227,10 +213,10 @@ const handleDimensionChange: DragInputCallbackType<
} }
let nextHeight = let nextHeight =
_property === "height" property === "height"
? Math.max(0, _nextValue) ? Math.max(0, nextValue)
: latestElement.height; : latestElement.height;
if (_property === "height") { if (property === "height") {
if (shouldChangeByStepSize) { if (shouldChangeByStepSize) {
nextHeight = getStepSizedValue(nextHeight, STEP_SIZE); nextHeight = getStepSizedValue(nextHeight, STEP_SIZE);
} else { } else {
@@ -248,7 +234,7 @@ const handleDimensionChange: DragInputCallbackType<
origElement, origElement,
originalElementsMap, originalElementsMap,
scene, scene,
_property === "width" ? "e" : "s", property === "width" ? "e" : "s",
{ {
shouldInformMutation: false, shouldInformMutation: false,
}, },