| 
					
				 | 
			
			
				@@ -10,7 +10,8 @@ import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   duplicateElement, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   resizeTest, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   isTextElement, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  textWysiwyg 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  textWysiwyg, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  getElementAbsoluteCoords 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } from "./element"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   clearSelection, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -115,6 +116,7 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   public componentDidMount() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     document.addEventListener("keydown", this.onKeyDown, false); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    document.addEventListener("mousemove", this.getCurrentCursorPosition); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     window.addEventListener("resize", this.onResize, false); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const savedState = restoreFromLocalStorage(elements); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -125,6 +127,11 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   public componentWillUnmount() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     document.removeEventListener("keydown", this.onKeyDown, false); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    document.removeEventListener( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      "mousemove", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.getCurrentCursorPosition, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      false 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     window.removeEventListener("resize", this.onResize, false); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -139,6 +146,8 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     viewBackgroundColor: "#ffffff", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     scrollX: 0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     scrollY: 0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    cursorX: 0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    cursorY: 0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     name: DEFAULT_PROJECT_NAME 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -146,6 +155,10 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.forceUpdate(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getCurrentCursorPosition = (e: MouseEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    this.setState({ cursorX: e.x, cursorY: e.y }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   private onKeyDown = (event: KeyboardEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if (isInputLike(event.target)) return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -263,7 +276,7 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         element.fillStyle = pastedElement?.fillStyle; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         element.opacity = pastedElement?.opacity; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         element.roughness = pastedElement?.roughness; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if(isTextElement(element)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (isTextElement(element)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           element.font = pastedElement?.font; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           this.redrawTextBoundingBox(element); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -331,11 +344,11 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  private pasteFromClipboard = (x?: number, y?: number) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private pasteFromClipboard = () => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if (navigator.clipboard) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       navigator.clipboard 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         .readText() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        .then(text => this.addElementsFromPaste(text, x, y)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        .then(text => this.addElementsFromPaste(text)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -345,7 +358,7 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     element.height = metrics.height; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     element.baseline = metrics.baseline; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.forceUpdate(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   public render() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -480,39 +493,45 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 <h5>Font size</h5> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 <ButtonSelect 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   options={[ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    { value: 16, text: "Small" },                     
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { value: 16, text: "Small" }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { value: 20, text: "Medium" }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { value: 28, text: "Large" }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { value: 36, text: "Very Large" } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   ]} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   value={getSelectedAttribute( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     elements, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    element => isTextElement(element) && +element.font.split("px ")[0] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    element => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      isTextElement(element) && +element.font.split("px ")[0] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   )} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   onChange={value => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     this.changeProperty(element => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                      if(isTextElement(element)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        element.font = `${value}px ${element.font.split("px ")[1]}`; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      if (isTextElement(element)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        element.font = `${value}px ${ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                          element.font.split("px ")[1] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        }`; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         this.redrawTextBoundingBox(element); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 <h5>Font familly</h5> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                <ButtonSelect  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                <ButtonSelect 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   options={[ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    {value: "Virgil", text: "Virgil"}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    {value: "Helvetica", text: "Helvetica"}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    {value: "Courier", text: "Courier"}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { value: "Virgil", text: "Virgil" }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { value: "Helvetica", text: "Helvetica" }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { value: "Courier", text: "Courier" } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   ]} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   value={getSelectedAttribute( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     elements, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    element => isTextElement(element) && element.font.split("px ")[1] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    element => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      isTextElement(element) && element.font.split("px ")[1] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   )} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   onChange={value => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     this.changeProperty(element => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                      if(isTextElement(element)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        element.font = `${element.font.split("px ")[0]}px ${value}`; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      if (isTextElement(element)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        element.font = `${ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                          element.font.split("px ")[0] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        }px ${value}`; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         this.redrawTextBoundingBox(element); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     }) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -609,7 +628,7 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 options: [ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   navigator.clipboard && { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     label: "Paste", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    action: () => this.pasteFromClipboard(x, y) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    action: () => this.pasteFromClipboard() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 ], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 top: e.clientY, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -632,7 +651,7 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 navigator.clipboard && { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   label: "Paste", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  action: () => this.pasteFromClipboard(x, y) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  action: () => this.pasteFromClipboard() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 { label: "Copy Styles", action: this.copyStyles }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 { label: "Paste Styles", action: this.pasteStyles }, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1105,7 +1124,7 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     })); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  private addElementsFromPaste = (paste: string, x?: number, y?: number) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private addElementsFromPaste = (paste: string) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     let parsedElements; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       parsedElements = JSON.parse(paste); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1117,19 +1136,47 @@ export class App extends React.Component<{}, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       clearSelection(elements); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if (x == null) x = 10 - this.state.scrollX; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if (y == null) y = 10 - this.state.scrollY; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      let subCanvasX1 = Infinity; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      let subCanvasX2 = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      let subCanvasY1 = Infinity; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      let subCanvasY2 = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const minX = Math.min(...parsedElements.map(element => element.x)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const minY = Math.min(...parsedElements.map(element => element.y)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      const dx = x - minX; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      const dy = y - minY; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const distance = (x: number, y: number) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return Math.abs(x > y ? x - y : y - x); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      parsedElements.forEach(parsedElement => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        const [x1, y1, x2, y2] = getElementAbsoluteCoords(parsedElement); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        subCanvasX1 = Math.min(subCanvasX1, x1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        subCanvasY1 = Math.min(subCanvasY1, y1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        subCanvasX2 = Math.max(subCanvasX2, x2); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        subCanvasY2 = Math.max(subCanvasY2, y2); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const elementsCenterX = distance(subCanvasX1, subCanvasX2) / 2; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const elementsCenterY = distance(subCanvasY1, subCanvasY2) / 2; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const dx = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.state.cursorX - 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.state.scrollX - 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        CANVAS_WINDOW_OFFSET_LEFT - 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        elementsCenterX; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const dy = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.state.cursorY - 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.state.scrollY - 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        CANVAS_WINDOW_OFFSET_TOP - 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        elementsCenterY; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       parsedElements.forEach(parsedElement => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         const duplicate = duplicateElement(parsedElement); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        duplicate.x += dx; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        duplicate.y += dy; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        duplicate.x += dx - minX; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        duplicate.y += dy - minY; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         elements.push(duplicate); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.forceUpdate(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 |