Fix group element removing (#1676)
This commit is contained in:
parent
17e9cc4506
commit
f413bab3de
@ -1,4 +1,4 @@
|
|||||||
import { deleteSelectedElements, isSomeElementSelected } from "../scene";
|
import { isSomeElementSelected } from "../scene";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -6,14 +6,49 @@ import { trash } from "../components/icons";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
import { newElementWith } from "../element/mutateElement";
|
||||||
|
import { getElementsInGroup } from "../groups";
|
||||||
|
|
||||||
|
const deleteSelectedElements = (
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
elements: elements.map((el) => {
|
||||||
|
if (appState.selectedElementIds[el.id]) {
|
||||||
|
return newElementWith(el, { isDeleted: true });
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}),
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
selectedElementIds: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const actionDeleteSelected = register({
|
export const actionDeleteSelected = register({
|
||||||
name: "deleteSelectedElements",
|
name: "deleteSelectedElements",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
const {
|
let {
|
||||||
elements: nextElements,
|
elements: nextElements,
|
||||||
appState: nextAppState,
|
appState: nextAppState,
|
||||||
} = deleteSelectedElements(elements, appState);
|
} = deleteSelectedElements(elements, appState);
|
||||||
|
|
||||||
|
if (appState.editingGroupId) {
|
||||||
|
const siblingElements = getElementsInGroup(
|
||||||
|
getNonDeletedElements(nextElements),
|
||||||
|
appState.editingGroupId!,
|
||||||
|
);
|
||||||
|
if (siblingElements.length) {
|
||||||
|
nextAppState = {
|
||||||
|
...nextAppState,
|
||||||
|
selectedElementIds: { [siblingElements[0].id]: true },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
elements: nextElements,
|
elements: nextElements,
|
||||||
appState: {
|
appState: {
|
||||||
|
@ -30,7 +30,6 @@ import {
|
|||||||
isNonDeletedElement,
|
isNonDeletedElement,
|
||||||
} from "../element";
|
} from "../element";
|
||||||
import {
|
import {
|
||||||
deleteSelectedElements,
|
|
||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
isOverScrollBars,
|
isOverScrollBars,
|
||||||
getElementAtPosition,
|
getElementAtPosition,
|
||||||
@ -126,7 +125,7 @@ import { invalidateShapeForElement } from "../renderer/renderElement";
|
|||||||
import { unstable_batchedUpdates } from "react-dom";
|
import { unstable_batchedUpdates } from "react-dom";
|
||||||
import { SceneStateCallbackRemover } from "../scene/globalScene";
|
import { SceneStateCallbackRemover } from "../scene/globalScene";
|
||||||
import { isLinearElement } from "../element/typeChecks";
|
import { isLinearElement } from "../element/typeChecks";
|
||||||
import { actionFinalize } from "../actions";
|
import { actionFinalize, actionDeleteSelected } from "../actions";
|
||||||
import {
|
import {
|
||||||
restoreUsernameFromLocalStorage,
|
restoreUsernameFromLocalStorage,
|
||||||
saveUsernameToLocalStorage,
|
saveUsernameToLocalStorage,
|
||||||
@ -593,13 +592,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.copyAll();
|
this.copyAll();
|
||||||
const { elements: nextElements, appState } = deleteSelectedElements(
|
this.actionManager.executeAction(actionDeleteSelected);
|
||||||
globalSceneState.getElementsIncludingDeleted(),
|
|
||||||
this.state,
|
|
||||||
);
|
|
||||||
globalSceneState.replaceAllElements(nextElements);
|
|
||||||
history.resumeRecording();
|
|
||||||
this.setState({ ...appState });
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
if (
|
if (
|
||||||
(element as any)[key] === value &&
|
(element as any)[key] === value &&
|
||||||
// if object, always update in case its deep prop was mutated
|
// if object, always update in case its deep prop was mutated
|
||||||
(typeof value !== "object" || value === null)
|
(typeof value !== "object" || value === null || key === "groupIds")
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import { measureText, getFontString } from "../utils";
|
|||||||
import { randomInteger, randomId } from "../random";
|
import { randomInteger, randomId } from "../random";
|
||||||
import { newElementWith } from "./mutateElement";
|
import { newElementWith } from "./mutateElement";
|
||||||
import { getNewGroupIdsForDuplication } from "../groups";
|
import { getNewGroupIdsForDuplication } from "../groups";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
|
||||||
type ElementConstructorOpts = {
|
type ElementConstructorOpts = {
|
||||||
x: ExcalidrawGenericElement["x"];
|
x: ExcalidrawGenericElement["x"];
|
||||||
@ -169,7 +170,7 @@ export const deepCopyElement = (val: any, depth: number = 0) => {
|
|||||||
* @param overrides Any element properties to override
|
* @param overrides Any element properties to override
|
||||||
*/
|
*/
|
||||||
export const duplicateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
export const duplicateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||||
editingGroupId: GroupId | null,
|
editingGroupId: AppState["editingGroupId"],
|
||||||
groupIdMapForOperation: Map<GroupId, GroupId>,
|
groupIdMapForOperation: Map<GroupId, GroupId>,
|
||||||
element: TElement,
|
element: TElement,
|
||||||
overrides?: Partial<TElement>,
|
overrides?: Partial<TElement>,
|
||||||
|
@ -21,7 +21,7 @@ type _ExcalidrawElementBase = Readonly<{
|
|||||||
version: number;
|
version: number;
|
||||||
versionNonce: number;
|
versionNonce: number;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
groupIds: GroupId[];
|
groupIds: readonly GroupId[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ExcalidrawSelectionElement = _ExcalidrawElementBase & {
|
export type ExcalidrawSelectionElement = _ExcalidrawElementBase & {
|
||||||
|
@ -7,15 +7,31 @@ export function selectGroup(
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
elements: readonly NonDeleted<ExcalidrawElement>[],
|
elements: readonly NonDeleted<ExcalidrawElement>[],
|
||||||
): AppState {
|
): AppState {
|
||||||
|
const elementsInGroup = elements.filter((element) =>
|
||||||
|
element.groupIds.includes(groupId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (elementsInGroup.length < 2) {
|
||||||
|
if (
|
||||||
|
appState.selectedGroupIds[groupId] ||
|
||||||
|
appState.editingGroupId === groupId
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...appState,
|
||||||
|
selectedGroupIds: { ...appState.selectedGroupIds, [groupId]: false },
|
||||||
|
editingGroupId: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return appState;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...appState,
|
...appState,
|
||||||
selectedGroupIds: { ...appState.selectedGroupIds, [groupId]: true },
|
selectedGroupIds: { ...appState.selectedGroupIds, [groupId]: true },
|
||||||
selectedElementIds: {
|
selectedElementIds: {
|
||||||
...appState.selectedElementIds,
|
...appState.selectedElementIds,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
elements
|
elementsInGroup.map((element) => [element.id, true]),
|
||||||
.filter((element) => element.groupIds.includes(groupId))
|
|
||||||
.map((element) => [element.id, true]),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -89,8 +105,8 @@ export function getSelectedGroupIdForElement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getNewGroupIdsForDuplication(
|
export function getNewGroupIdsForDuplication(
|
||||||
groupIds: GroupId[],
|
groupIds: ExcalidrawElement["groupIds"],
|
||||||
editingGroupId: GroupId | null,
|
editingGroupId: AppState["editingGroupId"],
|
||||||
mapper: (groupId: GroupId) => GroupId,
|
mapper: (groupId: GroupId) => GroupId,
|
||||||
) {
|
) {
|
||||||
const copy = [...groupIds];
|
const copy = [...groupIds];
|
||||||
@ -107,9 +123,9 @@ export function getNewGroupIdsForDuplication(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function addToGroup(
|
export function addToGroup(
|
||||||
prevGroupIds: GroupId[],
|
prevGroupIds: ExcalidrawElement["groupIds"],
|
||||||
newGroupId: GroupId,
|
newGroupId: GroupId,
|
||||||
editingGroupId: GroupId | null,
|
editingGroupId: AppState["editingGroupId"],
|
||||||
) {
|
) {
|
||||||
// insert before the editingGroupId, or push to the end.
|
// insert before the editingGroupId, or push to the end.
|
||||||
const groupIds = [...prevGroupIds];
|
const groupIds = [...prevGroupIds];
|
||||||
@ -123,7 +139,7 @@ export function addToGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function removeFromSelectedGroups(
|
export function removeFromSelectedGroups(
|
||||||
groupIds: GroupId[],
|
groupIds: ExcalidrawElement["groupIds"],
|
||||||
selectedGroupIds: { [groupId: string]: boolean },
|
selectedGroupIds: { [groupId: string]: boolean },
|
||||||
) {
|
) {
|
||||||
return groupIds.filter((groupId) => !selectedGroupIds[groupId]);
|
return groupIds.filter((groupId) => !selectedGroupIds[groupId]);
|
||||||
|
@ -22,6 +22,7 @@ const clearAppStatePropertiesForHistory = (appState: AppState) => {
|
|||||||
return {
|
return {
|
||||||
selectedElementIds: appState.selectedElementIds,
|
selectedElementIds: appState.selectedElementIds,
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
|
editingGroupId: appState.editingGroupId,
|
||||||
name: appState.name,
|
name: appState.name,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export { isOverScrollBars } from "./scrollbars";
|
export { isOverScrollBars } from "./scrollbars";
|
||||||
export {
|
export {
|
||||||
deleteSelectedElements,
|
|
||||||
isSomeElementSelected,
|
isSomeElementSelected,
|
||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
getCommonAttributeOfSelectedElements,
|
getCommonAttributeOfSelectedElements,
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getElementAbsoluteCoords, getElementBounds } from "../element";
|
import { getElementAbsoluteCoords, getElementBounds } from "../element";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { newElementWith } from "../element/mutateElement";
|
|
||||||
|
|
||||||
export const getElementsWithinSelection = (
|
export const getElementsWithinSelection = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
@ -31,24 +30,6 @@ export const getElementsWithinSelection = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteSelectedElements = (
|
|
||||||
elements: readonly ExcalidrawElement[],
|
|
||||||
appState: AppState,
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
elements: elements.map((el) => {
|
|
||||||
if (appState.selectedElementIds[el.id]) {
|
|
||||||
return newElementWith(el, { isDeleted: true });
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
}),
|
|
||||||
appState: {
|
|
||||||
...appState,
|
|
||||||
selectedElementIds: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isSomeElementSelected = (
|
export const isSomeElementSelected = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -71,8 +71,10 @@ export type AppState = {
|
|||||||
showShortcutsDialog: boolean;
|
showShortcutsDialog: boolean;
|
||||||
zenModeEnabled: boolean;
|
zenModeEnabled: boolean;
|
||||||
|
|
||||||
// groups
|
/** top-most selected groups (i.e. does not include nested groups) */
|
||||||
selectedGroupIds: { [groupId: string]: boolean };
|
selectedGroupIds: { [groupId: string]: boolean };
|
||||||
|
/** group being edited when you drill down to its constituent element
|
||||||
|
(e.g. when you double-click on a group's element) */
|
||||||
editingGroupId: GroupId | null;
|
editingGroupId: GroupId | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user