Center element on paste (#248)

* Center element on paste

* paste on cursor position

* correctly center elements

* rename vars
This commit is contained in:
Faustino Kialungila 2020-01-09 12:34:46 +01:00 committed by GitHub
parent a73e4e28aa
commit 1ea72e9134
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 29 deletions

View File

@ -21,7 +21,6 @@ Go to https://www.excalidraw.com to start sketching
<a href="https://twitter.com/lucasazzola/status/1215126440330416128"><img width="429" src="https://user-images.githubusercontent.com/197597/72039003-48e99580-3258-11ea-8daa-85dd055f2a82.png"> <a href="https://twitter.com/lucasazzola/status/1215126440330416128"><img width="429" src="https://user-images.githubusercontent.com/197597/72039003-48e99580-3258-11ea-8daa-85dd055f2a82.png">
## Run the code ## Run the code
### Code Sandbox ### Code Sandbox

View File

@ -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)) { if (isTextElement(element)) {
element.font = `${value}px ${element.font.split("px ")[1]}`; 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: "Virgil", text: "Virgil" },
{value: "Helvetica", text: "Helvetica"}, { value: "Helvetica", text: "Helvetica" },
{value: "Courier", text: "Courier"}, { 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)) { if (isTextElement(element)) {
element.font = `${element.font.split("px ")[0]}px ${value}`; 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; let subCanvasX1 = Infinity;
if (y == null) y = 10 - this.state.scrollY; 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.x += dx - minX;
duplicate.y += dy; duplicate.y += dy - minY;
elements.push(duplicate); elements.push(duplicate);
}); });
this.forceUpdate(); this.forceUpdate();
} }
}; };

View File

@ -22,9 +22,7 @@ export const hasStroke = (elements: ExcalidrawElement[]) =>
); );
export const hasText = (elements: ExcalidrawElement[]) => export const hasText = (elements: ExcalidrawElement[]) =>
elements.some( elements.some(element => element.isSelected && element.type === "text");
element => element.isSelected && element.type === "text"
);
export function getElementAtPosition( export function getElementAtPosition(
elements: ExcalidrawElement[], elements: ExcalidrawElement[],

View File

@ -11,5 +11,7 @@ export type AppState = {
viewBackgroundColor: string; viewBackgroundColor: string;
scrollX: number; scrollX: number;
scrollY: number; scrollY: number;
cursorX: number;
cursorY: number;
name: string; name: string;
}; };