import React from "react"; import { moveOneLeft, moveOneRight, moveAllLeft, moveAllRight, } from "../zindex"; import { KEYS, isDarwin } from "../keys"; import { t } from "../i18n"; import { getShortcutKey } from "../utils"; import { register } from "./register"; import { SendBackwardIcon, BringToFrontIcon, SendToBackIcon, BringForwardIcon, } from "../components/icons"; import { ExcalidrawElement } from "../element/types"; import { AppState } from "../types"; const getElementIndices = ( direction: "left" | "right", elements: readonly ExcalidrawElement[], appState: AppState, ) => { const selectedIndices: number[] = []; let deletedIndicesCache: number[] = []; const cb = (element: ExcalidrawElement, index: number) => { if (element.isDeleted) { // we want to build an array of deleted elements that are preceeding // a selected element so that we move them together deletedIndicesCache.push(index); } else { if (appState.selectedElementIds[element.id]) { selectedIndices.push(...deletedIndicesCache, index); } // always empty cache of deleted elements after either pushing a group // of selected/deleted elements, of after encountering non-deleted elem deletedIndicesCache = []; } }; // sending back → select contiguous deleted elements that are to the left of // selected element(s) if (direction === "left") { let i = -1; const len = elements.length; while (++i < len) { cb(elements[i], i); } // moving to front → loop from right to left so that we don't need to // backtrack when gathering deleted elements } else { let i = elements.length; while (--i > -1) { cb(elements[i], i); } } // sort in case we were gathering indexes from right to left return selectedIndices.sort(); }; const moveElements = ( func: typeof moveOneLeft, elements: readonly ExcalidrawElement[], appState: AppState, ) => { const _elements = elements.slice(); const direction = func === moveOneLeft || func === moveAllLeft ? "left" : "right"; const indices = getElementIndices(direction, _elements, appState); return func(_elements, indices); }; export const actionSendBackward = register({ name: "sendBackward", perform: (elements, appState) => { return { elements: moveElements(moveOneLeft, elements, appState), appState, commitToHistory: true, }; }, contextItemLabel: "labels.sendBackward", keyPriority: 40, keyTest: (event) => event[KEYS.CTRL_OR_CMD] && !event.shiftKey && event.code === "BracketLeft", PanelComponent: ({ updateData, appState }) => ( ), }); export const actionBringForward = register({ name: "bringForward", perform: (elements, appState) => { return { elements: moveElements(moveOneRight, elements, appState), appState, commitToHistory: true, }; }, contextItemLabel: "labels.bringForward", keyPriority: 40, keyTest: (event) => event[KEYS.CTRL_OR_CMD] && !event.shiftKey && event.code === "BracketRight", PanelComponent: ({ updateData, appState }) => ( ), }); export const actionSendToBack = register({ name: "sendToBack", perform: (elements, appState) => { return { elements: moveElements(moveAllLeft, elements, appState), appState, commitToHistory: true, }; }, contextItemLabel: "labels.sendToBack", keyTest: (event) => { return isDarwin ? event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === "BracketLeft" : event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.code === "BracketLeft"; }, PanelComponent: ({ updateData, appState }) => ( ), }); export const actionBringToFront = register({ name: "bringToFront", perform: (elements, appState) => { return { elements: moveElements(moveAllRight, elements, appState), appState, commitToHistory: true, }; }, contextItemLabel: "labels.bringToFront", keyTest: (event) => { return isDarwin ? event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === "BracketRight" : event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.code === "BracketRight"; }, PanelComponent: ({ updateData, appState }) => ( ), });