Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@
<Using Include="Microsoft.JSInterop" />
</ItemGroup>

<ItemGroup>
<Folder Include="wwwroot\css\" />
<Folder Include="wwwroot\js\" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@namespace BootstrapBlazor.Components
@using Microsoft.Extensions.Logging
@attribute [JSModuleAutoLoader("./_content/BootstrapBlazor.NodeGraph/js/Graph.js", AutoInvokeDispose = false, JSObjectReference = true)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Graph.js is imported both here and in the service

This creates two module instances. Unify to a single import to prevent duplicate downloads and ensure state is shared.

Suggested implementation:



@inherits BootstrapModuleComponentBase

@inject NodeGraphService NodeGraphService
@inject ILogger<NodeGraphCanvas> Logger

<div class="@ClassString" @attributes="@AdditionalAttributes" id="@Id">
<canvas @ref="_graphCanvas"></canvas>
</div>

@code {
public Graph Graph { get; private set; } = null!;

private string? ClassString => CssBuilder.Default("graph-main-container")
.AddClassFromAttributes(AdditionalAttributes)
.Build();

private IJSObjectReference _graphCanvasRef = null!;
private ElementReference _graphCanvas;

/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
// 初始化并添加引用
await InvokeVoidAsync("init", Id, DotNetObjectReference.Create(NodeGraphService));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Inline DotNetObjectReference.Create can leak references

Store the DotNetObjectReference in a private field and implement IAsyncDisposable to dispose it when the component is disposed.

// 创建图表配置
var graphRef = await InvokeAsync<IJSObjectReference>("createLGraph");
Graph = new Graph(graphRef!);
// var graph = new Graph(graphRef!);
// 创建图表画布
_graphCanvasRef = await InvokeAsync<IJSObjectReference>("createLGraphCanvas", _graphCanvas, graphRef)
?? throw new InvalidOperationException("Create GraphCanvas failed!");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.graph-main-container{
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components.Data.Interop;

/// <summary>
/// 节点插槽
/// </summary>
public class NodeSlotDto
{
/// <summary>
/// 插槽名称
/// </summary>
public string Name { get; set; } = string.Empty;
Comment on lines +12 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): NodeSlotDto omits the slot Id

Include the slot Id in the DTO so the JS side can uniquely identify slots with duplicate names.

Suggested change
/// <summary>
/// 插槽名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 插槽 Id
/// </summary>
public string Id { get; set; } = string.Empty;
/// <summary>
/// 插槽名称
/// </summary>
public string Name { get; set; } = string.Empty;


/// <summary>
/// 插槽类型 TypeName
/// </summary>
public string Type { get; set; } = string.Empty;
}

/// <summary>
/// 节点组件
/// </summary>
public class WidgetDto
{
/// <summary>
/// 组件ID,需要在当前节点下唯一
/// </summary>
public string WidgetId { get; set; } = string.Empty;

/// <summary>
/// 节点组件类型
/// </summary>
public string WidgetType { get; set; } = string.Empty;

/// <summary>
/// 节点组件名称
/// </summary>
public string DisplayName { get; set; } = string.Empty;

/// <summary>
/// 默认值
/// </summary>
public object? Value { get; set; }

/// <summary>
/// 节点组件配置
/// </summary>
public WidgetOptions? WidgetOptions { get; set; }

/// <summary>
/// 是否有回调函数
/// </summary>
public bool HasCallback { get; set; }
}

/// <summary>
/// 节点配置
/// </summary>
public class GraphNodeConfigDto
{
/// <summary>
/// 节点唯一类型路径,形如 groupA/groupB/NodeName
/// </summary>
public string TypePath { get; set; } = null!;

/// <summary>
/// 节点名称
/// </summary>
public string DisplayName { get; set; } = string.Empty;

/// <summary>
/// 输入插槽
/// </summary>
public List<NodeSlotDto> Inputs { get; set; } = new();

/// <summary>
/// 输出插槽
/// </summary>
public List<NodeSlotDto> Outputs { get; set; } = new();

/// <summary>
/// 节点组件
/// </summary>
public List<WidgetDto> Widgets { get; set; } = new();

/// <summary>
/// 是否有执行方法
/// </summary>
public bool HasAction { get; set; }
}
150 changes: 150 additions & 0 deletions src/components/BootstrapBlazor.NodeGraph/Data/WidgetsConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.Text.Json.Serialization;

namespace BootstrapBlazor.Components.Data;

/// <summary>
/// 节点组件配置
/// </summary>
public abstract record WidgetOptions
{
// public string? On { get; set; }
// public string? Off { get; set; }

/// <summary>
/// 是否只读
/// </summary>
public bool? ReadOnly { get; set; }

// public int? Y { get; set; }
// public bool? Multiline { get; set; }

// TODO: 待测试
public string? Property { get; set; }

/// <summary>
/// 不显示可编辑组件
/// </summary>
public bool? Socketless { get; set; }

// TODO: Combo中使用
// public TValue[]? Values { get; set; }
}

/// <summary>
/// 布尔组件配置
/// </summary>
public record BooleanWidgetOptions : WidgetOptions
{
}

/// <summary>
/// 数字组件配置
/// </summary>
public record NumberWidgetOptions : WidgetOptions
{
/// <summary>
/// 最小值
/// </summary>
public double? Min { get; set; }

/// <summary>
/// 最大值
/// </summary>
public double? Max { get; set; }

// step 已经废弃
/// <summary>
/// 步长
/// </summary>
[JsonPropertyName("step2")]
public double? Step { get; set; }

/// <summary>
/// 精度
/// </summary>
public int? Precision { get; set; }
}

/// <summary>
/// 滑动条组件配置
/// </summary>
public record SliderWidgetOptions : WidgetOptions
{
/// <summary>
/// 最小值
/// </summary>
public double Min { get; set; }

/// <summary>
/// 最大值
/// </summary>
public double Max { get; set; }

// step 已经废弃
/// <summary>
/// 步长
/// </summary>
[JsonPropertyName("step2")]
public double? Step { get; set; }

/// <summary>
/// 精度
/// </summary>
public int? Precision { get; set; }

// TODO: CanvasColour
// public object? SliderColor { get; set; } // TODO: Replace with actual type for CanvasColour
// public object? MarkerColor { get; set; } // TODO: Replace with actual type for CanvasColour
}

/// <summary>
/// 旋钮组件配置
/// </summary>
public record KnobWidgetOptions : WidgetOptions
{
/// <summary>
/// 最小值
/// </summary>
public double Min { get; set; }

/// <summary>
/// 最大值
/// </summary>
public double Max { get; set; }

// step 已经废弃
/// <summary>
/// 步长
/// </summary>
[JsonPropertyName("step2")]
public double? Step { get; set; }

/// <summary>
/// 精度
/// </summary>
public int? Precision { get; set; }

// TODO: CanvasColour
// public object? SliderColor { get; set; } // TODO: Replace with actual type for CanvasColour
// public object? MarkerColor { get; set; } // TODO: Replace with actual type for CanvasColour

// public string? GradientStops { get; set; }
}

/// <summary>
/// 文本组件配置
/// </summary>
public record StringWidgetOptions : WidgetOptions
{
}

/// <summary>
/// 按钮组件配置
/// </summary>
public record ButtonWidgetOptions : WidgetOptions
{
}
20 changes: 20 additions & 0 deletions src/components/BootstrapBlazor.NodeGraph/Graph.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components;

public class Graph
{
public IJSObjectReference GraphRef { get; }

internal Graph(IJSObjectReference lGraphRef)
{
GraphRef = lGraphRef;
}

public async Task RunStep(int step, bool ignoreErrors = true, int? limits = null)
{
await GraphRef.InvokeVoidAsync("runStep", step, ignoreErrors, limits);
}
}
36 changes: 36 additions & 0 deletions src/components/BootstrapBlazor.NodeGraph/GraphNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components;

public class GraphNode : IAsyncDisposable
{
private IJSObjectReference _graphNodeReference;

internal GraphNode(IJSObjectReference graphNodeReference)
{
_graphNodeReference = graphNodeReference;
}

/// <inheritdoc />
public ValueTask DisposeAsync()
{
return _graphNodeReference.DisposeAsync();
}

public ValueTask<T?> GetInputData<T>(int slotIndex)
{
return _graphNodeReference.InvokeAsync<T?>( "getInputData", slotIndex);
}

public ValueTask<T?> GetOutputData<T>(int slotIndex)
{
return _graphNodeReference.InvokeAsync<T?>( "getOutputData", slotIndex);
}

public ValueTask SetOutputData<T>(int slotIndex, T outputData)
{
return _graphNodeReference.InvokeVoidAsync( "setOutputData", slotIndex, outputData);
}
}
21 changes: 21 additions & 0 deletions src/components/BootstrapBlazor.NodeGraph/Interfaces/INodeSlot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components.Interfaces;

public interface INodeSlot
{
/// <summary>
/// slot name
/// </summary>
public string Name { get; set; }
/// <summary>
/// node下的唯一id
/// </summary>
public string Id { get; set; }
/// <summary>
/// 数据类型
/// </summary>
public Type ValueType { get; }
}
Loading