diff options
Diffstat (limited to 'packages/excalidraw/components/Stats/MultiAngle.tsx')
| -rw-r--r-- | packages/excalidraw/components/Stats/MultiAngle.tsx | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/packages/excalidraw/components/Stats/MultiAngle.tsx b/packages/excalidraw/components/Stats/MultiAngle.tsx new file mode 100644 index 0000000..d9a7882 --- /dev/null +++ b/packages/excalidraw/components/Stats/MultiAngle.tsx @@ -0,0 +1,136 @@ +import { mutateElement } from "../../element/mutateElement"; +import { getBoundTextElement } from "../../element/textElement"; +import { isArrowElement } from "../../element/typeChecks"; +import type { ExcalidrawElement } from "../../element/types"; +import { isInGroup } from "../../groups"; +import type Scene from "../../scene/Scene"; +import { angleIcon } from "../icons"; +import DragInput from "./DragInput"; +import type { DragInputCallbackType } from "./DragInput"; +import { getStepSizedValue, isPropertyEditable } from "./utils"; +import type { AppState } from "../../types"; +import type { Degrees } from "@excalidraw/math"; +import { degreesToRadians, radiansToDegrees } from "@excalidraw/math"; + +interface MultiAngleProps { + elements: readonly ExcalidrawElement[]; + scene: Scene; + appState: AppState; + property: "angle"; +} + +const STEP_SIZE = 15; + +const handleDegreeChange: DragInputCallbackType< + MultiAngleProps["property"] +> = ({ + accumulatedChange, + originalElements, + shouldChangeByStepSize, + nextValue, + property, + scene, +}) => { + const elementsMap = scene.getNonDeletedElementsMap(); + const editableLatestIndividualElements = originalElements + .map((el) => elementsMap.get(el.id)) + .filter((el) => el && !isInGroup(el) && isPropertyEditable(el, property)); + const editableOriginalIndividualElements = originalElements.filter( + (el) => !isInGroup(el) && isPropertyEditable(el, property), + ); + + if (nextValue !== undefined) { + const nextAngle = degreesToRadians(nextValue as Degrees); + + for (const element of editableLatestIndividualElements) { + if (!element) { + continue; + } + mutateElement( + element, + { + angle: nextAngle, + }, + false, + ); + + const boundTextElement = getBoundTextElement(element, elementsMap); + if (boundTextElement && !isArrowElement(element)) { + mutateElement(boundTextElement, { angle: nextAngle }, false); + } + } + + scene.triggerUpdate(); + + return; + } + + for (let i = 0; i < editableLatestIndividualElements.length; i++) { + const latestElement = editableLatestIndividualElements[i]; + if (!latestElement) { + continue; + } + const originalElement = editableOriginalIndividualElements[i]; + const originalAngleInDegrees = + Math.round(radiansToDegrees(originalElement.angle) * 100) / 100; + const changeInDegrees = Math.round(accumulatedChange); + let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360; + if (shouldChangeByStepSize) { + nextAngleInDegrees = getStepSizedValue(nextAngleInDegrees, STEP_SIZE); + } + + nextAngleInDegrees = + nextAngleInDegrees < 0 ? nextAngleInDegrees + 360 : nextAngleInDegrees; + + const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees); + + mutateElement( + latestElement, + { + angle: nextAngle, + }, + false, + ); + + const boundTextElement = getBoundTextElement(latestElement, elementsMap); + if (boundTextElement && !isArrowElement(latestElement)) { + mutateElement(boundTextElement, { angle: nextAngle }, false); + } + } + scene.triggerUpdate(); +}; + +const MultiAngle = ({ + elements, + scene, + appState, + property, +}: MultiAngleProps) => { + const editableLatestIndividualElements = elements.filter( + (el) => !isInGroup(el) && isPropertyEditable(el, "angle"), + ); + const angles = editableLatestIndividualElements.map( + (el) => Math.round((radiansToDegrees(el.angle) % 360) * 100) / 100, + ); + const value = new Set(angles).size === 1 ? angles[0] : "Mixed"; + + const editable = editableLatestIndividualElements.some((el) => + isPropertyEditable(el, "angle"), + ); + + return ( + <DragInput + label="A" + icon={angleIcon} + value={value} + elements={elements} + dragInputCallback={handleDegreeChange} + editable={editable} + appState={appState} + scene={scene} + property={property} + /> + ); +}; + +export default MultiAngle; |
