Send to back (#98)
This commit is contained in:
parent
b1a90c0020
commit
0d5272720f
@ -11,6 +11,8 @@ import {
|
|||||||
faFont
|
faFont
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import { moveOneLeft, moveAllLeft } from "./zindex";
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
type ExcalidrawElement = ReturnType<typeof newElement>;
|
type ExcalidrawElement = ReturnType<typeof newElement>;
|
||||||
@ -647,6 +649,16 @@ 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;
|
||||||
|
|
||||||
@ -710,7 +722,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;
|
||||||
});
|
});
|
||||||
|
51
src/zindex.test.ts
Normal file
51
src/zindex.test.ts
Normal 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
97
src/zindex.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user