Compare commits
3 Commits
master
...
ryan-di/cr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c79893608a | ||
|
|
a2cf15db9c | ||
|
|
8d3195e350 |
@ -124,6 +124,11 @@ export const getDefaultAppState = (): Omit<
|
|||||||
searchMatches: null,
|
searchMatches: null,
|
||||||
lockedMultiSelections: {},
|
lockedMultiSelections: {},
|
||||||
activeLockedId: null,
|
activeLockedId: null,
|
||||||
|
cropPositionMovement: {
|
||||||
|
enabled: false,
|
||||||
|
croppingElementId: undefined,
|
||||||
|
directionLock: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -249,6 +254,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
searchMatches: { browser: false, export: false, server: false },
|
searchMatches: { browser: false, export: false, server: false },
|
||||||
lockedMultiSelections: { browser: true, export: true, server: true },
|
lockedMultiSelections: { browser: true, export: true, server: true },
|
||||||
activeLockedId: { browser: false, export: false, server: false },
|
activeLockedId: { browser: false, export: false, server: false },
|
||||||
|
cropPositionMovement: { browser: false, export: false, server: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
|
|||||||
@ -191,6 +191,7 @@ import {
|
|||||||
FlowChartNavigator,
|
FlowChartNavigator,
|
||||||
getLinkDirectionFromKey,
|
getLinkDirectionFromKey,
|
||||||
cropElement,
|
cropElement,
|
||||||
|
getUncroppedImageElement,
|
||||||
wrapText,
|
wrapText,
|
||||||
isElementLink,
|
isElementLink,
|
||||||
parseElementLinkFromURL,
|
parseElementLinkFromURL,
|
||||||
@ -460,6 +461,7 @@ import type {
|
|||||||
} from "../types";
|
} from "../types";
|
||||||
import type { RoughCanvas } from "roughjs/bin/canvas";
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import type { Action, ActionResult } from "../actions/types";
|
import type { Action, ActionResult } from "../actions/types";
|
||||||
|
import type { GlobalPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
@ -6541,6 +6543,44 @@ 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.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<GlobalPoint>(
|
||||||
|
pointerDownState.origin.x,
|
||||||
|
pointerDownState.origin.y,
|
||||||
|
),
|
||||||
|
element: uncroppedElement,
|
||||||
|
threshold: this.getElementHitThreshold(uncroppedElement),
|
||||||
|
elementsMap: this.scene.getNonDeletedElementsMap(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hitUncroppedArea) {
|
||||||
|
// 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 (this.handleSelectionOnPointerDown(event, pointerDownState)) {
|
if (this.handleSelectionOnPointerDown(event, pointerDownState)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -6952,6 +6992,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
boxSelection: {
|
boxSelection: {
|
||||||
hasOccurred: false,
|
hasOccurred: false,
|
||||||
},
|
},
|
||||||
|
cropPositionMovement: {
|
||||||
|
enabled: false,
|
||||||
|
croppingElementId: undefined,
|
||||||
|
directionLock: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7176,11 +7221,39 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (this.state.croppingElementId) {
|
||||||
this.state.croppingElementId &&
|
const croppingElement = this.scene
|
||||||
pointerDownState.hit.element?.id !== this.state.croppingElementId
|
.getNonDeletedElementsMap()
|
||||||
) {
|
.get(this.state.croppingElementId);
|
||||||
this.finishImageCropping();
|
if (croppingElement) {
|
||||||
|
const uncroppedElement = getUncroppedImageElement(
|
||||||
|
croppingElement as any,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
const hitUncroppedArea = hitElementItself({
|
||||||
|
point: pointFrom<GlobalPoint>(
|
||||||
|
pointerDownState.origin.x,
|
||||||
|
pointerDownState.origin.y,
|
||||||
|
),
|
||||||
|
element: uncroppedElement,
|
||||||
|
threshold: this.getElementHitThreshold(uncroppedElement),
|
||||||
|
elementsMap: this.scene.getNonDeletedElementsMap(),
|
||||||
|
});
|
||||||
|
if (!hitUncroppedArea) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
} else {
|
||||||
|
// ensure the image remains selected so crop handles are rendered
|
||||||
|
if (
|
||||||
|
(!this.state.selectedElementIds ||
|
||||||
|
Object.keys(this.state.selectedElementIds).length === 0) &&
|
||||||
|
this.state.croppingElementId
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
selectedElementIds: { [this.state.croppingElementId]: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pointerDownState.hit.element) {
|
if (pointerDownState.hit.element) {
|
||||||
@ -7206,6 +7279,7 @@ 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 &&
|
||||||
@ -8029,6 +8103,165 @@ 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 transformHandleType = pointerDownState.resize.handleType;
|
||||||
|
|
||||||
|
if (!transformHandleType) {
|
||||||
|
const crop = croppingElement.crop;
|
||||||
|
const image =
|
||||||
|
isInitializedImageElement(croppingElement) &&
|
||||||
|
this.imageCache.get(croppingElement.fileId)?.image;
|
||||||
|
|
||||||
|
if (image && !(image instanceof Promise)) {
|
||||||
|
// calculate total drag offset from the original pointer down position
|
||||||
|
const totalDragOffset = {
|
||||||
|
x: pointerCoords.x - pointerDownState.origin.x,
|
||||||
|
y: pointerCoords.y - pointerDownState.origin.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
// apply shift key constraint for directional movement
|
||||||
|
const threshold = 20;
|
||||||
|
let snappingToOrigin = false;
|
||||||
|
if (event.shiftKey) {
|
||||||
|
if (!pointerDownState.cropPositionMovement.directionLock) {
|
||||||
|
if (
|
||||||
|
Math.abs(totalDragOffset.x) > threshold ||
|
||||||
|
Math.abs(totalDragOffset.y) > threshold
|
||||||
|
) {
|
||||||
|
pointerDownState.cropPositionMovement.directionLock =
|
||||||
|
Math.abs(totalDragOffset.x) > Math.abs(totalDragOffset.y)
|
||||||
|
? "x"
|
||||||
|
: "y";
|
||||||
|
} else {
|
||||||
|
// if within threshold and not locked, always snap to origin
|
||||||
|
snappingToOrigin = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if user moves back within threshold, unlock and snap back
|
||||||
|
if (
|
||||||
|
Math.abs(totalDragOffset.x) < threshold &&
|
||||||
|
Math.abs(totalDragOffset.y) < threshold
|
||||||
|
) {
|
||||||
|
pointerDownState.cropPositionMovement.directionLock = null;
|
||||||
|
snappingToOrigin = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pointerDownState.cropPositionMovement.directionLock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snappingToOrigin) {
|
||||||
|
totalDragOffset.x = 0;
|
||||||
|
totalDragOffset.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointerDownState.cropPositionMovement.directionLock === "x") {
|
||||||
|
totalDragOffset.y = 0;
|
||||||
|
} else if (
|
||||||
|
pointerDownState.cropPositionMovement.directionLock === "y"
|
||||||
|
) {
|
||||||
|
totalDragOffset.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scale the drag offset
|
||||||
|
const scaledDragOffset = vectorScale(
|
||||||
|
vector(totalDragOffset.x, totalDragOffset.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 scaledDragOffset onto leftEdge and topEdge to decompose
|
||||||
|
const offsetVector = vector(
|
||||||
|
vectorDot(scaledDragOffset, topEdge),
|
||||||
|
vectorDot(scaledDragOffset, leftEdge),
|
||||||
|
);
|
||||||
|
|
||||||
|
// get the original crop from when the drag started
|
||||||
|
const originalCroppingElement =
|
||||||
|
pointerDownState.originalElements.get(croppingElement.id) as
|
||||||
|
| ExcalidrawImageElement
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const originalCrop = originalCroppingElement?.crop || crop;
|
||||||
|
|
||||||
|
const nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: clamp(
|
||||||
|
originalCrop.x -
|
||||||
|
offsetVector[0] * Math.sign(croppingElement.scale[0]),
|
||||||
|
0,
|
||||||
|
image.naturalWidth - crop.width,
|
||||||
|
),
|
||||||
|
y: clamp(
|
||||||
|
originalCrop.y -
|
||||||
|
offsetVector[1] * Math.sign(croppingElement.scale[1]),
|
||||||
|
0,
|
||||||
|
image.naturalHeight - crop.height,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.scene.mutateElement(croppingElement, {
|
||||||
|
crop: nextCrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
// set drag occurred flag for consistency
|
||||||
|
pointerDownState.drag.hasOccurred = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.activeLockedId) {
|
if (this.state.activeLockedId) {
|
||||||
this.setState({
|
this.setState({
|
||||||
activeLockedId: null,
|
activeLockedId: null,
|
||||||
@ -8313,96 +8546,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #region move crop region
|
|
||||||
if (this.state.croppingElementId) {
|
|
||||||
const croppingElement = this.scene
|
|
||||||
.getNonDeletedElementsMap()
|
|
||||||
.get(this.state.croppingElementId);
|
|
||||||
|
|
||||||
if (
|
|
||||||
croppingElement &&
|
|
||||||
isImageElement(croppingElement) &&
|
|
||||||
croppingElement.crop !== null &&
|
|
||||||
pointerDownState.hit.element === croppingElement
|
|
||||||
) {
|
|
||||||
const crop = croppingElement.crop;
|
|
||||||
const image =
|
|
||||||
isInitializedImageElement(croppingElement) &&
|
|
||||||
this.imageCache.get(croppingElement.fileId)?.image;
|
|
||||||
|
|
||||||
if (image && !(image instanceof Promise)) {
|
|
||||||
const instantDragOffset = vectorScale(
|
|
||||||
vector(
|
|
||||||
pointerCoords.x - lastPointerCoords.x,
|
|
||||||
pointerCoords.y - lastPointerCoords.y,
|
|
||||||
),
|
|
||||||
Math.max(this.state.zoom.value, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
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 instantDrafOffset onto leftEdge and topEdge to decompose
|
|
||||||
const offsetVector = vector(
|
|
||||||
vectorDot(instantDragOffset, topEdge),
|
|
||||||
vectorDot(instantDragOffset, 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,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
// it snaps to its position if previously snapped already.
|
// it snaps to its position if previously snapped already.
|
||||||
@ -8886,6 +9029,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,
|
||||||
@ -9425,17 +9572,46 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// click outside the cropping region to exit
|
// click outside the cropping region to exit
|
||||||
if (
|
if (croppingElementId) {
|
||||||
// not in the cropping mode at all
|
const croppingElement = this.scene
|
||||||
!croppingElementId ||
|
.getNonDeletedElementsMap()
|
||||||
// in the cropping mode
|
.get(croppingElementId);
|
||||||
(croppingElementId &&
|
|
||||||
// not cropping and no hit element
|
if (
|
||||||
((!hitElement && !isCropping) ||
|
croppingElement &&
|
||||||
// hitting something else
|
isImageElement(croppingElement) &&
|
||||||
(hitElement && hitElement.id !== croppingElementId)))
|
croppingElement.crop
|
||||||
) {
|
) {
|
||||||
this.finishImageCropping();
|
const uncroppedElement = getUncroppedImageElement(
|
||||||
|
croppingElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
const pointer = pointFrom<GlobalPoint>(sceneCoords.x, sceneCoords.y);
|
||||||
|
const hitUncroppedArea = hitElementItself({
|
||||||
|
point: pointer,
|
||||||
|
element: uncroppedElement,
|
||||||
|
threshold: this.getElementHitThreshold(uncroppedElement),
|
||||||
|
elementsMap: this.scene.getNonDeletedElementsMap(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hitUncroppedArea) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
} else {
|
||||||
|
// ensure the image remains selected so crop handles are rendered
|
||||||
|
if (
|
||||||
|
(!this.state.selectedElementIds ||
|
||||||
|
Object.keys(this.state.selectedElementIds).length === 0) &&
|
||||||
|
this.state.croppingElementId
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
selectedElementIds: { [this.state.croppingElementId]: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fallback: if not in cropping mode or no cropping element, finish cropping
|
||||||
|
this.finishImageCropping();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pointerStart = this.lastPointerDownEvent;
|
const pointerStart = this.lastPointerDownEvent;
|
||||||
@ -9646,7 +9822,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
((hitElement &&
|
((hitElement &&
|
||||||
hitElementBoundingBoxOnly(
|
hitElementBoundingBoxOnly(
|
||||||
{
|
{
|
||||||
point: pointFrom(
|
point: pointFrom<GlobalPoint>(
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
),
|
),
|
||||||
@ -10598,6 +10774,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log("hi");
|
||||||
|
|
||||||
const croppingElement = this.scene
|
const croppingElement = this.scene
|
||||||
.getNonDeletedElementsMap()
|
.getNonDeletedElementsMap()
|
||||||
.get(this.state.croppingElementId);
|
.get(this.state.croppingElementId);
|
||||||
|
|||||||
@ -444,6 +444,11 @@ export interface AppState {
|
|||||||
// as elements are unlocked, we remove the groupId from the elements
|
// as elements are unlocked, we remove the groupId from the elements
|
||||||
// and also remove groupId from this map
|
// and also remove groupId from this map
|
||||||
lockedMultiSelections: { [groupId: string]: true };
|
lockedMultiSelections: { [groupId: string]: true };
|
||||||
|
cropPositionMovement: {
|
||||||
|
croppingElementId?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
directionLock: "x" | "y" | null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchMatch = {
|
export type SearchMatch = {
|
||||||
@ -797,6 +802,11 @@ export type PointerDownState = Readonly<{
|
|||||||
boxSelection: {
|
boxSelection: {
|
||||||
hasOccurred: boolean;
|
hasOccurred: boolean;
|
||||||
};
|
};
|
||||||
|
cropPositionMovement: {
|
||||||
|
croppingElementId?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
directionLock: "x" | "y" | null;
|
||||||
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type UnsubscribeCallback = () => void;
|
export type UnsubscribeCallback = () => void;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user