Make scene functions return array instead of mutate array

- Not all functions were changes; so the given argument was a new array to some
This commit is contained in:
Gasim Gasimzada 2020-01-09 12:19:55 +04:00
parent 299e7e9099
commit 2ef809fe5d
3 changed files with 101 additions and 67 deletions

View file

@ -53,7 +53,7 @@ import { Panel } from "./components/Panel";
import "./styles.scss"; import "./styles.scss";
const { elements } = createScene(); let { elements } = createScene();
const { history } = createHistory(); const { history } = createHistory();
const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`; const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
@ -106,9 +106,10 @@ export class App extends React.Component<{}, AppState> {
document.addEventListener("keydown", this.onKeyDown, false); document.addEventListener("keydown", this.onKeyDown, false);
window.addEventListener("resize", this.onResize, false); window.addEventListener("resize", this.onResize, false);
const savedState = restoreFromLocalStorage(elements); const appState = restoreFromLocalStorage(elements);
if (savedState) {
this.setState(savedState); if (appState) {
this.setState(appState);
} }
} }
@ -139,7 +140,7 @@ export class App extends React.Component<{}, AppState> {
if (isInputLike(event.target)) return; if (isInputLike(event.target)) return;
if (event.key === KEYS.ESCAPE) { if (event.key === KEYS.ESCAPE) {
clearSelection(elements); elements = clearSelection([...elements]);
this.forceUpdate(); this.forceUpdate();
event.preventDefault(); event.preventDefault();
} else if (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) { } else if (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) {
@ -149,13 +150,16 @@ export class App extends React.Component<{}, AppState> {
const step = event.shiftKey const step = event.shiftKey
? ELEMENT_SHIFT_TRANSLATE_AMOUNT ? ELEMENT_SHIFT_TRANSLATE_AMOUNT
: ELEMENT_TRANSLATE_AMOUNT; : ELEMENT_TRANSLATE_AMOUNT;
elements.forEach(element => { elements = elements.map(el => {
if (element.isSelected) { if (el.isSelected) {
const element = { ...el };
if (event.key === KEYS.ARROW_LEFT) element.x -= step; if (event.key === KEYS.ARROW_LEFT) element.x -= step;
else if (event.key === KEYS.ARROW_RIGHT) element.x += step; else if (event.key === KEYS.ARROW_RIGHT) element.x += step;
else if (event.key === KEYS.ARROW_UP) element.y -= step; else if (event.key === KEYS.ARROW_UP) element.y -= step;
else if (event.key === KEYS.ARROW_DOWN) element.y += step; else if (event.key === KEYS.ARROW_DOWN) element.y += step;
return element;
} }
return el;
}); });
this.forceUpdate(); this.forceUpdate();
event.preventDefault(); event.preventDefault();
@ -191,9 +195,12 @@ export class App extends React.Component<{}, AppState> {
event.preventDefault(); event.preventDefault();
// Select all: Cmd-A // Select all: Cmd-A
} else if (event[META_KEY] && event.code === "KeyA") { } else if (event[META_KEY] && event.code === "KeyA") {
elements.forEach(element => { let newElements = [...elements];
newElements.forEach(element => {
element.isSelected = true; element.isSelected = true;
}); });
elements = newElements;
this.forceUpdate(); this.forceUpdate();
event.preventDefault(); event.preventDefault();
} else if (shapesShortcutKeys.includes(event.key.toLowerCase())) { } else if (shapesShortcutKeys.includes(event.key.toLowerCase())) {
@ -219,13 +226,13 @@ export class App extends React.Component<{}, AppState> {
}; };
private deleteSelectedElements = () => { private deleteSelectedElements = () => {
deleteSelectedElements(elements); elements = deleteSelectedElements(elements);
this.forceUpdate(); this.forceUpdate();
}; };
private clearCanvas = () => { private clearCanvas = () => {
if (window.confirm("This will clear the whole canvas. Are you sure?")) { if (window.confirm("This will clear the whole canvas. Are you sure?")) {
elements.splice(0, elements.length); elements = [];
this.setState({ this.setState({
viewBackgroundColor: "#ffffff", viewBackgroundColor: "#ffffff",
scrollX: 0, scrollX: 0,
@ -244,36 +251,40 @@ export class App extends React.Component<{}, AppState> {
private pasteStyles = () => { private pasteStyles = () => {
const pastedElement = JSON.parse(copiedStyles); const pastedElement = JSON.parse(copiedStyles);
elements.forEach(element => { elements = elements.map(element => {
if (element.isSelected) { if (element.isSelected) {
element.backgroundColor = pastedElement?.backgroundColor; return {
element.strokeWidth = pastedElement?.strokeWidth; ...element,
element.strokeColor = pastedElement?.strokeColor; backgroundColor: pastedElement?.backgroundColor,
element.fillStyle = pastedElement?.fillStyle; strokeWidth: pastedElement?.strokeWidth,
element.opacity = pastedElement?.opacity; strokeColor: pastedElement?.strokeColor,
element.roughness = pastedElement?.roughness; fillStyle: pastedElement?.fillStyle,
opacity: pastedElement?.opacity,
roughness: pastedElement?.roughness
};
} }
return element;
}); });
this.forceUpdate(); this.forceUpdate();
}; };
private moveAllLeft = () => { private moveAllLeft = () => {
moveAllLeft(elements, getSelectedIndices(elements)); elements = moveAllLeft([...elements], getSelectedIndices(elements));
this.forceUpdate(); this.forceUpdate();
}; };
private moveOneLeft = () => { private moveOneLeft = () => {
moveOneLeft(elements, getSelectedIndices(elements)); elements = moveOneLeft([...elements], getSelectedIndices(elements));
this.forceUpdate(); this.forceUpdate();
}; };
private moveAllRight = () => { private moveAllRight = () => {
moveAllRight(elements, getSelectedIndices(elements)); elements = moveAllRight([...elements], getSelectedIndices(elements));
this.forceUpdate(); this.forceUpdate();
}; };
private moveOneRight = () => { private moveOneRight = () => {
moveOneRight(elements, getSelectedIndices(elements)); elements = moveOneRight([...elements], getSelectedIndices(elements));
this.forceUpdate(); this.forceUpdate();
}; };
@ -283,27 +294,38 @@ export class App extends React.Component<{}, AppState> {
this.setState({ name }); this.setState({ name });
} }
private changeProperty = (callback: (element: ExcalidrawElement) => void) => { private changeProperty = (
elements.forEach(element => { callback: (element: ExcalidrawElement) => ExcalidrawElement
if (element.isSelected) { ) => {
callback(element); elements = elements
} .filter(el => el.isSelected)
.map(element => {
return callback(element);
}); });
this.forceUpdate(); this.forceUpdate();
}; };
private changeOpacity = (event: React.ChangeEvent<HTMLInputElement>) => { private changeOpacity = (event: React.ChangeEvent<HTMLInputElement>) => {
this.changeProperty(element => (element.opacity = +event.target.value)); this.changeProperty(element => ({
...element,
opacity: +event.target.value
}));
}; };
private changeStrokeColor = (color: string) => { private changeStrokeColor = (color: string) => {
this.changeProperty(element => (element.strokeColor = color)); this.changeProperty(element => ({
...element,
strokeColor: color
}));
this.setState({ currentItemStrokeColor: color }); this.setState({ currentItemStrokeColor: color });
}; };
private changeBackgroundColor = (color: string) => { private changeBackgroundColor = (color: string) => {
this.changeProperty(element => (element.backgroundColor = color)); this.changeProperty(element => ({
...element,
backgroundColor: color
}));
this.setState({ currentItemBackgroundColor: color }); this.setState({ currentItemBackgroundColor: color });
}; };
@ -336,7 +358,7 @@ export class App extends React.Component<{}, AppState> {
"text/plain", "text/plain",
JSON.stringify(elements.filter(element => element.isSelected)) JSON.stringify(elements.filter(element => element.isSelected))
); );
deleteSelectedElements(elements); elements = deleteSelectedElements(elements);
this.forceUpdate(); this.forceUpdate();
e.preventDefault(); e.preventDefault();
}} }}
@ -358,7 +380,7 @@ export class App extends React.Component<{}, AppState> {
activeTool={this.state.elementType} activeTool={this.state.elementType}
onToolChange={value => { onToolChange={value => {
this.setState({ elementType: value }); this.setState({ elementType: value });
clearSelection(elements); elements = clearSelection([...elements]);
document.documentElement.style.cursor = document.documentElement.style.cursor =
value === "text" ? "text" : "crosshair"; value === "text" ? "text" : "crosshair";
this.forceUpdate(); this.forceUpdate();
@ -404,9 +426,10 @@ export class App extends React.Component<{}, AppState> {
element => element.fillStyle element => element.fillStyle
)} )}
onChange={value => { onChange={value => {
this.changeProperty(element => { this.changeProperty(element => ({
element.fillStyle = value; ...element,
}); fillStyle: value
}));
}} }}
/> />
</> </>
@ -426,9 +449,10 @@ export class App extends React.Component<{}, AppState> {
element => element.strokeWidth element => element.strokeWidth
)} )}
onChange={value => { onChange={value => {
this.changeProperty(element => { this.changeProperty(element => ({
element.strokeWidth = value; ...element,
}); strokeWidth: value
}));
}} }}
/> />
@ -444,9 +468,10 @@ export class App extends React.Component<{}, AppState> {
element => element.roughness element => element.roughness
)} )}
onChange={value => onChange={value =>
this.changeProperty(element => { this.changeProperty(element => ({
element.roughness = value; ...element,
}) roughness: value
}))
} }
/> />
</> </>
@ -552,7 +577,7 @@ export class App extends React.Component<{}, AppState> {
} }
if (!element.isSelected) { if (!element.isSelected) {
clearSelection(elements); elements = clearSelection([...elements]);
element.isSelected = true; element.isSelected = true;
this.forceUpdate(); this.forceUpdate();
} }
@ -660,14 +685,15 @@ export class App extends React.Component<{}, AppState> {
} else { } else {
// We unselect every other elements unless shift is pressed // We unselect every other elements unless shift is pressed
if (!e.shiftKey) { if (!e.shiftKey) {
clearSelection(elements); elements = clearSelection([...elements]);
} }
} }
// No matter what, we select it // No matter what, we select it
hitElement.isSelected = true; hitElement.isSelected = true;
// We duplicate the selected element if alt is pressed on Mouse down // We duplicate the selected element if alt is pressed on Mouse down
if (e.altKey) { if (e.altKey) {
elements.push( elements = [
...elements,
...elements.reduce((duplicates, element) => { ...elements.reduce((duplicates, element) => {
if (element.isSelected) { if (element.isSelected) {
duplicates.push(duplicateElement(element)); duplicates.push(duplicateElement(element));
@ -675,11 +701,11 @@ export class App extends React.Component<{}, AppState> {
} }
return duplicates; return duplicates;
}, [] as typeof elements) }, [] as typeof elements)
); ];
} }
} else { } else {
// If we don't click on anything, let's remove all the selected elements // If we don't click on anything, let's remove all the selected elements
clearSelection(elements); elements = clearSelection([...elements]);
} }
isDraggingElements = someElementIsSelected(elements); isDraggingElements = someElementIsSelected(elements);
@ -714,8 +740,7 @@ export class App extends React.Component<{}, AppState> {
font: this.state.currentItemFont, font: this.state.currentItemFont,
onSubmit: text => { onSubmit: text => {
addTextElement(element, text, this.state.currentItemFont); addTextElement(element, text, this.state.currentItemFont);
elements.push(element); elements = [...elements, { ...element, isSelected: true }];
element.isSelected = true;
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
elementType: "selection" elementType: "selection"
@ -725,14 +750,14 @@ export class App extends React.Component<{}, AppState> {
return; return;
} }
elements.push(element);
if (this.state.elementType === "text") { if (this.state.elementType === "text") {
elements = [...elements, { ...element, isSelected: true }];
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
elementType: "selection" elementType: "selection"
}); });
element.isSelected = true;
} else { } else {
elements = [...elements, element];
this.setState({ draggingElement: element }); this.setState({ draggingElement: element });
} }
@ -883,7 +908,7 @@ export class App extends React.Component<{}, AppState> {
: height; : height;
if (this.state.elementType === "selection") { if (this.state.elementType === "selection") {
setSelection(elements, draggingElement); elements = setSelection([...elements], draggingElement);
} }
// We don't want to save history when moving an element // We don't want to save history when moving an element
history.skipRecording(); history.skipRecording();
@ -901,7 +926,7 @@ export class App extends React.Component<{}, AppState> {
// if no element is clicked, clear the selection and redraw // if no element is clicked, clear the selection and redraw
if (draggingElement === null) { if (draggingElement === null) {
clearSelection(elements); elements = clearSelection([...elements]);
this.forceUpdate(); this.forceUpdate();
return; return;
} }
@ -910,7 +935,7 @@ export class App extends React.Component<{}, AppState> {
if (isDraggingElements) { if (isDraggingElements) {
isDraggingElements = false; isDraggingElements = false;
} }
elements.pop(); elements = elements.slice(0, -1);
} else { } else {
draggingElement.isSelected = true; draggingElement.isSelected = true;
} }
@ -998,8 +1023,7 @@ export class App extends React.Component<{}, AppState> {
text, text,
element.font || this.state.currentItemFont element.font || this.state.currentItemFont
); );
elements.push(element); elements = [...elements, { ...element, isSelected: true }];
element.isSelected = true;
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
elementType: "selection" elementType: "selection"
@ -1031,7 +1055,7 @@ export class App extends React.Component<{}, AppState> {
parsedElements.length > 0 && parsedElements.length > 0 &&
parsedElements[0].type // need to implement a better check here... parsedElements[0].type // need to implement a better check here...
) { ) {
clearSelection(elements); elements = clearSelection([...elements]);
if (x == null) x = 10 - this.state.scrollX; if (x == null) x = 10 - this.state.scrollX;
if (y == null) y = 10 - this.state.scrollY; if (y == null) y = 10 - this.state.scrollY;
@ -1040,12 +1064,15 @@ export class App extends React.Component<{}, AppState> {
const dx = x - minX; const dx = x - minX;
const dy = y - minY; const dy = y - minY;
parsedElements.forEach(parsedElement => { elements = [
...elements,
...parsedElements.map(parsedElement => {
const duplicate = duplicateElement(parsedElement); const duplicate = duplicateElement(parsedElement);
duplicate.x += dx; duplicate.x += dx;
duplicate.y += dy; duplicate.y += dy;
elements.push(duplicate); return duplicate;
}); })
];
this.forceUpdate(); this.forceUpdate();
} }
}; };

View file

@ -25,20 +25,20 @@ export function setSelection(
selectionX2 >= elementX2 && selectionX2 >= elementX2 &&
selectionY2 >= elementY2; selectionY2 >= elementY2;
}); });
return elements;
} }
export function clearSelection(elements: ExcalidrawElement[]) { export function clearSelection(elements: ExcalidrawElement[]) {
elements.forEach(element => { elements.forEach(element => {
element.isSelected = false; element.isSelected = false;
}); });
return elements;
} }
export function deleteSelectedElements(elements: ExcalidrawElement[]) { export function deleteSelectedElements(elements: ExcalidrawElement[]) {
for (let i = elements.length - 1; i >= 0; --i) { return elements.filter(el => !el.isSelected);
if (elements[i].isSelected) {
elements.splice(i, 1);
}
}
} }
export function getSelectedIndices(elements: ExcalidrawElement[]) { export function getSelectedIndices(elements: ExcalidrawElement[]) {

View file

@ -17,6 +17,8 @@ export function moveOneLeft<T>(elements: T[], indicesToMove: number[]) {
} }
swap(elements, index - 1, index); swap(elements, index - 1, index);
}); });
return elements;
} }
export function moveOneRight<T>(elements: T[], indicesToMove: number[]) { export function moveOneRight<T>(elements: T[], indicesToMove: number[]) {
@ -35,6 +37,7 @@ export function moveOneRight<T>(elements: T[], indicesToMove: number[]) {
} }
swap(elements, index + 1, index); swap(elements, index + 1, index);
}); });
return elements;
} }
// Let's go through an example // Let's go through an example
@ -112,6 +115,8 @@ export function moveAllLeft<T>(elements: T[], indicesToMove: number[]) {
leftMostElements.forEach((element, i) => { leftMostElements.forEach((element, i) => {
elements[i] = element; elements[i] = element;
}); });
return elements;
} }
// Let's go through an example // Let's go through an example
@ -190,4 +195,6 @@ export function moveAllRight<T>(elements: T[], indicesToMove: number[]) {
rightMostElements.forEach((element, i) => { rightMostElements.forEach((element, i) => {
elements[elements.length - i - 1] = element; elements[elements.length - i - 1] = element;
}); });
return elements;
} }