This is an automated email from the ASF dual-hosted git repository.

aicam pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/master by this push:
     new 12849accf7 feat(gui): Enhance Deleting Edges (#3636)
12849accf7 is described below

commit 12849accf7a1734ba0fd7feeabbf4df9e0bff812
Author: Seongjin Yoon <[email protected]>
AuthorDate: Mon Aug 11 15:26:12 2025 -0700

    feat(gui): Enhance Deleting Edges (#3636)
    
    Feature:
    - Deletion of selected links with keyboard
    - Right click on link to get drop down menu
    - Delete combination of operators, comment boxes, and links together
    - Modified Drop down menu for comment box as it included "run to this
    operator" despite not being an operator.
    
    Closes #3579
    Closes #3074
    
    
    
https://github.com/user-attachments/assets/7616ad8c-8030-4c0d-b014-96596e76bd63
    
    ---------
    
    Co-authored-by: Xinyuan Lin <[email protected]>
---
 .../context-menu/context-menu.component.html       | 46 +++++++++++++++++-----
 .../context-menu/context-menu.component.spec.ts    | 14 ++++++-
 .../context-menu/context-menu.component.ts         | 39 ++++++++++++++----
 .../workflow-editor/workflow-editor.component.ts   | 36 +++++++++++++++--
 4 files changed, 114 insertions(+), 21 deletions(-)

diff --git 
a/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.html
 
b/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.html
index 1a420c587a..7a6fcba6f2 100644
--- 
a/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.html
+++ 
b/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.html
@@ -21,7 +21,8 @@
   <li
     nz-menu-item
     *ngIf="(operatorMenuService.highlightedOperators.value.length > 0 ||
-  operatorMenuService.highlightedCommentBoxes.value.length > 0)"
+  operatorMenuService.highlightedCommentBoxes.value.length > 0) &&
+  !hasHighlightedLinks()"
     (click)="onCopy()">
     <span
       nz-icon
@@ -33,6 +34,7 @@
     nz-menu-item
     *ngIf="(operatorMenuService.highlightedOperators.value.length > 0 ||
   operatorMenuService.highlightedCommentBoxes.value.length > 0) &&
+  !hasHighlightedLinks() &&
   isWorkflowModifiable"
     (click)="onCut()">
     <span
@@ -44,7 +46,8 @@
   <li
     nz-menu-item
     *ngIf="(operatorMenuService.highlightedOperators.value.length === 0 &&
-  operatorMenuService.highlightedCommentBoxes.value.length === 0) &&
+  operatorMenuService.highlightedCommentBoxes.value.length === 0 &&
+  !hasHighlightedLinks()) &&
   isWorkflowModifiable"
     (click)="onPaste()">
     <span
@@ -55,7 +58,8 @@
   </li>
   <li
     nz-menu-item
-    *ngIf="operatorMenuService.isDisableOperator && 
operatorMenuService.isDisableOperatorClickable"
+    *ngIf="operatorMenuService.isDisableOperator && 
operatorMenuService.isDisableOperatorClickable &&
+  !hasHighlightedLinks()"
     (click)="operatorMenuService.disableHighlightedOperators()">
     <span
       nz-icon
@@ -65,7 +69,8 @@
   </li>
   <li
     nz-menu-item
-    *ngIf="!operatorMenuService.isDisableOperator && 
operatorMenuService.isDisableOperatorClickable"
+    *ngIf="!operatorMenuService.isDisableOperator && 
operatorMenuService.isDisableOperatorClickable &&
+  !hasHighlightedLinks()"
     (click)="operatorMenuService.disableHighlightedOperators()">
     <span
       nz-icon
@@ -75,7 +80,8 @@
   </li>
   <li
     nz-menu-item
-    *ngIf="(operatorMenuService.isToViewResult && 
operatorMenuService.isToViewResultClickable)"
+    *ngIf="(operatorMenuService.isToViewResult && 
operatorMenuService.isToViewResultClickable) &&
+  !hasHighlightedLinks()"
     (click)="operatorMenuService.viewResultHighlightedOperators()">
     <span
       nz-icon
@@ -85,7 +91,8 @@
   </li>
   <li
     nz-menu-item
-    *ngIf="(! operatorMenuService.isToViewResult && 
operatorMenuService.isToViewResultClickable)"
+    *ngIf="(! operatorMenuService.isToViewResult && 
operatorMenuService.isToViewResultClickable) &&
+  !hasHighlightedLinks()"
     (click)="operatorMenuService.viewResultHighlightedOperators()">
     <span
       nz-icon
@@ -96,7 +103,8 @@
   <li
     nz-menu-item
     [nzDisabled]="true"
-    *ngIf="(operatorMenuService.isMarkForReuse && 
operatorMenuService.isReuseResultClickable)"
+    *ngIf="(operatorMenuService.isMarkForReuse && 
operatorMenuService.isReuseResultClickable) &&
+  !hasHighlightedLinks()"
     (click)="operatorMenuService.reuseResultHighlightedOperator()">
     <span
       nz-icon
