mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-02-18 13:29:36 +01:00
fix: make getBoundTextElement and related helpers pure (#7601)
* fix: make getBoundTextElement pure * updating args * fix * pass boundTextElement to getBoundTextMaxWidth * fix labelled arrows * lint * pass elementsMap to removeElementsFromFrame * pass elementsMap to getMaximumGroups, alignElements and distributeElements * lint * pass allElementsMap to renderElement * lint * feat: make more typesafe * fix: remove unnecessary assertion * fix: remove unused params --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
2789d08154
commit
10bd08ef19
@ -40,8 +40,13 @@ const alignSelectedElements = (
|
||||
alignment: Alignment,
|
||||
) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
const elementsMap = arrayToMap(elements);
|
||||
|
||||
const updatedElements = alignElements(selectedElements, alignment);
|
||||
const updatedElements = alignElements(
|
||||
selectedElements,
|
||||
elementsMap,
|
||||
alignment,
|
||||
);
|
||||
|
||||
const updatedElementsMap = arrayToMap(updatedElements);
|
||||
|
||||
|
@ -45,8 +45,9 @@ export const actionUnbindText = register({
|
||||
},
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||
selectedElements.forEach((element) => {
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
const { width, height, baseline } = measureText(
|
||||
boundTextElement.originalText,
|
||||
@ -106,7 +107,10 @@ export const actionBindText = register({
|
||||
if (
|
||||
textElement &&
|
||||
bindingContainer &&
|
||||
getBoundTextElement(bindingContainer) === null
|
||||
getBoundTextElement(
|
||||
bindingContainer,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
) === null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -32,7 +32,11 @@ const distributeSelectedElements = (
|
||||
) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
const updatedElements = distributeElements(selectedElements, distribution);
|
||||
const updatedElements = distributeElements(
|
||||
selectedElements,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
distribution,
|
||||
);
|
||||
|
||||
const updatedElementsMap = arrayToMap(updatedElements);
|
||||
|
||||
|
@ -139,7 +139,7 @@ const duplicateElements = (
|
||||
continue;
|
||||
}
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, arrayToMap(elements));
|
||||
const isElementAFrameLike = isFrameLikeElement(element);
|
||||
|
||||
if (idsOfElementsToDuplicate.get(element.id)) {
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
ExcalidrawElement,
|
||||
NonDeleted,
|
||||
NonDeletedElementsMap,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../element/types";
|
||||
import { resizeMultipleElements } from "../element/resizeElements";
|
||||
import { AppState } from "../types";
|
||||
@ -67,7 +68,7 @@ export const actionFlipVertical = register({
|
||||
|
||||
const flipSelectedElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elementsMap: NonDeletedElementsMap,
|
||||
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap,
|
||||
appState: Readonly<AppState>,
|
||||
flipDirection: "horizontal" | "vertical",
|
||||
) => {
|
||||
@ -96,7 +97,7 @@ const flipSelectedElements = (
|
||||
|
||||
const flipElements = (
|
||||
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: NonDeletedElementsMap,
|
||||
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap,
|
||||
appState: AppState,
|
||||
flipDirection: "horizontal" | "vertical",
|
||||
): ExcalidrawElement[] => {
|
||||
|
@ -105,7 +105,10 @@ export const actionGroup = register({
|
||||
const frameElementsMap = groupByFrameLikes(selectedElements);
|
||||
|
||||
frameElementsMap.forEach((elementsInFrame, frameId) => {
|
||||
removeElementsFromFrame(elementsInFrame);
|
||||
removeElementsFromFrame(
|
||||
elementsInFrame,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -225,6 +228,7 @@ export const actionUngroup = register({
|
||||
nextElements,
|
||||
getElementsInResizingFrame(nextElements, frame, appState),
|
||||
frame,
|
||||
app,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -606,7 +606,7 @@ export const actionChangeFontSize = register({
|
||||
perform: (elements, appState, value, app) => {
|
||||
return changeFontSize(elements, appState, app, () => value, value);
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fontSize")}</legend>
|
||||
<ButtonIconSelect
|
||||
@ -644,14 +644,21 @@ export const actionChangeFontSize = register({
|
||||
if (isTextElement(element)) {
|
||||
return element.fontSize;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (boundTextElement) {
|
||||
return boundTextElement.fontSize;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(element) =>
|
||||
isTextElement(element) || getBoundTextElement(element) !== null,
|
||||
isTextElement(element) ||
|
||||
getBoundTextElement(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
) !== null,
|
||||
(hasSelection) =>
|
||||
hasSelection
|
||||
? null
|
||||
@ -738,7 +745,7 @@ export const actionChangeFontFamily = register({
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||
const options: {
|
||||
value: FontFamilyValues;
|
||||
text: string;
|
||||
@ -778,14 +785,21 @@ export const actionChangeFontFamily = register({
|
||||
if (isTextElement(element)) {
|
||||
return element.fontFamily;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (boundTextElement) {
|
||||
return boundTextElement.fontFamily;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(element) =>
|
||||
isTextElement(element) || getBoundTextElement(element) !== null,
|
||||
isTextElement(element) ||
|
||||
getBoundTextElement(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
) !== null,
|
||||
(hasSelection) =>
|
||||
hasSelection
|
||||
? null
|
||||
@ -830,7 +844,8 @@ export const actionChangeTextAlign = register({
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>{t("labels.textAlign")}</legend>
|
||||
@ -863,14 +878,18 @@ export const actionChangeTextAlign = register({
|
||||
if (isTextElement(element)) {
|
||||
return element.textAlign;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
if (boundTextElement) {
|
||||
return boundTextElement.textAlign;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(element) =>
|
||||
isTextElement(element) || getBoundTextElement(element) !== null,
|
||||
isTextElement(element) ||
|
||||
getBoundTextElement(element, elementsMap) !== null,
|
||||
(hasSelection) =>
|
||||
hasSelection ? null : appState.currentItemTextAlign,
|
||||
)}
|
||||
@ -913,7 +932,7 @@ export const actionChangeVerticalAlign = register({
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||
return (
|
||||
<fieldset>
|
||||
<ButtonIconSelect<VerticalAlign | false>
|
||||
@ -945,14 +964,21 @@ export const actionChangeVerticalAlign = register({
|
||||
if (isTextElement(element) && element.containerId) {
|
||||
return element.verticalAlign;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (boundTextElement) {
|
||||
return boundTextElement.verticalAlign;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(element) =>
|
||||
isTextElement(element) || getBoundTextElement(element) !== null,
|
||||
isTextElement(element) ||
|
||||
getBoundTextElement(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
) !== null,
|
||||
(hasSelection) => (hasSelection ? null : VERTICAL_ALIGN.MIDDLE),
|
||||
)}
|
||||
onChange={(value) => updateData(value)}
|
||||
|
@ -32,12 +32,15 @@ export let copiedStyles: string = "{}";
|
||||
export const actionCopyStyles = register({
|
||||
name: "copyStyles",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, formData, app) => {
|
||||
const elementsCopied = [];
|
||||
const element = elements.find((el) => appState.selectedElementIds[el.id]);
|
||||
elementsCopied.push(element);
|
||||
if (element && hasBoundTextElement(element)) {
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(
|
||||
element,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
elementsCopied.push(boundTextElement);
|
||||
}
|
||||
if (element) {
|
||||
@ -59,7 +62,7 @@ export const actionCopyStyles = register({
|
||||
export const actionPasteStyles = register({
|
||||
name: "pasteStyles",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, formData, app) => {
|
||||
const elementsCopied = JSON.parse(copiedStyles);
|
||||
const pastedElement = elementsCopied[0];
|
||||
const boundTextElement = elementsCopied[1];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ExcalidrawElement } from "./element/types";
|
||||
import { ElementsMap, ExcalidrawElement } from "./element/types";
|
||||
import { newElementWith } from "./element/mutateElement";
|
||||
import { BoundingBox, getCommonBoundingBox } from "./element/bounds";
|
||||
import { getMaximumGroups } from "./groups";
|
||||
@ -10,10 +10,13 @@ export interface Alignment {
|
||||
|
||||
export const alignElements = (
|
||||
selectedElements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
alignment: Alignment,
|
||||
): ExcalidrawElement[] => {
|
||||
const groups: ExcalidrawElement[][] = getMaximumGroups(selectedElements);
|
||||
|
||||
const groups: ExcalidrawElement[][] = getMaximumGroups(
|
||||
selectedElements,
|
||||
elementsMap,
|
||||
);
|
||||
const selectionBoundingBox = getCommonBoundingBox(selectedElements);
|
||||
|
||||
return groups.flatMap((group) => {
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { ExcalidrawElementType, NonDeletedElementsMap } from "../element/types";
|
||||
import {
|
||||
ExcalidrawElementType,
|
||||
NonDeletedElementsMap,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { useDevice } from "./App";
|
||||
import {
|
||||
@ -47,7 +51,7 @@ export const SelectedShapeActions = ({
|
||||
renderAction,
|
||||
}: {
|
||||
appState: UIAppState;
|
||||
elementsMap: NonDeletedElementsMap;
|
||||
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap;
|
||||
renderAction: ActionManager["renderAction"];
|
||||
}) => {
|
||||
const targetElements = getTargetElements(elementsMap, appState);
|
||||
|
@ -1431,6 +1431,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pendingImageElementId: this.state.pendingImageElementId,
|
||||
});
|
||||
|
||||
const allElementsMap = this.scene.getNonDeletedElementsMap();
|
||||
|
||||
const shouldBlockPointerEvents =
|
||||
!(
|
||||
this.state.editingElement && isLinearElement(this.state.editingElement)
|
||||
@ -1628,6 +1630,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
canvas={this.canvas}
|
||||
rc={this.rc}
|
||||
elementsMap={elementsMap}
|
||||
allElementsMap={allElementsMap}
|
||||
visibleElements={visibleElements}
|
||||
versionNonce={versionNonce}
|
||||
selectionNonce={
|
||||
@ -3869,7 +3872,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (!isTextElement(selectedElement)) {
|
||||
container = selectedElement as ExcalidrawTextContainer;
|
||||
}
|
||||
const midPoint = getContainerCenter(selectedElement, this.state);
|
||||
const midPoint = getContainerCenter(
|
||||
selectedElement,
|
||||
this.state,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
const sceneX = midPoint.x;
|
||||
const sceneY = midPoint.y;
|
||||
this.startTextEditing({
|
||||
@ -4333,6 +4340,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.frameNameBoundsCache,
|
||||
x,
|
||||
y,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
)
|
||||
? allHitElements[allHitElements.length - 2]
|
||||
: elementWithHighestZIndex;
|
||||
@ -4362,7 +4370,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
|
||||
return getElementsAtPosition(elements, (element) =>
|
||||
hitTest(element, this.state, this.frameNameBoundsCache, x, y),
|
||||
hitTest(
|
||||
element,
|
||||
this.state,
|
||||
this.frameNameBoundsCache,
|
||||
x,
|
||||
y,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
).filter((element) => {
|
||||
// hitting a frame's element from outside the frame is not considered a hit
|
||||
const containingFrame = getContainingFrame(element);
|
||||
@ -4399,7 +4414,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
container,
|
||||
);
|
||||
if (container && parentCenterPosition) {
|
||||
const boundTextElementToContainer = getBoundTextElement(container);
|
||||
const boundTextElementToContainer = getBoundTextElement(
|
||||
container,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (!boundTextElementToContainer) {
|
||||
shouldBindToContainer = true;
|
||||
}
|
||||
@ -4412,7 +4430,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (isTextElement(selectedElements[0])) {
|
||||
existingTextElement = selectedElements[0];
|
||||
} else if (container) {
|
||||
existingTextElement = getBoundTextElement(selectedElements[0]);
|
||||
existingTextElement = getBoundTextElement(
|
||||
selectedElements[0],
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
} else {
|
||||
existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
|
||||
}
|
||||
@ -4621,7 +4642,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
[sceneX, sceneY],
|
||||
)
|
||||
) {
|
||||
const midPoint = getContainerCenter(container, this.state);
|
||||
const midPoint = getContainerCenter(
|
||||
container,
|
||||
this.state,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
sceneX = midPoint.x;
|
||||
sceneY = midPoint.y;
|
||||
@ -5257,8 +5282,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
);
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
@ -5285,6 +5310,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
linearElementEditor,
|
||||
{ x: scenePointerX, y: scenePointerY },
|
||||
this.state,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) {
|
||||
@ -5300,6 +5326,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.frameNameBoundsCache,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
elementsMap,
|
||||
)
|
||||
) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
|
||||
@ -5311,6 +5338,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.frameNameBoundsCache,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
)
|
||||
) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
|
||||
@ -6060,6 +6088,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.history,
|
||||
pointerDownState.origin,
|
||||
linearElementEditor,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (ret.hitElement) {
|
||||
pointerDownState.hit.element = ret.hitElement;
|
||||
@ -6995,6 +7024,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
},
|
||||
linearElementEditor,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (didDrag) {
|
||||
pointerDownState.lastCoords.x = pointerCoords.x;
|
||||
@ -7713,7 +7743,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
groupIds: [],
|
||||
});
|
||||
|
||||
removeElementsFromFrame([linearElement]);
|
||||
removeElementsFromFrame(
|
||||
[linearElement],
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
this.scene.informMutation();
|
||||
}
|
||||
@ -7866,6 +7899,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.state,
|
||||
),
|
||||
frame,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
@ -8093,6 +8127,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.frameNameBoundsCache,
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
)) ||
|
||||
(!hitElement &&
|
||||
pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))
|
||||
@ -9334,7 +9369,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
let elementCenterX = container.x + container.width / 2;
|
||||
let elementCenterY = container.y + container.height / 2;
|
||||
|
||||
const elementCenter = getContainerCenter(container, appState);
|
||||
const elementCenter = getContainerCenter(
|
||||
container,
|
||||
appState,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (elementCenter) {
|
||||
elementCenterX = elementCenter.x;
|
||||
elementCenterY = elementCenter.y;
|
||||
|
@ -7,13 +7,17 @@ import type {
|
||||
RenderableElementsMap,
|
||||
StaticCanvasRenderConfig,
|
||||
} from "../../scene/types";
|
||||
import type { NonDeletedExcalidrawElement } from "../../element/types";
|
||||
import type {
|
||||
NonDeletedExcalidrawElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../../element/types";
|
||||
import { isRenderThrottlingEnabled } from "../../reactUtils";
|
||||
|
||||
type StaticCanvasProps = {
|
||||
canvas: HTMLCanvasElement;
|
||||
rc: RoughCanvas;
|
||||
elementsMap: RenderableElementsMap;
|
||||
allElementsMap: NonDeletedSceneElementsMap;
|
||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||
versionNonce: number | undefined;
|
||||
selectionNonce: number | undefined;
|
||||
@ -67,6 +71,7 @@ const StaticCanvas = (props: StaticCanvasProps) => {
|
||||
rc: props.rc,
|
||||
scale: props.scale,
|
||||
elementsMap: props.elementsMap,
|
||||
allElementsMap: props.allElementsMap,
|
||||
visibleElements: props.visibleElements,
|
||||
appState: props.appState,
|
||||
renderConfig: props.renderConfig,
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
normalizeText,
|
||||
} from "../element/textElement";
|
||||
import {
|
||||
ElementsMap,
|
||||
ExcalidrawArrowElement,
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawElement,
|
||||
@ -42,7 +43,7 @@ import {
|
||||
VerticalAlign,
|
||||
} from "../element/types";
|
||||
import { MarkOptional } from "../utility-types";
|
||||
import { assertNever, cloneJSON, getFontString } from "../utils";
|
||||
import { arrayToMap, assertNever, cloneJSON, getFontString } from "../utils";
|
||||
import { getSizeFromPoints } from "../points";
|
||||
import { randomId } from "../random";
|
||||
|
||||
@ -202,6 +203,7 @@ const DEFAULT_DIMENSION = 100;
|
||||
const bindTextToContainer = (
|
||||
container: ExcalidrawElement,
|
||||
textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
const textElement: ExcalidrawTextElement = newTextElement({
|
||||
x: 0,
|
||||
@ -623,6 +625,7 @@ export const convertToExcalidrawElements = (
|
||||
let [container, text] = bindTextToContainer(
|
||||
excalidrawElement,
|
||||
element?.label,
|
||||
arrayToMap(elementStore.getElements()),
|
||||
);
|
||||
elementStore.add(container);
|
||||
elementStore.add(text);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ExcalidrawElement } from "./element/types";
|
||||
import { newElementWith } from "./element/mutateElement";
|
||||
import { getMaximumGroups } from "./groups";
|
||||
import { getCommonBoundingBox } from "./element/bounds";
|
||||
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
||||
|
||||
export interface Distribution {
|
||||
space: "between";
|
||||
@ -10,6 +10,7 @@ export interface Distribution {
|
||||
|
||||
export const distributeElements = (
|
||||
selectedElements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
distribution: Distribution,
|
||||
): ExcalidrawElement[] => {
|
||||
const [start, mid, end, extent] =
|
||||
@ -18,7 +19,7 @@ export const distributeElements = (
|
||||
: (["minY", "midY", "maxY", "height"] as const);
|
||||
|
||||
const bounds = getCommonBoundingBox(selectedElements);
|
||||
const groups = getMaximumGroups(selectedElements)
|
||||
const groups = getMaximumGroups(selectedElements, elementsMap)
|
||||
.map((group) => [group, getCommonBoundingBox(group)] as const)
|
||||
.sort((a, b) => a[1][mid] - b[1][mid]);
|
||||
|
||||
|
@ -321,9 +321,9 @@ export const updateBoundElements = (
|
||||
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
|
||||
simultaneouslyUpdated,
|
||||
);
|
||||
|
||||
const scene = Scene.getScene(changedElement)!;
|
||||
getNonDeletedElements(
|
||||
Scene.getScene(changedElement)!,
|
||||
scene,
|
||||
boundLinearElements.map((el) => el.id),
|
||||
).forEach((element) => {
|
||||
if (!isLinearElement(element)) {
|
||||
@ -362,9 +362,12 @@ export const updateBoundElements = (
|
||||
endBinding,
|
||||
changedElement as ExcalidrawBindableElement,
|
||||
);
|
||||
const boundText = getBoundTextElement(element);
|
||||
const boundText = getBoundTextElement(
|
||||
element,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (boundText) {
|
||||
handleBindTextResize(element, false);
|
||||
handleBindTextResize(element, scene.getNonDeletedElementsMap(), false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
NonDeleted,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ElementsMapOrArray,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
import { distance2d, rotate, rotatePoint } from "../math";
|
||||
import rough from "roughjs/bin/rough";
|
||||
@ -74,13 +75,16 @@ export class ElementBounds {
|
||||
) {
|
||||
return cachedBounds.bounds;
|
||||
}
|
||||
|
||||
const bounds = ElementBounds.calculateBounds(element);
|
||||
const scene = Scene.getScene(element);
|
||||
const bounds = ElementBounds.calculateBounds(
|
||||
element,
|
||||
scene?.getNonDeletedElementsMap() || new Map(),
|
||||
);
|
||||
|
||||
// hack to ensure that downstream checks could retrieve element Scene
|
||||
// so as to have correctly calculated bounds
|
||||
// FIXME remove when we get rid of all the id:Scene / element:Scene mapping
|
||||
const shouldCache = Scene.getScene(element);
|
||||
const shouldCache = !!scene;
|
||||
|
||||
if (shouldCache) {
|
||||
ElementBounds.boundsCache.set(element, {
|
||||
@ -92,7 +96,10 @@ export class ElementBounds {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private static calculateBounds(element: ExcalidrawElement): Bounds {
|
||||
private static calculateBounds(
|
||||
element: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
): Bounds {
|
||||
let bounds: Bounds;
|
||||
|
||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
|
||||
@ -111,7 +118,7 @@ export class ElementBounds {
|
||||
maxY + element.y,
|
||||
];
|
||||
} else if (isLinearElement(element)) {
|
||||
bounds = getLinearElementRotatedBounds(element, cx, cy);
|
||||
bounds = getLinearElementRotatedBounds(element, cx, cy, elementsMap);
|
||||
} else if (element.type === "diamond") {
|
||||
const [x11, y11] = rotate(cx, y1, cx, cy, element.angle);
|
||||
const [x12, y12] = rotate(cx, y2, cx, cy, element.angle);
|
||||
@ -154,16 +161,17 @@ export const getElementAbsoluteCoords = (
|
||||
element: ExcalidrawElement,
|
||||
includeBoundText: boolean = false,
|
||||
): [number, number, number, number, number, number] => {
|
||||
const elementsMap =
|
||||
Scene.getScene(element)?.getElementsMapIncludingDeleted() || new Map();
|
||||
if (isFreeDrawElement(element)) {
|
||||
return getFreeDrawElementAbsoluteCoords(element);
|
||||
} else if (isLinearElement(element)) {
|
||||
return LinearElementEditor.getElementAbsoluteCoords(
|
||||
element,
|
||||
elementsMap,
|
||||
includeBoundText,
|
||||
);
|
||||
} else if (isTextElement(element)) {
|
||||
const elementsMap =
|
||||
Scene.getScene(element)?.getElementsMapIncludingDeleted();
|
||||
const container = elementsMap
|
||||
? getContainerElement(element, elementsMap)
|
||||
: null;
|
||||
@ -677,7 +685,10 @@ const getLinearElementRotatedBounds = (
|
||||
element: ExcalidrawLinearElement,
|
||||
cx: number,
|
||||
cy: number,
|
||||
elementsMap: ElementsMap,
|
||||
): Bounds => {
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
|
||||
if (element.points.length < 2) {
|
||||
const [pointX, pointY] = element.points[0];
|
||||
const [x, y] = rotate(
|
||||
@ -689,7 +700,6 @@ const getLinearElementRotatedBounds = (
|
||||
);
|
||||
|
||||
let coords: Bounds = [x, y, x, y];
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
@ -714,7 +724,6 @@ const getLinearElementRotatedBounds = (
|
||||
rotate(element.x + x, element.y + y, cx, cy, element.angle);
|
||||
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
|
||||
let coords: Bounds = [res[0], res[1], res[2], res[3]];
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
StrokeRoundness,
|
||||
ExcalidrawFrameLikeElement,
|
||||
ExcalidrawIframeLikeElement,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
|
||||
import {
|
||||
@ -78,6 +79,7 @@ export const hitTest = (
|
||||
frameNameBoundsCache: FrameNameBoundsCache,
|
||||
x: number,
|
||||
y: number,
|
||||
elementsMap: ElementsMap,
|
||||
): boolean => {
|
||||
// How many pixels off the shape boundary we still consider a hit
|
||||
const threshold = 10 / appState.zoom.value;
|
||||
@ -95,7 +97,7 @@ export const hitTest = (
|
||||
);
|
||||
}
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
const isHittingBoundTextElement = hitTest(
|
||||
boundTextElement,
|
||||
@ -103,6 +105,7 @@ export const hitTest = (
|
||||
frameNameBoundsCache,
|
||||
x,
|
||||
y,
|
||||
elementsMap,
|
||||
);
|
||||
if (isHittingBoundTextElement) {
|
||||
return true;
|
||||
@ -122,15 +125,16 @@ export const isHittingElementBoundingBoxWithoutHittingElement = (
|
||||
frameNameBoundsCache: FrameNameBoundsCache,
|
||||
x: number,
|
||||
y: number,
|
||||
elementsMap: ElementsMap,
|
||||
): boolean => {
|
||||
const threshold = 10 / appState.zoom.value;
|
||||
|
||||
// So that bound text element hit is considered within bounding box of container even if its outside actual bounding box of element
|
||||
// eg for linear elements text can be outside the element bounding box
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (
|
||||
boundTextElement &&
|
||||
hitTest(boundTextElement, appState, frameNameBoundsCache, x, y)
|
||||
hitTest(boundTextElement, appState, frameNameBoundsCache, x, y, elementsMap)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -57,7 +57,10 @@ export const dragSelectedElements = (
|
||||
// skip arrow labels since we calculate its position during render
|
||||
!isArrowElement(element)
|
||||
) {
|
||||
const textElement = getBoundTextElement(element);
|
||||
const textElement = getBoundTextElement(
|
||||
element,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (textElement) {
|
||||
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
PointBinding,
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
import {
|
||||
distance2d,
|
||||
@ -193,6 +194,7 @@ export class LinearElementEditor {
|
||||
pointSceneCoords: { x: number; y: number }[],
|
||||
) => void,
|
||||
linearElementEditor: LinearElementEditor,
|
||||
elementsMap: ElementsMap,
|
||||
): boolean {
|
||||
if (!linearElementEditor) {
|
||||
return false;
|
||||
@ -272,9 +274,9 @@ export class LinearElementEditor {
|
||||
);
|
||||
}
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
handleBindTextResize(element, false);
|
||||
handleBindTextResize(element, elementsMap, false);
|
||||
}
|
||||
|
||||
// suggest bindings for first and last point if selected
|
||||
@ -404,9 +406,10 @@ export class LinearElementEditor {
|
||||
|
||||
static getEditorMidPoints = (
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
appState: InteractiveCanvasAppState,
|
||||
): typeof editorMidPointsCache["points"] => {
|
||||
const boundText = getBoundTextElement(element);
|
||||
const boundText = getBoundTextElement(element, elementsMap);
|
||||
|
||||
// Since its not needed outside editor unless 2 pointer lines or bound text
|
||||
if (
|
||||
@ -465,6 +468,7 @@ export class LinearElementEditor {
|
||||
linearElementEditor: LinearElementEditor,
|
||||
scenePointer: { x: number; y: number },
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
const { elementId } = linearElementEditor;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
@ -503,7 +507,7 @@ export class LinearElementEditor {
|
||||
}
|
||||
let index = 0;
|
||||
const midPoints: typeof editorMidPointsCache["points"] =
|
||||
LinearElementEditor.getEditorMidPoints(element, appState);
|
||||
LinearElementEditor.getEditorMidPoints(element, elementsMap, appState);
|
||||
while (index < midPoints.length) {
|
||||
if (midPoints[index] !== null) {
|
||||
const distance = distance2d(
|
||||
@ -581,6 +585,7 @@ export class LinearElementEditor {
|
||||
linearElementEditor: LinearElementEditor,
|
||||
appState: AppState,
|
||||
midPoint: Point,
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
@ -588,7 +593,11 @@ export class LinearElementEditor {
|
||||
if (!element) {
|
||||
return -1;
|
||||
}
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(element, appState);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
element,
|
||||
elementsMap,
|
||||
appState,
|
||||
);
|
||||
let index = 0;
|
||||
while (index < midPoints.length) {
|
||||
if (LinearElementEditor.arePointsEqual(midPoint, midPoints[index])) {
|
||||
@ -605,6 +614,7 @@ export class LinearElementEditor {
|
||||
history: History,
|
||||
scenePointer: { x: number; y: number },
|
||||
linearElementEditor: LinearElementEditor,
|
||||
elementsMap: ElementsMap,
|
||||
): {
|
||||
didAddPoint: boolean;
|
||||
hitElement: NonDeleted<ExcalidrawElement> | null;
|
||||
@ -630,6 +640,7 @@ export class LinearElementEditor {
|
||||
linearElementEditor,
|
||||
scenePointer,
|
||||
appState,
|
||||
elementsMap,
|
||||
);
|
||||
let segmentMidpointIndex = null;
|
||||
if (segmentMidpoint) {
|
||||
@ -637,6 +648,7 @@ export class LinearElementEditor {
|
||||
linearElementEditor,
|
||||
appState,
|
||||
segmentMidpoint,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
if (event.altKey && appState.editingLinearElement) {
|
||||
@ -1418,6 +1430,7 @@ export class LinearElementEditor {
|
||||
|
||||
static getElementAbsoluteCoords = (
|
||||
element: ExcalidrawLinearElement,
|
||||
elementsMap: ElementsMap,
|
||||
includeBoundText: boolean = false,
|
||||
): [number, number, number, number, number, number] => {
|
||||
let coords: [number, number, number, number, number, number];
|
||||
@ -1462,7 +1475,7 @@ export class LinearElementEditor {
|
||||
if (!includeBoundText) {
|
||||
return coords;
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
coords = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||
element,
|
||||
|
@ -342,7 +342,7 @@ export const refreshTextDimensions = (
|
||||
text = wrapText(
|
||||
text,
|
||||
getFontString(textElement),
|
||||
getBoundTextMaxWidth(container),
|
||||
getBoundTextMaxWidth(container, textElement),
|
||||
);
|
||||
}
|
||||
const dimensions = getAdjustedDimensions(textElement, text);
|
||||
|
@ -126,6 +126,7 @@ export const transformElements = (
|
||||
rotateMultipleElements(
|
||||
originalElements,
|
||||
selectedElements,
|
||||
elementsMap,
|
||||
pointerX,
|
||||
pointerY,
|
||||
shouldRotateWithDiscreteAngle,
|
||||
@ -219,7 +220,7 @@ const measureFontSizeFromWidth = (
|
||||
if (hasContainer) {
|
||||
const container = getContainerElement(element, elementsMap);
|
||||
if (container) {
|
||||
width = getBoundTextMaxWidth(container);
|
||||
width = getBoundTextMaxWidth(container, element);
|
||||
}
|
||||
}
|
||||
const nextFontSize = element.fontSize * (nextWidth / width);
|
||||
@ -394,7 +395,7 @@ export const resizeSingleElement = (
|
||||
let scaleY = atStartBoundsHeight / boundsCurrentHeight;
|
||||
|
||||
let boundTextFont: { fontSize?: number; baseline?: number } = {};
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
|
||||
if (transformHandleDirection.includes("e")) {
|
||||
scaleX = (rotatedPointer[0] - startTopLeft[0]) / boundsCurrentWidth;
|
||||
@ -458,7 +459,7 @@ export const resizeSingleElement = (
|
||||
const nextFont = measureFontSizeFromWidth(
|
||||
boundTextElement,
|
||||
elementsMap,
|
||||
getBoundTextMaxWidth(updatedElement),
|
||||
getBoundTextMaxWidth(updatedElement, boundTextElement),
|
||||
getBoundTextMaxHeight(updatedElement, boundTextElement),
|
||||
);
|
||||
if (nextFont === null) {
|
||||
@ -640,6 +641,7 @@ export const resizeSingleElement = (
|
||||
}
|
||||
handleBindTextResize(
|
||||
element,
|
||||
elementsMap,
|
||||
transformHandleDirection,
|
||||
shouldMaintainAspectRatio,
|
||||
);
|
||||
@ -882,7 +884,7 @@ export const resizeMultipleElements = (
|
||||
newSize: { width, height },
|
||||
});
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement && boundTextFontSize) {
|
||||
mutateElement(
|
||||
boundTextElement,
|
||||
@ -892,7 +894,7 @@ export const resizeMultipleElements = (
|
||||
},
|
||||
false,
|
||||
);
|
||||
handleBindTextResize(element, transformHandleType, true);
|
||||
handleBindTextResize(element, elementsMap, transformHandleType, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -902,6 +904,7 @@ export const resizeMultipleElements = (
|
||||
const rotateMultipleElements = (
|
||||
originalElements: PointerDownState["originalElements"],
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
pointerX: number,
|
||||
pointerY: number,
|
||||
shouldRotateWithDiscreteAngle: boolean,
|
||||
@ -941,7 +944,7 @@ const rotateMultipleElements = (
|
||||
);
|
||||
updateBoundElements(element, { simultaneouslyUpdated: elements });
|
||||
|
||||
const boundText = getBoundTextElement(element);
|
||||
const boundText = getBoundTextElement(element, elementsMap);
|
||||
if (boundText && !isArrowElement(element)) {
|
||||
mutateElement(
|
||||
boundText,
|
||||
|
@ -319,17 +319,17 @@ describe("Test measureText", () => {
|
||||
|
||||
it("should return max width when container is rectangle", () => {
|
||||
const container = API.createElement({ type: "rectangle", ...params });
|
||||
expect(getBoundTextMaxWidth(container)).toBe(168);
|
||||
expect(getBoundTextMaxWidth(container, null)).toBe(168);
|
||||
});
|
||||
|
||||
it("should return max width when container is ellipse", () => {
|
||||
const container = API.createElement({ type: "ellipse", ...params });
|
||||
expect(getBoundTextMaxWidth(container)).toBe(116);
|
||||
expect(getBoundTextMaxWidth(container, null)).toBe(116);
|
||||
});
|
||||
|
||||
it("should return max width when container is diamond", () => {
|
||||
const container = API.createElement({ type: "diamond", ...params });
|
||||
expect(getBoundTextMaxWidth(container)).toBe(79);
|
||||
expect(getBoundTextMaxWidth(container, null)).toBe(79);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import { MaybeTransformHandleType } from "./transformHandles";
|
||||
import Scene from "../scene/Scene";
|
||||
import { isTextElement } from ".";
|
||||
import { isBoundToContainer, isArrowElement } from "./typeChecks";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
@ -89,7 +88,7 @@ export const redrawTextBoundingBox = (
|
||||
container,
|
||||
textElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container, textElement);
|
||||
|
||||
if (!isArrowElement(container) && metrics.height > maxContainerHeight) {
|
||||
const nextHeight = computeContainerDimensionForBoundText(
|
||||
@ -162,6 +161,7 @@ export const bindTextToShapeAfterDuplication = (
|
||||
|
||||
export const handleBindTextResize = (
|
||||
container: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
transformHandleType: MaybeTransformHandleType,
|
||||
shouldMaintainAspectRatio = false,
|
||||
) => {
|
||||
@ -170,25 +170,17 @@ export const handleBindTextResize = (
|
||||
return;
|
||||
}
|
||||
resetOriginalContainerCache(container.id);
|
||||
let textElement = Scene.getScene(container)!.getElement(
|
||||
boundTextElementId,
|
||||
) as ExcalidrawTextElement;
|
||||
const textElement = getBoundTextElement(container, elementsMap);
|
||||
if (textElement && textElement.text) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
textElement = Scene.getScene(container)!.getElement(
|
||||
boundTextElementId,
|
||||
) as ExcalidrawTextElement;
|
||||
let text = textElement.text;
|
||||
let nextHeight = textElement.height;
|
||||
let nextWidth = textElement.width;
|
||||
const maxWidth = getBoundTextMaxWidth(container);
|
||||
const maxHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
textElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
const maxWidth = getBoundTextMaxWidth(container, textElement);
|
||||
const maxHeight = getBoundTextMaxHeight(container, textElement);
|
||||
let containerHeight = container.height;
|
||||
let nextBaseLine = textElement.baseline;
|
||||
if (
|
||||
@ -243,10 +235,7 @@ export const handleBindTextResize = (
|
||||
if (!isArrowElement(container)) {
|
||||
mutateElement(
|
||||
textElement,
|
||||
computeBoundTextPosition(
|
||||
container,
|
||||
textElement as ExcalidrawTextElementWithContainer,
|
||||
),
|
||||
computeBoundTextPosition(container, textElement),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -264,7 +253,7 @@ export const computeBoundTextPosition = (
|
||||
}
|
||||
const containerCoords = getContainerCoords(container);
|
||||
const maxContainerHeight = getBoundTextMaxHeight(container, boundTextElement);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container, boundTextElement);
|
||||
|
||||
let x;
|
||||
let y;
|
||||
@ -667,17 +656,18 @@ export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
|
||||
: null;
|
||||
};
|
||||
|
||||
export const getBoundTextElement = (element: ExcalidrawElement | null) => {
|
||||
export const getBoundTextElement = (
|
||||
element: ExcalidrawElement | null,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
const boundTextElementId = getBoundTextElementId(element);
|
||||
|
||||
if (boundTextElementId) {
|
||||
return (
|
||||
(Scene.getScene(element)?.getElement(
|
||||
boundTextElementId,
|
||||
) as ExcalidrawTextElementWithContainer) || null
|
||||
);
|
||||
return (elementsMap.get(boundTextElementId) ||
|
||||
null) as ExcalidrawTextElementWithContainer | null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@ -699,6 +689,7 @@ export const getContainerElement = (
|
||||
export const getContainerCenter = (
|
||||
container: ExcalidrawElement,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
if (!isArrowElement(container)) {
|
||||
return {
|
||||
@ -718,6 +709,7 @@ export const getContainerCenter = (
|
||||
const index = container.points.length / 2 - 1;
|
||||
let midSegmentMidpoint = LinearElementEditor.getEditorMidPoints(
|
||||
container,
|
||||
elementsMap,
|
||||
appState,
|
||||
)[index];
|
||||
if (!midSegmentMidpoint) {
|
||||
@ -877,9 +869,7 @@ export const computeContainerDimensionForBoundText = (
|
||||
|
||||
export const getBoundTextMaxWidth = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElement | null = getBoundTextElement(
|
||||
container,
|
||||
),
|
||||
boundTextElement: ExcalidrawTextElement | null,
|
||||
) => {
|
||||
const { width } = container;
|
||||
if (isArrowElement(container)) {
|
||||
|
@ -34,6 +34,7 @@ import {
|
||||
computeContainerDimensionForBoundText,
|
||||
detectLineHeight,
|
||||
computeBoundTextPosition,
|
||||
getBoundTextElement,
|
||||
} from "./textElement";
|
||||
import {
|
||||
actionDecreaseFontSize,
|
||||
@ -196,7 +197,8 @@ export const textWysiwyg = ({
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth = getBoundTextMaxWidth(container);
|
||||
maxWidth = getBoundTextMaxWidth(container, updatedTextElement);
|
||||
|
||||
maxHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||
@ -361,10 +363,14 @@ export const textWysiwyg = ({
|
||||
fontFamily: app.state.currentItemFontFamily,
|
||||
});
|
||||
if (container) {
|
||||
const boundTextElement = getBoundTextElement(
|
||||
container,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
const wrappedText = wrapText(
|
||||
`${editable.value}${data}`,
|
||||
font,
|
||||
getBoundTextMaxWidth(container),
|
||||
getBoundTextMaxWidth(container, boundTextElement),
|
||||
);
|
||||
const width = getTextWidth(wrappedText, font);
|
||||
editable.style.width = `${width}px`;
|
||||
|
@ -279,6 +279,16 @@ export type NonDeletedElementsMap = Map<
|
||||
export type SceneElementsMap = Map<ExcalidrawElement["id"], ExcalidrawElement> &
|
||||
MakeBrand<"SceneElementsMap">;
|
||||
|
||||
/**
|
||||
* Map of all non-deleted Scene elements.
|
||||
* Not a subset. Use this type when you need access to current Scene elements.
|
||||
*/
|
||||
export type NonDeletedSceneElementsMap = Map<
|
||||
ExcalidrawElement["id"],
|
||||
NonDeletedExcalidrawElement
|
||||
> &
|
||||
MakeBrand<"NonDeletedSceneElementsMap">;
|
||||
|
||||
export type ElementsMapOrArray =
|
||||
| readonly ExcalidrawElement[]
|
||||
| Readonly<ElementsMap>;
|
||||
|
@ -444,6 +444,7 @@ export const addElementsToFrame = <T extends ElementsMapOrArray>(
|
||||
elementsToAdd: NonDeletedExcalidrawElement[],
|
||||
frame: ExcalidrawFrameLikeElement,
|
||||
): T => {
|
||||
const elementsMap = arrayToMap(allElements);
|
||||
const currTargetFrameChildrenMap = new Map<ExcalidrawElement["id"], true>();
|
||||
for (const element of allElements.values()) {
|
||||
if (element.frameId === frame.id) {
|
||||
@ -481,7 +482,7 @@ export const addElementsToFrame = <T extends ElementsMapOrArray>(
|
||||
finalElementsToAdd.push(element);
|
||||
}
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (
|
||||
boundTextElement &&
|
||||
!suppliedElementsToAddSet.has(boundTextElement.id) &&
|
||||
@ -506,6 +507,7 @@ export const addElementsToFrame = <T extends ElementsMapOrArray>(
|
||||
|
||||
export const removeElementsFromFrame = (
|
||||
elementsToRemove: ReadonlySetLike<NonDeletedExcalidrawElement>,
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
const _elementsToRemove = new Map<
|
||||
ExcalidrawElement["id"],
|
||||
@ -524,7 +526,7 @@ export const removeElementsFromFrame = (
|
||||
const arr = toRemoveElementsByFrame.get(element.frameId) || [];
|
||||
arr.push(element);
|
||||
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
_elementsToRemove.set(boundTextElement.id, boundTextElement);
|
||||
arr.push(boundTextElement);
|
||||
@ -550,7 +552,7 @@ export const removeAllElementsFromFrame = <T extends ExcalidrawElement>(
|
||||
frame: ExcalidrawFrameLikeElement,
|
||||
) => {
|
||||
const elementsInFrame = getFrameChildren(allElements, frame.id);
|
||||
removeElementsFromFrame(elementsInFrame);
|
||||
removeElementsFromFrame(elementsInFrame, arrayToMap(allElements));
|
||||
return allElements;
|
||||
};
|
||||
|
||||
@ -558,6 +560,7 @@ export const replaceAllElementsInFrame = <T extends ExcalidrawElement>(
|
||||
allElements: readonly T[],
|
||||
nextElementsInFrame: ExcalidrawElement[],
|
||||
frame: ExcalidrawFrameLikeElement,
|
||||
app: AppClassProperties,
|
||||
): T[] => {
|
||||
return addElementsToFrame(
|
||||
removeAllElementsFromFrame(allElements, frame),
|
||||
@ -608,7 +611,7 @@ export const updateFrameMembershipOfSelectedElements = <
|
||||
});
|
||||
|
||||
if (elementsToRemove.size > 0) {
|
||||
removeElementsFromFrame(elementsToRemove);
|
||||
removeElementsFromFrame(elementsToRemove, elementsMap);
|
||||
}
|
||||
return allElements;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
NonDeleted,
|
||||
NonDeletedExcalidrawElement,
|
||||
ElementsMapOrArray,
|
||||
ElementsMap,
|
||||
} from "./element/types";
|
||||
import {
|
||||
AppClassProperties,
|
||||
@ -329,12 +330,12 @@ export const removeFromSelectedGroups = (
|
||||
|
||||
export const getMaximumGroups = (
|
||||
elements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
): ExcalidrawElement[][] => {
|
||||
const groups: Map<String, ExcalidrawElement[]> = new Map<
|
||||
String,
|
||||
ExcalidrawElement[]
|
||||
>();
|
||||
|
||||
elements.forEach((element: ExcalidrawElement) => {
|
||||
const groupId =
|
||||
element.groupIds.length === 0
|
||||
@ -344,7 +345,7 @@ export const getMaximumGroups = (
|
||||
const currentGroupMembers = groups.get(groupId) || [];
|
||||
|
||||
// Include bound text if present when grouping
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement) {
|
||||
currentGroupMembers.push(boundTextElement);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ExcalidrawFrameLikeElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../element/types";
|
||||
import {
|
||||
isTextElement,
|
||||
@ -190,6 +191,7 @@ const cappedElementCanvasSize = (
|
||||
|
||||
const generateElementCanvas = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: RenderableElementsMap,
|
||||
zoom: Zoom,
|
||||
renderConfig: StaticCanvasRenderConfig,
|
||||
appState: StaticCanvasAppState,
|
||||
@ -247,7 +249,8 @@ const generateElementCanvas = (
|
||||
zoomValue: zoom.value,
|
||||
canvasOffsetX,
|
||||
canvasOffsetY,
|
||||
boundTextElementVersion: getBoundTextElement(element)?.version || null,
|
||||
boundTextElementVersion:
|
||||
getBoundTextElement(element, elementsMap)?.version || null,
|
||||
containingFrameOpacity: getContainingFrame(element)?.opacity || 100,
|
||||
};
|
||||
};
|
||||
@ -407,6 +410,7 @@ export const elementWithCanvasCache = new WeakMap<
|
||||
|
||||
const generateElementWithCanvas = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: RenderableElementsMap,
|
||||
renderConfig: StaticCanvasRenderConfig,
|
||||
appState: StaticCanvasAppState,
|
||||
) => {
|
||||
@ -416,7 +420,9 @@ const generateElementWithCanvas = (
|
||||
prevElementWithCanvas &&
|
||||
prevElementWithCanvas.zoomValue !== zoom.value &&
|
||||
!appState?.shouldCacheIgnoreZoom;
|
||||
const boundTextElementVersion = getBoundTextElement(element)?.version || null;
|
||||
const boundTextElementVersion =
|
||||
getBoundTextElement(element, elementsMap)?.version || null;
|
||||
|
||||
const containingFrameOpacity = getContainingFrame(element)?.opacity || 100;
|
||||
|
||||
if (
|
||||
@ -428,6 +434,7 @@ const generateElementWithCanvas = (
|
||||
) {
|
||||
const elementWithCanvas = generateElementCanvas(
|
||||
element,
|
||||
elementsMap,
|
||||
zoom,
|
||||
renderConfig,
|
||||
appState,
|
||||
@ -445,6 +452,7 @@ const drawElementFromCanvas = (
|
||||
context: CanvasRenderingContext2D,
|
||||
renderConfig: StaticCanvasRenderConfig,
|
||||
appState: StaticCanvasAppState,
|
||||
allElementsMap: NonDeletedSceneElementsMap,
|
||||
) => {
|
||||
const element = elementWithCanvas.element;
|
||||
const padding = getCanvasPadding(element);
|
||||
@ -464,7 +472,8 @@ const drawElementFromCanvas = (
|
||||
|
||||
context.save();
|
||||
context.scale(1 / window.devicePixelRatio, 1 / window.devicePixelRatio);
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
|
||||
const boundTextElement = getBoundTextElement(element, allElementsMap);
|
||||
|
||||
if (isArrowElement(element) && boundTextElement) {
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
@ -511,7 +520,6 @@ const drawElementFromCanvas = (
|
||||
offsetY -
|
||||
padding * zoom;
|
||||
tempCanvasContext.translate(-shiftX, -shiftY);
|
||||
|
||||
// Clear the bound text area
|
||||
tempCanvasContext.clearRect(
|
||||
-(boundTextElement.width / 2 + BOUND_TEXT_PADDING) *
|
||||
@ -573,6 +581,7 @@ const drawElementFromCanvas = (
|
||||
) {
|
||||
const textElement = getBoundTextElement(
|
||||
element,
|
||||
allElementsMap,
|
||||
) as ExcalidrawTextElementWithContainer;
|
||||
const coords = getContainerCoords(element);
|
||||
context.strokeStyle = "#c92a2a";
|
||||
@ -580,7 +589,7 @@ const drawElementFromCanvas = (
|
||||
context.strokeRect(
|
||||
(coords.x + appState.scrollX) * window.devicePixelRatio,
|
||||
(coords.y + appState.scrollY) * window.devicePixelRatio,
|
||||
getBoundTextMaxWidth(element) * window.devicePixelRatio,
|
||||
getBoundTextMaxWidth(element, textElement) * window.devicePixelRatio,
|
||||
getBoundTextMaxHeight(element, textElement) * window.devicePixelRatio,
|
||||
);
|
||||
}
|
||||
@ -616,6 +625,7 @@ export const renderSelectionElement = (
|
||||
export const renderElement = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: RenderableElementsMap,
|
||||
allElementsMap: NonDeletedSceneElementsMap,
|
||||
rc: RoughCanvas,
|
||||
context: CanvasRenderingContext2D,
|
||||
renderConfig: StaticCanvasRenderConfig,
|
||||
@ -687,6 +697,7 @@ export const renderElement = (
|
||||
} else {
|
||||
const elementWithCanvas = generateElementWithCanvas(
|
||||
element,
|
||||
elementsMap,
|
||||
renderConfig,
|
||||
appState,
|
||||
);
|
||||
@ -695,6 +706,7 @@ export const renderElement = (
|
||||
context,
|
||||
renderConfig,
|
||||
appState,
|
||||
allElementsMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -737,7 +749,7 @@ export const renderElement = (
|
||||
if (shouldResetImageFilter(element, renderConfig, appState)) {
|
||||
context.filter = "none";
|
||||
}
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
|
||||
if (isArrowElement(element) && boundTextElement) {
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
@ -820,6 +832,7 @@ export const renderElement = (
|
||||
} else {
|
||||
const elementWithCanvas = generateElementWithCanvas(
|
||||
element,
|
||||
elementsMap,
|
||||
renderConfig,
|
||||
appState,
|
||||
);
|
||||
@ -851,6 +864,7 @@ export const renderElement = (
|
||||
context,
|
||||
renderConfig,
|
||||
appState,
|
||||
allElementsMap,
|
||||
);
|
||||
|
||||
// reset
|
||||
@ -1096,7 +1110,7 @@ export const renderElementToSvg = (
|
||||
}
|
||||
case "line":
|
||||
case "arrow": {
|
||||
const boundText = getBoundTextElement(element);
|
||||
const boundText = getBoundTextElement(element, elementsMap);
|
||||
const maskPath = svgRoot.ownerDocument!.createElementNS(SVG_NS, "mask");
|
||||
if (boundText) {
|
||||
maskPath.setAttribute("id", `mask-${element.id}`);
|
||||
|
@ -246,6 +246,7 @@ const renderLinearPointHandles = (
|
||||
context: CanvasRenderingContext2D,
|
||||
appState: InteractiveCanvasAppState,
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: RenderableElementsMap,
|
||||
) => {
|
||||
if (!appState.selectedLinearElement) {
|
||||
return;
|
||||
@ -269,6 +270,7 @@ const renderLinearPointHandles = (
|
||||
//Rendering segment mid points
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
element,
|
||||
elementsMap,
|
||||
appState,
|
||||
).filter((midPoint) => midPoint !== null) as Point[];
|
||||
|
||||
@ -485,7 +487,12 @@ const _renderInteractiveScene = ({
|
||||
});
|
||||
|
||||
if (editingLinearElement) {
|
||||
renderLinearPointHandles(context, appState, editingLinearElement);
|
||||
renderLinearPointHandles(
|
||||
context,
|
||||
appState,
|
||||
editingLinearElement,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
|
||||
// Paint selection element
|
||||
@ -528,6 +535,7 @@ const _renderInteractiveScene = ({
|
||||
context,
|
||||
appState,
|
||||
selectedElements[0] as NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -553,6 +561,7 @@ const _renderInteractiveScene = ({
|
||||
context,
|
||||
appState,
|
||||
selectedElements[0] as ExcalidrawLinearElement,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
const selectionColor = renderConfig.selectionColor || oc.black;
|
||||
@ -891,6 +900,7 @@ const _renderStaticScene = ({
|
||||
canvas,
|
||||
rc,
|
||||
elementsMap,
|
||||
allElementsMap,
|
||||
visibleElements,
|
||||
scale,
|
||||
appState,
|
||||
@ -972,6 +982,7 @@ const _renderStaticScene = ({
|
||||
renderElement(
|
||||
element,
|
||||
elementsMap,
|
||||
allElementsMap,
|
||||
rc,
|
||||
context,
|
||||
renderConfig,
|
||||
@ -982,6 +993,7 @@ const _renderStaticScene = ({
|
||||
renderElement(
|
||||
element,
|
||||
elementsMap,
|
||||
allElementsMap,
|
||||
rc,
|
||||
context,
|
||||
renderConfig,
|
||||
@ -1005,6 +1017,7 @@ const _renderStaticScene = ({
|
||||
renderElement(
|
||||
element,
|
||||
elementsMap,
|
||||
allElementsMap,
|
||||
rc,
|
||||
context,
|
||||
renderConfig,
|
||||
@ -1024,6 +1037,7 @@ const _renderStaticScene = ({
|
||||
renderElement(
|
||||
label,
|
||||
elementsMap,
|
||||
allElementsMap,
|
||||
rc,
|
||||
context,
|
||||
renderConfig,
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
NonDeleted,
|
||||
ExcalidrawFrameLikeElement,
|
||||
ElementsMapOrArray,
|
||||
NonDeletedElementsMap,
|
||||
SceneElementsMap,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../element/types";
|
||||
import { isNonDeletedElement } from "../element";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
@ -27,7 +27,7 @@ type SelectionHash = string & { __brand: "selectionHash" };
|
||||
const getNonDeletedElements = <T extends ExcalidrawElement>(
|
||||
allElements: readonly T[],
|
||||
) => {
|
||||
const elementsMap = new Map() as NonDeletedElementsMap;
|
||||
const elementsMap = new Map() as NonDeletedSceneElementsMap;
|
||||
const elements: T[] = [];
|
||||
for (const element of allElements) {
|
||||
if (!element.isDeleted) {
|
||||
@ -120,8 +120,9 @@ class Scene {
|
||||
private callbacks: Set<SceneStateCallback> = new Set();
|
||||
|
||||
private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = [];
|
||||
private nonDeletedElementsMap: NonDeletedElementsMap =
|
||||
new Map() as NonDeletedElementsMap;
|
||||
private nonDeletedElementsMap = toBrandedType<NonDeletedSceneElementsMap>(
|
||||
new Map(),
|
||||
);
|
||||
private elements: readonly ExcalidrawElement[] = [];
|
||||
private nonDeletedFramesLikes: readonly NonDeleted<ExcalidrawFrameLikeElement>[] =
|
||||
[];
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
ExcalidrawFrameLikeElement,
|
||||
ExcalidrawTextElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../element/types";
|
||||
import {
|
||||
Bounds,
|
||||
@ -248,14 +249,15 @@ export const exportToCanvas = async (
|
||||
files,
|
||||
});
|
||||
|
||||
const elementsMap = toBrandedType<RenderableElementsMap>(
|
||||
arrayToMap(elementsForRender),
|
||||
);
|
||||
|
||||
renderStaticScene({
|
||||
canvas,
|
||||
rc: rough.canvas(canvas),
|
||||
elementsMap,
|
||||
elementsMap: toBrandedType<RenderableElementsMap>(
|
||||
arrayToMap(elementsForRender),
|
||||
),
|
||||
allElementsMap: toBrandedType<NonDeletedSceneElementsMap>(
|
||||
arrayToMap(elements),
|
||||
),
|
||||
visibleElements: elementsForRender,
|
||||
scale,
|
||||
appState: {
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
ExcalidrawTextElement,
|
||||
NonDeletedElementsMap,
|
||||
NonDeletedExcalidrawElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../element/types";
|
||||
import {
|
||||
AppClassProperties,
|
||||
@ -66,6 +67,7 @@ export type StaticSceneRenderConfig = {
|
||||
canvas: HTMLCanvasElement;
|
||||
rc: RoughCanvas;
|
||||
elementsMap: RenderableElementsMap;
|
||||
allElementsMap: NonDeletedSceneElementsMap;
|
||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||
scale: number;
|
||||
appState: StaticCanvasAppState;
|
||||
|
@ -16,6 +16,7 @@ import { KEYS } from "./keys";
|
||||
import { rangeIntersection, rangesOverlap, rotatePoint } from "./math";
|
||||
import { getVisibleAndNonSelectedElements } from "./scene/selection";
|
||||
import { AppState, KeyboardModifiersObject, Point } from "./types";
|
||||
import { arrayToMap } from "./utils";
|
||||
|
||||
const SNAP_DISTANCE = 8;
|
||||
|
||||
@ -286,7 +287,10 @@ export const getVisibleGaps = (
|
||||
appState,
|
||||
);
|
||||
|
||||
const referenceBounds = getMaximumGroups(referenceElements)
|
||||
const referenceBounds = getMaximumGroups(
|
||||
referenceElements,
|
||||
arrayToMap(elements),
|
||||
)
|
||||
.filter(
|
||||
(elementsGroup) =>
|
||||
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
||||
@ -572,7 +576,7 @@ export const getReferenceSnapPoints = (
|
||||
appState,
|
||||
);
|
||||
|
||||
return getMaximumGroups(referenceElements)
|
||||
return getMaximumGroups(referenceElements, arrayToMap(elements))
|
||||
.filter(
|
||||
(elementsGroup) =>
|
||||
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
import * as textElementUtils from "../element/textElement";
|
||||
import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
|
||||
import { vi } from "vitest";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
||||
const renderInteractiveScene = vi.spyOn(Renderer, "renderInteractiveScene");
|
||||
const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene");
|
||||
@ -307,6 +308,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const midPointsWithSharpEdge = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
@ -320,6 +322,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const midPointsWithRoundEdge = LinearElementEditor.getEditorMidPoints(
|
||||
h.elements[0] as ExcalidrawLinearElement,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
expect(midPointsWithRoundEdge[0]).not.toEqual(midPointsWithSharpEdge[0]);
|
||||
@ -351,7 +354,11 @@ describe("Test Linear Elements", () => {
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
||||
expect([line.x, line.y]).toEqual(points[0]);
|
||||
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
const startPoint = centerPoint(points[0], midPoints[0] as Point);
|
||||
const deltaX = 50;
|
||||
@ -373,6 +380,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
expect(midPoints[0]).not.toEqual(newMidPoints[0]);
|
||||
@ -458,7 +466,11 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
it("should update only the first segment midpoint when its point is dragged", async () => {
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
const hitCoords: Point = [points[0][0], points[0][1]];
|
||||
|
||||
@ -478,6 +490,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
@ -487,7 +500,11 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
it("should hide midpoints in the segment when points moved close", async () => {
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
const hitCoords: Point = [points[0][0], points[0][1]];
|
||||
|
||||
@ -507,6 +524,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
// This midpoint is hidden since the points are too close
|
||||
@ -526,7 +544,11 @@ describe("Test Linear Elements", () => {
|
||||
]);
|
||||
expect(line.points.length).toEqual(4);
|
||||
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
// delete 3rd point
|
||||
deletePoint(points[2]);
|
||||
@ -538,6 +560,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
expect(newMidPoints.length).toEqual(2);
|
||||
@ -615,7 +638,11 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
it("should update all the midpoints when its point is dragged", async () => {
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
const hitCoords: Point = [points[0][0], points[0][1]];
|
||||
|
||||
@ -630,6 +657,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
@ -651,7 +679,11 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
it("should hide midpoints in the segment when points moved close", async () => {
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
|
||||
const hitCoords: Point = [points[0][0], points[0][1]];
|
||||
|
||||
@ -671,6 +703,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
// This mid point is hidden due to point being too close
|
||||
@ -685,7 +718,11 @@ describe("Test Linear Elements", () => {
|
||||
]);
|
||||
expect(line.points.length).toEqual(4);
|
||||
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state);
|
||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
||||
|
||||
// delete 3rd point
|
||||
@ -694,6 +731,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
line,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.state,
|
||||
);
|
||||
expect(newMidPoints.length).toEqual(2);
|
||||
@ -762,7 +800,7 @@ describe("Test Linear Elements", () => {
|
||||
type: "text",
|
||||
x: 0,
|
||||
y: 0,
|
||||
text: wrapText(text, font, getBoundTextMaxWidth(container)),
|
||||
text: wrapText(text, font, getBoundTextMaxWidth(container, null)),
|
||||
containerId: container.id,
|
||||
width: 30,
|
||||
height: 20,
|
||||
@ -986,8 +1024,13 @@ describe("Test Linear Elements", () => {
|
||||
collaboration made
|
||||
easy"
|
||||
`);
|
||||
expect(LinearElementEditor.getElementAbsoluteCoords(container, true))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
LinearElementEditor.getElementAbsoluteCoords(
|
||||
container,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
true,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
20,
|
||||
20,
|
||||
@ -1020,8 +1063,13 @@ describe("Test Linear Elements", () => {
|
||||
"Online whiteboard
|
||||
collaboration made easy"
|
||||
`);
|
||||
expect(LinearElementEditor.getElementAbsoluteCoords(container, true))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
LinearElementEditor.getElementAbsoluteCoords(
|
||||
container,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
true,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
20,
|
||||
35,
|
||||
@ -1121,7 +1169,11 @@ describe("Test Linear Elements", () => {
|
||||
expect(rect.x).toBe(400);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(
|
||||
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
|
||||
wrapText(
|
||||
textElement.originalText,
|
||||
font,
|
||||
getBoundTextMaxWidth(arrow, null),
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
"Online whiteboard
|
||||
collaboration made easy"
|
||||
@ -1140,11 +1192,17 @@ describe("Test Linear Elements", () => {
|
||||
expect(rect.x).toBe(200);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(handleBindTextResizeSpy).toHaveBeenCalledWith(
|
||||
h.elements[1],
|
||||
h.elements[0],
|
||||
arrayToMap(h.elements),
|
||||
"nw",
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
|
||||
wrapText(
|
||||
textElement.originalText,
|
||||
font,
|
||||
getBoundTextMaxWidth(arrow, null),
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
"Online whiteboard
|
||||
collaboration made
|
||||
|
Loading…
Reference in New Issue
Block a user