Bläddra i källkod

Extract Sidebar panels into separate components (#230)

* Extract Sidebar panels into separate components

* Add Jest TS types
Gasim Gasimzada 5 år sedan
förälder
incheckning
85365e5bcb

+ 8 - 0
package-lock.json

@@ -1469,6 +1469,14 @@
         "@types/istanbul-lib-report": "*"
       }
     },
+    "@types/jest": {
+      "version": "24.0.25",
+      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.25.tgz",
+      "integrity": "sha512-hnP1WpjN4KbGEK4dLayul6lgtys6FPz0UfxMeMQCv0M+sTnzN3ConfiO72jHgLxl119guHgI8gLqDOrRLsyp2g==",
+      "requires": {
+        "jest-diff": "^24.3.0"
+      }
+    },
     "@types/json-schema": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "roughjs": "3.1.0"
   },
   "devDependencies": {
+    "@types/jest": "^24.0.25",
     "@types/react": "16.9.17",
     "@types/react-color": "^3.0.1",
     "@types/react-dom": "16.9.4",

+ 35 - 0
src/components/panels/PanelCanvas.tsx

@@ -0,0 +1,35 @@
+import React from "react";
+
+import { ColorPicker } from "../ColorPicker";
+
+interface PanelCanvasProps {
+  viewBackgroundColor: string;
+  onViewBackgroundColorChange: (val: string) => void;
+  onClearCanvas: React.MouseEventHandler;
+}
+
+export const PanelCanvas: React.FC<PanelCanvasProps> = ({
+  viewBackgroundColor,
+  onViewBackgroundColorChange,
+  onClearCanvas
+}) => {
+  return (
+    <>
+      <h4>Canvas</h4>
+      <div className="panelColumn">
+        <h5>Canvas Background Color</h5>
+        <ColorPicker
+          color={viewBackgroundColor}
+          onChange={color => onViewBackgroundColorChange(color)}
+        />
+        <button
+          type="button"
+          onClick={onClearCanvas}
+          title="Clear the canvas & reset background color"
+        >
+          Clear canvas
+        </button>
+      </div>
+    </>
+  );
+};

+ 24 - 0
src/components/panels/PanelColor.tsx

@@ -0,0 +1,24 @@
+import React from "react";
+import { ColorPicker } from "../ColorPicker";
+
+interface PanelColorProps {
+  title: string;
+  colorValue: string | null;
+  onColorChange: (value: string) => void;
+}
+
+export const PanelColor: React.FC<PanelColorProps> = ({
+  title,
+  onColorChange,
+  colorValue
+}) => {
+  return (
+    <>
+      <h5>{title}</h5>
+      <ColorPicker
+        color={colorValue}
+        onChange={color => onColorChange(color)}
+      />
+    </>
+  );
+};

+ 52 - 0
src/components/panels/PanelExport.tsx

@@ -0,0 +1,52 @@
+import React from "react";
+import { EditableText } from "../EditableText";
+
+interface PanelExportProps {
+  projectName: string;
+  onProjectNameChange: (name: string) => void;
+  onExportAsPNG: React.MouseEventHandler;
+  exportBackground: boolean;
+  onExportBackgroundChange: (val: boolean) => void;
+  onSaveScene: React.MouseEventHandler;
+  onLoadScene: React.MouseEventHandler;
+}
+
+export const PanelExport: React.FC<PanelExportProps> = ({
+  projectName,
+  exportBackground,
+  onProjectNameChange,
+  onExportBackgroundChange,
+  onSaveScene,
+  onLoadScene,
+  onExportAsPNG
+}) => {
+  return (
+    <>
+      <h4>Export</h4>
+      <div className="panelColumn">
+        <h5>Name</h5>
+        {projectName && (
+          <EditableText
+            value={projectName}
+            onChange={(name: string) => onProjectNameChange(name)}
+          />
+        )}
+        <h5>Image</h5>
+        <button onClick={onExportAsPNG}>Export to png</button>
+        <label>
+          <input
+            type="checkbox"
+            checked={exportBackground}
+            onChange={e => {
+              onExportBackgroundChange(e.target.checked);
+            }}
+          />
+          background
+        </label>
+        <h5>Scene</h5>
+        <button onClick={onSaveScene}>Save as...</button>
+        <button onClick={onLoadScene}>Load file...</button>
+      </div>
+    </>
+  );
+};

+ 35 - 0
src/components/panels/PanelSelection.tsx

@@ -0,0 +1,35 @@
+import React from "react";
+
+interface PanelSelectionProps {
+  onBringForward: React.MouseEventHandler;
+  onBringToFront: React.MouseEventHandler;
+  onSendBackward: React.MouseEventHandler;
+  onSendToBack: React.MouseEventHandler;
+}
+
+export const PanelSelection: React.FC<PanelSelectionProps> = ({
+  onBringForward,
+  onBringToFront,
+  onSendBackward,
+  onSendToBack
+}) => {
+  return (
+    <>
+      <h4>Selection</h4>
+      <div className="buttonList">
+        <button type="button" onClick={onBringForward}>
+          Bring forward
+        </button>
+        <button type="button" onClick={onBringToFront}>
+          Bring to front
+        </button>
+        <button type="button" onClick={onSendBackward}>
+          Send backward
+        </button>
+        <button type="button" onClick={onSendToBack}>
+          Send to back
+        </button>
+      </div>
+    </>
+  );
+};

+ 38 - 0
src/components/panels/PanelTools.tsx

@@ -0,0 +1,38 @@
+import React from "react";
+
+import { SHAPES } from "../../shapes";
+import { capitalizeString } from "../../utils";
+
+interface PanelToolsProps {
+  activeTool: string;
+  onToolChange: (value: string) => void;
+}
+
+export const PanelTools: React.FC<PanelToolsProps> = ({
+  activeTool,
+  onToolChange
+}) => {
+  return (
+    <>
+      <h4>Shapes</h4>
+      <div className="panelTools">
+        {SHAPES.map(({ value, icon }) => (
+          <label
+            key={value}
+            className="tool"
+            title={`${capitalizeString(value)} - ${capitalizeString(value)[0]}`}
+          >
+            <input
+              type="radio"
+              checked={activeTool === value}
+              onChange={() => {
+                onToolChange(value);
+              }}
+            />
+            <div className="toolIcon">{icon}</div>
+          </label>
+        ))}
+      </div>
+    </>
+  );
+};

+ 53 - 101
src/index.tsx

@@ -29,16 +29,19 @@ import {
 import { AppState } from "./types";
 import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
 
-import { getDateTime, capitalizeString, isInputLike } from "./utils";
+import { getDateTime, isInputLike } from "./utils";
 
-import { EditableText } from "./components/EditableText";
 import { ButtonSelect } from "./components/ButtonSelect";
-import { ColorPicker } from "./components/ColorPicker";
-import { SHAPES, findShapeByKey, shapesShortcutKeys } from "./shapes";
+import { findShapeByKey, shapesShortcutKeys } from "./shapes";
 import { createHistory } from "./history";
 
 import "./styles.scss";
 import ContextMenu from "./components/ContextMenu";
+import { PanelTools } from "./components/panels/PanelTools";
+import { PanelSelection } from "./components/panels/PanelSelection";
+import { PanelColor } from "./components/panels/PanelColor";
+import { PanelExport } from "./components/panels/PanelExport";
+import { PanelCanvas } from "./components/panels/PanelCanvas";
 
 const { elements } = createScene();
 const { history } = createHistory();
@@ -368,59 +371,45 @@ class App extends React.Component<{}, AppState> {
         }}
       >
         <div className="sidePanel">
-          <h4>Shapes</h4>
-          <div className="panelTools">
-            {SHAPES.map(({ value, icon }) => (
-              <label
-                key={value}
-                className="tool"
-                title={`${capitalizeString(value)} - ${
-                  capitalizeString(value)[0]
-                }`}
-              >
-                <input
-                  type="radio"
-                  checked={this.state.elementType === value}
-                  onChange={() => {
-                    this.setState({ elementType: value });
-                    clearSelection(elements);
-                    document.documentElement.style.cursor =
-                      value === "text" ? "text" : "crosshair";
-                    this.forceUpdate();
-                  }}
-                />
-                <div className="toolIcon">{icon}</div>
-              </label>
-            ))}
-          </div>
+          <PanelTools
+            activeTool={this.state.elementType}
+            onToolChange={value => {
+              this.setState({ elementType: value });
+              clearSelection(elements);
+              document.documentElement.style.cursor =
+                value === "text" ? "text" : "crosshair";
+              this.forceUpdate();
+            }}
+          />
           {someElementIsSelected(elements) && (
             <div className="panelColumn">
-              <h4>Selection</h4>
-              <div className="buttonList">
-                <button onClick={this.moveOneRight}>Bring forward</button>
-                <button onClick={this.moveAllRight}>Bring to front</button>
-                <button onClick={this.moveOneLeft}>Send backward</button>
-                <button onClick={this.moveAllLeft}>Send to back</button>
-              </div>
-              <h5>Stroke Color</h5>
-              <ColorPicker
-                color={getSelectedAttribute(
+              <PanelSelection
+                onBringForward={this.moveOneRight}
+                onBringToFront={this.moveAllRight}
+                onSendBackward={this.moveOneLeft}
+                onSendToBack={this.moveAllLeft}
+              />
+
+              <PanelColor
+                title="Stroke Color"
+                onColorChange={this.changeStrokeColor}
+                colorValue={getSelectedAttribute(
                   elements,
                   element => element.strokeColor
                 )}
-                onChange={color => this.changeStrokeColor(color)}
               />
 
               {hasBackground(elements) && (
                 <>
-                  <h5>Background Color</h5>
-                  <ColorPicker
-                    color={getSelectedAttribute(
+                  <PanelColor
+                    title="Background Color"
+                    onColorChange={this.changeBackgroundColor}
+                    colorValue={getSelectedAttribute(
                       elements,
                       element => element.backgroundColor
                     )}
-                    onChange={color => this.changeBackgroundColor(color)}
                   />
+
                   <h5>Fill</h5>
                   <ButtonSelect
                     options={[
@@ -498,63 +487,26 @@ class App extends React.Component<{}, AppState> {
               </button>
             </div>
           )}
-          <h4>Canvas</h4>
-          <div className="panelColumn">
-            <h5>Canvas Background Color</h5>
-            <ColorPicker
-              color={this.state.viewBackgroundColor}
-              onChange={color => this.setState({ viewBackgroundColor: color })}
-            />
-            <button
-              onClick={this.clearCanvas}
-              title="Clear the canvas & reset background color"
-            >
-              Clear canvas
-            </button>
-          </div>
-          <h4>Export</h4>
-          <div className="panelColumn">
-            <h5>Name</h5>
-            {this.state.name && (
-              <EditableText
-                value={this.state.name}
-                onChange={(name: string) => this.updateProjectName(name)}
-              />
-            )}
-            <h5>Image</h5>
-            <button
-              onClick={() => {
-                exportAsPNG(elements, canvas, this.state);
-              }}
-            >
-              Export to png
-            </button>
-            <label>
-              <input
-                type="checkbox"
-                checked={this.state.exportBackground}
-                onChange={e => {
-                  this.setState({ exportBackground: e.target.checked });
-                }}
-              />
-              background
-            </label>
-            <h5>Scene</h5>
-            <button
-              onClick={() => {
-                saveAsJSON(elements, this.state.name);
-              }}
-            >
-              Save as...
-            </button>
-            <button
-              onClick={() => {
-                loadFromJSON(elements).then(() => this.forceUpdate());
-              }}
-            >
-              Load file...
-            </button>
-          </div>
+          <PanelCanvas
+            onClearCanvas={this.clearCanvas}
+            onViewBackgroundColorChange={val =>
+              this.setState({ viewBackgroundColor: val })
+            }
+            viewBackgroundColor={this.state.viewBackgroundColor}
+          />
+          <PanelExport
+            projectName={this.state.name}
+            onProjectNameChange={this.updateProjectName}
+            onExportAsPNG={() => exportAsPNG(elements, canvas, this.state)}
+            exportBackground={this.state.exportBackground}
+            onExportBackgroundChange={val =>
+              this.setState({ exportBackground: val })
+            }
+            onSaveScene={() => saveAsJSON(elements, this.state.name)}
+            onLoadScene={() =>
+              loadFromJSON(elements).then(() => this.forceUpdate())
+            }
+          />
         </div>
         <canvas
           id="canvas"