Send to back

This commit is contained in:
Christopher Chedeau 2020-01-03 20:51:27 -08:00
parent 8605af2b54
commit 10ceffe7b1
3 changed files with 196 additions and 2 deletions

View file

@ -3,6 +3,8 @@ import ReactDOM from "react-dom";
import rough from "roughjs/bin/wrappers/rough"; import rough from "roughjs/bin/wrappers/rough";
import { RoughCanvas } from "roughjs/bin/canvas"; import { RoughCanvas } from "roughjs/bin/canvas";
import { moveOneLeft, moveAllLeft } from "./zindex";
import "./styles.css"; import "./styles.css";
type ExcalidrawElement = ReturnType<typeof newElement>; type ExcalidrawElement = ReturnType<typeof newElement>;
@ -442,7 +444,8 @@ function generateDraw(element: ExcalidrawElement) {
const shape = withCustomMathRandom(element.seed, () => { const shape = withCustomMathRandom(element.seed, () => {
return generator.rectangle(0, 0, element.width, element.height, { return generator.rectangle(0, 0, element.width, element.height, {
stroke: element.strokeColor, stroke: element.strokeColor,
fill: element.backgroundColor fill: element.backgroundColor,
fillStyle: "solid"
}); });
}); });
element.draw = (rc, context, { scrollX, scrollY }) => { 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_SHIFT_TRANSLATE_AMOUNT = 5;
const ELEMENT_TRANSLATE_AMOUNT = 1; const ELEMENT_TRANSLATE_AMOUNT = 1;
class App extends React.Component<{}, AppState> { class App extends React.Component<{}, AppState> {
public componentDidMount() { public componentDidMount() {
document.addEventListener("keydown", this.onKeyDown, false); document.addEventListener("keydown", this.onKeyDown, false);
window.addEventListener("resize", this.onResize, false);
const savedState = restore(); const savedState = restore();
if (savedState) { if (savedState) {
@ -654,6 +668,7 @@ class App extends React.Component<{}, AppState> {
public componentWillUnmount() { public componentWillUnmount() {
document.removeEventListener("keydown", this.onKeyDown, false); document.removeEventListener("keydown", this.onKeyDown, false);
window.removeEventListener("resize", this.onResize, false);
} }
public state: AppState = { public state: AppState = {
@ -669,6 +684,10 @@ class App extends React.Component<{}, AppState> {
scrollY: 0 scrollY: 0
}; };
private onResize = () => {
this.forceUpdate();
};
private onKeyDown = (event: KeyboardEvent) => { private onKeyDown = (event: KeyboardEvent) => {
if ((event.target as HTMLElement).nodeName === "INPUT") { if ((event.target as HTMLElement).nodeName === "INPUT") {
return; return;
@ -696,7 +715,26 @@ class App extends React.Component<{}, AppState> {
}); });
this.forceUpdate(); this.forceUpdate();
event.preventDefault(); 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 => { elements.forEach(element => {
element.isSelected = true; element.isSelected = true;
}); });
@ -705,6 +743,14 @@ class App extends React.Component<{}, AppState> {
} else if (shapesShortcutKeys.includes(event.key.toLowerCase())) { } else if (shapesShortcutKeys.includes(event.key.toLowerCase())) {
this.setState({ elementType: findElementByKey(event.key) }); this.setState({ elementType: findElementByKey(event.key) });
} }
console.log(
event,
event.altKey,
event.shiftKey,
event.metaKey,
event.ctrlKey,
event.key
);
}; };
public render() { public render() {

51
src/zindex.test.ts Normal file
View file

@ -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"]
);
});

97
src/zindex.ts Normal file
View file

@ -0,0 +1,97 @@
function swap<T>(elements: T[], indexA: number, indexB: number) {
const element = elements[indexA];
elements[indexA] = elements[indexB];
elements[indexB] = element;
}
export function moveOneLeft<T>(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<T>(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;
});
}