{"version":3,"sources":["zindex.ts","index.tsx"],"names":["swap","elements","indexA","indexB","element","moveOneLeft","indicesToMove","sort","a","b","isSorted","forEach","index","i","moveOneRight","reversedIndicesToMove","length","moveAllLeft","leftMostElements","map","reverse","concat","pos","moveAllRight","rightMostElements","Array","of","skipHistory","stateHistory","generateHistoryCurrentEntry","JSON","stringify","isSelected","restoreHistoryEntry","entry","newElements","parse","splice","newElement","generateDraw","push","randomSeed","Math","floor","random","withCustomMathRandom","seed","cb","imul","LCG","result","distanceBetweenPointAndSegment","x","y","x1","y1","x2","y2","xx","yy","C","D","lenSquare","param","dx","dy","hypot","hitTest","type","px","abs","width","py","height","tx","ty","ex","ey","rx","ry","qx","qy","r","q","min","max","t","getElementAbsoluteX1","getElementAbsoluteX2","getElementAbsoluteY1","getElementAbsoluteY2","getArrowPoints","x3","y3","x4","y4","console","warn","Error","resizeTest","sceneState","handlers","handlerRectangles","filter","Object","keys","key","handler","scrollX","scrollY","getScrollbars","canvasWidth","canvasHeight","scrollBarWidth","horizontalScrollBar","SCROLLBAR_MARGIN","scrollBarHeight","horizontal","vertical","SCROLLBAR_WIDTH","elementX1","elementX2","elementY1","elementY2","marginX","marginY","renderScene","rc","canvas","offsetX","offsetY","renderScrollbars","renderSelection","context","getContext","fillStyle","viewBackgroundColor","fillRect","clearRect","selectedIndices","getSelectedIndices","draw","lineDash","getLineDash","setLineDash","strokeRect","margin","values","scrollBars","window","devicePixelRatio","saveFile","name","data","link","document","createElement","setAttribute","click","remove","rotate","angle","cos","sin","generator","rough","isTextElement","isInputLike","target","HTMLInputElement","HTMLTextAreaElement","HTMLSelectElement","distance","minSize","xs","ys","PI","shape","rectangle","stroke","strokeColor","fill","backgroundColor","translate","ellipse","shapes","line","font","fillText","text","actualBoundingBoxAscent","clearSelection","resetCursor","documentElement","style","cursor","deleteSelectedElements","restore","savedElements","savedState","e","KEYS","SHAPES","icon","viewBox","d","value","shapesShortcutKeys","findElementByKey","reduce","isArrowKey","keyCode","someElementIsSelected","some","lastCanvasWidth","lastCanvasHeight","lastMouseUp","App","state","draggingElement","resizingElement","elementType","exportBackground","currentItemStrokeColor","currentItemBackgroundColor","onResize","forceUpdate","onKeyDown","event","preventDefault","step","shiftKey","metaKey","altKey","code","includes","toLowerCase","setState","lastEntry","pop","undefined","clearCanvas","confirm","removeWheelEventListener","handleWheel","deltaX","deltaY","addEventListener","this","localStorage","getItem","removeEventListener","innerWidth","innerHeight","className","onCut","clipboardData","setData","onCopy","onPaste","parsedElements","paste","getData","isArray","parsedElement","checked","onChange","onClick","title","exportPadding","alert","subCanvasX1","Infinity","subCanvasX2","subCanvasY1","subCanvasY2","tempCanvas","display","body","appendChild","toDataURL","exportAsPNG","serialized","version","source","location","origin","encodeURIComponent","saveAsJSON","input","reader","FileReader","accept","onchange","files","readAsText","Promise","resolve","onloadend","readyState","DONE","loadFromJSON","then","id","ref","passive","scale","onMouseDown","button","activeElement","blur","clientX","clientY","resizeHandle","isDraggingElements","isResizingElements","resizeElement","find","hitElement","prompt","measureText","actualBoundingBoxDescent","lastX","lastY","onMouseMove","HTMLElement","el","selectedElements","selection","selectionX1","selectionX2","selectionY1","selectionY2","setSelection","onMouseUp","newEntry","setItem","React","Component","rootElement","getElementById","ReactDOM","render"],"mappings":"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","file":"static/js/main.3def8d06.chunk.js","sourcesContent":["function swap(elements: T[], indexA: number, indexB: number) {\n const element = elements[indexA];\n elements[indexA] = elements[indexB];\n elements[indexB] = element;\n}\n\nexport function moveOneLeft(elements: T[], indicesToMove: number[]) {\n indicesToMove.sort((a: number, b: number) => a - b);\n let isSorted = true;\n // We go from left to right to avoid overriding the wrong elements\n indicesToMove.forEach((index, i) => {\n // We don't want to bubble the first elements that are sorted as they are\n // already in their correct position\n isSorted = isSorted && index === i;\n if (isSorted) {\n return;\n }\n swap(elements, index - 1, index);\n });\n}\n\nexport function moveOneRight(elements: T[], indicesToMove: number[]) {\n const reversedIndicesToMove = indicesToMove.sort(\n (a: number, b: number) => b - a\n );\n let isSorted = true;\n\n // We go from right to left to avoid overriding the wrong elements\n reversedIndicesToMove.forEach((index, i) => {\n // We don't want to bubble the first elements that are sorted as they are\n // already in their correct position\n isSorted = isSorted && index === elements.length - i - 1;\n if (isSorted) {\n return;\n }\n swap(elements, index + 1, index);\n });\n}\n\n// Let's go through an example\n// | |\n// [a, b, c, d, e, f, g]\n// -->\n// [c, f, a, b, d, e, g]\n//\n// We are going to override all the elements we want to move, so we keep them in an array\n// that we will restore at the end.\n// [c, f]\n//\n// From now on, we'll never read those values from the array anymore\n// |1 |0\n// [a, b, _, d, e, _, g]\n//\n// The idea is that we want to shift all the elements between the marker 0 and 1\n// by one slot to the right.\n//\n// |1 |0\n// [a, b, _, d, e, _, g]\n// -> ->\n//\n// which gives us\n//\n// |1 |0\n// [a, b, _, _, d, e, g]\n//\n// Now, we need to move all the elements from marker 1 to the beginning by two (not one)\n// slots to the right, which gives us\n//\n// |1 |0\n// [a, b, _, _, d, e, g]\n// ---|--^ ^\n// ------|\n//\n// which gives us\n//\n// |1 |0\n// [_, _, a, b, d, e, g]\n//\n// At this point, we can fill back the leftmost elements with the array we saved at\n// the beggining\n//\n// |1 |0\n// [c, f, a, b, d, e, g]\n//\n// And we are done!\nexport function moveAllLeft(elements: T[], indicesToMove: number[]) {\n indicesToMove.sort((a: number, b: number) => a - b);\n\n // Copy the elements to move\n const leftMostElements = indicesToMove.map(index => elements[index]);\n\n const reversedIndicesToMove = indicesToMove\n // We go from right to left to avoid overriding elements.\n .reverse()\n // We add 0 for the final marker\n .concat([0]);\n\n reversedIndicesToMove.forEach((index, i) => {\n // We skip the first one as it is not paired with anything else\n if (i === 0) {\n return;\n }\n\n // We go from the next marker to the right (i - 1) to the current one (index)\n for (let pos = reversedIndicesToMove[i - 1] - 1; pos >= index; --pos) {\n // We move by 1 the first time, 2 the second... So we can use the index i in the array\n elements[pos + i] = elements[pos];\n }\n });\n\n // The final step\n leftMostElements.forEach((element, i) => {\n elements[i] = element;\n });\n}\n\n// Let's go through an example\n// | |\n// [a, b, c, d, e, f, g]\n// -->\n// [a, b, d, e, g, c, f]\n//\n// We are going to override all the elements we want to move, so we keep them in an array\n// that we will restore at the end.\n// [c, f]\n//\n// From now on, we'll never read those values from the array anymore\n// |0 |1\n// [a, b, _, d, e, _, g]\n//\n// The idea is that we want to shift all the elements between the marker 0 and 1\n// by one slot to the left.\n//\n// |0 |1\n// [a, b, _, d, e, _, g]\n// <- <-\n//\n// which gives us\n//\n// |0 |1\n// [a, b, d, e, _, _, g]\n//\n// Now, we need to move all the elements from marker 1 to the end by two (not one)\n// slots to the left, which gives us\n//\n// |0 |1\n// [a, b, d, e, _, _, g]\n// ^------\n//\n// which gives us\n//\n// |0 |1\n// [a, b, d, e, g, _, _]\n//\n// At this point, we can fill back the rightmost elements with the array we saved at\n// the beggining\n//\n// |0 |1\n// [a, b, d, e, g, c, f]\n//\n// And we are done!\nexport function moveAllRight(elements: T[], indicesToMove: number[]) {\n const reversedIndicesToMove = indicesToMove.sort(\n (a: number, b: number) => b - a\n );\n\n // Copy the elements to move\n const rightMostElements = reversedIndicesToMove.map(index => elements[index]);\n\n indicesToMove = reversedIndicesToMove\n // We go from left to right to avoid overriding elements.\n .reverse()\n // We last element index for the final marker\n .concat([elements.length]);\n\n indicesToMove.forEach((index, i) => {\n // We skip the first one as it is not paired with anything else\n if (i === 0) {\n return;\n }\n\n // We go from the next marker to the left (i - 1) to the current one (index)\n for (let pos = indicesToMove[i - 1] + 1; pos < index; ++pos) {\n // We move by 1 the first time, 2 the second... So we can use the index i in the array\n elements[pos - i] = elements[pos];\n }\n });\n\n // The final step\n rightMostElements.forEach((element, i) => {\n elements[elements.length - i - 1] = element;\n });\n}\n","import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport rough from \"roughjs/bin/wrappers/rough\";\nimport { RoughCanvas } from \"roughjs/bin/canvas\";\n\nimport { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from \"./zindex\";\n\nimport \"./styles.scss\";\n\ntype ExcalidrawElement = ReturnType;\ntype ExcalidrawTextElement = ExcalidrawElement & {\n type: \"text\";\n font: string;\n text: string;\n actualBoundingBoxAscent: number;\n};\n\nconst LOCAL_STORAGE_KEY = \"excalidraw\";\nconst LOCAL_STORAGE_KEY_STATE = \"excalidraw-state\";\n\nconst elements = Array.of();\n\nlet skipHistory = false;\nconst stateHistory: string[] = [];\nfunction generateHistoryCurrentEntry() {\n return JSON.stringify(\n elements.map(element => ({ ...element, isSelected: false }))\n );\n}\nfunction pushHistoryEntry(newEntry: string) {\n if (\n stateHistory.length > 0 &&\n stateHistory[stateHistory.length - 1] === newEntry\n ) {\n // If the last entry is the same as this one, ignore it\n return;\n }\n stateHistory.push(newEntry);\n}\nfunction restoreHistoryEntry(entry: string) {\n const newElements = JSON.parse(entry);\n elements.splice(0, elements.length);\n newElements.forEach((newElement: ExcalidrawElement) => {\n generateDraw(newElement);\n elements.push(newElement);\n });\n // When restoring, we shouldn't add an history entry otherwise we'll be stuck with it and can't go back\n skipHistory = true;\n}\n\n// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript/47593316#47593316\nconst LCG = (seed: number) => () =>\n ((2 ** 31 - 1) & (seed = Math.imul(48271, seed))) / 2 ** 31;\n\nfunction randomSeed() {\n return Math.floor(Math.random() * 2 ** 31);\n}\n\n// Unfortunately, roughjs doesn't support a seed attribute (https://github.com/pshihn/rough/issues/27).\n// We can achieve the same result by overriding the Math.random function with a\n// pseudo random generator that supports a random seed and swapping it back after.\nfunction withCustomMathRandom(seed: number, cb: () => T): T {\n const random = Math.random;\n Math.random = LCG(seed);\n const result = cb();\n Math.random = random;\n return result;\n}\n\n// https://stackoverflow.com/a/6853926/232122\nfunction distanceBetweenPointAndSegment(\n x: number,\n y: number,\n x1: number,\n y1: number,\n x2: number,\n y2: number\n) {\n const A = x - x1;\n const B = y - y1;\n const C = x2 - x1;\n const D = y2 - y1;\n\n const dot = A * C + B * D;\n const lenSquare = C * C + D * D;\n let param = -1;\n if (lenSquare !== 0) {\n // in case of 0 length line\n param = dot / lenSquare;\n }\n\n let xx, yy;\n if (param < 0) {\n xx = x1;\n yy = y1;\n } else if (param > 1) {\n xx = x2;\n yy = y2;\n } else {\n xx = x1 + param * C;\n yy = y1 + param * D;\n }\n\n const dx = x - xx;\n const dy = y - yy;\n return Math.hypot(dx, dy);\n}\n\nfunction hitTest(element: ExcalidrawElement, x: number, y: number): boolean {\n // For shapes that are composed of lines, we only enable point-selection when the distance\n // of the click is less than x pixels of any of the lines that the shape is composed of\n const lineThreshold = 10;\n\n if (element.type === \"ellipse\") {\n // https://stackoverflow.com/a/46007540/232122\n const px = Math.abs(x - element.x - element.width / 2);\n const py = Math.abs(y - element.y - element.height / 2);\n\n let tx = 0.707;\n let ty = 0.707;\n\n const a = element.width / 2;\n const b = element.height / 2;\n\n [0, 1, 2, 3].forEach(x => {\n const xx = a * tx;\n const yy = b * ty;\n\n const ex = ((a * a - b * b) * tx ** 3) / a;\n const ey = ((b * b - a * a) * ty ** 3) / b;\n\n const rx = xx - ex;\n const ry = yy - ey;\n\n const qx = px - ex;\n const qy = py - ey;\n\n const r = Math.hypot(ry, rx);\n const q = Math.hypot(qy, qx);\n\n tx = Math.min(1, Math.max(0, ((qx * r) / q + ex) / a));\n ty = Math.min(1, Math.max(0, ((qy * r) / q + ey) / b));\n const t = Math.hypot(ty, tx);\n tx /= t;\n ty /= t;\n });\n\n return Math.hypot(a * tx - px, b * ty - py) < lineThreshold;\n } else if (element.type === \"rectangle\") {\n const x1 = getElementAbsoluteX1(element);\n const x2 = getElementAbsoluteX2(element);\n const y1 = getElementAbsoluteY1(element);\n const y2 = getElementAbsoluteY2(element);\n\n // (x1, y1) --A-- (x2, y1)\n // |D |B\n // (x1, y2) --C-- (x2, y2)\n return (\n distanceBetweenPointAndSegment(x, y, x1, y1, x2, y1) < lineThreshold || // A\n distanceBetweenPointAndSegment(x, y, x2, y1, x2, y2) < lineThreshold || // B\n distanceBetweenPointAndSegment(x, y, x2, y2, x1, y2) < lineThreshold || // C\n distanceBetweenPointAndSegment(x, y, x1, y2, x1, y1) < lineThreshold // D\n );\n } else if (element.type === \"arrow\") {\n let [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element);\n // The computation is done at the origin, we need to add a translation\n x -= element.x;\n y -= element.y;\n\n return (\n // \\\n distanceBetweenPointAndSegment(x, y, x3, y3, x2, y2) < lineThreshold ||\n // -----\n distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) < lineThreshold ||\n // /\n distanceBetweenPointAndSegment(x, y, x4, y4, x2, y2) < lineThreshold\n );\n } else if (element.type === \"text\") {\n const x1 = getElementAbsoluteX1(element);\n const x2 = getElementAbsoluteX2(element);\n const y1 = getElementAbsoluteY1(element);\n const y2 = getElementAbsoluteY2(element);\n\n return x >= x1 && x <= x2 && y >= y1 && y <= y2;\n } else if (element.type === \"selection\") {\n console.warn(\"This should not happen, we need to investigate why it does.\");\n return false;\n } else {\n throw new Error(\"Unimplemented type \" + element.type);\n }\n}\n\nfunction resizeTest(\n element: ExcalidrawElement,\n x: number,\n y: number,\n sceneState: SceneState\n): string | false {\n if (element.type === \"text\" || element.type === \"arrow\") return false;\n\n const handlers = handlerRectangles(element, sceneState);\n\n const filter = Object.keys(handlers).filter(key => {\n const handler = handlers[key];\n\n return (\n x + sceneState.scrollX >= handler[0] &&\n x + sceneState.scrollX <= handler[0] + handler[2] &&\n y + sceneState.scrollY >= handler[1] &&\n y + sceneState.scrollY <= handler[1] + handler[3]\n );\n });\n\n if (filter.length > 0) {\n return filter[0];\n }\n\n return false;\n}\n\nfunction newElement(\n type: string,\n x: number,\n y: number,\n strokeColor: string,\n backgroundColor: string,\n width = 0,\n height = 0\n) {\n const element = {\n type: type,\n x: x,\n y: y,\n width: width,\n height: height,\n isSelected: false,\n strokeColor: strokeColor,\n backgroundColor: backgroundColor,\n seed: randomSeed(),\n draw(\n rc: RoughCanvas,\n context: CanvasRenderingContext2D,\n sceneState: SceneState\n ) {}\n };\n return element;\n}\n\ntype SceneState = {\n scrollX: number;\n scrollY: number;\n // null indicates transparent bg\n viewBackgroundColor: string | null;\n};\n\nconst SCROLLBAR_WIDTH = 6;\nconst SCROLLBAR_MARGIN = 4;\nconst SCROLLBAR_COLOR = \"rgba(0,0,0,0.3)\";\nconst CANVAS_WINDOW_OFFSET_LEFT = 250;\nconst CANVAS_WINDOW_OFFSET_TOP = 0;\n\nfunction getScrollbars(\n canvasWidth: number,\n canvasHeight: number,\n scrollX: number,\n scrollY: number\n) {\n // horizontal scrollbar\n const sceneWidth = canvasWidth + Math.abs(scrollX);\n const scrollBarWidth = (canvasWidth * canvasWidth) / sceneWidth;\n const scrollBarX = scrollX > 0 ? 0 : canvasWidth - scrollBarWidth;\n const horizontalScrollBar = {\n x: scrollBarX + SCROLLBAR_MARGIN,\n y: canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,\n width: scrollBarWidth - SCROLLBAR_MARGIN * 2,\n height: SCROLLBAR_WIDTH\n };\n\n // vertical scrollbar\n const sceneHeight = canvasHeight + Math.abs(scrollY);\n const scrollBarHeight = (canvasHeight * canvasHeight) / sceneHeight;\n const scrollBarY = scrollY > 0 ? 0 : canvasHeight - scrollBarHeight;\n const verticalScrollBar = {\n x: canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,\n y: scrollBarY + SCROLLBAR_MARGIN,\n width: SCROLLBAR_WIDTH,\n height: scrollBarHeight - SCROLLBAR_WIDTH * 2\n };\n\n return {\n horizontal: horizontalScrollBar,\n vertical: verticalScrollBar\n };\n}\n\nfunction handlerRectangles(element: ExcalidrawElement, sceneState: SceneState) {\n const elementX1 = element.x;\n const elementX2 = element.x + element.width;\n const elementY1 = element.y;\n const elementY2 = element.y + element.height;\n\n const margin = 4;\n const minimumSize = 40;\n const handlers: { [handler: string]: number[] } = {};\n\n const marginX = element.width < 0 ? 8 : -8;\n const marginY = element.height < 0 ? 8 : -8;\n\n if (Math.abs(elementX2 - elementX1) > minimumSize) {\n handlers[\"n\"] = [\n elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4,\n elementY1 - margin + sceneState.scrollY + marginY,\n 8,\n 8\n ];\n\n handlers[\"s\"] = [\n elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4,\n elementY2 - margin + sceneState.scrollY - marginY,\n 8,\n 8\n ];\n }\n\n if (Math.abs(elementY2 - elementY1) > minimumSize) {\n handlers[\"w\"] = [\n elementX1 - margin + sceneState.scrollX + marginX,\n elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4,\n 8,\n 8\n ];\n\n handlers[\"e\"] = [\n elementX2 - margin + sceneState.scrollX - marginX,\n elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4,\n 8,\n 8\n ];\n }\n\n handlers[\"nw\"] = [\n elementX1 - margin + sceneState.scrollX + marginX,\n elementY1 - margin + sceneState.scrollY + marginY,\n 8,\n 8\n ]; // nw\n handlers[\"ne\"] = [\n elementX2 - margin + sceneState.scrollX - marginX,\n elementY1 - margin + sceneState.scrollY + marginY,\n 8,\n 8\n ]; // ne\n handlers[\"sw\"] = [\n elementX1 - margin + sceneState.scrollX + marginX,\n elementY2 - margin + sceneState.scrollY - marginY,\n 8,\n 8\n ]; // sw\n handlers[\"se\"] = [\n elementX2 - margin + sceneState.scrollX - marginX,\n elementY2 - margin + sceneState.scrollY - marginY,\n 8,\n 8\n ]; // se\n\n return handlers;\n}\n\nfunction renderScene(\n rc: RoughCanvas,\n canvas: HTMLCanvasElement,\n sceneState: SceneState,\n // extra options, currently passed by export helper\n {\n offsetX,\n offsetY,\n renderScrollbars = true,\n renderSelection = true\n }: {\n offsetX?: number;\n offsetY?: number;\n renderScrollbars?: boolean;\n renderSelection?: boolean;\n } = {}\n) {\n if (!canvas) return;\n const context = canvas.getContext(\"2d\")!;\n\n const fillStyle = context.fillStyle;\n if (typeof sceneState.viewBackgroundColor === \"string\") {\n context.fillStyle = sceneState.viewBackgroundColor;\n context.fillRect(0, 0, canvas.width, canvas.height);\n } else {\n context.clearRect(0, 0, canvas.width, canvas.height);\n }\n context.fillStyle = fillStyle;\n\n const selectedIndices = getSelectedIndices();\n\n sceneState = {\n ...sceneState,\n scrollX: typeof offsetX === \"number\" ? offsetX : sceneState.scrollX,\n scrollY: typeof offsetY === \"number\" ? offsetY : sceneState.scrollY\n };\n\n elements.forEach(element => {\n element.draw(rc, context, sceneState);\n if (renderSelection && element.isSelected) {\n const margin = 4;\n\n const elementX1 = getElementAbsoluteX1(element);\n const elementX2 = getElementAbsoluteX2(element);\n const elementY1 = getElementAbsoluteY1(element);\n const elementY2 = getElementAbsoluteY2(element);\n const lineDash = context.getLineDash();\n context.setLineDash([8, 4]);\n context.strokeRect(\n elementX1 - margin + sceneState.scrollX,\n elementY1 - margin + sceneState.scrollY,\n elementX2 - elementX1 + margin * 2,\n elementY2 - elementY1 + margin * 2\n );\n context.setLineDash(lineDash);\n\n if (\n element.type !== \"text\" &&\n element.type !== \"arrow\" &&\n selectedIndices.length === 1\n ) {\n const handlers = handlerRectangles(element, sceneState);\n Object.values(handlers).forEach(handler => {\n context.strokeRect(handler[0], handler[1], handler[2], handler[3]);\n });\n }\n }\n });\n\n if (renderScrollbars) {\n const scrollBars = getScrollbars(\n context.canvas.width / window.devicePixelRatio,\n context.canvas.height / window.devicePixelRatio,\n sceneState.scrollX,\n sceneState.scrollY\n );\n\n context.fillStyle = SCROLLBAR_COLOR;\n context.fillRect(\n scrollBars.horizontal.x,\n scrollBars.horizontal.y,\n scrollBars.horizontal.width,\n scrollBars.horizontal.height\n );\n context.fillRect(\n scrollBars.vertical.x,\n scrollBars.vertical.y,\n scrollBars.vertical.width,\n scrollBars.vertical.height\n );\n context.fillStyle = fillStyle;\n }\n}\n\nfunction saveAsJSON() {\n const serialized = JSON.stringify({\n version: 1,\n source: window.location.origin,\n elements\n });\n\n saveFile(\n \"excalidraw.json\",\n \"data:text/plain;charset=utf-8,\" + encodeURIComponent(serialized)\n );\n}\n\nfunction loadFromJSON() {\n const input = document.createElement(\"input\");\n const reader = new FileReader();\n input.type = \"file\";\n input.accept = \".json\";\n\n input.onchange = () => {\n if (!input.files!.length) {\n alert(\"A file was not selected.\");\n return;\n }\n\n reader.readAsText(input.files![0], \"utf8\");\n };\n\n input.click();\n\n return new Promise(resolve => {\n reader.onloadend = () => {\n if (reader.readyState === FileReader.DONE) {\n const data = JSON.parse(reader.result as string);\n restore(data.elements, null);\n resolve();\n }\n };\n });\n}\n\nfunction exportAsPNG({\n exportBackground,\n exportPadding = 10,\n viewBackgroundColor\n}: {\n exportBackground: boolean;\n exportPadding?: number;\n viewBackgroundColor: string;\n scrollX: number;\n scrollY: number;\n}) {\n if (!elements.length) return window.alert(\"Cannot export empty canvas.\");\n\n // calculate smallest area to fit the contents in\n\n let subCanvasX1 = Infinity;\n let subCanvasX2 = 0;\n let subCanvasY1 = Infinity;\n let subCanvasY2 = 0;\n\n elements.forEach(element => {\n subCanvasX1 = Math.min(subCanvasX1, getElementAbsoluteX1(element));\n subCanvasX2 = Math.max(subCanvasX2, getElementAbsoluteX2(element));\n subCanvasY1 = Math.min(subCanvasY1, getElementAbsoluteY1(element));\n subCanvasY2 = Math.max(subCanvasY2, getElementAbsoluteY2(element));\n });\n\n function distance(x: number, y: number) {\n return Math.abs(x > y ? x - y : y - x);\n }\n\n const tempCanvas = document.createElement(\"canvas\");\n tempCanvas.style.display = \"none\";\n document.body.appendChild(tempCanvas);\n tempCanvas.width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;\n tempCanvas.height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;\n\n renderScene(\n rough.canvas(tempCanvas),\n tempCanvas,\n {\n viewBackgroundColor: exportBackground ? viewBackgroundColor : null,\n scrollX: 0,\n scrollY: 0\n },\n {\n offsetX: -subCanvasX1 + exportPadding,\n offsetY: -subCanvasY1 + exportPadding,\n renderScrollbars: false,\n renderSelection: false\n }\n );\n\n saveFile(\"excalidraw.png\", tempCanvas.toDataURL(\"image/png\"));\n\n // clean up the DOM\n if (tempCanvas !== canvas) tempCanvas.remove();\n}\n\nfunction saveFile(name: string, data: string) {\n // create a temporary elem which we'll use to download the image\n const link = document.createElement(\"a\");\n link.setAttribute(\"download\", name);\n link.setAttribute(\"href\", data);\n link.click();\n\n // clean up\n link.remove();\n}\n\nfunction rotate(x1: number, y1: number, x2: number, y2: number, angle: number) {\n // π‘Žβ€²π‘₯=(π‘Žπ‘₯βˆ’π‘π‘₯)cosπœƒβˆ’(π‘Žπ‘¦βˆ’π‘π‘¦)sinπœƒ+𝑐π‘₯\n // π‘Žβ€²π‘¦=(π‘Žπ‘₯βˆ’π‘π‘₯)sinπœƒ+(π‘Žπ‘¦βˆ’π‘π‘¦)cosπœƒ+𝑐𝑦.\n // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line\n return [\n (x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,\n (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2\n ];\n}\n\n// Casting second argument (DrawingSurface) to any,\n// because it is requred by TS definitions and not required at runtime\nconst generator = rough.generator(null, null as any);\n\nfunction isTextElement(\n element: ExcalidrawElement\n): element is ExcalidrawTextElement {\n return element.type === \"text\";\n}\n\nfunction isInputLike(\n target: Element | EventTarget | null\n): target is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement {\n return (\n target instanceof HTMLInputElement ||\n target instanceof HTMLTextAreaElement ||\n target instanceof HTMLSelectElement\n );\n}\n\nfunction getArrowPoints(element: ExcalidrawElement) {\n const x1 = 0;\n const y1 = 0;\n const x2 = element.width;\n const y2 = element.height;\n\n const size = 30; // pixels\n const distance = Math.hypot(x2 - x1, y2 - y1);\n // Scale down the arrow until we hit a certain size so that it doesn't look weird\n const minSize = Math.min(size, distance / 2);\n const xs = x2 - ((x2 - x1) / distance) * minSize;\n const ys = y2 - ((y2 - y1) / distance) * minSize;\n\n const angle = 20; // degrees\n const [x3, y3] = rotate(xs, ys, x2, y2, (-angle * Math.PI) / 180);\n const [x4, y4] = rotate(xs, ys, x2, y2, (angle * Math.PI) / 180);\n\n return [x1, y1, x2, y2, x3, y3, x4, y4];\n}\n\nfunction generateDraw(element: ExcalidrawElement) {\n if (element.type === \"selection\") {\n element.draw = (rc, context, { scrollX, scrollY }) => {\n const fillStyle = context.fillStyle;\n context.fillStyle = \"rgba(0, 0, 255, 0.10)\";\n context.fillRect(\n element.x + scrollX,\n element.y + scrollY,\n element.width,\n element.height\n );\n context.fillStyle = fillStyle;\n };\n } else if (element.type === \"rectangle\") {\n const shape = withCustomMathRandom(element.seed, () => {\n return generator.rectangle(0, 0, element.width, element.height, {\n stroke: element.strokeColor,\n fill: element.backgroundColor\n });\n });\n element.draw = (rc, context, { scrollX, scrollY }) => {\n context.translate(element.x + scrollX, element.y + scrollY);\n rc.draw(shape);\n context.translate(-element.x - scrollX, -element.y - scrollY);\n };\n } else if (element.type === \"ellipse\") {\n const shape = withCustomMathRandom(element.seed, () =>\n generator.ellipse(\n element.width / 2,\n element.height / 2,\n element.width,\n element.height,\n { stroke: element.strokeColor, fill: element.backgroundColor }\n )\n );\n element.draw = (rc, context, { scrollX, scrollY }) => {\n context.translate(element.x + scrollX, element.y + scrollY);\n rc.draw(shape);\n context.translate(-element.x - scrollX, -element.y - scrollY);\n };\n } else if (element.type === \"arrow\") {\n const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element);\n const shapes = withCustomMathRandom(element.seed, () => [\n // \\\n generator.line(x3, y3, x2, y2, { stroke: element.strokeColor }),\n // -----\n generator.line(x1, y1, x2, y2, { stroke: element.strokeColor }),\n // /\n generator.line(x4, y4, x2, y2, { stroke: element.strokeColor })\n ]);\n\n element.draw = (rc, context, { scrollX, scrollY }) => {\n context.translate(element.x + scrollX, element.y + scrollY);\n shapes.forEach(shape => rc.draw(shape));\n context.translate(-element.x - scrollX, -element.y - scrollY);\n };\n return;\n } else if (isTextElement(element)) {\n element.draw = (rc, context, { scrollX, scrollY }) => {\n const font = context.font;\n context.font = element.font;\n const fillStyle = context.fillStyle;\n context.fillStyle = element.strokeColor;\n context.fillText(\n element.text,\n element.x + scrollX,\n element.y + element.actualBoundingBoxAscent + scrollY\n );\n context.fillStyle = fillStyle;\n context.font = font;\n };\n } else {\n throw new Error(\"Unimplemented type \" + element.type);\n }\n}\n\n// If the element is created from right to left, the width is going to be negative\n// This set of functions retrieves the absolute position of the 4 points.\n// We can't just always normalize it since we need to remember the fact that an arrow\n// is pointing left or right.\nfunction getElementAbsoluteX1(element: ExcalidrawElement) {\n return element.width >= 0 ? element.x : element.x + element.width;\n}\nfunction getElementAbsoluteX2(element: ExcalidrawElement) {\n return element.width >= 0 ? element.x + element.width : element.x;\n}\nfunction getElementAbsoluteY1(element: ExcalidrawElement) {\n return element.height >= 0 ? element.y : element.y + element.height;\n}\nfunction getElementAbsoluteY2(element: ExcalidrawElement) {\n return element.height >= 0 ? element.y + element.height : element.y;\n}\n\nfunction setSelection(selection: ExcalidrawElement) {\n const selectionX1 = getElementAbsoluteX1(selection);\n const selectionX2 = getElementAbsoluteX2(selection);\n const selectionY1 = getElementAbsoluteY1(selection);\n const selectionY2 = getElementAbsoluteY2(selection);\n elements.forEach(element => {\n const elementX1 = getElementAbsoluteX1(element);\n const elementX2 = getElementAbsoluteX2(element);\n const elementY1 = getElementAbsoluteY1(element);\n const elementY2 = getElementAbsoluteY2(element);\n element.isSelected =\n element.type !== \"selection\" &&\n selectionX1 <= elementX1 &&\n selectionY1 <= elementY1 &&\n selectionX2 >= elementX2 &&\n selectionY2 >= elementY2;\n });\n}\n\nfunction clearSelection() {\n elements.forEach(element => {\n element.isSelected = false;\n });\n}\n\nfunction resetCursor() {\n document.documentElement.style.cursor = \"\";\n}\n\nfunction deleteSelectedElements() {\n for (let i = elements.length - 1; i >= 0; --i) {\n if (elements[i].isSelected) {\n elements.splice(i, 1);\n }\n }\n}\n\nfunction save(state: AppState) {\n localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(elements));\n localStorage.setItem(LOCAL_STORAGE_KEY_STATE, JSON.stringify(state));\n}\n\nfunction restoreFromLocalStorage() {\n const savedElements = localStorage.getItem(LOCAL_STORAGE_KEY);\n const savedState = localStorage.getItem(LOCAL_STORAGE_KEY_STATE);\n\n return restore(savedElements, savedState);\n}\n\nfunction restore(\n savedElements: string | ExcalidrawElement[] | null,\n savedState: string | null\n) {\n try {\n if (savedElements) {\n elements.splice(\n 0,\n elements.length,\n ...(typeof savedElements === \"string\"\n ? JSON.parse(savedElements)\n : savedElements)\n );\n elements.forEach((element: ExcalidrawElement) => generateDraw(element));\n }\n\n return savedState ? JSON.parse(savedState) : null;\n } catch (e) {\n elements.splice(0, elements.length);\n return null;\n }\n}\n\ntype AppState = {\n draggingElement: ExcalidrawElement | null;\n resizingElement: ExcalidrawElement | null;\n elementType: string;\n exportBackground: boolean;\n currentItemStrokeColor: string;\n currentItemBackgroundColor: string;\n viewBackgroundColor: string;\n scrollX: number;\n scrollY: number;\n};\n\nconst KEYS = {\n ARROW_LEFT: \"ArrowLeft\",\n ARROW_RIGHT: \"ArrowRight\",\n ARROW_DOWN: \"ArrowDown\",\n ARROW_UP: \"ArrowUp\",\n ESCAPE: \"Escape\",\n DELETE: \"Delete\",\n BACKSPACE: \"Backspace\"\n};\n\n// We inline font-awesome icons in order to save on js size rather than including the font awesome react library\nconst SHAPES = [\n {\n icon: (\n // fa-mouse-pointer\n \n \n \n ),\n value: \"selection\"\n },\n {\n icon: (\n // fa-square\n \n \n \n ),\n value: \"rectangle\"\n },\n {\n icon: (\n // fa-circle\n \n \n \n ),\n value: \"ellipse\"\n },\n {\n icon: (\n // fa-long-arrow-alt-right\n \n \n \n ),\n value: \"arrow\"\n },\n {\n icon: (\n // fa-font\n \n \n \n ),\n value: \"text\"\n }\n];\n\nconst shapesShortcutKeys = SHAPES.map(shape => shape.value[0]);\n\nfunction findElementByKey(key: string) {\n const defaultElement = \"selection\";\n return SHAPES.reduce((element, shape) => {\n if (shape.value[0] !== key) return element;\n\n return shape.value;\n }, defaultElement);\n}\n\nfunction isArrowKey(keyCode: string) {\n return (\n keyCode === KEYS.ARROW_LEFT ||\n keyCode === KEYS.ARROW_RIGHT ||\n keyCode === KEYS.ARROW_DOWN ||\n keyCode === KEYS.ARROW_UP\n );\n}\n\nfunction getSelectedIndices() {\n const selectedIndices: number[] = [];\n elements.forEach((element, index) => {\n if (element.isSelected) {\n selectedIndices.push(index);\n }\n });\n return selectedIndices;\n}\n\nconst someElementIsSelected = () =>\n elements.some(element => element.isSelected);\n\nconst ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;\nconst ELEMENT_TRANSLATE_AMOUNT = 1;\n\nlet lastCanvasWidth = -1;\nlet lastCanvasHeight = -1;\n\nlet lastMouseUp: ((e: any) => void) | null = null;\n\nclass App extends React.Component<{}, AppState> {\n public componentDidMount() {\n document.addEventListener(\"keydown\", this.onKeyDown, false);\n window.addEventListener(\"resize\", this.onResize, false);\n\n const savedState = restoreFromLocalStorage();\n if (savedState) {\n this.setState(savedState);\n }\n }\n\n public componentWillUnmount() {\n document.removeEventListener(\"keydown\", this.onKeyDown, false);\n window.removeEventListener(\"resize\", this.onResize, false);\n }\n\n public state: AppState = {\n draggingElement: null,\n resizingElement: null,\n elementType: \"selection\",\n exportBackground: true,\n currentItemStrokeColor: \"#000000\",\n currentItemBackgroundColor: \"#ffffff\",\n viewBackgroundColor: \"#ffffff\",\n scrollX: 0,\n scrollY: 0\n };\n\n private onResize = () => {\n this.forceUpdate();\n };\n\n private onKeyDown = (event: KeyboardEvent) => {\n if (isInputLike(event.target)) return;\n\n if (event.key === KEYS.ESCAPE) {\n clearSelection();\n this.forceUpdate();\n event.preventDefault();\n } else if (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) {\n deleteSelectedElements();\n this.forceUpdate();\n event.preventDefault();\n } else if (isArrowKey(event.key)) {\n const step = event.shiftKey\n ? ELEMENT_SHIFT_TRANSLATE_AMOUNT\n : ELEMENT_TRANSLATE_AMOUNT;\n elements.forEach(element => {\n if (element.isSelected) {\n if (event.key === KEYS.ARROW_LEFT) element.x -= step;\n else if (event.key === KEYS.ARROW_RIGHT) element.x += step;\n else if (event.key === KEYS.ARROW_UP) element.y -= step;\n else if (event.key === KEYS.ARROW_DOWN) element.y += step;\n }\n });\n this.forceUpdate();\n event.preventDefault();\n\n // Send backward: Cmd-Shift-Alt-B\n } else if (\n event.metaKey &&\n event.shiftKey &&\n event.altKey &&\n event.code === \"KeyB\"\n ) {\n this.moveOneLeft();\n event.preventDefault();\n\n // Send to back: Cmd-Shift-B\n } else if (event.metaKey && event.shiftKey && event.code === \"KeyB\") {\n this.moveAllLeft();\n event.preventDefault();\n\n // Bring forward: Cmd-Shift-Alt-F\n } else if (\n event.metaKey &&\n event.shiftKey &&\n event.altKey &&\n event.code === \"KeyF\"\n ) {\n this.moveOneRight();\n event.preventDefault();\n\n // Bring to front: Cmd-Shift-F\n } else if (event.metaKey && event.shiftKey && event.code === \"KeyF\") {\n this.moveAllRight();\n event.preventDefault();\n\n // Select all: Cmd-A\n } else if (event.metaKey && event.code === \"KeyA\") {\n elements.forEach(element => {\n element.isSelected = true;\n });\n this.forceUpdate();\n event.preventDefault();\n } else if (shapesShortcutKeys.includes(event.key.toLowerCase())) {\n this.setState({ elementType: findElementByKey(event.key) });\n } else if (event.metaKey && event.code === \"KeyZ\") {\n let lastEntry = stateHistory.pop();\n // If nothing was changed since last, take the previous one\n if (generateHistoryCurrentEntry() === lastEntry) {\n lastEntry = stateHistory.pop();\n }\n if (lastEntry !== undefined) {\n restoreHistoryEntry(lastEntry);\n }\n this.forceUpdate();\n event.preventDefault();\n }\n };\n\n private deleteSelectedElements = () => {\n deleteSelectedElements();\n this.forceUpdate();\n };\n\n private clearCanvas = () => {\n if (window.confirm(\"This will clear the whole canvas. Are you sure?\")) {\n elements.splice(0, elements.length);\n this.setState({\n viewBackgroundColor: \"#ffffff\",\n scrollX: 0,\n scrollY: 0\n });\n this.forceUpdate();\n }\n };\n\n private moveAllLeft = () => {\n moveAllLeft(elements, getSelectedIndices());\n this.forceUpdate();\n };\n\n private moveOneLeft = () => {\n moveOneLeft(elements, getSelectedIndices());\n this.forceUpdate();\n };\n\n private moveAllRight = () => {\n moveAllRight(elements, getSelectedIndices());\n this.forceUpdate();\n };\n\n private moveOneRight = () => {\n moveOneRight(elements, getSelectedIndices());\n this.forceUpdate();\n };\n\n private removeWheelEventListener: (() => void) | undefined;\n\n public render() {\n const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT;\n const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP;\n\n return (\n {\n e.clipboardData.setData(\n \"text/plain\",\n JSON.stringify(elements.filter(element => element.isSelected))\n );\n deleteSelectedElements();\n this.forceUpdate();\n e.preventDefault();\n }}\n onCopy={e => {\n e.clipboardData.setData(\n \"text/plain\",\n JSON.stringify(elements.filter(element => element.isSelected))\n );\n e.preventDefault();\n }}\n onPaste={e => {\n const paste = e.clipboardData.getData(\"text\");\n let parsedElements;\n try {\n parsedElements = JSON.parse(paste);\n } catch (e) {}\n if (\n Array.isArray(parsedElements) &&\n parsedElements.length > 0 &&\n parsedElements[0].type // need to implement a better check here...\n ) {\n clearSelection();\n parsedElements.forEach(parsedElement => {\n parsedElement.x += 10;\n parsedElement.y += 10;\n parsedElement.seed = randomSeed();\n generateDraw(parsedElement);\n elements.push(parsedElement);\n });\n this.forceUpdate();\n }\n e.preventDefault();\n }}\n >\n
\n

Shapes

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

Colors

\n
\n \n \n \n
\n

Canvas

\n
\n \n Clear canvas\n \n
\n

Export

\n
\n {\n exportAsPNG(this.state);\n }}\n >\n Export to png\n \n \n
\n

Save/Load

\n
\n {\n saveAsJSON();\n }}\n >\n Save as...\n \n {\n loadFromJSON().then(() => this.forceUpdate());\n }}\n >\n Load file...\n \n
\n {someElementIsSelected() && (\n <>\n

Shape options

\n
\n \n \n \n \n \n
\n \n )}\n
\n {\n if (this.removeWheelEventListener) {\n this.removeWheelEventListener();\n this.removeWheelEventListener = undefined;\n }\n if (canvas) {\n canvas.addEventListener(\"wheel\", this.handleWheel, {\n passive: false\n });\n this.removeWheelEventListener = () =>\n canvas.removeEventListener(\"wheel\", this.handleWheel);\n\n // Whenever React sets the width/height of the canvas element,\n // the context loses the scale transform. We need to re-apply it\n if (\n canvasWidth !== lastCanvasWidth ||\n canvasHeight !== lastCanvasHeight\n ) {\n lastCanvasWidth = canvasWidth;\n lastCanvasHeight = canvasHeight;\n canvas\n .getContext(\"2d\")!\n .scale(window.devicePixelRatio, window.devicePixelRatio);\n }\n }\n }}\n onMouseDown={e => {\n if (lastMouseUp !== null) {\n // Unfortunately, sometimes we don't get a mouseup after a mousedown,\n // this can happen when a contextual menu or alert is triggered. In order to avoid\n // being in a weird state, we clean up on the next mousedown\n lastMouseUp(e);\n }\n // only handle left mouse button\n if (e.button !== 0) return;\n // fixes mousemove causing selection of UI texts #32\n e.preventDefault();\n // Preventing the event above disables default behavior\n // of defocusing potentially focused input, which is what we want\n // when clicking inside the canvas.\n if (isInputLike(document.activeElement)) {\n document.activeElement.blur();\n }\n\n const x =\n e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;\n const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;\n const element = newElement(\n this.state.elementType,\n x,\n y,\n this.state.currentItemStrokeColor,\n this.state.currentItemBackgroundColor\n );\n let resizeHandle: string | false = false;\n let isDraggingElements = false;\n let isResizingElements = false;\n if (this.state.elementType === \"selection\") {\n const resizeElement = elements.find(element => {\n return resizeTest(element, x, y, {\n scrollX: this.state.scrollX,\n scrollY: this.state.scrollY,\n viewBackgroundColor: this.state.viewBackgroundColor\n });\n });\n\n this.setState({\n resizingElement: resizeElement ? resizeElement : null\n });\n\n if (resizeElement) {\n resizeHandle = resizeTest(resizeElement, x, y, {\n scrollX: this.state.scrollX,\n scrollY: this.state.scrollY,\n viewBackgroundColor: this.state.viewBackgroundColor\n });\n document.documentElement.style.cursor = `${resizeHandle}-resize`;\n isResizingElements = true;\n } else {\n let hitElement = null;\n // We need to to hit testing from front (end of the array) to back (beginning of the array)\n for (let i = elements.length - 1; i >= 0; --i) {\n if (hitTest(elements[i], x, y)) {\n hitElement = elements[i];\n break;\n }\n }\n\n // If we click on something\n if (hitElement) {\n if (hitElement.isSelected) {\n // If that element is not already selected, do nothing,\n // we're likely going to drag it\n } else {\n // We unselect every other elements unless shift is pressed\n if (!e.shiftKey) {\n clearSelection();\n }\n // No matter what, we select it\n hitElement.isSelected = true;\n }\n } else {\n // If we don't click on anything, let's remove all the selected elements\n clearSelection();\n }\n\n isDraggingElements = someElementIsSelected();\n\n if (isDraggingElements) {\n document.documentElement.style.cursor = \"move\";\n }\n }\n }\n\n if (isTextElement(element)) {\n resetCursor();\n const text = prompt(\"What text do you want?\");\n if (text === null) {\n return;\n }\n element.text = text;\n element.font = \"20px Virgil\";\n const font = context.font;\n context.font = element.font;\n const {\n actualBoundingBoxAscent,\n actualBoundingBoxDescent,\n width\n } = context.measureText(element.text);\n element.actualBoundingBoxAscent = actualBoundingBoxAscent;\n context.font = font;\n const height = actualBoundingBoxAscent + actualBoundingBoxDescent;\n // Center the text\n element.x -= width / 2;\n element.y -= actualBoundingBoxAscent;\n element.width = width;\n element.height = height;\n }\n\n generateDraw(element);\n elements.push(element);\n if (this.state.elementType === \"text\") {\n this.setState({\n draggingElement: null,\n elementType: \"selection\"\n });\n element.isSelected = true;\n } else {\n this.setState({ draggingElement: element });\n }\n\n let lastX = x;\n let lastY = y;\n\n const onMouseMove = (e: MouseEvent) => {\n const target = e.target;\n if (!(target instanceof HTMLElement)) {\n return;\n }\n\n if (isResizingElements && this.state.resizingElement) {\n const el = this.state.resizingElement;\n const selectedElements = elements.filter(el => el.isSelected);\n if (selectedElements.length === 1) {\n const x =\n e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;\n const y =\n e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;\n selectedElements.forEach(element => {\n switch (resizeHandle) {\n case \"nw\":\n element.width += element.x - lastX;\n element.height += element.y - lastY;\n element.x = lastX;\n element.y = lastY;\n break;\n case \"ne\":\n element.width = lastX - element.x;\n element.height += element.y - lastY;\n element.y = lastY;\n break;\n case \"sw\":\n element.width += element.x - lastX;\n element.x = lastX;\n element.height = lastY - element.y;\n break;\n case \"se\":\n element.width += x - lastX;\n if (e.shiftKey) {\n element.height = element.width;\n } else {\n element.height += y - lastY;\n }\n break;\n case \"n\":\n element.height += element.y - lastY;\n element.y = lastY;\n break;\n case \"w\":\n element.width += element.x - lastX;\n element.x = lastX;\n break;\n case \"s\":\n element.height = lastY - element.y;\n break;\n case \"e\":\n element.width = lastX - element.x;\n break;\n }\n\n el.x = element.x;\n el.y = element.y;\n generateDraw(el);\n });\n lastX = x;\n lastY = y;\n // We don't want to save history when resizing an element\n skipHistory = true;\n this.forceUpdate();\n return;\n }\n }\n\n if (isDraggingElements) {\n const selectedElements = elements.filter(el => el.isSelected);\n if (selectedElements.length) {\n const x =\n e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;\n const y =\n e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;\n selectedElements.forEach(element => {\n element.x += x - lastX;\n element.y += y - lastY;\n });\n lastX = x;\n lastY = y;\n // We don't want to save history when dragging an element to initially size it\n skipHistory = true;\n this.forceUpdate();\n return;\n }\n }\n\n // It is very important to read this.state within each move event,\n // otherwise we would read a stale one!\n const draggingElement = this.state.draggingElement;\n if (!draggingElement) return;\n let width =\n e.clientX -\n CANVAS_WINDOW_OFFSET_LEFT -\n draggingElement.x -\n this.state.scrollX;\n let height =\n e.clientY -\n CANVAS_WINDOW_OFFSET_TOP -\n draggingElement.y -\n this.state.scrollY;\n draggingElement.width = width;\n // Make a perfect square or circle when shift is enabled\n draggingElement.height = e.shiftKey ? width : height;\n\n generateDraw(draggingElement);\n\n if (this.state.elementType === \"selection\") {\n setSelection(draggingElement);\n }\n // We don't want to save history when moving an element\n skipHistory = true;\n this.forceUpdate();\n };\n\n const onMouseUp = (e: MouseEvent) => {\n const { draggingElement, elementType } = this.state;\n\n lastMouseUp = null;\n window.removeEventListener(\"mousemove\", onMouseMove);\n window.removeEventListener(\"mouseup\", onMouseUp);\n\n resetCursor();\n\n // if no element is clicked, clear the selection and redraw\n if (draggingElement === null) {\n clearSelection();\n this.forceUpdate();\n return;\n }\n\n if (elementType === \"selection\") {\n if (isDraggingElements) {\n isDraggingElements = false;\n }\n elements.pop();\n } else {\n draggingElement.isSelected = true;\n }\n\n this.setState({\n draggingElement: null,\n elementType: \"selection\"\n });\n this.forceUpdate();\n };\n\n lastMouseUp = onMouseUp;\n\n window.addEventListener(\"mousemove\", onMouseMove);\n window.addEventListener(\"mouseup\", onMouseUp);\n\n // We don't want to save history on mouseDown, only on mouseUp when it's fully configured\n skipHistory = true;\n this.forceUpdate();\n }}\n />\n \n );\n }\n\n private handleWheel = (e: WheelEvent) => {\n e.preventDefault();\n const { deltaX, deltaY } = e;\n this.setState(state => ({\n scrollX: state.scrollX - deltaX,\n scrollY: state.scrollY - deltaY\n }));\n };\n\n componentDidUpdate() {\n renderScene(rc, canvas, {\n scrollX: this.state.scrollX,\n scrollY: this.state.scrollY,\n viewBackgroundColor: this.state.viewBackgroundColor\n });\n save(this.state);\n if (!skipHistory) {\n pushHistoryEntry(generateHistoryCurrentEntry());\n }\n skipHistory = false;\n }\n}\n\nconst rootElement = document.getElementById(\"root\");\nReactDOM.render(, rootElement);\nconst canvas = document.getElementById(\"canvas\") as HTMLCanvasElement;\nconst rc = rough.canvas(canvas);\nconst context = canvas.getContext(\"2d\")!;\n\nReactDOM.render(, rootElement);\n"],"sourceRoot":""}