Explorar el Código

feat: Support updating text properties by clicking on container (#4499)

Aakansha Doshi hace 3 años
padre
commit
dd8e465304

+ 61 - 40
src/actions/actionProperties.tsx

@@ -57,21 +57,27 @@ import {
   canChangeSharpness,
   canHaveArrowheads,
   getCommonAttributeOfSelectedElements,
+  getSelectedElements,
   getTargetElements,
   isSomeElementSelected,
 } from "../scene";
 import { hasStrokeColor } from "../scene/comparisons";
 import Scene from "../scene/Scene";
+import { arrayToMap } from "../utils";
 import { register } from "./register";
 
 const changeProperty = (
   elements: readonly ExcalidrawElement[],
   appState: AppState,
   callback: (element: ExcalidrawElement) => ExcalidrawElement,
+  includeBoundText = false,
 ) => {
+  const selectedElementIds = arrayToMap(
+    getSelectedElements(elements, appState, includeBoundText),
+  );
   return elements.map((element) => {
     if (
-      appState.selectedElementIds[element.id] ||
+      selectedElementIds.get(element.id) ||
       element.id === appState.editingElement?.id
     ) {
       return callback(element);
@@ -427,21 +433,26 @@ export const actionChangeFontSize = register({
   name: "changeFontSize",
   perform: (elements, appState, value) => {
     return {
-      elements: changeProperty(elements, appState, (el) => {
-        if (isTextElement(el)) {
-          const element: ExcalidrawTextElement = newElementWith(el, {
-            fontSize: value,
-          });
-          let container = null;
-          if (el.containerId) {
-            container = Scene.getScene(el)!.getElement(el.containerId);
+      elements: changeProperty(
+        elements,
+        appState,
+        (el) => {
+          if (isTextElement(el)) {
+            const element: ExcalidrawTextElement = newElementWith(el, {
+              fontSize: value,
+            });
+            let container = null;
+            if (el.containerId) {
+              container = Scene.getScene(el)!.getElement(el.containerId);
+            }
+            redrawTextBoundingBox(element, container, appState);
+            return element;
           }
-          redrawTextBoundingBox(element, container);
-          return element;
-        }
 
-        return el;
-      }),
+          return el;
+        },
+        true,
+      ),
       appState: {
         ...appState,
         currentItemFontSize: value,
@@ -492,21 +503,26 @@ export const actionChangeFontFamily = register({
   name: "changeFontFamily",
   perform: (elements, appState, value) => {
     return {
-      elements: changeProperty(elements, appState, (el) => {
-        if (isTextElement(el)) {
-          const element: ExcalidrawTextElement = newElementWith(el, {
-            fontFamily: value,
-          });
-          let container = null;
-          if (el.containerId) {
-            container = Scene.getScene(el)!.getElement(el.containerId);
+      elements: changeProperty(
+        elements,
+        appState,
+        (el) => {
+          if (isTextElement(el)) {
+            const element: ExcalidrawTextElement = newElementWith(el, {
+              fontFamily: value,
+            });
+            let container = null;
+            if (el.containerId) {
+              container = Scene.getScene(el)!.getElement(el.containerId);
+            }
+            redrawTextBoundingBox(element, container, appState);
+            return element;
           }
-          redrawTextBoundingBox(element, container);
-          return element;
-        }
 
-        return el;
-      }),
+          return el;
+        },
+        true,
+      ),
       appState: {
         ...appState,
         currentItemFontFamily: value,
@@ -560,21 +576,26 @@ export const actionChangeTextAlign = register({
   name: "changeTextAlign",
   perform: (elements, appState, value) => {
     return {
-      elements: changeProperty(elements, appState, (el) => {
-        if (isTextElement(el)) {
-          const element: ExcalidrawTextElement = newElementWith(el, {
-            textAlign: value,
-          });
-          let container = null;
-          if (el.containerId) {
-            container = Scene.getScene(el)!.getElement(el.containerId);
+      elements: changeProperty(
+        elements,
+        appState,
+        (el) => {
+          if (isTextElement(el)) {
+            const element: ExcalidrawTextElement = newElementWith(el, {
+              textAlign: value,
+            });
+            let container = null;
+            if (el.containerId) {
+              container = Scene.getScene(el)!.getElement(el.containerId);
+            }
+            redrawTextBoundingBox(element, container, appState);
+            return element;
           }
-          redrawTextBoundingBox(element, container);
-          return element;
-        }
 
-        return el;
-      }),
+          return el;
+        },
+        true,
+      ),
       appState: {
         ...appState,
         currentItemTextAlign: value,

+ 5 - 1
src/actions/actionStyles.ts

@@ -71,7 +71,11 @@ export const actionPasteStyles = register({
                 element.containerId,
               );
             }
-            redrawTextBoundingBox(element as ExcalidrawTextElement, container);
+            redrawTextBoundingBox(
+              element as ExcalidrawTextElement,
+              container,
+              appState,
+            );
           }
           return newElement;
         }

+ 28 - 0
src/element/textElement.ts

@@ -10,24 +10,52 @@ import { mutateElement } from "./mutateElement";
 import { BOUND_TEXT_PADDING } from "../constants";
 import { MaybeTransformHandleType } from "./transformHandles";
 import Scene from "../scene/Scene";
+import { AppState } from "../types";
+import { isTextElement } from ".";
 
 export const redrawTextBoundingBox = (
   element: ExcalidrawTextElement,
   container: ExcalidrawElement | null,
+  appState: AppState,
 ) => {
   const maxWidth = container
     ? container.width - BOUND_TEXT_PADDING * 2
     : undefined;
+  let text = element.originalText;
+
+  // Call wrapText only when updating text properties
+  // By clicking on the container
+  if (container && !isTextElement(appState.editingElement)) {
+    text = wrapText(
+      element.originalText,
+      getFontString(element),
+      container.width,
+    );
+  }
   const metrics = measureText(
     element.originalText,
     getFontString(element),
     maxWidth,
   );
 
+  let coordY = element.y;
+  // Resize container and vertically center align the text
+  if (container) {
+    coordY = container.y + container.height / 2 - metrics.height / 2;
+    let nextHeight = container.height;
+    if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) {
+      nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
+      coordY = container.y + nextHeight / 2 - metrics.height / 2;
+    }
+    mutateElement(container, { height: nextHeight });
+  }
+
   mutateElement(element, {
     width: metrics.width,
     height: metrics.height,
     baseline: metrics.baseline,
+    y: coordY,
+    text,
   });
 };
 

+ 4 - 13
src/element/textWysiwyg.tsx

@@ -102,7 +102,7 @@ export const textWysiwyg = ({
     if (updatedElement && isTextElement(updatedElement)) {
       let coordX = updatedElement.x;
       let coordY = updatedElement.y;
-      let container = updatedElement?.containerId
+      const container = updatedElement?.containerId
         ? Scene.getScene(updatedElement)!.getElement(updatedElement.containerId)
         : null;
       let maxWidth = updatedElement.width;
@@ -123,21 +123,12 @@ export const textWysiwyg = ({
           height = editorHeight;
         }
         if (propertiesUpdated) {
-          const currentContainer = Scene.getScene(updatedElement)?.getElement(
-            updatedElement.containerId,
-          ) as ExcalidrawBindableElement;
           approxLineHeight = isTextElement(updatedElement)
             ? getApproxLineHeight(getFontString(updatedElement))
             : 0;
-          if (
-            updatedElement.height >
-            currentContainer.height - BOUND_TEXT_PADDING * 2
-          ) {
-            const nextHeight = updatedElement.height + BOUND_TEXT_PADDING * 2;
-            originalContainerHeight = nextHeight;
-            mutateElement(container, { height: nextHeight });
-            container = { ...container, height: nextHeight };
-          }
+
+          originalContainerHeight = container.height;
+
           // update height of the editor after properties updated
           height = updatedElement.height;
         }

+ 1 - 1
src/scene/selection.ts

@@ -77,4 +77,4 @@ export const getTargetElements = (
 ) =>
   appState.editingElement
     ? [appState.editingElement]
-    : getSelectedElements(elements, appState);
+    : getSelectedElements(elements, appState, true);