浏览代码

merge update

Brady Madden 5 年之前
父节点
当前提交
0b61083ac9
共有 6 个文件被更改,包括 173 次插入204 次删除
  1. 0 29
      package-lock.json
  2. 0 4
      package.json
  3. 2 15
      public/index.html
  4. 二进制
      public/logo.png
  5. 148 154
      src/index.tsx
  6. 23 2
      src/styles.scss

+ 0 - 29
package-lock.json

@@ -1000,35 +1000,6 @@
       "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
       "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
       "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
       "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
     },
     },
-    "@fortawesome/fontawesome-common-types": {
-      "version": "0.2.26",
-      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.26.tgz",
-      "integrity": "sha512-CcM/fIFwZlRdiWG/25xE/wHbtyUuCtqoCTrr6BsWw7hH072fR++n4L56KPydAr3ANgMJMjT8v83ZFIsDc7kE+A=="
-    },
-    "@fortawesome/fontawesome-svg-core": {
-      "version": "1.2.26",
-      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.26.tgz",
-      "integrity": "sha512-3Dfd/v2IztP1TxKOxZiB5+4kaOZK9mNy0KU1vVK7nFlPWz3gzxrCWB+AloQhQUoJ8HhGqbzjliK89Vl7PExGbw==",
-      "requires": {
-        "@fortawesome/fontawesome-common-types": "^0.2.26"
-      }
-    },
-    "@fortawesome/free-solid-svg-icons": {
-      "version": "5.12.0",
-      "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.12.0.tgz",
-      "integrity": "sha512-CnpsWs6GhTs9ekNB3d8rcO5HYqRkXbYKf2YNiAlTWbj5eVlPqsd/XH1F9If8jkcR1aegryAbln/qYeKVZzpM0g==",
-      "requires": {
-        "@fortawesome/fontawesome-common-types": "^0.2.26"
-      }
-    },
-    "@fortawesome/react-fontawesome": {
-      "version": "0.1.8",
-      "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.8.tgz",
-      "integrity": "sha512-I5h9YQg/ePA3Br9ISS18fcwOYmzQYDSM1ftH03/8nHkiqIVHtUyQBw482+60dnzvlr82gHt3mGm+nDUp159FCw==",
-      "requires": {
-        "prop-types": "^15.5.10"
-      }
-    },
     "@hapi/address": {
     "@hapi/address": {
       "version": "2.1.4",
       "version": "2.1.4",
       "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
       "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",

+ 0 - 4
package.json

@@ -6,10 +6,6 @@
   "keywords": [],
   "keywords": [],
   "main": "src/index.js",
   "main": "src/index.js",
   "dependencies": {
   "dependencies": {
-    "@fortawesome/fontawesome-svg-core": "^1.2.26",
-    "@fortawesome/free-solid-svg-icons": "^5.12.0",
-    "@fortawesome/react-fontawesome": "^0.1.8",
-    "lodash": "4.17.15",
     "react": "16.12.0",
     "react": "16.12.0",
     "react-dom": "16.12.0",
     "react-dom": "16.12.0",
     "react-scripts": "3.3.0",
     "react-scripts": "3.3.0",

+ 2 - 15
public/index.html

@@ -7,12 +7,8 @@
       content="width=device-width, initial-scale=1, shrink-to-fit=no"
       content="width=device-width, initial-scale=1, shrink-to-fit=no"
     />
     />
     <meta name="theme-color" content="#000000" />
     <meta name="theme-color" content="#000000" />
-    <!--
-      manifest.json provides metadata used when your web app is added to the
-      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-    -->
-    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
+
+    <link rel="icon" href="%PUBLIC_URL%/logo.png" />
     <link
     <link
       rel="preload"
       rel="preload"
       href="https://uploads.codesandbox.io/uploads/user/ed077012-e728-4a42-8395-cbd299149d62/AflB-FG_Virgil.ttf"
       href="https://uploads.codesandbox.io/uploads/user/ed077012-e728-4a42-8395-cbd299149d62/AflB-FG_Virgil.ttf"
@@ -20,15 +16,6 @@
       type="font/ttf"
       type="font/ttf"
       crossorigin="anonymous"
       crossorigin="anonymous"
     />
     />
-    <!--
-      Notice the use of %PUBLIC_URL% in the tags above.
-      It will be replaced with the URL of the `public` folder during the build.
-      Only files inside the `public` folder can be referenced from the HTML.
-
-      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
-      work correctly both with client-side routing and a non-root public URL.
-      Learn how to configure a non-root public URL by running `npm run build`.
-    -->
     <title>Excalidraw</title>
     <title>Excalidraw</title>
 
 
     <script
     <script

二进制
public/logo.png


+ 148 - 154
src/index.tsx

@@ -2,14 +2,6 @@ import React from "react";
 import ReactDOM from "react-dom";
 import ReactDOM from "react-dom";
 import rough from "roughjs/bin/wrappers/rough";
 import rough from "roughjs/bin/wrappers/rough";
 import { RoughCanvas } from "roughjs/bin/canvas";
 import { RoughCanvas } from "roughjs/bin/canvas";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
-  faMousePointer,
-  faSquare,
-  faCircle,
-  faLongArrowAltRight,
-  faFont
-} from "@fortawesome/free-solid-svg-icons";
 
 
 import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
 import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
 
 
@@ -351,10 +343,23 @@ function handlerRectangles(
 
 
 function renderScene(
 function renderScene(
   rc: RoughCanvas,
   rc: RoughCanvas,
-  context: CanvasRenderingContext2D,
-  sceneState: SceneState
+  canvas: HTMLCanvasElement,
+  sceneState: SceneState,
+  // extra options, currently passed by export helper
+  {
+    offsetX,
+    offsetY,
+    renderScrollbars = true,
+    renderSelection = true
+  }: {
+    offsetX?: number;
+    offsetY?: number;
+    renderScrollbars?: boolean;
+    renderSelection?: boolean;
+  } = {}
 ) {
 ) {
-  if (!context) return;
+  if (!canvas) return;
+  const context = canvas.getContext("2d")!;
 
 
   const fillStyle = context.fillStyle;
   const fillStyle = context.fillStyle;
   if (typeof sceneState.viewBackgroundColor === "string") {
   if (typeof sceneState.viewBackgroundColor === "string") {
@@ -367,9 +372,15 @@ function renderScene(
 
 
   const selectedIndices = getSelectedIndices();
   const selectedIndices = getSelectedIndices();
 
 
+  sceneState = {
+    ...sceneState,
+    scrollX: typeof offsetX === "number" ? offsetX : sceneState.scrollX,
+    scrollY: typeof offsetY === "number" ? offsetY : sceneState.scrollY
+  };
+
   elements.forEach(element => {
   elements.forEach(element => {
     element.draw(rc, context, sceneState);
     element.draw(rc, context, sceneState);
-    if (element.isSelected) {
+    if (renderSelection && element.isSelected) {
       const margin = 4;
       const margin = 4;
 
 
       const elementX1 = getElementAbsoluteX1(element);
       const elementX1 = getElementAbsoluteX1(element);
@@ -405,119 +416,93 @@ function renderScene(
     }
     }
   });
   });
 
 
-  const scrollBars = getScrollbars(
-    context.canvas.width,
-    context.canvas.height,
-    sceneState.scrollX,
-    sceneState.scrollY
-  );
+  if (renderScrollbars) {
+    const scrollBars = getScrollbars(
+      context.canvas.width,
+      context.canvas.height,
+      sceneState.scrollX,
+      sceneState.scrollY
+    );
 
 
-  context.fillStyle = SCROLLBAR_COLOR;
-  context.fillRect(
-    scrollBars.horizontal.x,
-    scrollBars.horizontal.y,
-    scrollBars.horizontal.width,
-    scrollBars.horizontal.height
-  );
-  context.fillRect(
-    scrollBars.vertical.x,
-    scrollBars.vertical.y,
-    scrollBars.vertical.width,
-    scrollBars.vertical.height
-  );
-  context.fillStyle = fillStyle;
+    context.fillStyle = SCROLLBAR_COLOR;
+    context.fillRect(
+      scrollBars.horizontal.x,
+      scrollBars.horizontal.y,
+      scrollBars.horizontal.width,
+      scrollBars.horizontal.height
+    );
+    context.fillRect(
+      scrollBars.vertical.x,
+      scrollBars.vertical.y,
+      scrollBars.vertical.width,
+      scrollBars.vertical.height
+    );
+    context.fillStyle = fillStyle;
+  }
 }
 }
 
 
 function exportAsPNG({
 function exportAsPNG({
   exportBackground,
   exportBackground,
-  exportVisibleOnly,
   exportPadding = 10,
   exportPadding = 10,
   viewBackgroundColor
   viewBackgroundColor
 }: {
 }: {
   exportBackground: boolean;
   exportBackground: boolean;
-  exportVisibleOnly: boolean;
   exportPadding?: number;
   exportPadding?: number;
   viewBackgroundColor: string;
   viewBackgroundColor: string;
+  scrollX: number;
+  scrollY: number;
 }) {
 }) {
   if (!elements.length) return window.alert("Cannot export empty canvas.");
   if (!elements.length) return window.alert("Cannot export empty canvas.");
 
 
-  // deselect & rerender
-
-  clearSelection();
-  ReactDOM.render(<App />, rootElement, () => {
-    // calculate visible-area coords
+  // calculate smallest area to fit the contents in
 
 
-    let subCanvasX1 = Infinity;
-    let subCanvasX2 = 0;
-    let subCanvasY1 = Infinity;
-    let subCanvasY2 = 0;
+  let subCanvasX1 = Infinity;
+  let subCanvasX2 = 0;
+  let subCanvasY1 = Infinity;
+  let subCanvasY2 = 0;
 
 
-    elements.forEach(element => {
-      subCanvasX1 = Math.min(subCanvasX1, getElementAbsoluteX1(element));
-      subCanvasX2 = Math.max(subCanvasX2, getElementAbsoluteX2(element));
-      subCanvasY1 = Math.min(subCanvasY1, getElementAbsoluteY1(element));
-      subCanvasY2 = Math.max(subCanvasY2, getElementAbsoluteY2(element));
-    });
-
-    // create temporary canvas from which we'll export
-
-    const tempCanvas = document.createElement("canvas");
-    const tempCanvasCtx = tempCanvas.getContext("2d")!;
-    tempCanvas.style.display = "none";
-    document.body.appendChild(tempCanvas);
-    tempCanvas.width = exportVisibleOnly
-      ? subCanvasX2 - subCanvasX1 + exportPadding * 2
-      : canvas.width;
-    tempCanvas.height = exportVisibleOnly
-      ? subCanvasY2 - subCanvasY1 + exportPadding * 2
-      : canvas.height;
-
-    // if we're exporting without bg, we need to rerender the scene without it
-    //  (it's reset again, below)
-    if (!exportBackground) {
-      renderScene(rc, context, {
-        viewBackgroundColor: null,
-        scrollX: 0,
-        scrollY: 0
-      });
-    }
+  elements.forEach(element => {
+    subCanvasX1 = Math.min(subCanvasX1, getElementAbsoluteX1(element));
+    subCanvasX2 = Math.max(subCanvasX2, getElementAbsoluteX2(element));
+    subCanvasY1 = Math.min(subCanvasY1, getElementAbsoluteY1(element));
+    subCanvasY2 = Math.max(subCanvasY2, getElementAbsoluteY2(element));
+  });
 
 
-    // copy our original canvas onto the temp canvas
-    tempCanvasCtx.drawImage(
-      canvas, // source
-      exportVisibleOnly // sx
-        ? subCanvasX1 - exportPadding
-        : 0,
-      exportVisibleOnly // sy
-        ? subCanvasY1 - exportPadding
-        : 0,
-      exportVisibleOnly // sWidth
-        ? subCanvasX2 - subCanvasX1 + exportPadding * 2
-        : canvas.width,
-      exportVisibleOnly // sHeight
-        ? subCanvasY2 - subCanvasY1 + exportPadding * 2
-        : canvas.height,
-      0, // dx
-      0, // dy
-      exportVisibleOnly ? tempCanvas.width : canvas.width, // dWidth
-      exportVisibleOnly ? tempCanvas.height : canvas.height // dHeight
-    );
+  function distance(x: number, y: number) {
+    return Math.abs(x > y ? x - y : y - x);
+  }
 
 
-    // reset transparent bg back to original
-    if (!exportBackground) {
-      renderScene(rc, context, { viewBackgroundColor, scrollX: 0, scrollY: 0 });
+  const tempCanvas = document.createElement("canvas");
+  tempCanvas.style.display = "none";
+  document.body.appendChild(tempCanvas);
+  tempCanvas.width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
+  tempCanvas.height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
+
+  renderScene(
+    rough.canvas(tempCanvas),
+    tempCanvas,
+    {
+      viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
+      scrollX: 0,
+      scrollY: 0
+    },
+    {
+      offsetX: -subCanvasX1 + exportPadding,
+      offsetY: -subCanvasY1 + exportPadding,
+      renderScrollbars: false,
+      renderSelection: false
     }
     }
+  );
 
 
-    // create a temporary <a> elem which we'll use to download the image
-    const link = document.createElement("a");
-    link.setAttribute("download", "excalidraw.png");
-    link.setAttribute("href", tempCanvas.toDataURL("image/png"));
-    link.click();
+  // create a temporary <a> elem which we'll use to download the image
+  const link = document.createElement("a");
+  link.setAttribute("download", "excalidraw.png");
+  link.setAttribute("href", tempCanvas.toDataURL("image/png"));
+  link.click();
 
 
-    // clean up the DOM
-    link.remove();
-    if (tempCanvas !== canvas) tempCanvas.remove();
-  });
+  // clean up the DOM
+  link.remove();
+  if (tempCanvas !== canvas) tempCanvas.remove();
 }
 }
 
 
 function rotate(x1: number, y1: number, x2: number, y2: number, angle: number) {
 function rotate(x1: number, y1: number, x2: number, y2: number, angle: number) {
@@ -723,8 +708,6 @@ type AppState = {
   resizingElement: ExcalidrawElement | null;
   resizingElement: ExcalidrawElement | null;
   elementType: string;
   elementType: string;
   exportBackground: boolean;
   exportBackground: boolean;
-  exportVisibleOnly: boolean;
-  exportPadding: number;
   currentItemStrokeColor: string;
   currentItemStrokeColor: string;
   currentItemBackgroundColor: string;
   currentItemBackgroundColor: string;
   viewBackgroundColor: string;
   viewBackgroundColor: string;
@@ -742,25 +725,51 @@ const KEYS = {
   BACKSPACE: "Backspace"
   BACKSPACE: "Backspace"
 };
 };
 
 
+// We inline font-awesome icons in order to save on js size rather than including the font awesome react library
 const SHAPES = [
 const SHAPES = [
   {
   {
-    icon: faMousePointer,
+    icon: (
+      // fa-mouse-pointer
+      <svg viewBox="0 0 320 512">
+        <path d="M302.189 329.126H196.105l55.831 135.993c3.889 9.428-.555 19.999-9.444 23.999l-49.165 21.427c-9.165 4-19.443-.571-23.332-9.714l-53.053-129.136-86.664 89.138C18.729 472.71 0 463.554 0 447.977V18.299C0 1.899 19.921-6.096 30.277 5.443l284.412 292.542c11.472 11.179 3.007 31.141-12.5 31.141z" />
+      </svg>
+    ),
     value: "selection"
     value: "selection"
   },
   },
   {
   {
-    icon: faSquare,
+    icon: (
+      // fa-square
+      <svg viewBox="0 0 448 512">
+        <path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z" />
+      </svg>
+    ),
     value: "rectangle"
     value: "rectangle"
   },
   },
   {
   {
-    icon: faCircle,
+    icon: (
+      // fa-circle
+      <svg viewBox="0 0 512 512">
+        <path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z" />
+      </svg>
+    ),
     value: "ellipse"
     value: "ellipse"
   },
   },
   {
   {
-    icon: faLongArrowAltRight,
+    icon: (
+      // fa-long-arrow-alt-right
+      <svg viewBox="0 0 448 512">
+        <path d="M313.941 216H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12h301.941v46.059c0 21.382 25.851 32.09 40.971 16.971l86.059-86.059c9.373-9.373 9.373-24.569 0-33.941l-86.059-86.059c-15.119-15.119-40.971-4.411-40.971 16.971V216z" />
+      </svg>
+    ),
     value: "arrow"
     value: "arrow"
   },
   },
   {
   {
-    icon: faFont,
+    icon: (
+      // fa-font
+      <svg viewBox="0 0 448 512">
+        <path d="M432 416h-23.41L277.88 53.69A32 32 0 0 0 247.58 32h-47.16a32 32 0 0 0-30.3 21.69L39.41 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16h-19.58l23.3-64h152.56l23.3 64H304a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176.85 272L224 142.51 271.15 272z" />
+      </svg>
+    ),
     value: "text"
     value: "text"
   }
   }
 ];
 ];
@@ -821,9 +830,7 @@ class App extends React.Component<{}, AppState> {
     draggingElement: null,
     draggingElement: null,
     resizingElement: null,
     resizingElement: null,
     elementType: "selection",
     elementType: "selection",
-    exportBackground: false,
-    exportVisibleOnly: true,
-    exportPadding: 10,
+    exportBackground: true,
     currentItemStrokeColor: "#000000",
     currentItemStrokeColor: "#000000",
     currentItemBackgroundColor: "#ffffff",
     currentItemBackgroundColor: "#ffffff",
     viewBackgroundColor: "#ffffff",
     viewBackgroundColor: "#ffffff",
@@ -940,6 +947,8 @@ class App extends React.Component<{}, AppState> {
     this.forceUpdate();
     this.forceUpdate();
   };
   };
 
 
+  private removeWheelEventListener: (() => void) | undefined;
+
   public render() {
   public render() {
     return (
     return (
       <div
       <div
@@ -998,9 +1007,7 @@ class App extends React.Component<{}, AppState> {
                     this.forceUpdate();
                     this.forceUpdate();
                   }}
                   }}
                 />
                 />
-                <div className="toolIcon">
-                  <FontAwesomeIcon icon={icon} />
-                </div>
+                <div className="toolIcon">{icon}</div>
               </label>
               </label>
             ))}
             ))}
           </div>
           </div>
@@ -1050,12 +1057,7 @@ class App extends React.Component<{}, AppState> {
           <div className="panelColumn">
           <div className="panelColumn">
             <button
             <button
               onClick={() => {
               onClick={() => {
-                exportAsPNG({
-                  exportBackground: this.state.exportBackground,
-                  exportVisibleOnly: this.state.exportVisibleOnly,
-                  exportPadding: this.state.exportPadding,
-                  viewBackgroundColor: this.state.viewBackgroundColor
-                });
+                exportAsPNG(this.state);
               }}
               }}
             >
             >
               Export to png
               Export to png
@@ -1070,28 +1072,6 @@ class App extends React.Component<{}, AppState> {
               />
               />
               background
               background
             </label>
             </label>
-            <label>
-              <input
-                type="checkbox"
-                checked={this.state.exportVisibleOnly}
-                onChange={e => {
-                  this.setState({ exportVisibleOnly: e.target.checked });
-                }}
-              />
-              visible area only
-            </label>
-            <div>
-              (padding:
-              <input
-                type="number"
-                value={this.state.exportPadding}
-                onChange={e => {
-                  this.setState({ exportPadding: Number(e.target.value) });
-                }}
-                disabled={!this.state.exportVisibleOnly}
-              />
-              px)
-            </div>
           </div>
           </div>
           {someElementIsSelected() && (
           {someElementIsSelected() && (
             <>
             <>
@@ -1110,13 +1090,18 @@ class App extends React.Component<{}, AppState> {
           id="canvas"
           id="canvas"
           width={window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT}
           width={window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT}
           height={window.innerHeight - CANVAS_WINDOW_OFFSET_TOP}
           height={window.innerHeight - CANVAS_WINDOW_OFFSET_TOP}
-          onWheel={e => {
-            e.preventDefault();
-            const { deltaX, deltaY } = e;
-            this.setState(state => ({
-              scrollX: state.scrollX - deltaX,
-              scrollY: state.scrollY - deltaY
-            }));
+          ref={canvas => {
+            if (this.removeWheelEventListener) {
+              this.removeWheelEventListener();
+              this.removeWheelEventListener = undefined;
+            }
+            if (canvas) {
+              canvas.addEventListener("wheel", this.handleWheel, {
+                passive: false
+              });
+              this.removeWheelEventListener = () =>
+                canvas.removeEventListener("wheel", this.handleWheel);
+            }
           }}
           }}
           onMouseDown={e => {
           onMouseDown={e => {
             // only handle left mouse button
             // only handle left mouse button
@@ -1172,7 +1157,7 @@ class App extends React.Component<{}, AppState> {
                 isResizingElements = true;
                 isResizingElements = true;
               } else {
               } else {
                 let hitElement = null;
                 let hitElement = null;
-                
+
                 // We need to to hit testing from front (end of the array) to back (beginning of the array)
                 // We need to to hit testing from front (end of the array) to back (beginning of the array)
                 for (let i = elements.length - 1; i >= 0; --i) {
                 for (let i = elements.length - 1; i >= 0; --i) {
                   if (hitTest(elements[i], x, y)) {
                   if (hitTest(elements[i], x, y)) {
@@ -1394,8 +1379,17 @@ class App extends React.Component<{}, AppState> {
     );
     );
   }
   }
 
 
+  private handleWheel = (e: WheelEvent) => {
+    e.preventDefault();
+    const { deltaX, deltaY } = e;
+    this.setState(state => ({
+      scrollX: state.scrollX - deltaX,
+      scrollY: state.scrollY - deltaY
+    }));
+  };
+
   componentDidUpdate() {
   componentDidUpdate() {
-    renderScene(rc, context, {
+    renderScene(rc, canvas, {
       scrollX: this.state.scrollX,
       scrollX: this.state.scrollX,
       scrollY: this.state.scrollY,
       scrollY: this.state.scrollY,
       viewBackgroundColor: this.state.viewBackgroundColor
       viewBackgroundColor: this.state.viewBackgroundColor

+ 23 - 2
src/styles.scss

@@ -46,8 +46,13 @@ body {
 }
 }
 
 
 .tool {
 .tool {
+  position: relative;
+
   input[type="radio"] {
   input[type="radio"] {
-    display: none;
+    position: absolute;
+    opacity: 0;
+    width: 0;
+    height: 0;
   }
   }
 
 
   input[type="radio"] {
   input[type="radio"] {
@@ -62,6 +67,10 @@ body {
       align-items: center;
       align-items: center;
 
 
       border-radius: 3px;
       border-radius: 3px;
+
+      svg {
+        height: 1em;
+      }
     }
     }
     &:hover + .toolIcon {
     &:hover + .toolIcon {
       background-color: #e7e5e5;
       background-color: #e7e5e5;
@@ -69,6 +78,9 @@ body {
     &:checked + .toolIcon {
     &:checked + .toolIcon {
       background-color: #bdbebc;
       background-color: #bdbebc;
     }
     }
+    &:focus + .toolIcon {
+      box-shadow: 0 0 0 2px steelblue;
+    }
   }
   }
 }
 }
 
 
@@ -89,6 +101,11 @@ input[type="color"] {
 
 
 input {
 input {
   margin-right: 5px;
   margin-right: 5px;
+
+  &:focus {
+    outline: transparent;
+    box-shadow: 0 0 0 2px steelblue;
+  }
 }
 }
 
 
 button {
 button {
@@ -97,7 +114,11 @@ button {
   border-radius: 4px;
   border-radius: 4px;
   margin: 2px 0;
   margin: 2px 0;
   padding: 5px;
   padding: 5px;
-  outline: none;
+  outline: transparent;
+
+  &:focus {
+    box-shadow: 0 0 0 2px steelblue;
+  }
 
 
   &:hover {
   &:hover {
     background-color: #e7e5e5;
     background-color: #e7e5e5;