Position revert

This commit is contained in:
Mark Tolmacs 2025-03-30 09:10:25 +02:00
parent 9a599cfc05
commit 5cc5c626df
4 changed files with 87 additions and 45 deletions

View file

@ -18,10 +18,7 @@ import type { AppState } from "@excalidraw/excalidraw/types";
import type { Degrees, Radians } from "@excalidraw/math"; import type { Degrees, Radians } from "@excalidraw/math";
import type { import type { ExcalidrawElement } from "@excalidraw/element/types";
ExcalidrawElement,
NonDeletedExcalidrawElement,
} from "@excalidraw/element/types";
import { angleIcon } from "../icons"; import { angleIcon } from "../icons";
@ -75,14 +72,6 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
mutateElement(boundTextElement, { angle: nextAngle }); mutateElement(boundTextElement, { angle: nextAngle });
} }
setAppState({
suggestedBindings: getSuggestedBindingsForArrows(
[latestElement] as NonDeletedExcalidrawElement[],
elementsMap,
originalAppState.zoom,
),
});
return; return;
} }
@ -114,7 +103,7 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
setAppState({ setAppState({
suggestedBindings: getSuggestedBindingsForArrows( suggestedBindings: getSuggestedBindingsForArrows(
[latestElement] as NonDeletedExcalidrawElement[], [latestElement],
elementsMap, elementsMap,
originalAppState.zoom, originalAppState.zoom,
), ),
@ -122,7 +111,7 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
} }
}; };
const handleFinished: DragFinishedCallbackType = ({ const handleFinished: DragFinishedCallbackType<AngleProps["property"]> = ({
originalElements, originalElements,
originalAppState, originalAppState,
scene, scene,
@ -137,26 +126,16 @@ const handleFinished: DragFinishedCallbackType = ({
if (latestElement) { if (latestElement) {
updateBindings(latestElement, elementsMap, originalAppState.zoom, () => { updateBindings(latestElement, elementsMap, originalAppState.zoom, () => {
const revertAngle = (latestElement.angle - const change = degreesToRadians(accumulatedChange as Degrees);
degreesToRadians(accumulatedChange as Degrees)) as Radians;
mutateElement(latestElement, { mutateElement(latestElement, {
angle: revertAngle, angle: (latestElement.angle - change) as Radians,
});
}); });
const boundTextElement = getBoundTextElement(
latestElement,
elementsMap,
);
if (boundTextElement && !isArrowElement(latestElement)) {
mutateElement(boundTextElement, { angle: revertAngle });
}
setAppState({ setAppState({
suggestedBindings: [], suggestedBindings: [],
}); });
});
} }
} }
}; };

View file

@ -37,10 +37,14 @@ export type DragInputCallbackType<
setAppState: React.Component<any, AppState>["setState"]; setAppState: React.Component<any, AppState>["setState"];
}) => void; }) => void;
export type DragFinishedCallbackType<E = ExcalidrawElement> = (props: { export type DragFinishedCallbackType<
P extends StatsInputProperty,
E = ExcalidrawElement,
> = (props: {
originalElements: readonly E[]; originalElements: readonly E[];
originalElementsMap: ElementsMap; originalElementsMap: ElementsMap;
scene: Scene; scene: Scene;
property: P;
originalAppState: AppState; originalAppState: AppState;
accumulatedChange: number; accumulatedChange: number;
setAppState: React.Component<any, AppState>["setState"]; setAppState: React.Component<any, AppState>["setState"];
@ -57,7 +61,7 @@ interface StatsDragInputProps<
editable?: boolean; editable?: boolean;
shouldKeepAspectRatio?: boolean; shouldKeepAspectRatio?: boolean;
dragInputCallback: DragInputCallbackType<T, E>; dragInputCallback: DragInputCallbackType<T, E>;
dragFinishedCallback?: DragFinishedCallbackType<E>; dragFinishedCallback?: DragFinishedCallbackType<T, E>;
property: T; property: T;
scene: Scene; scene: Scene;
appState: AppState; appState: AppState;
@ -136,6 +140,7 @@ const StatsDragInput = <
// reason: idempotent to avoid unnecessary // reason: idempotent to avoid unnecessary
if (isNaN(original) || Math.abs(rounded - original) >= SMALLEST_DELTA) { if (isNaN(original) || Math.abs(rounded - original) >= SMALLEST_DELTA) {
stateRef.current.lastUpdatedValue = updatedValue; stateRef.current.lastUpdatedValue = updatedValue;
const originalElementsMap = app.scene.getNonDeletedElementsMap();
dragInputCallback({ dragInputCallback({
accumulatedChange: 0, accumulatedChange: 0,
instantChange: 0, instantChange: 0,
@ -150,6 +155,15 @@ const StatsDragInput = <
setInputValue: (value) => setInputValue(String(value)), setInputValue: (value) => setInputValue(String(value)),
setAppState, setAppState,
}); });
dragFinishedCallback?.({
originalElements: elements,
originalElementsMap,
scene,
originalAppState: appState,
accumulatedChange: rounded,
property,
setAppState,
});
app.syncActionResult({ app.syncActionResult({
captureUpdate: CaptureUpdateAction.IMMEDIATELY, captureUpdate: CaptureUpdateAction.IMMEDIATELY,
}); });
@ -303,6 +317,7 @@ const StatsDragInput = <
originalElementsMap, originalElementsMap,
scene, scene,
originalAppState, originalAppState,
property,
accumulatedChange, accumulatedChange,
setAppState, setAppState,
}); });

View file

@ -7,12 +7,17 @@ import {
import { mutateElement } from "@excalidraw/element/mutateElement"; import { mutateElement } from "@excalidraw/element/mutateElement";
import { isImageElement } from "@excalidraw/element/typeChecks"; import { isImageElement } from "@excalidraw/element/typeChecks";
import { getSuggestedBindingsForArrows } from "@excalidraw/element/binding";
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
import StatsDragInput from "./DragInput"; import StatsDragInput from "./DragInput";
import { getStepSizedValue, moveElement } from "./utils"; import { getStepSizedValue, moveElement, updateBindings } from "./utils";
import type { DragInputCallbackType } from "./DragInput"; import type {
DragFinishedCallbackType,
DragInputCallbackType,
} from "./DragInput";
import type Scene from "../../scene/Scene"; import type Scene from "../../scene/Scene";
import type { AppState } from "../../types"; import type { AppState } from "../../types";
@ -36,6 +41,7 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
property, property,
scene, scene,
originalAppState, originalAppState,
setAppState,
}) => { }) => {
const elementsMap = scene.getNonDeletedElementsMap(); const elementsMap = scene.getNonDeletedElementsMap();
const origElement = originalElements[0]; const origElement = originalElements[0];
@ -122,6 +128,14 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
crop: nextCrop, crop: nextCrop,
}); });
setAppState({
suggestedBindings: getSuggestedBindingsForArrows(
[origElement],
elementsMap,
originalAppState.zoom,
),
});
return; return;
} }
@ -135,6 +149,7 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
elementsMap, elementsMap,
originalElementsMap, originalElementsMap,
); );
return; return;
} }
@ -166,15 +181,51 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
elementsMap, elementsMap,
originalElementsMap, originalElementsMap,
); );
if (origElement) {
const latestElement = elementsMap.get(origElement.id);
if (latestElement) {
setAppState({
suggestedBindings: getSuggestedBindingsForArrows(
[latestElement],
elementsMap,
originalAppState.zoom,
),
});
}
}
}; };
const Position = ({ const handleFinished: DragFinishedCallbackType<"x" | "y"> = ({
property, originalElements,
element, originalAppState,
elementsMap,
scene, scene,
appState, accumulatedChange,
}: PositionProps) => { property,
setAppState,
}) => {
const elementsMap = scene.getNonDeletedElementsMap();
const origElement = originalElements[0];
if (origElement) {
const latestElement = elementsMap.get(origElement.id);
if (latestElement) {
updateBindings(latestElement, elementsMap, originalAppState.zoom, () => {
mutateElement(latestElement, {
[property]: latestElement[property] - accumulatedChange,
});
});
setAppState({
suggestedBindings: [],
});
}
}
};
const Position = ({ property, element, scene, appState }: PositionProps) => {
const [topLeftX, topLeftY] = pointRotateRads( const [topLeftX, topLeftY] = pointRotateRads(
pointFrom(element.x, element.y), pointFrom(element.x, element.y),
pointFrom(element.x + element.width / 2, element.y + element.height / 2), pointFrom(element.x + element.width / 2, element.y + element.height / 2),
@ -202,6 +253,7 @@ const Position = ({
label={property === "x" ? "X" : "Y"} label={property === "x" ? "X" : "Y"}
elements={[element]} elements={[element]}
dragInputCallback={handlePositionChange} dragInputCallback={handlePositionChange}
dragFinishedCallback={handleFinished}
scene={scene} scene={scene}
value={value} value={value}
property={property} property={property}

View file

@ -204,7 +204,7 @@ export const updateBindings = (
latestElement: ExcalidrawElement, latestElement: ExcalidrawElement,
elementsMap: NonDeletedSceneElementsMap, elementsMap: NonDeletedSceneElementsMap,
zoom?: AppState["zoom"], zoom?: AppState["zoom"],
remainedBound?: () => void, bindingElementKeptOriginalBinding?: () => void,
) => { ) => {
if (isBindingElement(latestElement)) { if (isBindingElement(latestElement)) {
const [start, end] = getOriginalBindingsIfStillCloseToArrowEnds( const [start, end] = getOriginalBindingsIfStillCloseToArrowEnds(
@ -217,7 +217,7 @@ export const updateBindings = (
(latestElement.startBinding && start) || (latestElement.startBinding && start) ||
(latestElement.endBinding && end) (latestElement.endBinding && end)
) { ) {
remainedBound?.(); bindingElementKeptOriginalBinding?.();
} else { } else {
if (latestElement.startBinding && !start) { if (latestElement.startBinding && !start) {
unbindLinearElement(latestElement, "start"); unbindLinearElement(latestElement, "start");
@ -227,9 +227,5 @@ export const updateBindings = (
unbindLinearElement(latestElement, "end"); unbindLinearElement(latestElement, "end");
} }
} }
// else if (end) {
// updateBoundElements(end, elementsMap);
// }
} }
}; };