浏览代码

fix: support collaboration in bound text (#4573)

* fix: support collaboration in bounded text

* align implementation irrespective of collab/submit

* don't wrap when submitted

* fix

* tests: exit editor via ESCAPE instead to remove async hacks

* simplify and remove dead comment

* remove mutating coords in submit since its taken care in updateWysiwygStyle

Co-authored-by: dwelle <luzar.david@gmail.com>
Aakansha Doshi 3 年之前
父节点
当前提交
24bf4cb5fb
共有 6 个文件被更改,包括 57 次插入83 次删除
  1. 9 14
      src/components/App.tsx
  2. 11 11
      src/element/newElement.ts
  3. 33 49
      src/element/textWysiwyg.tsx
  4. 1 1
      src/packages/excalidraw/index.tsx
  5. 2 8
      src/tests/binding.test.tsx
  6. 1 0
      src/types.ts

+ 9 - 14
src/components/App.tsx

@@ -476,7 +476,7 @@ class App extends React.Component<AppProps, AppState> {
               zenModeEnabled={zenModeEnabled}
               toggleZenMode={this.toggleZenMode}
               langCode={getLanguage().code}
-              isCollaborating={this.props.isCollaborating || false}
+              isCollaborating={this.props.isCollaborating}
               renderTopRightUI={renderTopRightUI}
               renderCustomFooter={renderFooter}
               viewModeEnabled={viewModeEnabled}
@@ -1912,20 +1912,15 @@ class App extends React.Component<AppProps, AppState> {
       text: string,
       originalText: string,
       isDeleted: boolean,
-      isSubmit: boolean,
     ) => {
       this.scene.replaceAllElements([
         ...this.scene.getElementsIncludingDeleted().map((_element) => {
           if (_element.id === element.id && isTextElement(_element)) {
-            return updateTextElement(
-              _element,
-              {
-                text,
-                isDeleted,
-                originalText,
-              },
-              isSubmit,
-            );
+            return updateTextElement(_element, {
+              text,
+              isDeleted,
+              originalText,
+            });
           }
           return _element;
         }),
@@ -1950,14 +1945,14 @@ class App extends React.Component<AppProps, AppState> {
         ];
       },
       onChange: withBatchedUpdates((text) => {
-        updateElement(text, text, false, false);
+        updateElement(text, text, false);
         if (isNonDeletedElement(element)) {
           updateBoundElements(element);
         }
       }),
       onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
         const isDeleted = !text.trim();
-        updateElement(text, originalText, isDeleted, true);
+        updateElement(text, originalText, isDeleted);
         // select the created text element only if submitting via keyboard
         // (when submitting via click it should act as signal to deselect)
         if (!isDeleted && viaKeyboard) {
@@ -1997,7 +1992,7 @@ class App extends React.Component<AppProps, AppState> {
 
     // do an initial update to re-initialize element position since we were
     // modifying element's x/y for sake of editor (case: syncing to remote)
-    updateElement(element.text, element.originalText, false, false);
+    updateElement(element.text, element.originalText, false);
   }
 
   private deselectElements() {

+ 11 - 11
src/element/newElement.ts

@@ -21,7 +21,7 @@ import { AppState } from "../types";
 import { getElementAbsoluteCoords } from ".";
 import { adjustXYWithRotation } from "../math";
 import { getResizedElementAbsoluteCoords } from "./bounds";
-import { getContainerElement, measureText } from "./textElement";
+import { getContainerElement, measureText, wrapText } from "./textElement";
 import { isBoundToContainer } from "./typeChecks";
 import { BOUND_TEXT_PADDING } from "../constants";
 
@@ -247,17 +247,17 @@ export const updateTextElement = (
     text,
     isDeleted,
     originalText,
-  }: { text: string; isDeleted?: boolean; originalText: string },
-
-  isSubmit: boolean,
+  }: {
+    text: string;
+    isDeleted?: boolean;
+    originalText: string;
+  },
 ): ExcalidrawTextElement => {
-  const boundToContainer = isBoundToContainer(element);
-
-  // Don't update dimensions and text value for bounded text unless submitted
-  const dimensions =
-    boundToContainer && !isSubmit
-      ? undefined
-      : getAdjustedDimensions(element, text);
+  const container = getContainerElement(element);
+  if (container) {
+    text = wrapText(text, getFontString(element), container.width);
+  }
+  const dimensions = getAdjustedDimensions(element, text);
   return newElementWith(element, {
     text,
     originalText,

+ 33 - 49
src/element/textWysiwyg.tsx

@@ -8,7 +8,11 @@ import {
 import Scene from "../scene/Scene";
 import { isBoundToContainer, isTextElement } from "./typeChecks";
 import { CLASSES, BOUND_TEXT_PADDING } from "../constants";
-import { ExcalidrawElement, ExcalidrawTextElement } from "./types";
+import {
+  ExcalidrawElement,
+  ExcalidrawTextElement,
+  ExcalidrawLinearElement,
+} from "./types";
 import { AppState } from "../types";
 import { mutateElement } from "./mutateElement";
 import {
@@ -100,7 +104,6 @@ export const textWysiwyg = ({
   let originalContainerHeight: number;
   let approxLineHeight = getApproxLineHeight(getFontString(element));
 
-  const initialText = element.originalText;
   const updateWysiwygStyle = () => {
     const updatedElement = Scene.getScene(element)?.getElement(id);
     if (updatedElement && isTextElement(updatedElement)) {
@@ -222,6 +225,7 @@ export const textWysiwyg = ({
       if (isTestEnv()) {
         editable.style.fontFamily = getFontFamilyString(updatedElement);
       }
+      mutateElement(updatedElement, { x: coordX, y: coordY });
     }
   };
 
@@ -442,61 +446,41 @@ export const textWysiwyg = ({
     // it'd get stuck in an infinite loop of blur→onSubmit after we re-focus the
     // wysiwyg on update
     cleanup();
-    const updateElement = Scene.getScene(element)?.getElement(element.id);
+    const updateElement = Scene.getScene(element)?.getElement(
+      element.id,
+    ) as ExcalidrawTextElement;
     if (!updateElement) {
       return;
     }
-    let wrappedText = "";
-    if (isTextElement(updateElement) && updateElement?.containerId) {
-      const container = getContainerElement(updateElement);
-
-      if (container) {
-        wrappedText = wrapText(
-          editable.value,
-          getFontString(updateElement),
-          container.width,
-        );
-
-        if (updateElement.containerId) {
-          const editorHeight = Number(editable.style.height.slice(0, -2));
-          if (editable.value) {
-            // Don't mutate if text is not updated
-            if (initialText !== editable.value) {
-              mutateElement(updateElement, {
-                // vertically center align
-                y: container.y + container.height / 2 - editorHeight / 2,
-                height: editorHeight,
-                width: Number(editable.style.width.slice(0, -2)),
-                // preserve padding
-                x: container.x + BOUND_TEXT_PADDING,
-                angle: container.angle,
-              });
-            }
-
-            const boundTextElementId = getBoundTextElementId(container);
-            if (!boundTextElementId || boundTextElementId !== element.id) {
-              mutateElement(container, {
-                boundElements: (container.boundElements || []).concat({
-                  type: "text",
-                  id: element.id,
-                }),
-              });
-            }
-          } else {
-            mutateElement(container, {
-              boundElements: container.boundElements?.filter(
-                (ele) => ele.type !== "text",
-              ),
-            });
-          }
+    let text = editable.value;
+    const container = getContainerElement(updateElement);
+
+    if (container) {
+      text = updateElement.text;
+      if (editable.value) {
+        const boundTextElementId = getBoundTextElementId(container);
+        if (!boundTextElementId || boundTextElementId !== element.id) {
+          mutateElement(container, {
+            boundElements: (container.boundElements || []).concat({
+              type: "text",
+              id: element.id,
+            }),
+          });
         }
+      } else {
+        mutateElement(container, {
+          boundElements: container.boundElements?.filter(
+            (ele) =>
+              !isTextElement(
+                ele as ExcalidrawTextElement | ExcalidrawLinearElement,
+              ),
+          ),
+        });
       }
-    } else {
-      wrappedText = editable.value;
     }
 
     onSubmit({
-      text: normalizeText(wrappedText),
+      text,
       viaKeyboard: submittedViaKeyboard,
       originalText: editable.value,
     });

+ 1 - 1
src/packages/excalidraw/index.tsx

@@ -17,7 +17,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
     initialData,
     excalidrawRef,
     onCollabButtonClick,
-    isCollaborating,
+    isCollaborating = false,
     onPointerUpdate,
     renderTopRightUI,
     renderFooter,

+ 2 - 8
src/tests/binding.test.tsx

@@ -158,11 +158,8 @@ describe("element binding", () => {
 
     expect(editor).not.toBe(null);
 
-    // we defer binding blur event on wysiwyg, hence wait a bit
-    await new Promise((r) => setTimeout(r, 30));
-
     fireEvent.change(editor, { target: { value: "" } });
-    editor.blur();
+    fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
 
     expect(
       document.querySelector(".excalidraw-textEditorContainer > textarea"),
@@ -202,11 +199,8 @@ describe("element binding", () => {
 
     expect(editor).not.toBe(null);
 
-    // we defer binding blur event on wysiwyg, hence wait a bit
-    await new Promise((r) => setTimeout(r, 30));
-
     fireEvent.change(editor, { target: { value: "asdasdasdasdas" } });
-    editor.blur();
+    fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
 
     expect(
       document.querySelector(".excalidraw-textEditorContainer > textarea"),

+ 1 - 0
src/types.ts

@@ -300,6 +300,7 @@ export type AppProps = ExcalidrawProps & {
   };
   detectScroll: boolean;
   handleKeyboardGlobally: boolean;
+  isCollaborating: boolean;
 };
 
 /** A subset of App class properties that we need to use elsewhere