Browse Source

Wysiwyg text 2.0 (#238)

* Fixed cleaning handlers after cleanup

* Double click to edit text

* Preserve text styles on change
Timur Khazamov 5 năm trước cách đây
mục cha
commit
10955f8bb0
4 tập tin đã thay đổi với 149 bổ sung26 xóa
  1. 1 0
      src/element/index.ts
  2. 71 0
      src/element/textWysiwyg.tsx
  3. 76 26
      src/index.tsx
  4. 1 0
      src/types.ts

+ 1 - 0
src/element/index.ts

@@ -9,3 +9,4 @@ export { handlerRectangles } from "./handlerRectangles";
 export { hitTest } from "./collision";
 export { resizeTest } from "./resizeTest";
 export { isTextElement } from "./typeChecks";
+export { textWysiwyg } from "./textWysiwyg";

+ 71 - 0
src/element/textWysiwyg.tsx

@@ -0,0 +1,71 @@
+import { KEYS } from "../index";
+
+type TextWysiwygParams = {
+  initText: string;
+  x: number;
+  y: number;
+  strokeColor: string;
+  font: string;
+  onSubmit: (text: string) => void;
+};
+
+export function textWysiwyg({
+  initText,
+  x,
+  y,
+  strokeColor,
+  font,
+  onSubmit
+}: TextWysiwygParams) {
+  const input = document.createElement("input");
+  input.value = initText;
+  Object.assign(input.style, {
+    color: strokeColor,
+    position: "absolute",
+    top: y - 8 + "px",
+    left: x + "px",
+    transform: "translate(-50%, -50%)",
+    boxShadow: "none",
+    textAlign: "center",
+    width: (window.innerWidth - x) * 2 + "px",
+    font: font,
+    border: "none",
+    background: "transparent"
+  });
+
+  input.onkeydown = ev => {
+    if (ev.key === KEYS.ESCAPE) {
+      ev.preventDefault();
+      cleanup();
+      return;
+    }
+    if (ev.key === KEYS.ENTER) {
+      ev.preventDefault();
+      handleSubmit();
+    }
+  };
+  input.onblur = handleSubmit;
+
+  function stopEvent(ev: Event) {
+    ev.stopPropagation();
+  }
+
+  function handleSubmit() {
+    if (input.value) {
+      onSubmit(input.value);
+    }
+    cleanup();
+  }
+
+  function cleanup() {
+    input.onblur = null;
+    input.onkeydown = null;
+    window.removeEventListener("wheel", stopEvent, true);
+    document.body.removeChild(input);
+  }
+
+  window.addEventListener("wheel", stopEvent, true);
+  document.body.appendChild(input);
+  input.focus();
+  input.select();
+}

+ 76 - 26
src/index.tsx

@@ -4,7 +4,7 @@ import rough from "roughjs/bin/wrappers/rough";
 
 import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
 import { randomSeed } from "./random";
-import { newElement, resizeTest, isTextElement } from "./element";
+import { newElement, resizeTest, isTextElement, textWysiwyg } from "./element";
 import {
   clearSelection,
   getSelectedIndices,
@@ -50,11 +50,12 @@ const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
 const CANVAS_WINDOW_OFFSET_LEFT = 250;
 const CANVAS_WINDOW_OFFSET_TOP = 0;
 
-const KEYS = {
+export const KEYS = {
   ARROW_LEFT: "ArrowLeft",
   ARROW_RIGHT: "ArrowRight",
   ARROW_DOWN: "ArrowDown",
   ARROW_UP: "ArrowUp",
+  ENTER: "Enter",
   ESCAPE: "Escape",
   DELETE: "Delete",
   BACKSPACE: "Backspace"
@@ -79,24 +80,26 @@ function resetCursor() {
   document.documentElement.style.cursor = "";
 }
 
-function addTextElement(element: ExcalidrawTextElement) {
+function addTextElement(
+  element: ExcalidrawTextElement,
+  text: string,
+  font: string
+) {
   resetCursor();
-  const text = prompt("What text do you want?");
   if (text === null || text === "") {
     return false;
   }
-  const fontSize = 20;
   element.text = text;
-  element.font = `${fontSize}px Virgil`;
-  const font = context.font;
+  element.font = font;
+  const currentFont = context.font;
   context.font = element.font;
   const textMeasure = context.measureText(element.text);
   const width = textMeasure.width;
   const actualBoundingBoxAscent =
-    textMeasure.actualBoundingBoxAscent || fontSize;
+    textMeasure.actualBoundingBoxAscent || parseInt(font);
   const actualBoundingBoxDescent = textMeasure.actualBoundingBoxDescent || 0;
   element.actualBoundingBoxAscent = actualBoundingBoxAscent;
-  context.font = font;
+  context.font = currentFont;
   const height = actualBoundingBoxAscent + actualBoundingBoxDescent;
   // Center the text
   element.x -= width / 2;
@@ -138,6 +141,7 @@ class App extends React.Component<{}, AppState> {
     exportBackground: true,
     currentItemStrokeColor: "#000000",
     currentItemBackgroundColor: "#ffffff",
+    currentItemFont: "20px Virgil",
     viewBackgroundColor: "#ffffff",
     scrollX: 0,
     scrollY: 0,
@@ -688,9 +692,23 @@ class App extends React.Component<{}, AppState> {
             }
 
             if (isTextElement(element)) {
-              if (!addTextElement(element)) {
-                return;
-              }
+              textWysiwyg({
+                initText: "",
+                x: e.clientX,
+                y: e.clientY,
+                strokeColor: this.state.currentItemStrokeColor,
+                font: this.state.currentItemFont,
+                onSubmit: text => {
+                  addTextElement(element, text, this.state.currentItemFont);
+                  elements.push(element);
+                  element.isSelected = true;
+                  this.setState({
+                    draggingElement: null,
+                    elementType: "selection"
+                  });
+                }
+              });
+              return;
             }
 
             elements.push(element);
@@ -903,9 +921,12 @@ class App extends React.Component<{}, AppState> {
             const x =
               e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
             const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
-
-            if (getElementAtPosition(elements, x, y)) {
+            const elementAtPosition = getElementAtPosition(elements, x, y);
+            if (elementAtPosition && !isTextElement(elementAtPosition)) {
               return;
+            } else if (elementAtPosition) {
+              elements.splice(elements.indexOf(elementAtPosition), 1);
+              this.forceUpdate();
             }
 
             const element = newElement(
@@ -918,21 +939,50 @@ class App extends React.Component<{}, AppState> {
               1,
               1,
               100
-            );
-
-            if (!addTextElement(element as ExcalidrawTextElement)) {
-              return;
+            ) as ExcalidrawTextElement;
+
+            let initText = "";
+            let textX = e.clientX;
+            let textY = e.clientY;
+            if (elementAtPosition) {
+              Object.assign(element, elementAtPosition);
+              // x and y will change after calling addTextElement function
+              element.x = elementAtPosition.x + elementAtPosition.width / 2;
+              element.y =
+                elementAtPosition.y + elementAtPosition.actualBoundingBoxAscent;
+              initText = elementAtPosition.text;
+              textX =
+                this.state.scrollX +
+                elementAtPosition.x +
+                CANVAS_WINDOW_OFFSET_LEFT +
+                elementAtPosition.width / 2;
+              textY =
+                this.state.scrollY +
+                elementAtPosition.y +
+                CANVAS_WINDOW_OFFSET_TOP +
+                elementAtPosition.actualBoundingBoxAscent;
             }
 
-            elements.push(element);
-
-            this.setState({
-              draggingElement: null,
-              elementType: "selection"
+            textWysiwyg({
+              initText,
+              x: textX,
+              y: textY,
+              strokeColor: element.strokeColor,
+              font: element.font || this.state.currentItemFont,
+              onSubmit: text => {
+                addTextElement(
+                  element,
+                  text,
+                  element.font || this.state.currentItemFont
+                );
+                elements.push(element);
+                element.isSelected = true;
+                this.setState({
+                  draggingElement: null,
+                  elementType: "selection"
+                });
+              }
             });
-            element.isSelected = true;
-
-            this.forceUpdate();
           }}
         />
       </div>

+ 1 - 0
src/types.ts

@@ -7,6 +7,7 @@ export type AppState = {
   exportBackground: boolean;
   currentItemStrokeColor: string;
   currentItemBackgroundColor: string;
+  currentItemFont: string;
   viewBackgroundColor: string;
   scrollX: number;
   scrollY: number;