|
@@ -36,6 +36,7 @@ import { SHAPES, findShapeByKey, shapesShortcutKeys } from "./shapes";
|
|
|
import { createHistory } from "./history";
|
|
|
|
|
|
import "./styles.scss";
|
|
|
+import ContextMenu from "./components/ContextMenu";
|
|
|
|
|
|
const { elements } = createScene();
|
|
|
const { history } = createHistory();
|
|
@@ -147,8 +148,7 @@ class App extends React.Component<{}, AppState> {
|
|
|
this.forceUpdate();
|
|
|
event.preventDefault();
|
|
|
} else if (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) {
|
|
|
- deleteSelectedElements(elements);
|
|
|
- this.forceUpdate();
|
|
|
+ this.deleteSelectedElements();
|
|
|
event.preventDefault();
|
|
|
} else if (isArrowKey(event.key)) {
|
|
|
const step = event.shiftKey
|
|
@@ -307,6 +307,23 @@ class App extends React.Component<{}, AppState> {
|
|
|
this.setState({ currentItemBackgroundColor: color });
|
|
|
};
|
|
|
|
|
|
+ private copyToClipboard = () => {
|
|
|
+ if (navigator.clipboard) {
|
|
|
+ const text = JSON.stringify(
|
|
|
+ elements.filter(element => element.isSelected)
|
|
|
+ );
|
|
|
+ navigator.clipboard.writeText(text);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ private pasteFromClipboard = (x?: number, y?: number) => {
|
|
|
+ if (navigator.clipboard) {
|
|
|
+ navigator.clipboard
|
|
|
+ .readText()
|
|
|
+ .then(text => this.addElementsFromPaste(text, x, y));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
public render() {
|
|
|
const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT;
|
|
|
const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP;
|
|
@@ -332,25 +349,7 @@ class App extends React.Component<{}, AppState> {
|
|
|
}}
|
|
|
onPaste={e => {
|
|
|
const paste = e.clipboardData.getData("text");
|
|
|
- let parsedElements;
|
|
|
- try {
|
|
|
- parsedElements = JSON.parse(paste);
|
|
|
- } catch (e) {}
|
|
|
- if (
|
|
|
- Array.isArray(parsedElements) &&
|
|
|
- parsedElements.length > 0 &&
|
|
|
- parsedElements[0].type // need to implement a better check here...
|
|
|
- ) {
|
|
|
- clearSelection(elements);
|
|
|
- parsedElements.forEach(parsedElement => {
|
|
|
- parsedElement.x = 10 - this.state.scrollX;
|
|
|
- parsedElement.y = 10 - this.state.scrollY;
|
|
|
- parsedElement.seed = randomSeed();
|
|
|
- generateDraw(parsedElement);
|
|
|
- elements.push(parsedElement);
|
|
|
- });
|
|
|
- this.forceUpdate();
|
|
|
- }
|
|
|
+ this.addElementsFromPaste(paste);
|
|
|
e.preventDefault();
|
|
|
}}
|
|
|
>
|
|
@@ -577,6 +576,54 @@ class App extends React.Component<{}, AppState> {
|
|
|
}
|
|
|
}
|
|
|
}}
|
|
|
+ onContextMenu={e => {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ const x =
|
|
|
+ e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
|
|
|
+ const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
|
|
|
+
|
|
|
+ const element = getElementAtPosition(elements, x, y);
|
|
|
+ if (!element) {
|
|
|
+ ContextMenu.push({
|
|
|
+ options: [
|
|
|
+ navigator.clipboard && {
|
|
|
+ label: "Paste",
|
|
|
+ action: () => this.pasteFromClipboard(x, y)
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ top: e.clientY,
|
|
|
+ left: e.clientX
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!element.isSelected) {
|
|
|
+ clearSelection(elements);
|
|
|
+ element.isSelected = true;
|
|
|
+ this.forceUpdate();
|
|
|
+ }
|
|
|
+
|
|
|
+ ContextMenu.push({
|
|
|
+ options: [
|
|
|
+ navigator.clipboard && {
|
|
|
+ label: "Copy",
|
|
|
+ action: this.copyToClipboard
|
|
|
+ },
|
|
|
+ navigator.clipboard && {
|
|
|
+ label: "Paste",
|
|
|
+ action: () => this.pasteFromClipboard(x, y)
|
|
|
+ },
|
|
|
+ { label: "Delete", action: this.deleteSelectedElements },
|
|
|
+ { label: "Move Forward", action: this.moveOneRight },
|
|
|
+ { label: "Send to Front", action: this.moveAllRight },
|
|
|
+ { label: "Move Backwards", action: this.moveOneLeft },
|
|
|
+ { label: "Send to Back", action: this.moveAllLeft }
|
|
|
+ ],
|
|
|
+ top: e.clientY,
|
|
|
+ left: e.clientX
|
|
|
+ });
|
|
|
+ }}
|
|
|
onMouseDown={e => {
|
|
|
if (lastMouseUp !== null) {
|
|
|
// Unfortunately, sometimes we don't get a mouseup after a mousedown,
|
|
@@ -942,6 +989,40 @@ class App extends React.Component<{}, AppState> {
|
|
|
}));
|
|
|
};
|
|
|
|
|
|
+ private addElementsFromPaste = (paste: string, x?: number, y?: number) => {
|
|
|
+ let parsedElements;
|
|
|
+ try {
|
|
|
+ parsedElements = JSON.parse(paste);
|
|
|
+ } catch (e) {}
|
|
|
+ if (
|
|
|
+ Array.isArray(parsedElements) &&
|
|
|
+ parsedElements.length > 0 &&
|
|
|
+ parsedElements[0].type // need to implement a better check here...
|
|
|
+ ) {
|
|
|
+ clearSelection(elements);
|
|
|
+
|
|
|
+ let dx: number;
|
|
|
+ let dy: number;
|
|
|
+ if (x) {
|
|
|
+ let minX = Math.min(...parsedElements.map(element => element.x));
|
|
|
+ dx = x - minX;
|
|
|
+ }
|
|
|
+ if (y) {
|
|
|
+ let minY = Math.min(...parsedElements.map(element => element.y));
|
|
|
+ dy = y - minY;
|
|
|
+ }
|
|
|
+
|
|
|
+ parsedElements.forEach(parsedElement => {
|
|
|
+ parsedElement.x = dx ? parsedElement.x + dx : 10 - this.state.scrollX;
|
|
|
+ parsedElement.y = dy ? parsedElement.y + dy : 10 - this.state.scrollY;
|
|
|
+ parsedElement.seed = randomSeed();
|
|
|
+ generateDraw(parsedElement);
|
|
|
+ elements.push(parsedElement);
|
|
|
+ });
|
|
|
+ this.forceUpdate();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
componentDidUpdate() {
|
|
|
renderScene(elements, rc, canvas, {
|
|
|
scrollX: this.state.scrollX,
|