@@ -106,7 +114,8 @@
   </li>
   <li
     nz-menu-item
-    *ngIf="(! operatorMenuService.isMarkForReuse && 
operatorMenuService.isReuseResultClickable)"
+    *ngIf="(! operatorMenuService.isMarkForReuse && 
operatorMenuService.isReuseResultClickable) &&
+  !hasHighlightedLinks()"
     (click)="operatorMenuService.reuseResultHighlightedOperator()">
     <span
       nz-icon
@@ -127,8 +136,26 @@
     >delete
   </li>
 
+  <!-- Delete option for links only -->
+  <li
+    nz-menu-item
+    *ngIf="hasHighlightedLinks() && 
+  operatorMenuService.highlightedOperators.value.length === 0 &&
+  operatorMenuService.highlightedCommentBoxes.value.length === 0 &&
+  isWorkflowModifiable"
+    (click)="onDelete()">
+    <span
+      nz-icon
+      nzType="delete"
+      nzTheme="outline"></span
+    >delete
+  </li>
+
   <li
     nz-menu-item
+    *ngIf="operatorMenuService.highlightedOperators.value.length === 1 &&
+  operatorMenuService.highlightedCommentBoxes.value.length === 0 &&
+  !hasHighlightedLinks()"
     [nzDisabled]="!canExecuteOperator()"
     (click)="operatorMenuService.executeUpToOperator()">
     <span
@@ -140,7 +167,8 @@
 
   <li
     *ngIf="workflowResultExportService.hasResultToExportOnHighlightedOperators 
&&
-           this.config.env.exportExecutionResultEnabled"
+           this.config.env.exportExecutionResultEnabled &&
+           !hasHighlightedLinks()"
     (click)="onClickExportHighlightedExecutionResult()"
     nz-menu-item>
     <span
diff --git 
a/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
 
