Merge branch 'master' into mrazator/separate-element-into-standalone-package

This commit is contained in:
Marcel Mraz 2025-03-21 11:58:33 +01:00
commit ffa8da2aa7
6 changed files with 149 additions and 117 deletions

View file

@ -491,20 +491,8 @@ export const bindLinearElement = (
return; return;
} }
const binding: PointBinding | FixedPointBinding = { let binding: PointBinding | FixedPointBinding = {
elementId: hoveredElement.id, elementId: hoveredElement.id,
...(isElbowArrow(linearElement)
? {
...calculateFixedPointForElbowArrowBinding(
linearElement,
hoveredElement,
startOrEnd,
elementsMap,
),
focus: 0,
gap: 0,
}
: {
...normalizePointBinding( ...normalizePointBinding(
calculateFocusAndGap( calculateFocusAndGap(
linearElement, linearElement,
@ -514,9 +502,20 @@ export const bindLinearElement = (
), ),
hoveredElement, hoveredElement,
), ),
}),
}; };
if (isElbowArrow(linearElement)) {
binding = {
...binding,
...calculateFixedPointForElbowArrowBinding(
linearElement,
hoveredElement,
startOrEnd,
elementsMap,
),
};
}
mutateElement(linearElement, { mutateElement(linearElement, {
[startOrEnd === "start" ? "startBinding" : "endBinding"]: binding, [startOrEnd === "start" ? "startBinding" : "endBinding"]: binding,
}); });
@ -1276,13 +1275,16 @@ const updateBoundPoint = (
pointDistance(adjacentPoint, edgePointAbsolute) + pointDistance(adjacentPoint, edgePointAbsolute) +
pointDistance(adjacentPoint, center) + pointDistance(adjacentPoint, center) +
Math.max(bindableElement.width, bindableElement.height) * 2; Math.max(bindableElement.width, bindableElement.height) * 2;
const intersections = intersectElementWithLineSegment( const intersections = [
...intersectElementWithLineSegment(
bindableElement, bindableElement,
lineSegment<GlobalPoint>( lineSegment<GlobalPoint>(
adjacentPoint, adjacentPoint,
pointFromVector( pointFromVector(
vectorScale( vectorScale(
vectorNormalize(vectorFromPoint(focusPointAbsolute, adjacentPoint)), vectorNormalize(
vectorFromPoint(focusPointAbsolute, adjacentPoint),
),
interceptorLength, interceptorLength,
), ),
adjacentPoint, adjacentPoint,
@ -1292,23 +1294,16 @@ const updateBoundPoint = (
).sort( ).sort(
(g, h) => (g, h) =>
pointDistanceSq(g, adjacentPoint) - pointDistanceSq(h, adjacentPoint), pointDistanceSq(g, adjacentPoint) - pointDistanceSq(h, adjacentPoint),
); ),
// Fallback when arrow doesn't point to the shape
// debugClear(); pointFromVector(
// debugDrawPoint(intersections[0], { color: "red", permanent: true }); vectorScale(
// debugDrawLine( vectorNormalize(vectorFromPoint(focusPointAbsolute, adjacentPoint)),
// lineSegment<GlobalPoint>( pointDistance(adjacentPoint, edgePointAbsolute),
// adjacentPoint, ),
// pointFromVector( adjacentPoint,
// vectorScale( ),
// vectorNormalize(vectorFromPoint(focusPointAbsolute, adjacentPoint)), ];
// interceptorLength,
// ),
// adjacentPoint,
// ),
// ),
// { permanent: true, color: "green" },
// );
if (intersections.length > 1) { if (intersections.length > 1) {
// The adjacent point is outside the shape (+ gap) // The adjacent point is outside the shape (+ gap)
@ -1731,21 +1726,6 @@ const determineFocusDistance = (
) )
.sort((g, h) => Math.abs(g) - Math.abs(h)); .sort((g, h) => Math.abs(g) - Math.abs(h));
// debugClear();
// [
// lineSegmentIntersectionPoints(rotatedInterceptor, interceptees[0]),
// lineSegmentIntersectionPoints(rotatedInterceptor, interceptees[1]),
// ]
// .filter((p): p is GlobalPoint => p !== null)
// .forEach((p) => debugDrawPoint(p, { color: "black", permanent: true }));
// debugDrawPoint(determineFocusPoint(element, ordered[0] ?? 0, rotatedA), {
// color: "red",
// permanent: true,
// });
// debugDrawLine(rotatedInterceptor, { color: "green", permanent: true });
// debugDrawLine(interceptees[0], { color: "red", permanent: true });
// debugDrawLine(interceptees[1], { color: "red", permanent: true });
const signedDistanceRatio = ordered[0] ?? 0; const signedDistanceRatio = ordered[0] ?? 0;
return signedDistanceRatio; return signedDistanceRatio;

View file

@ -13,6 +13,8 @@ import type {
import type Scene from "@excalidraw/excalidraw/scene/Scene"; import type Scene from "@excalidraw/excalidraw/scene/Scene";
import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
import { updateBoundElements } from "./binding"; import { updateBoundElements } from "./binding";
import { getCommonBounds } from "./bounds"; import { getCommonBounds } from "./bounds";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
@ -28,7 +30,7 @@ import {
} from "./typeChecks"; } from "./typeChecks";
import type { Bounds } from "./bounds"; import type { Bounds } from "./bounds";
import type { NonDeletedExcalidrawElement } from "./types"; import type { ExcalidrawElement } from "./types";
export const dragSelectedElements = ( export const dragSelectedElements = (
pointerDownState: PointerDownState, pointerDownState: PointerDownState,
@ -82,13 +84,20 @@ export const dragSelectedElements = (
} }
} }
const commonBounds = getCommonBounds( const origElements: ExcalidrawElement[] = [];
Array.from(elementsToUpdate).map(
(el) => pointerDownState.originalElements.get(el.id) ?? el, for (const element of elementsToUpdate) {
), const origElement = pointerDownState.originalElements.get(element.id);
); // if original element is not set (e.g. when you duplicate during a drag
// operation), exit to avoid undefined behavior
if (!origElement) {
return;
}
origElements.push(origElement);
}
const adjustedOffset = calculateOffset( const adjustedOffset = calculateOffset(
commonBounds, getCommonBounds(origElements),
offset, offset,
snapOffset, snapOffset,
gridSize, gridSize,

View file

@ -1004,23 +1004,32 @@ export const updateElbowArrowPoints = (
// 0. During all element replacement in the scene, we just need to renormalize // 0. During all element replacement in the scene, we just need to renormalize
// the arrow // the arrow
// TODO (dwelle,mtolmacs): Remove this once Scene.getScene() is removed // TODO (dwelle,mtolmacs): Remove this once Scene.getScene() is removed
const {
startBinding: updatedStartBinding,
endBinding: updatedEndBinding,
...restOfTheUpdates
} = updates;
const startBinding = const startBinding =
typeof updates.startBinding !== "undefined" typeof updatedStartBinding !== "undefined"
? updates.startBinding ? updatedStartBinding
: arrow.startBinding; : arrow.startBinding;
const endBinding = const endBinding =
typeof updates.endBinding !== "undefined" typeof updatedEndBinding !== "undefined"
? updates.endBinding ? updatedEndBinding
: arrow.endBinding; : arrow.endBinding;
const startElement = const startElement =
startBinding && startBinding &&
getBindableElementForId(startBinding.elementId, elementsMap); getBindableElementForId(startBinding.elementId, elementsMap);
const endElement = const endElement =
endBinding && getBindableElementForId(endBinding.elementId, elementsMap); endBinding && getBindableElementForId(endBinding.elementId, elementsMap);
if ( if (
(startBinding && !startElement) ||
(endBinding && !endElement) ||
(elementsMap.size === 0 && validateElbowPoints(updatedPoints)) || (elementsMap.size === 0 && validateElbowPoints(updatedPoints)) ||
startElement?.id !== startBinding?.elementId || (Object.keys(restOfTheUpdates).length === 0 &&
endElement?.id !== endBinding?.elementId (startElement?.id !== startBinding?.elementId ||
endElement?.id !== endBinding?.elementId))
) { ) {
return normalizeArrowElementUpdate( return normalizeArrowElementUpdate(
updatedPoints.map((p) => updatedPoints.map((p) =>
@ -1080,7 +1089,8 @@ export const updateElbowArrowPoints = (
p, p,
arrow.points[i] ?? pointFrom<LocalPoint>(Infinity, Infinity), arrow.points[i] ?? pointFrom<LocalPoint>(Infinity, Infinity),
), ),
) ) &&
validateElbowPoints(updatedPoints)
) { ) {
return {}; return {};
} }

View file

@ -67,6 +67,10 @@ export const actionDuplicateSelection = register({
icon: DuplicateIcon, icon: DuplicateIcon,
trackEvent: { category: "element" }, trackEvent: { category: "element" },
perform: (elements, appState, formData, app) => { perform: (elements, appState, formData, app) => {
if (appState.selectedElementsAreBeingDragged) {
return false;
}
// duplicate selected point(s) if editing a line // duplicate selected point(s) if editing a line
if (appState.editingLinearElement) { if (appState.editingLinearElement) {
// TODO: Invariants should be checked here instead of duplicateSelectedPoints() // TODO: Invariants should be checked here instead of duplicateSelectedPoints()

View file

@ -55,6 +55,8 @@ import {
import { hasStrokeColor } from "@excalidraw/element/comparisons"; import { hasStrokeColor } from "@excalidraw/element/comparisons";
import { updateElbowArrowPoints } from "@excalidraw/element/elbowArrow";
import type { LocalPoint } from "@excalidraw/math"; import type { LocalPoint } from "@excalidraw/math";
import type { import type {
@ -1582,7 +1584,7 @@ export const actionChangeArrowType = register({
if (!isArrowElement(el)) { if (!isArrowElement(el)) {
return el; return el;
} }
const newElement = newElementWith(el, { let newElement = newElementWith(el, {
roundness: roundness:
value === ARROW_TYPE.round value === ARROW_TYPE.round
? { ? {
@ -1597,6 +1599,8 @@ export const actionChangeArrowType = register({
}); });
if (isElbowArrow(newElement)) { if (isElbowArrow(newElement)) {
newElement.fixedSegments = null;
const elementsMap = app.scene.getNonDeletedElementsMap(); const elementsMap = app.scene.getNonDeletedElementsMap();
app.dismissLinearEditor(); app.dismissLinearEditor();
@ -1671,14 +1675,9 @@ export const actionChangeArrowType = register({
endHoveredElement && endHoveredElement &&
bindLinearElement(newElement, endHoveredElement, "end", elementsMap); bindLinearElement(newElement, endHoveredElement, "end", elementsMap);
mutateElement(newElement, { const startBinding =
points: [finalStartPoint, finalEndPoint].map( startElement && newElement.startBinding
(p): LocalPoint =>
pointFrom(p[0] - newElement.x, p[1] - newElement.y),
),
...(startElement && newElement.startBinding
? { ? {
startBinding: {
// @ts-ignore TS cannot discern check above // @ts-ignore TS cannot discern check above
...newElement.startBinding!, ...newElement.startBinding!,
...calculateFixedPointForElbowArrowBinding( ...calculateFixedPointForElbowArrowBinding(
@ -1687,12 +1686,11 @@ export const actionChangeArrowType = register({
"start", "start",
elementsMap, elementsMap,
), ),
},
} }
: {}), : null;
...(endElement && newElement.endBinding const endBinding =
endElement && newElement.endBinding
? { ? {
endBinding: {
// @ts-ignore TS cannot discern check above // @ts-ignore TS cannot discern check above
...newElement.endBinding, ...newElement.endBinding,
...calculateFixedPointForElbowArrowBinding( ...calculateFixedPointForElbowArrowBinding(
@ -1701,16 +1699,47 @@ export const actionChangeArrowType = register({
"end", "end",
elementsMap, elementsMap,
), ),
},
} }
: {}), : null;
});
newElement = {
...newElement,
startBinding,
endBinding,
...updateElbowArrowPoints(newElement, elementsMap, {
points: [finalStartPoint, finalEndPoint].map(
(p): LocalPoint =>
pointFrom(p[0] - newElement.x, p[1] - newElement.y),
),
startBinding,
endBinding,
fixedSegments: null,
}),
};
LinearElementEditor.updateEditorMidPointsCache( LinearElementEditor.updateEditorMidPointsCache(
newElement, newElement,
elementsMap, elementsMap,
app.state, app.state,
); );
} else {
const elementsMap = app.scene.getNonDeletedElementsMap();
if (newElement.startBinding) {
const startElement = elementsMap.get(
newElement.startBinding.elementId,
) as ExcalidrawBindableElement;
if (startElement) {
bindLinearElement(newElement, startElement, "start", elementsMap);
}
}
if (newElement.endBinding) {
const endElement = elementsMap.get(
newElement.endBinding.elementId,
) as ExcalidrawBindableElement;
if (endElement) {
bindLinearElement(newElement, endElement, "end", elementsMap);
}
}
} }
return newElement; return newElement;

View file

@ -818,8 +818,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"type": "arrow", "type": "arrow",
"updated": 1, "updated": 1,
"version": 30, "version": 30,
"width": 50, "width": 0,
"x": 200, "x": "149.29289",
"y": 0, "y": 0,
} }
`; `;
@ -852,7 +852,7 @@ History {
0, 0,
], ],
[ [
50, 0,
0, 0,
], ],
], ],
@ -937,7 +937,7 @@ History {
0, 0,
], ],
[ [
50, 0,
0, 0,
], ],
], ],