feat: box select frame & children to allow resizing at the same time (#9031)
* box select frame & children * avoid selecting children twice to avoid double their moving * do not show ele stats if frame and children selected together * do not update frame membership if selected together * do not group frame and its children * comment and refactor code * hide align altogether * include frame children when selecting all * simplify --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
@@ -21,10 +21,8 @@ import type { AppClassProperties, AppState, UIAppState } from "../types";
|
||||
import { arrayToMap, getShortcutKey } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
const alignActionsPredicate = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
export const alignActionsPredicate = (
|
||||
appState: UIAppState,
|
||||
_: unknown,
|
||||
app: AppClassProperties,
|
||||
) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
@@ -65,7 +63,8 @@ export const actionAlignTop = register({
|
||||
label: "labels.alignTop",
|
||||
icon: AlignTopIcon,
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
predicate: (elements, appState, appProps, app) =>
|
||||
alignActionsPredicate(appState, app),
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
@@ -80,7 +79,7 @@ export const actionAlignTop = register({
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_UP,
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
hidden={!alignActionsPredicate(appState, app)}
|
||||
type="button"
|
||||
icon={AlignTopIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@@ -98,7 +97,8 @@ export const actionAlignBottom = register({
|
||||
label: "labels.alignBottom",
|
||||
icon: AlignBottomIcon,
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
predicate: (elements, appState, appProps, app) =>
|
||||
alignActionsPredicate(appState, app),
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
@@ -113,7 +113,7 @@ export const actionAlignBottom = register({
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_DOWN,
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
hidden={!alignActionsPredicate(appState, app)}
|
||||
type="button"
|
||||
icon={AlignBottomIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@@ -131,7 +131,8 @@ export const actionAlignLeft = register({
|
||||
label: "labels.alignLeft",
|
||||
icon: AlignLeftIcon,
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
predicate: (elements, appState, appProps, app) =>
|
||||
alignActionsPredicate(appState, app),
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
@@ -146,7 +147,7 @@ export const actionAlignLeft = register({
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_LEFT,
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
hidden={!alignActionsPredicate(appState, app)}
|
||||
type="button"
|
||||
icon={AlignLeftIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@@ -164,7 +165,8 @@ export const actionAlignRight = register({
|
||||
label: "labels.alignRight",
|
||||
icon: AlignRightIcon,
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
predicate: (elements, appState, appProps, app) =>
|
||||
alignActionsPredicate(appState, app),
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
@@ -179,7 +181,7 @@ export const actionAlignRight = register({
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_RIGHT,
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
hidden={!alignActionsPredicate(appState, app)}
|
||||
type="button"
|
||||
icon={AlignRightIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@@ -197,7 +199,8 @@ export const actionAlignVerticallyCentered = register({
|
||||
label: "labels.centerVertically",
|
||||
icon: CenterVerticallyIcon,
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
predicate: (elements, appState, appProps, app) =>
|
||||
alignActionsPredicate(appState, app),
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
@@ -210,7 +213,7 @@ export const actionAlignVerticallyCentered = register({
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
hidden={!alignActionsPredicate(appState, app)}
|
||||
type="button"
|
||||
icon={CenterVerticallyIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@@ -226,7 +229,8 @@ export const actionAlignHorizontallyCentered = register({
|
||||
label: "labels.centerHorizontally",
|
||||
icon: CenterHorizontallyIcon,
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
predicate: (elements, appState, appProps, app) =>
|
||||
alignActionsPredicate(appState, app),
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
@@ -239,7 +243,7 @@ export const actionAlignHorizontallyCentered = register({
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
hidden={!alignActionsPredicate(appState, app)}
|
||||
type="button"
|
||||
icon={CenterHorizontallyIcon}
|
||||
onClick={() => updateData(null)}
|
||||
|
||||
@@ -200,6 +200,7 @@ export const actionWrapSelectionInFrame = register({
|
||||
[...app.scene.getElementsIncludingDeleted(), frame],
|
||||
selectedElements,
|
||||
frame,
|
||||
appState,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -25,8 +25,10 @@ import type {
|
||||
import type { AppClassProperties, AppState } from "../types";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
import {
|
||||
frameAndChildrenSelectedTogether,
|
||||
getElementsInResizingFrame,
|
||||
getFrameLikeElements,
|
||||
getRootElements,
|
||||
groupByFrameLikes,
|
||||
removeElementsFromFrame,
|
||||
replaceAllElementsInFrame,
|
||||
@@ -60,8 +62,11 @@ const enableActionGroup = (
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
});
|
||||
|
||||
return (
|
||||
selectedElements.length >= 2 && !allElementsInSameGroup(selectedElements)
|
||||
selectedElements.length >= 2 &&
|
||||
!allElementsInSameGroup(selectedElements) &&
|
||||
!frameAndChildrenSelectedTogether(selectedElements)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -71,10 +76,12 @@ export const actionGroup = register({
|
||||
icon: (appState) => <GroupIcon theme={appState.theme} />,
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
});
|
||||
const selectedElements = getRootElements(
|
||||
app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
}),
|
||||
);
|
||||
if (selectedElements.length < 2) {
|
||||
// nothing to group
|
||||
return { appState, elements, storeAction: StoreAction.NONE };
|
||||
|
||||
@@ -5,7 +5,6 @@ import { getNonDeletedElements, isTextElement } from "../element";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import { isLinearElement } from "../element/typeChecks";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { excludeElementsInFramesFromSelection } from "../scene/selection";
|
||||
import { selectAllIcon } from "../components/icons";
|
||||
import { StoreAction } from "../store";
|
||||
|
||||
@@ -20,17 +19,17 @@ export const actionSelectAll = register({
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectedElementIds = excludeElementsInFramesFromSelection(
|
||||
elements.filter(
|
||||
const selectedElementIds = elements
|
||||
.filter(
|
||||
(element) =>
|
||||
!element.isDeleted &&
|
||||
!(isTextElement(element) && element.containerId) &&
|
||||
!element.locked,
|
||||
),
|
||||
).reduce((map: Record<ExcalidrawElement["id"], true>, element) => {
|
||||
map[element.id] = true;
|
||||
return map;
|
||||
}, {});
|
||||
)
|
||||
.reduce((map: Record<ExcalidrawElement["id"], true>, element) => {
|
||||
map[element.id] = true;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
appState: {
|
||||
|
||||
Reference in New Issue
Block a user