aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/Stats/MultiAngle.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/excalidraw/components/Stats/MultiAngle.tsx')
-rw-r--r--packages/excalidraw/components/Stats/MultiAngle.tsx136
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;