diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 89cf662c..62b0ff9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,5 @@ # Contributing - ## Setup ### Option 1 - Manual @@ -17,7 +16,6 @@ > git fetch upstream > git branch --set-upstream-to=upstream/master master > ``` -> ### Option 2 - Codesandbox diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index 51ecf7ef..b82e5cad 100644 --- a/src/element/resizeTest.ts +++ b/src/element/resizeTest.ts @@ -34,7 +34,7 @@ export function resizeTest( } export function getElementWithResizeHandler( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], { x, y }: { x: number; y: number }, { scrollX, scrollY }: SceneScroll ) { diff --git a/src/history.ts b/src/history.ts index 5847c0f0..51442184 100644 --- a/src/history.ts +++ b/src/history.ts @@ -5,7 +5,7 @@ class SceneHistory { private stateHistory: string[] = []; private redoStack: string[] = []; - generateCurrentEntry(elements: ExcalidrawElement[]) { + generateCurrentEntry(elements: readonly ExcalidrawElement[]) { return JSON.stringify( elements.map(element => ({ ...element, isSelected: false })) ); @@ -22,30 +22,33 @@ class SceneHistory { this.stateHistory.push(newEntry); } - restoreEntry(elements: ExcalidrawElement[], entry: string) { - const newElements = JSON.parse(entry); - elements.splice(0, elements.length); - newElements.forEach((newElement: ExcalidrawElement) => { - elements.push(newElement); - }); + restoreEntry(entry: string) { // When restoring, we shouldn't add an history entry otherwise we'll be stuck with it and can't go back this.skipRecording(); + + try { + return JSON.parse(entry); + } catch { + return null; + } } clearRedoStack() { this.redoStack.splice(0, this.redoStack.length); } - redoOnce(elements: ExcalidrawElement[]) { + redoOnce(elements: readonly ExcalidrawElement[]) { const currentEntry = this.generateCurrentEntry(elements); const entryToRestore = this.redoStack.pop(); if (entryToRestore !== undefined) { - this.restoreEntry(elements, entryToRestore); this.stateHistory.push(currentEntry); + return this.restoreEntry(entryToRestore); } + + return null; } - undoOnce(elements: ExcalidrawElement[]) { + undoOnce(elements: readonly ExcalidrawElement[]) { const currentEntry = this.generateCurrentEntry(elements); let entryToRestore = this.stateHistory.pop(); @@ -54,9 +57,11 @@ class SceneHistory { entryToRestore = this.stateHistory.pop(); } if (entryToRestore !== undefined) { - this.restoreEntry(elements, entryToRestore); this.redoStack.push(currentEntry); + return this.restoreEntry(entryToRestore); } + + return null; } isRecording() { diff --git a/src/index.tsx b/src/index.tsx index 1f4163dc..6e3804ec 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -56,7 +56,7 @@ import { Panel } from "./components/Panel"; import "./styles.scss"; import { getElementWithResizeHandler } from "./element/resizeTest"; -const { elements } = createScene(); +let { elements } = createScene(); const { history } = createHistory(); const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`; @@ -119,9 +119,16 @@ export class App extends React.Component<{}, AppState> { document.addEventListener("mousemove", this.getCurrentCursorPosition); window.addEventListener("resize", this.onResize, false); - const savedState = restoreFromLocalStorage(elements); - if (savedState) { - this.setState(savedState); + const { elements: newElements, appState } = restoreFromLocalStorage(); + + if (newElements) { + elements = newElements; + } + + if (appState) { + this.setState(appState); + } else { + this.forceUpdate(); } } @@ -163,7 +170,7 @@ export class App extends React.Component<{}, AppState> { if (isInputLike(event.target)) return; if (event.key === KEYS.ESCAPE) { - clearSelection(elements); + elements = clearSelection(elements); this.forceUpdate(); event.preventDefault(); } else if (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) { @@ -173,13 +180,16 @@ export class App extends React.Component<{}, AppState> { const step = event.shiftKey ? ELEMENT_SHIFT_TRANSLATE_AMOUNT : ELEMENT_TRANSLATE_AMOUNT; - elements.forEach(element => { - if (element.isSelected) { + elements = elements.map(el => { + if (el.isSelected) { + const element = { ...el }; 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_UP) element.y -= step; else if (event.key === KEYS.ARROW_DOWN) element.y += step; + return element; } + return el; }); this.forceUpdate(); event.preventDefault(); @@ -215,9 +225,12 @@ export class App extends React.Component<{}, AppState> { event.preventDefault(); // Select all: Cmd-A } else if (event[META_KEY] && event.code === "KeyA") { - elements.forEach(element => { + let newElements = [...elements]; + newElements.forEach(element => { element.isSelected = true; }); + + elements = newElements; this.forceUpdate(); event.preventDefault(); } else if (shapesShortcutKeys.includes(event.key.toLowerCase())) { @@ -225,10 +238,16 @@ export class App extends React.Component<{}, AppState> { } else if (event[META_KEY] && event.code === "KeyZ") { if (event.shiftKey) { // Redo action - history.redoOnce(elements); + const data = history.redoOnce(elements); + if (data !== null) { + elements = data; + } } else { // undo action - history.undoOnce(elements); + const data = history.undoOnce(elements); + if (data !== null) { + elements = data; + } } this.forceUpdate(); event.preventDefault(); @@ -243,13 +262,13 @@ export class App extends React.Component<{}, AppState> { }; private deleteSelectedElements = () => { - deleteSelectedElements(elements); + elements = deleteSelectedElements(elements); this.forceUpdate(); }; private clearCanvas = () => { if (window.confirm("This will clear the whole canvas. Are you sure?")) { - elements.splice(0, elements.length); + elements = []; this.setState({ viewBackgroundColor: "#ffffff", scrollX: 0, @@ -268,40 +287,45 @@ export class App extends React.Component<{}, AppState> { private pasteStyles = () => { const pastedElement = JSON.parse(copiedStyles); - elements.forEach(element => { + elements = elements.map(element => { if (element.isSelected) { - element.backgroundColor = pastedElement?.backgroundColor; - element.strokeWidth = pastedElement?.strokeWidth; - element.strokeColor = pastedElement?.strokeColor; - element.fillStyle = pastedElement?.fillStyle; - element.opacity = pastedElement?.opacity; - element.roughness = pastedElement?.roughness; - if (isTextElement(element)) { - element.font = pastedElement?.font; - this.redrawTextBoundingBox(element); + const newElement = { + ...element, + backgroundColor: pastedElement?.backgroundColor, + strokeWidth: pastedElement?.strokeWidth, + strokeColor: pastedElement?.strokeColor, + fillStyle: pastedElement?.fillStyle, + opacity: pastedElement?.opacity, + roughness: pastedElement?.roughness + }; + if (isTextElement(newElement)) { + newElement.font = pastedElement?.font; + this.redrawTextBoundingBox(newElement); } + return newElement; } + return element; }); this.forceUpdate(); }; private moveAllLeft = () => { - moveAllLeft(elements, getSelectedIndices(elements)); + elements = moveAllLeft([...elements], getSelectedIndices(elements)); this.forceUpdate(); }; private moveOneLeft = () => { - moveOneLeft(elements, getSelectedIndices(elements)); + elements = moveOneLeft([...elements], getSelectedIndices(elements)); this.forceUpdate(); }; private moveAllRight = () => { - moveAllRight(elements, getSelectedIndices(elements)); + elements = moveAllRight([...elements], getSelectedIndices(elements)); this.forceUpdate(); }; private moveOneRight = () => { - moveOneRight(elements, getSelectedIndices(elements)); + elements = moveOneRight([...elements], getSelectedIndices(elements)); this.forceUpdate(); }; @@ -311,27 +335,39 @@ export class App extends React.Component<{}, AppState> { this.setState({ name }); } - private changeProperty = (callback: (element: ExcalidrawElement) => void) => { - elements.forEach(element => { + private changeProperty = ( + callback: (element: ExcalidrawElement) => ExcalidrawElement + ) => { + elements = elements.map(element => { if (element.isSelected) { - callback(element); + return callback(element); } + return element; }); this.forceUpdate(); }; private changeOpacity = (event: React.ChangeEvent) => { - this.changeProperty(element => (element.opacity = +event.target.value)); + this.changeProperty(element => ({ + ...element, + opacity: +event.target.value + })); }; private changeStrokeColor = (color: string) => { - this.changeProperty(element => (element.strokeColor = color)); + this.changeProperty(element => ({ + ...element, + strokeColor: color + })); this.setState({ currentItemStrokeColor: color }); }; private changeBackgroundColor = (color: string) => { - this.changeProperty(element => (element.backgroundColor = color)); + this.changeProperty(element => ({ + ...element, + backgroundColor: color + })); this.setState({ currentItemBackgroundColor: color }); }; @@ -357,7 +393,6 @@ export class App extends React.Component<{}, AppState> { element.width = metrics.width; element.height = metrics.height; element.baseline = metrics.baseline; - this.forceUpdate(); }; public render() { @@ -372,7 +407,7 @@ export class App extends React.Component<{}, AppState> { "text/plain", JSON.stringify(elements.filter(element => element.isSelected)) ); - deleteSelectedElements(elements); + elements = deleteSelectedElements(elements); this.forceUpdate(); e.preventDefault(); }} @@ -394,7 +429,7 @@ export class App extends React.Component<{}, AppState> { activeTool={this.state.elementType} onToolChange={value => { this.setState({ elementType: value }); - clearSelection(elements); + elements = clearSelection(elements); document.documentElement.style.cursor = value === "text" ? "text" : "crosshair"; this.forceUpdate(); @@ -440,9 +475,10 @@ export class App extends React.Component<{}, AppState> { element => element.fillStyle )} onChange={value => { - this.changeProperty(element => { - element.fillStyle = value; - }); + this.changeProperty(element => ({ + ...element, + fillStyle: value + })); }} /> @@ -462,9 +498,10 @@ export class App extends React.Component<{}, AppState> { element => element.strokeWidth )} onChange={value => { - this.changeProperty(element => { - element.strokeWidth = value; - }); + this.changeProperty(element => ({ + ...element, + strokeWidth: value + })); }} /> @@ -480,9 +517,10 @@ export class App extends React.Component<{}, AppState> { element => element.roughness )} onChange={value => - this.changeProperty(element => { - element.roughness = value; - }) + this.changeProperty(element => ({ + ...element, + roughness: value + })) } /> @@ -511,6 +549,8 @@ export class App extends React.Component<{}, AppState> { }`; this.redrawTextBoundingBox(element); } + + return element; }) } /> @@ -534,6 +574,8 @@ export class App extends React.Component<{}, AppState> { }px ${value}`; this.redrawTextBoundingBox(element); } + + return element; }) } /> @@ -575,7 +617,10 @@ export class App extends React.Component<{}, AppState> { } onSaveScene={() => saveAsJSON(elements, this.state.name)} onLoadScene={() => - loadFromJSON(elements).then(() => this.forceUpdate()) + loadFromJSON().then(({ elements: newElements }) => { + elements = newElements; + this.forceUpdate(); + }) } /> @@ -638,7 +683,7 @@ export class App extends React.Component<{}, AppState> { } if (!element.isSelected) { - clearSelection(elements); + elements = clearSelection(elements); element.isSelected = true; this.forceUpdate(); } @@ -730,36 +775,41 @@ export class App extends React.Component<{}, AppState> { document.documentElement.style.cursor = `${resizeHandle}-resize`; isResizingElements = true; } else { + const selected = getElementAtPosition( + elements.filter(el => el.isSelected), + x, + y + ); + // clear selection if shift is not clicked + if (!selected && !e.shiftKey) { + elements = clearSelection(elements); + } const hitElement = getElementAtPosition(elements, x, y); // If we click on something if (hitElement) { - if (hitElement.isSelected) { - // If that element is already selected, do nothing, - // we're likely going to drag it - } else { - // We unselect every other elements unless shift is pressed - if (!e.shiftKey) { - clearSelection(elements); - } - } - // No matter what, we select it + // deselect if item is selected + // if shift is not clicked, this will always return true + // otherwise, it will trigger selection based on current + // state of the box hitElement.isSelected = true; + + // No matter what, we select it // We duplicate the selected element if alt is pressed on Mouse down if (e.altKey) { - elements.push( + elements = [ + ...elements, ...elements.reduce((duplicates, element) => { if (element.isSelected) { - duplicates.push(duplicateElement(element)); + duplicates = duplicates.concat( + duplicateElement(element) + ); element.isSelected = false; } return duplicates; }, [] as typeof elements) - ); + ]; } - } else { - // If we don't click on anything, let's remove all the selected elements - clearSelection(elements); } isDraggingElements = someElementIsSelected(elements); @@ -794,8 +844,7 @@ export class App extends React.Component<{}, AppState> { font: this.state.currentItemFont, onSubmit: text => { addTextElement(element, text, this.state.currentItemFont); - elements.push(element); - element.isSelected = true; + elements = [...elements, { ...element, isSelected: true }]; this.setState({ draggingElement: null, elementType: "selection" @@ -805,14 +854,14 @@ export class App extends React.Component<{}, AppState> { return; } - elements.push(element); if (this.state.elementType === "text") { + elements = [...elements, { ...element, isSelected: true }]; this.setState({ draggingElement: null, elementType: "selection" }); - element.isSelected = true; } else { + elements = [...elements, element]; this.setState({ draggingElement: element }); } @@ -959,7 +1008,7 @@ export class App extends React.Component<{}, AppState> { : height; if (this.state.elementType === "selection") { - setSelection(elements, draggingElement); + elements = setSelection(elements, draggingElement); } // We don't want to save history when moving an element history.skipRecording(); @@ -977,7 +1026,7 @@ export class App extends React.Component<{}, AppState> { // if no element is clicked, clear the selection and redraw if (draggingElement === null) { - clearSelection(elements); + elements = clearSelection(elements); this.forceUpdate(); return; } @@ -986,7 +1035,7 @@ export class App extends React.Component<{}, AppState> { if (isDraggingElements) { isDraggingElements = false; } - elements.pop(); + elements = elements.slice(0, -1); } else { draggingElement.isSelected = true; } @@ -1029,7 +1078,9 @@ export class App extends React.Component<{}, AppState> { let textY = e.clientY; if (elementAtPosition && isTextElement(elementAtPosition)) { - elements.splice(elements.indexOf(elementAtPosition), 1); + elements = elements.filter( + element => element.id !== elementAtPosition.id + ); this.forceUpdate(); Object.assign(element, elementAtPosition); @@ -1073,8 +1124,7 @@ export class App extends React.Component<{}, AppState> { text, element.font || this.state.currentItemFont ); - elements.push(element); - element.isSelected = true; + elements = [...elements, { ...element, isSelected: true }]; this.setState({ draggingElement: null, elementType: "selection" @@ -1134,15 +1184,15 @@ export class App extends React.Component<{}, AppState> { parsedElements.length > 0 && parsedElements[0].type // need to implement a better check here... ) { - clearSelection(elements); + elements = clearSelection(elements); let subCanvasX1 = Infinity; let subCanvasX2 = 0; let subCanvasY1 = Infinity; let subCanvasY2 = 0; - const minX = Math.min(...parsedElements.map(element => element.x)); - const minY = Math.min(...parsedElements.map(element => element.y)); + //const minX = Math.min(parsedElements.map(element => element.x)); + //const minY = Math.min(parsedElements.map(element => element.y)); const distance = (x: number, y: number) => { return Math.abs(x > y ? x - y : y - x); @@ -1170,13 +1220,15 @@ export class App extends React.Component<{}, AppState> { CANVAS_WINDOW_OFFSET_TOP - elementsCenterY; - parsedElements.forEach(parsedElement => { - const duplicate = duplicateElement(parsedElement); - duplicate.x += dx - minX; - duplicate.y += dy - minY; - elements.push(duplicate); - }); - + elements = [ + ...elements, + ...parsedElements.map(parsedElement => { + const duplicate = duplicateElement(parsedElement); + duplicate.x += dx; + duplicate.y += dy; + return duplicate; + }) + ]; this.forceUpdate(); } }; diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 0901d47d..e5753f36 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -14,7 +14,7 @@ import { import { renderElement } from "./renderElement"; export function renderScene( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], rc: RoughCanvas, canvas: HTMLCanvasElement, sceneState: SceneState, diff --git a/src/scene/comparisons.ts b/src/scene/comparisons.ts index 30418a93..62b4b5df 100644 --- a/src/scene/comparisons.ts +++ b/src/scene/comparisons.ts @@ -2,7 +2,7 @@ import { ExcalidrawElement } from "../element/types"; import { hitTest } from "../element/collision"; import { getElementAbsoluteCoords } from "../element"; -export const hasBackground = (elements: ExcalidrawElement[]) => +export const hasBackground = (elements: readonly ExcalidrawElement[]) => elements.some( element => element.isSelected && @@ -11,7 +11,7 @@ export const hasBackground = (elements: ExcalidrawElement[]) => element.type === "diamond") ); -export const hasStroke = (elements: ExcalidrawElement[]) => +export const hasStroke = (elements: readonly ExcalidrawElement[]) => elements.some( element => element.isSelected && @@ -21,11 +21,11 @@ export const hasStroke = (elements: ExcalidrawElement[]) => element.type === "arrow") ); -export const hasText = (elements: ExcalidrawElement[]) => +export const hasText = (elements: readonly ExcalidrawElement[]) => elements.some(element => element.isSelected && element.type === "text"); export function getElementAtPosition( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], x: number, y: number ) { @@ -42,7 +42,7 @@ export function getElementAtPosition( } export function getElementContainingPosition( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], x: number, y: number ) { diff --git a/src/scene/createScene.ts b/src/scene/createScene.ts index fb697ec0..00476029 100644 --- a/src/scene/createScene.ts +++ b/src/scene/createScene.ts @@ -1,6 +1,6 @@ import { ExcalidrawElement } from "../element/types"; export const createScene = () => { - const elements = Array.of(); + const elements: readonly ExcalidrawElement[] = []; return { elements }; }; diff --git a/src/scene/data.ts b/src/scene/data.ts index bfcaf490..16309e8f 100644 --- a/src/scene/data.ts +++ b/src/scene/data.ts @@ -22,7 +22,15 @@ function saveFile(name: string, data: string) { link.remove(); } -export function saveAsJSON(elements: ExcalidrawElement[], name: string) { +interface DataState { + elements: readonly ExcalidrawElement[]; + appState: any; +} + +export function saveAsJSON( + elements: readonly ExcalidrawElement[], + name: string +) { const serialized = JSON.stringify({ version: 1, source: window.location.origin, @@ -35,7 +43,7 @@ export function saveAsJSON(elements: ExcalidrawElement[], name: string) { ); } -export function loadFromJSON(elements: ExcalidrawElement[]) { +export function loadFromJSON() { const input = document.createElement("input"); const reader = new FileReader(); input.type = "file"; @@ -52,19 +60,24 @@ export function loadFromJSON(elements: ExcalidrawElement[]) { input.click(); - return new Promise(resolve => { + return new Promise(resolve => { reader.onloadend = () => { if (reader.readyState === FileReader.DONE) { - const data = JSON.parse(reader.result as string); - restore(elements, data.elements, null); - resolve(); + let elements = []; + try { + const data = JSON.parse(reader.result as string); + elements = data.elements || []; + } catch (e) { + // Do nothing because elements array is already empty + } + resolve(restore(elements, null)); } }; }); } export function exportAsPNG( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], canvas: HTMLCanvasElement, { exportBackground, @@ -130,47 +143,52 @@ export function exportAsPNG( } function restore( - elements: ExcalidrawElement[], - savedElements: string | ExcalidrawElement[] | null, - savedState: string | null -) { - try { - if (savedElements) { - elements.splice( - 0, - elements.length, - ...(typeof savedElements === "string" - ? JSON.parse(savedElements) - : savedElements) - ); - elements.forEach((element: ExcalidrawElement) => { - element.id = element.id || nanoid(); - element.fillStyle = element.fillStyle || "hachure"; - element.strokeWidth = element.strokeWidth || 1; - element.roughness = element.roughness || 1; - element.opacity = - element.opacity === null || element.opacity === undefined - ? 100 - : element.opacity; - }); - } - - return savedState ? JSON.parse(savedState) : null; - } catch (e) { - elements.splice(0, elements.length); - return null; - } + savedElements: readonly ExcalidrawElement[], + savedState: any +): DataState { + return { + elements: savedElements.map(element => ({ + ...element, + id: element.id || nanoid(), + fillStyle: element.fillStyle || "hachure", + strokeWidth: element.strokeWidth || 1, + roughness: element.roughness || 1, + opacity: + element.opacity === null || element.opacity === undefined + ? 100 + : element.opacity + })), + appState: savedState + }; } -export function restoreFromLocalStorage(elements: ExcalidrawElement[]) { +export function restoreFromLocalStorage() { const savedElements = localStorage.getItem(LOCAL_STORAGE_KEY); const savedState = localStorage.getItem(LOCAL_STORAGE_KEY_STATE); - return restore(elements, savedElements, savedState); + let elements = []; + if (savedElements) { + try { + elements = JSON.parse(savedElements); + } catch (e) { + // Do nothing because elements array is already empty + } + } + + let appState = null; + if (savedState) { + try { + appState = JSON.parse(savedState); + } catch (e) { + // Do nothing because appState is already null + } + } + + return restore(elements, appState); } export function saveToLocalStorage( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], state: AppState ) { localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(elements)); diff --git a/src/scene/scrollbars.ts b/src/scene/scrollbars.ts index fbd77fb0..0c0043ce 100644 --- a/src/scene/scrollbars.ts +++ b/src/scene/scrollbars.ts @@ -7,7 +7,7 @@ export const SCROLLBAR_WIDTH = 6; export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; export function getScrollBars( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], canvasWidth: number, canvasHeight: number, scrollX: number, @@ -76,7 +76,7 @@ export function getScrollBars( } export function isOverScrollBars( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], x: number, y: number, canvasWidth: number, diff --git a/src/scene/selection.ts b/src/scene/selection.ts index b1fbc13e..6347fae8 100644 --- a/src/scene/selection.ts +++ b/src/scene/selection.ts @@ -2,7 +2,7 @@ import { ExcalidrawElement } from "../element/types"; import { getElementAbsoluteCoords } from "../element"; export function setSelection( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], selection: ExcalidrawElement ) { const [ @@ -25,23 +25,25 @@ export function setSelection( selectionX2 >= elementX2 && selectionY2 >= elementY2; }); + + return elements; } -export function clearSelection(elements: ExcalidrawElement[]) { - elements.forEach(element => { +export function clearSelection(elements: readonly ExcalidrawElement[]) { + const newElements = [...elements]; + + newElements.forEach(element => { element.isSelected = false; }); + + return newElements; } -export function deleteSelectedElements(elements: ExcalidrawElement[]) { - for (let i = elements.length - 1; i >= 0; --i) { - if (elements[i].isSelected) { - elements.splice(i, 1); - } - } +export function deleteSelectedElements(elements: readonly ExcalidrawElement[]) { + return elements.filter(el => !el.isSelected); } -export function getSelectedIndices(elements: ExcalidrawElement[]) { +export function getSelectedIndices(elements: readonly ExcalidrawElement[]) { const selectedIndices: number[] = []; elements.forEach((element, index) => { if (element.isSelected) { @@ -51,11 +53,11 @@ export function getSelectedIndices(elements: ExcalidrawElement[]) { return selectedIndices; } -export const someElementIsSelected = (elements: ExcalidrawElement[]) => +export const someElementIsSelected = (elements: readonly ExcalidrawElement[]) => elements.some(element => element.isSelected); export function getSelectedAttribute( - elements: ExcalidrawElement[], + elements: readonly ExcalidrawElement[], getAttribute: (element: ExcalidrawElement) => T ): T | null { const attributes = Array.from( diff --git a/src/zindex.ts b/src/zindex.ts index 9ea5850b..bc723294 100644 --- a/src/zindex.ts +++ b/src/zindex.ts @@ -17,6 +17,8 @@ export function moveOneLeft(elements: T[], indicesToMove: number[]) { } swap(elements, index - 1, index); }); + + return elements; } export function moveOneRight(elements: T[], indicesToMove: number[]) { @@ -35,6 +37,7 @@ export function moveOneRight(elements: T[], indicesToMove: number[]) { } swap(elements, index + 1, index); }); + return elements; } // Let's go through an example @@ -112,6 +115,8 @@ export function moveAllLeft(elements: T[], indicesToMove: number[]) { leftMostElements.forEach((element, i) => { elements[i] = element; }); + + return elements; } // Let's go through an example @@ -190,4 +195,6 @@ export function moveAllRight(elements: T[], indicesToMove: number[]) { rightMostElements.forEach((element, i) => { elements[elements.length - i - 1] = element; }); + + return elements; }