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