2
0
Эх сурвалжийг харах

alternative ux: drag with lasso right away

Ryan Di 2 сар өмнө
parent
commit
993eaa361b

+ 98 - 7
packages/excalidraw/components/App.tsx

@@ -6596,8 +6596,94 @@ class App extends React.Component<AppProps, AppState> {
           pointerDownState.origin.y,
           event.shiftKey,
         );
-        // Block drag until next pointerdown if a lasso selection is made
-        pointerDownState.drag.blockDragAfterLasso = true;
+      }
+
+      // For lasso tool, if we hit an element, select it immediately like normal selection
+      if (pointerDownState.hit.element && !hitSelectedElement) {
+        this.setState((prevState) => {
+          let nextSelectedElementIds: { [id: string]: true } = {
+            ...prevState.selectedElementIds,
+            [pointerDownState.hit.element!.id]: true,
+          };
+
+          const previouslySelectedElements: ExcalidrawElement[] = [];
+
+          Object.keys(prevState.selectedElementIds).forEach((id) => {
+            const element = this.scene.getElement(id);
+            element && previouslySelectedElements.push(element);
+          });
+
+          const hitElement = pointerDownState.hit.element!;
+
+          // if hitElement is frame-like, deselect all of its elements
+          // if they are selected
+          if (isFrameLikeElement(hitElement)) {
+            getFrameChildren(previouslySelectedElements, hitElement.id).forEach(
+              (element) => {
+                delete nextSelectedElementIds[element.id];
+              },
+            );
+          } else if (hitElement.frameId) {
+            // if hitElement is in a frame and its frame has been selected
+            // disable selection for the given element
+            if (nextSelectedElementIds[hitElement.frameId]) {
+              delete nextSelectedElementIds[hitElement.id];
+            }
+          } else {
+            // hitElement is neither a frame nor an element in a frame
+            // but since hitElement could be in a group with some frames
+            // this means selecting hitElement will have the frames selected as well
+            // because we want to keep the invariant:
+            // - frames and their elements are not selected at the same time
+            // we deselect elements in those frames that were previously selected
+
+            const groupIds = hitElement.groupIds;
+            const framesInGroups = new Set(
+              groupIds
+                .flatMap((gid) =>
+                  getElementsInGroup(this.scene.getNonDeletedElements(), gid),
+                )
+                .filter((element) => isFrameLikeElement(element))
+                .map((frame) => frame.id),
+            );
+
+            if (framesInGroups.size > 0) {
+              previouslySelectedElements.forEach((element) => {
+                if (element.frameId && framesInGroups.has(element.frameId)) {
+                  // deselect element and groups containing the element
+                  delete nextSelectedElementIds[element.id];
+                  element.groupIds
+                    .flatMap((gid) =>
+                      getElementsInGroup(
+                        this.scene.getNonDeletedElements(),
+                        gid,
+                      ),
+                    )
+                    .forEach((element) => {
+                      delete nextSelectedElementIds[element.id];
+                    });
+                }
+              });
+            }
+          }
+
+          return {
+            ...selectGroupsForSelectedElements(
+              {
+                editingGroupId: prevState.editingGroupId,
+                selectedElementIds: nextSelectedElementIds,
+              },
+              this.scene.getNonDeletedElements(),
+              prevState,
+              this,
+            ),
+            showHyperlinkPopup:
+              hitElement.link || isEmbeddableElement(hitElement)
+                ? "info"
+                : false,
+          };
+        });
+        pointerDownState.hit.wasAddedToSelection = true;
       }
     } else if (this.state.activeTool.type === "text") {
       this.handleTextOnPointerDown(event, pointerDownState);
@@ -6978,7 +7064,6 @@ class App extends React.Component<AppProps, AppState> {
         hasOccurred: false,
         offset: null,
         origin: { ...origin },
-        blockDragAfterLasso: false,
       },
       eventListeners: {
         onMove: null,
@@ -8289,12 +8374,14 @@ class App extends React.Component<AppProps, AppState> {
           pointerDownState.hit.element?.id;
       if (
         (hasHitASelectedElement ||
-          pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements) &&
+          pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements ||
+          (this.state.activeTool.type === "lasso" &&
+            pointerDownState.hit.element)) &&
         !isSelectingPointsInLineEditor &&
         (this.state.activeTool.type !== "lasso" ||
           pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements ||
-          hasHitASelectedElement) &&
-        !pointerDownState.drag.blockDragAfterLasso
+          hasHitASelectedElement ||
+          pointerDownState.hit.element)
       ) {
         const selectedElements = this.scene.getSelectedElements(this.state);
 
@@ -8319,6 +8406,11 @@ class App extends React.Component<AppProps, AppState> {
         // if elements should be deselected on pointerup
         pointerDownState.drag.hasOccurred = true;
 
+        // Clear lasso trail when starting to drag with lasso tool
+        if (this.state.activeTool.type === "lasso") {
+          this.lassoTrail.endPath();
+        }
+
         // prevent dragging even if we're no longer holding cmd/ctrl otherwise
         // it would have weird results (stuff jumping all over the screen)
         // Checking for editingTextElement to avoid jump while editing on mobile #6503
@@ -8915,7 +9007,6 @@ class App extends React.Component<AppProps, AppState> {
   ): (event: PointerEvent) => void {
     return withBatchedUpdates((childEvent: PointerEvent) => {
       this.removePointer(childEvent);
-      pointerDownState.drag.blockDragAfterLasso = false;
       if (pointerDownState.eventListeners.onMove) {
         pointerDownState.eventListeners.onMove.flush();
       }

+ 0 - 2
packages/excalidraw/types.ts

@@ -782,8 +782,6 @@ export type PointerDownState = Readonly<{
     // by default same as PointerDownState.origin. On alt-duplication, reset
     // to current pointer position at time of duplication.
     origin: { x: number; y: number };
-    // used to block drag after lasso selection until next pointerdown
-    blockDragAfterLasso: boolean;
   };
   // We need to have these in the state so that we can unsubscribe them
   eventListeners: {