mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
fix: remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap (#7663)
* fix: remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap * lint * fix * use non deleted elements where possible * use non deleted elements map in actions * pass elementsMap instead of array to elementOverlapsWithFrame * lint * fix * pass elementsMap to getElementsCorners * pass elementsMap to getEligibleElementsForBinding * pass elementsMap in bindOrUnbindSelectedElements and unbindLinearElements * pass elementsMap in elementsAreInFrameBounds,elementOverlapsWithFrame,isCursorInFrame,getElementsInResizingFrame * pass elementsMap in getElementsWithinSelection, getElementsCompletelyInFrame, isElementContainingFrame, getElementsInNewFrame * pass elementsMap to getElementWithTransformHandleType * pass elementsMap to getVisibleGaps, getMaximumGroups,getReferenceSnapPoints,snapDraggedElements * lint * pass elementsMap to bindTextToShapeAfterDuplication,bindLinearElementToElement,getTextBindableContainerAtPosition * revert changes for bindTextToShapeAfterDuplication
This commit is contained in:
parent
73bf50e8a8
commit
47f87f4ecb
36 changed files with 779 additions and 270 deletions
|
@ -1,6 +1,6 @@
|
|||
import { AppState } from "../types";
|
||||
import { sceneCoordsToViewportCoords } from "../utils";
|
||||
import { NonDeletedExcalidrawElement } from "./types";
|
||||
import { ElementsMap, NonDeletedExcalidrawElement } from "./types";
|
||||
import { getElementAbsoluteCoords } from ".";
|
||||
import { useExcalidrawAppState } from "../components/App";
|
||||
|
||||
|
@ -11,8 +11,9 @@ const CONTAINER_PADDING = 5;
|
|||
const getContainerCoords = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
||||
{ sceneX: x1 + element.width, sceneY: y1 },
|
||||
appState,
|
||||
|
@ -25,9 +26,11 @@ const getContainerCoords = (
|
|||
export const ElementCanvasButtons = ({
|
||||
children,
|
||||
element,
|
||||
elementsMap,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
element: NonDeletedExcalidrawElement;
|
||||
elementsMap: ElementsMap;
|
||||
}) => {
|
||||
const appState = useExcalidrawAppState();
|
||||
|
||||
|
@ -42,7 +45,7 @@ export const ElementCanvasButtons = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const { x, y } = getContainerCoords(element, appState);
|
||||
const { x, y } = getContainerCoords(element, appState, elementsMap);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
import { getEmbedLink, embeddableURLValidator } from "./embeddable";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import {
|
||||
ElementsMap,
|
||||
ExcalidrawEmbeddableElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./types";
|
||||
|
@ -60,12 +61,14 @@ const embeddableLinkCache = new Map<
|
|||
|
||||
export const Hyperlink = ({
|
||||
element,
|
||||
elementsMap,
|
||||
setAppState,
|
||||
onLinkOpen,
|
||||
setToast,
|
||||
updateEmbedValidationStatus,
|
||||
}: {
|
||||
element: NonDeletedExcalidrawElement;
|
||||
elementsMap: ElementsMap;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
||||
setToast: (
|
||||
|
@ -182,7 +185,7 @@ export const Hyperlink = ({
|
|||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
const shouldHide = shouldHideLinkPopup(element, appState, [
|
||||
const shouldHide = shouldHideLinkPopup(element, elementsMap, appState, [
|
||||
event.clientX,
|
||||
event.clientY,
|
||||
]) as boolean;
|
||||
|
@ -199,7 +202,7 @@ export const Hyperlink = ({
|
|||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
}, [appState, element, isEditing, setAppState]);
|
||||
}, [appState, element, isEditing, setAppState, elementsMap]);
|
||||
|
||||
const handleRemove = useCallback(() => {
|
||||
trackEvent("hyperlink", "delete");
|
||||
|
@ -214,7 +217,7 @@ export const Hyperlink = ({
|
|||
trackEvent("hyperlink", "edit", "popup-ui");
|
||||
setAppState({ showHyperlinkPopup: "editor" });
|
||||
};
|
||||
const { x, y } = getCoordsForPopover(element, appState);
|
||||
const { x, y } = getCoordsForPopover(element, appState, elementsMap);
|
||||
if (
|
||||
appState.contextMenu ||
|
||||
appState.draggingElement ||
|
||||
|
@ -324,8 +327,9 @@ export const Hyperlink = ({
|
|||
const getCoordsForPopover = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
||||
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
||||
appState,
|
||||
|
@ -430,11 +434,12 @@ export const getLinkHandleFromCoords = (
|
|||
|
||||
export const isPointHittingLinkIcon = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
[x, y]: Point,
|
||||
) => {
|
||||
const threshold = 4 / appState.zoom.value;
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||
[x1, y1, x2, y2],
|
||||
element.angle,
|
||||
|
@ -450,6 +455,7 @@ export const isPointHittingLinkIcon = (
|
|||
|
||||
export const isPointHittingLink = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
[x, y]: Point,
|
||||
isMobile: boolean,
|
||||
|
@ -461,23 +467,30 @@ export const isPointHittingLink = (
|
|||
if (
|
||||
!isMobile &&
|
||||
appState.viewModeEnabled &&
|
||||
isPointHittingElementBoundingBox(element, [x, y], threshold, null)
|
||||
isPointHittingElementBoundingBox(
|
||||
element,
|
||||
elementsMap,
|
||||
[x, y],
|
||||
threshold,
|
||||
null,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return isPointHittingLinkIcon(element, appState, [x, y]);
|
||||
return isPointHittingLinkIcon(element, elementsMap, appState, [x, y]);
|
||||
};
|
||||
|
||||
let HYPERLINK_TOOLTIP_TIMEOUT_ID: number | null = null;
|
||||
export const showHyperlinkTooltip = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
|
||||
clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
|
||||
}
|
||||
HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(
|
||||
() => renderTooltip(element, appState),
|
||||
() => renderTooltip(element, appState, elementsMap),
|
||||
HYPERLINK_TOOLTIP_DELAY,
|
||||
);
|
||||
};
|
||||
|
@ -485,6 +498,7 @@ export const showHyperlinkTooltip = (
|
|||
const renderTooltip = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
if (!element.link) {
|
||||
return;
|
||||
|
@ -496,7 +510,7 @@ const renderTooltip = (
|
|||
tooltipDiv.style.maxWidth = "20rem";
|
||||
tooltipDiv.textContent = element.link;
|
||||
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
|
||||
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||
[x1, y1, x2, y2],
|
||||
|
@ -535,6 +549,7 @@ export const hideHyperlinkToolip = () => {
|
|||
|
||||
export const shouldHideLinkPopup = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
[clientX, clientY]: Point,
|
||||
): Boolean => {
|
||||
|
@ -546,11 +561,17 @@ export const shouldHideLinkPopup = (
|
|||
const threshold = 15 / appState.zoom.value;
|
||||
// hitbox to prevent hiding when hovered in element bounding box
|
||||
if (
|
||||
isPointHittingElementBoundingBox(element, [sceneX, sceneY], threshold, null)
|
||||
isPointHittingElementBoundingBox(
|
||||
element,
|
||||
elementsMap,
|
||||
[sceneX, sceneY],
|
||||
threshold,
|
||||
null,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const [x1, y1, x2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
// hit box to prevent hiding when hovered in the vertical area between element and popover
|
||||
if (
|
||||
sceneX >= x1 &&
|
||||
|
@ -561,7 +582,11 @@ export const shouldHideLinkPopup = (
|
|||
return false;
|
||||
}
|
||||
// hit box to prevent hiding when hovered around popover within threshold
|
||||
const { x: popoverX, y: popoverY } = getCoordsForPopover(element, appState);
|
||||
const { x: popoverX, y: popoverY } = getCoordsForPopover(
|
||||
element,
|
||||
appState,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
if (
|
||||
clientX >= popoverX - threshold &&
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
NonDeletedExcalidrawElement,
|
||||
PointBinding,
|
||||
ExcalidrawElement,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
import { getElementAtPosition } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
|
@ -66,6 +67,7 @@ export const bindOrUnbindLinearElement = (
|
|||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||
endBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||
const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||
|
@ -76,6 +78,7 @@ export const bindOrUnbindLinearElement = (
|
|||
"start",
|
||||
boundToElementIds,
|
||||
unboundFromElementIds,
|
||||
elementsMap,
|
||||
);
|
||||
bindOrUnbindLinearElementEdge(
|
||||
linearElement,
|
||||
|
@ -84,6 +87,7 @@ export const bindOrUnbindLinearElement = (
|
|||
"end",
|
||||
boundToElementIds,
|
||||
unboundFromElementIds,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
const onlyUnbound = Array.from(unboundFromElementIds).filter(
|
||||
|
@ -111,6 +115,7 @@ const bindOrUnbindLinearElementEdge = (
|
|||
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||
// Is mutated
|
||||
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
if (bindableElement !== "keep") {
|
||||
if (bindableElement != null) {
|
||||
|
@ -127,7 +132,12 @@ const bindOrUnbindLinearElementEdge = (
|
|||
: startOrEnd === "start" ||
|
||||
otherEdgeBindableElement.id !== bindableElement.id)
|
||||
) {
|
||||
bindLinearElement(linearElement, bindableElement, startOrEnd);
|
||||
bindLinearElement(
|
||||
linearElement,
|
||||
bindableElement,
|
||||
startOrEnd,
|
||||
elementsMap,
|
||||
);
|
||||
boundToElementIds.add(bindableElement.id);
|
||||
}
|
||||
} else {
|
||||
|
@ -140,23 +150,34 @@ const bindOrUnbindLinearElementEdge = (
|
|||
};
|
||||
|
||||
export const bindOrUnbindSelectedElements = (
|
||||
elements: NonDeleted<ExcalidrawElement>[],
|
||||
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
elements.forEach((element) => {
|
||||
if (isBindingElement(element)) {
|
||||
selectedElements.forEach((selectedElement) => {
|
||||
if (isBindingElement(selectedElement)) {
|
||||
bindOrUnbindLinearElement(
|
||||
element,
|
||||
getElligibleElementForBindingElement(element, "start"),
|
||||
getElligibleElementForBindingElement(element, "end"),
|
||||
selectedElement,
|
||||
getElligibleElementForBindingElement(
|
||||
selectedElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
),
|
||||
getElligibleElementForBindingElement(
|
||||
selectedElement,
|
||||
"end",
|
||||
elementsMap,
|
||||
),
|
||||
elementsMap,
|
||||
);
|
||||
} else if (isBindableElement(element)) {
|
||||
maybeBindBindableElement(element);
|
||||
} else if (isBindableElement(selectedElement)) {
|
||||
maybeBindBindableElement(selectedElement, elementsMap);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const maybeBindBindableElement = (
|
||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
|
||||
([linearElement, where]) =>
|
||||
|
@ -164,6 +185,7 @@ const maybeBindBindableElement = (
|
|||
linearElement,
|
||||
where === "end" ? "keep" : bindableElement,
|
||||
where === "start" ? "keep" : bindableElement,
|
||||
elementsMap,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
@ -173,9 +195,15 @@ export const maybeBindLinearElement = (
|
|||
appState: AppState,
|
||||
scene: Scene,
|
||||
pointerCoords: { x: number; y: number },
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
if (appState.startBoundElement != null) {
|
||||
bindLinearElement(linearElement, appState.startBoundElement, "start");
|
||||
bindLinearElement(
|
||||
linearElement,
|
||||
appState.startBoundElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
|
||||
if (
|
||||
|
@ -186,7 +214,7 @@ export const maybeBindLinearElement = (
|
|||
"end",
|
||||
)
|
||||
) {
|
||||
bindLinearElement(linearElement, hoveredElement, "end");
|
||||
bindLinearElement(linearElement, hoveredElement, "end", elementsMap);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -194,11 +222,17 @@ export const bindLinearElement = (
|
|||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
hoveredElement: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
mutateElement(linearElement, {
|
||||
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
||||
elementId: hoveredElement.id,
|
||||
...calculateFocusAndGap(linearElement, hoveredElement, startOrEnd),
|
||||
...calculateFocusAndGap(
|
||||
linearElement,
|
||||
hoveredElement,
|
||||
startOrEnd,
|
||||
elementsMap,
|
||||
),
|
||||
} as PointBinding,
|
||||
});
|
||||
|
||||
|
@ -240,10 +274,11 @@ export const isLinearElementSimpleAndAlreadyBound = (
|
|||
|
||||
export const unbindLinearElements = (
|
||||
elements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
elements.forEach((element) => {
|
||||
if (isBindingElement(element)) {
|
||||
bindOrUnbindLinearElement(element, null, null);
|
||||
bindOrUnbindLinearElement(element, null, null, elementsMap);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -272,7 +307,11 @@ export const getHoveredElementForBinding = (
|
|||
scene.getNonDeletedElements(),
|
||||
(element) =>
|
||||
isBindableElement(element, false) &&
|
||||
bindingBorderTest(element, pointerCoords),
|
||||
bindingBorderTest(
|
||||
element,
|
||||
pointerCoords,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
);
|
||||
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
|
||||
};
|
||||
|
@ -281,21 +320,33 @@ const calculateFocusAndGap = (
|
|||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
hoveredElement: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
): { focus: number; gap: number } => {
|
||||
const direction = startOrEnd === "start" ? -1 : 1;
|
||||
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
||||
const adjacentPointIndex = edgePointIndex - direction;
|
||||
|
||||
const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
linearElement,
|
||||
edgePointIndex,
|
||||
elementsMap,
|
||||
);
|
||||
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
linearElement,
|
||||
adjacentPointIndex,
|
||||
elementsMap,
|
||||
);
|
||||
return {
|
||||
focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
|
||||
gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)),
|
||||
focus: determineFocusDistance(
|
||||
hoveredElement,
|
||||
adjacentPoint,
|
||||
edgePoint,
|
||||
elementsMap,
|
||||
),
|
||||
gap: Math.max(
|
||||
1,
|
||||
distanceToBindableElement(hoveredElement, edgePoint, elementsMap),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -306,6 +357,8 @@ const calculateFocusAndGap = (
|
|||
// in explicitly.
|
||||
export const updateBoundElements = (
|
||||
changedElement: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
|
||||
options?: {
|
||||
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
||||
newSize?: { width: number; height: number };
|
||||
|
@ -355,12 +408,14 @@ export const updateBoundElements = (
|
|||
"start",
|
||||
startBinding,
|
||||
changedElement as ExcalidrawBindableElement,
|
||||
elementsMap,
|
||||
);
|
||||
updateBoundPoint(
|
||||
element,
|
||||
"end",
|
||||
endBinding,
|
||||
changedElement as ExcalidrawBindableElement,
|
||||
elementsMap,
|
||||
);
|
||||
const boundText = getBoundTextElement(
|
||||
element,
|
||||
|
@ -393,6 +448,7 @@ const updateBoundPoint = (
|
|||
startOrEnd: "start" | "end",
|
||||
binding: PointBinding | null | undefined,
|
||||
changedElement: ExcalidrawBindableElement,
|
||||
elementsMap: ElementsMap,
|
||||
): void => {
|
||||
if (
|
||||
binding == null ||
|
||||
|
@ -414,11 +470,13 @@ const updateBoundPoint = (
|
|||
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
linearElement,
|
||||
adjacentPointIndex,
|
||||
elementsMap,
|
||||
);
|
||||
const focusPointAbsolute = determineFocusPoint(
|
||||
bindingElement,
|
||||
binding.focus,
|
||||
adjacentPoint,
|
||||
elementsMap,
|
||||
);
|
||||
let newEdgePoint;
|
||||
// The linear element was not originally pointing inside the bound shape,
|
||||
|
@ -431,6 +489,7 @@ const updateBoundPoint = (
|
|||
adjacentPoint,
|
||||
focusPointAbsolute,
|
||||
binding.gap,
|
||||
elementsMap,
|
||||
);
|
||||
if (intersections.length === 0) {
|
||||
// This should never happen, since focusPoint should always be
|
||||
|
@ -449,6 +508,7 @@ const updateBoundPoint = (
|
|||
point: LinearElementEditor.pointFromAbsoluteCoords(
|
||||
linearElement,
|
||||
newEdgePoint,
|
||||
elementsMap,
|
||||
),
|
||||
},
|
||||
],
|
||||
|
@ -480,12 +540,14 @@ const maybeCalculateNewGapWhenScaling = (
|
|||
// TODO: this is a bottleneck, optimise
|
||||
export const getEligibleElementsForBinding = (
|
||||
elements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: ElementsMap,
|
||||
): SuggestedBinding[] => {
|
||||
const includedElementIds = new Set(elements.map(({ id }) => id));
|
||||
return elements.flatMap((element) =>
|
||||
isBindingElement(element, false)
|
||||
? (getElligibleElementsForBindingElement(
|
||||
element as NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap,
|
||||
).filter(
|
||||
(element) => !includedElementIds.has(element.id),
|
||||
) as SuggestedBinding[])
|
||||
|
@ -499,10 +561,11 @@ export const getEligibleElementsForBinding = (
|
|||
|
||||
const getElligibleElementsForBindingElement = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
): NonDeleted<ExcalidrawBindableElement>[] => {
|
||||
return [
|
||||
getElligibleElementForBindingElement(linearElement, "start"),
|
||||
getElligibleElementForBindingElement(linearElement, "end"),
|
||||
getElligibleElementForBindingElement(linearElement, "start", elementsMap),
|
||||
getElligibleElementForBindingElement(linearElement, "end", elementsMap),
|
||||
].filter(
|
||||
(element): element is NonDeleted<ExcalidrawBindableElement> =>
|
||||
element != null,
|
||||
|
@ -512,9 +575,10 @@ const getElligibleElementsForBindingElement = (
|
|||
const getElligibleElementForBindingElement = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||
return getHoveredElementForBinding(
|
||||
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
||||
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
|
||||
Scene.getScene(linearElement)!,
|
||||
);
|
||||
};
|
||||
|
@ -522,17 +586,23 @@ const getElligibleElementForBindingElement = (
|
|||
const getLinearElementEdgeCoors = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
): { x: number; y: number } => {
|
||||
const index = startOrEnd === "start" ? 0 : -1;
|
||||
return tupleToCoors(
|
||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(linearElement, index),
|
||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
linearElement,
|
||||
index,
|
||||
elementsMap,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const getElligibleElementsForBindableElementAndWhere = (
|
||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||
): SuggestedPointBinding[] => {
|
||||
return Scene.getScene(bindableElement)!
|
||||
const scene = Scene.getScene(bindableElement)!;
|
||||
return scene
|
||||
.getNonDeletedElements()
|
||||
.map((element) => {
|
||||
if (!isBindingElement(element, false)) {
|
||||
|
@ -542,11 +612,13 @@ const getElligibleElementsForBindableElementAndWhere = (
|
|||
element,
|
||||
"start",
|
||||
bindableElement,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
|
||||
element,
|
||||
"end",
|
||||
bindableElement,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (!canBindStart && !canBindEnd) {
|
||||
return null;
|
||||
|
@ -564,6 +636,7 @@ const isLinearElementEligibleForNewBindingByBindable = (
|
|||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startOrEnd: "start" | "end",
|
||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||
elementsMap: ElementsMap,
|
||||
): boolean => {
|
||||
const existingBinding =
|
||||
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
|
||||
|
@ -576,7 +649,8 @@ const isLinearElementEligibleForNewBindingByBindable = (
|
|||
) &&
|
||||
bindingBorderTest(
|
||||
bindableElement,
|
||||
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
||||
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
|
||||
elementsMap,
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ROUNDNESS } from "../constants";
|
||||
import { arrayToMap } from "../utils";
|
||||
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
|
||||
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
|
||||
|
||||
|
@ -35,26 +36,26 @@ const _ce = ({
|
|||
|
||||
describe("getElementAbsoluteCoords", () => {
|
||||
it("test x1 coordinate", () => {
|
||||
const [x1] = getElementAbsoluteCoords(_ce({ x: 10, y: 0, w: 10, h: 0 }));
|
||||
const element = _ce({ x: 10, y: 20, w: 10, h: 0 });
|
||||
const [x1] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||
expect(x1).toEqual(10);
|
||||
});
|
||||
|
||||
it("test x2 coordinate", () => {
|
||||
const [, , x2] = getElementAbsoluteCoords(
|
||||
_ce({ x: 10, y: 0, w: 10, h: 0 }),
|
||||
);
|
||||
const element = _ce({ x: 10, y: 20, w: 10, h: 0 });
|
||||
const [, , x2] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||
expect(x2).toEqual(20);
|
||||
});
|
||||
|
||||
it("test y1 coordinate", () => {
|
||||
const [, y1] = getElementAbsoluteCoords(_ce({ x: 0, y: 10, w: 0, h: 10 }));
|
||||
const element = _ce({ x: 0, y: 10, w: 0, h: 10 });
|
||||
const [, y1] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||
expect(y1).toEqual(10);
|
||||
});
|
||||
|
||||
it("test y2 coordinate", () => {
|
||||
const [, , , y2] = getElementAbsoluteCoords(
|
||||
_ce({ x: 0, y: 10, w: 0, h: 10 }),
|
||||
);
|
||||
const element = _ce({ x: 0, y: 10, w: 0, h: 10 });
|
||||
const [, , , y2] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||
expect(y2).toEqual(20);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -102,8 +102,10 @@ export class ElementBounds {
|
|||
): Bounds {
|
||||
let bounds: Bounds;
|
||||
|
||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
|
||||
|
||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
if (isFreeDrawElement(element)) {
|
||||
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
||||
element.points.map(([x, y]) =>
|
||||
|
@ -159,10 +161,9 @@ export class ElementBounds {
|
|||
// This set of functions retrieves the absolute position of the 4 points.
|
||||
export const getElementAbsoluteCoords = (
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
includeBoundText: boolean = false,
|
||||
): [number, number, number, number, number, number] => {
|
||||
const elementsMap =
|
||||
Scene.getScene(element)?.getElementsMapIncludingDeleted() || new Map();
|
||||
if (isFreeDrawElement(element)) {
|
||||
return getFreeDrawElementAbsoluteCoords(element);
|
||||
} else if (isLinearElement(element)) {
|
||||
|
@ -179,6 +180,7 @@ export const getElementAbsoluteCoords = (
|
|||
const coords = LinearElementEditor.getBoundTextElementPosition(
|
||||
container,
|
||||
element as ExcalidrawTextElementWithContainer,
|
||||
elementsMap,
|
||||
);
|
||||
return [
|
||||
coords.x,
|
||||
|
@ -207,8 +209,12 @@ export const getElementAbsoluteCoords = (
|
|||
*/
|
||||
export const getElementLineSegments = (
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
): [Point, Point][] => {
|
||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
const center: Point = [cx, cy];
|
||||
|
||||
|
@ -703,6 +709,7 @@ const getLinearElementRotatedBounds = (
|
|||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
elementsMap,
|
||||
[x, y, x, y],
|
||||
boundTextElement,
|
||||
);
|
||||
|
@ -727,6 +734,7 @@ const getLinearElementRotatedBounds = (
|
|||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
elementsMap,
|
||||
coords,
|
||||
boundTextElement,
|
||||
);
|
||||
|
|
|
@ -91,6 +91,7 @@ export const hitTest = (
|
|||
) {
|
||||
return isPointHittingElementBoundingBox(
|
||||
element,
|
||||
elementsMap,
|
||||
point,
|
||||
threshold,
|
||||
frameNameBoundsCache,
|
||||
|
@ -116,6 +117,7 @@ export const hitTest = (
|
|||
appState,
|
||||
frameNameBoundsCache,
|
||||
point,
|
||||
elementsMap,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -145,9 +147,11 @@ export const isHittingElementBoundingBoxWithoutHittingElement = (
|
|||
appState,
|
||||
frameNameBoundsCache,
|
||||
[x, y],
|
||||
elementsMap,
|
||||
) &&
|
||||
isPointHittingElementBoundingBox(
|
||||
element,
|
||||
elementsMap,
|
||||
[x, y],
|
||||
threshold,
|
||||
frameNameBoundsCache,
|
||||
|
@ -160,6 +164,7 @@ export const isHittingElementNotConsideringBoundingBox = (
|
|||
appState: AppState,
|
||||
frameNameBoundsCache: FrameNameBoundsCache | null,
|
||||
point: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): boolean => {
|
||||
const threshold = 10 / appState.zoom.value;
|
||||
const check = isTextElement(element)
|
||||
|
@ -169,6 +174,7 @@ export const isHittingElementNotConsideringBoundingBox = (
|
|||
: isNearCheck;
|
||||
return hitTestPointAgainstElement({
|
||||
element,
|
||||
elementsMap,
|
||||
point,
|
||||
threshold,
|
||||
check,
|
||||
|
@ -183,6 +189,7 @@ const isElementSelected = (
|
|||
|
||||
export const isPointHittingElementBoundingBox = (
|
||||
element: NonDeleted<ExcalidrawElement>,
|
||||
elementsMap: ElementsMap,
|
||||
[x, y]: Point,
|
||||
threshold: number,
|
||||
frameNameBoundsCache: FrameNameBoundsCache | null,
|
||||
|
@ -194,6 +201,7 @@ export const isPointHittingElementBoundingBox = (
|
|||
if (isFrameLikeElement(element)) {
|
||||
return hitTestPointAgainstElement({
|
||||
element,
|
||||
elementsMap,
|
||||
point: [x, y],
|
||||
threshold,
|
||||
check: isInsideCheck,
|
||||
|
@ -201,7 +209,7 @@ export const isPointHittingElementBoundingBox = (
|
|||
});
|
||||
}
|
||||
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const elementCenterX = (x1 + x2) / 2;
|
||||
const elementCenterY = (y1 + y2) / 2;
|
||||
// reverse rotate to take element's angle into account.
|
||||
|
@ -224,12 +232,14 @@ export const isPointHittingElementBoundingBox = (
|
|||
export const bindingBorderTest = (
|
||||
element: NonDeleted<ExcalidrawBindableElement>,
|
||||
{ x, y }: { x: number; y: number },
|
||||
elementsMap: ElementsMap,
|
||||
): boolean => {
|
||||
const threshold = maxBindingGap(element, element.width, element.height);
|
||||
const check = isOutsideCheck;
|
||||
const point: Point = [x, y];
|
||||
return hitTestPointAgainstElement({
|
||||
element,
|
||||
elementsMap,
|
||||
point,
|
||||
threshold,
|
||||
check,
|
||||
|
@ -251,6 +261,7 @@ export const maxBindingGap = (
|
|||
|
||||
type HitTestArgs = {
|
||||
element: NonDeletedExcalidrawElement;
|
||||
elementsMap: ElementsMap;
|
||||
point: Point;
|
||||
threshold: number;
|
||||
check: (distance: number, threshold: number) => boolean;
|
||||
|
@ -266,19 +277,28 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
|||
case "text":
|
||||
case "diamond":
|
||||
case "ellipse":
|
||||
const distance = distanceToBindableElement(args.element, args.point);
|
||||
const distance = distanceToBindableElement(
|
||||
args.element,
|
||||
args.point,
|
||||
args.elementsMap,
|
||||
);
|
||||
return args.check(distance, args.threshold);
|
||||
case "freedraw": {
|
||||
if (
|
||||
!args.check(
|
||||
distanceToRectangle(args.element, args.point),
|
||||
distanceToRectangle(args.element, args.point, args.elementsMap),
|
||||
args.threshold,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hitTestFreeDrawElement(args.element, args.point, args.threshold);
|
||||
return hitTestFreeDrawElement(
|
||||
args.element,
|
||||
args.point,
|
||||
args.threshold,
|
||||
args.elementsMap,
|
||||
);
|
||||
}
|
||||
case "arrow":
|
||||
case "line":
|
||||
|
@ -293,7 +313,7 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
|||
// check distance to frame element first
|
||||
if (
|
||||
args.check(
|
||||
distanceToBindableElement(args.element, args.point),
|
||||
distanceToBindableElement(args.element, args.point, args.elementsMap),
|
||||
args.threshold,
|
||||
)
|
||||
) {
|
||||
|
@ -316,6 +336,7 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
|||
export const distanceToBindableElement = (
|
||||
element: ExcalidrawBindableElement,
|
||||
point: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): number => {
|
||||
switch (element.type) {
|
||||
case "rectangle":
|
||||
|
@ -325,11 +346,11 @@ export const distanceToBindableElement = (
|
|||
case "embeddable":
|
||||
case "frame":
|
||||
case "magicframe":
|
||||
return distanceToRectangle(element, point);
|
||||
return distanceToRectangle(element, point, elementsMap);
|
||||
case "diamond":
|
||||
return distanceToDiamond(element, point);
|
||||
return distanceToDiamond(element, point, elementsMap);
|
||||
case "ellipse":
|
||||
return distanceToEllipse(element, point);
|
||||
return distanceToEllipse(element, point, elementsMap);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -358,8 +379,13 @@ const distanceToRectangle = (
|
|||
| ExcalidrawIframeLikeElement
|
||||
| ExcalidrawFrameLikeElement,
|
||||
point: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): number => {
|
||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||
element,
|
||||
point,
|
||||
elementsMap,
|
||||
);
|
||||
return Math.max(
|
||||
GAPoint.distanceToLine(pointRel, GALine.equation(0, 1, -hheight)),
|
||||
GAPoint.distanceToLine(pointRel, GALine.equation(1, 0, -hwidth)),
|
||||
|
@ -377,8 +403,13 @@ const distanceToRectangleBox = (box: RectangleBox, point: Point): number => {
|
|||
const distanceToDiamond = (
|
||||
element: ExcalidrawDiamondElement,
|
||||
point: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): number => {
|
||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||
element,
|
||||
point,
|
||||
elementsMap,
|
||||
);
|
||||
const side = GALine.equation(hheight, hwidth, -hheight * hwidth);
|
||||
return GAPoint.distanceToLine(pointRel, side);
|
||||
};
|
||||
|
@ -386,16 +417,22 @@ const distanceToDiamond = (
|
|||
const distanceToEllipse = (
|
||||
element: ExcalidrawEllipseElement,
|
||||
point: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): number => {
|
||||
const [pointRel, tangent] = ellipseParamsForTest(element, point);
|
||||
const [pointRel, tangent] = ellipseParamsForTest(element, point, elementsMap);
|
||||
return -GALine.sign(tangent) * GAPoint.distanceToLine(pointRel, tangent);
|
||||
};
|
||||
|
||||
const ellipseParamsForTest = (
|
||||
element: ExcalidrawEllipseElement,
|
||||
point: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): [GA.Point, GA.Line] => {
|
||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||
element,
|
||||
point,
|
||||
elementsMap,
|
||||
);
|
||||
const [px, py] = GAPoint.toTuple(pointRel);
|
||||
|
||||
// We're working in positive quadrant, so start with `t = 45deg`, `tx=cos(t)`
|
||||
|
@ -440,6 +477,7 @@ const hitTestFreeDrawElement = (
|
|||
element: ExcalidrawFreeDrawElement,
|
||||
point: Point,
|
||||
threshold: number,
|
||||
elementsMap: ElementsMap,
|
||||
): boolean => {
|
||||
// Check point-distance-to-line-segment for every segment in the
|
||||
// element's points (its input points, not its outline points).
|
||||
|
@ -454,7 +492,10 @@ const hitTestFreeDrawElement = (
|
|||
y = point[1] - element.y;
|
||||
} else {
|
||||
// Counter-rotate the point around center before testing
|
||||
const [minX, minY, maxX, maxY] = getElementAbsoluteCoords(element);
|
||||
const [minX, minY, maxX, maxY] = getElementAbsoluteCoords(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
const rotatedPoint = rotatePoint(
|
||||
point,
|
||||
[minX + (maxX - minX) / 2, minY + (maxY - minY) / 2],
|
||||
|
@ -520,6 +561,7 @@ const hitTestLinear = (args: HitTestArgs): boolean => {
|
|||
const [point, pointAbs, hwidth, hheight] = pointRelativeToElement(
|
||||
args.element,
|
||||
args.point,
|
||||
args.elementsMap,
|
||||
);
|
||||
const side1 = GALine.equation(0, 1, -hheight);
|
||||
const side2 = GALine.equation(1, 0, -hwidth);
|
||||
|
@ -572,9 +614,10 @@ const hitTestLinear = (args: HitTestArgs): boolean => {
|
|||
const pointRelativeToElement = (
|
||||
element: ExcalidrawElement,
|
||||
pointTuple: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): [GA.Point, GA.Point, number, number] => {
|
||||
const point = GAPoint.from(pointTuple);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const center = coordsCenter(x1, y1, x2, y2);
|
||||
// GA has angle orientation opposite to `rotate`
|
||||
const rotate = GATransform.rotation(center, element.angle);
|
||||
|
@ -609,11 +652,12 @@ const pointRelativeToDivElement = (
|
|||
// Returns point in absolute coordinates
|
||||
export const pointInAbsoluteCoords = (
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
// Point relative to the element position
|
||||
point: Point,
|
||||
): Point => {
|
||||
const [x, y] = point;
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x2 - x1) / 2;
|
||||
const cy = (y2 - y1) / 2;
|
||||
const [rotatedX, rotatedY] = rotate(x, y, cx, cy, element.angle);
|
||||
|
@ -622,8 +666,9 @@ export const pointInAbsoluteCoords = (
|
|||
|
||||
const relativizationToElementCenter = (
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
): GA.Transform => {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const center = coordsCenter(x1, y1, x2, y2);
|
||||
// GA has angle orientation opposite to `rotate`
|
||||
const rotate = GATransform.rotation(center, element.angle);
|
||||
|
@ -649,12 +694,14 @@ const coordsCenter = (
|
|||
// of the element.
|
||||
export const determineFocusDistance = (
|
||||
element: ExcalidrawBindableElement,
|
||||
|
||||
// Point on the line, in absolute coordinates
|
||||
a: Point,
|
||||
// Another point on the line, in absolute coordinates (closer to element)
|
||||
b: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): number => {
|
||||
const relateToCenter = relativizationToElementCenter(element);
|
||||
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
||||
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
||||
const line = GALine.through(aRel, bRel);
|
||||
|
@ -693,13 +740,14 @@ export const determineFocusPoint = (
|
|||
// returned focusPoint
|
||||
focus: number,
|
||||
adjecentPoint: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): Point => {
|
||||
if (focus === 0) {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const center = coordsCenter(x1, y1, x2, y2);
|
||||
return GAPoint.toTuple(center);
|
||||
}
|
||||
const relateToCenter = relativizationToElementCenter(element);
|
||||
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||
const adjecentPointRel = GATransform.apply(
|
||||
relateToCenter,
|
||||
GAPoint.from(adjecentPoint),
|
||||
|
@ -728,14 +776,16 @@ export const determineFocusPoint = (
|
|||
// and the `element`, in ascending order of distance from `a`.
|
||||
export const intersectElementWithLine = (
|
||||
element: ExcalidrawBindableElement,
|
||||
|
||||
// Point on the line, in absolute coordinates
|
||||
a: Point,
|
||||
// Another point on the line, in absolute coordinates
|
||||
b: Point,
|
||||
// If given, the element is inflated by this value
|
||||
gap: number = 0,
|
||||
elementsMap: ElementsMap,
|
||||
): Point[] => {
|
||||
const relateToCenter = relativizationToElementCenter(element);
|
||||
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
||||
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
||||
const line = GALine.through(aRel, bRel);
|
||||
|
|
|
@ -65,7 +65,7 @@ export const dragSelectedElements = (
|
|||
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
||||
}
|
||||
}
|
||||
updateBoundElements(element, {
|
||||
updateBoundElements(element, scene.getElementsMapIncludingDeleted(), {
|
||||
simultaneouslyUpdated: Array.from(elementsToUpdate),
|
||||
});
|
||||
});
|
||||
|
|
|
@ -135,6 +135,7 @@ export class LinearElementEditor {
|
|||
event: PointerEvent,
|
||||
appState: AppState,
|
||||
setState: React.Component<any, AppState>["setState"],
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
if (
|
||||
!appState.editingLinearElement ||
|
||||
|
@ -151,10 +152,12 @@ export class LinearElementEditor {
|
|||
}
|
||||
|
||||
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
||||
getElementAbsoluteCoords(appState.draggingElement);
|
||||
getElementAbsoluteCoords(appState.draggingElement, elementsMap);
|
||||
|
||||
const pointsSceneCoords =
|
||||
LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
const pointsSceneCoords = LinearElementEditor.getPointsGlobalCoordinates(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
const nextSelectedPoints = pointsSceneCoords.reduce(
|
||||
(acc: number[], point, index) => {
|
||||
|
@ -222,6 +225,7 @@ export class LinearElementEditor {
|
|||
|
||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||
element,
|
||||
elementsMap,
|
||||
referencePoint,
|
||||
[scenePointerX, scenePointerY],
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
|
@ -239,6 +243,7 @@ export class LinearElementEditor {
|
|||
} else {
|
||||
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
||||
element,
|
||||
elementsMap,
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
|
@ -255,6 +260,7 @@ export class LinearElementEditor {
|
|||
linearElementEditor.pointerDownState.lastClickedPoint
|
||||
? LinearElementEditor.createPointAt(
|
||||
element,
|
||||
elementsMap,
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
|
@ -290,6 +296,7 @@ export class LinearElementEditor {
|
|||
LinearElementEditor.getPointGlobalCoordinates(
|
||||
element,
|
||||
element.points[0],
|
||||
elementsMap,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -303,6 +310,7 @@ export class LinearElementEditor {
|
|||
LinearElementEditor.getPointGlobalCoordinates(
|
||||
element,
|
||||
element.points[lastSelectedIndex],
|
||||
elementsMap,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -323,6 +331,7 @@ export class LinearElementEditor {
|
|||
event: PointerEvent,
|
||||
editingLinearElement: LinearElementEditor,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
): LinearElementEditor {
|
||||
const { elementId, selectedPointsIndices, isDragging, pointerDownState } =
|
||||
editingLinearElement;
|
||||
|
@ -364,6 +373,7 @@ export class LinearElementEditor {
|
|||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
element,
|
||||
selectedPoint!,
|
||||
elementsMap,
|
||||
),
|
||||
),
|
||||
Scene.getScene(element)!,
|
||||
|
@ -425,15 +435,23 @@ export class LinearElementEditor {
|
|||
) {
|
||||
return editorMidPointsCache.points;
|
||||
}
|
||||
LinearElementEditor.updateEditorMidPointsCache(element, appState);
|
||||
LinearElementEditor.updateEditorMidPointsCache(
|
||||
element,
|
||||
elementsMap,
|
||||
appState,
|
||||
);
|
||||
return editorMidPointsCache.points!;
|
||||
};
|
||||
|
||||
static updateEditorMidPointsCache = (
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
appState: InteractiveCanvasAppState,
|
||||
) => {
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
let index = 0;
|
||||
const midpoints: (Point | null)[] = [];
|
||||
|
@ -455,6 +473,7 @@ export class LinearElementEditor {
|
|||
points[index],
|
||||
points[index + 1],
|
||||
index + 1,
|
||||
elementsMap,
|
||||
);
|
||||
midpoints.push(segmentMidPoint);
|
||||
index++;
|
||||
|
@ -477,6 +496,7 @@ export class LinearElementEditor {
|
|||
}
|
||||
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||
element,
|
||||
elementsMap,
|
||||
appState.zoom,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
|
@ -484,7 +504,10 @@ export class LinearElementEditor {
|
|||
if (clickedPointIndex >= 0) {
|
||||
return null;
|
||||
}
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
if (points.length >= 3 && !appState.editingLinearElement) {
|
||||
return null;
|
||||
}
|
||||
|
@ -550,6 +573,7 @@ export class LinearElementEditor {
|
|||
startPoint: Point,
|
||||
endPoint: Point,
|
||||
endPointIndex: number,
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
let segmentMidPoint = centerPoint(startPoint, endPoint);
|
||||
if (element.points.length > 2 && element.roundness) {
|
||||
|
@ -574,6 +598,7 @@ export class LinearElementEditor {
|
|||
segmentMidPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||
element,
|
||||
[tx, ty],
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -658,6 +683,7 @@ export class LinearElementEditor {
|
|||
...element.points,
|
||||
LinearElementEditor.createPointAt(
|
||||
element,
|
||||
elementsMap,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
|
@ -693,6 +719,7 @@ export class LinearElementEditor {
|
|||
|
||||
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||
element,
|
||||
elementsMap,
|
||||
appState.zoom,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
|
@ -713,11 +740,12 @@ export class LinearElementEditor {
|
|||
element,
|
||||
startBindingElement,
|
||||
endBindingElement,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
const targetPoint =
|
||||
|
@ -779,6 +807,7 @@ export class LinearElementEditor {
|
|||
scenePointerX: number,
|
||||
scenePointerY: number,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
): LinearElementEditor | null {
|
||||
if (!appState.editingLinearElement) {
|
||||
return null;
|
||||
|
@ -809,6 +838,7 @@ export class LinearElementEditor {
|
|||
|
||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||
element,
|
||||
elementsMap,
|
||||
lastCommittedPoint,
|
||||
[scenePointerX, scenePointerY],
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
|
@ -821,6 +851,7 @@ export class LinearElementEditor {
|
|||
} else {
|
||||
newPoint = LinearElementEditor.createPointAt(
|
||||
element,
|
||||
elementsMap,
|
||||
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
||||
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
|
@ -847,8 +878,9 @@ export class LinearElementEditor {
|
|||
static getPointGlobalCoordinates(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
point: Point,
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
|
||||
|
@ -860,8 +892,9 @@ export class LinearElementEditor {
|
|||
/** scene coords */
|
||||
static getPointsGlobalCoordinates(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
): Point[] {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
return element.points.map((point) => {
|
||||
|
@ -873,13 +906,15 @@ export class LinearElementEditor {
|
|||
|
||||
static getPointAtIndexGlobalCoordinates(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
|
||||
indexMaybeFromEnd: number, // -1 for last element
|
||||
elementsMap: ElementsMap,
|
||||
): Point {
|
||||
const index =
|
||||
indexMaybeFromEnd < 0
|
||||
? element.points.length + indexMaybeFromEnd
|
||||
: indexMaybeFromEnd;
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
|
||||
|
@ -893,8 +928,9 @@ export class LinearElementEditor {
|
|||
static pointFromAbsoluteCoords(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
absoluteCoords: Point,
|
||||
elementsMap: ElementsMap,
|
||||
): Point {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
const [x, y] = rotate(
|
||||
|
@ -909,12 +945,15 @@ export class LinearElementEditor {
|
|||
|
||||
static getPointIndexUnderCursor(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
zoom: AppState["zoom"],
|
||||
x: number,
|
||||
y: number,
|
||||
) {
|
||||
const pointHandles =
|
||||
LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
const pointHandles = LinearElementEditor.getPointsGlobalCoordinates(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
let idx = pointHandles.length;
|
||||
// loop from right to left because points on the right are rendered over
|
||||
// points on the left, thus should take precedence when clicking, if they
|
||||
|
@ -934,12 +973,13 @@ export class LinearElementEditor {
|
|||
|
||||
static createPointAt(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
scenePointerX: number,
|
||||
scenePointerY: number,
|
||||
gridSize: number | null,
|
||||
): Point {
|
||||
const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, gridSize);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
const [rotatedX, rotatedY] = rotate(
|
||||
|
@ -1190,6 +1230,7 @@ export class LinearElementEditor {
|
|||
pointerCoords: PointerCoords,
|
||||
appState: AppState,
|
||||
snapToGrid: boolean,
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
|
@ -1208,6 +1249,7 @@ export class LinearElementEditor {
|
|||
|
||||
const midpoint = LinearElementEditor.createPointAt(
|
||||
element,
|
||||
elementsMap,
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
snapToGrid ? appState.gridSize : null,
|
||||
|
@ -1260,6 +1302,7 @@ export class LinearElementEditor {
|
|||
|
||||
private static _getShiftLockedDelta(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
referencePoint: Point,
|
||||
scenePointer: Point,
|
||||
gridSize: number | null,
|
||||
|
@ -1267,6 +1310,7 @@ export class LinearElementEditor {
|
|||
const referencePointCoords = LinearElementEditor.getPointGlobalCoordinates(
|
||||
element,
|
||||
referencePoint,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
|
@ -1288,8 +1332,12 @@ export class LinearElementEditor {
|
|||
static getBoundTextElementPosition = (
|
||||
element: ExcalidrawLinearElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
elementsMap: ElementsMap,
|
||||
): { x: number; y: number } => {
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
if (points.length < 2) {
|
||||
mutateElement(boundTextElement, { isDeleted: true });
|
||||
}
|
||||
|
@ -1300,6 +1348,7 @@ export class LinearElementEditor {
|
|||
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||
element,
|
||||
element.points[index],
|
||||
elementsMap,
|
||||
);
|
||||
x = midPoint[0] - boundTextElement.width / 2;
|
||||
y = midPoint[1] - boundTextElement.height / 2;
|
||||
|
@ -1319,6 +1368,7 @@ export class LinearElementEditor {
|
|||
points[index],
|
||||
points[index + 1],
|
||||
index + 1,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
x = midSegmentMidpoint[0] - boundTextElement.width / 2;
|
||||
|
@ -1329,6 +1379,7 @@ export class LinearElementEditor {
|
|||
|
||||
static getMinMaxXYWithBoundText = (
|
||||
element: ExcalidrawLinearElement,
|
||||
elementsMap: ElementsMap,
|
||||
elementBounds: Bounds,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
): [number, number, number, number, number, number] => {
|
||||
|
@ -1339,6 +1390,7 @@ export class LinearElementEditor {
|
|||
LinearElementEditor.getBoundTextElementPosition(
|
||||
element,
|
||||
boundTextElement,
|
||||
elementsMap,
|
||||
);
|
||||
const boundTextX2 = boundTextX1 + boundTextElement.width;
|
||||
const boundTextY2 = boundTextY1 + boundTextElement.height;
|
||||
|
@ -1479,6 +1531,7 @@ export class LinearElementEditor {
|
|||
if (boundTextElement) {
|
||||
coords = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
elementsMap,
|
||||
[x1, y1, x2, y2],
|
||||
boundTextElement,
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
ExcalidrawEmbeddableElement,
|
||||
ExcalidrawMagicFrameElement,
|
||||
ExcalidrawIframeElement,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
import {
|
||||
arrayToMap,
|
||||
|
@ -260,6 +261,7 @@ export const newTextElement = (
|
|||
|
||||
const getAdjustedDimensions = (
|
||||
element: ExcalidrawTextElement,
|
||||
elementsMap: ElementsMap,
|
||||
nextText: string,
|
||||
): {
|
||||
x: number;
|
||||
|
@ -294,7 +296,7 @@ const getAdjustedDimensions = (
|
|||
x = element.x - offsets.x;
|
||||
y = element.y - offsets.y;
|
||||
} else {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
|
||||
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
||||
element,
|
||||
|
@ -335,6 +337,7 @@ const getAdjustedDimensions = (
|
|||
export const refreshTextDimensions = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
elementsMap: ElementsMap,
|
||||
text = textElement.text,
|
||||
) => {
|
||||
if (textElement.isDeleted) {
|
||||
|
@ -347,13 +350,14 @@ export const refreshTextDimensions = (
|
|||
getBoundTextMaxWidth(container, textElement),
|
||||
);
|
||||
}
|
||||
const dimensions = getAdjustedDimensions(textElement, text);
|
||||
const dimensions = getAdjustedDimensions(textElement, elementsMap, text);
|
||||
return { text, ...dimensions };
|
||||
};
|
||||
|
||||
export const updateTextElement = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
elementsMap: ElementsMap,
|
||||
{
|
||||
text,
|
||||
isDeleted,
|
||||
|
@ -367,7 +371,7 @@ export const updateTextElement = (
|
|||
return newElementWith(textElement, {
|
||||
originalText,
|
||||
isDeleted: isDeleted ?? textElement.isDeleted,
|
||||
...refreshTextDimensions(textElement, container, originalText),
|
||||
...refreshTextDimensions(textElement, container, elementsMap, originalText),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -86,11 +86,12 @@ export const transformElements = (
|
|||
if (transformHandleType === "rotation") {
|
||||
rotateSingleElement(
|
||||
element,
|
||||
elementsMap,
|
||||
pointerX,
|
||||
pointerY,
|
||||
shouldRotateWithDiscreteAngle,
|
||||
);
|
||||
updateBoundElements(element);
|
||||
updateBoundElements(element, elementsMap);
|
||||
} else if (
|
||||
isTextElement(element) &&
|
||||
(transformHandleType === "nw" ||
|
||||
|
@ -106,7 +107,7 @@ export const transformElements = (
|
|||
pointerX,
|
||||
pointerY,
|
||||
);
|
||||
updateBoundElements(element);
|
||||
updateBoundElements(element, elementsMap);
|
||||
} else if (transformHandleType) {
|
||||
resizeSingleElement(
|
||||
originalElements,
|
||||
|
@ -157,11 +158,12 @@ export const transformElements = (
|
|||
|
||||
const rotateSingleElement = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
pointerX: number,
|
||||
pointerY: number,
|
||||
shouldRotateWithDiscreteAngle: boolean,
|
||||
) => {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
let angle: number;
|
||||
|
@ -266,7 +268,7 @@ const resizeSingleTextElement = (
|
|||
pointerX: number,
|
||||
pointerY: number,
|
||||
) => {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
// rotation pointer with reverse angle
|
||||
|
@ -629,7 +631,7 @@ export const resizeSingleElement = (
|
|||
) {
|
||||
mutateElement(element, resizedElement);
|
||||
|
||||
updateBoundElements(element, {
|
||||
updateBoundElements(element, elementsMap, {
|
||||
newSize: { width: resizedElement.width, height: resizedElement.height },
|
||||
});
|
||||
|
||||
|
@ -696,7 +698,11 @@ export const resizeMultipleElements = (
|
|||
if (!isBoundToContainer(text)) {
|
||||
return acc;
|
||||
}
|
||||
const xy = LinearElementEditor.getBoundTextElementPosition(orig, text);
|
||||
const xy = LinearElementEditor.getBoundTextElementPosition(
|
||||
orig,
|
||||
text,
|
||||
elementsMap,
|
||||
);
|
||||
return [...acc, { ...text, ...xy }];
|
||||
}, [] as ExcalidrawTextElementWithContainer[]);
|
||||
|
||||
|
@ -879,7 +885,7 @@ export const resizeMultipleElements = (
|
|||
|
||||
mutateElement(element, update, false);
|
||||
|
||||
updateBoundElements(element, {
|
||||
updateBoundElements(element, elementsMap, {
|
||||
simultaneouslyUpdated: elementsToUpdate,
|
||||
newSize: { width, height },
|
||||
});
|
||||
|
@ -921,7 +927,7 @@ const rotateMultipleElements = (
|
|||
elements
|
||||
.filter((element) => !isFrameLikeElement(element))
|
||||
.forEach((element) => {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
const origAngle =
|
||||
|
@ -942,7 +948,9 @@ const rotateMultipleElements = (
|
|||
},
|
||||
false,
|
||||
);
|
||||
updateBoundElements(element, { simultaneouslyUpdated: elements });
|
||||
updateBoundElements(element, elementsMap, {
|
||||
simultaneouslyUpdated: elements,
|
||||
});
|
||||
|
||||
const boundText = getBoundTextElement(element, elementsMap);
|
||||
if (boundText && !isArrowElement(element)) {
|
||||
|
@ -964,12 +972,13 @@ const rotateMultipleElements = (
|
|||
export const getResizeOffsetXY = (
|
||||
transformHandleType: MaybeTransformHandleType,
|
||||
selectedElements: NonDeletedExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
x: number,
|
||||
y: number,
|
||||
): [number, number] => {
|
||||
const [x1, y1, x2, y2] =
|
||||
selectedElements.length === 1
|
||||
? getElementAbsoluteCoords(selectedElements[0])
|
||||
? getElementAbsoluteCoords(selectedElements[0], elementsMap)
|
||||
: getCommonBounds(selectedElements);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
ExcalidrawElement,
|
||||
PointerType,
|
||||
NonDeletedExcalidrawElement,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
|
||||
import {
|
||||
|
@ -27,6 +28,7 @@ const isInsideTransformHandle = (
|
|||
|
||||
export const resizeTest = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
x: number,
|
||||
y: number,
|
||||
|
@ -38,7 +40,7 @@ export const resizeTest = (
|
|||
}
|
||||
|
||||
const { rotation: rotationTransformHandle, ...transformHandles } =
|
||||
getTransformHandles(element, zoom, pointerType);
|
||||
getTransformHandles(element, zoom, elementsMap, pointerType);
|
||||
|
||||
if (
|
||||
rotationTransformHandle &&
|
||||
|
@ -70,6 +72,7 @@ export const getElementWithTransformHandleType = (
|
|||
scenePointerY: number,
|
||||
zoom: Zoom,
|
||||
pointerType: PointerType,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
return elements.reduce((result, element) => {
|
||||
if (result) {
|
||||
|
@ -77,6 +80,7 @@ export const getElementWithTransformHandleType = (
|
|||
}
|
||||
const transformHandleType = resizeTest(
|
||||
element,
|
||||
elementsMap,
|
||||
appState,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
|
|
|
@ -53,6 +53,7 @@ const splitIntoLines = (text: string) => {
|
|||
export const redrawTextBoundingBox = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawElement | null,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
let maxWidth = undefined;
|
||||
const boundTextUpdates = {
|
||||
|
@ -110,7 +111,11 @@ export const redrawTextBoundingBox = (
|
|||
...textElement,
|
||||
...boundTextUpdates,
|
||||
} as ExcalidrawTextElementWithContainer;
|
||||
const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
||||
const { x, y } = computeBoundTextPosition(
|
||||
container,
|
||||
updatedTextElement,
|
||||
elementsMap,
|
||||
);
|
||||
boundTextUpdates.x = x;
|
||||
boundTextUpdates.y = y;
|
||||
}
|
||||
|
@ -119,11 +124,11 @@ export const redrawTextBoundingBox = (
|
|||
};
|
||||
|
||||
export const bindTextToShapeAfterDuplication = (
|
||||
sceneElements: ExcalidrawElement[],
|
||||
newElements: ExcalidrawElement[],
|
||||
oldElements: ExcalidrawElement[],
|
||||
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
||||
): void => {
|
||||
const sceneElementMap = arrayToMap(sceneElements) as Map<
|
||||
const newElementsMap = arrayToMap(newElements) as Map<
|
||||
ExcalidrawElement["id"],
|
||||
ExcalidrawElement
|
||||
>;
|
||||
|
@ -134,7 +139,7 @@ export const bindTextToShapeAfterDuplication = (
|
|||
if (boundTextElementId) {
|
||||
const newTextElementId = oldIdToDuplicatedId.get(boundTextElementId);
|
||||
if (newTextElementId) {
|
||||
const newContainer = sceneElementMap.get(newElementId);
|
||||
const newContainer = newElementsMap.get(newElementId);
|
||||
if (newContainer) {
|
||||
mutateElement(newContainer, {
|
||||
boundElements: (element.boundElements || [])
|
||||
|
@ -149,7 +154,7 @@ export const bindTextToShapeAfterDuplication = (
|
|||
}),
|
||||
});
|
||||
}
|
||||
const newTextElement = sceneElementMap.get(newTextElementId);
|
||||
const newTextElement = newElementsMap.get(newTextElementId);
|
||||
if (newTextElement && isTextElement(newTextElement)) {
|
||||
mutateElement(newTextElement, {
|
||||
containerId: newContainer ? newElementId : null,
|
||||
|
@ -236,7 +241,7 @@ export const handleBindTextResize = (
|
|||
if (!isArrowElement(container)) {
|
||||
mutateElement(
|
||||
textElement,
|
||||
computeBoundTextPosition(container, textElement),
|
||||
computeBoundTextPosition(container, textElement, elementsMap),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -245,11 +250,13 @@ export const handleBindTextResize = (
|
|||
export const computeBoundTextPosition = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
if (isArrowElement(container)) {
|
||||
return LinearElementEditor.getBoundTextElementPosition(
|
||||
container,
|
||||
boundTextElement,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
const containerCoords = getContainerCoords(container);
|
||||
|
@ -698,12 +705,16 @@ export const getContainerCenter = (
|
|||
y: container.y + container.height / 2,
|
||||
};
|
||||
}
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(container);
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||
container,
|
||||
elementsMap,
|
||||
);
|
||||
if (points.length % 2 === 1) {
|
||||
const index = Math.floor(container.points.length / 2);
|
||||
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||
container,
|
||||
container.points[index],
|
||||
elementsMap,
|
||||
);
|
||||
return { x: midPoint[0], y: midPoint[1] };
|
||||
}
|
||||
|
@ -719,6 +730,7 @@ export const getContainerCenter = (
|
|||
points[index],
|
||||
points[index + 1],
|
||||
index + 1,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] };
|
||||
|
@ -757,11 +769,13 @@ export const getTextElementAngle = (
|
|||
export const getBoundTextElementPosition = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
if (isArrowElement(container)) {
|
||||
return LinearElementEditor.getBoundTextElementPosition(
|
||||
container,
|
||||
boundTextElement,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -804,6 +818,7 @@ export const getTextBindableContainerAtPosition = (
|
|||
appState: AppState,
|
||||
x: number,
|
||||
y: number,
|
||||
elementsMap: ElementsMap,
|
||||
): ExcalidrawTextContainer | null => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
if (selectedElements.length === 1) {
|
||||
|
@ -817,7 +832,10 @@ export const getTextBindableContainerAtPosition = (
|
|||
if (elements[index].isDeleted) {
|
||||
continue;
|
||||
}
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index]);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(
|
||||
elements[index],
|
||||
elementsMap,
|
||||
);
|
||||
if (
|
||||
isArrowElement(elements[index]) &&
|
||||
isHittingElementNotConsideringBoundingBox(
|
||||
|
@ -825,6 +843,7 @@ export const getTextBindableContainerAtPosition = (
|
|||
appState,
|
||||
null,
|
||||
[x, y],
|
||||
elementsMap,
|
||||
)
|
||||
) {
|
||||
hitElement = elements[index];
|
||||
|
|
|
@ -121,13 +121,13 @@ export const textWysiwyg = ({
|
|||
return;
|
||||
}
|
||||
const { textAlign, verticalAlign } = updatedTextElement;
|
||||
|
||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||
if (updatedTextElement && isTextElement(updatedTextElement)) {
|
||||
let coordX = updatedTextElement.x;
|
||||
let coordY = updatedTextElement.y;
|
||||
const container = getContainerElement(
|
||||
updatedTextElement,
|
||||
app.scene.getElementsMapIncludingDeleted(),
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
let maxWidth = updatedTextElement.width;
|
||||
|
||||
|
@ -143,6 +143,7 @@ export const textWysiwyg = ({
|
|||
LinearElementEditor.getBoundTextElementPosition(
|
||||
container,
|
||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||
elementsMap,
|
||||
);
|
||||
coordX = boundTextCoords.x;
|
||||
coordY = boundTextCoords.y;
|
||||
|
@ -200,6 +201,7 @@ export const textWysiwyg = ({
|
|||
const { y } = computeBoundTextPosition(
|
||||
container,
|
||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||
elementsMap,
|
||||
);
|
||||
coordY = y;
|
||||
}
|
||||
|
@ -326,7 +328,7 @@ export const textWysiwyg = ({
|
|||
}
|
||||
const container = getContainerElement(
|
||||
element,
|
||||
app.scene.getElementsMapIncludingDeleted(),
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
const font = getFontString({
|
||||
|
@ -513,7 +515,7 @@ export const textWysiwyg = ({
|
|||
let text = editable.value;
|
||||
const container = getContainerElement(
|
||||
updateElement,
|
||||
app.scene.getElementsMapIncludingDeleted(),
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
if (container) {
|
||||
|
@ -541,7 +543,11 @@ export const textWysiwyg = ({
|
|||
),
|
||||
});
|
||||
}
|
||||
redrawTextBoundingBox(updateElement, container);
|
||||
redrawTextBoundingBox(
|
||||
updateElement,
|
||||
container,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
}
|
||||
|
||||
onSubmit({
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
ElementsMap,
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
PointerType,
|
||||
|
@ -230,6 +231,8 @@ export const getTransformHandlesFromCoords = (
|
|||
export const getTransformHandles = (
|
||||
element: ExcalidrawElement,
|
||||
zoom: Zoom,
|
||||
elementsMap: ElementsMap,
|
||||
|
||||
pointerType: PointerType = "mouse",
|
||||
): TransformHandles => {
|
||||
// so that when locked element is selected (especially when you toggle lock
|
||||
|
@ -267,7 +270,7 @@ export const getTransformHandles = (
|
|||
? DEFAULT_TRANSFORM_HANDLE_SPACING + 8
|
||||
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
||||
return getTransformHandlesFromCoords(
|
||||
getElementAbsoluteCoords(element, true),
|
||||
getElementAbsoluteCoords(element, elementsMap, true),
|
||||
element.angle,
|
||||
zoom,
|
||||
pointerType,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue