Skip to content

Commit 5cd8cc7

Browse files
committed
Remove dependencies between UI and core
1 parent 42b6c1f commit 5cd8cc7

15 files changed

Lines changed: 338 additions & 410 deletions

docs/blazor-maintainability-audit.md

Lines changed: 0 additions & 86 deletions
This file was deleted.

src/NodeDev.Blazor/Components/GraphCanvas.razor.cs

Lines changed: 4 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
namespace NodeDev.Blazor.Components;
2222

23-
public partial class GraphCanvas : ComponentBase, IDisposable, IGraphCanvas
23+
public partial class GraphCanvas : ComponentBase, IDisposable
2424
{
2525
[Parameter, EditorRequired]
2626
public Graph Graph { get; set; } = null!;
@@ -46,8 +46,6 @@ protected override void OnInitialized()
4646
base.OnInitialized();
4747
_ = NodeProvider.WarmExtensionMethodCatalogAsync();
4848

49-
Graph.GraphCanvas = this;
50-
5149
var options = new BlazorDiagramOptions
5250
{
5351
GridSize = 30,
@@ -79,6 +77,7 @@ protected override void OnInitialized()
7977
DiagramSynchronizer = new GraphDiagramSynchronizer(Graph, action => InvokeAsync(action));
8078
DiagramProjection = new GraphDiagramProjection(Graph, Diagram, DiagramSynchronizer, OnConnectionAdded);
8179
DebugVisualizer = new GraphDebugVisualizer(Graph, Diagram, action => InvokeAsync(action));
80+
IndexPage.RegisterGraphCanvas(this);
8281
}
8382

8483
#endregion
@@ -93,30 +92,13 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
9392
{
9493
await Task.Delay(100);
9594
Diagram.Batch(DiagramProjection.Initialize);
96-
DiagramSynchronizer.Start(DiagramProjection.Rebuild, StateHasChanged);
95+
DiagramSynchronizer.Start(DiagramProjection, StateHasChanged);
9796
DebugVisualizer.Start();
9897
}
9998
}
10099

101100
#endregion
102101

103-
#region UpdateConnectionType
104-
105-
public void UpdatePortColor(Connection connection)
106-
{
107-
var port = FindPort(connection);
108-
if (port == null)
109-
return;
110-
111-
var color = GetTypeShapeColor(connection.Type, connection.Parent.TypeFactory);
112-
foreach (var link in port.Links.Cast<LinkModel>())
113-
link.Color = color;
114-
115-
Diagram.Refresh();
116-
}
117-
118-
#endregion
119-
120102
#region UpdateNodes
121103

122104
private NodeModel? FindNodeModel(Node node)
@@ -426,10 +408,6 @@ private void OnNewOverloadSelected(Node.AlternateOverload overload)
426408

427409
GraphManagerService.SelectNodeOverload(PopupState.Node, overload);
428410

429-
// Refresh the node visually after overload selection
430-
// The node's ports have changed, so we need to update the UI
431-
Refresh(PopupState.Node);
432-
433411
CancelPopup();
434412
}
435413

@@ -639,92 +617,6 @@ public void ToggleBreakpointOnSelectedNode()
639617

640618
#endregion
641619

642-
#region RemoveNode
643-
644-
public void RemoveNode(Node node)
645-
{
646-
if (node is LambdaReturnNode { IsImplicit: true } boundaryReturn)
647-
{
648-
var group = FindLambdaGroup(boundaryReturn.CallableScopeId);
649-
if (group != null)
650-
{
651-
foreach (var port in group.Ports
652-
.OfType<GraphPortModel>()
653-
.Where(x => x.Connection.Parent == boundaryReturn)
654-
.ToList())
655-
{
656-
group.RemovePort(port);
657-
}
658-
group.Refresh();
659-
}
660-
return;
661-
}
662-
663-
var nodeModel = FindNodeModel(node);
664-
if (nodeModel == null)
665-
return;
666-
667-
using var suppression = DiagramSynchronizer.SuppressCanvasMutations();
668-
if (nodeModel is LambdaGroupModel modelGroup)
669-
Diagram.Groups.Remove(modelGroup);
670-
else
671-
Diagram.Nodes.Remove(nodeModel);
672-
}
673-
674-
#endregion
675-
676-
#region AddLink / RemoveLink
677-
678-
public void RemoveLinkFromGraphCanvas(Connection source, Connection destination)
679-
{
680-
using (DiagramSynchronizer.SuppressConnectionUpdates())
681-
{
682-
var link = Diagram.Links.FirstOrDefault(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection == destination);
683-
if (link != null)
684-
Diagram.Links.Remove(link);
685-
}
686-
}
687-
688-
public void AddLinkToGraphCanvas(Connection source, Connection destination)
689-
{
690-
using (DiagramSynchronizer.SuppressConnectionUpdates())
691-
{
692-
if (source.IsInput)
693-
(destination, source) = (source, destination);
694-
695-
var sourcePort = FindPort(source) ?? throw new InvalidOperationException($"No canvas port exists for {source.Parent.Name}.{source.Name}.");
696-
var destinationPort = FindPort(destination) ?? throw new InvalidOperationException($"No canvas port exists for {destination.Parent.Name}.{destination.Name}.");
697-
698-
// Make sure there isn't already an existing identical link
699-
if (Diagram.Links.OfType<LinkModel>().Any(x => (x.Source as SinglePortAnchor)?.Port == sourcePort && (x.Target as SinglePortAnchor)?.Port == destinationPort))
700-
return;
701-
702-
var link = Diagram.Links.Add(new LinkModel(sourcePort, destinationPort));
703-
704-
OnConnectionAdded(link, true);
705-
}
706-
}
707-
708-
#endregion
709-
710-
#region AddNode
711-
712-
public void AddNode(Node node)
713-
{
714-
DiagramProjection.AddNode(node);
715-
}
716-
717-
#endregion
718-
719-
#region Refresh
720-
721-
public void Refresh(Node node)
722-
{
723-
DiagramProjection.Refresh(node);
724-
}
725-
726-
#endregion
727-
728620
#region Initialize
729621

