mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -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
|
@ -58,7 +58,11 @@ export const actionUnbindText = register({
|
||||||
element.id,
|
element.id,
|
||||||
);
|
);
|
||||||
resetOriginalContainerCache(element.id);
|
resetOriginalContainerCache(element.id);
|
||||||
const { x, y } = computeBoundTextPosition(element, boundTextElement);
|
const { x, y } = computeBoundTextPosition(
|
||||||
|
element,
|
||||||
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
||||||
containerId: null,
|
containerId: null,
|
||||||
width,
|
width,
|
||||||
|
@ -145,7 +149,11 @@ export const actionBindText = register({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const originalContainerHeight = container.height;
|
const originalContainerHeight = container.height;
|
||||||
redrawTextBoundingBox(textElement, container);
|
redrawTextBoundingBox(
|
||||||
|
textElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
// overwritting the cache with original container height so
|
// overwritting the cache with original container height so
|
||||||
// it can be restored when unbind
|
// it can be restored when unbind
|
||||||
updateOriginalContainerCache(container.id, originalContainerHeight);
|
updateOriginalContainerCache(container.id, originalContainerHeight);
|
||||||
|
@ -286,7 +294,11 @@ export const actionWrapTextInContainer = register({
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
redrawTextBoundingBox(textElement, container);
|
redrawTextBoundingBox(
|
||||||
|
textElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
|
||||||
updatedElements = pushContainerBelowText(
|
updatedElements = pushContainerBelowText(
|
||||||
[...updatedElements, container],
|
[...updatedElements, container],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { isInvisiblySmallElement } from "../element";
|
import { isInvisiblySmallElement } from "../element";
|
||||||
import { updateActiveTool } from "../utils";
|
import { arrayToMap, updateActiveTool } from "../utils";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { done } from "../components/icons";
|
import { done } from "../components/icons";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
@ -26,6 +26,8 @@ export const actionFinalize = register({
|
||||||
_,
|
_,
|
||||||
{ interactiveCanvas, focusContainer, scene },
|
{ interactiveCanvas, focusContainer, scene },
|
||||||
) => {
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
|
|
||||||
if (appState.editingLinearElement) {
|
if (appState.editingLinearElement) {
|
||||||
const { elementId, startBindingElement, endBindingElement } =
|
const { elementId, startBindingElement, endBindingElement } =
|
||||||
appState.editingLinearElement;
|
appState.editingLinearElement;
|
||||||
|
@ -37,6 +39,7 @@ export const actionFinalize = register({
|
||||||
element,
|
element,
|
||||||
startBindingElement,
|
startBindingElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -125,12 +128,14 @@ export const actionFinalize = register({
|
||||||
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
multiPointElement,
|
multiPointElement,
|
||||||
-1,
|
-1,
|
||||||
|
arrayToMap(elements),
|
||||||
);
|
);
|
||||||
maybeBindLinearElement(
|
maybeBindLinearElement(
|
||||||
multiPointElement,
|
multiPointElement,
|
||||||
appState,
|
appState,
|
||||||
Scene.getScene(multiPointElement)!,
|
Scene.getScene(multiPointElement)!,
|
||||||
{ x, y },
|
{ x, y },
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ const flipElements = (
|
||||||
|
|
||||||
(isBindingEnabled(appState)
|
(isBindingEnabled(appState)
|
||||||
? bindOrUnbindSelectedElements
|
? bindOrUnbindSelectedElements
|
||||||
: unbindLinearElements)(selectedElements);
|
: unbindLinearElements)(selectedElements, elementsMap);
|
||||||
|
|
||||||
return selectedElements;
|
return selectedElements;
|
||||||
};
|
};
|
||||||
|
|
|
@ -180,6 +180,8 @@ export const actionUngroup = register({
|
||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
perform: (elements, appState, _, app) => {
|
perform: (elements, appState, _, app) => {
|
||||||
const groupIds = getSelectedGroupIds(appState);
|
const groupIds = getSelectedGroupIds(appState);
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
|
|
||||||
if (groupIds.length === 0) {
|
if (groupIds.length === 0) {
|
||||||
return { appState, elements, commitToHistory: false };
|
return { appState, elements, commitToHistory: false };
|
||||||
}
|
}
|
||||||
|
@ -226,7 +228,12 @@ export const actionUngroup = register({
|
||||||
if (frame) {
|
if (frame) {
|
||||||
nextElements = replaceAllElementsInFrame(
|
nextElements = replaceAllElementsInFrame(
|
||||||
nextElements,
|
nextElements,
|
||||||
getElementsInResizingFrame(nextElements, frame, appState),
|
getElementsInResizingFrame(
|
||||||
|
nextElements,
|
||||||
|
frame,
|
||||||
|
appState,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
frame,
|
frame,
|
||||||
app,
|
app,
|
||||||
);
|
);
|
||||||
|
|
|
@ -209,6 +209,7 @@ const changeFontSize = (
|
||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
newElement = offsetElementAfterFontResize(oldElement, newElement);
|
newElement = offsetElementAfterFontResize(oldElement, newElement);
|
||||||
|
@ -730,6 +731,7 @@ export const actionChangeFontFamily = register({
|
||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
|
@ -829,6 +831,7 @@ export const actionChangeTextAlign = register({
|
||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
|
@ -918,6 +921,7 @@ export const actionChangeVerticalAlign = register({
|
||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,11 @@ export const actionPasteStyles = register({
|
||||||
element.id === newElement.containerId,
|
element.id === newElement.containerId,
|
||||||
) || null;
|
) || null;
|
||||||
}
|
}
|
||||||
redrawTextBoundingBox(newElement, container);
|
redrawTextBoundingBox(
|
||||||
|
newElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -1536,6 +1536,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
key={firstSelectedElement.id}
|
key={firstSelectedElement.id}
|
||||||
element={firstSelectedElement}
|
element={firstSelectedElement}
|
||||||
|
elementsMap={allElementsMap}
|
||||||
setAppState={this.setAppState}
|
setAppState={this.setAppState}
|
||||||
onLinkOpen={this.props.onLinkOpen}
|
onLinkOpen={this.props.onLinkOpen}
|
||||||
setToast={this.setToast}
|
setToast={this.setToast}
|
||||||
|
@ -1549,6 +1550,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
isMagicFrameElement(firstSelectedElement) && (
|
isMagicFrameElement(firstSelectedElement) && (
|
||||||
<ElementCanvasButtons
|
<ElementCanvasButtons
|
||||||
element={firstSelectedElement}
|
element={firstSelectedElement}
|
||||||
|
elementsMap={elementsMap}
|
||||||
>
|
>
|
||||||
<ElementCanvasButton
|
<ElementCanvasButton
|
||||||
title={t("labels.convertToCode")}
|
title={t("labels.convertToCode")}
|
||||||
|
@ -1569,6 +1571,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
?.status === "done" && (
|
?.status === "done" && (
|
||||||
<ElementCanvasButtons
|
<ElementCanvasButtons
|
||||||
element={firstSelectedElement}
|
element={firstSelectedElement}
|
||||||
|
elementsMap={elementsMap}
|
||||||
>
|
>
|
||||||
<ElementCanvasButton
|
<ElementCanvasButton
|
||||||
title={t("labels.copySource")}
|
title={t("labels.copySource")}
|
||||||
|
@ -2599,10 +2602,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
|
|
||||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||||
this.updateEmbeddables();
|
this.updateEmbeddables();
|
||||||
if (
|
const elements = this.scene.getElementsIncludingDeleted();
|
||||||
!this.state.showWelcomeScreen &&
|
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
||||||
!this.scene.getElementsIncludingDeleted().length
|
|
||||||
) {
|
if (!this.state.showWelcomeScreen && !elements.length) {
|
||||||
this.setState({ showWelcomeScreen: true });
|
this.setState({ showWelcomeScreen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2756,27 +2759,21 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
multiElement,
|
multiElement,
|
||||||
-1,
|
-1,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.history.record(this.state, this.scene.getElementsIncludingDeleted());
|
this.history.record(this.state, elements);
|
||||||
|
|
||||||
// Do not notify consumers if we're still loading the scene. Among other
|
// Do not notify consumers if we're still loading the scene. Among other
|
||||||
// potential issues, this fixes a case where the tab isn't focused during
|
// potential issues, this fixes a case where the tab isn't focused during
|
||||||
// init, which would trigger onChange with empty elements, which would then
|
// init, which would trigger onChange with empty elements, which would then
|
||||||
// override whatever is in localStorage currently.
|
// override whatever is in localStorage currently.
|
||||||
if (!this.state.isLoading) {
|
if (!this.state.isLoading) {
|
||||||
this.props.onChange?.(
|
this.props.onChange?.(elements, this.state, this.files);
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.onChangeEmitter.trigger(elements, this.state, this.files);
|
||||||
this.state,
|
|
||||||
this.files,
|
|
||||||
);
|
|
||||||
this.onChangeEmitter.trigger(
|
|
||||||
this.scene.getElementsIncludingDeleted(),
|
|
||||||
this.state,
|
|
||||||
this.files,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3126,7 +3123,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
newElement,
|
newElement,
|
||||||
this.scene.getElementsMapIncludingDeleted(),
|
this.scene.getElementsMapIncludingDeleted(),
|
||||||
);
|
);
|
||||||
redrawTextBoundingBox(newElement, container);
|
redrawTextBoundingBox(
|
||||||
|
newElement,
|
||||||
|
container,
|
||||||
|
this.scene.getElementsMapIncludingDeleted(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3836,7 +3837,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
y: element.y + offsetY,
|
y: element.y + offsetY,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
|
||||||
simultaneouslyUpdated: selectedElements,
|
simultaneouslyUpdated: selectedElements,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4010,9 +4011,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
}
|
}
|
||||||
if (isArrowKey(event.key)) {
|
if (isArrowKey(event.key)) {
|
||||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
isBindingEnabled(this.state)
|
isBindingEnabled(this.state)
|
||||||
? bindOrUnbindSelectedElements(selectedElements)
|
? bindOrUnbindSelectedElements(selectedElements, elementsMap)
|
||||||
: unbindLinearElements(selectedElements);
|
: unbindLinearElements(selectedElements, elementsMap);
|
||||||
this.setState({ suggestedBindings: [] });
|
this.setState({ suggestedBindings: [] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4193,20 +4195,21 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
isExistingElement?: boolean;
|
isExistingElement?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
||||||
|
|
||||||
const updateElement = (
|
const updateElement = (
|
||||||
text: string,
|
text: string,
|
||||||
originalText: string,
|
originalText: string,
|
||||||
isDeleted: boolean,
|
isDeleted: boolean,
|
||||||
) => {
|
) => {
|
||||||
this.scene.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
|
// Not sure why we include deleted elements as well hence using deleted elements map
|
||||||
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
||||||
if (_element.id === element.id && isTextElement(_element)) {
|
if (_element.id === element.id && isTextElement(_element)) {
|
||||||
return updateTextElement(
|
return updateTextElement(
|
||||||
_element,
|
_element,
|
||||||
getContainerElement(
|
getContainerElement(_element, elementsMap),
|
||||||
_element,
|
elementsMap,
|
||||||
this.scene.getElementsMapIncludingDeleted(),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
text,
|
text,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
|
@ -4238,7 +4241,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
onChange: withBatchedUpdates((text) => {
|
onChange: withBatchedUpdates((text) => {
|
||||||
updateElement(text, text, false);
|
updateElement(text, text, false);
|
||||||
if (isNonDeletedElement(element)) {
|
if (isNonDeletedElement(element)) {
|
||||||
updateBoundElements(element);
|
updateBoundElements(element, elementsMap);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
|
onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
|
||||||
|
@ -4377,6 +4380,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
!(isTextElement(element) && element.containerId)),
|
!(isTextElement(element) && element.containerId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
return getElementsAtPosition(elements, (element) =>
|
return getElementsAtPosition(elements, (element) =>
|
||||||
hitTest(
|
hitTest(
|
||||||
element,
|
element,
|
||||||
|
@ -4384,7 +4388,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.frameNameBoundsCache,
|
this.frameNameBoundsCache,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
elementsMap,
|
||||||
),
|
),
|
||||||
).filter((element) => {
|
).filter((element) => {
|
||||||
// hitting a frame's element from outside the frame is not considered a hit
|
// hitting a frame's element from outside the frame is not considered a hit
|
||||||
|
@ -4392,7 +4396,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
return containingFrame &&
|
return containingFrame &&
|
||||||
this.state.frameRendering.enabled &&
|
this.state.frameRendering.enabled &&
|
||||||
this.state.frameRendering.clip
|
this.state.frameRendering.clip
|
||||||
? isCursorInFrame({ x, y }, containingFrame)
|
? isCursorInFrame({ x, y }, containingFrame, elementsMap)
|
||||||
: true;
|
: true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4637,6 +4641,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.state,
|
this.state,
|
||||||
sceneX,
|
sceneX,
|
||||||
sceneY,
|
sceneY,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
|
@ -4648,6 +4653,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.state,
|
this.state,
|
||||||
this.frameNameBoundsCache,
|
this.frameNameBoundsCache,
|
||||||
[sceneX, sceneY],
|
[sceneX, sceneY],
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const midPoint = getContainerCenter(
|
const midPoint = getContainerCenter(
|
||||||
|
@ -4688,6 +4694,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
index <= hitElementIndex &&
|
index <= hitElementIndex &&
|
||||||
isPointHittingLink(
|
isPointHittingLink(
|
||||||
element,
|
element,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.state,
|
this.state,
|
||||||
[scenePointer.x, scenePointer.y],
|
[scenePointer.x, scenePointer.y],
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
|
@ -4718,8 +4725,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.lastPointerDownEvent!,
|
this.lastPointerDownEvent!,
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
const lastPointerDownHittingLinkIcon = isPointHittingLink(
|
const lastPointerDownHittingLinkIcon = isPointHittingLink(
|
||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
|
@ -4730,6 +4739,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
);
|
);
|
||||||
const lastPointerUpHittingLinkIcon = isPointHittingLink(
|
const lastPointerUpHittingLinkIcon = isPointHittingLink(
|
||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
|
@ -4766,10 +4776,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
const frames = this.scene
|
const frames = this.scene
|
||||||
.getNonDeletedFramesLikes()
|
.getNonDeletedFramesLikes()
|
||||||
.filter((frame): frame is ExcalidrawFrameLikeElement =>
|
.filter((frame): frame is ExcalidrawFrameLikeElement =>
|
||||||
isCursorInFrame(sceneCoords, frame),
|
isCursorInFrame(sceneCoords, frame, elementsMap),
|
||||||
);
|
);
|
||||||
|
|
||||||
return frames.length ? frames[frames.length - 1] : null;
|
return frames.length ? frames[frames.length - 1] : null;
|
||||||
|
@ -4873,6 +4884,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
y: scenePointerY,
|
y: scenePointerY,
|
||||||
},
|
},
|
||||||
event,
|
event,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
|
@ -4912,6 +4924,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
scenePointerX,
|
scenePointerX,
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -5062,6 +5075,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
elementWithTransformHandleType &&
|
elementWithTransformHandleType &&
|
||||||
|
@ -5109,7 +5123,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
!this.state.selectedElementIds[this.hitLinkElement.id]
|
!this.state.selectedElementIds[this.hitLinkElement.id]
|
||||||
) {
|
) {
|
||||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||||
showHyperlinkTooltip(this.hitLinkElement, this.state);
|
showHyperlinkTooltip(
|
||||||
|
this.hitLinkElement,
|
||||||
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
hideHyperlinkToolip();
|
hideHyperlinkToolip();
|
||||||
if (
|
if (
|
||||||
|
@ -5305,10 +5323,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.state,
|
this.state,
|
||||||
this.frameNameBoundsCache,
|
this.frameNameBoundsCache,
|
||||||
[scenePointerX, scenePointerY],
|
[scenePointerX, scenePointerY],
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
scenePointerX,
|
scenePointerX,
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
|
@ -5738,10 +5758,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
if (
|
if (
|
||||||
clicklength < 300 &&
|
clicklength < 300 &&
|
||||||
isIframeLikeElement(this.hitLinkElement) &&
|
isIframeLikeElement(this.hitLinkElement) &&
|
||||||
!isPointHittingLinkIcon(this.hitLinkElement, this.state, [
|
!isPointHittingLinkIcon(
|
||||||
scenePointer.x,
|
this.hitLinkElement,
|
||||||
scenePointer.y,
|
this.scene.getNonDeletedElementsMap(),
|
||||||
])
|
this.state,
|
||||||
|
[scenePointer.x, scenePointer.y],
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
||||||
} else {
|
} else {
|
||||||
|
@ -6039,7 +6061,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (this.state.activeTool.type === "selection") {
|
if (this.state.activeTool.type === "selection") {
|
||||||
const elements = this.scene.getNonDeletedElements();
|
const elements = this.scene.getNonDeletedElements();
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||||
|
|
||||||
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
||||||
const elementWithTransformHandleType =
|
const elementWithTransformHandleType =
|
||||||
getElementWithTransformHandleType(
|
getElementWithTransformHandleType(
|
||||||
|
@ -6049,6 +6073,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (elementWithTransformHandleType != null) {
|
if (elementWithTransformHandleType != null) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -6072,6 +6097,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
getResizeOffsetXY(
|
getResizeOffsetXY(
|
||||||
pointerDownState.resize.handleType,
|
pointerDownState.resize.handleType,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
|
elementsMap,
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
),
|
),
|
||||||
|
@ -6352,6 +6378,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.state,
|
this.state,
|
||||||
sceneX,
|
sceneX,
|
||||||
sceneY,
|
sceneY,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasBoundTextElement(element)) {
|
if (hasBoundTextElement(element)) {
|
||||||
|
@ -6846,6 +6873,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
selectedElements,
|
selectedElements,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6869,6 +6897,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
selectedElements,
|
selectedElements,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6985,6 +7014,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
this.state,
|
this.state,
|
||||||
!event[KEYS.CTRL_OR_CMD],
|
!event[KEYS.CTRL_OR_CMD],
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return;
|
return;
|
||||||
|
@ -7143,10 +7173,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.maybeCacheReferenceSnapPoints(event, selectedElements);
|
this.maybeCacheReferenceSnapPoints(event, selectedElements);
|
||||||
|
|
||||||
const { snapOffset, snapLines } = snapDraggedElements(
|
const { snapOffset, snapLines } = snapDraggedElements(
|
||||||
getSelectedElements(originalElements, this.state),
|
originalElements,
|
||||||
dragOffset,
|
dragOffset,
|
||||||
this.state,
|
this.state,
|
||||||
event,
|
event,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({ snapLines });
|
this.setState({ snapLines });
|
||||||
|
@ -7330,6 +7361,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
event,
|
event,
|
||||||
this.state,
|
this.state,
|
||||||
this.setState.bind(this),
|
this.setState.bind(this),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
// regular box-select
|
// regular box-select
|
||||||
} else {
|
} else {
|
||||||
|
@ -7360,6 +7392,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
const elementsWithinSelection = getElementsWithinSelection(
|
const elementsWithinSelection = getElementsWithinSelection(
|
||||||
elements,
|
elements,
|
||||||
draggingElement,
|
draggingElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
|
@ -7491,7 +7524,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedElementsAreBeingDragged: false,
|
selectedElementsAreBeingDragged: false,
|
||||||
});
|
});
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
// Handle end of dragging a point of a linear element, might close a loop
|
// Handle end of dragging a point of a linear element, might close a loop
|
||||||
// and sets binding element
|
// and sets binding element
|
||||||
if (this.state.editingLinearElement) {
|
if (this.state.editingLinearElement) {
|
||||||
|
@ -7506,6 +7539,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
childEvent,
|
childEvent,
|
||||||
this.state.editingLinearElement,
|
this.state.editingLinearElement,
|
||||||
this.state,
|
this.state,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (editingLinearElement !== this.state.editingLinearElement) {
|
if (editingLinearElement !== this.state.editingLinearElement) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -7529,6 +7563,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
childEvent,
|
childEvent,
|
||||||
this.state.selectedLinearElement,
|
this.state.selectedLinearElement,
|
||||||
this.state,
|
this.state,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { startBindingElement, endBindingElement } =
|
const { startBindingElement, endBindingElement } =
|
||||||
|
@ -7539,6 +7574,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
element,
|
element,
|
||||||
startBindingElement,
|
startBindingElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7678,6 +7714,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.state,
|
this.state,
|
||||||
this.scene,
|
this.scene,
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.setState({ suggestedBindings: [], startBoundElement: null });
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
||||||
|
@ -7748,7 +7785,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
const frame = getContainingFrame(linearElement);
|
const frame = getContainingFrame(linearElement);
|
||||||
|
|
||||||
if (frame && linearElement) {
|
if (frame && linearElement) {
|
||||||
if (!elementOverlapsWithFrame(linearElement, frame)) {
|
if (
|
||||||
|
!elementOverlapsWithFrame(
|
||||||
|
linearElement,
|
||||||
|
frame,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
// remove the linear element from all groups
|
// remove the linear element from all groups
|
||||||
// before removing it from the frame as well
|
// before removing it from the frame as well
|
||||||
mutateElement(linearElement, {
|
mutateElement(linearElement, {
|
||||||
|
@ -7859,6 +7902,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
const elementsInsideFrame = getElementsInNewFrame(
|
const elementsInsideFrame = getElementsInNewFrame(
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
draggingElement,
|
draggingElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.scene.replaceAllElements(
|
this.scene.replaceAllElements(
|
||||||
|
@ -7909,6 +7953,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
frame,
|
frame,
|
||||||
this.state,
|
this.state,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
frame,
|
frame,
|
||||||
this,
|
this,
|
||||||
|
@ -8189,7 +8234,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
||||||
(isBindingEnabled(this.state)
|
(isBindingEnabled(this.state)
|
||||||
? bindOrUnbindSelectedElements
|
? bindOrUnbindSelectedElements
|
||||||
: unbindLinearElements)(this.scene.getSelectedElements(this.state));
|
: unbindLinearElements)(
|
||||||
|
this.scene.getSelectedElements(this.state),
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTool.type === "laser") {
|
if (activeTool.type === "laser") {
|
||||||
|
@ -8719,7 +8767,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
if (selectedElements.length > 50) {
|
if (selectedElements.length > 50) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const suggestedBindings = getEligibleElementsForBinding(selectedElements);
|
const suggestedBindings = getEligibleElementsForBinding(
|
||||||
|
selectedElements,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
this.setState({ suggestedBindings });
|
this.setState({ suggestedBindings });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9058,6 +9109,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
x: gridX - pointerDownState.originInGrid.x,
|
x: gridX - pointerDownState.originInGrid.x,
|
||||||
y: gridY - pointerDownState.originInGrid.y,
|
y: gridY - pointerDownState.originInGrid.y,
|
||||||
},
|
},
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
gridX += snapOffset.x;
|
gridX += snapOffset.x;
|
||||||
|
@ -9096,6 +9148,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
draggingElement as ExcalidrawFrameLikeElement,
|
draggingElement as ExcalidrawFrameLikeElement,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9215,6 +9268,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
frame,
|
frame,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
).forEach((element) => elementsToHighlight.add(element));
|
).forEach((element) => elementsToHighlight.add(element));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -462,6 +462,7 @@ export const restoreElements = (
|
||||||
refreshTextDimensions(
|
refreshTextDimensions(
|
||||||
element,
|
element,
|
||||||
getContainerElement(element, restoredElementsMap),
|
getContainerElement(element, restoredElementsMap),
|
||||||
|
restoredElementsMap,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@ const bindTextToContainer = (
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
redrawTextBoundingBox(textElement, container);
|
redrawTextBoundingBox(textElement, container, elementsMap);
|
||||||
return [container, textElement] as const;
|
return [container, textElement] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -231,6 +231,7 @@ const bindLinearElementToElement = (
|
||||||
start: ValidLinearElement["start"],
|
start: ValidLinearElement["start"],
|
||||||
end: ValidLinearElement["end"],
|
end: ValidLinearElement["end"],
|
||||||
elementStore: ElementStore,
|
elementStore: ElementStore,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): {
|
): {
|
||||||
linearElement: ExcalidrawLinearElement;
|
linearElement: ExcalidrawLinearElement;
|
||||||
startBoundElement?: ExcalidrawElement;
|
startBoundElement?: ExcalidrawElement;
|
||||||
|
@ -316,6 +317,7 @@ const bindLinearElementToElement = (
|
||||||
linearElement,
|
linearElement,
|
||||||
startBoundElement as ExcalidrawBindableElement,
|
startBoundElement as ExcalidrawBindableElement,
|
||||||
"start",
|
"start",
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,6 +392,7 @@ const bindLinearElementToElement = (
|
||||||
linearElement,
|
linearElement,
|
||||||
endBoundElement as ExcalidrawBindableElement,
|
endBoundElement as ExcalidrawBindableElement,
|
||||||
"end",
|
"end",
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -612,6 +615,7 @@ export const convertToExcalidrawElements = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elementsMap = arrayToMap(elementStore.getElements());
|
||||||
// Add labels and arrow bindings
|
// Add labels and arrow bindings
|
||||||
for (const [id, element] of elementsWithIds) {
|
for (const [id, element] of elementsWithIds) {
|
||||||
const excalidrawElement = elementStore.getElement(id)!;
|
const excalidrawElement = elementStore.getElement(id)!;
|
||||||
|
@ -625,7 +629,7 @@ export const convertToExcalidrawElements = (
|
||||||
let [container, text] = bindTextToContainer(
|
let [container, text] = bindTextToContainer(
|
||||||
excalidrawElement,
|
excalidrawElement,
|
||||||
element?.label,
|
element?.label,
|
||||||
arrayToMap(elementStore.getElements()),
|
elementsMap,
|
||||||
);
|
);
|
||||||
elementStore.add(container);
|
elementStore.add(container);
|
||||||
elementStore.add(text);
|
elementStore.add(text);
|
||||||
|
@ -653,6 +657,7 @@ export const convertToExcalidrawElements = (
|
||||||
originalStart,
|
originalStart,
|
||||||
originalEnd,
|
originalEnd,
|
||||||
elementStore,
|
elementStore,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
container = linearElement;
|
container = linearElement;
|
||||||
elementStore.add(linearElement);
|
elementStore.add(linearElement);
|
||||||
|
@ -677,6 +682,7 @@ export const convertToExcalidrawElements = (
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
elementStore,
|
elementStore,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
elementStore.add(linearElement);
|
elementStore.add(linearElement);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { sceneCoordsToViewportCoords } from "../utils";
|
import { sceneCoordsToViewportCoords } from "../utils";
|
||||||
import { NonDeletedExcalidrawElement } from "./types";
|
import { ElementsMap, NonDeletedExcalidrawElement } from "./types";
|
||||||
import { getElementAbsoluteCoords } from ".";
|
import { getElementAbsoluteCoords } from ".";
|
||||||
import { useExcalidrawAppState } from "../components/App";
|
import { useExcalidrawAppState } from "../components/App";
|
||||||
|
|
||||||
|
@ -11,8 +11,9 @@ const CONTAINER_PADDING = 5;
|
||||||
const getContainerCoords = (
|
const getContainerCoords = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x1 + element.width, sceneY: y1 },
|
{ sceneX: x1 + element.width, sceneY: y1 },
|
||||||
appState,
|
appState,
|
||||||
|
@ -25,9 +26,11 @@ const getContainerCoords = (
|
||||||
export const ElementCanvasButtons = ({
|
export const ElementCanvasButtons = ({
|
||||||
children,
|
children,
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
element: NonDeletedExcalidrawElement;
|
element: NonDeletedExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
}) => {
|
}) => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useExcalidrawAppState();
|
||||||
|
|
||||||
|
@ -42,7 +45,7 @@ export const ElementCanvasButtons = ({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x, y } = getContainerCoords(element, appState);
|
const { x, y } = getContainerCoords(element, appState, elementsMap);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
import { getEmbedLink, embeddableURLValidator } from "./embeddable";
|
import { getEmbedLink, embeddableURLValidator } from "./embeddable";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawEmbeddableElement,
|
ExcalidrawEmbeddableElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
@ -60,12 +61,14 @@ const embeddableLinkCache = new Map<
|
||||||
|
|
||||||
export const Hyperlink = ({
|
export const Hyperlink = ({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
setAppState,
|
setAppState,
|
||||||
onLinkOpen,
|
onLinkOpen,
|
||||||
setToast,
|
setToast,
|
||||||
updateEmbedValidationStatus,
|
updateEmbedValidationStatus,
|
||||||
}: {
|
}: {
|
||||||
element: NonDeletedExcalidrawElement;
|
element: NonDeletedExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
||||||
setToast: (
|
setToast: (
|
||||||
|
@ -182,7 +185,7 @@ export const Hyperlink = ({
|
||||||
if (timeoutId) {
|
if (timeoutId) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
const shouldHide = shouldHideLinkPopup(element, appState, [
|
const shouldHide = shouldHideLinkPopup(element, elementsMap, appState, [
|
||||||
event.clientX,
|
event.clientX,
|
||||||
event.clientY,
|
event.clientY,
|
||||||
]) as boolean;
|
]) as boolean;
|
||||||
|
@ -199,7 +202,7 @@ export const Hyperlink = ({
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [appState, element, isEditing, setAppState]);
|
}, [appState, element, isEditing, setAppState, elementsMap]);
|
||||||
|
|
||||||
const handleRemove = useCallback(() => {
|
const handleRemove = useCallback(() => {
|
||||||
trackEvent("hyperlink", "delete");
|
trackEvent("hyperlink", "delete");
|
||||||
|
@ -214,7 +217,7 @@ export const Hyperlink = ({
|
||||||
trackEvent("hyperlink", "edit", "popup-ui");
|
trackEvent("hyperlink", "edit", "popup-ui");
|
||||||
setAppState({ showHyperlinkPopup: "editor" });
|
setAppState({ showHyperlinkPopup: "editor" });
|
||||||
};
|
};
|
||||||
const { x, y } = getCoordsForPopover(element, appState);
|
const { x, y } = getCoordsForPopover(element, appState, elementsMap);
|
||||||
if (
|
if (
|
||||||
appState.contextMenu ||
|
appState.contextMenu ||
|
||||||
appState.draggingElement ||
|
appState.draggingElement ||
|
||||||
|
@ -324,8 +327,9 @@ export const Hyperlink = ({
|
||||||
const getCoordsForPopover = (
|
const getCoordsForPopover = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
||||||
appState,
|
appState,
|
||||||
|
@ -430,11 +434,12 @@ export const getLinkHandleFromCoords = (
|
||||||
|
|
||||||
export const isPointHittingLinkIcon = (
|
export const isPointHittingLinkIcon = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
[x, y]: Point,
|
[x, y]: Point,
|
||||||
) => {
|
) => {
|
||||||
const threshold = 4 / appState.zoom.value;
|
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(
|
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||||
[x1, y1, x2, y2],
|
[x1, y1, x2, y2],
|
||||||
element.angle,
|
element.angle,
|
||||||
|
@ -450,6 +455,7 @@ export const isPointHittingLinkIcon = (
|
||||||
|
|
||||||
export const isPointHittingLink = (
|
export const isPointHittingLink = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
[x, y]: Point,
|
[x, y]: Point,
|
||||||
isMobile: boolean,
|
isMobile: boolean,
|
||||||
|
@ -461,23 +467,30 @@ export const isPointHittingLink = (
|
||||||
if (
|
if (
|
||||||
!isMobile &&
|
!isMobile &&
|
||||||
appState.viewModeEnabled &&
|
appState.viewModeEnabled &&
|
||||||
isPointHittingElementBoundingBox(element, [x, y], threshold, null)
|
isPointHittingElementBoundingBox(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
[x, y],
|
||||||
|
threshold,
|
||||||
|
null,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return isPointHittingLinkIcon(element, appState, [x, y]);
|
return isPointHittingLinkIcon(element, elementsMap, appState, [x, y]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let HYPERLINK_TOOLTIP_TIMEOUT_ID: number | null = null;
|
let HYPERLINK_TOOLTIP_TIMEOUT_ID: number | null = null;
|
||||||
export const showHyperlinkTooltip = (
|
export const showHyperlinkTooltip = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
|
if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
|
||||||
clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
|
clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
|
||||||
}
|
}
|
||||||
HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(
|
HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(
|
||||||
() => renderTooltip(element, appState),
|
() => renderTooltip(element, appState, elementsMap),
|
||||||
HYPERLINK_TOOLTIP_DELAY,
|
HYPERLINK_TOOLTIP_DELAY,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -485,6 +498,7 @@ export const showHyperlinkTooltip = (
|
||||||
const renderTooltip = (
|
const renderTooltip = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (!element.link) {
|
if (!element.link) {
|
||||||
return;
|
return;
|
||||||
|
@ -496,7 +510,7 @@ const renderTooltip = (
|
||||||
tooltipDiv.style.maxWidth = "20rem";
|
tooltipDiv.style.maxWidth = "20rem";
|
||||||
tooltipDiv.textContent = element.link;
|
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(
|
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||||
[x1, y1, x2, y2],
|
[x1, y1, x2, y2],
|
||||||
|
@ -535,6 +549,7 @@ export const hideHyperlinkToolip = () => {
|
||||||
|
|
||||||
export const shouldHideLinkPopup = (
|
export const shouldHideLinkPopup = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
[clientX, clientY]: Point,
|
[clientX, clientY]: Point,
|
||||||
): Boolean => {
|
): Boolean => {
|
||||||
|
@ -546,11 +561,17 @@ export const shouldHideLinkPopup = (
|
||||||
const threshold = 15 / appState.zoom.value;
|
const threshold = 15 / appState.zoom.value;
|
||||||
// hitbox to prevent hiding when hovered in element bounding box
|
// hitbox to prevent hiding when hovered in element bounding box
|
||||||
if (
|
if (
|
||||||
isPointHittingElementBoundingBox(element, [sceneX, sceneY], threshold, null)
|
isPointHittingElementBoundingBox(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
[sceneX, sceneY],
|
||||||
|
threshold,
|
||||||
|
null,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
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
|
// hit box to prevent hiding when hovered in the vertical area between element and popover
|
||||||
if (
|
if (
|
||||||
sceneX >= x1 &&
|
sceneX >= x1 &&
|
||||||
|
@ -561,7 +582,11 @@ export const shouldHideLinkPopup = (
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// hit box to prevent hiding when hovered around popover within threshold
|
// 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 (
|
if (
|
||||||
clientX >= popoverX - threshold &&
|
clientX >= popoverX - threshold &&
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
PointBinding,
|
PointBinding,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { getElementAtPosition } from "../scene";
|
import { getElementAtPosition } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
@ -66,6 +67,7 @@ export const bindOrUnbindLinearElement = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startBindingElement: ExcalidrawBindableElement | null | "keep",
|
startBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||||
endBindingElement: ExcalidrawBindableElement | null | "keep",
|
endBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||||
const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||||
|
@ -76,6 +78,7 @@ export const bindOrUnbindLinearElement = (
|
||||||
"start",
|
"start",
|
||||||
boundToElementIds,
|
boundToElementIds,
|
||||||
unboundFromElementIds,
|
unboundFromElementIds,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
bindOrUnbindLinearElementEdge(
|
bindOrUnbindLinearElementEdge(
|
||||||
linearElement,
|
linearElement,
|
||||||
|
@ -84,6 +87,7 @@ export const bindOrUnbindLinearElement = (
|
||||||
"end",
|
"end",
|
||||||
boundToElementIds,
|
boundToElementIds,
|
||||||
unboundFromElementIds,
|
unboundFromElementIds,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const onlyUnbound = Array.from(unboundFromElementIds).filter(
|
const onlyUnbound = Array.from(unboundFromElementIds).filter(
|
||||||
|
@ -111,6 +115,7 @@ const bindOrUnbindLinearElementEdge = (
|
||||||
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||||
// Is mutated
|
// Is mutated
|
||||||
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
|
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
if (bindableElement !== "keep") {
|
if (bindableElement !== "keep") {
|
||||||
if (bindableElement != null) {
|
if (bindableElement != null) {
|
||||||
|
@ -127,7 +132,12 @@ const bindOrUnbindLinearElementEdge = (
|
||||||
: startOrEnd === "start" ||
|
: startOrEnd === "start" ||
|
||||||
otherEdgeBindableElement.id !== bindableElement.id)
|
otherEdgeBindableElement.id !== bindableElement.id)
|
||||||
) {
|
) {
|
||||||
bindLinearElement(linearElement, bindableElement, startOrEnd);
|
bindLinearElement(
|
||||||
|
linearElement,
|
||||||
|
bindableElement,
|
||||||
|
startOrEnd,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
boundToElementIds.add(bindableElement.id);
|
boundToElementIds.add(bindableElement.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,23 +150,34 @@ const bindOrUnbindLinearElementEdge = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bindOrUnbindSelectedElements = (
|
export const bindOrUnbindSelectedElements = (
|
||||||
elements: NonDeleted<ExcalidrawElement>[],
|
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
elements.forEach((element) => {
|
selectedElements.forEach((selectedElement) => {
|
||||||
if (isBindingElement(element)) {
|
if (isBindingElement(selectedElement)) {
|
||||||
bindOrUnbindLinearElement(
|
bindOrUnbindLinearElement(
|
||||||
element,
|
selectedElement,
|
||||||
getElligibleElementForBindingElement(element, "start"),
|
getElligibleElementForBindingElement(
|
||||||
getElligibleElementForBindingElement(element, "end"),
|
selectedElement,
|
||||||
|
"start",
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
getElligibleElementForBindingElement(
|
||||||
|
selectedElement,
|
||||||
|
"end",
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
} else if (isBindableElement(element)) {
|
} else if (isBindableElement(selectedElement)) {
|
||||||
maybeBindBindableElement(element);
|
maybeBindBindableElement(selectedElement, elementsMap);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const maybeBindBindableElement = (
|
const maybeBindBindableElement = (
|
||||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
|
getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
|
||||||
([linearElement, where]) =>
|
([linearElement, where]) =>
|
||||||
|
@ -164,6 +185,7 @@ const maybeBindBindableElement = (
|
||||||
linearElement,
|
linearElement,
|
||||||
where === "end" ? "keep" : bindableElement,
|
where === "end" ? "keep" : bindableElement,
|
||||||
where === "start" ? "keep" : bindableElement,
|
where === "start" ? "keep" : bindableElement,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -173,9 +195,15 @@ export const maybeBindLinearElement = (
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
pointerCoords: { x: number; y: number },
|
pointerCoords: { x: number; y: number },
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
if (appState.startBoundElement != null) {
|
if (appState.startBoundElement != null) {
|
||||||
bindLinearElement(linearElement, appState.startBoundElement, "start");
|
bindLinearElement(
|
||||||
|
linearElement,
|
||||||
|
appState.startBoundElement,
|
||||||
|
"start",
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
|
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
|
||||||
if (
|
if (
|
||||||
|
@ -186,7 +214,7 @@ export const maybeBindLinearElement = (
|
||||||
"end",
|
"end",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
bindLinearElement(linearElement, hoveredElement, "end");
|
bindLinearElement(linearElement, hoveredElement, "end", elementsMap);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -194,11 +222,17 @@ export const bindLinearElement = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
hoveredElement: ExcalidrawBindableElement,
|
hoveredElement: ExcalidrawBindableElement,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
mutateElement(linearElement, {
|
mutateElement(linearElement, {
|
||||||
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
||||||
elementId: hoveredElement.id,
|
elementId: hoveredElement.id,
|
||||||
...calculateFocusAndGap(linearElement, hoveredElement, startOrEnd),
|
...calculateFocusAndGap(
|
||||||
|
linearElement,
|
||||||
|
hoveredElement,
|
||||||
|
startOrEnd,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
} as PointBinding,
|
} as PointBinding,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -240,10 +274,11 @@ export const isLinearElementSimpleAndAlreadyBound = (
|
||||||
|
|
||||||
export const unbindLinearElements = (
|
export const unbindLinearElements = (
|
||||||
elements: NonDeleted<ExcalidrawElement>[],
|
elements: NonDeleted<ExcalidrawElement>[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
if (isBindingElement(element)) {
|
if (isBindingElement(element)) {
|
||||||
bindOrUnbindLinearElement(element, null, null);
|
bindOrUnbindLinearElement(element, null, null, elementsMap);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -272,7 +307,11 @@ export const getHoveredElementForBinding = (
|
||||||
scene.getNonDeletedElements(),
|
scene.getNonDeletedElements(),
|
||||||
(element) =>
|
(element) =>
|
||||||
isBindableElement(element, false) &&
|
isBindableElement(element, false) &&
|
||||||
bindingBorderTest(element, pointerCoords),
|
bindingBorderTest(
|
||||||
|
element,
|
||||||
|
pointerCoords,
|
||||||
|
scene.getNonDeletedElementsMap(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
|
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
|
||||||
};
|
};
|
||||||
|
@ -281,21 +320,33 @@ const calculateFocusAndGap = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
hoveredElement: ExcalidrawBindableElement,
|
hoveredElement: ExcalidrawBindableElement,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): { focus: number; gap: number } => {
|
): { focus: number; gap: number } => {
|
||||||
const direction = startOrEnd === "start" ? -1 : 1;
|
const direction = startOrEnd === "start" ? -1 : 1;
|
||||||
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
||||||
const adjacentPointIndex = edgePointIndex - direction;
|
const adjacentPointIndex = edgePointIndex - direction;
|
||||||
|
|
||||||
const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
linearElement,
|
linearElement,
|
||||||
edgePointIndex,
|
edgePointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
linearElement,
|
linearElement,
|
||||||
adjacentPointIndex,
|
adjacentPointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
|
focus: determineFocusDistance(
|
||||||
gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)),
|
hoveredElement,
|
||||||
|
adjacentPoint,
|
||||||
|
edgePoint,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
gap: Math.max(
|
||||||
|
1,
|
||||||
|
distanceToBindableElement(hoveredElement, edgePoint, elementsMap),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -306,6 +357,8 @@ const calculateFocusAndGap = (
|
||||||
// in explicitly.
|
// in explicitly.
|
||||||
export const updateBoundElements = (
|
export const updateBoundElements = (
|
||||||
changedElement: NonDeletedExcalidrawElement,
|
changedElement: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
|
||||||
options?: {
|
options?: {
|
||||||
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
||||||
newSize?: { width: number; height: number };
|
newSize?: { width: number; height: number };
|
||||||
|
@ -355,12 +408,14 @@ export const updateBoundElements = (
|
||||||
"start",
|
"start",
|
||||||
startBinding,
|
startBinding,
|
||||||
changedElement as ExcalidrawBindableElement,
|
changedElement as ExcalidrawBindableElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
updateBoundPoint(
|
updateBoundPoint(
|
||||||
element,
|
element,
|
||||||
"end",
|
"end",
|
||||||
endBinding,
|
endBinding,
|
||||||
changedElement as ExcalidrawBindableElement,
|
changedElement as ExcalidrawBindableElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const boundText = getBoundTextElement(
|
const boundText = getBoundTextElement(
|
||||||
element,
|
element,
|
||||||
|
@ -393,6 +448,7 @@ const updateBoundPoint = (
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
binding: PointBinding | null | undefined,
|
binding: PointBinding | null | undefined,
|
||||||
changedElement: ExcalidrawBindableElement,
|
changedElement: ExcalidrawBindableElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
if (
|
if (
|
||||||
binding == null ||
|
binding == null ||
|
||||||
|
@ -414,11 +470,13 @@ const updateBoundPoint = (
|
||||||
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
linearElement,
|
linearElement,
|
||||||
adjacentPointIndex,
|
adjacentPointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const focusPointAbsolute = determineFocusPoint(
|
const focusPointAbsolute = determineFocusPoint(
|
||||||
bindingElement,
|
bindingElement,
|
||||||
binding.focus,
|
binding.focus,
|
||||||
adjacentPoint,
|
adjacentPoint,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
let newEdgePoint;
|
let newEdgePoint;
|
||||||
// The linear element was not originally pointing inside the bound shape,
|
// The linear element was not originally pointing inside the bound shape,
|
||||||
|
@ -431,6 +489,7 @@ const updateBoundPoint = (
|
||||||
adjacentPoint,
|
adjacentPoint,
|
||||||
focusPointAbsolute,
|
focusPointAbsolute,
|
||||||
binding.gap,
|
binding.gap,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (intersections.length === 0) {
|
if (intersections.length === 0) {
|
||||||
// This should never happen, since focusPoint should always be
|
// This should never happen, since focusPoint should always be
|
||||||
|
@ -449,6 +508,7 @@ const updateBoundPoint = (
|
||||||
point: LinearElementEditor.pointFromAbsoluteCoords(
|
point: LinearElementEditor.pointFromAbsoluteCoords(
|
||||||
linearElement,
|
linearElement,
|
||||||
newEdgePoint,
|
newEdgePoint,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -480,12 +540,14 @@ const maybeCalculateNewGapWhenScaling = (
|
||||||
// TODO: this is a bottleneck, optimise
|
// TODO: this is a bottleneck, optimise
|
||||||
export const getEligibleElementsForBinding = (
|
export const getEligibleElementsForBinding = (
|
||||||
elements: NonDeleted<ExcalidrawElement>[],
|
elements: NonDeleted<ExcalidrawElement>[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): SuggestedBinding[] => {
|
): SuggestedBinding[] => {
|
||||||
const includedElementIds = new Set(elements.map(({ id }) => id));
|
const includedElementIds = new Set(elements.map(({ id }) => id));
|
||||||
return elements.flatMap((element) =>
|
return elements.flatMap((element) =>
|
||||||
isBindingElement(element, false)
|
isBindingElement(element, false)
|
||||||
? (getElligibleElementsForBindingElement(
|
? (getElligibleElementsForBindingElement(
|
||||||
element as NonDeleted<ExcalidrawLinearElement>,
|
element as NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap,
|
||||||
).filter(
|
).filter(
|
||||||
(element) => !includedElementIds.has(element.id),
|
(element) => !includedElementIds.has(element.id),
|
||||||
) as SuggestedBinding[])
|
) as SuggestedBinding[])
|
||||||
|
@ -499,10 +561,11 @@ export const getEligibleElementsForBinding = (
|
||||||
|
|
||||||
const getElligibleElementsForBindingElement = (
|
const getElligibleElementsForBindingElement = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): NonDeleted<ExcalidrawBindableElement>[] => {
|
): NonDeleted<ExcalidrawBindableElement>[] => {
|
||||||
return [
|
return [
|
||||||
getElligibleElementForBindingElement(linearElement, "start"),
|
getElligibleElementForBindingElement(linearElement, "start", elementsMap),
|
||||||
getElligibleElementForBindingElement(linearElement, "end"),
|
getElligibleElementForBindingElement(linearElement, "end", elementsMap),
|
||||||
].filter(
|
].filter(
|
||||||
(element): element is NonDeleted<ExcalidrawBindableElement> =>
|
(element): element is NonDeleted<ExcalidrawBindableElement> =>
|
||||||
element != null,
|
element != null,
|
||||||
|
@ -512,9 +575,10 @@ const getElligibleElementsForBindingElement = (
|
||||||
const getElligibleElementForBindingElement = (
|
const getElligibleElementForBindingElement = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||||
return getHoveredElementForBinding(
|
return getHoveredElementForBinding(
|
||||||
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
|
||||||
Scene.getScene(linearElement)!,
|
Scene.getScene(linearElement)!,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -522,17 +586,23 @@ const getElligibleElementForBindingElement = (
|
||||||
const getLinearElementEdgeCoors = (
|
const getLinearElementEdgeCoors = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): { x: number; y: number } => {
|
): { x: number; y: number } => {
|
||||||
const index = startOrEnd === "start" ? 0 : -1;
|
const index = startOrEnd === "start" ? 0 : -1;
|
||||||
return tupleToCoors(
|
return tupleToCoors(
|
||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(linearElement, index),
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
linearElement,
|
||||||
|
index,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getElligibleElementsForBindableElementAndWhere = (
|
const getElligibleElementsForBindableElementAndWhere = (
|
||||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||||
): SuggestedPointBinding[] => {
|
): SuggestedPointBinding[] => {
|
||||||
return Scene.getScene(bindableElement)!
|
const scene = Scene.getScene(bindableElement)!;
|
||||||
|
return scene
|
||||||
.getNonDeletedElements()
|
.getNonDeletedElements()
|
||||||
.map((element) => {
|
.map((element) => {
|
||||||
if (!isBindingElement(element, false)) {
|
if (!isBindingElement(element, false)) {
|
||||||
|
@ -542,11 +612,13 @@ const getElligibleElementsForBindableElementAndWhere = (
|
||||||
element,
|
element,
|
||||||
"start",
|
"start",
|
||||||
bindableElement,
|
bindableElement,
|
||||||
|
scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
|
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
|
||||||
element,
|
element,
|
||||||
"end",
|
"end",
|
||||||
bindableElement,
|
bindableElement,
|
||||||
|
scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (!canBindStart && !canBindEnd) {
|
if (!canBindStart && !canBindEnd) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -564,6 +636,7 @@ const isLinearElementEligibleForNewBindingByBindable = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const existingBinding =
|
const existingBinding =
|
||||||
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
|
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
|
||||||
|
@ -576,7 +649,8 @@ const isLinearElementEligibleForNewBindingByBindable = (
|
||||||
) &&
|
) &&
|
||||||
bindingBorderTest(
|
bindingBorderTest(
|
||||||
bindableElement,
|
bindableElement,
|
||||||
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ROUNDNESS } from "../constants";
|
import { ROUNDNESS } from "../constants";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
|
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
|
||||||
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
|
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
|
||||||
|
|
||||||
|
@ -35,26 +36,26 @@ const _ce = ({
|
||||||
|
|
||||||
describe("getElementAbsoluteCoords", () => {
|
describe("getElementAbsoluteCoords", () => {
|
||||||
it("test x1 coordinate", () => {
|
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);
|
expect(x1).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test x2 coordinate", () => {
|
it("test x2 coordinate", () => {
|
||||||
const [, , x2] = getElementAbsoluteCoords(
|
const element = _ce({ x: 10, y: 20, w: 10, h: 0 });
|
||||||
_ce({ x: 10, y: 0, w: 10, h: 0 }),
|
const [, , x2] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||||
);
|
|
||||||
expect(x2).toEqual(20);
|
expect(x2).toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test y1 coordinate", () => {
|
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);
|
expect(y1).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test y2 coordinate", () => {
|
it("test y2 coordinate", () => {
|
||||||
const [, , , y2] = getElementAbsoluteCoords(
|
const element = _ce({ x: 0, y: 10, w: 0, h: 10 });
|
||||||
_ce({ x: 0, y: 10, w: 0, h: 10 }),
|
const [, , , y2] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||||
);
|
|
||||||
expect(y2).toEqual(20);
|
expect(y2).toEqual(20);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -102,8 +102,10 @@ export class ElementBounds {
|
||||||
): Bounds {
|
): Bounds {
|
||||||
let bounds: 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)) {
|
if (isFreeDrawElement(element)) {
|
||||||
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
||||||
element.points.map(([x, y]) =>
|
element.points.map(([x, y]) =>
|
||||||
|
@ -159,10 +161,9 @@ export class ElementBounds {
|
||||||
// This set of functions retrieves the absolute position of the 4 points.
|
// This set of functions retrieves the absolute position of the 4 points.
|
||||||
export const getElementAbsoluteCoords = (
|
export const getElementAbsoluteCoords = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
includeBoundText: boolean = false,
|
includeBoundText: boolean = false,
|
||||||
): [number, number, number, number, number, number] => {
|
): [number, number, number, number, number, number] => {
|
||||||
const elementsMap =
|
|
||||||
Scene.getScene(element)?.getElementsMapIncludingDeleted() || new Map();
|
|
||||||
if (isFreeDrawElement(element)) {
|
if (isFreeDrawElement(element)) {
|
||||||
return getFreeDrawElementAbsoluteCoords(element);
|
return getFreeDrawElementAbsoluteCoords(element);
|
||||||
} else if (isLinearElement(element)) {
|
} else if (isLinearElement(element)) {
|
||||||
|
@ -179,6 +180,7 @@ export const getElementAbsoluteCoords = (
|
||||||
const coords = LinearElementEditor.getBoundTextElementPosition(
|
const coords = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
element as ExcalidrawTextElementWithContainer,
|
element as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
return [
|
return [
|
||||||
coords.x,
|
coords.x,
|
||||||
|
@ -207,8 +209,12 @@ export const getElementAbsoluteCoords = (
|
||||||
*/
|
*/
|
||||||
export const getElementLineSegments = (
|
export const getElementLineSegments = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): [Point, Point][] => {
|
): [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];
|
const center: Point = [cx, cy];
|
||||||
|
|
||||||
|
@ -703,6 +709,7 @@ const getLinearElementRotatedBounds = (
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
[x, y, x, y],
|
[x, y, x, y],
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
|
@ -727,6 +734,7 @@ const getLinearElementRotatedBounds = (
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
coords,
|
coords,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
|
|
|
@ -91,6 +91,7 @@ export const hitTest = (
|
||||||
) {
|
) {
|
||||||
return isPointHittingElementBoundingBox(
|
return isPointHittingElementBoundingBox(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point,
|
point,
|
||||||
threshold,
|
threshold,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
|
@ -116,6 +117,7 @@ export const hitTest = (
|
||||||
appState,
|
appState,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
point,
|
point,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -145,9 +147,11 @@ export const isHittingElementBoundingBoxWithoutHittingElement = (
|
||||||
appState,
|
appState,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
[x, y],
|
[x, y],
|
||||||
|
elementsMap,
|
||||||
) &&
|
) &&
|
||||||
isPointHittingElementBoundingBox(
|
isPointHittingElementBoundingBox(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
[x, y],
|
[x, y],
|
||||||
threshold,
|
threshold,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
|
@ -160,6 +164,7 @@ export const isHittingElementNotConsideringBoundingBox = (
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
frameNameBoundsCache: FrameNameBoundsCache | null,
|
frameNameBoundsCache: FrameNameBoundsCache | null,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const threshold = 10 / appState.zoom.value;
|
const threshold = 10 / appState.zoom.value;
|
||||||
const check = isTextElement(element)
|
const check = isTextElement(element)
|
||||||
|
@ -169,6 +174,7 @@ export const isHittingElementNotConsideringBoundingBox = (
|
||||||
: isNearCheck;
|
: isNearCheck;
|
||||||
return hitTestPointAgainstElement({
|
return hitTestPointAgainstElement({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point,
|
point,
|
||||||
threshold,
|
threshold,
|
||||||
check,
|
check,
|
||||||
|
@ -183,6 +189,7 @@ const isElementSelected = (
|
||||||
|
|
||||||
export const isPointHittingElementBoundingBox = (
|
export const isPointHittingElementBoundingBox = (
|
||||||
element: NonDeleted<ExcalidrawElement>,
|
element: NonDeleted<ExcalidrawElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
[x, y]: Point,
|
[x, y]: Point,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
frameNameBoundsCache: FrameNameBoundsCache | null,
|
frameNameBoundsCache: FrameNameBoundsCache | null,
|
||||||
|
@ -194,6 +201,7 @@ export const isPointHittingElementBoundingBox = (
|
||||||
if (isFrameLikeElement(element)) {
|
if (isFrameLikeElement(element)) {
|
||||||
return hitTestPointAgainstElement({
|
return hitTestPointAgainstElement({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point: [x, y],
|
point: [x, y],
|
||||||
threshold,
|
threshold,
|
||||||
check: isInsideCheck,
|
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 elementCenterX = (x1 + x2) / 2;
|
||||||
const elementCenterY = (y1 + y2) / 2;
|
const elementCenterY = (y1 + y2) / 2;
|
||||||
// reverse rotate to take element's angle into account.
|
// reverse rotate to take element's angle into account.
|
||||||
|
@ -224,12 +232,14 @@ export const isPointHittingElementBoundingBox = (
|
||||||
export const bindingBorderTest = (
|
export const bindingBorderTest = (
|
||||||
element: NonDeleted<ExcalidrawBindableElement>,
|
element: NonDeleted<ExcalidrawBindableElement>,
|
||||||
{ x, y }: { x: number; y: number },
|
{ x, y }: { x: number; y: number },
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const threshold = maxBindingGap(element, element.width, element.height);
|
const threshold = maxBindingGap(element, element.width, element.height);
|
||||||
const check = isOutsideCheck;
|
const check = isOutsideCheck;
|
||||||
const point: Point = [x, y];
|
const point: Point = [x, y];
|
||||||
return hitTestPointAgainstElement({
|
return hitTestPointAgainstElement({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point,
|
point,
|
||||||
threshold,
|
threshold,
|
||||||
check,
|
check,
|
||||||
|
@ -251,6 +261,7 @@ export const maxBindingGap = (
|
||||||
|
|
||||||
type HitTestArgs = {
|
type HitTestArgs = {
|
||||||
element: NonDeletedExcalidrawElement;
|
element: NonDeletedExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
point: Point;
|
point: Point;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
check: (distance: number, threshold: number) => boolean;
|
check: (distance: number, threshold: number) => boolean;
|
||||||
|
@ -266,19 +277,28 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
||||||
case "text":
|
case "text":
|
||||||
case "diamond":
|
case "diamond":
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
const distance = distanceToBindableElement(args.element, args.point);
|
const distance = distanceToBindableElement(
|
||||||
|
args.element,
|
||||||
|
args.point,
|
||||||
|
args.elementsMap,
|
||||||
|
);
|
||||||
return args.check(distance, args.threshold);
|
return args.check(distance, args.threshold);
|
||||||
case "freedraw": {
|
case "freedraw": {
|
||||||
if (
|
if (
|
||||||
!args.check(
|
!args.check(
|
||||||
distanceToRectangle(args.element, args.point),
|
distanceToRectangle(args.element, args.point, args.elementsMap),
|
||||||
args.threshold,
|
args.threshold,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hitTestFreeDrawElement(args.element, args.point, args.threshold);
|
return hitTestFreeDrawElement(
|
||||||
|
args.element,
|
||||||
|
args.point,
|
||||||
|
args.threshold,
|
||||||
|
args.elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
case "arrow":
|
case "arrow":
|
||||||
case "line":
|
case "line":
|
||||||
|
@ -293,7 +313,7 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
||||||
// check distance to frame element first
|
// check distance to frame element first
|
||||||
if (
|
if (
|
||||||
args.check(
|
args.check(
|
||||||
distanceToBindableElement(args.element, args.point),
|
distanceToBindableElement(args.element, args.point, args.elementsMap),
|
||||||
args.threshold,
|
args.threshold,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -316,6 +336,7 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
||||||
export const distanceToBindableElement = (
|
export const distanceToBindableElement = (
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
|
@ -325,11 +346,11 @@ export const distanceToBindableElement = (
|
||||||
case "embeddable":
|
case "embeddable":
|
||||||
case "frame":
|
case "frame":
|
||||||
case "magicframe":
|
case "magicframe":
|
||||||
return distanceToRectangle(element, point);
|
return distanceToRectangle(element, point, elementsMap);
|
||||||
case "diamond":
|
case "diamond":
|
||||||
return distanceToDiamond(element, point);
|
return distanceToDiamond(element, point, elementsMap);
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
return distanceToEllipse(element, point);
|
return distanceToEllipse(element, point, elementsMap);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -358,8 +379,13 @@ const distanceToRectangle = (
|
||||||
| ExcalidrawIframeLikeElement
|
| ExcalidrawIframeLikeElement
|
||||||
| ExcalidrawFrameLikeElement,
|
| ExcalidrawFrameLikeElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||||
|
element,
|
||||||
|
point,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
return Math.max(
|
return Math.max(
|
||||||
GAPoint.distanceToLine(pointRel, GALine.equation(0, 1, -hheight)),
|
GAPoint.distanceToLine(pointRel, GALine.equation(0, 1, -hheight)),
|
||||||
GAPoint.distanceToLine(pointRel, GALine.equation(1, 0, -hwidth)),
|
GAPoint.distanceToLine(pointRel, GALine.equation(1, 0, -hwidth)),
|
||||||
|
@ -377,8 +403,13 @@ const distanceToRectangleBox = (box: RectangleBox, point: Point): number => {
|
||||||
const distanceToDiamond = (
|
const distanceToDiamond = (
|
||||||
element: ExcalidrawDiamondElement,
|
element: ExcalidrawDiamondElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||||
|
element,
|
||||||
|
point,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const side = GALine.equation(hheight, hwidth, -hheight * hwidth);
|
const side = GALine.equation(hheight, hwidth, -hheight * hwidth);
|
||||||
return GAPoint.distanceToLine(pointRel, side);
|
return GAPoint.distanceToLine(pointRel, side);
|
||||||
};
|
};
|
||||||
|
@ -386,16 +417,22 @@ const distanceToDiamond = (
|
||||||
const distanceToEllipse = (
|
const distanceToEllipse = (
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const [pointRel, tangent] = ellipseParamsForTest(element, point);
|
const [pointRel, tangent] = ellipseParamsForTest(element, point, elementsMap);
|
||||||
return -GALine.sign(tangent) * GAPoint.distanceToLine(pointRel, tangent);
|
return -GALine.sign(tangent) * GAPoint.distanceToLine(pointRel, tangent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ellipseParamsForTest = (
|
const ellipseParamsForTest = (
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): [GA.Point, GA.Line] => {
|
): [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);
|
const [px, py] = GAPoint.toTuple(pointRel);
|
||||||
|
|
||||||
// We're working in positive quadrant, so start with `t = 45deg`, `tx=cos(t)`
|
// We're working in positive quadrant, so start with `t = 45deg`, `tx=cos(t)`
|
||||||
|
@ -440,6 +477,7 @@ const hitTestFreeDrawElement = (
|
||||||
element: ExcalidrawFreeDrawElement,
|
element: ExcalidrawFreeDrawElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
// Check point-distance-to-line-segment for every segment in the
|
// Check point-distance-to-line-segment for every segment in the
|
||||||
// element's points (its input points, not its outline points).
|
// element's points (its input points, not its outline points).
|
||||||
|
@ -454,7 +492,10 @@ const hitTestFreeDrawElement = (
|
||||||
y = point[1] - element.y;
|
y = point[1] - element.y;
|
||||||
} else {
|
} else {
|
||||||
// Counter-rotate the point around center before testing
|
// 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(
|
const rotatedPoint = rotatePoint(
|
||||||
point,
|
point,
|
||||||
[minX + (maxX - minX) / 2, minY + (maxY - minY) / 2],
|
[minX + (maxX - minX) / 2, minY + (maxY - minY) / 2],
|
||||||
|
@ -520,6 +561,7 @@ const hitTestLinear = (args: HitTestArgs): boolean => {
|
||||||
const [point, pointAbs, hwidth, hheight] = pointRelativeToElement(
|
const [point, pointAbs, hwidth, hheight] = pointRelativeToElement(
|
||||||
args.element,
|
args.element,
|
||||||
args.point,
|
args.point,
|
||||||
|
args.elementsMap,
|
||||||
);
|
);
|
||||||
const side1 = GALine.equation(0, 1, -hheight);
|
const side1 = GALine.equation(0, 1, -hheight);
|
||||||
const side2 = GALine.equation(1, 0, -hwidth);
|
const side2 = GALine.equation(1, 0, -hwidth);
|
||||||
|
@ -572,9 +614,10 @@ const hitTestLinear = (args: HitTestArgs): boolean => {
|
||||||
const pointRelativeToElement = (
|
const pointRelativeToElement = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
pointTuple: Point,
|
pointTuple: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): [GA.Point, GA.Point, number, number] => {
|
): [GA.Point, GA.Point, number, number] => {
|
||||||
const point = GAPoint.from(pointTuple);
|
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);
|
const center = coordsCenter(x1, y1, x2, y2);
|
||||||
// GA has angle orientation opposite to `rotate`
|
// GA has angle orientation opposite to `rotate`
|
||||||
const rotate = GATransform.rotation(center, element.angle);
|
const rotate = GATransform.rotation(center, element.angle);
|
||||||
|
@ -609,11 +652,12 @@ const pointRelativeToDivElement = (
|
||||||
// Returns point in absolute coordinates
|
// Returns point in absolute coordinates
|
||||||
export const pointInAbsoluteCoords = (
|
export const pointInAbsoluteCoords = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
// Point relative to the element position
|
// Point relative to the element position
|
||||||
point: Point,
|
point: Point,
|
||||||
): Point => {
|
): Point => {
|
||||||
const [x, y] = 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 cx = (x2 - x1) / 2;
|
||||||
const cy = (y2 - y1) / 2;
|
const cy = (y2 - y1) / 2;
|
||||||
const [rotatedX, rotatedY] = rotate(x, y, cx, cy, element.angle);
|
const [rotatedX, rotatedY] = rotate(x, y, cx, cy, element.angle);
|
||||||
|
@ -622,8 +666,9 @@ export const pointInAbsoluteCoords = (
|
||||||
|
|
||||||
const relativizationToElementCenter = (
|
const relativizationToElementCenter = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): GA.Transform => {
|
): GA.Transform => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const center = coordsCenter(x1, y1, x2, y2);
|
const center = coordsCenter(x1, y1, x2, y2);
|
||||||
// GA has angle orientation opposite to `rotate`
|
// GA has angle orientation opposite to `rotate`
|
||||||
const rotate = GATransform.rotation(center, element.angle);
|
const rotate = GATransform.rotation(center, element.angle);
|
||||||
|
@ -649,12 +694,14 @@ const coordsCenter = (
|
||||||
// of the element.
|
// of the element.
|
||||||
export const determineFocusDistance = (
|
export const determineFocusDistance = (
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
|
|
||||||
// Point on the line, in absolute coordinates
|
// Point on the line, in absolute coordinates
|
||||||
a: Point,
|
a: Point,
|
||||||
// Another point on the line, in absolute coordinates (closer to element)
|
// Another point on the line, in absolute coordinates (closer to element)
|
||||||
b: Point,
|
b: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const relateToCenter = relativizationToElementCenter(element);
|
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||||
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
||||||
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
||||||
const line = GALine.through(aRel, bRel);
|
const line = GALine.through(aRel, bRel);
|
||||||
|
@ -693,13 +740,14 @@ export const determineFocusPoint = (
|
||||||
// returned focusPoint
|
// returned focusPoint
|
||||||
focus: number,
|
focus: number,
|
||||||
adjecentPoint: Point,
|
adjecentPoint: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point => {
|
): Point => {
|
||||||
if (focus === 0) {
|
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);
|
const center = coordsCenter(x1, y1, x2, y2);
|
||||||
return GAPoint.toTuple(center);
|
return GAPoint.toTuple(center);
|
||||||
}
|
}
|
||||||
const relateToCenter = relativizationToElementCenter(element);
|
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||||
const adjecentPointRel = GATransform.apply(
|
const adjecentPointRel = GATransform.apply(
|
||||||
relateToCenter,
|
relateToCenter,
|
||||||
GAPoint.from(adjecentPoint),
|
GAPoint.from(adjecentPoint),
|
||||||
|
@ -728,14 +776,16 @@ export const determineFocusPoint = (
|
||||||
// and the `element`, in ascending order of distance from `a`.
|
// and the `element`, in ascending order of distance from `a`.
|
||||||
export const intersectElementWithLine = (
|
export const intersectElementWithLine = (
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
|
|
||||||
// Point on the line, in absolute coordinates
|
// Point on the line, in absolute coordinates
|
||||||
a: Point,
|
a: Point,
|
||||||
// Another point on the line, in absolute coordinates
|
// Another point on the line, in absolute coordinates
|
||||||
b: Point,
|
b: Point,
|
||||||
// If given, the element is inflated by this value
|
// If given, the element is inflated by this value
|
||||||
gap: number = 0,
|
gap: number = 0,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point[] => {
|
): Point[] => {
|
||||||
const relateToCenter = relativizationToElementCenter(element);
|
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||||
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
||||||
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
||||||
const line = GALine.through(aRel, bRel);
|
const line = GALine.through(aRel, bRel);
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const dragSelectedElements = (
|
||||||
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, scene.getElementsMapIncludingDeleted(), {
|
||||||
simultaneouslyUpdated: Array.from(elementsToUpdate),
|
simultaneouslyUpdated: Array.from(elementsToUpdate),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -135,6 +135,7 @@ export class LinearElementEditor {
|
||||||
event: PointerEvent,
|
event: PointerEvent,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
setState: React.Component<any, AppState>["setState"],
|
setState: React.Component<any, AppState>["setState"],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
!appState.editingLinearElement ||
|
!appState.editingLinearElement ||
|
||||||
|
@ -151,10 +152,12 @@ export class LinearElementEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
||||||
getElementAbsoluteCoords(appState.draggingElement);
|
getElementAbsoluteCoords(appState.draggingElement, elementsMap);
|
||||||
|
|
||||||
const pointsSceneCoords =
|
const pointsSceneCoords = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
LinearElementEditor.getPointsGlobalCoordinates(element);
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const nextSelectedPoints = pointsSceneCoords.reduce(
|
const nextSelectedPoints = pointsSceneCoords.reduce(
|
||||||
(acc: number[], point, index) => {
|
(acc: number[], point, index) => {
|
||||||
|
@ -222,6 +225,7 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
referencePoint,
|
referencePoint,
|
||||||
[scenePointerX, scenePointerY],
|
[scenePointerX, scenePointerY],
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
|
@ -239,6 +243,7 @@ export class LinearElementEditor {
|
||||||
} else {
|
} else {
|
||||||
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
|
@ -255,6 +260,7 @@ export class LinearElementEditor {
|
||||||
linearElementEditor.pointerDownState.lastClickedPoint
|
linearElementEditor.pointerDownState.lastClickedPoint
|
||||||
? LinearElementEditor.createPointAt(
|
? LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
|
@ -290,6 +296,7 @@ export class LinearElementEditor {
|
||||||
LinearElementEditor.getPointGlobalCoordinates(
|
LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
element.points[0],
|
element.points[0],
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -303,6 +310,7 @@ export class LinearElementEditor {
|
||||||
LinearElementEditor.getPointGlobalCoordinates(
|
LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
element.points[lastSelectedIndex],
|
element.points[lastSelectedIndex],
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -323,6 +331,7 @@ export class LinearElementEditor {
|
||||||
event: PointerEvent,
|
event: PointerEvent,
|
||||||
editingLinearElement: LinearElementEditor,
|
editingLinearElement: LinearElementEditor,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): LinearElementEditor {
|
): LinearElementEditor {
|
||||||
const { elementId, selectedPointsIndices, isDragging, pointerDownState } =
|
const { elementId, selectedPointsIndices, isDragging, pointerDownState } =
|
||||||
editingLinearElement;
|
editingLinearElement;
|
||||||
|
@ -364,6 +373,7 @@ export class LinearElementEditor {
|
||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
selectedPoint!,
|
selectedPoint!,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Scene.getScene(element)!,
|
Scene.getScene(element)!,
|
||||||
|
@ -425,15 +435,23 @@ export class LinearElementEditor {
|
||||||
) {
|
) {
|
||||||
return editorMidPointsCache.points;
|
return editorMidPointsCache.points;
|
||||||
}
|
}
|
||||||
LinearElementEditor.updateEditorMidPointsCache(element, appState);
|
LinearElementEditor.updateEditorMidPointsCache(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
return editorMidPointsCache.points!;
|
return editorMidPointsCache.points!;
|
||||||
};
|
};
|
||||||
|
|
||||||
static updateEditorMidPointsCache = (
|
static updateEditorMidPointsCache = (
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
) => {
|
) => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const midpoints: (Point | null)[] = [];
|
const midpoints: (Point | null)[] = [];
|
||||||
|
@ -455,6 +473,7 @@ export class LinearElementEditor {
|
||||||
points[index],
|
points[index],
|
||||||
points[index + 1],
|
points[index + 1],
|
||||||
index + 1,
|
index + 1,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
midpoints.push(segmentMidPoint);
|
midpoints.push(segmentMidPoint);
|
||||||
index++;
|
index++;
|
||||||
|
@ -477,6 +496,7 @@ export class LinearElementEditor {
|
||||||
}
|
}
|
||||||
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
|
@ -484,7 +504,10 @@ export class LinearElementEditor {
|
||||||
if (clickedPointIndex >= 0) {
|
if (clickedPointIndex >= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (points.length >= 3 && !appState.editingLinearElement) {
|
if (points.length >= 3 && !appState.editingLinearElement) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -550,6 +573,7 @@ export class LinearElementEditor {
|
||||||
startPoint: Point,
|
startPoint: Point,
|
||||||
endPoint: Point,
|
endPoint: Point,
|
||||||
endPointIndex: number,
|
endPointIndex: number,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
let segmentMidPoint = centerPoint(startPoint, endPoint);
|
let segmentMidPoint = centerPoint(startPoint, endPoint);
|
||||||
if (element.points.length > 2 && element.roundness) {
|
if (element.points.length > 2 && element.roundness) {
|
||||||
|
@ -574,6 +598,7 @@ export class LinearElementEditor {
|
||||||
segmentMidPoint = LinearElementEditor.getPointGlobalCoordinates(
|
segmentMidPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
[tx, ty],
|
[tx, ty],
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -658,6 +683,7 @@ export class LinearElementEditor {
|
||||||
...element.points,
|
...element.points,
|
||||||
LinearElementEditor.createPointAt(
|
LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
|
@ -693,6 +719,7 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
|
@ -713,11 +740,12 @@ export class LinearElementEditor {
|
||||||
element,
|
element,
|
||||||
startBindingElement,
|
startBindingElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const targetPoint =
|
const targetPoint =
|
||||||
|
@ -779,6 +807,7 @@ export class LinearElementEditor {
|
||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): LinearElementEditor | null {
|
): LinearElementEditor | null {
|
||||||
if (!appState.editingLinearElement) {
|
if (!appState.editingLinearElement) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -809,6 +838,7 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
lastCommittedPoint,
|
lastCommittedPoint,
|
||||||
[scenePointerX, scenePointerY],
|
[scenePointerX, scenePointerY],
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
|
@ -821,6 +851,7 @@ export class LinearElementEditor {
|
||||||
} else {
|
} else {
|
||||||
newPoint = LinearElementEditor.createPointAt(
|
newPoint = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
||||||
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
|
@ -847,8 +878,9 @@ export class LinearElementEditor {
|
||||||
static getPointGlobalCoordinates(
|
static getPointGlobalCoordinates(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
point: Point,
|
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 cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
@ -860,8 +892,9 @@ export class LinearElementEditor {
|
||||||
/** scene coords */
|
/** scene coords */
|
||||||
static getPointsGlobalCoordinates(
|
static getPointsGlobalCoordinates(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point[] {
|
): Point[] {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
return element.points.map((point) => {
|
return element.points.map((point) => {
|
||||||
|
@ -873,13 +906,15 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
static getPointAtIndexGlobalCoordinates(
|
static getPointAtIndexGlobalCoordinates(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
|
||||||
indexMaybeFromEnd: number, // -1 for last element
|
indexMaybeFromEnd: number, // -1 for last element
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point {
|
): Point {
|
||||||
const index =
|
const index =
|
||||||
indexMaybeFromEnd < 0
|
indexMaybeFromEnd < 0
|
||||||
? element.points.length + indexMaybeFromEnd
|
? element.points.length + indexMaybeFromEnd
|
||||||
: indexMaybeFromEnd;
|
: indexMaybeFromEnd;
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
@ -893,8 +928,9 @@ export class LinearElementEditor {
|
||||||
static pointFromAbsoluteCoords(
|
static pointFromAbsoluteCoords(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
absoluteCoords: Point,
|
absoluteCoords: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point {
|
): Point {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const [x, y] = rotate(
|
const [x, y] = rotate(
|
||||||
|
@ -909,12 +945,15 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
static getPointIndexUnderCursor(
|
static getPointIndexUnderCursor(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
zoom: AppState["zoom"],
|
zoom: AppState["zoom"],
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) {
|
) {
|
||||||
const pointHandles =
|
const pointHandles = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
LinearElementEditor.getPointsGlobalCoordinates(element);
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
let idx = pointHandles.length;
|
let idx = pointHandles.length;
|
||||||
// loop from right to left because points on the right are rendered over
|
// 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
|
// points on the left, thus should take precedence when clicking, if they
|
||||||
|
@ -934,12 +973,13 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
static createPointAt(
|
static createPointAt(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
gridSize: number | null,
|
gridSize: number | null,
|
||||||
): Point {
|
): Point {
|
||||||
const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, gridSize);
|
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 cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const [rotatedX, rotatedY] = rotate(
|
const [rotatedX, rotatedY] = rotate(
|
||||||
|
@ -1190,6 +1230,7 @@ export class LinearElementEditor {
|
||||||
pointerCoords: PointerCoords,
|
pointerCoords: PointerCoords,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
snapToGrid: boolean,
|
snapToGrid: boolean,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
const element = LinearElementEditor.getElement(
|
const element = LinearElementEditor.getElement(
|
||||||
linearElementEditor.elementId,
|
linearElementEditor.elementId,
|
||||||
|
@ -1208,6 +1249,7 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
const midpoint = LinearElementEditor.createPointAt(
|
const midpoint = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
pointerCoords.x,
|
pointerCoords.x,
|
||||||
pointerCoords.y,
|
pointerCoords.y,
|
||||||
snapToGrid ? appState.gridSize : null,
|
snapToGrid ? appState.gridSize : null,
|
||||||
|
@ -1260,6 +1302,7 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
private static _getShiftLockedDelta(
|
private static _getShiftLockedDelta(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
referencePoint: Point,
|
referencePoint: Point,
|
||||||
scenePointer: Point,
|
scenePointer: Point,
|
||||||
gridSize: number | null,
|
gridSize: number | null,
|
||||||
|
@ -1267,6 +1310,7 @@ export class LinearElementEditor {
|
||||||
const referencePointCoords = LinearElementEditor.getPointGlobalCoordinates(
|
const referencePointCoords = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
referencePoint,
|
referencePoint,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
|
@ -1288,8 +1332,12 @@ export class LinearElementEditor {
|
||||||
static getBoundTextElementPosition = (
|
static getBoundTextElementPosition = (
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): { x: number; y: number } => {
|
): { x: number; y: number } => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (points.length < 2) {
|
if (points.length < 2) {
|
||||||
mutateElement(boundTextElement, { isDeleted: true });
|
mutateElement(boundTextElement, { isDeleted: true });
|
||||||
}
|
}
|
||||||
|
@ -1300,6 +1348,7 @@ export class LinearElementEditor {
|
||||||
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
element.points[index],
|
element.points[index],
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
x = midPoint[0] - boundTextElement.width / 2;
|
x = midPoint[0] - boundTextElement.width / 2;
|
||||||
y = midPoint[1] - boundTextElement.height / 2;
|
y = midPoint[1] - boundTextElement.height / 2;
|
||||||
|
@ -1319,6 +1368,7 @@ export class LinearElementEditor {
|
||||||
points[index],
|
points[index],
|
||||||
points[index + 1],
|
points[index + 1],
|
||||||
index + 1,
|
index + 1,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
x = midSegmentMidpoint[0] - boundTextElement.width / 2;
|
x = midSegmentMidpoint[0] - boundTextElement.width / 2;
|
||||||
|
@ -1329,6 +1379,7 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
static getMinMaxXYWithBoundText = (
|
static getMinMaxXYWithBoundText = (
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
elementBounds: Bounds,
|
elementBounds: Bounds,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
): [number, number, number, number, number, number] => {
|
): [number, number, number, number, number, number] => {
|
||||||
|
@ -1339,6 +1390,7 @@ export class LinearElementEditor {
|
||||||
LinearElementEditor.getBoundTextElementPosition(
|
LinearElementEditor.getBoundTextElementPosition(
|
||||||
element,
|
element,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const boundTextX2 = boundTextX1 + boundTextElement.width;
|
const boundTextX2 = boundTextX1 + boundTextElement.width;
|
||||||
const boundTextY2 = boundTextY1 + boundTextElement.height;
|
const boundTextY2 = boundTextY1 + boundTextElement.height;
|
||||||
|
@ -1479,6 +1531,7 @@ export class LinearElementEditor {
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
coords = LinearElementEditor.getMinMaxXYWithBoundText(
|
coords = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
[x1, y1, x2, y2],
|
[x1, y1, x2, y2],
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
ExcalidrawEmbeddableElement,
|
ExcalidrawEmbeddableElement,
|
||||||
ExcalidrawMagicFrameElement,
|
ExcalidrawMagicFrameElement,
|
||||||
ExcalidrawIframeElement,
|
ExcalidrawIframeElement,
|
||||||
|
ElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import {
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
|
@ -260,6 +261,7 @@ export const newTextElement = (
|
||||||
|
|
||||||
const getAdjustedDimensions = (
|
const getAdjustedDimensions = (
|
||||||
element: ExcalidrawTextElement,
|
element: ExcalidrawTextElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
nextText: string,
|
nextText: string,
|
||||||
): {
|
): {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -294,7 +296,7 @@ const getAdjustedDimensions = (
|
||||||
x = element.x - offsets.x;
|
x = element.x - offsets.x;
|
||||||
y = element.y - offsets.y;
|
y = element.y - offsets.y;
|
||||||
} else {
|
} else {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
|
||||||
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
||||||
element,
|
element,
|
||||||
|
@ -335,6 +337,7 @@ const getAdjustedDimensions = (
|
||||||
export const refreshTextDimensions = (
|
export const refreshTextDimensions = (
|
||||||
textElement: ExcalidrawTextElement,
|
textElement: ExcalidrawTextElement,
|
||||||
container: ExcalidrawTextContainer | null,
|
container: ExcalidrawTextContainer | null,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
text = textElement.text,
|
text = textElement.text,
|
||||||
) => {
|
) => {
|
||||||
if (textElement.isDeleted) {
|
if (textElement.isDeleted) {
|
||||||
|
@ -347,13 +350,14 @@ export const refreshTextDimensions = (
|
||||||
getBoundTextMaxWidth(container, textElement),
|
getBoundTextMaxWidth(container, textElement),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const dimensions = getAdjustedDimensions(textElement, text);
|
const dimensions = getAdjustedDimensions(textElement, elementsMap, text);
|
||||||
return { text, ...dimensions };
|
return { text, ...dimensions };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateTextElement = (
|
export const updateTextElement = (
|
||||||
textElement: ExcalidrawTextElement,
|
textElement: ExcalidrawTextElement,
|
||||||
container: ExcalidrawTextContainer | null,
|
container: ExcalidrawTextContainer | null,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
{
|
{
|
||||||
text,
|
text,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
|
@ -367,7 +371,7 @@ export const updateTextElement = (
|
||||||
return newElementWith(textElement, {
|
return newElementWith(textElement, {
|
||||||
originalText,
|
originalText,
|
||||||
isDeleted: isDeleted ?? textElement.isDeleted,
|
isDeleted: isDeleted ?? textElement.isDeleted,
|
||||||
...refreshTextDimensions(textElement, container, originalText),
|
...refreshTextDimensions(textElement, container, elementsMap, originalText),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -86,11 +86,12 @@ export const transformElements = (
|
||||||
if (transformHandleType === "rotation") {
|
if (transformHandleType === "rotation") {
|
||||||
rotateSingleElement(
|
rotateSingleElement(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
shouldRotateWithDiscreteAngle,
|
shouldRotateWithDiscreteAngle,
|
||||||
);
|
);
|
||||||
updateBoundElements(element);
|
updateBoundElements(element, elementsMap);
|
||||||
} else if (
|
} else if (
|
||||||
isTextElement(element) &&
|
isTextElement(element) &&
|
||||||
(transformHandleType === "nw" ||
|
(transformHandleType === "nw" ||
|
||||||
|
@ -106,7 +107,7 @@ export const transformElements = (
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
);
|
);
|
||||||
updateBoundElements(element);
|
updateBoundElements(element, elementsMap);
|
||||||
} else if (transformHandleType) {
|
} else if (transformHandleType) {
|
||||||
resizeSingleElement(
|
resizeSingleElement(
|
||||||
originalElements,
|
originalElements,
|
||||||
|
@ -157,11 +158,12 @@ export const transformElements = (
|
||||||
|
|
||||||
const rotateSingleElement = (
|
const rotateSingleElement = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
shouldRotateWithDiscreteAngle: boolean,
|
shouldRotateWithDiscreteAngle: boolean,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
let angle: number;
|
let angle: number;
|
||||||
|
@ -266,7 +268,7 @@ const resizeSingleTextElement = (
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
// rotation pointer with reverse angle
|
// rotation pointer with reverse angle
|
||||||
|
@ -629,7 +631,7 @@ export const resizeSingleElement = (
|
||||||
) {
|
) {
|
||||||
mutateElement(element, resizedElement);
|
mutateElement(element, resizedElement);
|
||||||
|
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, elementsMap, {
|
||||||
newSize: { width: resizedElement.width, height: resizedElement.height },
|
newSize: { width: resizedElement.width, height: resizedElement.height },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -696,7 +698,11 @@ export const resizeMultipleElements = (
|
||||||
if (!isBoundToContainer(text)) {
|
if (!isBoundToContainer(text)) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
const xy = LinearElementEditor.getBoundTextElementPosition(orig, text);
|
const xy = LinearElementEditor.getBoundTextElementPosition(
|
||||||
|
orig,
|
||||||
|
text,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
return [...acc, { ...text, ...xy }];
|
return [...acc, { ...text, ...xy }];
|
||||||
}, [] as ExcalidrawTextElementWithContainer[]);
|
}, [] as ExcalidrawTextElementWithContainer[]);
|
||||||
|
|
||||||
|
@ -879,7 +885,7 @@ export const resizeMultipleElements = (
|
||||||
|
|
||||||
mutateElement(element, update, false);
|
mutateElement(element, update, false);
|
||||||
|
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, elementsMap, {
|
||||||
simultaneouslyUpdated: elementsToUpdate,
|
simultaneouslyUpdated: elementsToUpdate,
|
||||||
newSize: { width, height },
|
newSize: { width, height },
|
||||||
});
|
});
|
||||||
|
@ -921,7 +927,7 @@ const rotateMultipleElements = (
|
||||||
elements
|
elements
|
||||||
.filter((element) => !isFrameLikeElement(element))
|
.filter((element) => !isFrameLikeElement(element))
|
||||||
.forEach((element) => {
|
.forEach((element) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const origAngle =
|
const origAngle =
|
||||||
|
@ -942,7 +948,9 @@ const rotateMultipleElements = (
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
updateBoundElements(element, { simultaneouslyUpdated: elements });
|
updateBoundElements(element, elementsMap, {
|
||||||
|
simultaneouslyUpdated: elements,
|
||||||
|
});
|
||||||
|
|
||||||
const boundText = getBoundTextElement(element, elementsMap);
|
const boundText = getBoundTextElement(element, elementsMap);
|
||||||
if (boundText && !isArrowElement(element)) {
|
if (boundText && !isArrowElement(element)) {
|
||||||
|
@ -964,12 +972,13 @@ const rotateMultipleElements = (
|
||||||
export const getResizeOffsetXY = (
|
export const getResizeOffsetXY = (
|
||||||
transformHandleType: MaybeTransformHandleType,
|
transformHandleType: MaybeTransformHandleType,
|
||||||
selectedElements: NonDeletedExcalidrawElement[],
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
): [number, number] => {
|
): [number, number] => {
|
||||||
const [x1, y1, x2, y2] =
|
const [x1, y1, x2, y2] =
|
||||||
selectedElements.length === 1
|
selectedElements.length === 1
|
||||||
? getElementAbsoluteCoords(selectedElements[0])
|
? getElementAbsoluteCoords(selectedElements[0], elementsMap)
|
||||||
: getCommonBounds(selectedElements);
|
: getCommonBounds(selectedElements);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
PointerType,
|
PointerType,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
|
ElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -27,6 +28,7 @@ const isInsideTransformHandle = (
|
||||||
|
|
||||||
export const resizeTest = (
|
export const resizeTest = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
@ -38,7 +40,7 @@ export const resizeTest = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rotation: rotationTransformHandle, ...transformHandles } =
|
const { rotation: rotationTransformHandle, ...transformHandles } =
|
||||||
getTransformHandles(element, zoom, pointerType);
|
getTransformHandles(element, zoom, elementsMap, pointerType);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
rotationTransformHandle &&
|
rotationTransformHandle &&
|
||||||
|
@ -70,6 +72,7 @@ export const getElementWithTransformHandleType = (
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
pointerType: PointerType,
|
pointerType: PointerType,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return elements.reduce((result, element) => {
|
return elements.reduce((result, element) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
|
@ -77,6 +80,7 @@ export const getElementWithTransformHandleType = (
|
||||||
}
|
}
|
||||||
const transformHandleType = resizeTest(
|
const transformHandleType = resizeTest(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
appState,
|
appState,
|
||||||
scenePointerX,
|
scenePointerX,
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
|
|
|
@ -53,6 +53,7 @@ const splitIntoLines = (text: string) => {
|
||||||
export const redrawTextBoundingBox = (
|
export const redrawTextBoundingBox = (
|
||||||
textElement: ExcalidrawTextElement,
|
textElement: ExcalidrawTextElement,
|
||||||
container: ExcalidrawElement | null,
|
container: ExcalidrawElement | null,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
let maxWidth = undefined;
|
let maxWidth = undefined;
|
||||||
const boundTextUpdates = {
|
const boundTextUpdates = {
|
||||||
|
@ -110,7 +111,11 @@ export const redrawTextBoundingBox = (
|
||||||
...textElement,
|
...textElement,
|
||||||
...boundTextUpdates,
|
...boundTextUpdates,
|
||||||
} as ExcalidrawTextElementWithContainer;
|
} as ExcalidrawTextElementWithContainer;
|
||||||
const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
const { x, y } = computeBoundTextPosition(
|
||||||
|
container,
|
||||||
|
updatedTextElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
boundTextUpdates.x = x;
|
boundTextUpdates.x = x;
|
||||||
boundTextUpdates.y = y;
|
boundTextUpdates.y = y;
|
||||||
}
|
}
|
||||||
|
@ -119,11 +124,11 @@ export const redrawTextBoundingBox = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bindTextToShapeAfterDuplication = (
|
export const bindTextToShapeAfterDuplication = (
|
||||||
sceneElements: ExcalidrawElement[],
|
newElements: ExcalidrawElement[],
|
||||||
oldElements: ExcalidrawElement[],
|
oldElements: ExcalidrawElement[],
|
||||||
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
||||||
): void => {
|
): void => {
|
||||||
const sceneElementMap = arrayToMap(sceneElements) as Map<
|
const newElementsMap = arrayToMap(newElements) as Map<
|
||||||
ExcalidrawElement["id"],
|
ExcalidrawElement["id"],
|
||||||
ExcalidrawElement
|
ExcalidrawElement
|
||||||
>;
|
>;
|
||||||
|
@ -134,7 +139,7 @@ export const bindTextToShapeAfterDuplication = (
|
||||||
if (boundTextElementId) {
|
if (boundTextElementId) {
|
||||||
const newTextElementId = oldIdToDuplicatedId.get(boundTextElementId);
|
const newTextElementId = oldIdToDuplicatedId.get(boundTextElementId);
|
||||||
if (newTextElementId) {
|
if (newTextElementId) {
|
||||||
const newContainer = sceneElementMap.get(newElementId);
|
const newContainer = newElementsMap.get(newElementId);
|
||||||
if (newContainer) {
|
if (newContainer) {
|
||||||
mutateElement(newContainer, {
|
mutateElement(newContainer, {
|
||||||
boundElements: (element.boundElements || [])
|
boundElements: (element.boundElements || [])
|
||||||
|
@ -149,7 +154,7 @@ export const bindTextToShapeAfterDuplication = (
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newTextElement = sceneElementMap.get(newTextElementId);
|
const newTextElement = newElementsMap.get(newTextElementId);
|
||||||
if (newTextElement && isTextElement(newTextElement)) {
|
if (newTextElement && isTextElement(newTextElement)) {
|
||||||
mutateElement(newTextElement, {
|
mutateElement(newTextElement, {
|
||||||
containerId: newContainer ? newElementId : null,
|
containerId: newContainer ? newElementId : null,
|
||||||
|
@ -236,7 +241,7 @@ export const handleBindTextResize = (
|
||||||
if (!isArrowElement(container)) {
|
if (!isArrowElement(container)) {
|
||||||
mutateElement(
|
mutateElement(
|
||||||
textElement,
|
textElement,
|
||||||
computeBoundTextPosition(container, textElement),
|
computeBoundTextPosition(container, textElement, elementsMap),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,11 +250,13 @@ export const handleBindTextResize = (
|
||||||
export const computeBoundTextPosition = (
|
export const computeBoundTextPosition = (
|
||||||
container: ExcalidrawElement,
|
container: ExcalidrawElement,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
return LinearElementEditor.getBoundTextElementPosition(
|
return LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const containerCoords = getContainerCoords(container);
|
const containerCoords = getContainerCoords(container);
|
||||||
|
@ -698,12 +705,16 @@ export const getContainerCenter = (
|
||||||
y: container.y + container.height / 2,
|
y: container.y + container.height / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(container);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
container,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (points.length % 2 === 1) {
|
if (points.length % 2 === 1) {
|
||||||
const index = Math.floor(container.points.length / 2);
|
const index = Math.floor(container.points.length / 2);
|
||||||
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
container,
|
container,
|
||||||
container.points[index],
|
container.points[index],
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
return { x: midPoint[0], y: midPoint[1] };
|
return { x: midPoint[0], y: midPoint[1] };
|
||||||
}
|
}
|
||||||
|
@ -719,6 +730,7 @@ export const getContainerCenter = (
|
||||||
points[index],
|
points[index],
|
||||||
points[index + 1],
|
points[index + 1],
|
||||||
index + 1,
|
index + 1,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] };
|
return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] };
|
||||||
|
@ -757,11 +769,13 @@ export const getTextElementAngle = (
|
||||||
export const getBoundTextElementPosition = (
|
export const getBoundTextElementPosition = (
|
||||||
container: ExcalidrawElement,
|
container: ExcalidrawElement,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
return LinearElementEditor.getBoundTextElementPosition(
|
return LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -804,6 +818,7 @@ export const getTextBindableContainerAtPosition = (
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): ExcalidrawTextContainer | null => {
|
): ExcalidrawTextContainer | null => {
|
||||||
const selectedElements = getSelectedElements(elements, appState);
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
if (selectedElements.length === 1) {
|
if (selectedElements.length === 1) {
|
||||||
|
@ -817,7 +832,10 @@ export const getTextBindableContainerAtPosition = (
|
||||||
if (elements[index].isDeleted) {
|
if (elements[index].isDeleted) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index]);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(
|
||||||
|
elements[index],
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
isArrowElement(elements[index]) &&
|
isArrowElement(elements[index]) &&
|
||||||
isHittingElementNotConsideringBoundingBox(
|
isHittingElementNotConsideringBoundingBox(
|
||||||
|
@ -825,6 +843,7 @@ export const getTextBindableContainerAtPosition = (
|
||||||
appState,
|
appState,
|
||||||
null,
|
null,
|
||||||
[x, y],
|
[x, y],
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
hitElement = elements[index];
|
hitElement = elements[index];
|
||||||
|
|
|
@ -121,13 +121,13 @@ export const textWysiwyg = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { textAlign, verticalAlign } = updatedTextElement;
|
const { textAlign, verticalAlign } = updatedTextElement;
|
||||||
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
if (updatedTextElement && isTextElement(updatedTextElement)) {
|
if (updatedTextElement && isTextElement(updatedTextElement)) {
|
||||||
let coordX = updatedTextElement.x;
|
let coordX = updatedTextElement.x;
|
||||||
let coordY = updatedTextElement.y;
|
let coordY = updatedTextElement.y;
|
||||||
const container = getContainerElement(
|
const container = getContainerElement(
|
||||||
updatedTextElement,
|
updatedTextElement,
|
||||||
app.scene.getElementsMapIncludingDeleted(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
let maxWidth = updatedTextElement.width;
|
let maxWidth = updatedTextElement.width;
|
||||||
|
|
||||||
|
@ -143,6 +143,7 @@ export const textWysiwyg = ({
|
||||||
LinearElementEditor.getBoundTextElementPosition(
|
LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
coordX = boundTextCoords.x;
|
coordX = boundTextCoords.x;
|
||||||
coordY = boundTextCoords.y;
|
coordY = boundTextCoords.y;
|
||||||
|
@ -200,6 +201,7 @@ export const textWysiwyg = ({
|
||||||
const { y } = computeBoundTextPosition(
|
const { y } = computeBoundTextPosition(
|
||||||
container,
|
container,
|
||||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
coordY = y;
|
coordY = y;
|
||||||
}
|
}
|
||||||
|
@ -326,7 +328,7 @@ export const textWysiwyg = ({
|
||||||
}
|
}
|
||||||
const container = getContainerElement(
|
const container = getContainerElement(
|
||||||
element,
|
element,
|
||||||
app.scene.getElementsMapIncludingDeleted(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const font = getFontString({
|
const font = getFontString({
|
||||||
|
@ -513,7 +515,7 @@ export const textWysiwyg = ({
|
||||||
let text = editable.value;
|
let text = editable.value;
|
||||||
const container = getContainerElement(
|
const container = getContainerElement(
|
||||||
updateElement,
|
updateElement,
|
||||||
app.scene.getElementsMapIncludingDeleted(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
|
@ -541,7 +543,11 @@ export const textWysiwyg = ({
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
redrawTextBoundingBox(updateElement, container);
|
redrawTextBoundingBox(
|
||||||
|
updateElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit({
|
onSubmit({
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
PointerType,
|
PointerType,
|
||||||
|
@ -230,6 +231,8 @@ export const getTransformHandlesFromCoords = (
|
||||||
export const getTransformHandles = (
|
export const getTransformHandles = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
|
||||||
pointerType: PointerType = "mouse",
|
pointerType: PointerType = "mouse",
|
||||||
): TransformHandles => {
|
): TransformHandles => {
|
||||||
// so that when locked element is selected (especially when you toggle lock
|
// 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 + 8
|
||||||
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
||||||
return getTransformHandlesFromCoords(
|
return getTransformHandlesFromCoords(
|
||||||
getElementAbsoluteCoords(element, true),
|
getElementAbsoluteCoords(element, elementsMap, true),
|
||||||
element.angle,
|
element.angle,
|
||||||
zoom,
|
zoom,
|
||||||
pointerType,
|
pointerType,
|
||||||
|
|
|
@ -65,10 +65,11 @@ export const bindElementsToFramesAfterDuplication = (
|
||||||
export function isElementIntersectingFrame(
|
export function isElementIntersectingFrame(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
const frameLineSegments = getElementLineSegments(frame);
|
const frameLineSegments = getElementLineSegments(frame, elementsMap);
|
||||||
|
|
||||||
const elementLineSegments = getElementLineSegments(element);
|
const elementLineSegments = getElementLineSegments(element, elementsMap);
|
||||||
|
|
||||||
const intersecting = frameLineSegments.some((frameLineSegment) =>
|
const intersecting = frameLineSegments.some((frameLineSegment) =>
|
||||||
elementLineSegments.some((elementLineSegment) =>
|
elementLineSegments.some((elementLineSegment) =>
|
||||||
|
@ -82,9 +83,10 @@ export function isElementIntersectingFrame(
|
||||||
export const getElementsCompletelyInFrame = (
|
export const getElementsCompletelyInFrame = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) =>
|
) =>
|
||||||
omitGroupsContainingFrameLikes(
|
omitGroupsContainingFrameLikes(
|
||||||
getElementsWithinSelection(elements, frame, false),
|
getElementsWithinSelection(elements, frame, elementsMap, false),
|
||||||
).filter(
|
).filter(
|
||||||
(element) =>
|
(element) =>
|
||||||
(!isFrameLikeElement(element) && !element.frameId) ||
|
(!isFrameLikeElement(element) && !element.frameId) ||
|
||||||
|
@ -95,8 +97,9 @@ export const isElementContainingFrame = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return getElementsWithinSelection(elements, element).some(
|
return getElementsWithinSelection(elements, element, elementsMap).some(
|
||||||
(e) => e.id === frame.id,
|
(e) => e.id === frame.id,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -104,13 +107,22 @@ export const isElementContainingFrame = (
|
||||||
export const getElementsIntersectingFrame = (
|
export const getElementsIntersectingFrame = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => elements.filter((element) => isElementIntersectingFrame(element, frame));
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
|
return elements.filter((element) =>
|
||||||
|
isElementIntersectingFrame(element, frame, elementsMap),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const elementsAreInFrameBounds = (
|
export const elementsAreInFrameBounds = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [frameX1, frameY1, frameX2, frameY2] = getElementAbsoluteCoords(frame);
|
const [frameX1, frameY1, frameX2, frameY2] = getElementAbsoluteCoords(
|
||||||
|
frame,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const [elementX1, elementY1, elementX2, elementY2] =
|
const [elementX1, elementY1, elementX2, elementY2] =
|
||||||
getCommonBounds(elements);
|
getCommonBounds(elements);
|
||||||
|
@ -126,11 +138,12 @@ export const elementsAreInFrameBounds = (
|
||||||
export const elementOverlapsWithFrame = (
|
export const elementOverlapsWithFrame = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
elementsAreInFrameBounds([element], frame) ||
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
||||||
isElementIntersectingFrame(element, frame) ||
|
isElementIntersectingFrame(element, frame, elementsMap) ||
|
||||||
isElementContainingFrame([frame], element, frame)
|
isElementContainingFrame([frame], element, frame, elementsMap)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -140,8 +153,9 @@ export const isCursorInFrame = (
|
||||||
y: number;
|
y: number;
|
||||||
},
|
},
|
||||||
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame);
|
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame, elementsMap);
|
||||||
|
|
||||||
return isPointWithinBounds(
|
return isPointWithinBounds(
|
||||||
[fx1, fy1],
|
[fx1, fy1],
|
||||||
|
@ -155,6 +169,7 @@ export const groupsAreAtLeastIntersectingTheFrame = (
|
||||||
groupIds: readonly string[],
|
groupIds: readonly string[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => {
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
const elementsInGroup = groupIds.flatMap((groupId) =>
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
||||||
getElementsInGroup(elements, groupId),
|
getElementsInGroup(elements, groupId),
|
||||||
);
|
);
|
||||||
|
@ -165,8 +180,8 @@ export const groupsAreAtLeastIntersectingTheFrame = (
|
||||||
|
|
||||||
return !!elementsInGroup.find(
|
return !!elementsInGroup.find(
|
||||||
(element) =>
|
(element) =>
|
||||||
elementsAreInFrameBounds([element], frame) ||
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
||||||
isElementIntersectingFrame(element, frame),
|
isElementIntersectingFrame(element, frame, elementsMap),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -175,6 +190,7 @@ export const groupsAreCompletelyOutOfFrame = (
|
||||||
groupIds: readonly string[],
|
groupIds: readonly string[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => {
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
const elementsInGroup = groupIds.flatMap((groupId) =>
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
||||||
getElementsInGroup(elements, groupId),
|
getElementsInGroup(elements, groupId),
|
||||||
);
|
);
|
||||||
|
@ -186,8 +202,8 @@ export const groupsAreCompletelyOutOfFrame = (
|
||||||
return (
|
return (
|
||||||
elementsInGroup.find(
|
elementsInGroup.find(
|
||||||
(element) =>
|
(element) =>
|
||||||
elementsAreInFrameBounds([element], frame) ||
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
||||||
isElementIntersectingFrame(element, frame),
|
isElementIntersectingFrame(element, frame, elementsMap),
|
||||||
) === undefined
|
) === undefined
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -258,14 +274,15 @@ export const getElementsInResizingFrame = (
|
||||||
allElements: ExcalidrawElementsIncludingDeleted,
|
allElements: ExcalidrawElementsIncludingDeleted,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
const prevElementsInFrame = getFrameChildren(allElements, frame.id);
|
const prevElementsInFrame = getFrameChildren(allElements, frame.id);
|
||||||
const nextElementsInFrame = new Set<ExcalidrawElement>(prevElementsInFrame);
|
const nextElementsInFrame = new Set<ExcalidrawElement>(prevElementsInFrame);
|
||||||
|
|
||||||
const elementsCompletelyInFrame = new Set([
|
const elementsCompletelyInFrame = new Set([
|
||||||
...getElementsCompletelyInFrame(allElements, frame),
|
...getElementsCompletelyInFrame(allElements, frame, elementsMap),
|
||||||
...prevElementsInFrame.filter((element) =>
|
...prevElementsInFrame.filter((element) =>
|
||||||
isElementContainingFrame(allElements, element, frame),
|
isElementContainingFrame(allElements, element, frame, elementsMap),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -283,7 +300,7 @@ export const getElementsInResizingFrame = (
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const element of elementsNotCompletelyInFrame) {
|
for (const element of elementsNotCompletelyInFrame) {
|
||||||
if (!isElementIntersectingFrame(element, frame)) {
|
if (!isElementIntersectingFrame(element, frame, elementsMap)) {
|
||||||
if (element.groupIds.length === 0) {
|
if (element.groupIds.length === 0) {
|
||||||
nextElementsInFrame.delete(element);
|
nextElementsInFrame.delete(element);
|
||||||
}
|
}
|
||||||
|
@ -334,7 +351,7 @@ export const getElementsInResizingFrame = (
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
const elementsInGroup = getElementsInGroup(allElements, id);
|
const elementsInGroup = getElementsInGroup(allElements, id);
|
||||||
|
|
||||||
if (elementsAreInFrameBounds(elementsInGroup, frame)) {
|
if (elementsAreInFrameBounds(elementsInGroup, frame, elementsMap)) {
|
||||||
for (const element of elementsInGroup) {
|
for (const element of elementsInGroup) {
|
||||||
nextElementsInFrame.add(element);
|
nextElementsInFrame.add(element);
|
||||||
}
|
}
|
||||||
|
@ -348,12 +365,13 @@ export const getElementsInResizingFrame = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElementsInNewFrame = (
|
export const getElementsInNewFrame = (
|
||||||
allElements: ExcalidrawElementsIncludingDeleted,
|
elements: ExcalidrawElementsIncludingDeleted,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return omitGroupsContainingFrameLikes(
|
return omitGroupsContainingFrameLikes(
|
||||||
allElements,
|
elements,
|
||||||
getElementsCompletelyInFrame(allElements, frame),
|
getElementsCompletelyInFrame(elements, frame, elementsMap),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -388,7 +406,7 @@ export const filterElementsEligibleAsFrameChildren = (
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => {
|
) => {
|
||||||
const otherFrames = new Set<ExcalidrawFrameLikeElement["id"]>();
|
const otherFrames = new Set<ExcalidrawFrameLikeElement["id"]>();
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
elements = omitGroupsContainingFrameLikes(elements);
|
elements = omitGroupsContainingFrameLikes(elements);
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
|
@ -415,14 +433,18 @@ export const filterElementsEligibleAsFrameChildren = (
|
||||||
if (!processedGroups.has(shallowestGroupId)) {
|
if (!processedGroups.has(shallowestGroupId)) {
|
||||||
processedGroups.add(shallowestGroupId);
|
processedGroups.add(shallowestGroupId);
|
||||||
const groupElements = getElementsInGroup(elements, shallowestGroupId);
|
const groupElements = getElementsInGroup(elements, shallowestGroupId);
|
||||||
if (groupElements.some((el) => elementOverlapsWithFrame(el, frame))) {
|
if (
|
||||||
|
groupElements.some((el) =>
|
||||||
|
elementOverlapsWithFrame(el, frame, elementsMap),
|
||||||
|
)
|
||||||
|
) {
|
||||||
for (const child of groupElements) {
|
for (const child of groupElements) {
|
||||||
eligibleElements.push(child);
|
eligibleElements.push(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const overlaps = elementOverlapsWithFrame(element, frame);
|
const overlaps = elementOverlapsWithFrame(element, frame, elementsMap);
|
||||||
if (overlaps) {
|
if (overlaps) {
|
||||||
eligibleElements.push(element);
|
eligibleElements.push(element);
|
||||||
}
|
}
|
||||||
|
@ -682,12 +704,12 @@ export const getTargetFrame = (
|
||||||
// given an element, return if the element is in some frame
|
// given an element, return if the element is in some frame
|
||||||
export const isElementInFrame = (
|
export const isElementInFrame = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
allElements: ElementsMap,
|
allElementsMap: ElementsMap,
|
||||||
appState: StaticCanvasAppState,
|
appState: StaticCanvasAppState,
|
||||||
) => {
|
) => {
|
||||||
const frame = getTargetFrame(element, allElements, appState);
|
const frame = getTargetFrame(element, allElementsMap, appState);
|
||||||
const _element = isTextElement(element)
|
const _element = isTextElement(element)
|
||||||
? getContainerElement(element, allElements) || element
|
? getContainerElement(element, allElementsMap) || element
|
||||||
: element;
|
: element;
|
||||||
|
|
||||||
if (frame) {
|
if (frame) {
|
||||||
|
@ -703,16 +725,18 @@ export const isElementInFrame = (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_element.groupIds.length === 0) {
|
if (_element.groupIds.length === 0) {
|
||||||
return elementOverlapsWithFrame(_element, frame);
|
return elementOverlapsWithFrame(_element, frame, allElementsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allElementsInGroup = new Set(
|
const allElementsInGroup = new Set(
|
||||||
_element.groupIds.flatMap((gid) => getElementsInGroup(allElements, gid)),
|
_element.groupIds.flatMap((gid) =>
|
||||||
|
getElementsInGroup(allElementsMap, gid),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
|
if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
|
||||||
const selectedElements = new Set(
|
const selectedElements = new Set(
|
||||||
getSelectedElements(allElements, appState),
|
getSelectedElements(allElementsMap, appState),
|
||||||
);
|
);
|
||||||
|
|
||||||
const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
|
const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
|
||||||
|
@ -733,7 +757,7 @@ export const isElementInFrame = (
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const elementInGroup of allElementsInGroup) {
|
for (const elementInGroup of allElementsInGroup) {
|
||||||
if (elementOverlapsWithFrame(elementInGroup, frame)) {
|
if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
|
ElementsMap,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import {
|
import {
|
||||||
isTextElement,
|
isTextElement,
|
||||||
|
@ -137,6 +138,7 @@ export interface ExcalidrawElementWithCanvas {
|
||||||
|
|
||||||
const cappedElementCanvasSize = (
|
const cappedElementCanvasSize = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
): {
|
): {
|
||||||
width: number;
|
width: number;
|
||||||
|
@ -155,7 +157,7 @@ const cappedElementCanvasSize = (
|
||||||
|
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const elementWidth =
|
const elementWidth =
|
||||||
isLinearElement(element) || isFreeDrawElement(element)
|
isLinearElement(element) || isFreeDrawElement(element)
|
||||||
? distance(x1, x2)
|
? distance(x1, x2)
|
||||||
|
@ -200,7 +202,11 @@ const generateElementCanvas = (
|
||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
|
|
||||||
const { width, height, scale } = cappedElementCanvasSize(element, zoom);
|
const { width, height, scale } = cappedElementCanvasSize(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
zoom,
|
||||||
|
);
|
||||||
|
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
|
@ -209,7 +215,7 @@ const generateElementCanvas = (
|
||||||
let canvasOffsetY = 0;
|
let canvasOffsetY = 0;
|
||||||
|
|
||||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
|
||||||
canvasOffsetX =
|
canvasOffsetX =
|
||||||
element.x > x1
|
element.x > x1
|
||||||
|
@ -468,7 +474,7 @@ const drawElementFromCanvas = (
|
||||||
const element = elementWithCanvas.element;
|
const element = elementWithCanvas.element;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
const zoom = elementWithCanvas.scale;
|
const zoom = elementWithCanvas.scale;
|
||||||
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element, allElementsMap);
|
||||||
|
|
||||||
// Free draw elements will otherwise "shuffle" as the min x and y change
|
// Free draw elements will otherwise "shuffle" as the min x and y change
|
||||||
if (isFreeDrawElement(element)) {
|
if (isFreeDrawElement(element)) {
|
||||||
|
@ -513,8 +519,10 @@ const drawElementFromCanvas = (
|
||||||
elementWithCanvas.canvas.height,
|
elementWithCanvas.canvas.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [, , , , boundTextCx, boundTextCy] =
|
const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(
|
||||||
getElementAbsoluteCoords(boundTextElement);
|
boundTextElement,
|
||||||
|
allElementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
tempCanvasContext.rotate(-element.angle);
|
tempCanvasContext.rotate(-element.angle);
|
||||||
|
|
||||||
|
@ -694,7 +702,7 @@ export const renderElement = (
|
||||||
ShapeCache.generateElementShape(element, null);
|
ShapeCache.generateElementShape(element, null);
|
||||||
|
|
||||||
if (renderConfig.isExporting) {
|
if (renderConfig.isExporting) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2 + appState.scrollX;
|
const cx = (x1 + x2) / 2 + appState.scrollX;
|
||||||
const cy = (y1 + y2) / 2 + appState.scrollY;
|
const cy = (y1 + y2) / 2 + appState.scrollY;
|
||||||
const shiftX = (x2 - x1) / 2 - (element.x - x1);
|
const shiftX = (x2 - x1) / 2 - (element.x - x1);
|
||||||
|
@ -737,7 +745,7 @@ export const renderElement = (
|
||||||
// rely on existing shapes
|
// rely on existing shapes
|
||||||
ShapeCache.generateElementShape(element, renderConfig);
|
ShapeCache.generateElementShape(element, renderConfig);
|
||||||
if (renderConfig.isExporting) {
|
if (renderConfig.isExporting) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2 + appState.scrollX;
|
const cx = (x1 + x2) / 2 + appState.scrollX;
|
||||||
const cy = (y1 + y2) / 2 + appState.scrollY;
|
const cy = (y1 + y2) / 2 + appState.scrollY;
|
||||||
let shiftX = (x2 - x1) / 2 - (element.x - x1);
|
let shiftX = (x2 - x1) / 2 - (element.x - x1);
|
||||||
|
@ -749,6 +757,7 @@ export const renderElement = (
|
||||||
LinearElementEditor.getBoundTextElementPosition(
|
LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
element as ExcalidrawTextElementWithContainer,
|
element as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
shiftX = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
shiftX = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
||||||
shiftY = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
shiftY = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
||||||
|
@ -804,8 +813,10 @@ export const renderElement = (
|
||||||
tempCanvasContext.rotate(-element.angle);
|
tempCanvasContext.rotate(-element.angle);
|
||||||
|
|
||||||
// Shift the canvas to center of bound text
|
// Shift the canvas to center of bound text
|
||||||
const [, , , , boundTextCx, boundTextCy] =
|
const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(
|
||||||
getElementAbsoluteCoords(boundTextElement);
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const boundTextShiftX = (x1 + x2) / 2 - boundTextCx;
|
const boundTextShiftX = (x1 + x2) / 2 - boundTextCx;
|
||||||
const boundTextShiftY = (y1 + y2) / 2 - boundTextCy;
|
const boundTextShiftY = (y1 + y2) / 2 - boundTextCy;
|
||||||
tempCanvasContext.translate(-boundTextShiftX, -boundTextShiftY);
|
tempCanvasContext.translate(-boundTextShiftX, -boundTextShiftY);
|
||||||
|
@ -939,17 +950,18 @@ export const renderElementToSvg = (
|
||||||
renderConfig: SVGRenderConfig,
|
renderConfig: SVGRenderConfig,
|
||||||
) => {
|
) => {
|
||||||
const offset = { x: offsetX, y: offsetY };
|
const offset = { x: offsetX, y: offsetY };
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
let cx = (x2 - x1) / 2 - (element.x - x1);
|
let cx = (x2 - x1) / 2 - (element.x - x1);
|
||||||
let cy = (y2 - y1) / 2 - (element.y - y1);
|
let cy = (y2 - y1) / 2 - (element.y - y1);
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
const container = getContainerElement(element, elementsMap);
|
const container = getContainerElement(element, elementsMap);
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(container);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(container, elementsMap);
|
||||||
|
|
||||||
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
element as ExcalidrawTextElementWithContainer,
|
element as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
cx = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
cx = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
||||||
cy = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
cy = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
||||||
|
@ -1151,6 +1163,7 @@ export const renderElementToSvg = (
|
||||||
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
||||||
element,
|
element,
|
||||||
boundText,
|
boundText,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const maskX = offsetX + boundTextCoords.x - element.x;
|
const maskX = offsetX + boundTextCoords.x - element.x;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
GroupId,
|
GroupId,
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
|
ElementsMap,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import {
|
import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
|
@ -256,7 +257,10 @@ const renderLinearPointHandles = (
|
||||||
context.save();
|
context.save();
|
||||||
context.translate(appState.scrollX, appState.scrollY);
|
context.translate(appState.scrollX, appState.scrollY);
|
||||||
context.lineWidth = 1 / appState.zoom.value;
|
context.lineWidth = 1 / appState.zoom.value;
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const { POINT_HANDLE_SIZE } = LinearElementEditor;
|
const { POINT_HANDLE_SIZE } = LinearElementEditor;
|
||||||
const radius = appState.editingLinearElement
|
const radius = appState.editingLinearElement
|
||||||
|
@ -340,6 +344,7 @@ const highlightPoint = (
|
||||||
const renderLinearElementPointHighlight = (
|
const renderLinearElementPointHighlight = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const { elementId, hoverPointIndex } = appState.selectedLinearElement!;
|
const { elementId, hoverPointIndex } = appState.selectedLinearElement!;
|
||||||
if (
|
if (
|
||||||
|
@ -356,6 +361,7 @@ const renderLinearElementPointHighlight = (
|
||||||
const point = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const point = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
hoverPointIndex,
|
hoverPointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
context.save();
|
context.save();
|
||||||
context.translate(appState.scrollX, appState.scrollY);
|
context.translate(appState.scrollX, appState.scrollY);
|
||||||
|
@ -510,12 +516,22 @@ const _renderInteractiveScene = ({
|
||||||
appState.suggestedBindings
|
appState.suggestedBindings
|
||||||
.filter((binding) => binding != null)
|
.filter((binding) => binding != null)
|
||||||
.forEach((suggestedBinding) => {
|
.forEach((suggestedBinding) => {
|
||||||
renderBindingHighlight(context, appState, suggestedBinding!);
|
renderBindingHighlight(
|
||||||
|
context,
|
||||||
|
appState,
|
||||||
|
suggestedBinding!,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.frameToHighlight) {
|
if (appState.frameToHighlight) {
|
||||||
renderFrameHighlight(context, appState, appState.frameToHighlight);
|
renderFrameHighlight(
|
||||||
|
context,
|
||||||
|
appState,
|
||||||
|
appState.frameToHighlight,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.elementsToHighlight) {
|
if (appState.elementsToHighlight) {
|
||||||
|
@ -545,7 +561,7 @@ const _renderInteractiveScene = ({
|
||||||
appState.selectedLinearElement &&
|
appState.selectedLinearElement &&
|
||||||
appState.selectedLinearElement.hoverPointIndex >= 0
|
appState.selectedLinearElement.hoverPointIndex >= 0
|
||||||
) {
|
) {
|
||||||
renderLinearElementPointHighlight(context, appState);
|
renderLinearElementPointHighlight(context, appState, elementsMap);
|
||||||
}
|
}
|
||||||
// Paint selected elements
|
// Paint selected elements
|
||||||
if (!appState.multiElement && !appState.editingLinearElement) {
|
if (!appState.multiElement && !appState.editingLinearElement) {
|
||||||
|
@ -608,7 +624,7 @@ const _renderInteractiveScene = ({
|
||||||
|
|
||||||
if (selectionColors.length) {
|
if (selectionColors.length) {
|
||||||
const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
|
const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
|
||||||
getElementAbsoluteCoords(element, true);
|
getElementAbsoluteCoords(element, elementsMap, true);
|
||||||
selections.push({
|
selections.push({
|
||||||
angle: element.angle,
|
angle: element.angle,
|
||||||
elementX1,
|
elementX1,
|
||||||
|
@ -666,7 +682,8 @@ const _renderInteractiveScene = ({
|
||||||
const transformHandles = getTransformHandles(
|
const transformHandles = getTransformHandles(
|
||||||
selectedElements[0],
|
selectedElements[0],
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
"mouse", // when we render we don't know which pointer type so use mouse
|
elementsMap,
|
||||||
|
"mouse", // when we render we don't know which pointer type so use mouse,
|
||||||
);
|
);
|
||||||
if (!appState.viewModeEnabled && showBoundingBox) {
|
if (!appState.viewModeEnabled && showBoundingBox) {
|
||||||
renderTransformHandles(
|
renderTransformHandles(
|
||||||
|
@ -953,7 +970,11 @@ const _renderStaticScene = ({
|
||||||
element.groupIds.length > 0 &&
|
element.groupIds.length > 0 &&
|
||||||
appState.frameToHighlight &&
|
appState.frameToHighlight &&
|
||||||
appState.selectedElementIds[element.id] &&
|
appState.selectedElementIds[element.id] &&
|
||||||
(elementOverlapsWithFrame(element, appState.frameToHighlight) ||
|
(elementOverlapsWithFrame(
|
||||||
|
element,
|
||||||
|
appState.frameToHighlight,
|
||||||
|
elementsMap,
|
||||||
|
) ||
|
||||||
element.groupIds.find((groupId) => groupsToBeAddedToFrame.has(groupId)))
|
element.groupIds.find((groupId) => groupsToBeAddedToFrame.has(groupId)))
|
||||||
) {
|
) {
|
||||||
element.groupIds.forEach((groupId) =>
|
element.groupIds.forEach((groupId) =>
|
||||||
|
@ -1004,7 +1025,7 @@ const _renderStaticScene = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!isExporting) {
|
if (!isExporting) {
|
||||||
renderLinkIcon(element, context, appState);
|
renderLinkIcon(element, context, appState, elementsMap);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -1048,7 +1069,7 @@ const _renderStaticScene = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!isExporting) {
|
if (!isExporting) {
|
||||||
renderLinkIcon(element, context, appState);
|
renderLinkIcon(element, context, appState, elementsMap);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// - when exporting the whole canvas, we DO NOT apply clipping
|
// - when exporting the whole canvas, we DO NOT apply clipping
|
||||||
|
@ -1247,6 +1268,7 @@ const renderBindingHighlight = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
suggestedBinding: SuggestedBinding,
|
suggestedBinding: SuggestedBinding,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const renderHighlight = Array.isArray(suggestedBinding)
|
const renderHighlight = Array.isArray(suggestedBinding)
|
||||||
? renderBindingHighlightForSuggestedPointBinding
|
? renderBindingHighlightForSuggestedPointBinding
|
||||||
|
@ -1254,7 +1276,7 @@ const renderBindingHighlight = (
|
||||||
|
|
||||||
context.save();
|
context.save();
|
||||||
context.translate(appState.scrollX, appState.scrollY);
|
context.translate(appState.scrollX, appState.scrollY);
|
||||||
renderHighlight(context, suggestedBinding as any);
|
renderHighlight(context, suggestedBinding as any, elementsMap);
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
|
@ -1262,8 +1284,9 @@ const renderBindingHighlight = (
|
||||||
const renderBindingHighlightForBindableElement = (
|
const renderBindingHighlightForBindableElement = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const width = x2 - x1;
|
const width = x2 - x1;
|
||||||
const height = y2 - y1;
|
const height = y2 - y1;
|
||||||
const threshold = maxBindingGap(element, width, height);
|
const threshold = maxBindingGap(element, width, height);
|
||||||
|
@ -1323,8 +1346,9 @@ const renderFrameHighlight = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
|
||||||
const width = x2 - x1;
|
const width = x2 - x1;
|
||||||
const height = y2 - y1;
|
const height = y2 - y1;
|
||||||
|
|
||||||
|
@ -1398,6 +1422,7 @@ const renderElementsBoxHighlight = (
|
||||||
const renderBindingHighlightForSuggestedPointBinding = (
|
const renderBindingHighlightForSuggestedPointBinding = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
suggestedBinding: SuggestedPointBinding,
|
suggestedBinding: SuggestedPointBinding,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [element, startOrEnd, bindableElement] = suggestedBinding;
|
const [element, startOrEnd, bindableElement] = suggestedBinding;
|
||||||
|
|
||||||
|
@ -1416,6 +1441,7 @@ const renderBindingHighlightForSuggestedPointBinding = (
|
||||||
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
index,
|
index,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
fillCircle(context, x, y, threshold);
|
fillCircle(context, x, y, threshold);
|
||||||
});
|
});
|
||||||
|
@ -1426,9 +1452,10 @@ const renderLinkIcon = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: StaticCanvasAppState,
|
appState: StaticCanvasAppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (element.link && !appState.selectedElementIds[element.id]) {
|
if (element.link && !appState.selectedElementIds[element.id]) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const [x, y, width, height] = getLinkHandleFromCoords(
|
const [x, y, width, height] = getLinkHandleFromCoords(
|
||||||
[x1, y1, x2, y2],
|
[x1, y1, x2, y2],
|
||||||
element.angle,
|
element.angle,
|
||||||
|
|
|
@ -60,10 +60,8 @@ export class Fonts {
|
||||||
return newElementWith(element, {
|
return newElementWith(element, {
|
||||||
...refreshTextDimensions(
|
...refreshTextDimensions(
|
||||||
element,
|
element,
|
||||||
getContainerElement(
|
getContainerElement(element, this.scene.getNonDeletedElementsMap()),
|
||||||
element,
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.scene.getElementsMapIncludingDeleted(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -392,8 +392,9 @@ export const exportToSvg = async (
|
||||||
const frameElements = getFrameLikeElements(elements);
|
const frameElements = getFrameLikeElements(elements);
|
||||||
|
|
||||||
let exportingFrameClipPath = "";
|
let exportingFrameClipPath = "";
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
for (const frame of frameElements) {
|
for (const frame of frameElements) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
|
||||||
const cx = (x2 - x1) / 2 - (frame.x - x1);
|
const cx = (x2 - x1) / 2 - (frame.x - x1);
|
||||||
const cy = (y2 - y1) / 2 - (frame.y - y1);
|
const cy = (y2 - y1) / 2 - (frame.y - y1);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ElementsMapOrArray,
|
ElementsMapOrArray,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
|
@ -44,10 +45,11 @@ export const excludeElementsInFramesFromSelection = <
|
||||||
export const getElementsWithinSelection = (
|
export const getElementsWithinSelection = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selection: NonDeletedExcalidrawElement,
|
selection: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
excludeElementsInFrames: boolean = true,
|
excludeElementsInFrames: boolean = true,
|
||||||
) => {
|
) => {
|
||||||
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
||||||
getElementAbsoluteCoords(selection);
|
getElementAbsoluteCoords(selection, elementsMap);
|
||||||
|
|
||||||
let elementsInSelection = elements.filter((element) => {
|
let elementsInSelection = elements.filter((element) => {
|
||||||
let [elementX1, elementY1, elementX2, elementY2] =
|
let [elementX1, elementY1, elementX2, elementY2] =
|
||||||
|
@ -82,7 +84,7 @@ export const getElementsWithinSelection = (
|
||||||
const containingFrame = getContainingFrame(element);
|
const containingFrame = getContainingFrame(element);
|
||||||
|
|
||||||
if (containingFrame) {
|
if (containingFrame) {
|
||||||
return elementOverlapsWithFrame(element, containingFrame);
|
return elementOverlapsWithFrame(element, containingFrame, elementsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -8,15 +8,18 @@ import {
|
||||||
import { MaybeTransformHandleType } from "./element/transformHandles";
|
import { MaybeTransformHandleType } from "./element/transformHandles";
|
||||||
import { isBoundToContainer, isFrameLikeElement } from "./element/typeChecks";
|
import { isBoundToContainer, isFrameLikeElement } from "./element/typeChecks";
|
||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./element/types";
|
} from "./element/types";
|
||||||
import { getMaximumGroups } from "./groups";
|
import { getMaximumGroups } from "./groups";
|
||||||
import { KEYS } from "./keys";
|
import { KEYS } from "./keys";
|
||||||
import { rangeIntersection, rangesOverlap, rotatePoint } from "./math";
|
import { rangeIntersection, rangesOverlap, rotatePoint } from "./math";
|
||||||
import { getVisibleAndNonSelectedElements } from "./scene/selection";
|
import {
|
||||||
|
getSelectedElements,
|
||||||
|
getVisibleAndNonSelectedElements,
|
||||||
|
} from "./scene/selection";
|
||||||
import { AppState, KeyboardModifiersObject, Point } from "./types";
|
import { AppState, KeyboardModifiersObject, Point } from "./types";
|
||||||
import { arrayToMap } from "./utils";
|
|
||||||
|
|
||||||
const SNAP_DISTANCE = 8;
|
const SNAP_DISTANCE = 8;
|
||||||
|
|
||||||
|
@ -167,6 +170,7 @@ export const areRoughlyEqual = (a: number, b: number, precision = 0.01) => {
|
||||||
|
|
||||||
export const getElementsCorners = (
|
export const getElementsCorners = (
|
||||||
elements: ExcalidrawElement[],
|
elements: ExcalidrawElement[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
{
|
{
|
||||||
omitCenter,
|
omitCenter,
|
||||||
boundingBoxCorners,
|
boundingBoxCorners,
|
||||||
|
@ -185,7 +189,10 @@ export const getElementsCorners = (
|
||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
const element = elements[0];
|
const element = elements[0];
|
||||||
|
|
||||||
let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
|
let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
if (dragOffset) {
|
if (dragOffset) {
|
||||||
x1 += dragOffset.x;
|
x1 += dragOffset.x;
|
||||||
|
@ -280,6 +287,7 @@ export const getVisibleGaps = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selectedElements: ExcalidrawElement[],
|
selectedElements: ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const referenceElements: ExcalidrawElement[] = getReferenceElements(
|
const referenceElements: ExcalidrawElement[] = getReferenceElements(
|
||||||
elements,
|
elements,
|
||||||
|
@ -287,10 +295,7 @@ export const getVisibleGaps = (
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const referenceBounds = getMaximumGroups(
|
const referenceBounds = getMaximumGroups(referenceElements, elementsMap)
|
||||||
referenceElements,
|
|
||||||
arrayToMap(elements),
|
|
||||||
)
|
|
||||||
.filter(
|
.filter(
|
||||||
(elementsGroup) =>
|
(elementsGroup) =>
|
||||||
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
||||||
|
@ -569,19 +574,19 @@ export const getReferenceSnapPoints = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selectedElements: ExcalidrawElement[],
|
selectedElements: ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const referenceElements = getReferenceElements(
|
const referenceElements = getReferenceElements(
|
||||||
elements,
|
elements,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
return getMaximumGroups(referenceElements, elementsMap)
|
||||||
return getMaximumGroups(referenceElements, arrayToMap(elements))
|
|
||||||
.filter(
|
.filter(
|
||||||
(elementsGroup) =>
|
(elementsGroup) =>
|
||||||
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
||||||
)
|
)
|
||||||
.flatMap((elementGroup) => getElementsCorners(elementGroup));
|
.flatMap((elementGroup) => getElementsCorners(elementGroup, elementsMap));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPointSnaps = (
|
const getPointSnaps = (
|
||||||
|
@ -641,11 +646,13 @@ const getPointSnaps = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const snapDraggedElements = (
|
export const snapDraggedElements = (
|
||||||
selectedElements: ExcalidrawElement[],
|
elements: ExcalidrawElement[],
|
||||||
dragOffset: Vector2D,
|
dragOffset: Vector2D,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
event: KeyboardModifiersObject,
|
event: KeyboardModifiersObject,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
if (
|
if (
|
||||||
!isSnappingEnabled({ appState, event, selectedElements }) ||
|
!isSnappingEnabled({ appState, event, selectedElements }) ||
|
||||||
selectedElements.length === 0
|
selectedElements.length === 0
|
||||||
|
@ -658,7 +665,6 @@ export const snapDraggedElements = (
|
||||||
snapLines: [],
|
snapLines: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
dragOffset.x = round(dragOffset.x);
|
dragOffset.x = round(dragOffset.x);
|
||||||
dragOffset.y = round(dragOffset.y);
|
dragOffset.y = round(dragOffset.y);
|
||||||
const nearestSnapsX: Snaps = [];
|
const nearestSnapsX: Snaps = [];
|
||||||
|
@ -669,7 +675,7 @@ export const snapDraggedElements = (
|
||||||
y: snapDistance,
|
y: snapDistance,
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectionPoints = getElementsCorners(selectedElements, {
|
const selectionPoints = getElementsCorners(selectedElements, elementsMap, {
|
||||||
dragOffset,
|
dragOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -719,7 +725,7 @@ export const snapDraggedElements = (
|
||||||
|
|
||||||
getPointSnaps(
|
getPointSnaps(
|
||||||
selectedElements,
|
selectedElements,
|
||||||
getElementsCorners(selectedElements, {
|
getElementsCorners(selectedElements, elementsMap, {
|
||||||
dragOffset: newDragOffset,
|
dragOffset: newDragOffset,
|
||||||
}),
|
}),
|
||||||
appState,
|
appState,
|
||||||
|
@ -1204,6 +1210,7 @@ export const snapNewElement = (
|
||||||
event: KeyboardModifiersObject,
|
event: KeyboardModifiersObject,
|
||||||
origin: Vector2D,
|
origin: Vector2D,
|
||||||
dragOffset: Vector2D,
|
dragOffset: Vector2D,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
!isSnappingEnabled({ event, selectedElements: [draggingElement], appState })
|
!isSnappingEnabled({ event, selectedElements: [draggingElement], appState })
|
||||||
|
@ -1248,7 +1255,7 @@ export const snapNewElement = (
|
||||||
nearestSnapsX.length = 0;
|
nearestSnapsX.length = 0;
|
||||||
nearestSnapsY.length = 0;
|
nearestSnapsY.length = 0;
|
||||||
|
|
||||||
const corners = getElementsCorners([draggingElement], {
|
const corners = getElementsCorners([draggingElement], elementsMap, {
|
||||||
boundingBoxCorners: true,
|
boundingBoxCorners: true,
|
||||||
omitCenter: true,
|
omitCenter: true,
|
||||||
});
|
});
|
||||||
|
@ -1276,6 +1283,7 @@ export const getSnapLinesAtPointer = (
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
pointer: Vector2D,
|
pointer: Vector2D,
|
||||||
event: KeyboardModifiersObject,
|
event: KeyboardModifiersObject,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (!isSnappingEnabled({ event, selectedElements: [], appState })) {
|
if (!isSnappingEnabled({ event, selectedElements: [], appState })) {
|
||||||
return {
|
return {
|
||||||
|
@ -1301,7 +1309,7 @@ export const getSnapLinesAtPointer = (
|
||||||
const verticalSnapLines: PointerSnapLine[] = [];
|
const verticalSnapLines: PointerSnapLine[] = [];
|
||||||
|
|
||||||
for (const referenceElement of referenceElements) {
|
for (const referenceElement of referenceElements) {
|
||||||
const corners = getElementsCorners([referenceElement]);
|
const corners = getElementsCorners([referenceElement], elementsMap);
|
||||||
|
|
||||||
for (const corner of corners) {
|
for (const corner of corners) {
|
||||||
const offsetX = corner[0] - pointer.x;
|
const offsetX = corner[0] - pointer.x;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { getTransformHandles } from "../element/transformHandles";
|
||||||
import { API } from "./helpers/api";
|
import { API } from "./helpers/api";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
|
@ -91,8 +92,12 @@ describe("element binding", () => {
|
||||||
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||||
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
||||||
|
|
||||||
const rotation = getTransformHandles(arrow, h.state.zoom, "mouse")
|
const rotation = getTransformHandles(
|
||||||
.rotation!;
|
arrow,
|
||||||
|
h.state.zoom,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
"mouse",
|
||||||
|
).rotation!;
|
||||||
const rotationHandleX = rotation[0] + rotation[2] / 2;
|
const rotationHandleX = rotation[0] + rotation[2] / 2;
|
||||||
const rotationHandleY = rotation[1] + rotation[3] / 2;
|
const rotationHandleY = rotation[1] + rotation[3] / 2;
|
||||||
mouse.down(rotationHandleX, rotationHandleY);
|
mouse.down(rotationHandleX, rotationHandleY);
|
||||||
|
|
|
@ -27,7 +27,7 @@ import * as blob from "../data/blob";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { getBoundTextElementPosition } from "../element/textElement";
|
import { getBoundTextElementPosition } from "../element/textElement";
|
||||||
import { createPasteEvent } from "../clipboard";
|
import { createPasteEvent } from "../clipboard";
|
||||||
import { cloneJSON } from "../utils";
|
import { arrayToMap, cloneJSON } from "../utils";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
|
@ -194,9 +194,10 @@ const checkElementsBoundingBox = async (
|
||||||
element2: ExcalidrawElement,
|
element2: ExcalidrawElement,
|
||||||
toleranceInPx: number = 0,
|
toleranceInPx: number = 0,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element1);
|
const elementsMap = arrayToMap([element1, element2]);
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element1, elementsMap);
|
||||||
|
|
||||||
const [x12, y12, x22, y22] = getElementAbsoluteCoords(element2);
|
const [x12, y12, x22, y22] = getElementAbsoluteCoords(element2, elementsMap);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// Check if width and height did not change
|
// Check if width and height did not change
|
||||||
|
@ -853,7 +854,11 @@ describe("mutliple elements", () => {
|
||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
const arrowText = h.elements[1] as ExcalidrawTextElementWithContainer;
|
const arrowText = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||||
const arrowTextPos = getBoundTextElementPosition(arrow.get(), arrowText)!;
|
const arrowTextPos = getBoundTextElementPosition(
|
||||||
|
arrow.get(),
|
||||||
|
arrowText,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
)!;
|
||||||
const rectText = h.elements[3] as ExcalidrawTextElementWithContainer;
|
const rectText = h.elements[3] as ExcalidrawTextElementWithContainer;
|
||||||
|
|
||||||
expect(arrow.x).toBeCloseTo(180);
|
expect(arrow.x).toBeCloseTo(180);
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
import { getCommonBounds, getElementPointsCoords } from "../../element/bounds";
|
import { getCommonBounds, getElementPointsCoords } from "../../element/bounds";
|
||||||
import { rotatePoint } from "../../math";
|
import { rotatePoint } from "../../math";
|
||||||
import { getTextEditor } from "../queries/dom";
|
import { getTextEditor } from "../queries/dom";
|
||||||
|
import { arrayToMap } from "../../utils";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
|
@ -286,9 +287,12 @@ const transform = (
|
||||||
let handleCoords: TransformHandle | undefined;
|
let handleCoords: TransformHandle | undefined;
|
||||||
|
|
||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
handleCoords = getTransformHandles(elements[0], h.state.zoom, "mouse")[
|
handleCoords = getTransformHandles(
|
||||||
handle
|
elements[0],
|
||||||
];
|
h.state.zoom,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
"mouse",
|
||||||
|
)[handle];
|
||||||
} else {
|
} else {
|
||||||
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||||
const isFrameSelected = elements.some(isFrameLikeElement);
|
const isFrameSelected = elements.some(isFrameLikeElement);
|
||||||
|
|
|
@ -343,6 +343,8 @@ describe("Test Linear Elements", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update all the midpoints when element position changed", async () => {
|
it("should update all the midpoints when element position changed", async () => {
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
|
||||||
createThreePointerLinearElement("line", {
|
createThreePointerLinearElement("line", {
|
||||||
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
});
|
});
|
||||||
|
@ -351,7 +353,10 @@ describe("Test Linear Elements", () => {
|
||||||
expect(line.points.length).toEqual(3);
|
expect(line.points.length).toEqual(3);
|
||||||
enterLineEditingMode(line);
|
enterLineEditingMode(line);
|
||||||
|
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([line.x, line.y]).toEqual(points[0]);
|
expect([line.x, line.y]).toEqual(points[0]);
|
||||||
|
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
|
@ -465,7 +470,11 @@ describe("Test Linear Elements", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update only the first segment midpoint when its point is dragged", async () => {
|
it("should update only the first segment midpoint when its point is dragged", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
|
@ -482,7 +491,10 @@ describe("Test Linear Elements", () => {
|
||||||
);
|
);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] - delta,
|
points[0][0] - delta,
|
||||||
points[0][1] - delta,
|
points[0][1] - delta,
|
||||||
|
@ -499,7 +511,11 @@ describe("Test Linear Elements", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hide midpoints in the segment when points moved close", async () => {
|
it("should hide midpoints in the segment when points moved close", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
|
@ -516,7 +532,10 @@ describe("Test Linear Elements", () => {
|
||||||
);
|
);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] + delta,
|
points[0][0] + delta,
|
||||||
points[0][1] + delta,
|
points[0][1] + delta,
|
||||||
|
@ -535,7 +554,10 @@ describe("Test Linear Elements", () => {
|
||||||
it("should remove the midpoint when one of the points in the segment is deleted", async () => {
|
it("should remove the midpoint when one of the points in the segment is deleted", async () => {
|
||||||
const line = h.elements[0] as ExcalidrawLinearElement;
|
const line = h.elements[0] as ExcalidrawLinearElement;
|
||||||
enterLineEditingMode(line);
|
enterLineEditingMode(line);
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
);
|
||||||
|
|
||||||
// dragging line from last segment midpoint
|
// dragging line from last segment midpoint
|
||||||
drag(lastSegmentMidpoint, [
|
drag(lastSegmentMidpoint, [
|
||||||
|
@ -637,7 +659,11 @@ describe("Test Linear Elements", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update all the midpoints when its point is dragged", async () => {
|
it("should update all the midpoints when its point is dragged", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
|
@ -649,7 +675,10 @@ describe("Test Linear Elements", () => {
|
||||||
// Drag from first point
|
// Drag from first point
|
||||||
drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]);
|
drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] - delta,
|
points[0][0] - delta,
|
||||||
points[0][1] - delta,
|
points[0][1] - delta,
|
||||||
|
@ -678,7 +707,11 @@ describe("Test Linear Elements", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hide midpoints in the segment when points moved close", async () => {
|
it("should hide midpoints in the segment when points moved close", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
|
@ -695,7 +728,10 @@ describe("Test Linear Elements", () => {
|
||||||
);
|
);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] + delta,
|
points[0][0] + delta,
|
||||||
points[0][1] + delta,
|
points[0][1] + delta,
|
||||||
|
@ -712,6 +748,8 @@ describe("Test Linear Elements", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update all the midpoints when a point is deleted", async () => {
|
it("should update all the midpoints when a point is deleted", async () => {
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
|
||||||
drag(lastSegmentMidpoint, [
|
drag(lastSegmentMidpoint, [
|
||||||
lastSegmentMidpoint[0] + delta,
|
lastSegmentMidpoint[0] + delta,
|
||||||
lastSegmentMidpoint[1] + delta,
|
lastSegmentMidpoint[1] + delta,
|
||||||
|
@ -723,7 +761,10 @@ describe("Test Linear Elements", () => {
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
h.state,
|
h.state,
|
||||||
);
|
);
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
// delete 3rd point
|
// delete 3rd point
|
||||||
deletePoint(points[2]);
|
deletePoint(points[2]);
|
||||||
|
@ -837,6 +878,7 @@ describe("Test Linear Elements", () => {
|
||||||
const position = LinearElementEditor.getBoundTextElementPosition(
|
const position = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
textElement,
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
@ -859,6 +901,7 @@ describe("Test Linear Elements", () => {
|
||||||
const position = LinearElementEditor.getBoundTextElementPosition(
|
const position = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
textElement,
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
@ -893,6 +936,7 @@ describe("Test Linear Elements", () => {
|
||||||
const position = LinearElementEditor.getBoundTextElementPosition(
|
const position = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
textElement,
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
@ -1012,8 +1056,13 @@ describe("Test Linear Elements", () => {
|
||||||
);
|
);
|
||||||
expect(container.width).toBe(70);
|
expect(container.width).toBe(70);
|
||||||
expect(container.height).toBe(50);
|
expect(container.height).toBe(50);
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
expect(
|
||||||
.toMatchInlineSnapshot(`
|
getBoundTextElementPosition(
|
||||||
|
container,
|
||||||
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 75,
|
"x": 75,
|
||||||
"y": 60,
|
"y": 60,
|
||||||
|
@ -1051,8 +1100,13 @@ describe("Test Linear Elements", () => {
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
expect(
|
||||||
.toMatchInlineSnapshot(`
|
getBoundTextElementPosition(
|
||||||
|
container,
|
||||||
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 271.11716195150507,
|
"x": 271.11716195150507,
|
||||||
"y": 45,
|
"y": 45,
|
||||||
|
@ -1090,7 +1144,8 @@ describe("Test Linear Elements", () => {
|
||||||
arrow,
|
arrow,
|
||||||
);
|
);
|
||||||
expect(container.width).toBe(40);
|
expect(container.width).toBe(40);
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
expect(getBoundTextElementPosition(container, textElement, elementsMap))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 25,
|
"x": 25,
|
||||||
|
@ -1102,7 +1157,10 @@ describe("Test Linear Elements", () => {
|
||||||
collaboration made
|
collaboration made
|
||||||
easy"
|
easy"
|
||||||
`);
|
`);
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(container);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
container,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
// Drag from last point
|
// Drag from last point
|
||||||
drag(points[1], [points[1][0] + 300, points[1][1]]);
|
drag(points[1], [points[1][0] + 300, points[1][1]]);
|
||||||
|
@ -1115,7 +1173,7 @@ describe("Test Linear Elements", () => {
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
expect(getBoundTextElementPosition(container, textElement, elementsMap))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 75,
|
"x": 75,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
|
|
||||||
// Unmount ReactDOM from root
|
// Unmount ReactDOM from root
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
|
@ -75,12 +76,13 @@ describe("move element", () => {
|
||||||
const rectA = UI.createElement("rectangle", { size: 100 });
|
const rectA = UI.createElement("rectangle", { size: 100 });
|
||||||
const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
|
const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
|
||||||
const line = UI.createElement("line", { x: 110, y: 50, size: 80 });
|
const line = UI.createElement("line", { x: 110, y: 50, size: 80 });
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
// bind line to two rectangles
|
// bind line to two rectangles
|
||||||
bindOrUnbindLinearElement(
|
bindOrUnbindLinearElement(
|
||||||
line.get() as NonDeleted<ExcalidrawLinearElement>,
|
line.get() as NonDeleted<ExcalidrawLinearElement>,
|
||||||
rectA.get() as ExcalidrawRectangleElement,
|
rectA.get() as ExcalidrawRectangleElement,
|
||||||
rectB.get() as ExcalidrawRectangleElement,
|
rectB.get() as ExcalidrawRectangleElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
// select the second rectangles
|
// select the second rectangles
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { API } from "./helpers/api";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { isLinearElement } from "../element/typeChecks";
|
import { isLinearElement } from "../element/typeChecks";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
|
|
||||||
|
@ -301,10 +302,12 @@ describe("arrow element", () => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const label = await UI.editText(arrow, "Hello");
|
const label = await UI.editText(arrow, "Hello");
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
UI.resize(arrow, "se", [50, 30]);
|
UI.resize(arrow, "se", [50, 30]);
|
||||||
let labelPos = LinearElementEditor.getBoundTextElementPosition(
|
let labelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
arrow,
|
arrow,
|
||||||
label,
|
label,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
||||||
|
@ -317,7 +320,11 @@ describe("arrow element", () => {
|
||||||
expect(label.fontSize).toEqual(20);
|
expect(label.fontSize).toEqual(20);
|
||||||
|
|
||||||
UI.resize(arrow, "w", [20, 0]);
|
UI.resize(arrow, "w", [20, 0]);
|
||||||
labelPos = LinearElementEditor.getBoundTextElementPosition(arrow, label);
|
labelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
|
arrow,
|
||||||
|
label,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
||||||
arrow.x + arrow.points[2][0],
|
arrow.x + arrow.points[2][0],
|
||||||
|
@ -743,15 +750,17 @@ describe("multiple selection", () => {
|
||||||
const selectionTop = 20 - topArrowLabel.height / 2;
|
const selectionTop = 20 - topArrowLabel.height / 2;
|
||||||
const move = [80, 0] as [number, number];
|
const move = [80, 0] as [number, number];
|
||||||
const scale = move[0] / selectionWidth + 1;
|
const scale = move[0] / selectionWidth + 1;
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
UI.resize([topArrow.get(), bottomArrow.get()], "se", move);
|
UI.resize([topArrow.get(), bottomArrow.get()], "se", move);
|
||||||
const topArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
const topArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
topArrow,
|
topArrow,
|
||||||
topArrowLabel,
|
topArrowLabel,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const bottomArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
const bottomArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
bottomArrow,
|
bottomArrow,
|
||||||
bottomArrowLabel,
|
bottomArrowLabel,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(topArrow.x).toBeCloseTo(0);
|
expect(topArrow.x).toBeCloseTo(0);
|
||||||
|
@ -944,12 +953,13 @@ describe("multiple selection", () => {
|
||||||
const scaleX = move[0] / selectionWidth + 1;
|
const scaleX = move[0] / selectionWidth + 1;
|
||||||
const scaleY = -scaleX;
|
const scaleY = -scaleX;
|
||||||
const lineOrigBounds = getBoundsFromPoints(line);
|
const lineOrigBounds = getBoundsFromPoints(line);
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
UI.resize([line, image, rectangle, boundArrow], "se", move);
|
UI.resize([line, image, rectangle, boundArrow], "se", move);
|
||||||
const lineNewBounds = getBoundsFromPoints(line);
|
const lineNewBounds = getBoundsFromPoints(line);
|
||||||
const arrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
const arrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
boundArrow,
|
boundArrow,
|
||||||
arrowLabel,
|
arrowLabel,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(line.x).toBeCloseTo(60 * scaleX);
|
expect(line.x).toBeCloseTo(60 * scaleX);
|
||||||
|
|
Loading…
Add table
Reference in a new issue