Add project files.
This commit is contained in:
24
Examples/Nodify.Calculator/APIOperationViewModel.cs
Normal file
24
Examples/Nodify.Calculator/APIOperationViewModel.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class APIOperationViewModel : OperationViewModel
|
||||
{
|
||||
public APIOperationViewModel()
|
||||
{
|
||||
_operationType = "GET";
|
||||
}
|
||||
|
||||
private string _operationType;
|
||||
|
||||
public string OperationType
|
||||
{
|
||||
get => _operationType;
|
||||
set => SetProperty(ref _operationType, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Examples/Nodify.Calculator/AddVariableDialog.xaml
Normal file
52
Examples/Nodify.Calculator/AddVariableDialog.xaml
Normal file
@@ -0,0 +1,52 @@
|
||||
<Window x:Class="Nodify.Calculator.AddVariableDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Add New Variable"
|
||||
Width="350"
|
||||
Height="280"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
Background="#2D2D30"
|
||||
Foreground="White">
|
||||
<Grid Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Variable Name:" Grid.Row="0" Margin="0 0 0 4" />
|
||||
<TextBox x:Name="VariableNameBox" Grid.Row="1" Margin="0 0 0 10"
|
||||
Background="#3E3E42" Foreground="White" Padding="4" />
|
||||
|
||||
<TextBlock Text="Variable Type:" Grid.Row="2" Margin="0 0 0 4" />
|
||||
<ComboBox x:Name="VariableTypeBox" Grid.Row="3" Margin="0 0 0 10"
|
||||
Background="#3E3E42" Foreground="White" SelectedIndex="0">
|
||||
<ComboBoxItem Content="string" />
|
||||
<ComboBoxItem Content="int" />
|
||||
<ComboBoxItem Content="double" />
|
||||
<ComboBoxItem Content="bool" />
|
||||
<ComboBoxItem Content="float" />
|
||||
<ComboBoxItem Content="decimal" />
|
||||
<ComboBoxItem Content="long" />
|
||||
<ComboBoxItem Content="DateTime" />
|
||||
<ComboBoxItem Content="object" />
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Text="Default Value (optional):" Grid.Row="4" Margin="0 0 0 4" />
|
||||
<TextBox x:Name="DefaultValueBox" Grid.Row="5" Margin="0 0 0 10"
|
||||
Background="#3E3E42" Foreground="White" Padding="4" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="7">
|
||||
<Button Content="Add" Width="80" Margin="0 0 10 0" Padding="4"
|
||||
Click="OnAddClick" IsDefault="True" />
|
||||
<Button Content="Cancel" Width="80" Padding="4"
|
||||
Click="OnCancelClick" IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
36
Examples/Nodify.Calculator/AddVariableDialog.xaml.cs
Normal file
36
Examples/Nodify.Calculator/AddVariableDialog.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public partial class AddVariableDialog : Window
|
||||
{
|
||||
public string VariableName { get; private set; } = string.Empty;
|
||||
public string VariableType { get; private set; } = "string";
|
||||
public string DefaultValue { get; private set; } = string.Empty;
|
||||
|
||||
public AddVariableDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnAddClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(VariableNameBox.Text))
|
||||
{
|
||||
MessageBox.Show("Please enter a variable name.", "Validation", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
VariableName = VariableNameBox.Text.Trim();
|
||||
VariableType = ((ComboBoxItem)VariableTypeBox.SelectedItem).Content.ToString()!;
|
||||
DefaultValue = DefaultValueBox.Text.Trim();
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
private void OnCancelClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Examples/Nodify.Calculator/App.xaml
Normal file
44
Examples/Nodify.Calculator/App.xaml
Normal file
@@ -0,0 +1,44 @@
|
||||
<Application x:Class="Nodify.Calculator.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Dark.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/FocusVisual.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Icons.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Dark.xaml" />
|
||||
|
||||
<ResourceDictionary>
|
||||
|
||||
<Style x:Key="{x:Static SystemParameters.FocusVisualStyleKey}">
|
||||
<Setter Property="Control.Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Rectangle StrokeThickness="1"
|
||||
StrokeDashArray="2"
|
||||
Margin="-2"
|
||||
RadiusX="3"
|
||||
RadiusY="3"
|
||||
Stroke="DodgerBlue" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--WPF is wonderful-->
|
||||
<Style x:Key="OriginalNodeInputStyle"
|
||||
TargetType="{x:Type nodify:NodeInput}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodeInput}}" />
|
||||
|
||||
<Style x:Key="OriginalNodeOutputStyle"
|
||||
TargetType="{x:Type nodify:NodeOutput}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
17
Examples/Nodify.Calculator/App.xaml.cs
Normal file
17
Examples/Nodify.Calculator/App.xaml.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
142
Examples/Nodify.Calculator/ApplicationViewModel.cs
Normal file
142
Examples/Nodify.Calculator/ApplicationViewModel.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using Newtonsoft.Json;
|
||||
using Nodify.Calculator.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class ApplicationViewModel : ObservableObject
|
||||
{
|
||||
public NodifyObservableCollection<EditorViewModel> Editors { get; } = new NodifyObservableCollection<EditorViewModel>();
|
||||
|
||||
public ApplicationViewModel()
|
||||
{
|
||||
AddEditorCommand = new DelegateCommand(() => Editors.Add(new EditorViewModel
|
||||
{
|
||||
Name = $"Editor {Editors.Count + 1}"
|
||||
}));
|
||||
CloseEditorCommand = new DelegateCommand<Guid>(
|
||||
id => Editors.RemoveOne(editor => editor.Id == id),
|
||||
_ => Editors.Count > 0 && SelectedEditor != null);
|
||||
RunFlowCommand = new DelegateCommand(() =>
|
||||
{
|
||||
Executor ex = new Executor(Editors.First());
|
||||
var result = ex.PerformPreCheck();
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
MessageBox.Show("Error occured while executing the request : " + result);
|
||||
}
|
||||
else
|
||||
{
|
||||
FlowRunner runner = new FlowRunner(ex);
|
||||
runner.ShowDialog();
|
||||
}
|
||||
});
|
||||
SaveFileCommand = new DelegateCommand(() =>
|
||||
{
|
||||
var firstEditor = Editors.First();
|
||||
var allNodes = firstEditor.Calculator.Operations;
|
||||
|
||||
SaveGraphModel svm = new SaveGraphModel();
|
||||
foreach (var item in allNodes)
|
||||
{
|
||||
SaveNodes svn = new SaveNodes()
|
||||
{
|
||||
Location = item.Location,
|
||||
};
|
||||
svm.Nodes.Add(svn);
|
||||
}
|
||||
|
||||
|
||||
var jsonEditors = JsonConvert.SerializeObject(svm, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
//var jsonEditors = JsonConvert.SerializeObject(Editors);
|
||||
File.WriteAllText("SaveFile.AEXN", jsonEditors);
|
||||
});
|
||||
OpenFileCommand = new DelegateCommand(() =>
|
||||
{
|
||||
if (File.Exists("SaveFile.AEXN"))
|
||||
{
|
||||
var allText = File.ReadAllText("SaveFile.AEXN");
|
||||
var edts = JsonConvert.DeserializeObject<SaveGraphModel>(allText);
|
||||
var firstEditor = Editors.First();
|
||||
var nodesToAdd = edts.Nodes;
|
||||
foreach (var item in nodesToAdd)
|
||||
{
|
||||
firstEditor.Calculator.Operations.Add(new()
|
||||
{
|
||||
Location = item.Location,
|
||||
NodeId = "H",
|
||||
Title = "HHA"
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
Editors.WhenAdded((editor) =>
|
||||
{
|
||||
if (AutoSelectNewEditor || Editors.Count == 1)
|
||||
{
|
||||
SelectedEditor = editor;
|
||||
}
|
||||
editor.OnOpenInnerCalculator += OnOpenInnerCalculator;
|
||||
})
|
||||
.WhenRemoved((editor) =>
|
||||
{
|
||||
editor.OnOpenInnerCalculator -= OnOpenInnerCalculator;
|
||||
var childEditors = Editors.Where(ed => ed.Parent == editor).ToList();
|
||||
childEditors.ForEach(ed => Editors.Remove(ed));
|
||||
});
|
||||
Editors.Add(new EditorViewModel
|
||||
{
|
||||
Name = $"Editor {Editors.Count + 1}"
|
||||
});
|
||||
}
|
||||
|
||||
private void OnOpenInnerCalculator(EditorViewModel parentEditor, CalculatorViewModel calculator)
|
||||
{
|
||||
var editor = Editors.FirstOrDefault(e => e.Calculator == calculator);
|
||||
if (editor != null)
|
||||
{
|
||||
SelectedEditor = editor;
|
||||
}
|
||||
else
|
||||
{
|
||||
var childEditor = new EditorViewModel
|
||||
{
|
||||
Parent = parentEditor,
|
||||
Calculator = calculator,
|
||||
Name = $"[Inner] Editor {Editors.Count + 1}"
|
||||
};
|
||||
Editors.Add(childEditor);
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand AddEditorCommand { get; }
|
||||
public ICommand CloseEditorCommand { get; }
|
||||
public ICommand RunFlowCommand { get; }
|
||||
public ICommand SaveFileCommand { get; }
|
||||
public ICommand OpenFileCommand { get; }
|
||||
|
||||
private EditorViewModel? _selectedEditor;
|
||||
public EditorViewModel? SelectedEditor
|
||||
{
|
||||
get => _selectedEditor;
|
||||
set => SetProperty(ref _selectedEditor, value);
|
||||
}
|
||||
|
||||
private bool _autoSelectNewEditor = true;
|
||||
public bool AutoSelectNewEditor
|
||||
{
|
||||
get => _autoSelectNewEditor;
|
||||
set => SetProperty(ref _autoSelectNewEditor, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Examples/Nodify.Calculator/AssemblyInfo.cs
Normal file
10
Examples/Nodify.Calculator/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
25
Examples/Nodify.Calculator/AuthOperationViewModel.cs
Normal file
25
Examples/Nodify.Calculator/AuthOperationViewModel.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class AuthOperationViewModel : SystemOperationViewModel
|
||||
{
|
||||
public AuthOperationViewModel()
|
||||
{
|
||||
Title = "Auth";
|
||||
SystemOperationType = SystemOperations.AUTH;
|
||||
}
|
||||
|
||||
private string _baseUrl = string.Empty;
|
||||
public string BaseUrl
|
||||
{
|
||||
get => _baseUrl;
|
||||
set => SetProperty(ref _baseUrl, value);
|
||||
}
|
||||
|
||||
private string _authType = "Bearer Token";
|
||||
public string AuthType
|
||||
{
|
||||
get => _authType;
|
||||
set => SetProperty(ref _authType, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Examples/Nodify.Calculator/BinarySerializationHelper.cs
Normal file
178
Examples/Nodify.Calculator/BinarySerializationHelper.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
public static class BinarySerializationHelper
|
||||
{
|
||||
public static void SerializeObject<T>(T obj, BinaryWriter writer)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
writer.Write(false); // Null flag
|
||||
return;
|
||||
}
|
||||
|
||||
writer.Write(true); // Not null
|
||||
|
||||
Type type = typeof(T);
|
||||
|
||||
// Serialize all non-indexed properties
|
||||
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (property.CanRead && property.GetIndexParameters().Length == 0) // Skip indexers
|
||||
{
|
||||
var value = property.GetValue(obj);
|
||||
SerializeValue(value, writer);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize all public fields
|
||||
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
var value = field.GetValue(obj);
|
||||
SerializeValue(value, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static T DeserializeObject<T>(BinaryReader reader) where T : new()
|
||||
{
|
||||
if (!reader.ReadBoolean()) // Null flag
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
T obj = new T();
|
||||
Type type = typeof(T);
|
||||
|
||||
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (property.CanWrite && property.GetIndexParameters().Length == 0) // Skip indexers
|
||||
{
|
||||
var value = DeserializeValue(property.PropertyType, reader);
|
||||
property.SetValue(obj, value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
var value = DeserializeValue(field.FieldType, reader);
|
||||
field.SetValue(obj, value);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static void SerializeValue(object value, BinaryWriter writer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.Write(false); // Null flag for the value
|
||||
return;
|
||||
}
|
||||
|
||||
writer.Write(true); // Not null!
|
||||
|
||||
Type type = value.GetType();
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
writer.Write((int)value);
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
writer.Write((string)value);
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
writer.Write((bool)value);
|
||||
}
|
||||
else if (type == typeof(double))
|
||||
{
|
||||
writer.Write((double)value);
|
||||
}
|
||||
else if (type == typeof(float))
|
||||
{
|
||||
writer.Write((float)value);
|
||||
}
|
||||
else if (type == typeof(long))
|
||||
{
|
||||
writer.Write((long)value);
|
||||
}
|
||||
else if (type.IsArray)
|
||||
{
|
||||
var array = (Array)value;
|
||||
writer.Write(array.Length); // Write array length
|
||||
foreach (var item in array)
|
||||
{
|
||||
SerializeValue(item, writer);
|
||||
}
|
||||
}
|
||||
else if (typeof(System.Collections.IList).IsAssignableFrom(type))
|
||||
{
|
||||
var list = (System.Collections.IList)value;
|
||||
writer.Write(list.Count); // Write list count
|
||||
foreach (var item in list)
|
||||
{
|
||||
SerializeValue(item, writer); // Serialize each item
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializeObject(value, writer); // Serialize nested objects
|
||||
}
|
||||
}
|
||||
|
||||
private static object DeserializeValue(Type type, BinaryReader reader)
|
||||
{
|
||||
if (!reader.ReadBoolean()) // Null flag
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
return reader.ReadInt32();
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
return reader.ReadString();
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
return reader.ReadBoolean();
|
||||
}
|
||||
else if (type == typeof(double))
|
||||
{
|
||||
return reader.ReadDouble();
|
||||
}
|
||||
else if (type.IsArray)
|
||||
{
|
||||
int length = reader.ReadInt32();
|
||||
var array = Array.CreateInstance(type.GetElementType(), length);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
array.SetValue(DeserializeValue(type.GetElementType(), reader), i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
else if (typeof(System.Collections.IList).IsAssignableFrom(type))
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
var list = (System.Collections.IList)Activator.CreateInstance(type);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
list.Add(DeserializeValue(type.GenericTypeArguments[0], reader));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
else
|
||||
{
|
||||
var method = typeof(BinarySerializationHelper)
|
||||
.GetMethod("DeserializeObject")
|
||||
.MakeGenericMethod(type);
|
||||
|
||||
return method.Invoke(null, new object[] { reader });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class CalculatorInputOperationViewModel : OperationViewModel
|
||||
{
|
||||
public CalculatorInputOperationViewModel()
|
||||
{
|
||||
AddOutputCommand = new RequeryCommand(
|
||||
() => Output.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = $"In {Output.Count}"
|
||||
}),
|
||||
() => Output.Count < 10);
|
||||
|
||||
RemoveOutputCommand = new RequeryCommand(
|
||||
() => Output.RemoveAt(Output.Count - 1),
|
||||
() => Output.Count > 1);
|
||||
|
||||
Output.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = $"In {Output.Count}"
|
||||
});
|
||||
}
|
||||
|
||||
public new NodifyObservableCollection<ConnectorViewModel> Output { get; set; } =
|
||||
new NodifyObservableCollection<ConnectorViewModel>();
|
||||
|
||||
public INodifyCommand AddOutputCommand { get; }
|
||||
public INodifyCommand RemoveOutputCommand { get; }
|
||||
}
|
||||
}
|
||||
51
Examples/Nodify.Calculator/CalculatorOperationViewModel.cs
Normal file
51
Examples/Nodify.Calculator/CalculatorOperationViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class CalculatorOperationViewModel : OperationViewModel
|
||||
{
|
||||
public CalculatorViewModel InnerCalculator { get; } = new CalculatorViewModel();
|
||||
|
||||
private OperationViewModel InnerOutput { get; } = new OperationViewModel
|
||||
{
|
||||
Title = "Output Parameters",
|
||||
Input = { new ConnectorViewModel() },
|
||||
Location = new Point(500, 300),
|
||||
IsReadOnly = true
|
||||
};
|
||||
|
||||
private CalculatorInputOperationViewModel InnerInput { get; } = new CalculatorInputOperationViewModel
|
||||
{
|
||||
Title = "Input Parameters",
|
||||
Location = new Point(300, 300),
|
||||
IsReadOnly = true
|
||||
};
|
||||
|
||||
public CalculatorOperationViewModel()
|
||||
{
|
||||
InnerCalculator.Operations.Add(InnerInput);
|
||||
InnerCalculator.Operations.Add(InnerOutput);
|
||||
|
||||
|
||||
InnerInput.Output.ForEach(x => Input.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = x.Title
|
||||
}));
|
||||
|
||||
InnerInput.Output
|
||||
.WhenAdded(x => Input.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = x.Title
|
||||
}))
|
||||
.WhenRemoved(x => Input.RemoveOne(i => i.Title == x.Title));
|
||||
}
|
||||
|
||||
protected override void OnInputValueChanged()
|
||||
{
|
||||
for (var i = 0; i < Input.Count; i++)
|
||||
{
|
||||
InnerInput.Output[i].Value = Input[i].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
184
Examples/Nodify.Calculator/CalculatorViewModel.cs
Normal file
184
Examples/Nodify.Calculator/CalculatorViewModel.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class CalculatorViewModel : ObservableObject
|
||||
{
|
||||
public CalculatorViewModel()
|
||||
{
|
||||
CreateConnectionCommand = new DelegateCommand<ConnectorViewModel>(
|
||||
_ => CreateConnection(PendingConnection.Source, PendingConnection.Target),
|
||||
_ => CanCreateConnection(PendingConnection.Source, PendingConnection.Target));
|
||||
StartConnectionCommand = new DelegateCommand<ConnectorViewModel>(_ => PendingConnection.IsVisible = true, (c) => c != null && !(c.IsConnected && c.IsInput));
|
||||
DisconnectConnectorCommand = new DelegateCommand<ConnectorViewModel>(DisconnectConnector);
|
||||
DeleteSelectionCommand = new DelegateCommand(DeleteSelection);
|
||||
GroupSelectionCommand = new DelegateCommand(GroupSelectedOperations, () => SelectedOperations.Count > 0);
|
||||
|
||||
Connections.WhenAdded(c =>
|
||||
{
|
||||
c.Input.IsConnected = true;
|
||||
c.Output.IsConnected = true;
|
||||
|
||||
c.Input.Value = c.Output.Value;
|
||||
|
||||
c.Output.ValueObservers.Add(c.Input);
|
||||
})
|
||||
.WhenRemoved(c =>
|
||||
{
|
||||
var ic = Connections.Count(con => con.Input == c.Input || con.Output == c.Input);
|
||||
var oc = Connections.Count(con => con.Input == c.Output || con.Output == c.Output);
|
||||
|
||||
if (ic == 0)
|
||||
{
|
||||
c.Input.IsConnected = false;
|
||||
}
|
||||
|
||||
if (oc == 0)
|
||||
{
|
||||
c.Output.IsConnected = false;
|
||||
}
|
||||
|
||||
c.Output.ValueObservers.Remove(c.Input);
|
||||
});
|
||||
|
||||
Operations.WhenAdded(x =>
|
||||
{
|
||||
x.Input.WhenRemoved(RemoveConnection);
|
||||
x.NodeId = (Operations.Count + 1).ToString();
|
||||
Debug.WriteLine($"Currently adding the node with node id : {x.NodeId} , Title : {x.Title}");
|
||||
if (x is CalculatorInputOperationViewModel ci)
|
||||
{
|
||||
ci.Output.WhenRemoved(RemoveConnection);
|
||||
}
|
||||
|
||||
void RemoveConnection(ConnectorViewModel i)
|
||||
{
|
||||
var c = Connections.Where(con => con.Input == i || con.Output == i).ToArray();
|
||||
c.ForEach(con => Connections.Remove(con));
|
||||
}
|
||||
})
|
||||
.WhenRemoved(x =>
|
||||
{
|
||||
foreach (var input in x.Input)
|
||||
{
|
||||
DisconnectConnector(input);
|
||||
}
|
||||
|
||||
foreach (var item in x.Output)
|
||||
{
|
||||
DisconnectConnector(item);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
OperationsMenu = new OperationsMenuViewModel(this);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<OperationViewModel> _operations = new NodifyObservableCollection<OperationViewModel>();
|
||||
public NodifyObservableCollection<OperationViewModel> Operations
|
||||
{
|
||||
get => _operations;
|
||||
set => SetProperty(ref _operations, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<OperationViewModel> _selectedOperations = new NodifyObservableCollection<OperationViewModel>();
|
||||
public NodifyObservableCollection<OperationViewModel> SelectedOperations
|
||||
{
|
||||
get => _selectedOperations;
|
||||
set => SetProperty(ref _selectedOperations, value);
|
||||
}
|
||||
|
||||
public NodifyObservableCollection<ConnectionViewModel> Connections { get; } = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
public PendingConnectionViewModel PendingConnection { get; set; } = new PendingConnectionViewModel();
|
||||
public OperationsMenuViewModel OperationsMenu { get; set; }
|
||||
|
||||
public INodifyCommand StartConnectionCommand { get; }
|
||||
public INodifyCommand CreateConnectionCommand { get; }
|
||||
public INodifyCommand DisconnectConnectorCommand { get; }
|
||||
public INodifyCommand DeleteSelectionCommand { get; }
|
||||
public INodifyCommand GroupSelectionCommand { get; }
|
||||
|
||||
private void DisconnectConnector(ConnectorViewModel connector)
|
||||
{
|
||||
var connections = Connections.Where(c => c.Input == connector || c.Output == connector).ToList();
|
||||
connections.ForEach(c => Connections.Remove(c));
|
||||
}
|
||||
|
||||
internal bool CanCreateConnection(ConnectorViewModel source, ConnectorViewModel? target)
|
||||
=> target == null || (source != target &&
|
||||
source.Shape == target.Shape &&
|
||||
!source.IsConnected &&
|
||||
!target.IsConnected &&
|
||||
source.IsInput != target.IsInput);
|
||||
|
||||
internal void OpenGetSetVariable(Point TargetLocation, string className)
|
||||
{
|
||||
OperationsMenu.OpenOnlyGetSetVariable(TargetLocation, className);
|
||||
OperationsMenu.Closed += OnOperationsMenuClosed;
|
||||
}
|
||||
|
||||
internal void OpenGetSetForVariable(Point targetLocation, OperationInfoViewModel variableInfo)
|
||||
{
|
||||
OperationsMenu.OpenGetSetForVariable(targetLocation, variableInfo);
|
||||
OperationsMenu.Closed += OnOperationsMenuClosed;
|
||||
}
|
||||
|
||||
internal void CreateConnection(ConnectorViewModel source, ConnectorViewModel? target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
PendingConnection.IsVisible = true;
|
||||
OperationsMenu.OpenAt(PendingConnection.TargetLocation);
|
||||
OperationsMenu.Closed += OnOperationsMenuClosed;
|
||||
return;
|
||||
}
|
||||
|
||||
var input = source.IsInput ? source : target;
|
||||
var output = target.IsInput ? source : target;
|
||||
|
||||
PendingConnection.IsVisible = false;
|
||||
|
||||
DisconnectConnector(input);
|
||||
|
||||
Connections.Add(new ConnectionViewModel
|
||||
{
|
||||
Input = input,
|
||||
Output = output,
|
||||
InputNodeId = source.Operation.NodeId,
|
||||
OutputNodeId = target.Operation.NodeId
|
||||
|
||||
});
|
||||
|
||||
Debug.WriteLine($"Creating a connection between input node id: {source.Operation.NodeId} and :{target.Operation.NodeId}");
|
||||
}
|
||||
|
||||
private void OnOperationsMenuClosed()
|
||||
{
|
||||
PendingConnection.IsVisible = false;
|
||||
OperationsMenu.Closed -= OnOperationsMenuClosed;
|
||||
}
|
||||
|
||||
private void DeleteSelection()
|
||||
{
|
||||
var selected = SelectedOperations.ToList();
|
||||
selected.ForEach(o => Operations.Remove(o));
|
||||
}
|
||||
|
||||
private void GroupSelectedOperations()
|
||||
{
|
||||
var selected = SelectedOperations.ToList();
|
||||
var bounding = selected.GetBoundingBox(50);
|
||||
|
||||
Operations.Add(new OperationGroupViewModel
|
||||
{
|
||||
Title = "Operations",
|
||||
Location = bounding.Location,
|
||||
GroupSize = new Size(bounding.Width, bounding.Height)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
Examples/Nodify.Calculator/ColorToSolidBrushConverter.cs
Normal file
27
Examples/Nodify.Calculator/ColorToSolidBrushConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
internal static class ColorToSolidBrushConverter
|
||||
{
|
||||
public static Brush Convert(Color value)
|
||||
{
|
||||
var brush = new SolidColorBrush(value);
|
||||
return brush;
|
||||
}
|
||||
|
||||
public static Color ConvertBack(Brush value)
|
||||
{
|
||||
if (value is SolidColorBrush brush)
|
||||
return brush.Color;
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Examples/Nodify.Calculator/ConnectionViewModel.cs
Normal file
36
Examples/Nodify.Calculator/ConnectionViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class ConnectionViewModel : ObservableObject
|
||||
{
|
||||
private ConnectorViewModel _input = default!;
|
||||
public ConnectorViewModel Input
|
||||
{
|
||||
get => _input;
|
||||
set => SetProperty(ref _input, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel _output = default!;
|
||||
public ConnectorViewModel Output
|
||||
{
|
||||
get => _output;
|
||||
set => SetProperty(ref _output, value);
|
||||
}
|
||||
|
||||
private string _inputNodeId;
|
||||
|
||||
public string InputNodeId
|
||||
{
|
||||
get { return _inputNodeId; }
|
||||
set { _inputNodeId = value; }
|
||||
}
|
||||
|
||||
private string _outputNodeId;
|
||||
|
||||
public string OutputNodeId
|
||||
{
|
||||
get { return _outputNodeId; }
|
||||
set { _outputNodeId = value; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
100
Examples/Nodify.Calculator/ConnectorViewModel.cs
Normal file
100
Examples/Nodify.Calculator/ConnectorViewModel.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using LiteDB;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public enum ConnectorShape
|
||||
{
|
||||
Circle,
|
||||
Triangle,
|
||||
Square,
|
||||
}
|
||||
|
||||
public class ConnectorViewModel : ObservableObject
|
||||
{
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private double _value;
|
||||
public double Value
|
||||
{
|
||||
get => _value;
|
||||
set => SetProperty(ref _value, value)
|
||||
.Then(() => ValueObservers.ForEach(o => o.Value = value));
|
||||
}
|
||||
|
||||
private ConnectorShape _shape;
|
||||
public ConnectorShape Shape
|
||||
{
|
||||
get => _shape;
|
||||
set => SetProperty(ref _shape, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private bool _isInput;
|
||||
public bool IsInput
|
||||
{
|
||||
get => _isInput;
|
||||
set => SetProperty(ref _isInput, value);
|
||||
}
|
||||
|
||||
private Point _anchor;
|
||||
public Point Anchor
|
||||
{
|
||||
get => _anchor;
|
||||
set => SetProperty(ref _anchor, value);
|
||||
}
|
||||
|
||||
private System.Drawing.Color _color = System.Drawing.Color.DodgerBlue;
|
||||
public System.Drawing.Color ConnectorColor
|
||||
{
|
||||
set
|
||||
{
|
||||
_color = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Brush Color
|
||||
{
|
||||
get
|
||||
{
|
||||
var mediacolor = System.Windows.Media.Color.FromArgb
|
||||
(
|
||||
_color.A,
|
||||
_color.R,
|
||||
_color.G,
|
||||
_color.B
|
||||
);
|
||||
return ColorToSolidBrushConverter.Convert(mediacolor);
|
||||
}
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[BsonIgnore]
|
||||
private OperationViewModel _operation = default!;
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[BsonIgnore]
|
||||
public OperationViewModel Operation
|
||||
{
|
||||
get => _operation;
|
||||
set => SetProperty(ref _operation, value);
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[BsonIgnore]
|
||||
public List<ConnectorViewModel> ValueObservers { get; } = new List<ConnectorViewModel>();
|
||||
}
|
||||
}
|
||||
31
Examples/Nodify.Calculator/Converters/ItemToListConverter.cs
Normal file
31
Examples/Nodify.Calculator/Converters/ItemToListConverter.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class ItemToListConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var argType = value.GetType();
|
||||
var listType = typeof(List<>).MakeGenericType(argType);
|
||||
var list = Activator.CreateInstance(listType) as IList;
|
||||
list?.Add(value);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Examples/Nodify.Calculator/CreateOperationInfoViewModel.cs
Normal file
16
Examples/Nodify.Calculator/CreateOperationInfoViewModel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class CreateOperationInfoViewModel
|
||||
{
|
||||
public CreateOperationInfoViewModel(OperationInfoViewModel info, Point location)
|
||||
{
|
||||
Info = info;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public OperationInfoViewModel Info { get; }
|
||||
public Point Location { get; }
|
||||
}
|
||||
}
|
||||
699
Examples/Nodify.Calculator/EditorView.xaml
Normal file
699
Examples/Nodify.Calculator/EditorView.xaml
Normal file
@@ -0,0 +1,699 @@
|
||||
<UserControl x:Class="Nodify.Calculator.EditorView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
|
||||
d:DataContext="{d:DesignInstance Type=local:EditorViewModel}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<GeometryDrawing x:Key="SmallGridGeometry"
|
||||
Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
|
||||
Brush="{DynamicResource GridLinesBrush}" />
|
||||
|
||||
<GeometryDrawing x:Key="LargeGridGeometry"
|
||||
Geometry="M0,0 L0,1 0.015,1 0.015,0.015 1,0.015 1,0 Z"
|
||||
Brush="{DynamicResource GridLinesBrush}" />
|
||||
|
||||
<DrawingBrush x:Key="SmallGridLinesDrawingBrush"
|
||||
TileMode="Tile"
|
||||
ViewportUnits="Absolute"
|
||||
Viewport="0 0 15 15"
|
||||
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||
Drawing="{StaticResource SmallGridGeometry}" />
|
||||
|
||||
<DrawingBrush x:Key="LargeGridLinesDrawingBrush"
|
||||
TileMode="Tile"
|
||||
ViewportUnits="Absolute"
|
||||
Opacity="0.5"
|
||||
Viewport="0 0 150 150"
|
||||
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||
Drawing="{StaticResource LargeGridGeometry}" />
|
||||
|
||||
<LinearGradientBrush x:Key="AnimatedBrush"
|
||||
StartPoint="0 0"
|
||||
EndPoint="1 0">
|
||||
<GradientStop Color="#6366f1"
|
||||
Offset="0" />
|
||||
<GradientStop Color="#a855f7"
|
||||
Offset="0.5" />
|
||||
<GradientStop Color="#ec4899"
|
||||
Offset="1" />
|
||||
</LinearGradientBrush>
|
||||
<Border x:Key="AnimatedBorderPlaceholder"
|
||||
BorderBrush="{StaticResource AnimatedBrush}" />
|
||||
|
||||
<Storyboard x:Key="AnimateBorder"
|
||||
RepeatBehavior="Forever">
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="1 0" />
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="1 1"
|
||||
BeginTime="0:0:2" />
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="0 1"
|
||||
BeginTime="0:0:4" />
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="0 0"
|
||||
BeginTime="0:0:6" />
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="1 1" />
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="0 1"
|
||||
BeginTime="0:0:2" />
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="0 0"
|
||||
BeginTime="0:0:4" />
|
||||
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||
Duration="0:0:2"
|
||||
To="1 0"
|
||||
BeginTime="0:0:6" />
|
||||
</Storyboard>
|
||||
|
||||
<local:ItemToListConverter x:Key="ItemToListConverter" />
|
||||
|
||||
<DataTemplate x:Key="ConnectionTemplate"
|
||||
DataType="{x:Type local:ConnectionViewModel}">
|
||||
<nodify:CircuitConnection Source="{Binding Output.Anchor}"
|
||||
Target="{Binding Input.Anchor}"
|
||||
Foreground="{Binding Input.Color}"
|
||||
Stroke="{Binding Input.Color}"
|
||||
StrokeThickness="2"
|
||||
/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="PendingConnectionTemplate"
|
||||
DataType="{x:Type local:PendingConnectionViewModel}">
|
||||
<nodify:PendingConnection IsVisible="{Binding IsVisible}"
|
||||
Source="{Binding Source, Mode=OneWayToSource}"
|
||||
Target="{Binding Target, Mode=OneWayToSource}"
|
||||
TargetAnchor="{Binding TargetLocation, Mode=OneWayToSource}"
|
||||
StartedCommand="{Binding DataContext.StartConnectionCommand, RelativeSource={RelativeSource AncestorType={x:Type nodify:NodifyEditor}}}"
|
||||
CompletedCommand="{Binding DataContext.CreateConnectionCommand, RelativeSource={RelativeSource AncestorType={x:Type nodify:NodifyEditor}}}" />
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="ItemContainerStyle"
|
||||
TargetType="{x:Type nodify:ItemContainer}"
|
||||
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Setter Property="IsSelected"
|
||||
Value="{Binding IsSelected}" />
|
||||
<Setter Property="ActualSize"
|
||||
Value="{Binding Size, Mode=OneWayToSource}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{Binding BorderBrush, Source={StaticResource AnimatedBorderPlaceholder}}" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="2" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="SquareConnectorColor" Color="MediumSlateBlue"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="TriangleConnectorColor" Color="White"></SolidColorBrush>
|
||||
<Style x:Key="ConnectionStyle" TargetType="{x:Type nodify:BaseConnection}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Input.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="Stroke" Value="{StaticResource SquareConnectorColor}"></Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Input.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="Stroke" Value="{StaticResource TriangleConnectorColor}"></Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Stroke" Value="{DynamicResource Connection.StrokeBrush}"></Setter>
|
||||
<Setter Property="Cursor" Value="Hand"></Setter>
|
||||
<Setter Property="ToolTip" Value="Double click to split"></Setter>
|
||||
</Style>
|
||||
<ControlTemplate x:Key="SquareConnector" TargetType="Control">
|
||||
<Rectangle Width="14"
|
||||
Height="14"
|
||||
StrokeDashCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
Fill="{TemplateBinding Background}"
|
||||
StrokeThickness="2" />
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TriangleConnector" TargetType="Control">
|
||||
<Polygon Width="14"
|
||||
Height="14"
|
||||
Points="2,2 4,2 12,7 4,12 2,12"
|
||||
StrokeDashCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
Fill="{TemplateBinding Background}"
|
||||
|
||||
/>
|
||||
</ControlTemplate>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
|
||||
<nodify:NodifyEditor DataContext="{Binding Calculator}"
|
||||
ItemsSource="{Binding Operations}"
|
||||
Connections="{Binding Connections}"
|
||||
SelectedItems="{Binding SelectedOperations}"
|
||||
DisconnectConnectorCommand="{Binding DisconnectConnectorCommand}"
|
||||
PendingConnection="{Binding PendingConnection}"
|
||||
PendingConnectionTemplate="{StaticResource PendingConnectionTemplate}"
|
||||
ConnectionTemplate="{StaticResource ConnectionTemplate}"
|
||||
Background="{StaticResource SmallGridLinesDrawingBrush}"
|
||||
ItemContainerStyle="{StaticResource ItemContainerStyle}"
|
||||
HasCustomContextMenu="True"
|
||||
GridCellSize="15"
|
||||
AllowDrop="True"
|
||||
Drop="OnDropNode"
|
||||
x:Name="Editor">
|
||||
<nodify:NodifyEditor.Resources>
|
||||
<Style TargetType="{x:Type nodify:NodeInput}"
|
||||
BasedOn="{StaticResource OriginalNodeInputStyle}">
|
||||
<Setter Property="Header"
|
||||
Value="{Binding}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="ToolTip"
|
||||
Value="{Binding Value}" />
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Title}"
|
||||
Margin="0 0 5 0" />
|
||||
<TextBox Text="{Binding Value}"
|
||||
Visibility="{Binding IsConnected, Converter={shared:BooleanToVisibilityConverter Negate=True}}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Title}" Value="">
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Title}" Value="{x:Null}">
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="ConnectorTemplate" Value="{StaticResource SquareConnector}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SquareConnectorColor}"></Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="ConnectorTemplate" Value="{StaticResource TriangleConnector}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TriangleConnectorColor}"></Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Circle}">
|
||||
<Setter Property="BorderBrush" Value="{Binding Color}"></Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type nodify:NodeOutput}"
|
||||
BasedOn="{StaticResource OriginalNodeOutputStyle}">
|
||||
<Setter Property="Header"
|
||||
Value="{Binding}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<TextBox Text="{Binding Title}"
|
||||
IsEnabled="False" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Title}" Value="">
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Title}" Value="{x:Null}">
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="ConnectorTemplate" Value="{StaticResource SquareConnector}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SquareConnectorColor}"></Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="ConnectorTemplate" Value="{StaticResource TriangleConnector}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TriangleConnectorColor}"></Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Circle}">
|
||||
<Setter Property="BorderBrush" Value="{Binding Color}"></Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:OperationGraphViewModel}">
|
||||
<nodify:GroupingNode Header="{Binding}"
|
||||
CanResize="{Binding IsExpanded}"
|
||||
ActualSize="{Binding DesiredSize, Mode=TwoWay}"
|
||||
MovementMode="Self">
|
||||
<nodify:GroupingNode.HeaderTemplate>
|
||||
<DataTemplate DataType="{x:Type local:OperationGraphViewModel}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="5 0 0 0"
|
||||
Grid.Column="1">
|
||||
<TextBlock Text="Expand?"
|
||||
Visibility="{Binding IsExpanded, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Margin="0 0 5 0" />
|
||||
<CheckBox IsChecked="{Binding IsExpanded}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</nodify:GroupingNode.HeaderTemplate>
|
||||
<Grid>
|
||||
<ScrollViewer CanContentScroll="True"
|
||||
Visibility="{Binding IsExpanded, Converter={shared:BooleanToVisibilityConverter}}">
|
||||
<nodify:NodifyEditor Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}"
|
||||
DataContext="{Binding InnerCalculator}"
|
||||
ItemsSource="{Binding Operations}"
|
||||
Connections="{Binding Connections}"
|
||||
SelectedItems="{Binding SelectedOperations}"
|
||||
DisconnectConnectorCommand="{Binding DisconnectConnectorCommand}"
|
||||
PendingConnection="{Binding PendingConnection}"
|
||||
PendingConnectionTemplate="{StaticResource PendingConnectionTemplate}"
|
||||
ConnectionTemplate="{StaticResource ConnectionTemplate}"
|
||||
ItemContainerStyle="{StaticResource ItemContainerStyle}"
|
||||
HasCustomContextMenu="True"
|
||||
Background="Transparent"
|
||||
GridCellSize="15"
|
||||
AllowDrop="True"
|
||||
Drop="OnDropNode"
|
||||
Visibility="{Binding DataContext.IsExpanded, RelativeSource={RelativeSource AncestorType=nodify:GroupingNode}, Converter={shared:BooleanToVisibilityConverter}}">
|
||||
|
||||
<nodify:NodifyEditor.InputBindings>
|
||||
<KeyBinding Key="Delete"
|
||||
Command="{Binding DeleteSelectionCommand}" />
|
||||
<KeyBinding Key="G"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding GroupSelectionCommand}" />
|
||||
</nodify:NodifyEditor.InputBindings>
|
||||
|
||||
<nodify:NodifyEditor.CommandBindings>
|
||||
<CommandBinding Command="{x:Static ApplicationCommands.ContextMenu}"
|
||||
Executed="OpenContextMenu_Executed" />
|
||||
</nodify:NodifyEditor.CommandBindings>
|
||||
|
||||
<CompositeCollection>
|
||||
<nodify:DecoratorContainer DataContext="{Binding OperationsMenu}"
|
||||
Location="{Binding Location}">
|
||||
<local:OperationsMenuView />
|
||||
</nodify:DecoratorContainer>
|
||||
</CompositeCollection>
|
||||
</nodify:NodifyEditor>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Input}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<nodify:NodeInput />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<nodify:NodeOutput DataContext="{Binding Output}"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</nodify:GroupingNode>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:ExpandoOperationViewModel}">
|
||||
<nodify:Node Header="{Binding Title}"
|
||||
Content="{Binding}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}">
|
||||
<nodify:Node.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ExpandoOperationViewModel}">
|
||||
<StackPanel>
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Content="{StaticResource PlusIcon}"
|
||||
FocusVisualStyle="{StaticResource {x:Static SystemParameters.FocusVisualStyleKey}}"
|
||||
Command="{Binding AddInputCommand}" />
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Content="{StaticResource RemoveKeyIcon}"
|
||||
FocusVisualStyle="{StaticResource {x:Static SystemParameters.FocusVisualStyleKey}}"
|
||||
Command="{Binding RemoveInputCommand}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</nodify:Node.ContentTemplate>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:APIOperationViewModel}">
|
||||
<nodify:Node Header="{Binding Title}"
|
||||
Content="{Binding}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}">
|
||||
<nodify:Node.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type local:APIOperationViewModel}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding OperationType}"></TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</nodify:Node.ContentTemplate>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:ExpressionOperationViewModel}">
|
||||
<nodify:Node Content="{Binding}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}">
|
||||
<nodify:Node.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ExpressionOperationViewModel}">
|
||||
<TextBox Text="{Binding Expression}"
|
||||
MinWidth="100"
|
||||
Margin="5 0 0 0" />
|
||||
</DataTemplate>
|
||||
</nodify:Node.ContentTemplate>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:CalculatorOperationViewModel}">
|
||||
<nodify:Node Header="{Binding Title}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}"
|
||||
ToolTip="Double click to expand">
|
||||
<nodify:Node.InputBindings>
|
||||
<MouseBinding Gesture="LeftDoubleClick"
|
||||
Command="{Binding DataContext.OpenCalculatorCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding InnerCalculator}" />
|
||||
</nodify:Node.InputBindings>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:CalculatorInputOperationViewModel}">
|
||||
<DataTemplate.Resources>
|
||||
<Style TargetType="{x:Type nodify:NodeOutput}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}">
|
||||
<Setter Property="Header"
|
||||
Value="{Binding}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding Value}"
|
||||
IsEnabled="False" />
|
||||
<TextBlock Text="{Binding Title}"
|
||||
Margin="5 0 0 0" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataTemplate.Resources>
|
||||
<nodify:Node Header="{Binding Title}"
|
||||
Output="{Binding Output}">
|
||||
<StackPanel>
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Content="{StaticResource PlusIcon}"
|
||||
Command="{Binding AddOutputCommand}" />
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Content="{StaticResource RemoveKeyIcon}"
|
||||
Command="{Binding RemoveOutputCommand}" />
|
||||
</StackPanel>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:OperationGroupViewModel}">
|
||||
<nodify:GroupingNode ActualSize="{Binding GroupSize, Mode=TwoWay}">
|
||||
<nodify:GroupingNode.Header>
|
||||
<shared:EditableTextBlock Text="{Binding Title}"
|
||||
FontWeight="SemiBold"
|
||||
IsEditing="True" />
|
||||
</nodify:GroupingNode.Header>
|
||||
</nodify:GroupingNode>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:AuthOperationViewModel}">
|
||||
<nodify:Node Header="🔐 Auth Configuration"
|
||||
Content="{Binding}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}">
|
||||
<nodify:Node.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type local:AuthOperationViewModel}">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="Base URL" FontWeight="SemiBold" Margin="0 0 0 2" />
|
||||
<TextBox Text="{Binding BaseUrl, UpdateSourceTrigger=PropertyChanged}"
|
||||
MinWidth="180" Margin="0 0 0 6" />
|
||||
<TextBlock Text="Auth Type" FontWeight="SemiBold" Margin="0 0 0 2" />
|
||||
<ComboBox SelectedItem="{Binding AuthType}"
|
||||
MinWidth="180" Margin="0 0 0 6">
|
||||
<sys:String>Bearer Token</sys:String>
|
||||
<sys:String>Basic Auth</sys:String>
|
||||
<sys:String>API Key</sys:String>
|
||||
<sys:String>None</sys:String>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</nodify:Node.ContentTemplate>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||
<nodify:Node Content="{Binding Title}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}" />
|
||||
</DataTemplate>
|
||||
</nodify:NodifyEditor.Resources>
|
||||
|
||||
<nodify:NodifyEditor.InputBindings>
|
||||
<KeyBinding Key="Delete"
|
||||
Command="{Binding DeleteSelectionCommand}" />
|
||||
<KeyBinding Key="G"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding GroupSelectionCommand}" />
|
||||
</nodify:NodifyEditor.InputBindings>
|
||||
|
||||
<nodify:NodifyEditor.CommandBindings>
|
||||
<CommandBinding Command="{x:Static ApplicationCommands.ContextMenu}"
|
||||
Executed="OpenContextMenu_Executed" />
|
||||
</nodify:NodifyEditor.CommandBindings>
|
||||
|
||||
<nodify:NodifyEditor.Triggers>
|
||||
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
|
||||
<BeginStoryboard Name="AnimateBorder"
|
||||
Storyboard="{StaticResource AnimateBorder}" />
|
||||
</EventTrigger>
|
||||
</nodify:NodifyEditor.Triggers>
|
||||
|
||||
<CompositeCollection>
|
||||
<nodify:DecoratorContainer DataContext="{Binding OperationsMenu}"
|
||||
Location="{Binding Location}">
|
||||
<local:OperationsMenuView />
|
||||
</nodify:DecoratorContainer>
|
||||
</CompositeCollection>
|
||||
</nodify:NodifyEditor>
|
||||
|
||||
<Grid Background="{StaticResource LargeGridLinesDrawingBrush}"
|
||||
Panel.ZIndex="-2" />
|
||||
|
||||
<Border HorizontalAlignment="Right"
|
||||
MinWidth="200"
|
||||
MaxWidth="300"
|
||||
MaxHeight="500"
|
||||
Padding="7"
|
||||
Margin="10"
|
||||
CornerRadius="3"
|
||||
BorderThickness="2">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{DynamicResource BackgroundColor}"
|
||||
Opacity="0.7" />
|
||||
</Border.Background>
|
||||
<DockPanel>
|
||||
<Button Content="📥 Import Swagger"
|
||||
Command="{Binding Calculator.OperationsMenu.ImportSwaggerCommand}"
|
||||
Margin="5"
|
||||
Padding="8 4"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource NodeInput.BorderBrush}"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
FontWeight="Bold"
|
||||
DockPanel.Dock="Top" />
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.AvailableOperations}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style>
|
||||
<Setter Property="FrameworkElement.Margin"
|
||||
Value="5" />
|
||||
<Setter Property="FrameworkElement.HorizontalAlignment"
|
||||
Value="Left" />
|
||||
<Setter Property="FrameworkElement.Cursor"
|
||||
Value="Hand" />
|
||||
<Setter Property="FrameworkElement.ToolTip"
|
||||
Value="Drag and drop into the editor" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||
<nodify:Node Content="{Binding Title}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}"
|
||||
BorderBrush="{StaticResource AnimatedBrush}"
|
||||
BorderThickness="2"
|
||||
MouseMove="OnNodeDrag"
|
||||
Focusable="True"
|
||||
KeyboardNavigation.TabNavigation="None">
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<Border HorizontalAlignment="Left"
|
||||
MinWidth="200"
|
||||
MaxWidth="250"
|
||||
MaxHeight="500"
|
||||
MinHeight="200"
|
||||
Padding="7"
|
||||
Margin="10"
|
||||
CornerRadius="3"
|
||||
BorderThickness="2">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{DynamicResource BackgroundColor}"
|
||||
Opacity="0.7" />
|
||||
</Border.Background>
|
||||
<DockPanel>
|
||||
<TextBlock Text="Variables" FontWeight="Bold" Margin="0 0 0 4" DockPanel.Dock="Top" />
|
||||
<Button Content="➕ Add New Variable"
|
||||
Command="{Binding Calculator.OperationsMenu.AddVariableCommand}"
|
||||
Margin="0 0 0 6"
|
||||
Padding="6 3"
|
||||
Cursor="Hand"
|
||||
DockPanel.Dock="Top"
|
||||
Background="{DynamicResource NodeInput.BorderBrush}"
|
||||
Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<!-- Simple Variables -->
|
||||
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.AvailableVariables}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style>
|
||||
<Setter Property="FrameworkElement.Margin" Value="3" />
|
||||
<Setter Property="FrameworkElement.HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="FrameworkElement.Cursor" Value="Hand" />
|
||||
<Setter Property="FrameworkElement.ToolTip" Value="Drag and drop to GET or SET this variable" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:OperationInfoViewModel}">
|
||||
<Border Background="#3E3E42" CornerRadius="3" Padding="6 4"
|
||||
MouseMove="OnNodeDrag" Cursor="Hand">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Title}" Foreground="LightGreen" FontWeight="SemiBold" />
|
||||
<TextBlock Text=" : " Foreground="Gray" />
|
||||
<TextBlock Text="{Binding VariableType}" Foreground="CornflowerBlue" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Separator -->
|
||||
<TextBlock Text="Models" FontWeight="Bold" Margin="0 8 0 4"
|
||||
Visibility="{Binding Calculator.OperationsMenu.AvailableModels.Count, Converter={shared:BooleanToVisibilityConverter}}" />
|
||||
|
||||
<!-- Class Model Variables -->
|
||||
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.AvailableModels}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style>
|
||||
<Setter Property="FrameworkElement.Margin" Value="5" />
|
||||
<Setter Property="FrameworkElement.HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="FrameworkElement.Cursor" Value="Hand" />
|
||||
<Setter Property="FrameworkElement.ToolTip" Value="Drag and drop into the editor" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||
<nodify:Node Content="{Binding Title}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}"
|
||||
BorderBrush="{StaticResource AnimatedBrush}"
|
||||
BorderThickness="2"
|
||||
MouseMove="OnNodeDrag"
|
||||
Focusable="True"
|
||||
KeyboardNavigation.TabNavigation="None">
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
101
Examples/Nodify.Calculator/EditorView.xaml.cs
Normal file
101
Examples/Nodify.Calculator/EditorView.xaml.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class OperationsMenuHandler : InputElementState<NodifyEditor>
|
||||
{
|
||||
private static InputGesture OpenGesture { get; } = new Interactivity.MouseGesture(MouseAction.RightClick);
|
||||
private static InputGesture CloseGesture { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick);
|
||||
|
||||
private OperationsMenuViewModel ViewModel => ((CalculatorViewModel)Element.DataContext).OperationsMenu;
|
||||
|
||||
public OperationsMenuHandler(NodifyEditor element) : base(element)
|
||||
{
|
||||
ProcessHandledEvents = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseButtonEventArgs e)
|
||||
{
|
||||
if (!e.Handled && OpenGesture.Matches(e.Source, e))
|
||||
{
|
||||
ViewModel.OpenAt(Element.MouseLocation);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
if (CloseGesture.Matches(e.Source, e))
|
||||
{
|
||||
ViewModel.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EditorView : UserControl
|
||||
{
|
||||
public EditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
static EditorView()
|
||||
{
|
||||
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(editor => new OperationsMenuHandler(editor));
|
||||
}
|
||||
|
||||
private void OnDropNode(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Source is NodifyEditor editor && editor.DataContext is CalculatorViewModel calculator
|
||||
&& e.Data.GetData(typeof(OperationInfoViewModel)) is OperationInfoViewModel operation)
|
||||
{
|
||||
if (operation.IsModelNode)
|
||||
{
|
||||
var dc = editor.DataContext as CalculatorViewModel;
|
||||
var lc = editor.GetLocationInsideEditor(e);
|
||||
var orTitle = operation.Title;
|
||||
dc.OpenGetSetVariable(lc, orTitle);
|
||||
}
|
||||
else if (operation.IsSimpleVariable)
|
||||
{
|
||||
var lc = editor.GetLocationInsideEditor(e);
|
||||
calculator.OpenGetSetForVariable(lc, operation);
|
||||
}
|
||||
else
|
||||
{
|
||||
OperationViewModel op = OperationFactory.GetOperation(operation);
|
||||
op.Location = editor.GetLocationInsideEditor(e);
|
||||
calculator.Operations.Add(op);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodeDrag(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed && ((FrameworkElement)sender).DataContext is OperationInfoViewModel operation)
|
||||
{
|
||||
var data = new DataObject(typeof(OperationInfoViewModel), operation);
|
||||
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenContextMenu_Executed(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (e.Source is NodifyEditor editor && editor.DataContext is CalculatorViewModel calculator)
|
||||
{
|
||||
if (calculator.OperationsMenu.IsVisible)
|
||||
{
|
||||
calculator.OperationsMenu.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
calculator.OperationsMenu.OpenAt(editor.ViewportLocation + new Vector(editor.ViewportSize.Width / 3, editor.ViewportSize.Height / 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Examples/Nodify.Calculator/EditorViewModel.cs
Normal file
40
Examples/Nodify.Calculator/EditorViewModel.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class EditorViewModel : ObservableObject
|
||||
{
|
||||
public event Action<EditorViewModel, CalculatorViewModel>? OnOpenInnerCalculator;
|
||||
|
||||
public EditorViewModel? Parent { get; set; }
|
||||
|
||||
public EditorViewModel()
|
||||
{
|
||||
Calculator = new CalculatorViewModel();
|
||||
OpenCalculatorCommand = new DelegateCommand<CalculatorViewModel>(calculator =>
|
||||
{
|
||||
OnOpenInnerCalculator?.Invoke(this, calculator);
|
||||
});
|
||||
}
|
||||
|
||||
public INodifyCommand OpenCalculatorCommand { get; }
|
||||
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
|
||||
private CalculatorViewModel _calculator = default!;
|
||||
public CalculatorViewModel Calculator
|
||||
{
|
||||
get => _calculator;
|
||||
set => SetProperty(ref _calculator, value);
|
||||
}
|
||||
|
||||
private string? _name;
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
677
Examples/Nodify.Calculator/Executor.cs
Normal file
677
Examples/Nodify.Calculator/Executor.cs
Normal file
@@ -0,0 +1,677 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.Net.Http;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
#endif
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public enum logType
|
||||
{
|
||||
Information,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
public delegate void LogMe(string message, logType logtype = logType.Information);
|
||||
|
||||
public class Executor
|
||||
{
|
||||
private readonly EditorViewModel editorViewModel;
|
||||
public event LogMe OnLogMe;
|
||||
|
||||
static bool isAlreadyLogged = false;
|
||||
static Dictionary<string, string> outputs = new Dictionary<string, string>();
|
||||
static Dictionary<string, dynamic> variables = new Dictionary<string, dynamic>();
|
||||
|
||||
// Auth configuration populated from the Auth node
|
||||
private string _authBaseUrl = string.Empty;
|
||||
private string _authType = string.Empty;
|
||||
private string _authToken = string.Empty;
|
||||
private string _authApiKey = string.Empty;
|
||||
private string _authUsername = string.Empty;
|
||||
private string _authPassword = string.Empty;
|
||||
|
||||
public Executor(EditorViewModel editorViewModel)
|
||||
{
|
||||
this.editorViewModel = editorViewModel;
|
||||
}
|
||||
|
||||
public string PerformPreCheck()
|
||||
{
|
||||
string errorString = string.Empty;
|
||||
var calcModel = editorViewModel.Calculator;
|
||||
var allnodes = calcModel.Operations;
|
||||
errorString = ValidateRequiredNodes("end", allnodes);
|
||||
if (!string.IsNullOrEmpty(errorString))
|
||||
{
|
||||
OnLogMe?.Invoke(errorString, logType.Error);
|
||||
return errorString;
|
||||
}
|
||||
errorString = ValidateRequiredNodes("begin", allnodes);
|
||||
OnLogMe?.Invoke(errorString, logType.Error);
|
||||
return errorString;
|
||||
}
|
||||
|
||||
public string Execute()
|
||||
{
|
||||
string errorString = string.Empty;
|
||||
try
|
||||
{
|
||||
OnLogMe?.Invoke("Starting executing the flow.");
|
||||
OnLogMe?.Invoke("Wish all the best to us :)");
|
||||
OnLogMe?.Invoke("Perorming chain check, it may take some time depending upon the number of nodes.");
|
||||
outputs.Clear();
|
||||
var calcModel = editorViewModel.Calculator;
|
||||
var allnodes = calcModel.Operations;
|
||||
var allConnections = calcModel.Connections;
|
||||
|
||||
// Resolve Auth node configuration before execution
|
||||
ResolveAuthNode(allnodes);
|
||||
|
||||
// Resolve GET variable nodes (they are not in the flow chain)
|
||||
ResolveGetVariableNodes(allnodes, allConnections);
|
||||
|
||||
PerformChainCheck(allnodes, allConnections, false);
|
||||
PerformChainCheck(allnodes, allConnections, true); //Execute operations
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnLogMe?.Invoke("either your or our bad luck :(", logType.Error);
|
||||
OnLogMe?.Invoke("Error Details : ", logType.Error);
|
||||
OnLogMe?.Invoke(e.Message, logType.Error);
|
||||
OnLogMe?.Invoke(e.StackTrace, logType.Error);
|
||||
errorString = "Error occured dueing execution of flow";
|
||||
}
|
||||
|
||||
return errorString;
|
||||
}
|
||||
|
||||
public static string GenerateClassFromJson(string json, string className)
|
||||
{
|
||||
// Parse the JSON to understand its structure
|
||||
JToken token = JToken.Parse(json);
|
||||
|
||||
if (token.Type == JTokenType.Array)
|
||||
{
|
||||
// If the JSON is an array, get the first object in the array for structure
|
||||
JArray array = (JArray)token;
|
||||
if (array.Count > 0 && array[0].Type == JTokenType.Object)
|
||||
{
|
||||
JObject firstObject = (JObject)array[0];
|
||||
return $"{GenerateClassFromJObject(firstObject, className)}";
|
||||
}
|
||||
|
||||
return $"The JSON array does not contain valid objects.";
|
||||
}
|
||||
else if (token.Type == JTokenType.Object)
|
||||
{
|
||||
// If the JSON is an object
|
||||
JObject obj = (JObject)token;
|
||||
return GenerateClassFromJObject(obj, className);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Unsupported JSON structure.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a class definition string from a JObject.
|
||||
/// </summary>
|
||||
/// <param name="jObject">The JObject representing the JSON object.</param>
|
||||
/// <param name="className">The name of the class to generate.</param>
|
||||
/// <returns>The class definition as a string.</returns>
|
||||
public static string GenerateClassFromJObject(JObject jObject, string className)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Start class definition
|
||||
sb.AppendLine($"public class {className}");
|
||||
sb.AppendLine("{");
|
||||
|
||||
// Loop through properties in the JObject and generate class fields
|
||||
foreach (var property in jObject.Properties())
|
||||
{
|
||||
string propName = property.Name;
|
||||
string propType = GetCSharpType(property.Value.Type);
|
||||
|
||||
// Generate the property definition
|
||||
sb.AppendLine($"\tpublic {propType} {ToPascalCase(propName)} {{ get; set; }}");
|
||||
}
|
||||
|
||||
// End class definition
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a JTokenType to a C# type.
|
||||
/// </summary>
|
||||
/// <param name="jsonType">The JTokenType.</param>
|
||||
/// <returns>The corresponding C# type as a string.</returns>
|
||||
public static string GetCSharpType(JTokenType jsonType)
|
||||
{
|
||||
return jsonType switch
|
||||
{
|
||||
JTokenType.Integer => "int",
|
||||
JTokenType.Float => "double",
|
||||
JTokenType.String => "string",
|
||||
JTokenType.Boolean => "bool",
|
||||
JTokenType.Object => "object",
|
||||
JTokenType.Array => "List<object>",
|
||||
_ => "string", // Default to string for other types
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string to PascalCase for naming conventions.
|
||||
/// </summary>
|
||||
/// <param name="input">The string to convert.</param>
|
||||
/// <returns>A PascalCase version of the input string.</returns>
|
||||
public static string ToPascalCase(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
return char.ToUpper(input[0]) + input.Substring(1);
|
||||
}
|
||||
|
||||
static Type CreateTypeFromJson(JsonElement root, string typeName)
|
||||
{
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
var assemblyName = new AssemblyName("DynamicAssembly");
|
||||
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
|
||||
|
||||
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public);
|
||||
|
||||
foreach (var prop in root.EnumerateObject())
|
||||
{
|
||||
Type propType = prop.Value.ValueKind switch
|
||||
{
|
||||
JsonValueKind.String => typeof(string),
|
||||
JsonValueKind.Number => typeof(int),
|
||||
JsonValueKind.True or JsonValueKind.False => typeof(bool),
|
||||
_ => typeof(object)
|
||||
};
|
||||
|
||||
var fieldBuilder = typeBuilder.DefineField("_" + prop.Name, propType, FieldAttributes.Private);
|
||||
var propertyBuilder = typeBuilder.DefineProperty(prop.Name, PropertyAttributes.HasDefault, propType, null);
|
||||
|
||||
// Getter
|
||||
var getter = typeBuilder.DefineMethod(
|
||||
"get_" + prop.Name,
|
||||
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
|
||||
propType,
|
||||
Type.EmptyTypes
|
||||
);
|
||||
var getterIL = getter.GetILGenerator();
|
||||
getterIL.Emit(OpCodes.Ldarg_0);
|
||||
getterIL.Emit(OpCodes.Ldfld, fieldBuilder);
|
||||
getterIL.Emit(OpCodes.Ret);
|
||||
propertyBuilder.SetGetMethod(getter);
|
||||
|
||||
// Setter
|
||||
var setter = typeBuilder.DefineMethod(
|
||||
"set_" + prop.Name,
|
||||
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
|
||||
null,
|
||||
new Type[] { propType }
|
||||
);
|
||||
var setterIL = setter.GetILGenerator();
|
||||
setterIL.Emit(OpCodes.Ldarg_0);
|
||||
setterIL.Emit(OpCodes.Ldarg_1);
|
||||
setterIL.Emit(OpCodes.Stfld, fieldBuilder);
|
||||
setterIL.Emit(OpCodes.Ret);
|
||||
propertyBuilder.SetSetMethod(setter);
|
||||
}
|
||||
return typeBuilder.CreateType()!;
|
||||
#endif
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private void CreateModelsFromString(string jsonString)
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
|
||||
var jsonDoc = JsonDocument.Parse(jsonString);
|
||||
var root = jsonDoc.RootElement;
|
||||
|
||||
if (root.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
ParseFromJsonelement(root);
|
||||
}
|
||||
else if(root.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in root.EnumerateArray())
|
||||
{
|
||||
if (item.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
ParseFromJsonelement(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
private void ParseFromJsonelement(JsonElement jsonElem)
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
Type dynamicType = CreateTypeFromJson(jsonElem, "DynamicPerson");
|
||||
|
||||
object instance = Activator.CreateInstance(dynamicType);
|
||||
|
||||
foreach (var prop in jsonElem.EnumerateObject())
|
||||
{
|
||||
PropertyInfo pi = dynamicType.GetProperty(prop.Name);
|
||||
object value = prop.Value.ValueKind switch
|
||||
{
|
||||
JsonValueKind.String => prop.Value.GetString(),
|
||||
JsonValueKind.Number => prop.Value.GetDouble(),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
_ => null
|
||||
};
|
||||
pi.SetValue(instance, value);
|
||||
}
|
||||
Console.WriteLine("Properties of runtime class:");
|
||||
foreach (var prop in dynamicType.GetProperties())
|
||||
{
|
||||
Console.WriteLine($"{prop.Name} = {prop.GetValue(instance)}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void StartExecution(OperationViewModel op, ICollection<ConnectionViewModel> connections)
|
||||
{
|
||||
var url = op.Title ?? "";
|
||||
if (url.ToLower().Contains("create model"))
|
||||
{
|
||||
var conByTitle = connections.Where(c => c.Input.Operation.Title.ToLower().Contains("create model")).ToList();
|
||||
if (conByTitle.Count <= 0)
|
||||
{
|
||||
OnLogMe?.Invoke("No input found", logType.Error);
|
||||
throw new Exception("Input connection missig for : " + url);
|
||||
}
|
||||
var flowConnection = conByTitle.Where(c=>c.Input.Shape != ConnectorShape.Triangle).FirstOrDefault();
|
||||
if (flowConnection == null)
|
||||
{
|
||||
OnLogMe?.Invoke("No input found", logType.Error);
|
||||
throw new Exception("Input connection missig for : " + url);
|
||||
}
|
||||
|
||||
var outputNodeId = flowConnection.InputNodeId;
|
||||
|
||||
string outputValue = string.Empty;
|
||||
if (outputs.TryGetValue(outputNodeId, out outputValue))
|
||||
{
|
||||
//Convert model here
|
||||
//CreateModelsFromString(outputValue);
|
||||
var customModelDir = "CustomModels";
|
||||
Directory.CreateDirectory(customModelDir);
|
||||
string className = "Class 1";
|
||||
className = className.Replace(" ", "");
|
||||
var classStruct = GenerateClassFromJson(outputValue, className);
|
||||
string flPath = Path.Join(customModelDir, $"{className}.cs");
|
||||
File.WriteAllText(flPath, classStruct);
|
||||
OperationInfoViewModel opv = new OperationInfoViewModel()
|
||||
{
|
||||
Title = className,
|
||||
IsModelNode = true,
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
ClassName = className
|
||||
};
|
||||
this.editorViewModel.Calculator.OperationsMenu.AddNewModel(opv);
|
||||
}
|
||||
}
|
||||
|
||||
if (url.ToLower().Contains("set"))
|
||||
{
|
||||
// Handle simple variable SET (e.g., "SET myVar (string)")
|
||||
if (op is SystemOperationViewModel sysVarOp && url.Contains("(") && url.Contains(")"))
|
||||
{
|
||||
var parts = url.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
var varName = parts[1];
|
||||
// Find the data input connection (non-triangle)
|
||||
var inputCons = connections.Where(c => c.Input.Operation == op && c.Input.Shape != ConnectorShape.Triangle).ToList();
|
||||
if (inputCons.Any())
|
||||
{
|
||||
var sourceConn = inputCons.First();
|
||||
var sourceNodeId = sourceConn.Output.Operation.NodeId;
|
||||
if (outputs.TryGetValue(sourceNodeId, out var sourceVal))
|
||||
{
|
||||
variables[varName] = sourceVal;
|
||||
OnLogMe?.Invoke($"Variable '{varName}' SET to: {sourceVal}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the connector value directly
|
||||
variables[varName] = sourceConn.Output.Value.ToString();
|
||||
OnLogMe?.Invoke($"Variable '{varName}' SET to connector value: {sourceConn.Output.Value}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No connection — use default value from the Value input connector
|
||||
var valueInput = op.Input.FirstOrDefault(i => i.Title == "Value");
|
||||
if (valueInput != null)
|
||||
{
|
||||
variables[varName] = valueInput.Value.ToString();
|
||||
OnLogMe?.Invoke($"Variable '{varName}' SET to default: {valueInput.Value}");
|
||||
}
|
||||
}
|
||||
outputs[op.NodeId] = variables.ContainsKey(varName) ? variables[varName]?.ToString() ?? "" : "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var fullTitle = url;
|
||||
var classNameToSet = fullTitle.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1];
|
||||
var inputConnectionList = connections.Where(c => c.Input.Operation.Title == url).ToList();
|
||||
var inputConnection = inputConnectionList.Where(c => c.Input.Shape != ConnectorShape.Triangle).FirstOrDefault();
|
||||
if (inputConnection == null)
|
||||
{
|
||||
OnLogMe?.Invoke("No input found", logType.Error);
|
||||
throw new Exception("Input connection missig for : " + url);
|
||||
}
|
||||
var outPutNode = inputConnection.Output;
|
||||
if (outPutNode == null)
|
||||
{
|
||||
OnLogMe?.Invoke("No output found", logType.Error);
|
||||
throw new Exception("Output connection missig for : " + url);
|
||||
}
|
||||
if (outPutNode.Operation.Title.ToLower().Contains("parse json"))
|
||||
{
|
||||
var inputNodeForParseJson = connections.Where(c => c.Input.Operation.Title == outPutNode.Title).FirstOrDefault();
|
||||
if (inputNodeForParseJson == null)
|
||||
{
|
||||
OnLogMe?.Invoke("No input found", logType.Error);
|
||||
throw new Exception("Input connection missig for : " + url);
|
||||
}
|
||||
var inputNodeOutput = outputs[inputNodeForParseJson.InputNodeId];
|
||||
if (inputNodeOutput != null)
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject(inputNodeOutput);
|
||||
variables[op.NodeId] = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnLogMe?.Invoke($"Execution started : {url}");
|
||||
if (url.ToLower() == "begin" || url.ToLower() == "end")
|
||||
{
|
||||
OnLogMe?.Invoke($"Being or End node found. skipping it");
|
||||
return;
|
||||
}
|
||||
if (url.ToLower() == "auth")
|
||||
{
|
||||
ResolveAuthNode(editorViewModel.Calculator.Operations);
|
||||
OnLogMe?.Invoke($"Auth node resolved. Base URL: {_authBaseUrl}, Auth Type: {_authType}");
|
||||
return;
|
||||
}
|
||||
OnLogMe?.Invoke($"Starting Execution : {url}");
|
||||
var res = GetResponse(url, "get");
|
||||
if (!string.IsNullOrEmpty(res))
|
||||
{
|
||||
outputs.Add(op.NodeId, res);
|
||||
}
|
||||
OnLogMe?.Invoke($"Response Result : {res}");
|
||||
}
|
||||
|
||||
private void ResolveAuthNode(ICollection<OperationViewModel> allNodes)
|
||||
{
|
||||
var authNode = allNodes.OfType<AuthOperationViewModel>().FirstOrDefault();
|
||||
if (authNode == null)
|
||||
{
|
||||
OnLogMe?.Invoke("No Auth node found. Using defaults.", logType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var inp in authNode.Input)
|
||||
{
|
||||
var title = inp.Title?.Trim() ?? string.Empty;
|
||||
// For unconnected inputs the user types a value into the textbox, which is stored as Value (double).
|
||||
// But the actual text is stored in the Title's textbox or the Value textbox.
|
||||
// We read the connector's Value textbox string representation via the bound text.
|
||||
var val = GetConnectorTextValue(inp);
|
||||
|
||||
switch (title)
|
||||
{
|
||||
case "Base URL": _authBaseUrl = val; break;
|
||||
case "Auth Type": _authType = val; break;
|
||||
case "Token": _authToken = val; break;
|
||||
case "API Key": _authApiKey = val; break;
|
||||
case "Username": _authUsername = val; break;
|
||||
case "Password": _authPassword = val; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Also read from the view-model properties which are bound to the text boxes in the node
|
||||
if (!string.IsNullOrWhiteSpace(authNode.BaseUrl))
|
||||
_authBaseUrl = authNode.BaseUrl;
|
||||
if (!string.IsNullOrWhiteSpace(authNode.AuthType))
|
||||
_authType = authNode.AuthType;
|
||||
|
||||
OnLogMe?.Invoke($"Auth configured — Base URL: {_authBaseUrl}, Auth Type: {_authType}");
|
||||
}
|
||||
|
||||
private void ResolveGetVariableNodes(ICollection<OperationViewModel> allNodes, ICollection<ConnectionViewModel> connections)
|
||||
{
|
||||
// Find all GET variable nodes (not in flow chain) and populate their output
|
||||
foreach (var node in allNodes)
|
||||
{
|
||||
var title = node.Title ?? "";
|
||||
if (!title.StartsWith("GET ") || !title.Contains("(") || !title.Contains(")"))
|
||||
continue;
|
||||
|
||||
var parts = title.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length < 2) continue;
|
||||
var varName = parts[1];
|
||||
|
||||
// Check if the variable already has a value
|
||||
if (variables.TryGetValue(varName, out var existingVal))
|
||||
{
|
||||
outputs[node.NodeId] = existingVal?.ToString() ?? "";
|
||||
// Set the output connector value for downstream nodes
|
||||
var outConn = node.Output.FirstOrDefault(c => c.Shape != ConnectorShape.Triangle);
|
||||
if (outConn != null)
|
||||
{
|
||||
if (double.TryParse(existingVal?.ToString(), out double dVal))
|
||||
outConn.Value = dVal;
|
||||
}
|
||||
OnLogMe?.Invoke($"GET variable '{varName}' resolved to: {existingVal}");
|
||||
}
|
||||
else
|
||||
{
|
||||
OnLogMe?.Invoke($"GET variable '{varName}' has no value yet (will resolve during execution).", logType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetConnectorTextValue(ConnectorViewModel connector)
|
||||
{
|
||||
// When the connector is not connected, the user enters text in the Value text box.
|
||||
// Value is a double, so we try to use it as a string representation.
|
||||
// However, for string inputs (URL, token, etc.) the value won't be meaningful as a double.
|
||||
// The user-typed string is actually stored in the Title field for unconnected inputs in some cases,
|
||||
// but here we rely on the ConnectorViewModel.Value being 0 (default) and the actual string
|
||||
// is not captured as double. So we return empty — the AuthOperationViewModel properties are the
|
||||
// primary source set via the node's text boxes.
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string GetResponse(string url, string type)
|
||||
{
|
||||
string baseURL = !string.IsNullOrWhiteSpace(_authBaseUrl) ? _authBaseUrl : "https://localhost:7107";
|
||||
string responseString = string.Empty;
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri(baseURL);
|
||||
|
||||
// Apply authentication headers
|
||||
if (!string.IsNullOrWhiteSpace(_authType))
|
||||
{
|
||||
var authTypeLower = _authType.Trim().ToLower();
|
||||
if (authTypeLower.Contains("bearer") && !string.IsNullOrWhiteSpace(_authToken))
|
||||
{
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _authToken);
|
||||
}
|
||||
else if (authTypeLower.Contains("basic") &&
|
||||
!string.IsNullOrWhiteSpace(_authUsername))
|
||||
{
|
||||
var credentials = Convert.ToBase64String(
|
||||
Encoding.UTF8.GetBytes($"{_authUsername}:{_authPassword}"));
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);
|
||||
}
|
||||
else if (authTypeLower.Contains("api") && !string.IsNullOrWhiteSpace(_authApiKey))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("X-Api-Key", _authApiKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == "get")
|
||||
{
|
||||
HttpResponseMessage response = client.GetAsync(url).Result;
|
||||
response.EnsureSuccessStatusCode();
|
||||
responseString = response.Content.ReadAsStringAsync().Result;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return responseString;
|
||||
}
|
||||
|
||||
private void PerformChainCheck(ICollection<OperationViewModel> allNodes, ICollection<ConnectionViewModel> connections, bool isExecute)
|
||||
{
|
||||
string startNodeTitle = "begin";
|
||||
string endNodeTitle = "end";
|
||||
|
||||
// Find the starting node
|
||||
var startNode = allNodes.FirstOrDefault(node => node.Title?.ToLower() == startNodeTitle);
|
||||
if (startNode == null)
|
||||
{
|
||||
OnLogMe?.Invoke("No Begin node found", logType.Error);
|
||||
throw new Exception("Begin node not found");
|
||||
}
|
||||
|
||||
// Track visited nodes to prevent infinite loops or revisits
|
||||
HashSet<string> visitedNodes = new HashSet<string>();
|
||||
|
||||
// Perform the DFS Traversal
|
||||
bool isValidChain = TraverseChain(startNode, endNodeTitle, connections, visitedNodes, isExecute);
|
||||
|
||||
if (isValidChain)
|
||||
{
|
||||
OnLogMe?.Invoke("Found complete chain...");
|
||||
OnLogMe?.Invoke("You did it champ...");
|
||||
}
|
||||
else
|
||||
{
|
||||
OnLogMe?.Invoke("Broken Chain found!!! Please fix the chain from begin to end.", logType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TraverseChain(OperationViewModel currentNode, string endNodeTitle,
|
||||
ICollection<ConnectionViewModel> connections,
|
||||
HashSet<string> visitedNodes, bool isExecute)
|
||||
{
|
||||
OnLogMe?.Invoke($"Checking node: {currentNode.Title} , NodeId : {currentNode.NodeId}");
|
||||
|
||||
// If we've reached the "end" node, the chain is valid
|
||||
if (currentNode.Title?.Equals(endNodeTitle, StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Detect cycles and avoid infinite recursion
|
||||
if (visitedNodes.Contains(currentNode.NodeId))
|
||||
{
|
||||
OnLogMe?.Invoke($"Cycle detected at node: {currentNode.Title}. Broken chain found!", logType.Warning);
|
||||
return false;
|
||||
}
|
||||
visitedNodes.Add(currentNode.NodeId);
|
||||
|
||||
// Find all outgoing connections from the current node
|
||||
var outgoingConnections = connections.Where(conn => conn.Output?.Operation == currentNode && conn.Output.Shape == ConnectorShape.Triangle);
|
||||
|
||||
if (!outgoingConnections.Any())
|
||||
{
|
||||
// Only log the first "broken chain" warning, and stop further logging on backtrack
|
||||
OnLogMe?.Invoke($"Broken chain detected at node: {currentNode.Title}", logType.Warning);
|
||||
isAlreadyLogged = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check all outgoing connections
|
||||
foreach (var connection in outgoingConnections)
|
||||
{
|
||||
var nextNode = connection.Input?.Operation;
|
||||
if (nextNode == null)
|
||||
{
|
||||
// Handle null input in the connection
|
||||
OnLogMe?.Invoke($"Broken chain detected due to null input connection from node: {currentNode.Title}", logType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
OnLogMe?.Invoke($"Following connection from {currentNode.Title} to {nextNode.Title}");
|
||||
|
||||
if (isExecute)
|
||||
{
|
||||
StartExecution(currentNode, connections);
|
||||
}
|
||||
|
||||
// Recursively check the next node; stop on the first failure
|
||||
if (TraverseChain(nextNode, endNodeTitle, connections, visitedNodes, isExecute))
|
||||
{
|
||||
return true; // If a valid path to the "end" is found, exit
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAlreadyLogged)
|
||||
{
|
||||
// If no connections lead to the end, this is the broken point => log here ONLY
|
||||
OnLogMe?.Invoke($"Broken chain detected at node: {currentNode.Title}", logType.Warning);
|
||||
isAlreadyLogged = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ValidateRequiredNodes(string nodeName, ICollection<OperationViewModel> allnodes)
|
||||
{
|
||||
var requireNode = allnodes.Where(c => c.Title.ToLower() == nodeName).ToList();
|
||||
if (requireNode.Count > 1)
|
||||
{
|
||||
return $"One or more {nodeName} node found. Only one allowed at a time.";
|
||||
}
|
||||
else if (requireNode.Count == 0)
|
||||
{
|
||||
return $"No {nodeName} node found. At least one {nodeName} node required system to work";
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Examples/Nodify.Calculator/ExpandoOperationViewModel.cs
Normal file
33
Examples/Nodify.Calculator/ExpandoOperationViewModel.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class ExpandoOperationViewModel : OperationViewModel
|
||||
{
|
||||
public ExpandoOperationViewModel()
|
||||
{
|
||||
AddInputCommand = new RequeryCommand(
|
||||
() => Input.Add(new ConnectorViewModel()),
|
||||
() => Input.Count < MaxInput);
|
||||
|
||||
RemoveInputCommand = new RequeryCommand(
|
||||
() => Input.RemoveAt(Input.Count - 1),
|
||||
() => Input.Count > MinInput);
|
||||
}
|
||||
|
||||
public INodifyCommand AddInputCommand { get; }
|
||||
public INodifyCommand RemoveInputCommand { get; }
|
||||
|
||||
private uint _minInput = 0;
|
||||
public uint MinInput
|
||||
{
|
||||
get => _minInput;
|
||||
set => SetProperty(ref _minInput, value);
|
||||
}
|
||||
|
||||
private uint _maxInput = uint.MaxValue;
|
||||
public uint MaxInput
|
||||
{
|
||||
get => _maxInput;
|
||||
set => SetProperty(ref _maxInput, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Examples/Nodify.Calculator/ExpressionOperationViewModel.cs
Normal file
59
Examples/Nodify.Calculator/ExpressionOperationViewModel.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using StringMath;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class ExpressionOperationViewModel : OperationViewModel
|
||||
{
|
||||
private MathExpr? _expr;
|
||||
private string? _expression;
|
||||
public string? Expression
|
||||
{
|
||||
get => _expression;
|
||||
set => SetProperty(ref _expression, value)
|
||||
.Then(GenerateInput);
|
||||
}
|
||||
|
||||
private void GenerateInput()
|
||||
{
|
||||
try
|
||||
{
|
||||
_expr = Expression!.ToMathExpr();
|
||||
ConnectorViewModel[]? toRemove = Input.Where(i => !_expr.LocalVariables.Contains(i.Title)).ToArray();
|
||||
toRemove.ForEach(i => Input.Remove(i));
|
||||
HashSet<string> existingVars = Input.Select(s => s.Title).Where(s => s != null).ToHashSet()!;
|
||||
|
||||
foreach (string variable in _expr.LocalVariables.Except(existingVars))
|
||||
{
|
||||
Input.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = variable
|
||||
});
|
||||
}
|
||||
|
||||
OnInputValueChanged();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInputValueChanged()
|
||||
{
|
||||
if (Output != null && _expr != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Input.ForEach(i => _expr.Substitute(i.Title!, i.Value));
|
||||
//Output.Value = _expr.Result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Examples/Nodify.Calculator/FlowRunner.xaml
Normal file
23
Examples/Nodify.Calculator/FlowRunner.xaml
Normal file
@@ -0,0 +1,23 @@
|
||||
<Window x:Class="Nodify.Calculator.FlowRunner"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||
mc:Ignorable="d"
|
||||
Loaded="Window_Loaded"
|
||||
Title="FlowRunner" Height="450" Width="800" ResizeMode="NoResize" WindowStartupLocation="CenterOwner">
|
||||
<Grid>
|
||||
<RichTextBox x:Name="LogRichTextBox"
|
||||
IsReadOnly="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Background="#FF2D2D30"
|
||||
Foreground="White"
|
||||
FontFamily="Consolas"
|
||||
FontSize="14"
|
||||
BorderThickness="0">
|
||||
<!-- Initialize with an empty document -->
|
||||
<FlowDocument />
|
||||
</RichTextBox>
|
||||
</Grid>
|
||||
</Window>
|
||||
73
Examples/Nodify.Calculator/FlowRunner.xaml.cs
Normal file
73
Examples/Nodify.Calculator/FlowRunner.xaml.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for FlowRunner.xaml
|
||||
/// </summary>
|
||||
public partial class FlowRunner : Window
|
||||
{
|
||||
private readonly Executor _executor;
|
||||
|
||||
public FlowRunner(Executor executor)
|
||||
{
|
||||
InitializeComponent();
|
||||
_executor = executor;
|
||||
}
|
||||
|
||||
private void WriteLog(string message, logType logtype)
|
||||
{
|
||||
// IMPORTANT: UI updates must happen on the main UI thread.
|
||||
// Dispatcher.Invoke ensures that, even if the event is fired from a background thread.
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
// Choose a color based on the log type
|
||||
Brush color = Brushes.White; // Default for Information
|
||||
switch (logtype)
|
||||
{
|
||||
case logType.Warning:
|
||||
color = Brushes.Yellow;
|
||||
break;
|
||||
case logType.Error:
|
||||
color = Brushes.Red;
|
||||
break;
|
||||
}
|
||||
|
||||
// Create a text run with the specified color
|
||||
var run = new Run($"{DateTime.Now:HH:mm:ss} [{logtype}]: {message}\n")
|
||||
{
|
||||
Foreground = color
|
||||
};
|
||||
|
||||
// Add the text to a new paragraph and add the paragraph to the RichTextBox
|
||||
var paragraph = new Paragraph();
|
||||
paragraph.Inlines.Add(run);
|
||||
LogRichTextBox.Document.Blocks.Add(paragraph);
|
||||
|
||||
// Auto-scroll to the bottom
|
||||
LogRichTextBox.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Task.Run(() => {
|
||||
_executor.OnLogMe += WriteLog;
|
||||
_executor.Execute();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
189
Examples/Nodify.Calculator/LiteDBHelper.cs
Normal file
189
Examples/Nodify.Calculator/LiteDBHelper.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using LiteDB;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
public class LiteDbHelper<T> : IDisposable where T : class
|
||||
{
|
||||
private string dbPath = @"MyData.db";
|
||||
private readonly LiteDatabase _database;
|
||||
private readonly ILiteCollection<T> _collection;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the LiteDbHelper class.
|
||||
/// </summary>
|
||||
/// <param name="databasePath">The path to the LiteDB database file.</param>
|
||||
/// <param name="collectionName">The name of the collection to work with.</param>
|
||||
public LiteDbHelper(string collectionName)
|
||||
{
|
||||
_database = new LiteDatabase(dbPath);
|
||||
_collection = _database.GetCollection<T>(collectionName);
|
||||
}
|
||||
|
||||
// --- CRUD Operations ---
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new document into the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to insert.</param>
|
||||
/// <returns>The BsonValue of the inserted document's Id.</returns>
|
||||
public BsonValue Insert(T document)
|
||||
{
|
||||
return _collection.Insert(document);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a collection of documents.
|
||||
/// </summary>
|
||||
/// <param name="documents">The documents to insert.</param>
|
||||
/// <returns>The number of documents inserted.</returns>
|
||||
public int BulkInsert(IEnumerable<T> documents)
|
||||
{
|
||||
return _collection.Insert(documents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a document in the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to update.</param>
|
||||
/// <returns>True if the document was updated, otherwise false.</returns>
|
||||
public bool Update(T document)
|
||||
{
|
||||
return _collection.Update(document);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a document from the collection by its Id.
|
||||
/// </summary>
|
||||
/// <param name="id">The Id of the document to delete.</param>
|
||||
/// <returns>True if the document was deleted, otherwise false.</returns>
|
||||
public bool Delete(BsonValue id)
|
||||
{
|
||||
return _collection.Delete(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes documents from the collection based on a predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The expression to filter documents to delete.</param>
|
||||
/// <returns>The number of documents deleted.</returns>
|
||||
public int DeleteMany(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return _collection.DeleteMany(predicate);
|
||||
}
|
||||
|
||||
// --- Query Operations ---
|
||||
|
||||
/// <summary>
|
||||
/// Finds a single document by its Id.
|
||||
/// </summary>
|
||||
/// <param name="id">The Id of the document.</param>
|
||||
/// <returns>The document if found, otherwise null.</returns>
|
||||
public T FindById(BsonValue id)
|
||||
{
|
||||
return _collection.FindById(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first document that matches the predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The expression to filter documents.</param>
|
||||
/// <returns>The first matching document, or null if none are found.</returns>
|
||||
public T FindOne(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return _collection.FindOne(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all documents in the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable of all documents.</returns>
|
||||
public IEnumerable<T> FindAll()
|
||||
{
|
||||
return _collection.FindAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds documents based on a predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The expression to filter documents.</param>
|
||||
/// <returns>An enumerable of matching documents.</returns>
|
||||
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return _collection.Find(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any document exists that matches the predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The expression to filter documents.</param>
|
||||
/// <returns>True if a matching document exists, otherwise false.</returns>
|
||||
public bool Exists(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return _collection.Exists(predicate);
|
||||
}
|
||||
|
||||
// --- Indexing ---
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that an index is created for the specified field.
|
||||
/// </summary>
|
||||
/// <param name="field">The expression representing the field to be indexed.</param>
|
||||
/// <param name="unique">Whether the index should enforce unique values.</param>
|
||||
/// <returns>True if the index was created, otherwise false.</returns>
|
||||
public bool EnsureIndex<K>(Expression<Func<T, K>> field, bool unique = false)
|
||||
{
|
||||
return _collection.EnsureIndex(field, unique);
|
||||
}
|
||||
|
||||
// --- File Storage Operations ---
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a file to the LiteDB file storage.
|
||||
/// </summary>
|
||||
/// <param name="id">A unique identifier for the file.</param>
|
||||
/// <param name="filePath">The path to the file to upload.</param>
|
||||
public void UploadFile(string id, string filePath)
|
||||
{
|
||||
_database.FileStorage.Upload(id, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a file from the LiteDB file storage.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the file.</param>
|
||||
/// <param name="destinationPath">The path to save the downloaded file.</param>
|
||||
public void DownloadFile(string id, Stream destinationPath)
|
||||
{
|
||||
_database.FileStorage.Download(id, destinationPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a file from the LiteDB file storage.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the file to delete.</param>
|
||||
/// <returns>True if the file was deleted, otherwise false.</returns>
|
||||
public bool DeleteFile(string id)
|
||||
{
|
||||
return _database.FileStorage.Delete(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a file's metadata in the LiteDB file storage.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the file.</param>
|
||||
/// <returns>The LiteFileInfo object if found, otherwise null.</returns>
|
||||
public LiteFileInfo<string> FindFileById(string id)
|
||||
{
|
||||
return _database.FileStorage.FindById(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the LiteDatabase connection.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_database?.Dispose();
|
||||
}
|
||||
}
|
||||
164
Examples/Nodify.Calculator/MainWindow.xaml
Normal file
164
Examples/Nodify.Calculator/MainWindow.xaml
Normal file
@@ -0,0 +1,164 @@
|
||||
<Window x:Class="Nodify.Calculator.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
Background="{DynamicResource NodifyEditor.BackgroundBrush}"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow"
|
||||
Height="650"
|
||||
Width="1200">
|
||||
<Window.DataContext>
|
||||
<local:ApplicationViewModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="T"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding Source={x:Static shared:ThemeManager.SetNextThemeCommand}}" />
|
||||
<KeyBinding Key="N"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding AddEditorCommand}" />
|
||||
|
||||
<KeyBinding Key="R"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding RunFlowCommand}" />
|
||||
|
||||
<KeyBinding Key="S"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding SaveFileCommand}" />
|
||||
|
||||
<KeyBinding Key="O"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding OpenFileCommand}" />
|
||||
|
||||
<KeyBinding Key="W"
|
||||
Modifiers="Ctrl"
|
||||
Command="{Binding CloseEditorCommand}"
|
||||
CommandParameter="{Binding SelectedEditor.Id}"/>
|
||||
</Window.InputBindings>
|
||||
|
||||
<Window.Resources>
|
||||
<shared:BindingProxy x:Key="Proxy"
|
||||
DataContext="{Binding}"/>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:EditorViewModel}">
|
||||
<local:EditorView/>
|
||||
</DataTemplate>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<shared:TabControlEx ItemsSource="{Binding Editors}"
|
||||
SelectedItem="{Binding SelectedEditor}"
|
||||
AddTabCommand="{Binding AddEditorCommand}"
|
||||
AutoScrollToEnd="{Binding AutoSelectNewEditor}">
|
||||
<shared:TabControlEx.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type shared:TabItemEx}"
|
||||
BasedOn="{StaticResource {x:Type shared:TabItemEx}}">
|
||||
<Setter Property="Header"
|
||||
Value="{Binding Name}"/>
|
||||
<Setter Property="CloseTabCommand"
|
||||
Value="{Binding DataContext.CloseEditorCommand ,Source={StaticResource Proxy}}"/>
|
||||
<Setter Property="CloseTabCommandParameter"
|
||||
Value="{Binding Id}"/>
|
||||
<Setter Property="ToolTip"
|
||||
Value="Double click to edit" />
|
||||
</Style>
|
||||
</shared:TabControlEx.ItemContainerStyle>
|
||||
</shared:TabControlEx>
|
||||
|
||||
<Expander Header="Click to hide/show"
|
||||
IsExpanded="True"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom">
|
||||
<Border MaxWidth="325"
|
||||
MaxHeight="300"
|
||||
CornerRadius="3">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{DynamicResource BackgroundColor}"
|
||||
Opacity="0.7" />
|
||||
</Border.Background>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="10"
|
||||
IsHitTestVisible="False">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}"
|
||||
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Margin"
|
||||
Value="0 0 0 5" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<StackPanel Margin="0 0 0 20">
|
||||
<TextBlock Text="(New) Drag and drop nodes from the toolbox"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource NodeInput.BorderBrush}"
|
||||
FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">CTRL + N/W</Run>
|
||||
<Run>: open/close editor</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">CTRL + R</Run>
|
||||
<Run>: Run Flow</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">CTRL + S</Run>
|
||||
<Run>: Save Flow</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">CTRL + O</Run>
|
||||
<Run>: Open Saved Flow</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">ALT + Click</Run>
|
||||
<Run>: disconnect connector</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">Right Click</Run>
|
||||
<Run>: show operations menu (create nodes)</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">Delete</Run>
|
||||
<Run>: delete selection</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">CTRL + T</Run>
|
||||
<Run>: change theme</Run>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Foreground="Red"
|
||||
FontWeight="Bold">CTRL + G</Run>
|
||||
<Run>: group selection (hold SHIFT and mouse drag the header to move the group node alone)</Run>
|
||||
</TextBlock>
|
||||
<TextBlock Text="Drag a connection and drop it on the editor"
|
||||
TextWrapping="Wrap"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Text="Hover over a connector to see its value"
|
||||
TextWrapping="Wrap"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Text="Create a Calculator node and double click it to open"
|
||||
TextWrapping="Wrap"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Text="Create an Operation Graph and add operations to it"
|
||||
TextWrapping="Wrap"
|
||||
FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Expander>
|
||||
</Grid>
|
||||
</Window>
|
||||
26
Examples/Nodify.Calculator/MainWindow.xaml.cs
Normal file
26
Examples/Nodify.Calculator/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
EditorGestures.Mappings.Editor.Cutting.Unbind();
|
||||
|
||||
EventManager.RegisterClassHandler(
|
||||
typeof(UIElement),
|
||||
Keyboard.PreviewGotKeyboardFocusEvent,
|
||||
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
|
||||
}
|
||||
|
||||
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
Title = e.NewFocus.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Examples/Nodify.Calculator/Models/SaveGraphModel.cs
Normal file
22
Examples/Nodify.Calculator/Models/SaveGraphModel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator.Models
|
||||
{
|
||||
public class SaveGraphModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<SaveNodes> Nodes { get; set; } = new List<SaveNodes>();
|
||||
}
|
||||
|
||||
public class SaveNodes
|
||||
{
|
||||
public Point Location { get; set; }
|
||||
}
|
||||
}
|
||||
13
Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs
Normal file
13
Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.Calculator.Models
|
||||
{
|
||||
public class SwaggerNodeModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string OPType { get; set; } = string.Empty;
|
||||
public List<string> InputNames { get; set; } = new List<string>();
|
||||
public string SwaggerFileName { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
12
Examples/Nodify.Calculator/Models/VariableModel.cs
Normal file
12
Examples/Nodify.Calculator/Models/VariableModel.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.Calculator.Models
|
||||
{
|
||||
public class VariableModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string VariableType { get; set; } = "string";
|
||||
public string DefaultValue { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
30
Examples/Nodify.Calculator/Nodify.Calculator.csproj
Normal file
30
Examples/Nodify.Calculator/Nodify.Calculator.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFrameworks>net9-windows;</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="leet swagger.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="leet swagger.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LiteDB" Version="5.0.21" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="14.5.0" />
|
||||
<PackageReference Include="NSwag.Core" Version="14.5.0" />
|
||||
<PackageReference Include="StringMath" Version="4.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Nodify\Nodify.csproj" />
|
||||
<ProjectReference Include="..\Nodify.Shared\Nodify.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
44
Examples/Nodify.Calculator/OperationGraphViewModel.cs
Normal file
44
Examples/Nodify.Calculator/OperationGraphViewModel.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class OperationGraphViewModel : CalculatorOperationViewModel
|
||||
{
|
||||
private Size _size;
|
||||
public Size DesiredSize
|
||||
{
|
||||
get => _size;
|
||||
set => SetProperty(ref _size, value);
|
||||
}
|
||||
|
||||
private Size _prevSize;
|
||||
|
||||
private bool _isExpanded = true;
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isExpanded, value))
|
||||
{
|
||||
if (_isExpanded)
|
||||
{
|
||||
DesiredSize = _prevSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
_prevSize = Size;
|
||||
// Fit content
|
||||
DesiredSize = new Size(double.NaN, double.NaN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OperationGraphViewModel()
|
||||
{
|
||||
InnerCalculator.Operations[0].Location = new Point(50, 50);
|
||||
InnerCalculator.Operations[1].Location = new Point(200, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Examples/Nodify.Calculator/OperationGroupViewModel.cs
Normal file
14
Examples/Nodify.Calculator/OperationGroupViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class OperationGroupViewModel : OperationViewModel
|
||||
{
|
||||
private Size _size;
|
||||
public Size GroupSize
|
||||
{
|
||||
get => _size;
|
||||
set => SetProperty(ref _size, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Examples/Nodify.Calculator/OperationInfoViewModel.cs
Normal file
36
Examples/Nodify.Calculator/OperationInfoViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
Normal,
|
||||
Expando,
|
||||
Expression,
|
||||
Calculator,
|
||||
Group,
|
||||
Graph,
|
||||
API,
|
||||
System
|
||||
}
|
||||
|
||||
public class OperationInfoViewModel
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
public OperationType Type { get; set; }
|
||||
public IOperation? Operation { get; set; }
|
||||
public SystemOperations sysOp { get; set; }
|
||||
public List<string?> Input { get; } = new List<string?>();
|
||||
public List<string?> Output { get; } = new List<string?>();
|
||||
public uint MinInput { get; set; }
|
||||
public uint MaxInput { get; set; }
|
||||
public string InputType { get; set; } = string.Empty;
|
||||
public string OPType { get; set; }
|
||||
public bool IsFlowNode { get; set; }
|
||||
public bool IsModelNode { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string VariableType { get; set; } = string.Empty;
|
||||
public string DefaultValue { get; set; } = string.Empty;
|
||||
public bool IsSimpleVariable { get; set; }
|
||||
}
|
||||
}
|
||||
128
Examples/Nodify.Calculator/OperationViewModel.cs
Normal file
128
Examples/Nodify.Calculator/OperationViewModel.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using LiteDB;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class OperationViewModel : ObservableObject
|
||||
{
|
||||
public OperationViewModel()
|
||||
{
|
||||
Input.WhenAdded(x =>
|
||||
{
|
||||
x.Operation = this;
|
||||
x.IsInput = true;
|
||||
x.PropertyChanged += OnInputValueChanged;
|
||||
})
|
||||
.WhenRemoved(x =>
|
||||
{
|
||||
x.PropertyChanged -= OnInputValueChanged;
|
||||
});
|
||||
|
||||
Output.WhenAdded(c =>
|
||||
{
|
||||
c.Operation = this;
|
||||
c.IsInput = false;
|
||||
c.Value = 0;
|
||||
c.PropertyChanged += OnInputValueChanged;
|
||||
})
|
||||
.WhenRemoved(x =>
|
||||
{
|
||||
x.PropertyChanged -= OnInputValueChanged;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnInputValueChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(ConnectorViewModel.Value))
|
||||
{
|
||||
OnInputValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
private Size _size;
|
||||
public Size Size
|
||||
{
|
||||
get => _size;
|
||||
set => SetProperty(ref _size, value);
|
||||
}
|
||||
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public bool IsReadOnly { get; set; }
|
||||
|
||||
[BsonIgnore]
|
||||
private IOperation? _operation;
|
||||
[BsonIgnore]
|
||||
public IOperation? Operation
|
||||
{
|
||||
get => _operation;
|
||||
set => SetProperty(ref _operation, value)
|
||||
.Then(OnInputValueChanged);
|
||||
}
|
||||
|
||||
private string nodeId;
|
||||
|
||||
public string NodeId
|
||||
{
|
||||
get { return nodeId; }
|
||||
set => SetProperty(ref nodeId, value);
|
||||
}
|
||||
|
||||
[BsonIgnore]
|
||||
public NodifyObservableCollection<ConnectorViewModel> Input { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||
|
||||
[BsonIgnore]
|
||||
public NodifyObservableCollection<ConnectorViewModel> Output { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||
|
||||
//private ConnectorViewModel? _output;
|
||||
//public ConnectorViewModel? Output
|
||||
//{
|
||||
// get => _output;
|
||||
// set
|
||||
// {
|
||||
// if (SetProperty(ref _output, value) && _output != null)
|
||||
// {
|
||||
// _output.Operation = this;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
protected virtual void OnInputValueChanged()
|
||||
{
|
||||
//if (Output != null && Operation != null)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// var input = Input.Select(i => i.Value).ToArray();
|
||||
// Output.Value = Operation?.Execute(input) ?? 0;
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Examples/Nodify.Calculator/Operations/BinaryOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/BinaryOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class BinaryOperation : IOperation
|
||||
{
|
||||
private readonly Func<double, double, double> _func;
|
||||
|
||||
public BinaryOperation(Func<double, double, double> func) => _func = func;
|
||||
|
||||
public double Execute(params double[] operands)
|
||||
=> _func.Invoke(operands[0], operands[1]);
|
||||
}
|
||||
}
|
||||
7
Examples/Nodify.Calculator/Operations/IOperation.cs
Normal file
7
Examples/Nodify.Calculator/Operations/IOperation.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public interface IOperation
|
||||
{
|
||||
double Execute(params double[] operands);
|
||||
}
|
||||
}
|
||||
469
Examples/Nodify.Calculator/Operations/OperationFactory.cs
Normal file
469
Examples/Nodify.Calculator/Operations/OperationFactory.cs
Normal file
@@ -0,0 +1,469 @@
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Shapes;
|
||||
using Size = System.Windows.Size;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public static class OperationFactory
|
||||
{
|
||||
|
||||
public static List<OperationInfoViewModel> GetSystemNodes()
|
||||
{
|
||||
List<OperationInfoViewModel> systemNodes = new List<OperationInfoViewModel>();
|
||||
|
||||
var copynode = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "COPY",
|
||||
Type = OperationType.System,
|
||||
OPType = "copy",
|
||||
IsFlowNode = false,
|
||||
};
|
||||
copynode.Input.Add("");
|
||||
copynode.Output.Add("");
|
||||
copynode.Output.Add("");
|
||||
|
||||
var begin = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "Begin",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.BEGIN,
|
||||
IsFlowNode = true
|
||||
};
|
||||
begin.Output.Add("");
|
||||
|
||||
var ending = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "End",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.END,
|
||||
IsFlowNode = true
|
||||
};
|
||||
ending.Input.Add("");
|
||||
|
||||
var debugAndCreateModels = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "Debug & Create Model",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.DEBUG_AND_CREATE_MODEL,
|
||||
IsFlowNode = true
|
||||
};
|
||||
debugAndCreateModels.Input.Add("");
|
||||
debugAndCreateModels.Output.Add("");
|
||||
|
||||
var jsonParseNode = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "Parse Json",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.PARSEJSON,
|
||||
IsFlowNode = false
|
||||
};
|
||||
jsonParseNode.Input.Add("");
|
||||
jsonParseNode.Output.Add("");
|
||||
|
||||
var splitNode = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "Split",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.SPLIT,
|
||||
IsFlowNode = false
|
||||
};
|
||||
splitNode.Input.Add("");
|
||||
splitNode.Output.Add("");
|
||||
|
||||
var takeNode = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "TAKE",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.TAKE,
|
||||
IsFlowNode = false
|
||||
};
|
||||
takeNode.Input.Add("");
|
||||
takeNode.Output.Add("");
|
||||
|
||||
var authNode = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "Auth",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.AUTH,
|
||||
IsFlowNode = true
|
||||
};
|
||||
authNode.Input.Add("Base URL");
|
||||
authNode.Input.Add("Auth Type");
|
||||
authNode.Input.Add("Token");
|
||||
authNode.Input.Add("API Key");
|
||||
authNode.Input.Add("Username");
|
||||
authNode.Input.Add("Password");
|
||||
|
||||
systemNodes.Add(authNode);
|
||||
systemNodes.Add(copynode);
|
||||
systemNodes.Add(begin);
|
||||
systemNodes.Add(ending);
|
||||
systemNodes.Add(debugAndCreateModels);
|
||||
systemNodes.Add(jsonParseNode);
|
||||
systemNodes.Add(splitNode);
|
||||
systemNodes.Add(takeNode);
|
||||
return systemNodes;
|
||||
}
|
||||
|
||||
public static List<OperationInfoViewModel> GetOperationsInfo(Type container)
|
||||
{
|
||||
List<OperationInfoViewModel> result = new List<OperationInfoViewModel>();
|
||||
|
||||
foreach (var method in container.GetMethods())
|
||||
{
|
||||
if (method.IsStatic)
|
||||
{
|
||||
OperationInfoViewModel op = new OperationInfoViewModel
|
||||
{
|
||||
Title = method.Name
|
||||
};
|
||||
|
||||
var attr = method.GetCustomAttribute<OperationAttribute>();
|
||||
var para = method.GetParameters();
|
||||
|
||||
bool generateInputNames = true;
|
||||
|
||||
op.Type = OperationType.Normal;
|
||||
|
||||
if (para.Length == 2)
|
||||
{
|
||||
var delType = typeof(Func<double, double, double>);
|
||||
var del = (Func<double, double, double>)Delegate.CreateDelegate(delType, method);
|
||||
|
||||
op.Operation = new BinaryOperation(del);
|
||||
}
|
||||
else if (para.Length == 1)
|
||||
{
|
||||
if (para[0].ParameterType.IsArray)
|
||||
{
|
||||
op.Type = OperationType.Expando;
|
||||
|
||||
var delType = typeof(Func<double[], double>);
|
||||
var del = (Func<double[], double>)Delegate.CreateDelegate(delType, method);
|
||||
|
||||
op.Operation = new ParamsOperation(del);
|
||||
op.MaxInput = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var delType = typeof(Func<double, double>);
|
||||
var del = (Func<double, double>)Delegate.CreateDelegate(delType, method);
|
||||
|
||||
op.Operation = new UnaryOperation(del);
|
||||
}
|
||||
}
|
||||
else if (para.Length == 0)
|
||||
{
|
||||
var delType = typeof(Func<double>);
|
||||
var del = (Func<double>)Delegate.CreateDelegate(delType, method);
|
||||
|
||||
op.Operation = new ValueOperation(del);
|
||||
}
|
||||
|
||||
if (attr != null)
|
||||
{
|
||||
op.MinInput = attr.MinInput;
|
||||
op.MaxInput = attr.MaxInput;
|
||||
generateInputNames = attr.GenerateInputNames;
|
||||
}
|
||||
else
|
||||
{
|
||||
op.MinInput = (uint)para.Length;
|
||||
op.MaxInput = (uint)para.Length;
|
||||
}
|
||||
|
||||
foreach (var param in para)
|
||||
{
|
||||
op.Input.Add(generateInputNames ? param.Name : null);
|
||||
}
|
||||
|
||||
for (int i = op.Input.Count; i < op.MinInput; i++)
|
||||
{
|
||||
op.Input.Add(null);
|
||||
}
|
||||
op.Output.Add("");
|
||||
result.Add(op);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static OperationViewModel GetOperation(OperationInfoViewModel info)
|
||||
{
|
||||
var input = info.Input.Select(i => new ConnectorViewModel
|
||||
{
|
||||
Title = i
|
||||
});
|
||||
|
||||
switch (info.Type)
|
||||
{
|
||||
case OperationType.Expression:
|
||||
var eo = new ExpressionOperationViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
Operation = info.Operation,
|
||||
Expression = "1 + sin {a} + cos {b}"
|
||||
};
|
||||
eo.Output.Add(new ConnectorViewModel());
|
||||
return eo;
|
||||
case OperationType.Calculator:
|
||||
return new CalculatorOperationViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
Operation = info.Operation,
|
||||
};
|
||||
|
||||
case OperationType.Expando:
|
||||
var o = new ExpandoOperationViewModel
|
||||
{
|
||||
MaxInput = info.MaxInput,
|
||||
MinInput = info.MinInput,
|
||||
Title = info.Title,
|
||||
Operation = info.Operation
|
||||
};
|
||||
o.Output.Add(new ConnectorViewModel());
|
||||
o.Input.AddRange(input);
|
||||
return o;
|
||||
|
||||
case OperationType.Group:
|
||||
return new OperationGroupViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
};
|
||||
|
||||
case OperationType.Graph:
|
||||
return new OperationGraphViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
DesiredSize = new Size(420, 250)
|
||||
};
|
||||
|
||||
case OperationType.API:
|
||||
var _o = new APIOperationViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
OperationType = info.OPType.ToUpper()
|
||||
};
|
||||
var connectorViewModel = new ConnectorViewModel()
|
||||
{
|
||||
Title = "",
|
||||
Shape = ConnectorShape.Triangle
|
||||
};
|
||||
var connectorViewModel2 = new ConnectorViewModel()
|
||||
{
|
||||
Title = "",
|
||||
Shape = ConnectorShape.Triangle,
|
||||
IsInput = false
|
||||
};
|
||||
_o.Output.Add(connectorViewModel2);
|
||||
_o.Output.Add(new ConnectorViewModel());
|
||||
_o.Input.Add(connectorViewModel);
|
||||
foreach (var item in input)
|
||||
{
|
||||
item.ConnectorColor = Color.GreenYellow;
|
||||
_o.Input.Add(item);
|
||||
}
|
||||
//_o.Input.AddRange(input);
|
||||
return _o;
|
||||
case OperationType.System:
|
||||
if (info.sysOp == SystemOperations.AUTH)
|
||||
{
|
||||
var authOp = new AuthOperationViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
SystemOperationType = SystemOperations.AUTH
|
||||
};
|
||||
// Add flow connectors (triangle)
|
||||
var flowIn = new ConnectorViewModel()
|
||||
{
|
||||
Title = "",
|
||||
Shape = ConnectorShape.Triangle
|
||||
};
|
||||
var flowOut = new ConnectorViewModel()
|
||||
{
|
||||
Title = "",
|
||||
Shape = ConnectorShape.Triangle,
|
||||
IsInput = false
|
||||
};
|
||||
authOp.Input.Add(flowIn);
|
||||
authOp.Output.Add(flowOut);
|
||||
// Add data input connectors
|
||||
foreach (var inp in input)
|
||||
{
|
||||
inp.ConnectorColor = System.Drawing.Color.Orange;
|
||||
authOp.Input.Add(inp);
|
||||
}
|
||||
return authOp;
|
||||
}
|
||||
|
||||
var sysOp = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
SystemOperationType = info.sysOp
|
||||
};
|
||||
|
||||
if (info.sysOp == SystemOperations.GET_SET && info.IsModelNode)
|
||||
{
|
||||
if (info.Title == "GET")
|
||||
{
|
||||
info.Output.Add("");
|
||||
info.IsFlowNode = false;
|
||||
}
|
||||
else if (info.Title == "SET")
|
||||
{
|
||||
info.Input.Add("");
|
||||
var customModelDir = "CustomModels";
|
||||
Directory.CreateDirectory(customModelDir);
|
||||
var flpath = System.IO.Path.Combine(customModelDir, info.ClassName + ".cs");
|
||||
if (File.Exists(flpath))
|
||||
{
|
||||
//Read all the properties from the file
|
||||
var fileContent = File.ReadAllText(flpath);
|
||||
|
||||
// Parse and analyze properties
|
||||
var properties = GetPropertiesFromClass(fileContent);
|
||||
|
||||
// Print out the extracted properties and their types
|
||||
Console.WriteLine("Properties found:");
|
||||
foreach (var property in properties)
|
||||
{
|
||||
Console.WriteLine($"Property Name: {property.Name}, Type: {property.Type}");
|
||||
info.Output.Add(property.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
info.IsFlowNode = true;
|
||||
}
|
||||
var flTitle = $"{info.Title} {info.ClassName}";
|
||||
sysOp.Title = flTitle;
|
||||
}
|
||||
|
||||
if (info.sysOp == SystemOperations.GET_SET && info.IsSimpleVariable)
|
||||
{
|
||||
var varLabel = $"{info.Title} {info.ClassName} ({info.VariableType})";
|
||||
sysOp.Title = varLabel;
|
||||
|
||||
if (info.Title == "GET")
|
||||
{
|
||||
// GET variable: output the value, no flow needed
|
||||
info.Output.Add("Value");
|
||||
info.IsFlowNode = false;
|
||||
}
|
||||
else if (info.Title == "SET")
|
||||
{
|
||||
// SET variable: input connector for the value, flow node
|
||||
info.Input.Add("Value");
|
||||
info.IsFlowNode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.sysOp != SystemOperations.BEGIN &&
|
||||
info.sysOp != SystemOperations.END &&
|
||||
info.IsFlowNode)
|
||||
{
|
||||
var flowinputnode = new ConnectorViewModel()
|
||||
{
|
||||
Title = "",
|
||||
Shape = ConnectorShape.Triangle
|
||||
};
|
||||
var flowoutputnode = new ConnectorViewModel()
|
||||
{
|
||||
Title = "",
|
||||
Shape = ConnectorShape.Triangle,
|
||||
IsInput = false
|
||||
};
|
||||
sysOp.Input.Add(flowinputnode);
|
||||
sysOp.Output.Add(flowoutputnode);
|
||||
}
|
||||
foreach (var item in info.Output)
|
||||
{
|
||||
var out1 = new ConnectorViewModel()
|
||||
{
|
||||
Title = string.IsNullOrEmpty(item) ? "" : item,
|
||||
IsInput = false,
|
||||
ConnectorColor = Color.DarkRed,
|
||||
Shape = (info.sysOp == SystemOperations.BEGIN || info.sysOp == SystemOperations.END) ? ConnectorShape.Triangle : ConnectorShape.Circle,
|
||||
};
|
||||
if (out1.Shape != ConnectorShape.Triangle)
|
||||
{
|
||||
out1.ConnectorColor = Color.DeepPink;
|
||||
}
|
||||
sysOp.Output.Add(out1);
|
||||
}
|
||||
foreach (var item in input)
|
||||
{
|
||||
item.Shape = (info.sysOp == SystemOperations.BEGIN || info.sysOp == SystemOperations.END) ? ConnectorShape.Triangle : ConnectorShape.Circle;
|
||||
sysOp.Input.Add(item);
|
||||
}
|
||||
return sysOp;
|
||||
default:
|
||||
{
|
||||
var op = new OperationViewModel
|
||||
{
|
||||
Title = info.Title,
|
||||
Operation = info.Operation,
|
||||
};
|
||||
var ccv = new ConnectorViewModel()
|
||||
{
|
||||
IsInput = false,
|
||||
Shape = ConnectorShape.Circle,
|
||||
Title = ""
|
||||
};
|
||||
op.Output.Add(ccv);
|
||||
|
||||
op.Input.AddRange(input);
|
||||
return op;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static List<CustomProperty> GetPropertiesFromClass(string classContent)
|
||||
{
|
||||
// Parse the C# class content using Roslyn
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(classContent);
|
||||
var root = syntaxTree.GetRoot();
|
||||
|
||||
// Find the first class declaration (you can refine this if there are multiple classes)
|
||||
var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||
|
||||
// List to store extracted properties
|
||||
var properties = new List<CustomProperty>();
|
||||
|
||||
if (classDeclaration != null)
|
||||
{
|
||||
// Look for property declarations inside the class
|
||||
var propertyDeclarations = classDeclaration.Members.OfType<PropertyDeclarationSyntax>();
|
||||
foreach (var property in propertyDeclarations)
|
||||
{
|
||||
var propertyName = property.Identifier.Text; // Property name
|
||||
var propertyType = property.Type.ToString(); // Property type
|
||||
|
||||
// Add the property and its type to the list
|
||||
properties.Add(new CustomProperty
|
||||
{
|
||||
Name = propertyName,
|
||||
Type = propertyType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomProperty // <- Renamed to avoid conflicts with System.Reflection.PropertyInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
35
Examples/Nodify.Calculator/Operations/OperationsContainer.cs
Normal file
35
Examples/Nodify.Calculator/Operations/OperationsContainer.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public static class OperationsContainer
|
||||
{
|
||||
[Operation(MinInput = 2, MaxInput = 10, GenerateInputNames = false)]
|
||||
public static double Add(params double[] operands)
|
||||
=> operands.Sum();
|
||||
|
||||
[Operation(MinInput = 2, MaxInput = 10, GenerateInputNames = false)]
|
||||
public static double Multiply(params double[] operands)
|
||||
=> operands.Aggregate((x, y) => x * y);
|
||||
|
||||
public static double Divide(double a, double b)
|
||||
=> a / b;
|
||||
|
||||
public static double Subtract(double a, double b)
|
||||
=> a - b;
|
||||
|
||||
public static double Pow(double value, double exp)
|
||||
=> (double)Math.Pow((double)value, (double)exp);
|
||||
|
||||
public static double PI()
|
||||
=> (double)Math.PI;
|
||||
}
|
||||
|
||||
public sealed class OperationAttribute : Attribute
|
||||
{
|
||||
public uint MaxInput { get; set; }
|
||||
public uint MinInput { get; set; }
|
||||
public bool GenerateInputNames { get; set; }
|
||||
}
|
||||
}
|
||||
14
Examples/Nodify.Calculator/Operations/ParamsOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/ParamsOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class ParamsOperation : IOperation
|
||||
{
|
||||
private readonly Func<double[], double> _func;
|
||||
|
||||
public ParamsOperation(Func<double[], double> func) => _func = func;
|
||||
|
||||
public double Execute(params double[] operands)
|
||||
=> _func.Invoke(operands);
|
||||
}
|
||||
}
|
||||
14
Examples/Nodify.Calculator/Operations/UnaryOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/UnaryOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class UnaryOperation : IOperation
|
||||
{
|
||||
private readonly Func<double, double> _func;
|
||||
|
||||
public UnaryOperation(Func<double, double> func) => _func = func;
|
||||
|
||||
public double Execute(params double[] operands)
|
||||
=> _func.Invoke(operands[0]);
|
||||
}
|
||||
}
|
||||
14
Examples/Nodify.Calculator/Operations/ValueOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/ValueOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class ValueOperation : IOperation
|
||||
{
|
||||
private readonly Func<double> _func;
|
||||
|
||||
public ValueOperation(Func<double> func) => _func = func;
|
||||
|
||||
public double Execute(params double[] operands)
|
||||
=> _func();
|
||||
}
|
||||
}
|
||||
50
Examples/Nodify.Calculator/OperationsExtensions.cs
Normal file
50
Examples/Nodify.Calculator/OperationsExtensions.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public static class OperationsExtensions
|
||||
{
|
||||
public static Rect GetBoundingBox(this IEnumerable<OperationViewModel> nodes, double padding = 0, int gridCellSize = 15)
|
||||
{
|
||||
var minX = double.MaxValue;
|
||||
var minY = double.MaxValue;
|
||||
|
||||
var maxX = double.MinValue;
|
||||
var maxY = double.MinValue;
|
||||
|
||||
const int width = 200; //node.Width
|
||||
const int height = 100; //node.Height
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (node.Location.X < minX)
|
||||
{
|
||||
minX = node.Location.X;
|
||||
}
|
||||
|
||||
if (node.Location.Y < minY)
|
||||
{
|
||||
minY = node.Location.Y;
|
||||
}
|
||||
|
||||
var sizeX = node.Location.X + width;
|
||||
if (sizeX > maxX)
|
||||
{
|
||||
maxX = sizeX;
|
||||
}
|
||||
|
||||
var sizeY = node.Location.Y + height;
|
||||
if (sizeY > maxY)
|
||||
{
|
||||
maxY = sizeY;
|
||||
}
|
||||
}
|
||||
|
||||
var result = new Rect(minX - padding, minY - padding, maxX - minX + padding * 2, maxY - minY + padding * 2);
|
||||
result.X = (int)result.X / gridCellSize * gridCellSize;
|
||||
result.Y = (int)result.Y / gridCellSize * gridCellSize;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Examples/Nodify.Calculator/OperationsMenuView.xaml
Normal file
81
Examples/Nodify.Calculator/OperationsMenuView.xaml
Normal file
@@ -0,0 +1,81 @@
|
||||
<UserControl x:Class="Nodify.Calculator.OperationsMenuView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
mc:Ignorable="d"
|
||||
MinWidth="250"
|
||||
d:DesignHeight="400"
|
||||
d:DesignWidth="250"
|
||||
d:DataContext="{d:DesignInstance local:OperationsMenuViewModel}"
|
||||
Visibility="{Binding IsVisible, Converter={shared:BooleanToVisibilityConverter}}">
|
||||
<UserControl.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}"
|
||||
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{DynamicResource ForegroundBrush}" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border Padding="7"
|
||||
CornerRadius="3"
|
||||
Background="{DynamicResource Node.BackgroundBrush}"
|
||||
BorderBrush="{StaticResource NodifyEditor.SelectionRectangleStrokeBrush}"
|
||||
BorderThickness="2">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ItemsControl Grid.Row="1"
|
||||
x:Name="OperationsList"
|
||||
Focusable="True"
|
||||
KeyboardNavigation.TabNavigation="Cycle"
|
||||
ItemsSource="{Binding MenuAvailableOperations}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:OperationInfoViewModel}">
|
||||
<Button Content="{Binding Title}"
|
||||
Command="{Binding DataContext.CreateOperationCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
ClickMode="Press"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
Padding="3"
|
||||
Cursor="Hand"
|
||||
HorizontalContentAlignment="Left">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}">
|
||||
<Setter Property="FocusVisualStyle"
|
||||
Value="{StaticResource {x:Static SystemParameters.FocusVisualStyleKey}}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver"
|
||||
Value="True">
|
||||
<Setter Property="Background"
|
||||
TargetName="Border"
|
||||
Value="{DynamicResource NodeInput.BorderBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
51
Examples/Nodify.Calculator/OperationsMenuView.xaml.cs
Normal file
51
Examples/Nodify.Calculator/OperationsMenuView.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public partial class OperationsMenuView : UserControl
|
||||
{
|
||||
private readonly WeakReference<UIElement?> _focusToRestore = new WeakReference<UIElement?>(null!);
|
||||
|
||||
public OperationsMenuView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
IsVisibleChanged += OperationsMenuView_IsVisibleChanged;
|
||||
}
|
||||
|
||||
private void OperationsMenuView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.NewValue is true)
|
||||
{
|
||||
_focusToRestore.SetTarget(Keyboard.FocusedElement as UIElement);
|
||||
Dispatcher.BeginInvoke(new Action(() => OperationsList.Focus()), System.Windows.Threading.DispatcherPriority.Input);
|
||||
}
|
||||
else if (e.NewValue is false)
|
||||
{
|
||||
if (_focusToRestore.TryGetTarget(out var elementToFocus))
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
elementToFocus!.Focus();
|
||||
}), System.Windows.Threading.DispatcherPriority.Input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
SetCurrentValue(VisibilityProperty, Visibility.Collapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
380
Examples/Nodify.Calculator/OperationsMenuViewModel.cs
Normal file
380
Examples/Nodify.Calculator/OperationsMenuViewModel.cs
Normal file
@@ -0,0 +1,380 @@
|
||||
using LiteDB;
|
||||
using Microsoft.Win32;
|
||||
using NSwag;
|
||||
using NSwag.CodeGeneration.CSharp;
|
||||
using Nodify.Calculator.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class OperationsMenuViewModel : ObservableObject
|
||||
{
|
||||
private bool _isVisible;
|
||||
public bool IsVisible
|
||||
{
|
||||
get => _isVisible;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _isVisible, value);
|
||||
if (!value)
|
||||
{
|
||||
Closed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
public event Action? Closed;
|
||||
|
||||
public void OpenAt(Point targetLocation)
|
||||
{
|
||||
MenuAvailableOperations.Clear();
|
||||
MenuAvailableOperations.AddRange(AvailableOperations);
|
||||
Close();
|
||||
Location = targetLocation;
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
public void OpenOnlyGetSetVariable(Point targetLocation, string className)
|
||||
{
|
||||
var getVM = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "GET",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
IsModelNode = true,
|
||||
ClassName = className,
|
||||
};
|
||||
var setVM = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "SET",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
IsModelNode = true,
|
||||
ClassName = className
|
||||
};
|
||||
MenuAvailableOperations.Clear();
|
||||
MenuAvailableOperations.Add(getVM);
|
||||
MenuAvailableOperations.Add(setVM);
|
||||
Close();
|
||||
Location = targetLocation;
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
public void OpenGetSetForVariable(Point targetLocation, OperationInfoViewModel variableInfo)
|
||||
{
|
||||
var getVM = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "GET",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
IsSimpleVariable = true,
|
||||
VariableType = variableInfo.VariableType,
|
||||
DefaultValue = variableInfo.DefaultValue,
|
||||
ClassName = variableInfo.Title ?? string.Empty
|
||||
};
|
||||
var setVM = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "SET",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
IsSimpleVariable = true,
|
||||
VariableType = variableInfo.VariableType,
|
||||
DefaultValue = variableInfo.DefaultValue,
|
||||
ClassName = variableInfo.Title ?? string.Empty
|
||||
};
|
||||
MenuAvailableOperations.Clear();
|
||||
MenuAvailableOperations.Add(getVM);
|
||||
MenuAvailableOperations.Add(setVM);
|
||||
Close();
|
||||
Location = targetLocation;
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
|
||||
public void Close()
|
||||
{
|
||||
IsVisible = false;
|
||||
}
|
||||
|
||||
public NodifyObservableCollection<OperationInfoViewModel> MenuAvailableOperations { get; }
|
||||
public NodifyObservableCollection<OperationInfoViewModel> AvailableOperations { get; }
|
||||
public NodifyObservableCollection<OperationInfoViewModel> SwaggerOperations { get; }
|
||||
public NodifyObservableCollection<OperationInfoViewModel> AvailableModels { get; }
|
||||
public NodifyObservableCollection<OperationInfoViewModel> AvailableVariables { get; }
|
||||
public INodifyCommand CreateOperationCommand { get; }
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[BsonIgnore]
|
||||
private readonly CalculatorViewModel _calculator;
|
||||
|
||||
public OperationsMenuViewModel(CalculatorViewModel calculator)
|
||||
{
|
||||
_calculator = calculator;
|
||||
List<OperationInfoViewModel> operations = new List<OperationInfoViewModel>();
|
||||
AvailableModels = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||
AvailableVariables = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||
LoadVariablesFromDb();
|
||||
|
||||
var customModelDir = "CustomModels";
|
||||
Directory.CreateDirectory(customModelDir);
|
||||
var dirInfo = new DirectoryInfo(customModelDir);
|
||||
var allFiles = dirInfo.GetFiles("*.cs");
|
||||
foreach (var item in allFiles)
|
||||
{
|
||||
var flName = Path.GetFileNameWithoutExtension(item.Name);
|
||||
var opVInfo = new OperationInfoViewModel()
|
||||
{
|
||||
Title = flName,
|
||||
IsModelNode = true,
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
ClassName = flName
|
||||
};
|
||||
AvailableModels.Add(opVInfo);
|
||||
}
|
||||
|
||||
operations.AddRange(OperationFactory.GetSystemNodes());
|
||||
operations.AddRange(OperationFactory.GetOperationsInfo(typeof(OperationsContainer)));
|
||||
|
||||
SwaggerOperations = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||
LoadSwaggerNodesFromDb();
|
||||
|
||||
operations.AddRange(SwaggerOperations);
|
||||
|
||||
AvailableOperations = new NodifyObservableCollection<OperationInfoViewModel>(operations);
|
||||
MenuAvailableOperations = new NodifyObservableCollection<OperationInfoViewModel>(operations);
|
||||
CreateOperationCommand = new DelegateCommand<OperationInfoViewModel>(CreateOperation);
|
||||
ImportSwaggerCommand = new DelegateCommand(ImportSwagger);
|
||||
AddVariableCommand = new DelegateCommand(AddVariable);
|
||||
}
|
||||
|
||||
|
||||
public void AddNewModel(OperationInfoViewModel opModel)
|
||||
{
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
// Add directly if on UI thread
|
||||
AvailableModels.Add(opModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, marshal the call to the UI thread
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
AvailableModels.Add(opModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public INodifyCommand ImportSwaggerCommand { get; }
|
||||
public INodifyCommand AddVariableCommand { get; }
|
||||
|
||||
private void AddVariable()
|
||||
{
|
||||
var dialog = new AddVariableDialog();
|
||||
dialog.Owner = System.Windows.Application.Current.MainWindow;
|
||||
if (dialog.ShowDialog() != true)
|
||||
return;
|
||||
|
||||
var varName = dialog.VariableName;
|
||||
var varType = dialog.VariableType;
|
||||
var defaultVal = dialog.DefaultValue;
|
||||
|
||||
// Check for duplicate name
|
||||
if (AvailableVariables.Any(v => v.Title == varName))
|
||||
{
|
||||
MessageBox.Show($"A variable named '{varName}' already exists.", "Duplicate Variable", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var varInfo = CreateVariableInfo(varName, varType, defaultVal);
|
||||
AvailableVariables.Add(varInfo);
|
||||
SaveVariableToDb(varName, varType, defaultVal);
|
||||
}
|
||||
|
||||
private OperationInfoViewModel CreateVariableInfo(string name, string varType, string defaultValue)
|
||||
{
|
||||
return new OperationInfoViewModel
|
||||
{
|
||||
Title = name,
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
IsSimpleVariable = true,
|
||||
VariableType = varType,
|
||||
DefaultValue = defaultValue,
|
||||
IsModelNode = false
|
||||
};
|
||||
}
|
||||
|
||||
private void SaveVariableToDb(string name, string varType, string defaultValue)
|
||||
{
|
||||
using var db = new LiteDbHelper<VariableModel>("Variables");
|
||||
db.Insert(new VariableModel
|
||||
{
|
||||
Name = name,
|
||||
VariableType = varType,
|
||||
DefaultValue = defaultValue
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadVariablesFromDb()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = new LiteDbHelper<VariableModel>("Variables");
|
||||
foreach (var v in db.FindAll())
|
||||
{
|
||||
AvailableVariables.Add(CreateVariableInfo(v.Name, v.VariableType, v.DefaultValue));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// DB may not exist yet
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportSwagger()
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "JSON files (*.json)|*.json|Text files (*.txt)|*.txt|All files (*.*)|*.*",
|
||||
Title = "Import Swagger JSON File"
|
||||
};
|
||||
|
||||
if (openFileDialog.ShowDialog() != true)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var nodes = ParseSwaggerFile(openFileDialog.FileName);
|
||||
var fileName = Path.GetFileName(openFileDialog.FileName);
|
||||
SaveSwaggerNodesToDb(nodes, fileName);
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
SwaggerOperations.Add(node);
|
||||
AvailableOperations.Add(node);
|
||||
MenuAvailableOperations.Add(node);
|
||||
}
|
||||
|
||||
MessageBox.Show($"Successfully imported {nodes.Count} API endpoints from Swagger.", "Import Swagger", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to parse Swagger file: {ex.Message}", "Import Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private List<OperationInfoViewModel> ParseSwaggerFile(string jsonFilePath)
|
||||
{
|
||||
var operations = new List<OperationInfoViewModel>();
|
||||
var openApiDocument = OpenApiDocument.FromFileAsync(jsonFilePath).Result;
|
||||
|
||||
foreach (var path in openApiDocument.Paths)
|
||||
{
|
||||
foreach (var method in path.Value)
|
||||
{
|
||||
var ovmodel = new OperationInfoViewModel
|
||||
{
|
||||
Title = path.Key,
|
||||
OPType = method.Key,
|
||||
Type = OperationType.API
|
||||
};
|
||||
|
||||
var addedParams = new HashSet<string>();
|
||||
foreach (var parameter in method.Value.Parameters)
|
||||
{
|
||||
if (addedParams.Add(parameter.Name))
|
||||
{
|
||||
ovmodel.Input.Add(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
ovmodel.Output.Add("");
|
||||
operations.Add(ovmodel);
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
private void SaveSwaggerNodesToDb(List<OperationInfoViewModel> nodes, string swaggerFileName)
|
||||
{
|
||||
using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
|
||||
db.DeleteMany(n => n.SwaggerFileName == swaggerFileName);
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
db.Insert(new SwaggerNodeModel
|
||||
{
|
||||
Title = node.Title ?? string.Empty,
|
||||
OPType = node.OPType ?? string.Empty,
|
||||
InputNames = new List<string>(node.Input),
|
||||
SwaggerFileName = swaggerFileName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSwaggerNodesFromDb()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
|
||||
var savedNodes = db.FindAll();
|
||||
|
||||
foreach (var saved in savedNodes)
|
||||
{
|
||||
var ovmodel = new OperationInfoViewModel
|
||||
{
|
||||
Title = saved.Title,
|
||||
OPType = saved.OPType,
|
||||
Type = OperationType.API
|
||||
};
|
||||
|
||||
foreach (var inputName in saved.InputNames)
|
||||
{
|
||||
ovmodel.Input.Add(inputName);
|
||||
}
|
||||
|
||||
ovmodel.Output.Add("");
|
||||
SwaggerOperations.Add(ovmodel);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// DB may not exist yet on first run
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateOperation(OperationInfoViewModel operationInfo)
|
||||
{
|
||||
OperationViewModel op = OperationFactory.GetOperation(operationInfo);
|
||||
op.Location = Location;
|
||||
|
||||
_calculator.Operations.Add(op);
|
||||
|
||||
var pending = _calculator.PendingConnection;
|
||||
if (pending.IsVisible)
|
||||
{
|
||||
var connector = pending.Source.IsInput ? op.Output.FirstOrDefault() : op.Input.FirstOrDefault();
|
||||
if (connector != null && _calculator.CanCreateConnection(pending.Source, connector))
|
||||
{
|
||||
_calculator.CreateConnection(pending.Source, connector);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Examples/Nodify.Calculator/PendingConnectionViewModel.cs
Normal file
36
Examples/Nodify.Calculator/PendingConnectionViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class PendingConnectionViewModel : ObservableObject
|
||||
{
|
||||
private ConnectorViewModel _source = default!;
|
||||
public ConnectorViewModel Source
|
||||
{
|
||||
get => _source;
|
||||
set => SetProperty(ref _source, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel? _target;
|
||||
public ConnectorViewModel? Target
|
||||
{
|
||||
get => _target;
|
||||
set => SetProperty(ref _target, value);
|
||||
}
|
||||
|
||||
private bool _isVisible;
|
||||
public bool IsVisible
|
||||
{
|
||||
get => _isVisible;
|
||||
set => SetProperty(ref _isVisible, value);
|
||||
}
|
||||
|
||||
private Point _targetLocation;
|
||||
|
||||
public Point TargetLocation
|
||||
{
|
||||
get => _targetLocation;
|
||||
set => SetProperty(ref _targetLocation, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Examples/Nodify.Calculator/SystemOperationViewModel.cs
Normal file
33
Examples/Nodify.Calculator/SystemOperationViewModel.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public enum SystemOperations
|
||||
{
|
||||
COPY,
|
||||
IF,
|
||||
TAKE,
|
||||
BEGIN,
|
||||
END,
|
||||
DEBUG_AND_CREATE_MODEL,
|
||||
GET_SET,
|
||||
PARSEJSON,
|
||||
SPLIT,
|
||||
AUTH
|
||||
}
|
||||
|
||||
public class SystemOperationViewModel : OperationViewModel
|
||||
{
|
||||
private SystemOperations _systemOperation;
|
||||
|
||||
public SystemOperations SystemOperationType
|
||||
{
|
||||
get => _systemOperation;
|
||||
set => SetProperty(ref _systemOperation, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
189
Examples/Nodify.Calculator/leet swagger.txt
Normal file
189
Examples/Nodify.Calculator/leet swagger.txt
Normal file
@@ -0,0 +1,189 @@
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "LeetU",
|
||||
"version": "1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/course": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Course"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Course"
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Course"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Course"
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Course"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/course/{courseId}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Course"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "courseId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/student/{studentId}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Student"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "studentId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/student": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Student"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/student/{studentId}/course": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Student"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "studentId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/student/{studentId}/course/{courseId}": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Student"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "studentId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "courseId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Course": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"startDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user