mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-03 20:34:40 +01:00 
			
		
		
		
	Center element on paste (#248)
* Center element on paste * paste on cursor position * correctly center elements * rename vars
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							a73e4e28aa
						
					
				
				
					commit
					1ea72e9134
				
			@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,7 @@ export const hasStroke = (elements: ExcalidrawElement[]) =>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
export const hasText = (elements: ExcalidrawElement[]) =>
 | 
			
		||||
  elements.some(
 | 
			
		||||
    element => element.isSelected && element.type === "text"
 | 
			
		||||
  );
 | 
			
		||||
  elements.some(element => element.isSelected && element.type === "text");
 | 
			
		||||
 | 
			
		||||
export function getElementAtPosition(
 | 
			
		||||
  elements: ExcalidrawElement[],
 | 
			
		||||
 
 | 
			
		||||
@@ -11,5 +11,7 @@ export type AppState = {
 | 
			
		||||
  viewBackgroundColor: string;
 | 
			
		||||
  scrollX: number;
 | 
			
		||||
  scrollY: number;
 | 
			
		||||
  cursorX: number;
 | 
			
		||||
  cursorY: number;
 | 
			
		||||
  name: string;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user