Skip to content

Commit 5f38236

Browse files
authored
Merge pull request #989 from kanushka/fix-comp-diagram-2
Fix listener overlap and component menu button click issue in Architecture Diagram
2 parents 9281889 + 3774c29 commit 5f38236

File tree

7 files changed

+107
-10
lines changed

7 files changed

+107
-10
lines changed

workspaces/ballerina/component-diagram/src/components/nodes/ConnectionNode/ConnectionNodeWidget.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ export function ConnectionNodeWidget(props: ConnectionNodeWidgetProps) {
164164
setMenuAnchorEl(null);
165165
};
166166

167+
const handleMenuMouseDown = (event: React.MouseEvent) => {
168+
event.stopPropagation();
169+
};
170+
171+
const handleMenuMouseUp = (event: React.MouseEvent) => {
172+
event.stopPropagation();
173+
};
174+
167175
const getNodeTitle = () => {
168176
return model.node.symbol;
169177
};
@@ -199,7 +207,12 @@ export function ConnectionNodeWidget(props: ConnectionNodeWidgetProps) {
199207
<Title hovered={isHovered}>{getNodeTitle()}</Title>
200208
<Description>{getNodeDescription()}</Description>
201209
</Header>
202-
<MenuButton appearance="icon" onClick={handleOnMenuClick}>
210+
<MenuButton
211+
appearance="icon"
212+
onClick={handleOnMenuClick}
213+
onMouseDown={handleMenuMouseDown}
214+
onMouseUp={handleMenuMouseUp}
215+
>
203216
<MoreVertIcon />
204217
</MenuButton>
205218
</ClickableArea>

workspaces/ballerina/component-diagram/src/components/nodes/EntryNode/components/AIServiceWidget.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ export function AIServiceWidget({ model, engine }: BaseNodeWidgetProps) {
8181
setMenuAnchorEl(null);
8282
};
8383

84+
const handleMenuMouseDown = (event: React.MouseEvent) => {
85+
event.stopPropagation();
86+
};
87+
88+
const handleMenuMouseUp = (event: React.MouseEvent) => {
89+
event.stopPropagation();
90+
};
91+
8492
const menuItems: Item[] = [
8593
{ id: "edit", label: "Edit", onClick: () => handleOnClick() },
8694
{ id: "delete", label: "Delete", onClick: () => onDeleteComponent(model.node) },
@@ -101,7 +109,12 @@ export function AIServiceWidget({ model, engine }: BaseNodeWidgetProps) {
101109
<Title hovered={isHovered}>{getNodeTitle(model)}</Title>
102110
<Description>{getNodeDescription(model)}</Description>
103111
</Header>
104-
<MenuButton appearance="icon" onClick={handleOnMenuClick}>
112+
<MenuButton
113+
appearance="icon"
114+
onClick={handleOnMenuClick}
115+
onMouseDown={handleMenuMouseDown}
116+
onMouseUp={handleMenuMouseUp}
117+
>
105118
<MoreVertIcon />
106119
</MenuButton>
107120
</ServiceBox>

workspaces/ballerina/component-diagram/src/components/nodes/EntryNode/components/GeneralWidget.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ export function GeneralServiceWidget({ model, engine }: BaseNodeWidgetProps) {
197197
setMenuAnchorEl(null);
198198
};
199199

200+
const handleMenuMouseDown = (event: React.MouseEvent) => {
201+
event.stopPropagation();
202+
};
203+
204+
const handleMenuMouseUp = (event: React.MouseEvent) => {
205+
event.stopPropagation();
206+
};
207+
200208
const menuItems: Item[] = [
201209
{ id: "edit", label: "Edit", onClick: () => handleOnClick() },
202210
{ id: "delete", label: "Delete", onClick: () => onDeleteComponent(model.node) },
@@ -251,7 +259,12 @@ export function GeneralServiceWidget({ model, engine }: BaseNodeWidgetProps) {
251259
<Title hovered={isHovered}>{getNodeTitle(model)}</Title>
252260
<Description>{getNodeDescription(model)}</Description>
253261
</Header>
254-
<MenuButton appearance="icon" onClick={handleOnMenuClick}>
262+
<MenuButton
263+
appearance="icon"
264+
onClick={handleOnMenuClick}
265+
onMouseDown={handleMenuMouseDown}
266+
onMouseUp={handleMenuMouseUp}
267+
>
255268
<MoreVertIcon />
256269
</MenuButton>
257270
</ServiceBox>

workspaces/ballerina/component-diagram/src/components/nodes/EntryNode/components/GraphQLServiceWidget.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,14 @@ export function GraphQLServiceWidget({ model, engine }: BaseNodeWidgetProps) {
191191
setMenuAnchorEl(null);
192192
};
193193

194+
const handleMenuMouseDown = (event: React.MouseEvent) => {
195+
event.stopPropagation();
196+
};
197+
198+
const handleMenuMouseUp = (event: React.MouseEvent) => {
199+
event.stopPropagation();
200+
};
201+
194202
const menuItems: Item[] = [
195203
{ id: "edit", label: "Edit", onClick: () => handleOnClick() },
196204
{ id: "delete", label: "Delete", onClick: () => onDeleteComponent(model.node) },
@@ -245,7 +253,12 @@ export function GraphQLServiceWidget({ model, engine }: BaseNodeWidgetProps) {
245253
<Title hovered={isHovered}>{getNodeTitle(model)}</Title>
246254
<Description>{getNodeDescription(model)}</Description>
247255
</Header>
248-
<MenuButton appearance="icon" onClick={handleOnMenuClick}>
256+
<MenuButton
257+
appearance="icon"
258+
onClick={handleOnMenuClick}
259+
onMouseDown={handleMenuMouseDown}
260+
onMouseUp={handleMenuMouseUp}
261+
>
249262
<MoreVertIcon />
250263
</MenuButton>
251264
</ServiceBox>

workspaces/ballerina/component-diagram/src/components/nodes/ListenerNode/ListenerNodeWidget.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ export function ListenerNodeWidget(props: ListenerNodeWidgetProps) {
174174
setMenuAnchorEl(null);
175175
};
176176

177+
const handleMenuMouseDown = (event: React.MouseEvent) => {
178+
event.stopPropagation();
179+
};
180+
181+
const handleMenuMouseUp = (event: React.MouseEvent) => {
182+
event.stopPropagation();
183+
};
184+
177185
const menuItems: Item[] = [
178186
{ id: "edit", label: "Edit", onClick: () => handleOnClick() },
179187

@@ -196,7 +204,12 @@ export function ListenerNodeWidget(props: ListenerNodeWidgetProps) {
196204

197205
<RightPortWidget port={model.getPort("out")!} engine={engine} />
198206
</Circle>
199-
<MenuButton appearance="icon" onClick={handleOnMenuClick}>
207+
<MenuButton
208+
appearance="icon"
209+
onClick={handleOnMenuClick}
210+
onMouseDown={handleMenuMouseDown}
211+
onMouseUp={handleMenuMouseUp}
212+
>
200213
<MoreVertIcon />
201214
</MenuButton>
202215
<Header>

workspaces/ballerina/component-diagram/src/utils/diagram.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { NodeModel } from "./types";
2424
import { EntryNodeFactory, EntryNodeModel } from "../components/nodes/EntryNode";
2525
import { ConnectionNodeFactory } from "../components/nodes/ConnectionNode/ConnectionNodeFactory";
2626
import { ListenerNodeFactory } from "../components/nodes/ListenerNode/ListenerNodeFactory";
27-
import { LISTENER_NODE_WIDTH, NodeTypes, NODE_GAP_X, ENTRY_NODE_WIDTH } from "../resources/constants";
27+
import { LISTENER_NODE_WIDTH, NodeTypes, NODE_GAP_X, ENTRY_NODE_WIDTH, NODE_GAP_Y, LISTENER_NODE_HEIGHT } from "../resources/constants";
2828
import { ListenerNodeModel } from "../components/nodes/ListenerNode";
2929
import { ConnectionNodeModel } from "../components/nodes/ConnectionNode";
3030
import { CDConnection, CDResourceFunction, CDFunction, CDService } from "@wso2/ballerina-core";
@@ -64,16 +64,26 @@ export function autoDistribute(engine: DiagramEngine) {
6464
const entryX = listenerX + LISTENER_NODE_WIDTH + NODE_GAP_X;
6565
const connectionX = entryX + ENTRY_NODE_WIDTH + NODE_GAP_X;
6666

67-
// Position listeners while maintaining relative Y positions of their services
67+
// Separate listeners into connected and unconnected
68+
const connectedListeners: ListenerNodeModel[] = [];
69+
const unconnectedListeners: ListenerNodeModel[] = [];
70+
6871
listenerNodes.forEach((node) => {
6972
const listenerNode = node as ListenerNodeModel;
7073
const attachedServices = listenerNode.node.attachedServices;
7174

72-
// Find the average Y position of attached services
75+
// Find the attached service nodes
7376
const serviceNodes = entryNodes.filter((n) => attachedServices.includes(n.getID()));
74-
const avgY = serviceNodes.reduce((sum, n) => sum + n.getY(), 0) / serviceNodes.length;
7577

76-
listenerNode.setPosition(listenerX, avgY);
78+
if (serviceNodes.length > 0) {
79+
// Has attached services - position at average Y of services
80+
const avgY = serviceNodes.reduce((sum, n) => sum + n.getY(), 0) / serviceNodes.length;
81+
listenerNode.setPosition(listenerX, avgY);
82+
connectedListeners.push(listenerNode);
83+
} else {
84+
// No attached services - will position later
85+
unconnectedListeners.push(listenerNode);
86+
}
7787
});
7888

7989
// Update X positions for entry nodes while keeping their Y positions
@@ -88,6 +98,26 @@ export function autoDistribute(engine: DiagramEngine) {
8898
connectionNode.setPosition(connectionX, node.getY());
8999
});
90100

101+
// Position unconnected listeners below all other nodes
102+
if (unconnectedListeners.length > 0) {
103+
// Find the maximum Y position among all nodes
104+
const allNodes = [...connectedListeners, ...entryNodes, ...connectionNodes];
105+
let maxY = 100; // Default starting position if no other nodes
106+
107+
if (allNodes.length > 0) {
108+
maxY = Math.max(...allNodes.map(node => {
109+
const nodeHeight = node.height || LISTENER_NODE_HEIGHT;
110+
return node.getY() + nodeHeight;
111+
}));
112+
}
113+
114+
// Position unconnected listeners below, with spacing
115+
unconnectedListeners.forEach((listenerNode, index) => {
116+
const yPosition = maxY + NODE_GAP_Y/2 + (index * (LISTENER_NODE_HEIGHT + NODE_GAP_Y/2));
117+
listenerNode.setPosition(listenerX, yPosition);
118+
});
119+
}
120+
91121
engine.repaintCanvas();
92122
}
93123

workspaces/common-libs/ui-toolkit/src/components/Button/Button.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export interface ButtonProps {
3232
sx?: React.CSSProperties;
3333
buttonSx?: React.CSSProperties;
3434
onClick?: (() => void) | ((event: React.MouseEvent<HTMLElement | SVGSVGElement>) => void);
35+
onMouseDown?: (event: React.MouseEvent) => void;
36+
onMouseUp?: (event: React.MouseEvent) => void;
3537
}
3638

3739
export const IconLabel = styled.div`

0 commit comments

Comments
 (0)