Initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
namespace RobotNet10.ScriptEditor.Models;
|
||||
|
||||
internal interface IHierarchyItem : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
bool IsModified { get; }
|
||||
int WarningCount { get; }
|
||||
int ErrorCount { get; }
|
||||
|
||||
event Action? Modified;
|
||||
event Action? NameChanged;
|
||||
event Action<int, int>? DiagnosticsChanged;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using RobotNet10.ScriptEngine.Shared;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Models;
|
||||
|
||||
public class ScriptFile(DocumentId id, ScriptFileDto data, ScriptFolder? parent = null) : IHierarchyItem
|
||||
{
|
||||
public DocumentId Id { get; } = id;
|
||||
public ScriptFolder? Parent => parent;
|
||||
public string Path => System.IO.Path.Combine(parent?.Path ?? "", Name);
|
||||
public int Level { get; } = data.Level;
|
||||
public bool IsModified { get; private set; }
|
||||
public int WarningCount { get; private set; }
|
||||
public int ErrorCount { get; private set; }
|
||||
|
||||
public event Action? Modified
|
||||
{
|
||||
add => _modified += value;
|
||||
remove => _modified -= value;
|
||||
}
|
||||
|
||||
private event Action? _modified;
|
||||
public event Action? NameChanged;
|
||||
public event Action<int, int>? DiagnosticsChanged;
|
||||
|
||||
private string _name = data.Name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException($"Tên file {_name} không được để trống");
|
||||
|
||||
if (_name == value) return;
|
||||
|
||||
_name = value;
|
||||
NameChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private string _code = data.Code;
|
||||
public string Code
|
||||
{
|
||||
get => _code;
|
||||
set
|
||||
{
|
||||
_code = value;
|
||||
if (IsModified)
|
||||
{
|
||||
if (SavedCode == _code)
|
||||
{
|
||||
IsModified = false;
|
||||
_modified?.Invoke();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SavedCode != _code)
|
||||
{
|
||||
IsModified = true;
|
||||
_modified?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Diagnostic> _diagnostics = [];
|
||||
public IEnumerable<Diagnostic> Diagnostics
|
||||
{
|
||||
get => _diagnostics;
|
||||
set
|
||||
{
|
||||
_diagnostics = value;
|
||||
var warning = _diagnostics.Count(d => d.Severity == DiagnosticSeverity.Warning);
|
||||
var error = _diagnostics.Count(d => d.Severity == DiagnosticSeverity.Error);
|
||||
|
||||
if (WarningCount != warning || ErrorCount != error)
|
||||
{
|
||||
WarningCount = warning;
|
||||
ErrorCount = error;
|
||||
// Send absolute values, not delta, for consistency with ScriptFolder.OnDiagnosticsChanged
|
||||
DiagnosticsChanged?.Invoke(warning, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string SavedCode = data.Code;
|
||||
|
||||
public void Saved()
|
||||
{
|
||||
if (!IsModified || SavedCode == Code)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SavedCode = Code;
|
||||
IsModified = false;
|
||||
_modified?.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using RobotNet10.ScriptEngine.Shared;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Models;
|
||||
|
||||
public class ScriptFolder(ScriptFolderDto data, ScriptFolder? parent = null) : IHierarchyItem
|
||||
{
|
||||
public ScriptFolder? Parent => parent;
|
||||
public string Path => System.IO.Path.Combine(parent?.Path ?? "", Name);
|
||||
public int Level { get; } = data.Level;
|
||||
|
||||
public bool IsModified { get; private set; }
|
||||
public int WarningCount { get; private set; }
|
||||
public int ErrorCount { get; private set; }
|
||||
public bool IsExpanded { get; set; } = false;
|
||||
|
||||
public event Action? ChildrenChanged;
|
||||
public event Action? Modified;
|
||||
public event Action? NameChanged;
|
||||
public event Action<int, int>? DiagnosticsChanged;
|
||||
|
||||
public IEnumerable<ScriptFolder> Folders => WorkspaceFolders;
|
||||
public IEnumerable<ScriptFile> Files => WorkspaceFiles;
|
||||
|
||||
private string _name = data.Name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException($"Tên file {_name} không được để trống");
|
||||
|
||||
if (_name == value) return;
|
||||
|
||||
_name = value;
|
||||
|
||||
NameChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<ScriptFolder> WorkspaceFolders = [];
|
||||
private readonly List<ScriptFile> WorkspaceFiles = [];
|
||||
|
||||
public void AddFiles(params IEnumerable<ScriptFile> files)
|
||||
{
|
||||
WorkspaceFiles.AddRange(files);
|
||||
WorkspaceFiles.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
// Recalculate totals including new files
|
||||
var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(folder => folder.WarningCount);
|
||||
var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(folder => folder.ErrorCount);
|
||||
var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(folder => folder.IsModified);
|
||||
|
||||
var warningChanged = WarningCount != totalWarningCount;
|
||||
var errorChanged = ErrorCount != totalErrorCount;
|
||||
var modifiedChanged = IsModified != newIsModified;
|
||||
|
||||
WarningCount = totalWarningCount;
|
||||
ErrorCount = totalErrorCount;
|
||||
IsModified = newIsModified;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
file.Modified += UpdateModified;
|
||||
file.DiagnosticsChanged += OnDiagnosticsChanged;
|
||||
}
|
||||
|
||||
// Notify changes
|
||||
if (warningChanged || errorChanged)
|
||||
{
|
||||
DiagnosticsChanged?.Invoke(WarningCount, ErrorCount);
|
||||
}
|
||||
if (modifiedChanged)
|
||||
{
|
||||
Modified?.Invoke();
|
||||
}
|
||||
|
||||
ChildrenChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void AddFolders(params IEnumerable<ScriptFolder> folders)
|
||||
{
|
||||
WorkspaceFolders.AddRange(folders);
|
||||
WorkspaceFolders.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
// Recalculate totals including new folders
|
||||
var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(folder => folder.WarningCount);
|
||||
var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(folder => folder.ErrorCount);
|
||||
var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(folder => folder.IsModified);
|
||||
|
||||
var warningChanged = WarningCount != totalWarningCount;
|
||||
var errorChanged = ErrorCount != totalErrorCount;
|
||||
var modifiedChanged = IsModified != newIsModified;
|
||||
|
||||
WarningCount = totalWarningCount;
|
||||
ErrorCount = totalErrorCount;
|
||||
IsModified = newIsModified;
|
||||
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
folder.Modified += UpdateModified;
|
||||
folder.DiagnosticsChanged += OnDiagnosticsChanged;
|
||||
}
|
||||
|
||||
// Notify changes
|
||||
if (warningChanged || errorChanged)
|
||||
{
|
||||
DiagnosticsChanged?.Invoke(WarningCount, ErrorCount);
|
||||
}
|
||||
if (modifiedChanged)
|
||||
{
|
||||
Modified?.Invoke();
|
||||
}
|
||||
|
||||
ChildrenChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void RemoveFile(ScriptFile file)
|
||||
{
|
||||
if (WorkspaceFiles.Remove(file))
|
||||
{
|
||||
// Unsubscribe from file events
|
||||
file.Modified -= UpdateModified;
|
||||
file.DiagnosticsChanged -= OnDiagnosticsChanged;
|
||||
|
||||
// Recalculate totals after removal
|
||||
var totalWarningCount = Files.Sum(f => f.WarningCount) + Folders.Sum(folder => folder.WarningCount);
|
||||
var totalErrorCount = Files.Sum(f => f.ErrorCount) + Folders.Sum(folder => folder.ErrorCount);
|
||||
var newIsModified = Files.Any(f => f.IsModified) || Folders.Any(folder => folder.IsModified);
|
||||
|
||||
var warningChanged = WarningCount != totalWarningCount;
|
||||
var errorChanged = ErrorCount != totalErrorCount;
|
||||
var modifiedChanged = IsModified != newIsModified;
|
||||
|
||||
WarningCount = totalWarningCount;
|
||||
ErrorCount = totalErrorCount;
|
||||
IsModified = newIsModified;
|
||||
|
||||
// Dispose the removed file
|
||||
file.Dispose();
|
||||
|
||||
// Notify changes
|
||||
if (warningChanged || errorChanged)
|
||||
{
|
||||
DiagnosticsChanged?.Invoke(WarningCount, ErrorCount);
|
||||
}
|
||||
if (modifiedChanged)
|
||||
{
|
||||
Modified?.Invoke();
|
||||
}
|
||||
|
||||
ChildrenChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFolder(ScriptFolder folder)
|
||||
{
|
||||
if (WorkspaceFolders.Remove(folder))
|
||||
{
|
||||
// Unsubscribe from folder events
|
||||
folder.Modified -= UpdateModified;
|
||||
folder.DiagnosticsChanged -= OnDiagnosticsChanged;
|
||||
|
||||
// Recalculate totals after removal
|
||||
var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(f => f.WarningCount);
|
||||
var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(f => f.ErrorCount);
|
||||
var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(f => f.IsModified);
|
||||
|
||||
var warningChanged = WarningCount != totalWarningCount;
|
||||
var errorChanged = ErrorCount != totalErrorCount;
|
||||
var modifiedChanged = IsModified != newIsModified;
|
||||
|
||||
WarningCount = totalWarningCount;
|
||||
ErrorCount = totalErrorCount;
|
||||
IsModified = newIsModified;
|
||||
|
||||
// Dispose the removed folder (will handle its children)
|
||||
folder.Dispose();
|
||||
|
||||
// Notify changes
|
||||
if (warningChanged || errorChanged)
|
||||
{
|
||||
DiagnosticsChanged?.Invoke(WarningCount, ErrorCount);
|
||||
}
|
||||
if (modifiedChanged)
|
||||
{
|
||||
Modified?.Invoke();
|
||||
}
|
||||
|
||||
ChildrenChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateModified()
|
||||
{
|
||||
var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(folder => folder.IsModified);
|
||||
|
||||
// Only update and notify if the value actually changed
|
||||
if (IsModified != newIsModified)
|
||||
{
|
||||
IsModified = newIsModified;
|
||||
Modified?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDiagnosticsChanged(int warningCount, int errorCount)
|
||||
{
|
||||
// Recalculate totals from all children
|
||||
// Note: The parameters (warningCount, errorCount) are the absolute values from the child that changed,
|
||||
// but we recalculate from all children to ensure accuracy, especially when multiple files change simultaneously
|
||||
var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(folder => folder.WarningCount);
|
||||
var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(folder => folder.ErrorCount);
|
||||
|
||||
if (WarningCount != totalWarningCount || ErrorCount != totalErrorCount)
|
||||
{
|
||||
WarningCount = totalWarningCount;
|
||||
ErrorCount = totalErrorCount;
|
||||
DiagnosticsChanged?.Invoke(WarningCount, ErrorCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates WarningCount, ErrorCount, and IsModified from all children.
|
||||
/// This is useful after diagnostics have been updated for all children.
|
||||
/// </summary>
|
||||
internal void RecalculateTotals()
|
||||
{
|
||||
// First, recalculate all child folders recursively
|
||||
foreach (var subFolder in Folders)
|
||||
{
|
||||
subFolder.RecalculateTotals();
|
||||
}
|
||||
|
||||
// Then calculate totals from all children
|
||||
var totalWarningCount = Files.Sum(f => f.WarningCount) + Folders.Sum(f => f.WarningCount);
|
||||
var totalErrorCount = Files.Sum(f => f.ErrorCount) + Folders.Sum(f => f.ErrorCount);
|
||||
var isModified = Files.Any(f => f.IsModified) || Folders.Any(f => f.IsModified);
|
||||
|
||||
// Update properties if changed
|
||||
var warningChanged = WarningCount != totalWarningCount;
|
||||
var errorChanged = ErrorCount != totalErrorCount;
|
||||
var modifiedChanged = IsModified != isModified;
|
||||
|
||||
if (warningChanged || errorChanged || modifiedChanged)
|
||||
{
|
||||
WarningCount = totalWarningCount;
|
||||
ErrorCount = totalErrorCount;
|
||||
IsModified = isModified;
|
||||
|
||||
if (warningChanged || errorChanged)
|
||||
{
|
||||
DiagnosticsChanged?.Invoke(WarningCount, ErrorCount);
|
||||
}
|
||||
|
||||
if (modifiedChanged)
|
||||
{
|
||||
Modified?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var file in WorkspaceFiles)
|
||||
{
|
||||
file.Dispose();
|
||||
}
|
||||
WorkspaceFiles.Clear();
|
||||
foreach (var folder in Folders)
|
||||
{
|
||||
folder.Dispose();
|
||||
}
|
||||
WorkspaceFolders.Clear();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
namespace RobotNet10.ScriptEditor.Models;
|
||||
|
||||
public class ScriptMissionParameterValueModel(string name, string type, string valueDefault)
|
||||
{
|
||||
public static readonly Dictionary<string, Type> PredefinedTypeMap = new()
|
||||
{
|
||||
["System.Boolean"] = typeof(bool),
|
||||
["System.Byte"] = typeof(byte),
|
||||
["System.SByte"] = typeof(sbyte),
|
||||
["System.Int16"] = typeof(short),
|
||||
["System.UInt16"] = typeof(ushort),
|
||||
["System.Int32"] = typeof(int),
|
||||
["System.UInt32"] = typeof(uint),
|
||||
["System.Int64"] = typeof(long),
|
||||
["System.UInt64"] = typeof(ulong),
|
||||
["System.Single"] = typeof(float),
|
||||
["System.Double"] = typeof(double),
|
||||
["System.Decimal"] = typeof(double),
|
||||
["System.Char"] = typeof(char),
|
||||
["System.String"] = typeof(string)
|
||||
};
|
||||
|
||||
public string Name { get; } = name;
|
||||
public string Type { get; } = type;
|
||||
public string? Default { get; } = valueDefault;
|
||||
public string Errors { get; set; } = string.Empty;
|
||||
public object? Value { get; set; } = null;
|
||||
|
||||
private void EnsureType(string expectedType)
|
||||
{
|
||||
if (Type != expectedType)
|
||||
throw new InvalidOperationException($"Parameter '{Name}' is not of type '{expectedType}'. Actual type: '{Type}'.");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value?.ToString() ?? "null";
|
||||
}
|
||||
|
||||
public bool BoolValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Boolean");
|
||||
return Value is not null && (bool)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Boolean");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public byte ByteValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Byte");
|
||||
return Value is null ? default : (byte)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Byte");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public sbyte SByteValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.SByte");
|
||||
return Value is null ? default : (sbyte)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.SByte");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public short ShortValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Int16");
|
||||
return Value is null ? default : (short)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Int16");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort UShortValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.UInt16");
|
||||
return Value is null ? default : (ushort)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.UInt16");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int IntValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Int32");
|
||||
return Value is null ? default : (int)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Int32");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public uint UIntValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.UInt32");
|
||||
return Value is null ? default : (uint)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.UInt32");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public long LongValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Int64");
|
||||
return Value is null ? default : (long)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Int64");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong ULongValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.UInt64");
|
||||
return Value is null ? default : (ulong)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.UInt64");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float FloatValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Single");
|
||||
return Value is null ? default : (float)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Single");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public double DoubleValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Double");
|
||||
return Value is null ? default : (double)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Double");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public double DecimalValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Decimal");
|
||||
return Value is null ? default : (double)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Decimal");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public char CharValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.Char");
|
||||
return Value is null ? default : (char)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.Char");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string StringValue
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureType("System.String");
|
||||
return Value is null ? string.Empty : (string)Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureType("System.String");
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (PredefinedTypeMap.TryGetValue(Type, out var type))
|
||||
{
|
||||
Value = type.IsValueType ? Activator.CreateInstance(type) : (type == typeof(string) ? string.Empty : null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user