Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}