Files
Marvin 2f61e5dc00 Refactor ViewModels to inherit from BaseViewModel and improve UI thread handling
- Updated HookBrowserViewModel, MainViewModel, SettingsViewModel, and SyncViewModel to inherit from BaseViewModel.
- Introduced BaseViewModel with methods for running actions on the UI thread.
- Enhanced SyncViewModel to handle property changes on the UI thread.
- Refactored ExtractorService to return ExtractorResult instead of IReadOnlyList<HookDefinition>.
- Added ExtractorResult class to encapsulate extraction results, including success status and error messages.
- Implemented UIProgress class for reporting progress on the UI thread.
- Updated hook_groups.json with additional classes and methods for various categories.
- Improved error handling and logging in ExtractorService and SyncViewModel.
2026-04-20 04:26:06 +02:00

234 lines
6.6 KiB
C#

using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using gregExtractor.Models;
using gregExtractor.Services;
using gregExtractor.Utils;
namespace gregExtractor.GUI.ViewModels;
public sealed class CreateViewModel : BaseViewModel
{
private readonly TemplateService _templateService = new();
private int _step = 1;
private string _modName = "MyDataCenterMod";
private string _author = "TeamGreg";
private string _version = "1.0.0";
private string _description = "Generated by gregExtractor";
private string _outputPath = Directory.GetCurrentDirectory();
private TemplateType _selectedTemplateType = TemplateType.HarmonyPatch;
private string? _selectedCategory;
private bool _isLoading;
public CreateViewModel()
{
HookClassifier classifier = HookClassifier.LoadFromFile(null, new UIProgress<string>(AddLog));
foreach (string group in classifier.Groups.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
Categories.Add(group);
if (!Categories.Contains("Misc", StringComparer.OrdinalIgnoreCase))
Categories.Add("Misc");
TemplateTypes = Enum.GetValues<TemplateType>();
NextStepCommand = new RelayCommand(NextStep, () => !IsLoading);
PreviousStepCommand = new RelayCommand(PreviousStep, () => !IsLoading);
BrowseOutputPathCommand = new AsyncRelayCommand(BrowseOutputPathAsync);
GenerateCommand = new AsyncRelayCommand(GenerateAsync, () => !IsLoading && !string.IsNullOrWhiteSpace(ModName));
OpenOutputCommand = new RelayCommand(OpenOutput, () => Directory.Exists(OutputPath));
}
public Func<string?, Task<string?>>? BrowseFolderHandler { get; set; }
public int Step
{
get => _step;
set => SetProperty(ref _step, Math.Clamp(value, 1, 3));
}
public bool IsLoading
{
get => _isLoading;
set
{
if (Dispatcher.UIThread.CheckAccess())
{
if (!SetProperty(ref _isLoading, value))
return;
}
else
{
Dispatcher.UIThread.Post(() => SetProperty(ref _isLoading, value));
return;
}
NextStepCommand.NotifyCanExecuteChanged();
PreviousStepCommand.NotifyCanExecuteChanged();
GenerateCommand.NotifyCanExecuteChanged();
}
}
public string ModName
{
get => _modName;
set
{
if (!SetProperty(ref _modName, value))
return;
GenerateCommand.NotifyCanExecuteChanged();
}
}
public string Author
{
get => _author;
set => SetProperty(ref _author, value);
}
public string Version
{
get => _version;
set => SetProperty(ref _version, value);
}
public string Description
{
get => _description;
set => SetProperty(ref _description, value);
}
public string OutputPath
{
get => _outputPath;
set
{
if (!SetProperty(ref _outputPath, value?.Trim() ?? string.Empty))
return;
OpenOutputCommand.NotifyCanExecuteChanged();
}
}
public TemplateType SelectedTemplateType
{
get => _selectedTemplateType;
set => SetProperty(ref _selectedTemplateType, value);
}
public string? SelectedCategory
{
get => _selectedCategory;
set => SetProperty(ref _selectedCategory, value);
}
public IReadOnlyList<TemplateType> TemplateTypes { get; }
public ObservableCollection<string> Categories { get; } = new();
public ObservableCollection<string> PreviewTree { get; } = new();
public ObservableCollection<string> LogLines { get; } = new();
public IRelayCommand NextStepCommand { get; }
public IRelayCommand PreviousStepCommand { get; }
public IAsyncRelayCommand BrowseOutputPathCommand { get; }
public IAsyncRelayCommand GenerateCommand { get; }
public IRelayCommand OpenOutputCommand { get; }
private void NextStep()
{
Step = Math.Min(3, Step + 1);
if (Step == 3)
RefreshPreview();
}
private void PreviousStep()
{
Step = Math.Max(1, Step - 1);
}
private async Task BrowseOutputPathAsync()
{
if (BrowseFolderHandler is null)
return;
string? selected = await BrowseFolderHandler("CreateOutput").ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(selected))
OutputPath = selected;
}
private async Task GenerateAsync()
{
IsLoading = true;
try
{
string hooksPath = Path.Combine(Directory.GetCurrentDirectory(), "game_hooks.json");
var result = await Task.Run(() => _templateService.GenerateModAsync(
ModName,
SelectedCategory,
SelectedTemplateType,
OutputPath,
hooksPath,
new UIProgress<string>(AddLog),
CancellationToken.None)).ConfigureAwait(false);
await Dispatcher.UIThread.InvokeAsync(() =>
{
PreviewTree.Clear();
PreviewTree.Add(Path.GetFileName(result.ModDirectory) + "/");
foreach (string file in result.GeneratedFiles.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
PreviewTree.Add(" " + Path.GetRelativePath(result.ModDirectory, file).Replace('\\', '/'));
});
AddLog($"Mod erstellt: {result.ModDirectory}");
}
catch (Exception ex)
{
AddLog($"Mod-Erstellung fehlgeschlagen: {ex.Message}");
}
finally
{
IsLoading = false;
}
}
private void OpenOutput()
{
if (!Directory.Exists(OutputPath))
return;
Process.Start(new ProcessStartInfo
{
FileName = OutputPath,
UseShellExecute = true,
});
}
private void RefreshPreview()
{
RunOnUIThread(() =>
{
PreviewTree.Clear();
PreviewTree.Add(ModName + "/");
PreviewTree.Add(" " + ModName + ".csproj");
PreviewTree.Add(" MainPlugin.cs");
PreviewTree.Add(" mod.json");
PreviewTree.Add(" Patches/");
});
}
private void AddLog(string message)
{
string line = $"[{DateTime.Now:HH:mm:ss}] {message}";
if (LogLines.Count > 1000)
LogLines.RemoveAt(0);
LogLines.Add(line);
}
}