Ver Fonte

Allow user to set file name (#145)

* Allow user to set file name

* Add EditableText component

Added editable text component and use component for project name edit.

* rebased branch

* Updated EditableText component

* Set default project name

* Move project name field away from the top section.
IA há 5 anos atrás
pai
commit
5f806474e3
4 ficheiros alterados com 115 adições e 7 exclusões
  1. 68 0
      src/components/EditableText.tsx
  2. 27 7
      src/index.tsx
  3. 9 0
      src/styles.scss
  4. 11 0
      src/utils/index.ts

+ 68 - 0
src/components/EditableText.tsx

@@ -0,0 +1,68 @@
+import React, { Fragment, Component } from "react";
+
+type InputState = {
+  value: string;
+  edit: boolean;
+};
+
+type Props = {
+  value: string;
+  onChange: (value: string) => void;
+};
+
+export default class EditableText extends Component<Props, InputState> {
+  constructor(props: Props) {
+    super(props);
+
+    this.state = {
+      value: props.value,
+      edit: false
+    };
+  }
+
+  componentWillReceiveProps(props: Props) {
+    this.setState({ value: props.value });
+  }
+
+  private handleEdit(e: React.ChangeEvent<HTMLInputElement>) {
+    this.setState({ value: e.target.value });
+  }
+
+  private handleBlur() {
+    const { value } = this.state;
+
+    if (!value) {
+      this.setState({ value: this.props.value, edit: false });
+      return;
+    }
+    this.props.onChange(value);
+    this.setState({ edit: false });
+  }
+
+  public render() {
+    const { value, edit } = this.state;
+
+    return (
+      <Fragment>
+        {edit ? (
+          <input
+            className="project-name-input"
+            name="name"
+            maxLength={25}
+            value={value}
+            onChange={e => this.handleEdit(e)}
+            onBlur={() => this.handleBlur()}
+            autoFocus
+          />
+        ) : (
+          <span
+            onClick={() => this.setState({ edit: true })}
+            className="project-name"
+          >
+            {value}
+          </span>
+        )}
+      </Fragment>
+    );
+  }
+}

+ 27 - 7
src/index.tsx

@@ -6,6 +6,8 @@ import { SketchPicker } from "react-color";
 
 import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
 import { roundRect } from "./roundRect";
+import EditableText from "./components/EditableText";
+import { getDateTime } from "./utils";
 
 import "./styles.scss";
 
@@ -22,6 +24,8 @@ const LOCAL_STORAGE_KEY_STATE = "excalidraw-state";
 
 const elements = Array.of<ExcalidrawElement>();
 
+const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
+
 let skipHistory = false;
 const stateHistory: string[] = [];
 function generateHistoryCurrentEntry() {
@@ -529,7 +533,7 @@ function renderScene(
   }
 }
 
-function saveAsJSON() {
+function saveAsJSON(name: string) {
   const serialized = JSON.stringify({
     version: 1,
     source: window.location.origin,
@@ -537,7 +541,7 @@ function saveAsJSON() {
   });
 
   saveFile(
-    "excalidraw.json",
+    `${name}.json`,
     "data:text/plain;charset=utf-8," + encodeURIComponent(serialized)
   );
 }
@@ -573,16 +577,17 @@ function loadFromJSON() {
 function exportAsPNG({
   exportBackground,
   exportPadding = 10,
-  viewBackgroundColor
+  viewBackgroundColor,
+  name
 }: {
   exportBackground: boolean;
   exportPadding?: number;
   viewBackgroundColor: string;
   scrollX: number;
   scrollY: number;
+  name: string;
 }) {
   if (!elements.length) return window.alert("Cannot export empty canvas.");
-
   // calculate smallest area to fit the contents in
 
   let subCanvasX1 = Infinity;
@@ -623,7 +628,7 @@ function exportAsPNG({
     }
   );
 
-  saveFile("excalidraw.png", tempCanvas.toDataURL("image/png"));
+  saveFile(`${name}.png`, tempCanvas.toDataURL("image/png"));
 
   // clean up the DOM
   if (tempCanvas !== canvas) tempCanvas.remove();
@@ -872,6 +877,7 @@ type AppState = {
   viewBackgroundColor: string;
   scrollX: number;
   scrollY: number;
+  name: string;
 };
 
 const KEYS = {
@@ -1004,7 +1010,8 @@ class App extends React.Component<{}, AppState> {
     currentItemBackgroundColor: "#ffffff",
     viewBackgroundColor: "#ffffff",
     scrollX: 0,
-    scrollY: 0
+    scrollY: 0,
+    name: DEFAULT_PROJECT_NAME
   };
 
   private onResize = () => {
@@ -1129,6 +1136,10 @@ class App extends React.Component<{}, AppState> {
 
   private removeWheelEventListener: (() => void) | undefined;
 
+  private updateProjectName(name: string): void {
+    this.setState({ name });
+  }
+
   public render() {
     const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT;
     const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP;
@@ -1349,11 +1360,20 @@ class App extends React.Component<{}, AppState> {
               background
             </label>
           </div>
+          <h4>Project name</h4>
+          <div className="panelColumn">
+            {this.state.name && (
+              <EditableText
+                value={this.state.name}
+                onChange={(name: string) => this.updateProjectName(name)}
+              />
+            )}
+          </div>
           <h4>Save/Load</h4>
           <div className="panelColumn">
             <button
               onClick={() => {
-                saveAsJSON();
+                saveAsJSON(this.state.name);
               }}
             >
               Save as...

+ 9 - 0
src/styles.scss

@@ -173,3 +173,12 @@ button {
   padding: 2px 4px;
   border: 1px solid #ddd;
 }
+.project-name {
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.project-name-input {
+  width: 200px;
+  font: inherit;
+}

+ 11 - 0
src/utils/index.ts

@@ -0,0 +1,11 @@
+export const getDateTime = (): string => {
+  const date = new Date();
+  const year = date.getFullYear();
+  const month = date.getMonth() + 1;
+  const day = date.getDate();
+  const hr = date.getHours();
+  const min = date.getMinutes();
+  const secs = date.getSeconds();
+
+  return `${year}${month}${day}${hr}${min}${secs}`;
+};