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)
+ );
+ });
}
/**