[skip ci] First iteration of bringing over previous changes

This commit is contained in:
Mark Tolmacs
2025-03-24 19:22:45 +01:00
parent 4ee99de2fb
commit fbde68c849
19 changed files with 599 additions and 596 deletions

View File

@@ -16,6 +16,7 @@ import {
vectorSubtract,
vectorDot,
vectorNormalize,
lineSegment,
} from "@excalidraw/math";
import { isPointInShape } from "@excalidraw/utils/collision";
import { getSelectionBoxShape } from "@excalidraw/utils/shape";
@@ -302,7 +303,7 @@ import {
import { isNonDeletedElement } from "@excalidraw/element";
import type { LocalPoint, Radians } from "@excalidraw/math";
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
import type {
ExcalidrawBindableElement,
@@ -5997,9 +5998,19 @@ class App extends React.Component<AppProps, AppState> {
{
points: [
...points.slice(0, -1),
pointFrom<LocalPoint>(
lastCommittedX + dxFromLastCommitted,
lastCommittedY + dyFromLastCommitted,
toLocalPoint(
getOutlineAvoidingPoint(
multiElement,
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
multiElement.points.length - 1,
this.scene,
this.state.zoom,
pointFrom<GlobalPoint>(
multiElement.x + lastCommittedX + dxFromLastCommitted,
multiElement.y + lastCommittedY + dyFromLastCommitted,
),
),
multiElement,
),
],
},
@@ -7751,18 +7762,34 @@ class App extends React.Component<AppProps, AppState> {
}
const { x: rx, y: ry, lastCommittedPoint } = multiElement;
const lastGlobalPoint = pointFrom<GlobalPoint>(
rx + multiElement.points[multiElement.points.length - 1][0],
ry + multiElement.points[multiElement.points.length - 1][1],
);
const hoveredElementForBinding = getHoveredElementForBinding(
{
x: lastGlobalPoint[0],
y: lastGlobalPoint[1],
},
this.scene.getNonDeletedElements(),
this.scene.getNonDeletedElementsMap(),
this.state.zoom,
true,
isElbowArrow(multiElement),
);
// clicking inside commit zone → finalize arrow
if (
multiElement.points.length > 1 &&
lastCommittedPoint &&
pointDistance(
pointFrom(
pointerDownState.origin.x - rx,
pointerDownState.origin.y - ry,
),
lastCommittedPoint,
) < LINE_CONFIRM_THRESHOLD
hoveredElementForBinding ||
(multiElement.points.length > 1 &&
lastCommittedPoint &&
pointDistance(
pointFrom(
pointerDownState.origin.x - rx,
pointerDownState.origin.y - ry,
),
lastCommittedPoint,
) < LINE_CONFIRM_THRESHOLD)
) {
this.actionManager.executeAction(actionFinalize);
return;
@@ -7806,53 +7833,92 @@ class App extends React.Component<AppProps, AppState> {
? [currentItemStartArrowhead, currentItemEndArrowhead]
: [null, null];
const element =
elementType === "arrow"
? newArrowElement({
type: elementType,
x: gridX,
y: gridY,
strokeColor: this.state.currentItemStrokeColor,
backgroundColor: this.state.currentItemBackgroundColor,
fillStyle: this.state.currentItemFillStyle,
strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
roundness:
this.state.currentItemArrowType === ARROW_TYPE.round
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
: // note, roundness doesn't have any effect for elbow arrows,
// but it's best to set it to null as well
null,
startArrowhead,
endArrowhead,
locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null,
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
fixedSegments:
this.state.currentItemArrowType === ARROW_TYPE.elbow
? []
: null,
})
: newLinearElement({
type: elementType,
x: gridX,
y: gridY,
strokeColor: this.state.currentItemStrokeColor,
backgroundColor: this.state.currentItemBackgroundColor,
fillStyle: this.state.currentItemFillStyle,
strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
roundness:
this.state.currentItemRoundness === "round"
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
: null,
locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null,
});
let element: NonDeleted<ExcalidrawLinearElement>;
if (elementType === "arrow") {
const arrow: Mutable<NonDeleted<ExcalidrawArrowElement>> =
newArrowElement({
type: "arrow",
x: gridX,
y: gridY,
strokeColor: this.state.currentItemStrokeColor,
backgroundColor: this.state.currentItemBackgroundColor,
fillStyle: this.state.currentItemFillStyle,
strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
roundness:
this.state.currentItemArrowType === ARROW_TYPE.round
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
: // note, roundness doesn't have any effect for elbow arrows,
// but it's best to set it to null as well
null,
startArrowhead,
endArrowhead,
locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null,
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
fixedSegments:
this.state.currentItemArrowType === ARROW_TYPE.elbow ? [] : null,
});
const hoveredElement = getHoveredElementForBinding(
{ x: gridX, y: gridY },
this.scene.getNonDeletedElements(),
this.scene.getNonDeletedElementsMap(),
this.state.zoom,
true,
this.state.currentItemArrowType === ARROW_TYPE.elbow,
);
if (hoveredElement) {
[arrow.x, arrow.y] =
intersectElementWithLineSegment(
hoveredElement,
lineSegment(
pointFrom<GlobalPoint>(gridX, gridY),
pointFrom<GlobalPoint>(
gridX,
hoveredElement.y + hoveredElement.height / 2,
),
),
2 * FIXED_BINDING_DISTANCE,
)[0] ??
intersectElementWithLineSegment(
hoveredElement,
lineSegment(
pointFrom<GlobalPoint>(gridX, gridY),
pointFrom<GlobalPoint>(
hoveredElement.x + hoveredElement.width / 2,
gridY,
),
),
2 * FIXED_BINDING_DISTANCE,
)[0] ??
pointFrom<GlobalPoint>(gridX, gridY);
}
element = arrow;
} else {
element = newLinearElement({
type: elementType,
x: gridX,
y: gridY,
strokeColor: this.state.currentItemStrokeColor,
backgroundColor: this.state.currentItemBackgroundColor,
fillStyle: this.state.currentItemFillStyle,
strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
roundness:
this.state.currentItemRoundness === "round"
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
: null,
locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null,
});
}
this.setState((prevState) => {
const nextSelectedElementIds = {
...prevState.selectedElementIds,
@@ -8167,12 +8233,6 @@ class App extends React.Component<AppProps, AppState> {
this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
}
const [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
);
// for arrows/lines, don't start dragging until a given threshold
// to ensure we don't create a 2-point arrow by mistake when
// user clicks mouse in a way that it moves a tiny bit (thus
@@ -8273,7 +8333,6 @@ class App extends React.Component<AppProps, AppState> {
);
},
linearElementEditor,
this.scene,
);
if (newLinearElementEditor) {
pointerDownState.lastCoords.x = pointerCoords.x;
@@ -8660,6 +8719,11 @@ class App extends React.Component<AppProps, AppState> {
} else if (isLinearElement(newElement)) {
pointerDownState.drag.hasOccurred = true;
const points = newElement.points;
const [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
);
let dx = gridX - newElement.x;
let dy = gridY - newElement.y;
@@ -8676,7 +8740,23 @@ class App extends React.Component<AppProps, AppState> {
mutateElement(
newElement,
{
points: [...points, pointFrom<LocalPoint>(dx, dy)],
points: [
...points,
toLocalPoint(
getOutlineAvoidingPoint(
newElement,
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
newElement.points.length - 1,
this.scene,
this.state.zoom,
pointFrom<GlobalPoint>(
newElement.x + dx,
newElement.y + dy,
),
),
newElement,
),
],
},
false,
);
@@ -8687,7 +8767,23 @@ class App extends React.Component<AppProps, AppState> {
mutateElement(
newElement,
{
points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
points: [
...points.slice(0, -1),
toLocalPoint(
getOutlineAvoidingPoint(
newElement,
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
newElement.points.length - 1,
this.scene,
this.state.zoom,
pointFrom<GlobalPoint>(
newElement.x + dx,
newElement.y + dy,
),
),
newElement,
),
],
},
false,
{ isDragging: true },
@@ -10725,12 +10821,6 @@ class App extends React.Component<AppProps, AppState> {
updateBoundElements(
croppingElement,
this.scene.getNonDeletedElementsMap(),
{
newSize: {
width: croppingElement.width,
height: croppingElement.height,
},
},
);
this.setState({

View File

@@ -87,9 +87,7 @@ const resizeElementInGroup = (
);
if (boundTextElement) {
const newFontSize = boundTextElement.fontSize * scale;
updateBoundElements(latestElement, elementsMap, {
newSize: { width: updates.width, height: updates.height },
});
updateBoundElements(latestElement, elementsMap);
const latestBoundTextElement = elementsMap.get(boundTextElement.id);
if (latestBoundTextElement && isTextElement(latestBoundTextElement)) {
mutateElement(