Browse Source

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>
pull/9011/head
Ryan Di 11 months ago committed by GitHub
parent
commit
00b5b0a0ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 49
      packages/excalidraw/actions/actionFrame.ts
  2. 2
      packages/excalidraw/actions/shortcuts.ts
  3. 3
      packages/excalidraw/actions/types.ts
  4. 3
      packages/excalidraw/components/App.tsx
  5. 1
      packages/excalidraw/components/CommandPalette/CommandPalette.tsx
  6. 1
      packages/excalidraw/components/Stats/MultiPosition.tsx
  7. 3
      packages/excalidraw/locales/en.json
  8. 50
      packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap
  9. 3
      packages/excalidraw/tests/contextmenu.test.tsx

49
packages/excalidraw/actions/actionFrame.ts

@ -1,6 +1,6 @@ @@ -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"; @@ -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({ @@ -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
packages/excalidraw/actions/shortcuts.ts

@ -47,6 +47,7 @@ export type ShortcutName = @@ -47,6 +47,7 @@ export type ShortcutName =
| "saveFileToDisk"
| "saveToActiveFile"
| "toggleShortcuts"
| "wrapSelectionInFrame"
>
| "saveScene"
| "imageExport"
@ -112,6 +113,7 @@ const shortcutMap: Record<ShortcutName, string[]> = { @@ -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) => {

3
packages/excalidraw/actions/types.ts

@ -137,7 +137,8 @@ export type ActionName = @@ -137,7 +137,8 @@ export type ActionName =
| "searchMenu"
| "copyElementLink"
| "linkToElement"
| "cropEditor";
| "cropEditor"
| "wrapSelectionInFrame";
export type PanelComponentProps = {
elements: readonly ExcalidrawElement[];

3
packages/excalidraw/components/App.tsx

@ -378,6 +378,7 @@ import { actionPaste } from "../actions/actionClipboard"; @@ -378,6 +378,7 @@ import { actionPaste } from "../actions/actionClipboard";
import {
actionRemoveAllElementsFromFrame,
actionSelectAllElementsInFrame,
actionWrapSelectionInFrame,
} from "../actions/actionFrame";
import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas";
import { jotaiStore } from "../jotai";
@ -10664,8 +10665,10 @@ class App extends React.Component<AppProps, AppState> { @@ -10664,8 +10665,10 @@ class App extends React.Component<AppProps, AppState> {
actionCut,
actionCopy,
actionPaste,
CONTEXT_MENU_SEPARATOR,
actionSelectAllElementsInFrame,
actionRemoveAllElementsFromFrame,
actionWrapSelectionInFrame,
CONTEXT_MENU_SEPARATOR,
actionToggleCropEditor,
CONTEXT_MENU_SEPARATOR,

1
packages/excalidraw/components/CommandPalette/CommandPalette.tsx

@ -263,6 +263,7 @@ function CommandPaletteInner({ @@ -263,6 +263,7 @@ function CommandPaletteInner({
actionManager.actions.cut,
actionManager.actions.copy,
actionManager.actions.deleteSelectedElements,
actionManager.actions.wrapSelectionInFrame,
actionManager.actions.copyStyles,
actionManager.actions.pasteStyles,
actionManager.actions.bringToFront,

1
packages/excalidraw/components/Stats/MultiPosition.tsx

@ -237,6 +237,7 @@ const MultiPosition = ({ @@ -237,6 +237,7 @@ const MultiPosition = ({
const [x1, y1] = getCommonBounds(elementsInUnit);
return Math.round((property === "x" ? x1 : y1) * 100) / 100;
}
const [el] = elementsInUnit;
const [cx, cy] = [el.x + el.width / 2, el.y + el.height / 2];

3
packages/excalidraw/locales/en.json

@ -164,7 +164,8 @@ @@ -164,7 +164,8 @@
"imageCropping": "Image cropping",
"unCroppedDimension": "Uncropped dimension",
"copyElementLink": "Copy link to object",
"linkToElement": "Link to object"
"linkToElement": "Link to object",
"wrapSelectionInFrame": "Wrap selection in frame"
},
"elementLink": {
"title": "Link to object",

50
packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap

@ -97,6 +97,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro @@ -97,6 +97,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
"category": "element",
},
},
"separator",
{
"label": "labels.selectAllElementsInFrame",
"name": "selectAllElementsInFrame",
@ -115,6 +116,15 @@ exports[`contextMenu element > right-clicking on a group should select whole gro @@ -115,6 +116,15 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
"category": "history",
},
},
{
"label": "labels.wrapSelectionInFrame",
"name": "wrapSelectionInFrame",
"perform": [Function],
"predicate": [Function],
"trackEvent": {
"category": "element",
},
},
"separator",
{
"PanelComponent": [Function],
@ -4731,6 +4741,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi @@ -4731,6 +4741,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"category": "element",
},
},
"separator",
{
"label": "labels.selectAllElementsInFrame",
"name": "selectAllElementsInFrame",
@ -4749,6 +4760,15 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi @@ -4749,6 +4760,15 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"category": "history",
},
},
{
"label": "labels.wrapSelectionInFrame",
"name": "wrapSelectionInFrame",
"perform": [Function],
"predicate": [Function],
"trackEvent": {
"category": "element",
},
},
"separator",
{
"PanelComponent": [Function],
@ -5942,6 +5962,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro @@ -5942,6 +5962,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"category": "element",
},
},
"separator",
{
"label": "labels.selectAllElementsInFrame",
"name": "selectAllElementsInFrame",
@ -5960,6 +5981,15 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro @@ -5960,6 +5981,15 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"category": "history",
},
},
{
"label": "labels.wrapSelectionInFrame",
"name": "wrapSelectionInFrame",
"perform": [Function],
"predicate": [Function],
"trackEvent": {
"category": "element",
},
},
"separator",
{
"PanelComponent": [Function],
@ -7876,6 +7906,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap @@ -7876,6 +7906,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
"category": "element",
},
},
"separator",
{
"label": "labels.selectAllElementsInFrame",
"name": "selectAllElementsInFrame",
@ -7894,6 +7925,15 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap @@ -7894,6 +7925,15 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
"category": "history",
},
},
{
"label": "labels.wrapSelectionInFrame",
"name": "wrapSelectionInFrame",
"perform": [Function],
"predicate": [Function],
"trackEvent": {
"category": "element",
},
},
"separator",
{
"PanelComponent": [Function],
@ -8854,6 +8894,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap @@ -8854,6 +8894,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
"category": "element",
},
},
"separator",
{
"label": "labels.selectAllElementsInFrame",
"name": "selectAllElementsInFrame",
@ -8872,6 +8913,15 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap @@ -8872,6 +8913,15 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
"category": "history",
},
},
{
"label": "labels.wrapSelectionInFrame",
"name": "wrapSelectionInFrame",
"perform": [Function],
"predicate": [Function],
"trackEvent": {
"category": "element",
},
},
"separator",
{
"PanelComponent": [Function],

3
packages/excalidraw/tests/contextmenu.test.tsx

@ -120,6 +120,7 @@ describe("contextMenu element", () => { @@ -120,6 +120,7 @@ describe("contextMenu element", () => {
"cut",
"copy",
"paste",
"wrapSelectionInFrame",
"copyStyles",
"pasteStyles",
"deleteSelectedElements",
@ -213,6 +214,7 @@ describe("contextMenu element", () => { @@ -213,6 +214,7 @@ describe("contextMenu element", () => {
"cut",
"copy",
"paste",
"wrapSelectionInFrame",
"copyStyles",
"pasteStyles",
"deleteSelectedElements",
@ -269,6 +271,7 @@ describe("contextMenu element", () => { @@ -269,6 +271,7 @@ describe("contextMenu element", () => {
"cut",
"copy",
"paste",
"wrapSelectionInFrame",
"copyStyles",
"pasteStyles",
"deleteSelectedElements",

Loading…
Cancel
Save