730622
public static string GetTypeShapeColor(TypeBase type, TypeFactory typeFactory)
@@ -750,9 +642,7 @@ public static string GetTypeShapeColor(TypeBase type, TypeFactory typeFactory)
750642
public void Dispose()
751643
{
752644
GC.SuppressFinalize(this);
753-
754-
if (Graph.GraphCanvas == this)
755-
Graph.GraphCanvas = null;
645+
IndexPage.UnregisterGraphCanvas(this);
756646

757647
DebugVisualizer.Dispose();
758648
DiagramSynchronizer.Dispose();

src/NodeDev.Blazor/Components/GraphDiagramProjection.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Blazor.Diagrams;
2+
using Blazor.Diagrams.Core.Anchors;
23
using Blazor.Diagrams.Core.Models;
34
using Blazor.Diagrams.Core.Models.Base;
45
using NodeDev.Blazor.DiagramsModels;
@@ -95,6 +96,9 @@ public void Rebuild()
9596
/// </summary>
9697
public void AddNode(Node node)
9798
{
99+
if (FindNodeModel(node) != null)
100+
return;
101+
98102
if (node is CreateDelegateNode delegateNode)
99103
AddLambdaGroupModel(delegateNode);
100104
else if (node is LambdaReturnNode { IsImplicit: true } boundaryReturn)
@@ -105,6 +109,96 @@ public void AddNode(Node node)
105109
ReparentScopedModels();
106110
}
107111

112+
/// <summary>
113+
/// Removes the model representing a domain node without allowing diagram callbacks to repeat the domain mutation.
114+
/// </summary>
115+
public void RemoveNode(Node node)
116+
{
117+
if (node is LambdaReturnNode { IsImplicit: true } boundaryReturn)
118+
{
119+
var group = FindLambdaGroup(boundaryReturn.CallableScopeId);
120+
if (group == null)
121+
return;
122+
123+
foreach (var port in group.Ports
124+
.OfType<GraphPortModel>()
125+
.Where(port => port.Connection.Parent == boundaryReturn)
126+
.ToList())
127+
{
128+
group.RemovePort(port);
129+
}
130+
group.Refresh();
131+
return;
132+
}
133+
134+
var nodeModel = FindNodeModel(node);
135+
if (nodeModel == null)
136+
return;
137+
138+
using var suppression = Synchronizer.SuppressCanvasMutations();
139+
if (nodeModel is LambdaGroupModel groupModel)
140+
Diagram.Groups.Remove(groupModel);
141+
else
142+
Diagram.Nodes.Remove(nodeModel);
143+
}
144+
145+
/// <summary>
146+
/// Adds a projected link if the diagram does not already contain it.
147+
/// </summary>
148+
public void AddLink(Connection source, Connection destination)
149+
{
150+
if (source.IsInput)
151+
(destination, source) = (source, destination);
152+
153+
var sourcePort = FindPort(source) ?? throw new InvalidOperationException($"No canvas port exists for {source.Parent.Name}.{source.Name}.");
154+
var destinationPort = FindPort(destination) ?? throw new InvalidOperationException($"No canvas port exists for {destination.Parent.Name}.{destination.Name}.");
155+
if (Diagram.Links.Any(link =>
156+
(link.Source.Model as GraphPortModel)?.Connection == source &&
157+
(link.Target.Model as GraphPortModel)?.Connection == destination))
158+
{
159+
return;
160+
}
161+
162+
LinkModel link;
163+
using (Synchronizer.SuppressConnectionUpdates())
164+
link = Diagram.Links.Add(new LinkModel(new SinglePortAnchor(sourcePort), new SinglePortAnchor(destinationPort)));
165+
166+
ConfigureConnection(link, true);
167+
}
168+
169+
/// <summary>
170+
/// Removes a projected link if it still exists.
171+
/// </summary>
172+
public void RemoveLink(Connection source, Connection destination)
173+
{
174+
if (source.IsInput)
175+
(destination, source) = (source, destination);
176+
177+
using var suppression = Synchronizer.SuppressConnectionUpdates();
178+
var link = Diagram.Links.FirstOrDefault(candidate =>
179+
(candidate.Source.Model as GraphPortModel)?.Connection == source &&
180+
(candidate.Target.Model as GraphPortModel)?.Connection == destination);
181+
if (link != null)
182+
Diagram.Links.Remove(link);
183+
}
184+
185+
/// <summary>
186+
/// Recomputes the visual state of a changed domain connection and its links.
187+
/// </summary>
188+
public void RefreshConnection(Connection connection)
189+
{
190+
var port = FindPort(connection);
191+
if (port == null)
192+
return;
193+
194+
var color = GraphCanvas.GetTypeShapeColor(connection.Type, connection.Parent.TypeFactory);
195+
foreach (var link in port.Links.Cast<LinkModel>())
196+
link.Color = color;
197+
198+
port.Parent.Refresh();
199+
Diagram.Refresh();
200+
}
201+
108202
/// <summary>
109203
/// Refreshes an existing projected node. Port topology is rebuilt only when the domain node's
110204
/// inputs or outputs changed; otherwise the cheaper model refresh path is used.

0 commit comments

Comments
 (0)