break polygon on restore/finalize if invalid & prevent creation

This commit is contained in:
dwelle 2025-05-25 22:57:32 +02:00
parent ad1c28c27c
commit d56bb4087f
6 changed files with 73 additions and 24 deletions

View File

@ -1290,16 +1290,17 @@ export class LinearElementEditor {
app.state.editingLinearElement?.lastUncommittedPoint ===
element.points[element.points.length - 1];
const isPolygon = isLineElement(element) && element.polygon;
// break polygon if deleting start/end point
if (isLineElement(element) && element.polygon) {
if (
pointIndices.includes(0) ||
(pointIndices.includes(element.points.length - 1) &&
// don't disable polygon if cleaning up uncommitted point
!isUncommittedPoint)
) {
app.scene.mutateElement(element, { polygon: false });
}
if (
isPolygon &&
// don't disable polygon if cleaning up uncommitted point
!isUncommittedPoint &&
(pointIndices.includes(0) ||
pointIndices.includes(element.points.length - 1))
) {
app.scene.mutateElement(element, { polygon: false });
}
const nextPoints = element.points.filter((_, idx) => {

View File

@ -36,6 +36,8 @@ import { ShapeCache } from "./ShapeCache";
import { getElementAbsoluteCoords, type Bounds } from "./bounds";
import { canBecomePolygon } from "./typeChecks";
import type {
ElementsMap,
ExcalidrawElement,
@ -402,10 +404,17 @@ export const isPathALoop = (
export const toggleLinePolygonState = (
element: ExcalidrawLineElement,
nextPolygonState: boolean,
) => {
): {
polygon: ExcalidrawLineElement["polygon"];
points: ExcalidrawLineElement["points"];
} | null => {
const updatedPoints = [...element.points];
if (nextPolygonState) {
if (!canBecomePolygon(element.points)) {
return null;
}
const firstPoint = updatedPoints[0];
const lastPoint = updatedPoints[updatedPoints.length - 1];

View File

@ -1,5 +1,7 @@
import { ROUNDNESS, assertNever } from "@excalidraw/common";
import { pointsEqual } from "@excalidraw/math";
import type { ElementOrToolType } from "@excalidraw/excalidraw/types";
import type { MarkNonNullable } from "@excalidraw/common/utility-types";
@ -379,3 +381,26 @@ export const getLinearElementSubType = (
}
return "line";
};
/**
* Checks if current element points meet all the conditions for polygon=true
* (this isn't a element type check, for that use isLineElement).
*
* If you want to check if points *can* be turned into a polygon, use
* canBecomePolygon(points).
*/
export const isValidPolygon = (
points: ExcalidrawLineElement["points"],
): boolean => {
return points.length > 3 && pointsEqual(points[0], points[points.length - 1]);
};
export const canBecomePolygon = (
points: ExcalidrawLineElement["points"],
): boolean => {
return (
points.length > 3 ||
// 3-point polygons can't have all points in a single line
(points.length === 3 && !pointsEqual(points[0], points[points.length - 1]))
);
};

View File

@ -5,7 +5,7 @@ import {
bindOrUnbindLinearElement,
isBindingEnabled,
} from "@excalidraw/element/binding";
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
import { isValidPolygon, LinearElementEditor } from "@excalidraw/element";
import {
isBindingElement,
@ -99,6 +99,12 @@ export const actionFinalize = register({
scene,
);
}
if (isLineElement(element) && !isValidPolygon(element.points)) {
scene.mutateElement(element, {
polygon: false,
});
}
return {
elements:
element.points.length < 2 || isInvisiblySmallElement(element)
@ -198,6 +204,12 @@ export const actionFinalize = register({
}
}
if (isLineElement(element) && !isValidPolygon(element.points)) {
scene.mutateElement(element, {
polygon: false,
});
}
if (
isBindingElement(element) &&
!isLoop &&

View File

@ -24,7 +24,7 @@ import {
reduceToCommonValue,
} from "@excalidraw/common";
import { getNonDeletedElements } from "@excalidraw/element";
import { canBecomePolygon, getNonDeletedElements } from "@excalidraw/element";
import {
bindLinearElement,
@ -364,18 +364,16 @@ export const actionChangeBackgroundColor = register({
};
}
const selectedElements = app.scene.getSelectedElements(appState);
const shouldEnablePolygon = selectedElements.every((el) =>
isLineElement(el),
);
let nextElements;
if (
shouldEnablePolygon &&
value.currentItemBackgroundColor &&
!isTransparent(value.currentItemBackgroundColor)
) {
const selectedElements = app.scene.getSelectedElements(appState);
const shouldEnablePolygon =
!isTransparent(value.currentItemBackgroundColor) &&
selectedElements.every(
(el) => isLineElement(el) && canBecomePolygon(el.points),
);
if (shouldEnablePolygon) {
const selectedElementsMap = arrayToMap(selectedElements);
nextElements = elements.map((el) => {
if (selectedElementsMap.has(el.id) && isLineElement(el)) {

View File

@ -18,7 +18,7 @@ import {
normalizeLink,
getLineHeight,
} from "@excalidraw/common";
import { getNonDeletedElements } from "@excalidraw/element";
import { getNonDeletedElements, isValidPolygon } from "@excalidraw/element";
import { normalizeFixedPoint } from "@excalidraw/element";
import {
updateElbowArrowPoints,
@ -342,7 +342,11 @@ const restoreElement = (
x,
y,
...(isLineElement(element)
? { polygon: element.polygon ?? false }
? {
polygon: isValidPolygon(element.points)
? element.polygon ?? false
: false,
}
: {}),
...getSizeFromPoints(points),
});