Compare commits

..

1 Commits

Author SHA1 Message Date
Márk Tolmács
c141500400
chore: Relocate visualdebug so ESLint doesn't complain (#9668) 2025-06-18 14:45:51 +02:00
4 changed files with 68 additions and 258 deletions

View File

@ -18,10 +18,10 @@ import {
} from "@excalidraw/math"; } from "@excalidraw/math";
import { isCurve } from "@excalidraw/math/curve"; import { isCurve } from "@excalidraw/math/curve";
import type { DebugElement } from "@excalidraw/excalidraw/visualdebug";
import type { Curve } from "@excalidraw/math"; import type { Curve } from "@excalidraw/math";
import type { DebugElement } from "@excalidraw/utils/visualdebug";
import { STORAGE_KEYS } from "../app_constants"; import { STORAGE_KEYS } from "../app_constants";
const renderLine = ( const renderLine = (

View File

@ -191,7 +191,6 @@ import {
FlowChartNavigator, FlowChartNavigator,
getLinkDirectionFromKey, getLinkDirectionFromKey,
cropElement, cropElement,
getUncroppedImageElement,
wrapText, wrapText,
isElementLink, isElementLink,
parseElementLinkFromURL, parseElementLinkFromURL,
@ -6542,50 +6541,7 @@ class App extends React.Component<AppProps, AppState> {
this.clearSelectionIfNotUsingSelection(); this.clearSelectionIfNotUsingSelection();
this.updateBindingEnabledOnPointerMove(event); this.updateBindingEnabledOnPointerMove(event);
// Check if we're in crop mode and hitting uncropped area - if so, skip selection handling if (this.handleSelectionOnPointerDown(event, pointerDownState)) {
let skipSelectionHandling = false;
if (this.state.croppingElementId) {
const croppingElement = this.scene
.getNonDeletedElementsMap()
.get(this.state.croppingElementId);
if (
croppingElement &&
isImageElement(croppingElement) &&
croppingElement.crop
) {
const uncroppedElement = getUncroppedImageElement(
croppingElement,
this.scene.getNonDeletedElementsMap(),
);
const hitUncroppedArea = hitElementItself({
point: pointFrom(
pointerDownState.origin.x,
pointerDownState.origin.y,
),
element: uncroppedElement,
threshold: this.getElementHitThreshold(uncroppedElement),
elementsMap: this.scene.getNonDeletedElementsMap(),
});
if (hitUncroppedArea) {
skipSelectionHandling = true;
// Set a dedicated flag for crop position movement
pointerDownState.cropPositionMovement.enabled = true;
pointerDownState.cropPositionMovement.croppingElementId =
croppingElement.id;
// Set isCropping state to true so crop mode UI stays active
this.setState({
isCropping: true,
});
}
}
}
if (
!skipSelectionHandling &&
this.handleSelectionOnPointerDown(event, pointerDownState)
) {
return; return;
} }
@ -6996,9 +6952,6 @@ class App extends React.Component<AppProps, AppState> {
boxSelection: { boxSelection: {
hasOccurred: false, hasOccurred: false,
}, },
cropPositionMovement: {
enabled: false,
},
}; };
} }
@ -7253,7 +7206,6 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState.hit.allHitElements.some((element) => pointerDownState.hit.allHitElements.some((element) =>
this.isASelectedElement(element), this.isASelectedElement(element),
); );
if ( if (
(hitElement === null || !someHitElementIsSelected) && (hitElement === null || !someHitElementIsSelected) &&
!event.shiftKey && !event.shiftKey &&
@ -8077,105 +8029,6 @@ class App extends React.Component<AppProps, AppState> {
} }
const pointerCoords = viewportCoordsToSceneCoords(event, this.state); const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
// #region dedicated crop position movement
if (
pointerDownState.cropPositionMovement.enabled &&
pointerDownState.cropPositionMovement.croppingElementId
) {
const croppingElement = pointerDownState.cropPositionMovement
.croppingElementId
? this.scene
.getNonDeletedElementsMap()
.get(pointerDownState.cropPositionMovement.croppingElementId)
: null;
if (
croppingElement &&
isImageElement(croppingElement) &&
croppingElement.crop
) {
const crop = croppingElement.crop;
const image =
isInitializedImageElement(croppingElement) &&
this.imageCache.get(croppingElement.fileId)?.image;
if (image && !(image instanceof Promise)) {
const dragOffset = vectorScale(
vector(
pointerCoords.x - pointerDownState.lastCoords.x,
pointerCoords.y - pointerDownState.lastCoords.y,
),
Math.max(this.state.zoom.value, 2),
);
const elementsMap = this.scene.getNonDeletedElementsMap();
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
croppingElement,
elementsMap,
);
const topLeft = vectorFromPoint(
pointRotateRads(
pointFrom(x1, y1),
pointFrom(cx, cy),
croppingElement.angle,
),
);
const topRight = vectorFromPoint(
pointRotateRads(
pointFrom(x2, y1),
pointFrom(cx, cy),
croppingElement.angle,
),
);
const bottomLeft = vectorFromPoint(
pointRotateRads(
pointFrom(x1, y2),
pointFrom(cx, cy),
croppingElement.angle,
),
);
const topEdge = vectorNormalize(vectorSubtract(topRight, topLeft));
const leftEdge = vectorNormalize(
vectorSubtract(bottomLeft, topLeft),
);
// project dragOffset onto leftEdge and topEdge to decompose
const offsetVector = vector(
vectorDot(dragOffset, topEdge),
vectorDot(dragOffset, leftEdge),
);
const nextCrop = {
...crop,
x: clamp(
crop.x - offsetVector[0] * Math.sign(croppingElement.scale[0]),
0,
image.naturalWidth - crop.width,
),
y: clamp(
crop.y - offsetVector[1] * Math.sign(croppingElement.scale[1]),
0,
image.naturalHeight - crop.height,
),
};
this.scene.mutateElement(croppingElement, {
crop: nextCrop,
});
// Update last coords for next move and set drag occurred flag
pointerDownState.lastCoords.x = pointerCoords.x;
pointerDownState.lastCoords.y = pointerCoords.y;
// @ts-ignore - we need to set this for proper shift direction locking
pointerDownState.drag.hasOccurred = true;
return;
}
}
}
// #endregion dedicated crop position movement
if (this.state.activeLockedId) { if (this.state.activeLockedId) {
this.setState({ this.setState({
activeLockedId: null, activeLockedId: null,
@ -8469,7 +8322,8 @@ class App extends React.Component<AppProps, AppState> {
if ( if (
croppingElement && croppingElement &&
isImageElement(croppingElement) && isImageElement(croppingElement) &&
croppingElement.crop !== null croppingElement.crop !== null &&
pointerDownState.hit.element === croppingElement
) { ) {
const crop = croppingElement.crop; const crop = croppingElement.crop;
const image = const image =
@ -8477,23 +8331,6 @@ class App extends React.Component<AppProps, AppState> {
this.imageCache.get(croppingElement.fileId)?.image; this.imageCache.get(croppingElement.fileId)?.image;
if (image && !(image instanceof Promise)) { if (image && !(image instanceof Promise)) {
// Check if we're hitting either the cropped element or the uncropped area
const hitCroppedElement =
pointerDownState.hit.element === croppingElement;
const uncroppedElement = getUncroppedImageElement(
croppingElement,
elementsMap,
);
const hitUncroppedArea =
!hitCroppedElement &&
hitElementItself({
point: pointFrom(pointerCoords.x, pointerCoords.y),
element: uncroppedElement,
threshold: this.getElementHitThreshold(uncroppedElement),
elementsMap,
});
if (hitCroppedElement || hitUncroppedArea) {
const instantDragOffset = vectorScale( const instantDragOffset = vectorScale(
vector( vector(
pointerCoords.x - lastPointerCoords.x, pointerCoords.x - lastPointerCoords.x,
@ -8502,21 +8339,6 @@ class App extends React.Component<AppProps, AppState> {
Math.max(this.state.zoom.value, 2), Math.max(this.state.zoom.value, 2),
); );
// Apply shift key constraint for directional movement
let constrainedDragOffset = instantDragOffset;
if (event.shiftKey) {
const absX = Math.abs(instantDragOffset[0]);
const absY = Math.abs(instantDragOffset[1]);
if (absX > absY) {
// Horizontal movement only
constrainedDragOffset = vector(instantDragOffset[0], 0);
} else {
// Vertical movement only
constrainedDragOffset = vector(0, instantDragOffset[1]);
}
}
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords( const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
croppingElement, croppingElement,
elementsMap, elementsMap,
@ -8550,10 +8372,10 @@ class App extends React.Component<AppProps, AppState> {
vectorSubtract(bottomLeft, topLeft), vectorSubtract(bottomLeft, topLeft),
); );
// project instantDragOffset onto leftEdge and topEdge to decompose // project instantDrafOffset onto leftEdge and topEdge to decompose
const offsetVector = vector( const offsetVector = vector(
vectorDot(constrainedDragOffset, topEdge), vectorDot(instantDragOffset, topEdge),
vectorDot(constrainedDragOffset, leftEdge), vectorDot(instantDragOffset, leftEdge),
); );
const nextCrop = { const nextCrop = {
@ -8580,7 +8402,6 @@ class App extends React.Component<AppProps, AppState> {
} }
} }
} }
}
// Snap cache *must* be synchronously popuplated before initial drag, // Snap cache *must* be synchronously popuplated before initial drag,
// otherwise the first drag even will not snap, causing a jump before // otherwise the first drag even will not snap, causing a jump before
@ -9065,15 +8886,10 @@ class App extends React.Component<AppProps, AppState> {
isCropping, isCropping,
} = this.state; } = this.state;
// Clean up crop position movement flag
const wasCropPositionMovement =
pointerDownState.cropPositionMovement.enabled;
this.setState((prevState) => ({ this.setState((prevState) => ({
isResizing: false, isResizing: false,
isRotating: false, isRotating: false,
// Keep isCropping true if we were doing crop position movement isCropping: false,
isCropping: wasCropPositionMovement,
resizingElement: null, resizingElement: null,
selectionElement: null, selectionElement: null,
frameToHighlight: null, frameToHighlight: null,
@ -9614,10 +9430,8 @@ class App extends React.Component<AppProps, AppState> {
!croppingElementId || !croppingElementId ||
// in the cropping mode // in the cropping mode
(croppingElementId && (croppingElementId &&
// not cropping and no hit element (but not doing crop position movement) // not cropping and no hit element
((!hitElement && ((!hitElement && !isCropping) ||
!isCropping &&
!pointerDownState.cropPositionMovement.enabled) ||
// hitting something else // hitting something else
(hitElement && hitElement.id !== croppingElementId))) (hitElement && hitElement.id !== croppingElementId)))
) { ) {

View File

@ -797,10 +797,6 @@ export type PointerDownState = Readonly<{
boxSelection: { boxSelection: {
hasOccurred: boolean; hasOccurred: boolean;
}; };
cropPositionMovement: {
croppingElementId?: string;
enabled: boolean;
};
}>; }>;
export type UnsubscribeCallback = () => void; export type UnsubscribeCallback = () => void;