Fix group element removing (#1676)

This commit is contained in:
David Luzar 2020-05-30 22:48:57 +02:00 committed by GitHub
parent 17e9cc4506
commit f413bab3de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 478 additions and 43 deletions

View File

@ -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: {

View File

@ -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();
}); });

View File

@ -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;
} }

View File

@ -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>,

View File

@ -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 & {

View File

@ -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]);

View File

@ -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,
}; };
}; };

View File

@ -1,6 +1,5 @@
export { isOverScrollBars } from "./scrollbars"; export { isOverScrollBars } from "./scrollbars";
export { export {
deleteSelectedElements,
isSomeElementSelected, isSomeElementSelected,
getElementsWithinSelection, getElementsWithinSelection,
getCommonAttributeOfSelectedElements, getCommonAttributeOfSelectedElements,

View File

@ -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

View File

@ -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;
}; };