diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index 6016bcc6..9273943e 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -156,6 +156,7 @@ public partial class App : Application services.AddView(); services.AddView(); services.AddView(); + services.AddView(); services.AddView(); services.AddSingleton(); // services.AddView(); diff --git a/BetterGenshinImpact/Model/Gear/GearTaskData.cs b/BetterGenshinImpact/Model/Gear/GearTaskData.cs index 5f122494..fae54cbf 100644 --- a/BetterGenshinImpact/Model/Gear/GearTaskData.cs +++ b/BetterGenshinImpact/Model/Gear/GearTaskData.cs @@ -1,3 +1,4 @@ +using BetterGenshinImpact.Core.Script.Group; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -24,6 +25,9 @@ public class GearTaskDefinitionData [JsonProperty("order")] public int Order { get; set; } = 0; + [JsonProperty("group_config")] + public ScriptGroupConfig GroupConfig { get; set; } = new(); + [JsonProperty("root_task")] public GearTaskData? RootTask { get; set; } } @@ -65,4 +69,4 @@ public class GearTaskData [JsonProperty("children")] public List Children { get; set; } = new(); -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs b/BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs index a1303f0f..497fd5e7 100644 --- a/BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs +++ b/BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs @@ -9,6 +9,7 @@ using BetterGenshinImpact.Model.Gear.Tasks; using BetterGenshinImpact.Model.Gear.Parameter; using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Script.Group; using BetterGenshinImpact.Service.GearTask; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -52,7 +53,7 @@ public class GearTaskConverter { _logger.LogInformation("开始转换任务定义: {TaskDefinitionName}", taskDefinition.Name); - var rootTask = await ConvertTaskDataAsync(taskDefinition.RootTask); + var rootTask = await ConvertTaskDataAsync(taskDefinition.RootTask, null, taskDefinition.GroupConfig); tasks.Add(rootTask); _logger.LogInformation("任务定义转换完成: {TaskDefinitionName}, 共 {TaskCount} 个任务", @@ -73,7 +74,7 @@ public class GearTaskConverter /// 任务数据 /// 父任务 /// 转换后的任务 - public async Task ConvertTaskDataAsync(GearTaskData taskData, BaseGearTask? parent = null) + public async Task ConvertTaskDataAsync(GearTaskData taskData, BaseGearTask? parent = null, ScriptGroupConfig? groupConfig = null) { if (taskData == null) { @@ -106,7 +107,7 @@ public class GearTaskConverter else { // 使用工厂创建具体的任务实例 - var preparedTaskData = PrepareTaskDataForExecution(taskData); + var preparedTaskData = PrepareTaskDataForExecution(taskData, groupConfig); task = await _taskFactory.CreateTaskAsync(preparedTaskData); task.Father = parent; @@ -123,7 +124,7 @@ public class GearTaskConverter { try { - var childTask = await ConvertTaskDataAsync(childData, task); + var childTask = await ConvertTaskDataAsync(childData, task, groupConfig); task.Children.Add(childTask); } catch (Exception ex) @@ -369,49 +370,54 @@ public class GearTaskConverter return result; } - private GearTaskData PrepareTaskDataForExecution(GearTaskData taskData) + private GearTaskData PrepareTaskDataForExecution(GearTaskData taskData, ScriptGroupConfig? groupConfig) { + if (string.Equals(taskData.TaskType, "Javascript", StringComparison.OrdinalIgnoreCase)) + { + var javascriptParameters = DeserializeJavascriptParams(taskData.Parameters); + javascriptParameters.PathingPartyConfig = groupConfig?.PathingConfig; + return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(javascriptParameters)); + } + + if (string.Equals(taskData.TaskType, "Shell", StringComparison.OrdinalIgnoreCase)) + { + if (groupConfig?.EnableShellConfig == true) + { + return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(groupConfig.ShellConfig)); + } + + return taskData; + } + if (!string.Equals(taskData.TaskType, "Pathing", StringComparison.OrdinalIgnoreCase)) { return taskData; } - var parameters = DeserializePathingParams(taskData.Parameters); - if (!string.IsNullOrWhiteSpace(parameters.Path) - && !TryExtractPathingRepoRelativePath(parameters.Path, out _)) + var pathingParameters = DeserializePathingParams(taskData.Parameters); + pathingParameters.PathingPartyConfig = groupConfig?.PathingConfig; + if (!string.IsNullOrWhiteSpace(pathingParameters.Path) + && !TryExtractPathingRepoRelativePath(pathingParameters.Path, out _)) { - return taskData; + return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters)); } if (string.IsNullOrWhiteSpace(taskData.Path)) { - return taskData; + return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters)); } if (TryExtractPathingRepoRelativePath(taskData.Path, out var repoRelativePath) && repoRelativePath.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { - parameters.Path = GetPathingExecutionFilePath(repoRelativePath); + pathingParameters.Path = GetPathingExecutionFilePath(repoRelativePath); } else { - parameters.Path = taskData.Path.Trim().TrimEnd('\\', '/'); + pathingParameters.Path = taskData.Path.Trim().TrimEnd('\\', '/'); } - return new GearTaskData - { - Name = taskData.Name, - TaskType = taskData.TaskType, - Path = taskData.Path, - IsEnabled = taskData.IsEnabled, - IsDirectory = taskData.IsDirectory, - IsExpanded = taskData.IsExpanded, - Parameters = JsonConvert.SerializeObject(parameters), - CreatedTime = taskData.CreatedTime, - ModifiedTime = taskData.ModifiedTime, - Priority = taskData.Priority, - Children = taskData.Children - }; + return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters)); } private PathingGearTaskParams DeserializePathingParams(string? parametersJson) @@ -431,6 +437,41 @@ public class GearTaskConverter } } + private JavascriptGearTaskParams DeserializeJavascriptParams(string? parametersJson) + { + if (string.IsNullOrWhiteSpace(parametersJson)) + { + return new JavascriptGearTaskParams(); + } + + try + { + return JsonConvert.DeserializeObject(parametersJson) ?? new JavascriptGearTaskParams(); + } + catch + { + return new JavascriptGearTaskParams(); + } + } + + private static GearTaskData BuildPreparedTaskData(GearTaskData taskData, string parametersJson) + { + return new GearTaskData + { + Name = taskData.Name, + TaskType = taskData.TaskType, + Path = taskData.Path, + IsEnabled = taskData.IsEnabled, + IsDirectory = taskData.IsDirectory, + IsExpanded = taskData.IsExpanded, + Parameters = parametersJson, + CreatedTime = taskData.CreatedTime, + ModifiedTime = taskData.ModifiedTime, + Priority = taskData.Priority, + Children = taskData.Children + }; + } + private static string BuildPathingPlaceholderPath(string repoRelativePath, bool isDirectory) { var normalized = repoRelativePath.Replace('/', Path.DirectorySeparatorChar); diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskStorageService.cs b/BetterGenshinImpact/Service/GearTask/GearTaskStorageService.cs index 4dc8a6c1..f39d06ed 100644 --- a/BetterGenshinImpact/Service/GearTask/GearTaskStorageService.cs +++ b/BetterGenshinImpact/Service/GearTask/GearTaskStorageService.cs @@ -185,6 +185,7 @@ public class GearTaskStorageService CreatedTime = viewModel.CreatedTime, ModifiedTime = viewModel.ModifiedTime, Order = viewModel.Order, + GroupConfig = viewModel.GroupConfig, RootTask = viewModel.RootTask != null ? ConvertTaskToData(viewModel.RootTask) : null }; } @@ -229,6 +230,7 @@ public class GearTaskStorageService CreatedTime = data.CreatedTime, ModifiedTime = data.ModifiedTime, Order = data.Order, + GroupConfig = data.GroupConfig ?? new(), RootTask = data.RootTask != null ? ConvertTaskToViewModel(data.RootTask) : null }; @@ -276,4 +278,4 @@ public class GearTaskStorageService var safeName = string.Join("_", name.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries)); return string.IsNullOrWhiteSpace(safeName) ? "unnamed_task" : safeName; } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/View/MainWindow.xaml b/BetterGenshinImpact/View/MainWindow.xaml index c92e2197..b1b20107 100644 --- a/BetterGenshinImpact/View/MainWindow.xaml +++ b/BetterGenshinImpact/View/MainWindow.xaml @@ -138,6 +138,13 @@ + + + + + - + + Icon="{ui:SymbolIcon Folder24}" + Margin="0,0,10,0" /> + diff --git a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml.cs b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml.cs index d7d2b2f3..2b55348f 100644 --- a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml.cs +++ b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml.cs @@ -1,5 +1,4 @@ -using System.Windows.Controls; -using BetterGenshinImpact.Core.Script.Group; +using System.Windows.Controls; using BetterGenshinImpact.ViewModel.Pages.View; namespace BetterGenshinImpact.View.Pages.View @@ -13,9 +12,8 @@ namespace BetterGenshinImpact.View.Pages.View public ScriptGroupConfigView(ScriptGroupConfigViewModel viewModel) { - DataContext = ViewModel = viewModel; + DataContext = ViewModel = viewModel; InitializeComponent(); - } } } diff --git a/BetterGenshinImpact/ViewModel/Pages/Component/GearTaskDefinitionViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/Component/GearTaskDefinitionViewModel.cs index d12f2901..d20fc1ac 100644 --- a/BetterGenshinImpact/ViewModel/Pages/Component/GearTaskDefinitionViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/Component/GearTaskDefinitionViewModel.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using BetterGenshinImpact.Core.Script.Group; using CommunityToolkit.Mvvm.ComponentModel; namespace BetterGenshinImpact.ViewModel.Pages.Component; @@ -39,6 +39,9 @@ public partial class GearTaskDefinitionViewModel : ObservableObject [ObservableProperty] private bool _hasExecutionBadge; + [ObservableProperty] + private ScriptGroupConfig _groupConfig = new(); + /// /// 任务根节点 /// diff --git a/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs index 3a0f97d1..ea2862d6 100644 --- a/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs @@ -1,24 +1,32 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Script; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.Helpers.Ui; using BetterGenshinImpact.Service; using BetterGenshinImpact.Service.GearTask; using BetterGenshinImpact.Service.GearTask.Execution; +using BetterGenshinImpact.View.Pages.View; using BetterGenshinImpact.View.Windows; using BetterGenshinImpact.View.Windows.GearTask; using BetterGenshinImpact.ViewModel.Pages.Component; +using BetterGenshinImpact.ViewModel.Pages.View; using BetterGenshinImpact.ViewModel.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; +using Wpf.Ui; using Wpf.Ui.Violeta.Controls; namespace BetterGenshinImpact.ViewModel.Pages; @@ -432,6 +440,37 @@ public partial class GearTaskListPageViewModel : ViewModel _taskExecutor.StopExecution(); } + [RelayCommand] + private async Task OpenSelectedTaskDefinitionSettings() + { + if (SelectedTaskDefinition == null) + { + Toast.Warning("请先选择一个任务定义"); + return; + } + + var dialogWindow = new Wpf.Ui.Controls.FluentWindow + { + Title = "任务组设置", + Content = new ScriptGroupConfigView(new ScriptGroupConfigViewModel(TaskContext.Instance().Config, SelectedTaskDefinition.GroupConfig)), + Width = 800, + Height = 600, + MinWidth = 800, + MaxWidth = 800, + MinHeight = 600, + Owner = Application.Current.MainWindow, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + ExtendsContentIntoTitleBar = true, + WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.Auto, + }; + dialogWindow.SourceInitialized += (_, _) => WindowHelper.TryApplySystemBackdrop(dialogWindow); + + dialogWindow.ShowDialog(); + + SelectedTaskDefinition.ModifiedTime = DateTime.Now; + await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); + } + [RelayCommand] private async Task AddTaskNode(string? taskType = null) { @@ -679,6 +718,11 @@ public partial class GearTaskListPageViewModel : ViewModel { SetupTaskPropertyChangeListener(taskDefinition.RootTask, taskDefinition); } + + if (taskDefinition.GroupConfig != null) + { + SetupTaskGroupConfigChangeListener(taskDefinition.GroupConfig, taskDefinition); + } } /// @@ -726,6 +770,115 @@ public partial class GearTaskListPageViewModel : ViewModel }; } + /// + /// 递归监听任务组配置及其子配置变化,实现自动保存。 + /// + private void SetupTaskGroupConfigChangeListener(object configObject, GearTaskDefinitionViewModel parentDefinition) + { + var visited = new HashSet(); + SubscribeTaskGroupConfigChanges(configObject, parentDefinition, visited); + } + + private void SubscribeTaskGroupConfigChanges(object? current, GearTaskDefinitionViewModel parentDefinition, HashSet visited) + { + if (current == null || !visited.Add(current)) + { + return; + } + + if (current is INotifyPropertyChanged notifyPropertyChanged) + { + notifyPropertyChanged.PropertyChanged += async (_, _) => + { + try + { + parentDefinition.ModifiedTime = DateTime.Now; + await _storageService.SaveTaskDefinitionAsync(parentDefinition); + } + catch (Exception ex) + { + _logger.LogError(ex, "任务组配置自动保存失败: {TaskName}", parentDefinition.Name); + } + }; + } + + if (current is INotifyCollectionChanged notifyCollectionChanged) + { + notifyCollectionChanged.CollectionChanged += async (_, e) => + { + if (e.NewItems != null) + { + foreach (var item in e.NewItems) + { + SubscribeTaskGroupConfigChanges(item, parentDefinition, visited); + } + } + + try + { + parentDefinition.ModifiedTime = DateTime.Now; + await _storageService.SaveTaskDefinitionAsync(parentDefinition); + } + catch (Exception ex) + { + _logger.LogError(ex, "任务组配置自动保存失败: {TaskName}", parentDefinition.Name); + } + }; + } + + foreach (var child in EnumerateConfigChildren(current)) + { + SubscribeTaskGroupConfigChanges(child, parentDefinition, visited); + } + } + + private static IEnumerable EnumerateConfigChildren(object current) + { + var properties = current.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (var property in properties) + { + if (!property.CanRead || property.GetIndexParameters().Length > 0) + { + continue; + } + + if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) + { + continue; + } + + object? value; + try + { + value = property.GetValue(current); + } + catch + { + continue; + } + + if (value == null || value is string) + { + continue; + } + + if (value is IEnumerable enumerable) + { + foreach (var item in enumerable) + { + if (item != null && item is not string) + { + yield return item; + } + } + + continue; + } + + yield return value; + } + } + private async Task RefreshTaskDefinitionExecutionSummariesAsync() { foreach (var taskDefinition in TaskDefinitions)