b/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
index 5d8a85e83f..f3ca76420d 100644
--- 
a/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
+++ 
b/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
@@ -50,12 +50,18 @@ describe("ContextMenuComponent", () => {
     jointGraphWrapperSpy = jasmine.createSpyObj("JointGraphWrapper", [
       "getCurrentHighlightedOperatorIDs",
       "getCurrentHighlightedCommentBoxIDs",
+      "getCurrentHighlightedLinkIDs",
     ]);
 
     jointGraphWrapperSpy.getCurrentHighlightedOperatorIDs.and.returnValue([]);
     
jointGraphWrapperSpy.getCurrentHighlightedCommentBoxIDs.and.returnValue([]);
+    jointGraphWrapperSpy.getCurrentHighlightedLinkIDs.and.returnValue([]);
 
-    const texeraGraphSpy = jasmine.createSpyObj("TexeraGraph", 
["isOperatorDisabled"]);
+    const texeraGraphSpy = jasmine.createSpyObj("TexeraGraph", [
+      "isOperatorDisabled",
+      "hasLinkWithID",
+      "bundleActions",
+    ]);
 
     const workflowActionServiceSpy = 
jasmine.createSpyObj("WorkflowActionService", [
       "getJointGraphWrapper",
@@ -64,14 +70,20 @@ describe("ContextMenuComponent", () => {
       "deleteCommentBox",
       "getWorkflowMetadata", // Add this if used in the component
       "getTexeraGraph",
+      "deleteLinkWithID",
     ]);
     
workflowActionServiceSpy.getJointGraphWrapper.and.returnValue(jointGraphWrapperSpy);
     
workflowActionServiceSpy.getWorkflowModificationEnabledStream.and.returnValue(of(true));
     workflowActionServiceSpy.getTexeraGraph.and.returnValue(texeraGraphSpy);
     workflowActionServiceSpy.deleteOperatorsAndLinks.and.returnValue();
     workflowActionServiceSpy.deleteCommentBox.and.returnValue();
+    workflowActionServiceSpy.deleteLinkWithID.and.returnValue();
     workflowActionServiceSpy.getWorkflowMetadata.and.returnValue({ name: "Test 
Workflow" }); // Mock return value
 
+    // Set up TexeraGraph spy return values
+    texeraGraphSpy.hasLinkWithID.and.returnValue(false);
+    texeraGraphSpy.bundleActions.and.callFake((callback: Function) => 
callback());
+
     const workflowResultServiceSpy = 
jasmine.createSpyObj("WorkflowResultService", [
       "getResultService",
       "hasAnyResult",
diff --git 
a/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.ts
 
b/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.ts
index e44578e1e7..2807066b76 100644
--- 
a/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.ts
+++ 
b/core/gui/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.ts
@@ -73,6 +73,10 @@ export class ContextMenuComponent {
     );
   }
 
+  public hasHighlightedLinks(): boolean {
+    return 
this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedLinkIDs().length
 > 0;
+  }
+
   public onCopy(): void {
     this.operatorMenuService.saveHighlightedElements();
   }
@@ -87,14 +91,35 @@ export class ContextMenuComponent {
   }
 
   public onDelete(): void {
-    const highlightedOperatorIDs = 
this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs();
-    const highlightedCommentBoxIDs = this.workflowActionService
-      .getJointGraphWrapper()
-      .getCurrentHighlightedCommentBoxIDs();
-    this.workflowActionService.deleteOperatorsAndLinks(highlightedOperatorIDs);
-    highlightedCommentBoxIDs.forEach(highlightedCommentBoxID =>
-      this.workflowActionService.deleteCommentBox(highlightedCommentBoxID)
+    // Capture all highlighted IDs before starting deletion to avoid 
modification during iteration
+    const highlightedOperatorIDs = Array.from(
+      
this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()
+    );
+    const highlightedCommentBoxIDs = Array.from(
+      
this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedCommentBoxIDs()
+    );
+    const highlightedLinkIDs = Array.from(
+      
this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedLinkIDs()
     );
+
+    // Bundle all deletions together for proper undo/redo support
+    this.workflowActionService.getTexeraGraph().bundleActions(() => {
+      // Delete operators and their connected links
+      
this.workflowActionService.deleteOperatorsAndLinks(highlightedOperatorIDs);
+
+      // Delete standalone selected links
+      highlightedLinkIDs.forEach(highlightedLinkID => {
+        // Only delete if the link still exists (might have been deleted with 
operators)
+        if 
(this.workflowActionService.getTexeraGraph().hasLinkWithID(highlightedLinkID)) {
+          this.workflowActionService.deleteLinkWithID(highlightedLinkID);
+        }
+      });
+
+      // Delete comment boxes
+      highlightedCommentBoxIDs.forEach(highlightedCommentBoxID =>
+        this.workflowActionService.deleteCommentBox(highlightedCommentBoxID)
+      );
+    });
   }
 
   private registerWorkflowModifiableChangedHandler() {
diff --git 
a/core/gui/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
 
b/core/gui/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
index e56cf6e38c..089ea9eead 100644
--- 
a/core/gui/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
+++ 
b/core/gui/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
@@ -802,6 +802,15 @@ export class WorkflowEditorComponent implements 
AfterViewInit, OnDestroy {
         this.jointUIService.unfoldOperatorDetails(this.paper, operatorID);
       });
 
+    // Handle right-click on links
+    fromJointPaperEvent(this.paper, "link:contextmenu")
+      .pipe(untilDestroyed(this))
+      .subscribe(event => {
+        const linkID = event[0].model.id.toString();
+        // Highlight the link when right-clicked
+        this.workflowActionService.highlightLinks(false, linkID);
+      });
+
     fromJointPaperEvent(this.paper, "blank:pointerdown")
       .pipe(untilDestroyed(this))
       .subscribe(() => {
@@ -946,10 +955,29 @@ export class WorkflowEditorComponent implements 
AfterViewInit, OnDestroy {
   }
 
   private deleteElements(): void {
-    
this.workflowActionService.deleteOperatorsAndLinks(this.wrapper.getCurrentHighlightedOperatorIDs());
-    this.wrapper
-      .getCurrentHighlightedCommentBoxIDs()
-      .forEach(highlightedCommentBoxesID => 
this.workflowActionService.deleteCommentBox(highlightedCommentBoxesID));
+    // Capture all highlighted IDs before starting deletion to avoid 
modification during iteration
+    const highlightedOperatorIDs = 
Array.from(this.wrapper.getCurrentHighlightedOperatorIDs());
+    const highlightedCommentBoxIDs = 
Array.from(this.wrapper.getCurrentHighlightedCommentBoxIDs());
+    const highlightedLinkIDs = 
Array.from(this.wrapper.getCurrentHighlightedLinkIDs());
+
+    // Bundle all deletions together for proper undo/redo support
+    this.workflowActionService.getTexeraGraph().bundleActions(() => {
+      // Delete operators and their connected links
+      
this.workflowActionService.deleteOperatorsAndLinks(highlightedOperatorIDs);
+
+      // Delete standalone selected links
+      highlightedLinkIDs.forEach(highlightedLinkID => {
+        // Only delete if the link still exists (might have been deleted with 
operators)
+        if 
(this.workflowActionService.getTexeraGraph().hasLinkWithID(highlightedLinkID)) {
+          this.workflowActionService.deleteLinkWithID(highlightedLinkID);
+        }
+      });
+
+      // Delete comment boxes
+      highlightedCommentBoxIDs.forEach(highlightedCommentBoxID =>
+        this.workflowActionService.deleteCommentBox(highlightedCommentBoxID)
+      );
+    });
   }
 
   /**

Reply via email to