diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/components/Stats/utils.ts | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/excalidraw/components/Stats/utils.ts')
| -rw-r--r-- | packages/excalidraw/components/Stats/utils.ts | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/packages/excalidraw/components/Stats/utils.ts b/packages/excalidraw/components/Stats/utils.ts new file mode 100644 index 0000000..61555ba --- /dev/null +++ b/packages/excalidraw/components/Stats/utils.ts @@ -0,0 +1,219 @@ +import type { Radians } from "@excalidraw/math"; +import { pointFrom, pointRotateRads } from "@excalidraw/math"; +import { + bindOrUnbindLinearElements, + updateBoundElements, +} from "../../element/binding"; +import { mutateElement } from "../../element/mutateElement"; +import { getBoundTextElement } from "../../element/textElement"; +import { + isFrameLikeElement, + isLinearElement, + isTextElement, +} from "../../element/typeChecks"; +import type { + ElementsMap, + ExcalidrawElement, + NonDeletedExcalidrawElement, + NonDeletedSceneElementsMap, +} from "../../element/types"; +import { + getSelectedGroupIds, + getElementsInGroup, + isInGroup, +} from "../../groups"; +import type Scene from "../../scene/Scene"; +import type { AppState } from "../../types"; + +export type StatsInputProperty = + | "x" + | "y" + | "width" + | "height" + | "angle" + | "fontSize" + | "gridStep"; + +export const SMALLEST_DELTA = 0.01; + +export const isPropertyEditable = ( + element: ExcalidrawElement, + property: keyof ExcalidrawElement, +) => { + if (property === "height" && isTextElement(element)) { + return false; + } + if (property === "width" && isTextElement(element)) { + return false; + } + if (property === "angle" && isFrameLikeElement(element)) { + return false; + } + return true; +}; + +export const getStepSizedValue = (value: number, stepSize: number) => { + const v = value + stepSize / 2; + return v - (v % stepSize); +}; + +export type AtomicUnit = Record<string, true>; +export const getElementsInAtomicUnit = ( + atomicUnit: AtomicUnit, + elementsMap: ElementsMap, + originalElementsMap?: ElementsMap, +) => { + return Object.keys(atomicUnit) + .map((id) => ({ + original: (originalElementsMap ?? elementsMap).get(id), + latest: elementsMap.get(id), + })) + .filter((el) => el.original !== undefined && el.latest !== undefined) as { + original: NonDeletedExcalidrawElement; + latest: NonDeletedExcalidrawElement; + }[]; +}; + +export const newOrigin = ( + x1: number, + y1: number, + w1: number, + h1: number, + w2: number, + h2: number, + angle: number, +) => { + /** + * The formula below is the result of solving + * rotate(x1, y1, cx1, cy1, angle) = rotate(x2, y2, cx2, cy2, angle) + * where rotate is the function defined in math.ts + * + * This is so that the new origin (x2, y2), + * when rotated against the new center (cx2, cy2), + * coincides with (x1, y1) rotated against (cx1, cy1) + * + * The reason for doing this computation is so the element's top left corner + * on the canvas remains fixed after any changes in its dimension. + */ + + return { + x: + x1 + + (w1 - w2) / 2 + + ((w2 - w1) / 2) * Math.cos(angle) + + ((h1 - h2) / 2) * Math.sin(angle), + y: + y1 + + (h1 - h2) / 2 + + ((w2 - w1) / 2) * Math.sin(angle) + + ((h2 - h1) / 2) * Math.cos(angle), + }; +}; + +export const moveElement = ( + newTopLeftX: number, + newTopLeftY: number, + originalElement: ExcalidrawElement, + elementsMap: NonDeletedSceneElementsMap, + elements: readonly NonDeletedExcalidrawElement[], + scene: Scene, + originalElementsMap: ElementsMap, + shouldInformMutation = true, +) => { + const latestElement = elementsMap.get(originalElement.id); + if (!latestElement) { + return; + } + const [cx, cy] = [ + originalElement.x + originalElement.width / 2, + originalElement.y + originalElement.height / 2, + ]; + const [topLeftX, topLeftY] = pointRotateRads( + pointFrom(originalElement.x, originalElement.y), + pointFrom(cx, cy), + originalElement.angle, + ); + + const changeInX = newTopLeftX - topLeftX; + const changeInY = newTopLeftY - topLeftY; + + const [x, y] = pointRotateRads( + pointFrom(newTopLeftX, newTopLeftY), + pointFrom(cx + changeInX, cy + changeInY), + -originalElement.angle as Radians, + ); + + mutateElement( + latestElement, + { + x, + y, + }, + shouldInformMutation, + ); + updateBindings(latestElement, elementsMap, elements, scene); + + const boundTextElement = getBoundTextElement( + originalElement, + originalElementsMap, + ); + if (boundTextElement) { + const latestBoundTextElement = elementsMap.get(boundTextElement.id); + latestBoundTextElement && + mutateElement( + latestBoundTextElement, + { + x: boundTextElement.x + changeInX, + y: boundTextElement.y + changeInY, + }, + shouldInformMutation, + ); + } +}; + +export const getAtomicUnits = ( + targetElements: readonly ExcalidrawElement[], + appState: AppState, +) => { + const selectedGroupIds = getSelectedGroupIds(appState); + const _atomicUnits = selectedGroupIds.map((gid) => { + return getElementsInGroup(targetElements, gid).reduce((acc, el) => { + acc[el.id] = true; + return acc; + }, {} as AtomicUnit); + }); + targetElements + .filter((el) => !isInGroup(el)) + .forEach((el) => { + _atomicUnits.push({ + [el.id]: true, + }); + }); + return _atomicUnits; +}; + +export const updateBindings = ( + latestElement: ExcalidrawElement, + elementsMap: NonDeletedSceneElementsMap, + elements: readonly NonDeletedExcalidrawElement[], + scene: Scene, + options?: { + simultaneouslyUpdated?: readonly ExcalidrawElement[]; + newSize?: { width: number; height: number }; + zoom?: AppState["zoom"]; + }, +) => { + if (isLinearElement(latestElement)) { + bindOrUnbindLinearElements( + [latestElement], + elementsMap, + elements, + scene, + true, + [], + options?.zoom, + ); + } else { + updateBoundElements(latestElement, elementsMap, options); + } +}; |
