diff --git a/package-lock.json b/package-lock.json index 7a836c9a4..79f18d860 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1034,6 +1034,11 @@ "@hapi/hoek": "^8.3.0" } }, + "@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" + }, "@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", @@ -1511,6 +1516,15 @@ "csstype": "^2.2.0" } }, + "@types/react-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.1.tgz", + "integrity": "sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", @@ -9369,6 +9383,11 @@ "object-visit": "^1.0.0" } }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -12094,6 +12113,19 @@ "whatwg-fetch": "^3.0.0" } }, + "react-color": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz", + "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==", + "requires": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.11", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + } + }, "react-dev-utils": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.0.0.tgz", @@ -12315,6 +12347,14 @@ "workbox-webpack-plugin": "4.3.1" } }, + "reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "requires": { + "lodash": "^4.0.1" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -14397,6 +14437,11 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 735e8be1e..1be5d6df2 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,14 @@ "main": "src/index.js", "dependencies": { "react": "16.12.0", + "react-color": "^2.17.3", "react-dom": "16.12.0", "react-scripts": "3.3.0", "roughjs": "3.1.0" }, "devDependencies": { "@types/react": "16.9.17", + "@types/react-color": "^3.0.1", "@types/react-dom": "16.9.4", "husky": "3.1.0", "lint-staged": "9.5.0", diff --git a/src/index.tsx b/src/index.tsx index df5b3d8d0..57b5533f1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,22 +1,23 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import rough from "roughjs/bin/wrappers/rough"; -import { RoughCanvas } from "roughjs/bin/canvas"; +import React from 'react'; +import ReactDOM from 'react-dom'; +import rough from 'roughjs/bin/wrappers/rough'; +import { RoughCanvas } from 'roughjs/bin/canvas'; +import { SketchPicker } from 'react-color'; -import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex"; +import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from './zindex'; -import "./styles.scss"; +import './styles.scss'; type ExcalidrawElement = ReturnType; type ExcalidrawTextElement = ExcalidrawElement & { - type: "text"; + type: 'text'; font: string; text: string; actualBoundingBoxAscent: number; }; -const LOCAL_STORAGE_KEY = "excalidraw"; -const LOCAL_STORAGE_KEY_STATE = "excalidraw-state"; +const LOCAL_STORAGE_KEY = 'excalidraw'; +const LOCAL_STORAGE_KEY_STATE = 'excalidraw-state'; const elements = Array.of(); @@ -111,7 +112,7 @@ function hitTest(element: ExcalidrawElement, x: number, y: number): boolean { // of the click is less than x pixels of any of the lines that the shape is composed of const lineThreshold = 10; - if (element.type === "ellipse") { + if (element.type === 'ellipse') { // https://stackoverflow.com/a/46007540/232122 const px = Math.abs(x - element.x - element.width / 2); const py = Math.abs(y - element.y - element.height / 2); @@ -146,7 +147,7 @@ function hitTest(element: ExcalidrawElement, x: number, y: number): boolean { }); return Math.hypot(a * tx - px, b * ty - py) < lineThreshold; - } else if (element.type === "rectangle") { + } else if (element.type === 'rectangle') { const x1 = getElementAbsoluteX1(element); const x2 = getElementAbsoluteX2(element); const y1 = getElementAbsoluteY1(element); @@ -161,7 +162,7 @@ function hitTest(element: ExcalidrawElement, x: number, y: number): boolean { distanceBetweenPointAndSegment(x, y, x2, y2, x1, y2) < lineThreshold || // C distanceBetweenPointAndSegment(x, y, x1, y2, x1, y1) < lineThreshold // D ); - } else if (element.type === "arrow") { + } else if (element.type === 'arrow') { let [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element); // The computation is done at the origin, we need to add a translation x -= element.x; @@ -175,18 +176,18 @@ function hitTest(element: ExcalidrawElement, x: number, y: number): boolean { // / distanceBetweenPointAndSegment(x, y, x4, y4, x2, y2) < lineThreshold ); - } else if (element.type === "text") { + } else if (element.type === 'text') { const x1 = getElementAbsoluteX1(element); const x2 = getElementAbsoluteX2(element); const y1 = getElementAbsoluteY1(element); const y2 = getElementAbsoluteY2(element); return x >= x1 && x <= x2 && y >= y1 && y <= y2; - } else if (element.type === "selection") { - console.warn("This should not happen, we need to investigate why it does."); + } else if (element.type === 'selection') { + console.warn('This should not happen, we need to investigate why it does.'); return false; } else { - throw new Error("Unimplemented type " + element.type); + throw new Error('Unimplemented type ' + element.type); } } @@ -196,7 +197,7 @@ function resizeTest( y: number, sceneState: SceneState ): string | false { - if (element.type === "text" || element.type === "arrow") return false; + if (element.type === 'text' || element.type === 'arrow') return false; const handlers = handlerRectangles(element, sceneState); @@ -241,7 +242,7 @@ function newElement( rc: RoughCanvas, context: CanvasRenderingContext2D, sceneState: SceneState - ) {} + ) {}, }; return element; } @@ -255,7 +256,7 @@ type SceneState = { const SCROLLBAR_WIDTH = 6; const SCROLLBAR_MARGIN = 4; -const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; +const SCROLLBAR_COLOR = 'rgba(0,0,0,0.3)'; const CANVAS_WINDOW_OFFSET_LEFT = 250; const CANVAS_WINDOW_OFFSET_TOP = 0; @@ -273,7 +274,7 @@ function getScrollbars( x: scrollBarX + SCROLLBAR_MARGIN, y: canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, width: scrollBarWidth - SCROLLBAR_MARGIN * 2, - height: SCROLLBAR_WIDTH + height: SCROLLBAR_WIDTH, }; // vertical scrollbar @@ -284,12 +285,12 @@ function getScrollbars( x: canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN, y: scrollBarY + SCROLLBAR_MARGIN, width: SCROLLBAR_WIDTH, - height: scrollBarHeight - SCROLLBAR_WIDTH * 2 + height: scrollBarHeight - SCROLLBAR_WIDTH * 2, }; return { horizontal: horizontalScrollBar, - vertical: verticalScrollBar + vertical: verticalScrollBar, }; } @@ -305,7 +306,7 @@ function isOverScrollBars( const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [ scrollBars.horizontal, - scrollBars.vertical + scrollBars.vertical, ].map( scrollBar => scrollBar.x <= x && @@ -316,7 +317,7 @@ function isOverScrollBars( return { isOverHorizontalScrollBar, - isOverVerticalScrollBar + isOverVerticalScrollBar, }; } @@ -334,60 +335,60 @@ function handlerRectangles(element: ExcalidrawElement, sceneState: SceneState) { const marginY = element.height < 0 ? 8 : -8; if (Math.abs(elementX2 - elementX1) > minimumSize) { - handlers["n"] = [ + handlers['n'] = [ elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4, elementY1 - margin + sceneState.scrollY + marginY, 8, - 8 + 8, ]; - handlers["s"] = [ + handlers['s'] = [ elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4, elementY2 - margin + sceneState.scrollY - marginY, 8, - 8 + 8, ]; } if (Math.abs(elementY2 - elementY1) > minimumSize) { - handlers["w"] = [ + handlers['w'] = [ elementX1 - margin + sceneState.scrollX + marginX, elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4, 8, - 8 + 8, ]; - handlers["e"] = [ + handlers['e'] = [ elementX2 - margin + sceneState.scrollX - marginX, elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4, 8, - 8 + 8, ]; } - handlers["nw"] = [ + handlers['nw'] = [ elementX1 - margin + sceneState.scrollX + marginX, elementY1 - margin + sceneState.scrollY + marginY, 8, - 8 + 8, ]; // nw - handlers["ne"] = [ + handlers['ne'] = [ elementX2 - margin + sceneState.scrollX - marginX, elementY1 - margin + sceneState.scrollY + marginY, 8, - 8 + 8, ]; // ne - handlers["sw"] = [ + handlers['sw'] = [ elementX1 - margin + sceneState.scrollX + marginX, elementY2 - margin + sceneState.scrollY - marginY, 8, - 8 + 8, ]; // sw - handlers["se"] = [ + handlers['se'] = [ elementX2 - margin + sceneState.scrollX - marginX, elementY2 - margin + sceneState.scrollY - marginY, 8, - 8 + 8, ]; // se return handlers; @@ -402,7 +403,7 @@ function renderScene( offsetX, offsetY, renderScrollbars = true, - renderSelection = true + renderSelection = true, }: { offsetX?: number; offsetY?: number; @@ -411,10 +412,10 @@ function renderScene( } = {} ) { if (!canvas) return; - const context = canvas.getContext("2d")!; + const context = canvas.getContext('2d')!; const fillStyle = context.fillStyle; - if (typeof sceneState.viewBackgroundColor === "string") { + if (typeof sceneState.viewBackgroundColor === 'string') { context.fillStyle = sceneState.viewBackgroundColor; context.fillRect(0, 0, canvas.width, canvas.height); } else { @@ -426,8 +427,8 @@ function renderScene( sceneState = { ...sceneState, - scrollX: typeof offsetX === "number" ? offsetX : sceneState.scrollX, - scrollY: typeof offsetY === "number" ? offsetY : sceneState.scrollY + scrollX: typeof offsetX === 'number' ? offsetX : sceneState.scrollX, + scrollY: typeof offsetY === 'number' ? offsetY : sceneState.scrollY, }; elements.forEach(element => { @@ -450,8 +451,8 @@ function renderScene( context.setLineDash(lineDash); if ( - element.type !== "text" && - element.type !== "arrow" && + element.type !== 'text' && + element.type !== 'arrow' && selectedIndices.length === 1 ) { const handlers = handlerRectangles(element, sceneState); @@ -491,28 +492,28 @@ function saveAsJSON() { const serialized = JSON.stringify({ version: 1, source: window.location.origin, - elements + elements, }); saveFile( - "excalidraw.json", - "data:text/plain;charset=utf-8," + encodeURIComponent(serialized) + 'excalidraw.json', + 'data:text/plain;charset=utf-8,' + encodeURIComponent(serialized) ); } function loadFromJSON() { - const input = document.createElement("input"); + const input = document.createElement('input'); const reader = new FileReader(); - input.type = "file"; - input.accept = ".json"; + input.type = 'file'; + input.accept = '.json'; input.onchange = () => { if (!input.files!.length) { - alert("A file was not selected."); + alert('A file was not selected.'); return; } - reader.readAsText(input.files![0], "utf8"); + reader.readAsText(input.files![0], 'utf8'); }; input.click(); @@ -531,7 +532,7 @@ function loadFromJSON() { function exportAsPNG({ exportBackground, exportPadding = 10, - viewBackgroundColor + viewBackgroundColor, }: { exportBackground: boolean; exportPadding?: number; @@ -539,7 +540,7 @@ function exportAsPNG({ scrollX: number; scrollY: number; }) { - if (!elements.length) return window.alert("Cannot export empty canvas."); + if (!elements.length) return window.alert('Cannot export empty canvas.'); // calculate smallest area to fit the contents in @@ -559,8 +560,8 @@ function exportAsPNG({ return Math.abs(x > y ? x - y : y - x); } - const tempCanvas = document.createElement("canvas"); - tempCanvas.style.display = "none"; + 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; @@ -571,17 +572,17 @@ function exportAsPNG({ { viewBackgroundColor: exportBackground ? viewBackgroundColor : null, scrollX: 0, - scrollY: 0 + scrollY: 0, }, { offsetX: -subCanvasX1 + exportPadding, offsetY: -subCanvasY1 + exportPadding, renderScrollbars: false, - renderSelection: false + renderSelection: false, } ); - saveFile("excalidraw.png", tempCanvas.toDataURL("image/png")); + saveFile('excalidraw.png', tempCanvas.toDataURL('image/png')); // clean up the DOM if (tempCanvas !== canvas) tempCanvas.remove(); @@ -589,9 +590,9 @@ function exportAsPNG({ function saveFile(name: string, data: string) { // create a temporary elem which we'll use to download the image - const link = document.createElement("a"); - link.setAttribute("download", name); - link.setAttribute("href", data); + const link = document.createElement('a'); + link.setAttribute('download', name); + link.setAttribute('href', data); link.click(); // clean up @@ -604,7 +605,7 @@ function rotate(x1: number, y1: number, x2: number, y2: number, angle: number) { // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line return [ (x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, - (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2 + (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2, ]; } @@ -615,7 +616,7 @@ const generator = rough.generator(null, null as any); function isTextElement( element: ExcalidrawElement ): element is ExcalidrawTextElement { - return element.type === "text"; + return element.type === 'text'; } function isInputLike( @@ -649,10 +650,10 @@ function getArrowPoints(element: ExcalidrawElement) { } function generateDraw(element: ExcalidrawElement) { - if (element.type === "selection") { + if (element.type === 'selection') { element.draw = (rc, context, { scrollX, scrollY }) => { const fillStyle = context.fillStyle; - context.fillStyle = "rgba(0, 0, 255, 0.10)"; + context.fillStyle = 'rgba(0, 0, 255, 0.10)'; context.fillRect( element.x + scrollX, element.y + scrollY, @@ -661,11 +662,11 @@ function generateDraw(element: ExcalidrawElement) { ); context.fillStyle = fillStyle; }; - } else if (element.type === "rectangle") { + } else if (element.type === 'rectangle') { const shape = withCustomMathRandom(element.seed, () => { return generator.rectangle(0, 0, element.width, element.height, { stroke: element.strokeColor, - fill: element.backgroundColor + fill: element.backgroundColor, }); }); element.draw = (rc, context, { scrollX, scrollY }) => { @@ -673,7 +674,7 @@ function generateDraw(element: ExcalidrawElement) { rc.draw(shape); context.translate(-element.x - scrollX, -element.y - scrollY); }; - } else if (element.type === "ellipse") { + } else if (element.type === 'ellipse') { const shape = withCustomMathRandom(element.seed, () => generator.ellipse( element.width / 2, @@ -688,7 +689,7 @@ function generateDraw(element: ExcalidrawElement) { rc.draw(shape); context.translate(-element.x - scrollX, -element.y - scrollY); }; - } else if (element.type === "arrow") { + } else if (element.type === 'arrow') { const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element); const shapes = withCustomMathRandom(element.seed, () => [ // \ @@ -696,7 +697,7 @@ function generateDraw(element: ExcalidrawElement) { // ----- generator.line(x1, y1, x2, y2, { stroke: element.strokeColor }), // / - generator.line(x4, y4, x2, y2, { stroke: element.strokeColor }) + generator.line(x4, y4, x2, y2, { stroke: element.strokeColor }), ]); element.draw = (rc, context, { scrollX, scrollY }) => { @@ -720,7 +721,7 @@ function generateDraw(element: ExcalidrawElement) { context.font = font; }; } else { - throw new Error("Unimplemented type " + element.type); + throw new Error('Unimplemented type ' + element.type); } } @@ -752,7 +753,7 @@ function setSelection(selection: ExcalidrawElement) { const elementY1 = getElementAbsoluteY1(element); const elementY2 = getElementAbsoluteY2(element); element.isSelected = - element.type !== "selection" && + element.type !== 'selection' && selectionX1 <= elementX1 && selectionY1 <= elementY1 && selectionX2 >= elementX2 && @@ -767,7 +768,7 @@ function clearSelection() { } function resetCursor() { - document.documentElement.style.cursor = ""; + document.documentElement.style.cursor = ''; } function deleteSelectedElements() { @@ -799,7 +800,7 @@ function restore( elements.splice( 0, elements.length, - ...(typeof savedElements === "string" + ...(typeof savedElements === 'string' ? JSON.parse(savedElements) : savedElements) ); @@ -816,6 +817,7 @@ function restore( type AppState = { draggingElement: ExcalidrawElement | null; resizingElement: ExcalidrawElement | null; + currentColorPicker: 'Background' | null; elementType: string; exportBackground: boolean; currentItemStrokeColor: string; @@ -826,13 +828,13 @@ type AppState = { }; const KEYS = { - ARROW_LEFT: "ArrowLeft", - ARROW_RIGHT: "ArrowRight", - ARROW_DOWN: "ArrowDown", - ARROW_UP: "ArrowUp", - ESCAPE: "Escape", - DELETE: "Delete", - BACKSPACE: "Backspace" + ARROW_LEFT: 'ArrowLeft', + ARROW_RIGHT: 'ArrowRight', + ARROW_DOWN: 'ArrowDown', + ARROW_UP: 'ArrowUp', + ESCAPE: 'Escape', + DELETE: 'Delete', + BACKSPACE: 'Backspace', }; // We inline font-awesome icons in order to save on js size rather than including the font awesome react library @@ -844,7 +846,7 @@ const SHAPES = [ ), - value: "selection" + value: 'selection', }, { icon: ( @@ -853,7 +855,7 @@ const SHAPES = [ ), - value: "rectangle" + value: 'rectangle', }, { icon: ( @@ -862,7 +864,7 @@ const SHAPES = [ ), - value: "ellipse" + value: 'ellipse', }, { icon: ( @@ -871,7 +873,7 @@ const SHAPES = [ ), - value: "arrow" + value: 'arrow', }, { icon: ( @@ -880,19 +882,18 @@ const SHAPES = [ ), - value: "text" - } + value: 'text', + }, ]; const shapesShortcutKeys = SHAPES.map(shape => shape.value[0]); - function capitalize(str: string) { return str.charAt(0).toUpperCase() + str.slice(1); } function findElementByKey(key: string) { - const defaultElement = "selection"; + const defaultElement = 'selection'; return SHAPES.reduce((element, shape) => { if (shape.value[0] !== key) return element; @@ -932,8 +933,8 @@ let lastMouseUp: ((e: any) => void) | null = null; class App extends React.Component<{}, AppState> { public componentDidMount() { - document.addEventListener("keydown", this.onKeyDown, false); - window.addEventListener("resize", this.onResize, false); + document.addEventListener('keydown', this.onKeyDown, false); + window.addEventListener('resize', this.onResize, false); const savedState = restoreFromLocalStorage(); if (savedState) { @@ -942,20 +943,21 @@ class App extends React.Component<{}, AppState> { } public componentWillUnmount() { - document.removeEventListener("keydown", this.onKeyDown, false); - window.removeEventListener("resize", this.onResize, false); + document.removeEventListener('keydown', this.onKeyDown, false); + window.removeEventListener('resize', this.onResize, false); } public state: AppState = { draggingElement: null, resizingElement: null, - elementType: "selection", + elementType: 'selection', + currentColorPicker: null, exportBackground: true, - currentItemStrokeColor: "#000000", - currentItemBackgroundColor: "#ffffff", - viewBackgroundColor: "#ffffff", + currentItemStrokeColor: '#000000', + currentItemBackgroundColor: '#ffffff', + viewBackgroundColor: '#ffffff', scrollX: 0, - scrollY: 0 + scrollY: 0, }; private onResize = () => { @@ -993,13 +995,13 @@ class App extends React.Component<{}, AppState> { event.metaKey && event.shiftKey && event.altKey && - event.code === "KeyB" + event.code === 'KeyB' ) { this.moveOneLeft(); event.preventDefault(); // Send to back: Cmd-Shift-B - } else if (event.metaKey && event.shiftKey && event.code === "KeyB") { + } else if (event.metaKey && event.shiftKey && event.code === 'KeyB') { this.moveAllLeft(); event.preventDefault(); @@ -1008,18 +1010,18 @@ class App extends React.Component<{}, AppState> { event.metaKey && event.shiftKey && event.altKey && - event.code === "KeyF" + event.code === 'KeyF' ) { this.moveOneRight(); event.preventDefault(); // Bring to front: Cmd-Shift-F - } else if (event.metaKey && event.shiftKey && event.code === "KeyF") { + } else if (event.metaKey && event.shiftKey && event.code === 'KeyF') { this.moveAllRight(); event.preventDefault(); // Select all: Cmd-A - } else if (event.metaKey && event.code === "KeyA") { + } else if (event.metaKey && event.code === 'KeyA') { elements.forEach(element => { element.isSelected = true; }); @@ -1027,7 +1029,7 @@ class App extends React.Component<{}, AppState> { event.preventDefault(); } else if (shapesShortcutKeys.includes(event.key.toLowerCase())) { this.setState({ elementType: findElementByKey(event.key) }); - } else if (event.metaKey && event.code === "KeyZ") { + } else if (event.metaKey && event.code === 'KeyZ') { let lastEntry = stateHistory.pop(); // If nothing was changed since last, take the previous one if (generateHistoryCurrentEntry() === lastEntry) { @@ -1047,12 +1049,12 @@ class App extends React.Component<{}, AppState> { }; private clearCanvas = () => { - if (window.confirm("This will clear the whole canvas. Are you sure?")) { + if (window.confirm('This will clear the whole canvas. Are you sure?')) { elements.splice(0, elements.length); this.setState({ - viewBackgroundColor: "#ffffff", + viewBackgroundColor: '#ffffff', scrollX: 0, - scrollY: 0 + scrollY: 0, }); this.forceUpdate(); } @@ -1089,7 +1091,7 @@ class App extends React.Component<{}, AppState> { className="container" onCut={e => { e.clipboardData.setData( - "text/plain", + 'text/plain', JSON.stringify(elements.filter(element => element.isSelected)) ); deleteSelectedElements(); @@ -1098,13 +1100,13 @@ class App extends React.Component<{}, AppState> { }} onCopy={e => { e.clipboardData.setData( - "text/plain", + 'text/plain', JSON.stringify(elements.filter(element => element.isSelected)) ); e.preventDefault(); }} onPaste={e => { - const paste = e.clipboardData.getData("text"); + const paste = e.clipboardData.getData('text'); let parsedElements; try { parsedElements = JSON.parse(paste); @@ -1131,7 +1133,11 @@ class App extends React.Component<{}, AppState> {

Shapes

{SHAPES.map(({ value, icon }) => ( -

Colors

-