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