aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/actions/actionElementLock.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/excalidraw/actions/actionElementLock.ts')
-rw-r--r--packages/excalidraw/actions/actionElementLock.ts119
1 files changed, 119 insertions, 0 deletions
diff --git a/packages/excalidraw/actions/actionElementLock.ts b/packages/excalidraw/actions/actionElementLock.ts
new file mode 100644
index 0000000..eba21f2
--- /dev/null
+++ b/packages/excalidraw/actions/actionElementLock.ts
@@ -0,0 +1,119 @@
+import { LockedIcon, UnlockedIcon } from "../components/icons";
+import { newElementWith } from "../element/mutateElement";
+import { isFrameLikeElement } from "../element/typeChecks";
+import type { ExcalidrawElement } from "../element/types";
+import { KEYS } from "../keys";
+import { getSelectedElements } from "../scene";
+import { CaptureUpdateAction } from "../store";
+import { arrayToMap } from "../utils";
+import { register } from "./register";
+
+const shouldLock = (elements: readonly ExcalidrawElement[]) =>
+ elements.every((el) => !el.locked);
+
+export const actionToggleElementLock = register({
+ name: "toggleElementLock",
+ label: (elements, appState, app) => {
+ const selected = app.scene.getSelectedElements({
+ selectedElementIds: appState.selectedElementIds,
+ includeBoundTextElement: false,
+ });
+ if (selected.length === 1 && !isFrameLikeElement(selected[0])) {
+ return selected[0].locked
+ ? "labels.elementLock.unlock"
+ : "labels.elementLock.lock";
+ }
+
+ return shouldLock(selected)
+ ? "labels.elementLock.lockAll"
+ : "labels.elementLock.unlockAll";
+ },
+ icon: (appState, elements) => {
+ const selectedElements = getSelectedElements(elements, appState);
+ return shouldLock(selectedElements) ? LockedIcon : UnlockedIcon;
+ },
+ trackEvent: { category: "element" },
+ predicate: (elements, appState, _, app) => {
+ const selectedElements = app.scene.getSelectedElements(appState);
+ return (
+ selectedElements.length > 0 &&
+ !selectedElements.some((element) => element.locked && element.frameId)
+ );
+ },
+ perform: (elements, appState, _, app) => {
+ const selectedElements = app.scene.getSelectedElements({
+ selectedElementIds: appState.selectedElementIds,
+ includeBoundTextElement: true,
+ includeElementsInFrames: true,
+ });
+
+ if (!selectedElements.length) {
+ return false;
+ }
+
+ const nextLockState = shouldLock(selectedElements);
+ const selectedElementsMap = arrayToMap(selectedElements);
+ return {
+ elements: elements.map((element) => {
+ if (!selectedElementsMap.has(element.id)) {
+ return element;
+ }
+
+ return newElementWith(element, { locked: nextLockState });
+ }),
+ appState: {
+ ...appState,
+ selectedLinearElement: nextLockState
+ ? null
+ : appState.selectedLinearElement,
+ },
+ captureUpdate: CaptureUpdateAction.IMMEDIATELY,
+ };
+ },
+ keyTest: (event, appState, elements, app) => {
+ return (
+ event.key.toLocaleLowerCase() === KEYS.L &&
+ event[KEYS.CTRL_OR_CMD] &&
+ event.shiftKey &&
+ app.scene.getSelectedElements({
+ selectedElementIds: appState.selectedElementIds,
+ includeBoundTextElement: false,
+ }).length > 0
+ );
+ },
+});
+
+export const actionUnlockAllElements = register({
+ name: "unlockAllElements",
+ paletteName: "Unlock all elements",
+ trackEvent: { category: "canvas" },
+ viewMode: false,
+ icon: UnlockedIcon,
+ predicate: (elements, appState) => {
+ const selectedElements = getSelectedElements(elements, appState);
+ return (
+ selectedElements.length === 0 &&
+ elements.some((element) => element.locked)
+ );
+ },
+ perform: (elements, appState) => {
+ const lockedElements = elements.filter((el) => el.locked);
+
+ return {
+ elements: elements.map((element) => {
+ if (element.locked) {
+ return newElementWith(element, { locked: false });
+ }
+ return element;
+ }),
+ appState: {
+ ...appState,
+ selectedElementIds: Object.fromEntries(
+ lockedElements.map((el) => [el.id, true]),
+ ),
+ },
+ captureUpdate: CaptureUpdateAction.IMMEDIATELY,
+ };
+ },
+ label: "labels.elementLock.unlockAll",
+});