mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
refactor: decoupling global Scene state part-1 (#7577)
This commit is contained in:
parent
740a165452
commit
0415c616b1
31 changed files with 630 additions and 384 deletions
|
@ -5,6 +5,7 @@ import {
|
|||
ExcalidrawFreeDrawElement,
|
||||
NonDeleted,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ElementsMapOrArray,
|
||||
} from "./types";
|
||||
import { distance2d, rotate, rotatePoint } from "../math";
|
||||
import rough from "roughjs/bin/rough";
|
||||
|
@ -161,7 +162,11 @@ export const getElementAbsoluteCoords = (
|
|||
includeBoundText,
|
||||
);
|
||||
} else if (isTextElement(element)) {
|
||||
const container = getContainerElement(element);
|
||||
const elementsMap =
|
||||
Scene.getScene(element)?.getElementsMapIncludingDeleted();
|
||||
const container = elementsMap
|
||||
? getContainerElement(element, elementsMap)
|
||||
: null;
|
||||
if (isArrowElement(container)) {
|
||||
const coords = LinearElementEditor.getBoundTextElementPosition(
|
||||
container,
|
||||
|
@ -729,10 +734,8 @@ const getLinearElementRotatedBounds = (
|
|||
export const getElementBounds = (element: ExcalidrawElement): Bounds => {
|
||||
return ElementBounds.getBounds(element);
|
||||
};
|
||||
export const getCommonBounds = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
): Bounds => {
|
||||
if (!elements.length) {
|
||||
export const getCommonBounds = (elements: ElementsMapOrArray): Bounds => {
|
||||
if ("size" in elements ? !elements.size : !elements.length) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
|
||||
|
|
|
@ -5,17 +5,12 @@ import { ExcalidrawProps } from "../types";
|
|||
import { getFontString, updateActiveTool } from "../utils";
|
||||
import { setCursorForShape } from "../cursor";
|
||||
import { newTextElement } from "./newElement";
|
||||
import { getContainerElement, wrapText } from "./textElement";
|
||||
import {
|
||||
isFrameLikeElement,
|
||||
isIframeElement,
|
||||
isIframeLikeElement,
|
||||
} from "./typeChecks";
|
||||
import { wrapText } from "./textElement";
|
||||
import { isIframeElement } from "./typeChecks";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawIframeLikeElement,
|
||||
IframeData,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./types";
|
||||
|
||||
const embeddedLinkCache = new Map<string, IframeData>();
|
||||
|
@ -217,21 +212,6 @@ export const getEmbedLink = (
|
|||
return { link, intrinsicSize: aspectRatio, type };
|
||||
};
|
||||
|
||||
export const isIframeLikeOrItsLabel = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
): Boolean => {
|
||||
if (isIframeLikeElement(element)) {
|
||||
return true;
|
||||
}
|
||||
if (element.type === "text") {
|
||||
const container = getContainerElement(element);
|
||||
if (container && isFrameLikeElement(container)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const createPlaceholderEmbeddableLabel = (
|
||||
element: ExcalidrawIframeLikeElement,
|
||||
): ExcalidrawElement => {
|
||||
|
|
|
@ -31,7 +31,6 @@ import { getElementAbsoluteCoords } from ".";
|
|||
import { adjustXYWithRotation } from "../math";
|
||||
import { getResizedElementAbsoluteCoords } from "./bounds";
|
||||
import {
|
||||
getContainerElement,
|
||||
measureText,
|
||||
normalizeText,
|
||||
wrapText,
|
||||
|
@ -333,12 +332,12 @@ const getAdjustedDimensions = (
|
|||
|
||||
export const refreshTextDimensions = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
text = textElement.text,
|
||||
) => {
|
||||
if (textElement.isDeleted) {
|
||||
return;
|
||||
}
|
||||
const container = getContainerElement(textElement);
|
||||
if (container) {
|
||||
text = wrapText(
|
||||
text,
|
||||
|
@ -352,6 +351,7 @@ export const refreshTextDimensions = (
|
|||
|
||||
export const updateTextElement = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
{
|
||||
text,
|
||||
isDeleted,
|
||||
|
@ -365,7 +365,7 @@ export const updateTextElement = (
|
|||
return newElementWith(textElement, {
|
||||
originalText,
|
||||
isDeleted: isDeleted ?? textElement.isDeleted,
|
||||
...refreshTextDimensions(textElement, originalText),
|
||||
...refreshTextDimensions(textElement, container, originalText),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
ExcalidrawElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ExcalidrawImageElement,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
import type { Mutable } from "../utility-types";
|
||||
import {
|
||||
|
@ -41,7 +42,7 @@ import {
|
|||
MaybeTransformHandleType,
|
||||
TransformHandleDirection,
|
||||
} from "./transformHandles";
|
||||
import { AppState, Point, PointerDownState } from "../types";
|
||||
import { Point, PointerDownState } from "../types";
|
||||
import Scene from "../scene/Scene";
|
||||
import {
|
||||
getApproxMinLineWidth,
|
||||
|
@ -68,10 +69,10 @@ export const normalizeAngle = (angle: number): number => {
|
|||
|
||||
// Returns true when transform (resizing/rotation) happened
|
||||
export const transformElements = (
|
||||
pointerDownState: PointerDownState,
|
||||
originalElements: PointerDownState["originalElements"],
|
||||
transformHandleType: MaybeTransformHandleType,
|
||||
selectedElements: readonly NonDeletedExcalidrawElement[],
|
||||
resizeArrowDirection: "origin" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
shouldRotateWithDiscreteAngle: boolean,
|
||||
shouldResizeFromCenter: boolean,
|
||||
shouldMaintainAspectRatio: boolean,
|
||||
|
@ -79,7 +80,6 @@ export const transformElements = (
|
|||
pointerY: number,
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
appState: AppState,
|
||||
) => {
|
||||
if (selectedElements.length === 1) {
|
||||
const [element] = selectedElements;
|
||||
|
@ -89,7 +89,6 @@ export const transformElements = (
|
|||
pointerX,
|
||||
pointerY,
|
||||
shouldRotateWithDiscreteAngle,
|
||||
pointerDownState.originalElements,
|
||||
);
|
||||
updateBoundElements(element);
|
||||
} else if (
|
||||
|
@ -101,6 +100,7 @@ export const transformElements = (
|
|||
) {
|
||||
resizeSingleTextElement(
|
||||
element,
|
||||
elementsMap,
|
||||
transformHandleType,
|
||||
shouldResizeFromCenter,
|
||||
pointerX,
|
||||
|
@ -109,9 +109,10 @@ export const transformElements = (
|
|||
updateBoundElements(element);
|
||||
} else if (transformHandleType) {
|
||||
resizeSingleElement(
|
||||
pointerDownState.originalElements,
|
||||
originalElements,
|
||||
shouldMaintainAspectRatio,
|
||||
element,
|
||||
elementsMap,
|
||||
transformHandleType,
|
||||
shouldResizeFromCenter,
|
||||
pointerX,
|
||||
|
@ -123,7 +124,7 @@ export const transformElements = (
|
|||
} else if (selectedElements.length > 1) {
|
||||
if (transformHandleType === "rotation") {
|
||||
rotateMultipleElements(
|
||||
pointerDownState,
|
||||
originalElements,
|
||||
selectedElements,
|
||||
pointerX,
|
||||
pointerY,
|
||||
|
@ -139,8 +140,9 @@ export const transformElements = (
|
|||
transformHandleType === "se"
|
||||
) {
|
||||
resizeMultipleElements(
|
||||
pointerDownState,
|
||||
originalElements,
|
||||
selectedElements,
|
||||
elementsMap,
|
||||
transformHandleType,
|
||||
shouldResizeFromCenter,
|
||||
pointerX,
|
||||
|
@ -157,7 +159,6 @@ const rotateSingleElement = (
|
|||
pointerX: number,
|
||||
pointerY: number,
|
||||
shouldRotateWithDiscreteAngle: boolean,
|
||||
originalElements: Map<string, NonDeleted<ExcalidrawElement>>,
|
||||
) => {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const cx = (x1 + x2) / 2;
|
||||
|
@ -207,6 +208,7 @@ const rescalePointsInElement = (
|
|||
|
||||
const measureFontSizeFromWidth = (
|
||||
element: NonDeleted<ExcalidrawTextElement>,
|
||||
elementsMap: ElementsMap,
|
||||
nextWidth: number,
|
||||
nextHeight: number,
|
||||
): { size: number; baseline: number } | null => {
|
||||
|
@ -215,7 +217,7 @@ const measureFontSizeFromWidth = (
|
|||
|
||||
const hasContainer = isBoundToContainer(element);
|
||||
if (hasContainer) {
|
||||
const container = getContainerElement(element);
|
||||
const container = getContainerElement(element, elementsMap);
|
||||
if (container) {
|
||||
width = getBoundTextMaxWidth(container);
|
||||
}
|
||||
|
@ -257,6 +259,7 @@ const getSidesForTransformHandle = (
|
|||
|
||||
const resizeSingleTextElement = (
|
||||
element: NonDeleted<ExcalidrawTextElement>,
|
||||
elementsMap: ElementsMap,
|
||||
transformHandleType: "nw" | "ne" | "sw" | "se",
|
||||
shouldResizeFromCenter: boolean,
|
||||
pointerX: number,
|
||||
|
@ -303,7 +306,12 @@ const resizeSingleTextElement = (
|
|||
if (scale > 0) {
|
||||
const nextWidth = element.width * scale;
|
||||
const nextHeight = element.height * scale;
|
||||
const metrics = measureFontSizeFromWidth(element, nextWidth, nextHeight);
|
||||
const metrics = measureFontSizeFromWidth(
|
||||
element,
|
||||
elementsMap,
|
||||
nextWidth,
|
||||
nextHeight,
|
||||
);
|
||||
if (metrics === null) {
|
||||
return;
|
||||
}
|
||||
|
@ -342,6 +350,7 @@ export const resizeSingleElement = (
|
|||
originalElements: PointerDownState["originalElements"],
|
||||
shouldMaintainAspectRatio: boolean,
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
transformHandleDirection: TransformHandleDirection,
|
||||
shouldResizeFromCenter: boolean,
|
||||
pointerX: number,
|
||||
|
@ -448,6 +457,7 @@ export const resizeSingleElement = (
|
|||
|
||||
const nextFont = measureFontSizeFromWidth(
|
||||
boundTextElement,
|
||||
elementsMap,
|
||||
getBoundTextMaxWidth(updatedElement),
|
||||
getBoundTextMaxHeight(updatedElement, boundTextElement),
|
||||
);
|
||||
|
@ -637,8 +647,9 @@ export const resizeSingleElement = (
|
|||
};
|
||||
|
||||
export const resizeMultipleElements = (
|
||||
pointerDownState: PointerDownState,
|
||||
originalElements: PointerDownState["originalElements"],
|
||||
selectedElements: readonly NonDeletedExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
transformHandleType: "nw" | "ne" | "sw" | "se",
|
||||
shouldResizeFromCenter: boolean,
|
||||
pointerX: number,
|
||||
|
@ -658,7 +669,7 @@ export const resizeMultipleElements = (
|
|||
}[],
|
||||
element,
|
||||
) => {
|
||||
const origElement = pointerDownState.originalElements.get(element.id);
|
||||
const origElement = originalElements.get(element.id);
|
||||
if (origElement) {
|
||||
acc.push({ orig: origElement, latest: element });
|
||||
}
|
||||
|
@ -679,7 +690,7 @@ export const resizeMultipleElements = (
|
|||
if (!textId) {
|
||||
return acc;
|
||||
}
|
||||
const text = pointerDownState.originalElements.get(textId) ?? null;
|
||||
const text = originalElements.get(textId) ?? null;
|
||||
if (!isBoundToContainer(text)) {
|
||||
return acc;
|
||||
}
|
||||
|
@ -825,7 +836,12 @@ export const resizeMultipleElements = (
|
|||
}
|
||||
|
||||
if (isTextElement(orig)) {
|
||||
const metrics = measureFontSizeFromWidth(orig, width, height);
|
||||
const metrics = measureFontSizeFromWidth(
|
||||
orig,
|
||||
elementsMap,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
if (!metrics) {
|
||||
return;
|
||||
}
|
||||
|
@ -833,7 +849,7 @@ export const resizeMultipleElements = (
|
|||
update.baseline = metrics.baseline;
|
||||
}
|
||||
|
||||
const boundTextElement = pointerDownState.originalElements.get(
|
||||
const boundTextElement = originalElements.get(
|
||||
getBoundTextElementId(orig) ?? "",
|
||||
) as ExcalidrawTextElementWithContainer | undefined;
|
||||
|
||||
|
@ -884,7 +900,7 @@ export const resizeMultipleElements = (
|
|||
};
|
||||
|
||||
const rotateMultipleElements = (
|
||||
pointerDownState: PointerDownState,
|
||||
originalElements: PointerDownState["originalElements"],
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
pointerX: number,
|
||||
pointerY: number,
|
||||
|
@ -906,8 +922,7 @@ const rotateMultipleElements = (
|
|||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
const origAngle =
|
||||
pointerDownState.originalElements.get(element.id)?.angle ??
|
||||
element.angle;
|
||||
originalElements.get(element.id)?.angle ?? element.angle;
|
||||
const [rotatedCX, rotatedCY] = rotate(
|
||||
cx,
|
||||
cy,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { getFontString, arrayToMap, isTestEnv, normalizeEOL } from "../utils";
|
||||
import {
|
||||
ElementsMap,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawElementType,
|
||||
ExcalidrawTextContainer,
|
||||
|
@ -682,17 +683,15 @@ export const getBoundTextElement = (element: ExcalidrawElement | null) => {
|
|||
};
|
||||
|
||||
export const getContainerElement = (
|
||||
element:
|
||||
| (ExcalidrawElement & {
|
||||
containerId: ExcalidrawElement["id"] | null;
|
||||
})
|
||||
| null,
|
||||
) => {
|
||||
element: ExcalidrawTextElement | null,
|
||||
elementsMap: ElementsMap,
|
||||
): ExcalidrawTextContainer | null => {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
if (element.containerId) {
|
||||
return Scene.getScene(element)?.getElement(element.containerId) || null;
|
||||
return (elementsMap.get(element.containerId) ||
|
||||
null) as ExcalidrawTextContainer | null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
@ -752,28 +751,16 @@ export const getContainerCoords = (container: NonDeletedExcalidrawElement) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const getTextElementAngle = (textElement: ExcalidrawTextElement) => {
|
||||
const container = getContainerElement(textElement);
|
||||
export const getTextElementAngle = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
) => {
|
||||
if (!container || isArrowElement(container)) {
|
||||
return textElement.angle;
|
||||
}
|
||||
return container.angle;
|
||||
};
|
||||
|
||||
export const getBoundTextElementOffset = (
|
||||
boundTextElement: ExcalidrawTextElement | null,
|
||||
) => {
|
||||
const container = getContainerElement(boundTextElement);
|
||||
if (!container || !boundTextElement) {
|
||||
return 0;
|
||||
}
|
||||
if (isArrowElement(container)) {
|
||||
return BOUND_TEXT_PADDING * 8;
|
||||
}
|
||||
|
||||
return BOUND_TEXT_PADDING;
|
||||
};
|
||||
|
||||
export const getBoundTextElementPosition = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
|
@ -788,12 +775,12 @@ export const getBoundTextElementPosition = (
|
|||
|
||||
export const shouldAllowVerticalAlign = (
|
||||
selectedElements: NonDeletedExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
return selectedElements.some((element) => {
|
||||
const hasBoundContainer = isBoundToContainer(element);
|
||||
if (hasBoundContainer) {
|
||||
const container = getContainerElement(element);
|
||||
if (isTextElement(element) && isArrowElement(container)) {
|
||||
if (isBoundToContainer(element)) {
|
||||
const container = getContainerElement(element, elementsMap);
|
||||
if (isArrowElement(container)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -804,12 +791,12 @@ export const shouldAllowVerticalAlign = (
|
|||
|
||||
export const suppportsHorizontalAlign = (
|
||||
selectedElements: NonDeletedExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
return selectedElements.some((element) => {
|
||||
const hasBoundContainer = isBoundToContainer(element);
|
||||
if (hasBoundContainer) {
|
||||
const container = getContainerElement(element);
|
||||
if (isTextElement(element) && isArrowElement(container)) {
|
||||
if (isBoundToContainer(element)) {
|
||||
const container = getContainerElement(element, elementsMap);
|
||||
if (isArrowElement(container)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -153,7 +153,10 @@ export const textWysiwyg = ({
|
|||
if (updatedTextElement && isTextElement(updatedTextElement)) {
|
||||
let coordX = updatedTextElement.x;
|
||||
let coordY = updatedTextElement.y;
|
||||
const container = getContainerElement(updatedTextElement);
|
||||
const container = getContainerElement(
|
||||
updatedTextElement,
|
||||
app.scene.getElementsMapIncludingDeleted(),
|
||||
);
|
||||
let maxWidth = updatedTextElement.width;
|
||||
|
||||
let maxHeight = updatedTextElement.height;
|
||||
|
@ -277,7 +280,7 @@ export const textWysiwyg = ({
|
|||
transform: getTransform(
|
||||
textElementWidth,
|
||||
textElementHeight,
|
||||
getTextElementAngle(updatedTextElement),
|
||||
getTextElementAngle(updatedTextElement, container),
|
||||
appState,
|
||||
maxWidth,
|
||||
editorMaxHeight,
|
||||
|
@ -348,7 +351,10 @@ export const textWysiwyg = ({
|
|||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const container = getContainerElement(element);
|
||||
const container = getContainerElement(
|
||||
element,
|
||||
app.scene.getElementsMapIncludingDeleted(),
|
||||
);
|
||||
|
||||
const font = getFontString({
|
||||
fontSize: app.state.currentItemFontSize,
|
||||
|
@ -528,7 +534,10 @@ export const textWysiwyg = ({
|
|||
return;
|
||||
}
|
||||
let text = editable.value;
|
||||
const container = getContainerElement(updateElement);
|
||||
const container = getContainerElement(
|
||||
updateElement,
|
||||
app.scene.getElementsMapIncludingDeleted(),
|
||||
);
|
||||
|
||||
if (container) {
|
||||
text = updateElement.text;
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
THEME,
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import { MarkNonNullable, ValueOf } from "../utility-types";
|
||||
import { MakeBrand, MarkNonNullable, ValueOf } from "../utility-types";
|
||||
import { MagicCacheData } from "../data/magic";
|
||||
|
||||
export type ChartType = "bar" | "line";
|
||||
|
@ -254,3 +254,31 @@ export type ExcalidrawFreeDrawElement = _ExcalidrawElementBase &
|
|||
export type FileId = string & { _brand: "FileId" };
|
||||
|
||||
export type ExcalidrawElementType = ExcalidrawElement["type"];
|
||||
|
||||
/**
|
||||
* Map of excalidraw elements.
|
||||
* Unspecified whether deleted or non-deleted.
|
||||
* Can be a subset of Scene elements.
|
||||
*/
|
||||
export type ElementsMap = Map<ExcalidrawElement["id"], ExcalidrawElement>;
|
||||
|
||||
/**
|
||||
* Map of non-deleted elements.
|
||||
* Can be a subset of Scene elements.
|
||||
*/
|
||||
export type NonDeletedElementsMap = Map<
|
||||
ExcalidrawElement["id"],
|
||||
NonDeletedExcalidrawElement
|
||||
> &
|
||||
MakeBrand<"NonDeletedElementsMap">;
|
||||
|
||||
/**
|
||||
* Map of all excalidraw Scene elements, including deleted.
|
||||
* Not a subset. Use this type when you need access to current Scene elements.
|
||||
*/
|
||||
export type SceneElementsMap = Map<ExcalidrawElement["id"], ExcalidrawElement> &
|
||||
MakeBrand<"SceneElementsMap">;
|
||||
|
||||
export type ElementsMapOrArray =
|
||||
| readonly ExcalidrawElement[]
|
||||
| Readonly<ElementsMap>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue