From 10ceffe7b18fd59bc63b81767835b5499e2b1b70 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 3 Jan 2020 20:51:27 -0800 Subject: [PATCH] Send to back --- src/index.tsx | 50 +++++++++++++++++++++++- src/zindex.test.ts | 51 ++++++++++++++++++++++++ src/zindex.ts | 97 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/zindex.test.ts create mode 100644 src/zindex.ts diff --git a/src/index.tsx b/src/index.tsx index b5c2970a9..be1bfa590 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,6 +3,8 @@ import ReactDOM from "react-dom"; import rough from "roughjs/bin/wrappers/rough"; import { RoughCanvas } from "roughjs/bin/canvas"; +import { moveOneLeft, moveAllLeft } from "./zindex"; + import "./styles.css"; type ExcalidrawElement = ReturnType; @@ -442,7 +444,8 @@ function generateDraw(element: ExcalidrawElement) { const shape = withCustomMathRandom(element.seed, () => { return generator.rectangle(0, 0, element.width, element.height, { stroke: element.strokeColor, - fill: element.backgroundColor + fill: element.backgroundColor, + fillStyle: "solid" }); }); element.draw = (rc, context, { scrollX, scrollY }) => { @@ -639,12 +642,23 @@ function isArrowKey(keyCode: string) { ); } +function getSelectedIndices() { + const selectedIndices: number[] = []; + elements.forEach((element, index) => { + if (element.isSelected) { + selectedIndices.push(index); + } + }); + return selectedIndices; +} + const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5; const ELEMENT_TRANSLATE_AMOUNT = 1; class App extends React.Component<{}, AppState> { public componentDidMount() { document.addEventListener("keydown", this.onKeyDown, false); + window.addEventListener("resize", this.onResize, false); const savedState = restore(); if (savedState) { @@ -654,6 +668,7 @@ class App extends React.Component<{}, AppState> { public componentWillUnmount() { document.removeEventListener("keydown", this.onKeyDown, false); + window.removeEventListener("resize", this.onResize, false); } public state: AppState = { @@ -669,6 +684,10 @@ class App extends React.Component<{}, AppState> { scrollY: 0 }; + private onResize = () => { + this.forceUpdate(); + }; + private onKeyDown = (event: KeyboardEvent) => { if ((event.target as HTMLElement).nodeName === "INPUT") { return; @@ -696,7 +715,26 @@ class App extends React.Component<{}, AppState> { }); this.forceUpdate(); event.preventDefault(); - } else if (event.key === "a" && event.metaKey) { + + // Send backwards: Cmd-Shift-Alt-B + } else if ( + event.metaKey && + event.shiftKey && + event.altKey && + event.code === "KeyB" + ) { + moveOneLeft(elements, getSelectedIndices()); + this.forceUpdate(); + event.preventDefault(); + + // Send to back: Cmd-Shift-B + } else if (event.metaKey && event.shiftKey && event.code === "KeyB") { + moveAllLeft(elements, getSelectedIndices()); + this.forceUpdate(); + event.preventDefault(); + + // Select all: Cmd-A + } else if (event.metaKey && event.code === "KeyA") { elements.forEach(element => { element.isSelected = true; }); @@ -705,6 +743,14 @@ class App extends React.Component<{}, AppState> { } else if (shapesShortcutKeys.includes(event.key.toLowerCase())) { this.setState({ elementType: findElementByKey(event.key) }); } + console.log( + event, + event.altKey, + event.shiftKey, + event.metaKey, + event.ctrlKey, + event.key + ); }; public render() { diff --git a/src/zindex.test.ts b/src/zindex.test.ts new file mode 100644 index 000000000..dbc66e47f --- /dev/null +++ b/src/zindex.test.ts @@ -0,0 +1,51 @@ +import { moveOneLeft, moveAllLeft } from "./zindex"; + +function expectMove(fn, elems, indices, equal) { + fn(elems, indices); + expect(elems).toEqual(equal); +} + +it("should moveOneLeft", () => { + expectMove(moveOneLeft, ["a", "b", "c", "d"], [1, 2], ["b", "c", "a", "d"]); + expectMove(moveOneLeft, ["a", "b", "c", "d"], [0], ["a", "b", "c", "d"]); + expectMove( + moveOneLeft, + ["a", "b", "c", "d"], + [0, 1, 2, 3], + ["a", "b", "c", "d"] + ); + expectMove(moveOneLeft, ["a", "b", "c", "d"], [1, 3], ["b", "a", "d", "c"]); +}); + +it("should moveAllLeft", () => { + expectMove( + moveAllLeft, + ["a", "b", "c", "d", "e", "f", "g"], + [2, 5], + ["c", "f", "a", "b", "d", "e", "g"] + ); + expectMove( + moveAllLeft, + ["a", "b", "c", "d", "e", "f", "g"], + [5], + ["f", "a", "b", "c", "d", "e", "g"] + ); + expectMove( + moveAllLeft, + ["a", "b", "c", "d", "e", "f", "g"], + [0, 1, 2, 3, 4, 5, 6], + ["a", "b", "c", "d", "e", "f", "g"] + ); + expectMove( + moveAllLeft, + ["a", "b", "c", "d", "e", "f", "g"], + [0, 1, 2], + ["a", "b", "c", "d", "e", "f", "g"] + ); + expectMove( + moveAllLeft, + ["a", "b", "c", "d", "e", "f", "g"], + [4, 5, 6], + ["e", "f", "g", "a", "b", "c", "d"] + ); +}); diff --git a/src/zindex.ts b/src/zindex.ts new file mode 100644 index 000000000..a3ed35dfc --- /dev/null +++ b/src/zindex.ts @@ -0,0 +1,97 @@ +function swap(elements: T[], indexA: number, indexB: number) { + const element = elements[indexA]; + elements[indexA] = elements[indexB]; + elements[indexB] = element; +} + +export function moveOneLeft(elements: T[], indicesToMove: number[]) { + indicesToMove.sort((a: number, b: number) => a - b); + let isSorted = true; + // We go from left to right to avoid overriding the wrong elements + indicesToMove.forEach((index, i) => { + // We don't want to bubble the first elements that are sorted as they are + // already in their correct position + isSorted = isSorted && index === i; + if (isSorted) { + return; + } + swap(elements, index - 1, index); + }); +} + +// Let's go through an example +// | | +// [a, b, c, d, e, f, g] +// --> +// [c, f, a, b, d, e, g] +// +// We are going to override all the elements we want to move, so we keep them in an array +// that we will restore at the end. +// [c, f] +// +// From now on, we'll never read those values from the array anymore +// |1 |0 +// [a, b, _, d, e, _, g] +// +// The idea is that we want to shift all the elements between the marker 0 and 1 +// by one slot to the right. +// +// |1 |0 +// [a, b, _, d, e, _, g] +// -> -> +// +// which gives us +// +// |1 |0 +// [a, b, _, _, d, e, g] +// +// Now, we need to move all the elements from marker 1 to the beginning by two (not one) +// slots to the right, which gives us +// +// |1 |0 +// [a, b, _, _, d, e, g] +// ---|--^ ^ +// ------| +// +// which gives us +// +// |1 |0 +// [_, _, a, b, d, e, g] +// +// At this point, we can fill back the leftmost elements with the array we saved at +// the beggining +// +// |1 |0 +// [c, f, a, b, d, e, g] +// +// And we are done! +export function moveAllLeft(elements: T[], indicesToMove: number[]) { + indicesToMove.sort((a: number, b: number) => a - b); + + // Copy the elements to move + const leftMostElements = indicesToMove.map(index => elements[index]); + + const reversedIndicesToMove = indicesToMove + // We go from right to left to avoid overriding elements. + .reverse() + // We add 0 for the final marker + .concat([0]); + + reversedIndicesToMove.forEach((index, i) => { + // We skip the first one as it is not paired with anything else + if (i === 0) { + return; + } + + // We go from the next marker to the right (i - 1) to the current one (index) + for (let pos = reversedIndicesToMove[i - 1] - 1; pos >= index; --pos) { + // We move by 1 the first time, 2 the second... So we can use the index i in the array + elements[pos + i] = elements[pos]; + } + }); + + // The final step + leftMostElements.forEach((element, i) => { + elements[i] = element; + }); +}