feat: add action to wrap selected items in a frame (#9005)

* feat: add action to wrap selected items in a frame

* fix type

* select frame on wrap & refactor

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di
2025-01-13 15:03:56 +00:00
committed by GitHub
co-authored by dwelle
parent c92f3bebf5
commit 00b5b0a0ca
9 changed files with 111 additions and 4 deletions
+47 -2
View File
@@ -1,6 +1,6 @@
import { getNonDeletedElements } from "../element";
import { getCommonBounds, getNonDeletedElements } from "../element";
import type { ExcalidrawElement } from "../element/types";
import { removeAllElementsFromFrame } from "../frame";
import { addElementsToFrame, removeAllElementsFromFrame } from "../frame";
import { getFrameChildren } from "../frame";
import { KEYS } from "../keys";
import type { AppClassProperties, AppState, UIAppState } from "../types";
@@ -10,6 +10,8 @@ import { register } from "./register";
import { isFrameLikeElement } from "../element/typeChecks";
import { frameToolIcon } from "../components/icons";
import { StoreAction } from "../store";
import { getSelectedElements } from "../scene";
import { newFrameElement } from "../element/newElement";
const isSingleFrameSelected = (
appState: UIAppState,
@@ -144,3 +146,46 @@ export const actionSetFrameAsActiveTool = register({
!event.altKey &&
event.key.toLocaleLowerCase() === KEYS.F,
});
export const actionWrapSelectionInFrame = register({
name: "wrapSelectionInFrame",
label: "labels.wrapSelectionInFrame",
trackEvent: { category: "element" },
predicate: (elements, appState, _, app) => {
const selectedElements = getSelectedElements(elements, appState);
return (
selectedElements.length > 0 &&
!selectedElements.some((element) => isFrameLikeElement(element))
);
},
perform: (elements, appState, _, app) => {
const selectedElements = getSelectedElements(elements, appState);
const [x1, y1, x2, y2] = getCommonBounds(
selectedElements,
app.scene.getNonDeletedElementsMap(),
);
const PADDING = 16;
const frame = newFrameElement({
x: x1 - PADDING,
y: y1 - PADDING,
width: x2 - x1 + PADDING * 2,
height: y2 - y1 + PADDING * 2,
});
const nextElements = addElementsToFrame(
[...app.scene.getElementsIncludingDeleted(), frame],
selectedElements,
frame,
);
return {
elements: nextElements,
appState: {
selectedElementIds: { [frame.id]: true },
},
storeAction: StoreAction.CAPTURE,
};
},
});
+2
View File
@@ -47,6 +47,7 @@ export type ShortcutName =
| "saveFileToDisk"
| "saveToActiveFile"
| "toggleShortcuts"
| "wrapSelectionInFrame"
>
| "saveScene"
| "imageExport"
@@ -112,6 +113,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
saveToActiveFile: [getShortcutKey("CtrlOrCmd+S")],
toggleShortcuts: [getShortcutKey("?")],
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
wrapSelectionInFrame: [],
};
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {
+2 -1
View File
@@ -137,7 +137,8 @@ export type ActionName =
| "searchMenu"
| "copyElementLink"
| "linkToElement"
| "cropEditor";
| "cropEditor"
| "wrapSelectionInFrame";
export type PanelComponentProps = {
elements: readonly ExcalidrawElement[];