This is an automated email from the ASF dual-hosted git repository.
linxinyuan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new 1d2e9cce00 fix(ui): Prevent link delete buttons from overlapping
(#3602)
1d2e9cce00 is described below
commit 1d2e9cce00eef2c8b44d7914e8d8230b9ba6abfb
Author: Ma77Ball <[email protected]>
AuthorDate: Sun Sep 28 23:03:40 2025 -0700
fix(ui): Prevent link delete buttons from overlapping (#3602)
# Purpose
This pull request moves the delete button on links from the sending
operator to the receiving operator to avoid overlap.
Closes #3074
# Summary of Changes
- Changed link delete button from tool markup to a linktools.button
- changed the distance of the info button to -60 to start from the end
of the link
- Subscribe to linkView and add info and remove button using the tools
view
# Demo
## Before:
[Screencast from 2025-09-10
11-06-48.webm](https://github.com/user-attachments/assets/6b0706b0-621f-417e-aeeb-5717cc5a6fb2)
## After:
[Screencast from 2025-09-10
11-01-41.webm](https://github.com/user-attachments/assets/10e0e0f5-1747-49fc-9e98-89e16d1ee7fd)
---------
Signed-off-by: Ma77Ball <[email protected]>
Co-authored-by: Xinyuan Lin <[email protected]>
Co-authored-by: Copilot <[email protected]>
---
.../workflow-editor/workflow-editor.component.ts | 134 ++++++++++++++++++---
.../workspace/service/joint-ui/joint-ui.service.ts | 38 ------
2 files changed, 120 insertions(+), 52 deletions(-)
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 089ea9eead..e46bb9acec 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
@@ -17,14 +17,19 @@
* under the License.
*/
-import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, OnDestroy }
from "@angular/core";
+import { OnInit, AfterViewInit, ChangeDetectorRef, Component, ElementRef,
OnDestroy } from "@angular/core";
import { fromEvent, merge, Subject } from "rxjs";
import { NzModalCommentBoxComponent } from
"./comment-box-modal/nz-modal-comment-box.component";
import { NzModalRef, NzModalService } from "ng-zorro-antd/modal";
import { DragDropService } from "../../service/drag-drop/drag-drop.service";
import { DynamicSchemaService } from
"../../service/dynamic-schema/dynamic-schema.service";
import { ExecuteWorkflowService } from
"../../service/execute-workflow/execute-workflow.service";
-import { fromJointPaperEvent, JointUIService, linkPathStrokeColor } from
"../../service/joint-ui/joint-ui.service";
+import {
+ deleteButtonPath,
+ fromJointPaperEvent,
+ JointUIService,
+ linkPathStrokeColor,
+} from "../../service/joint-ui/joint-ui.service";
import { ValidationWorkflowService } from
"../../service/validation/validation-workflow.service";
import { WorkflowActionService } from
"../../service/workflow-graph/model/workflow-action.service";
import { WorkflowStatusService } from
"../../service/workflow-status/workflow-status.service";
@@ -82,7 +87,7 @@ export const MAIN_CANVAS = {
templateUrl: "workflow-editor.component.html",
styleUrls: ["workflow-editor.component.scss"],
})
-export class WorkflowEditorComponent implements AfterViewInit, OnDestroy {
+export class WorkflowEditorComponent implements OnInit, AfterViewInit,
OnDestroy {
editor!: HTMLElement;
editorWrapper!: HTMLElement;
paper!: joint.dia.Paper;
@@ -91,6 +96,8 @@ export class WorkflowEditorComponent implements
AfterViewInit, OnDestroy {
private _onProcessKeyboardActionObservable: Subject<void> = new Subject();
private wrapper;
private currentOpenedOperatorID: string | null = null;
+ private removeButton!: new () => joint.linkTools.Button;
+ private breakpointButton!: new () => joint.linkTools.Button;
constructor(
private workflowActionService: WorkflowActionService,
@@ -114,6 +121,12 @@ export class WorkflowEditorComponent implements
AfterViewInit, OnDestroy {
this.wrapper = this.workflowActionService.getJointGraphWrapper();
}
+ ngOnInit(): void {
+ // Cache the tool constructors
+ this.removeButton = WorkflowEditorComponent.getRemoveButton();
+ this.breakpointButton = WorkflowEditorComponent.getBreakpointButton();
+ }
+
/**
* This function is provided to JointJS to disallow links starting from an
in port.
*
@@ -1086,18 +1099,17 @@ export class WorkflowEditorComponent implements
AfterViewInit, OnDestroy {
fromJointPaperEvent(this.paper, "link:mouseenter")
.pipe(map(value => value[0]))
.pipe(untilDestroyed(this))
- .subscribe(elementView => {
+ .subscribe(linkView => {
+ // Create an array to hold the tools
+ const tools: joint.dia.ToolView[] = [new this.removeButton()];
+
+ // If breakpoints are enabled, also add the breakpoint button
if (this.config.env.linkBreakpointEnabled) {
- this.paper.getModelById(elementView.model.id).attr({
- ".tool-remove": { display: "block" },
- });
-
this.paper.getModelById(elementView.model.id).findView(this.paper).showTools();
- } else {
- // only display the delete button
- this.paper.getModelById(elementView.model.id).attr({
- ".tool-remove": { display: "block" },
- });
+ tools.push(new this.breakpointButton());
}
+
+ const toolsView = new joint.dia.ToolsView({ tools });
+ linkView.addTools(toolsView);
});
/**
@@ -1139,7 +1151,7 @@ export class WorkflowEditorComponent implements
AfterViewInit, OnDestroy {
.pipe(this.wrapper.jointGraphContext.bufferWhileAsync,
untilDestroyed(this))
.subscribe(link => {
const linkView = link.findView(this.paper);
- const breakpointButtonTool = this.jointUIService.getBreakpointButton();
+ const breakpointButtonTool = this.breakpointButton;
const breakpointButton = new breakpointButtonTool();
const toolsView = new joint.dia.ToolsView({
name: "basic-tools",
@@ -1362,4 +1374,98 @@ export class WorkflowEditorComponent implements
AfterViewInit, OnDestroy {
this.paper.translate(-targetCoord.x, -targetCoord.y);
});
}
+
+ /**
+ * Info button on link between operator shown when user hovers over links
+ */
+ private static getBreakpointButton(): new () => joint.linkTools.Button {
+ return joint.linkTools.Button.extend({
+ name: "info-button",
+ options: {
+ markup: [
+ {
+ tagName: "circle",
+ selector: "info-button",
+ attributes: {
+ r: 10,
+ fill: "#001DFF",
+ cursor: "pointer",
+ },
+ },
+ {
+ tagName: "path",
+ selector: "icon",
+ attributes: {
+ d: "M -2 4 2 4 M 0 3 0 0 M -2 -1 1 -1 M -1 -4 1 -4",
+ fill: "none",
+ stroke: "#FFFFFF",
+ "stroke-width": 2,
+ "pointer-events": "none",
+ },
+ },
+ ],
+ distance: -60,
+ offset: 0,
+ action: function (event: JQuery.Event, linkView: joint.dia.LinkView) {
+ // when this button is clicked, it triggers an joint paper event
+ if (linkView.paper) {
+ linkView.paper.trigger("tool:breakpoint", linkView, event);
+ }
+ },
+ },
+ });
+ }
+
+ /**
+ * Remove button on link between operator shown when user hovers over links
+ */
+ private static RemoveButton: new () => joint.linkTools.Button;
+
+ private static getRemoveButton(): new () => joint.linkTools.Button {
+ // Check if the class has already been created.
+ if (!WorkflowEditorComponent.RemoveButton) {
+ // If not, create it once and store it in the static property.
+ WorkflowEditorComponent.RemoveButton = joint.linkTools.Button.extend({
+ name: "remove-button",
+ options: {
+ markup: [
+ {
+ tagName: "circle",
+ selector: "button",
+ attributes: {
+ r: 10,
+ fill: "none",
+ stroke: "#D8656A",
+ "stroke-width": 2,
+ "pointer-events": "visibleStroke",
+ cursor: "pointer",
+ },
+ },
+ {
+ tagName: "path",
+ selector: "icon",
+ attributes: {
+ d: "M -4 -4 L 4 4 M 4 -4 L -4 4",
+ fill: "none",
+ stroke: "#D8656A",
+ "stroke-width": 2,
+ "stroke-linecap": "round",
+ "pointer-events": "none",
+ },
+ },
+ ],
+ distance: -90,
+ offset: 0,
+ action: function (evt: JQuery.Event, linkView: joint.dia.LinkView) {
+ if (linkView.paper) {
+ linkView.paper.trigger("tool:remove", linkView, evt);
+ }
+ },
+ },
+ });
+ }
+
+ // Return the cached class.
+ return WorkflowEditorComponent.RemoveButton;
+ }
}
diff --git a/core/gui/src/app/workspace/service/joint-ui/joint-ui.service.ts
b/core/gui/src/app/workspace/service/joint-ui/joint-ui.service.ts
index 700594ab2d..ff0c930ab7 100644
--- a/core/gui/src/app/workspace/service/joint-ui/joint-ui.service.ts
+++ b/core/gui/src/app/workspace/service/joint-ui/joint-ui.service.ts
@@ -472,44 +472,6 @@ export class JointUIService {
jointPaper.getModelById(operator.operatorID).attr(`.${operatorNameClass}/text`,
displayName);
}
- public getBreakpointButton(): new () => joint.linkTools.Button {
- return joint.linkTools.Button.extend({
- name: "info-button",
- options: {
- markup: [
- {
- tagName: "circle",
- selector: "info-button",
- attributes: {
- r: 10,
- fill: "#001DFF",
- cursor: "pointer",
- },
- },
- {
- tagName: "path",
- selector: "icon",
- attributes: {
- d: "M -2 4 2 4 M 0 3 0 0 M -2 -1 1 -1 M -1 -4 1 -4",
- fill: "none",
- stroke: "#FFFFFF",
- "stroke-width": 2,
- "pointer-events": "none",
- },
- },
- ],
- distance: 60,
- offset: 0,
- action: function (event: JQuery.Event, linkView: joint.dia.LinkView) {
- // when this button is clicked, it triggers an joint paper event
- if (linkView.paper) {
- linkView.paper.trigger("tool:breakpoint", linkView, event);
- }
- },
- },
- });
- }
-
public getCommentElement(commentBox: CommentBox): joint.dia.Element {
const basic = new joint.shapes.standard.Rectangle();
if (commentBox.commentBoxPosition)
basic.position(commentBox.commentBoxPosition.x,
commentBox.commentBoxPosition.y);