diff --git a/BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml b/BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml new file mode 100644 index 00000000..bef0d7e2 --- /dev/null +++ b/BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml.cs b/BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml.cs new file mode 100644 index 00000000..31193b7c --- /dev/null +++ b/BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml.cs @@ -0,0 +1,52 @@ +using System; +using System.Windows; +using BetterGenshinImpact.Helpers.Ui; +using BetterGenshinImpact.ViewModel.Windows; + +namespace BetterGenshinImpact.View.Windows; + +public partial class AddTaskNodeDialog +{ + public AddTaskNodeDialogViewModel ViewModel { get; } + + public AddTaskNodeDialog(string taskType) + { + ViewModel = new AddTaskNodeDialogViewModel(taskType); + DataContext = ViewModel; + + InitializeComponent(); + + ViewModel.RequestClose += OnRequestClose; + this.SourceInitialized += OnSourceInitialized; + } + + private void OnSourceInitialized(object? sender, EventArgs e) + { + // 应用与主窗口相同的背景主题 + WindowHelper.TryApplySystemBackdrop(this); + } + + private void OnRequestClose(object? sender, bool result) + { + DialogResult = result; + Close(); + } + + /// + /// 显示添加任务对话框 + /// + /// 任务类型 + /// 父窗口 + /// 如果用户点击确定返回ViewModel,否则返回null + public static AddTaskNodeDialogViewModel? ShowDialog(string taskType, Window? owner = null) + { + var dialog = new AddTaskNodeDialog(taskType); + if (owner != null) + { + dialog.Owner = owner; + } + + var result = dialog.ShowDialog(); + return result == true ? dialog.ViewModel : null; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml b/BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml new file mode 100644 index 00000000..741df141 --- /dev/null +++ b/BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml.cs b/BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml.cs new file mode 100644 index 00000000..aab74a08 --- /dev/null +++ b/BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml.cs @@ -0,0 +1,40 @@ +using System.Windows; +using BetterGenshinImpact.ViewModel.Windows; +using Wpf.Ui.Controls; + +namespace BetterGenshinImpact.View.Windows; + +public partial class JsScriptSelectionWindow : FluentWindow +{ + public JsScriptSelectionViewModel ViewModel { get; } + + public JsScriptInfo? SelectedScript => ViewModel.SelectedScript; + + public bool DialogResult { get; private set; } + + public JsScriptSelectionWindow() + { + ViewModel = new JsScriptSelectionViewModel(); + DataContext = ViewModel; + InitializeComponent(); + } + + private void OnOkClick(object sender, RoutedEventArgs e) + { + if (ViewModel.SelectedScript != null) + { + DialogResult = true; + Close(); + } + else + { + Wpf.Ui.Violeta.Controls.Toast.Warning("请选择一个JS脚本"); + } + } + + private void OnCancelClick(object sender, RoutedEventArgs e) + { + DialogResult = false; + Close(); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs index 1700022f..f777b19c 100644 --- a/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/GearTaskListPageViewModel.cs @@ -2,13 +2,13 @@ using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; -using System.Windows; using System.Linq; using System; using BetterGenshinImpact.ViewModel.Pages.Component; using BetterGenshinImpact.Service; using System.Threading.Tasks; using System.Collections.Specialized; +using System.Windows; using BetterGenshinImpact.View.Windows; using BetterGenshinImpact.ViewModel.Windows; using Wpf.Ui.Violeta.Controls; @@ -26,36 +26,32 @@ public partial class GearTaskListPageViewModel : ViewModel /// /// 任务定义列表(左侧) /// - [ObservableProperty] - private ObservableCollection _taskDefinitions = new(); + [ObservableProperty] private ObservableCollection _taskDefinitions = new(); /// /// 当前选中的任务定义 /// - [ObservableProperty] - private GearTaskDefinitionViewModel? _selectedTaskDefinition; + [ObservableProperty] private GearTaskDefinitionViewModel? _selectedTaskDefinition; /// /// 当前任务树根节点(右侧) /// - [ObservableProperty] - private GearTaskViewModel _currentTaskTreeRoot = new(); + [ObservableProperty] private GearTaskViewModel _currentTaskTreeRoot = new(); /// /// 当前选中的任务节点 /// - [ObservableProperty] - private GearTaskViewModel? _selectedTaskNode; - + [ObservableProperty] private GearTaskViewModel? _selectedTaskNode; + public GearTaskListPageViewModel(ILogger logger, GearTaskStorageService storageService) { _logger = logger; _storageService = storageService; InitializeData(); - + // 监听集合变化,实现自动保存 TaskDefinitions.CollectionChanged += OnTaskDefinitionsChanged; - + // 监听当前任务树根节点的子集合变化,用于拖拽后自动保存 CurrentTaskTreeRoot.Children.CollectionChanged += OnCurrentTaskTreeChanged; } @@ -77,13 +73,13 @@ public partial class GearTaskListPageViewModel : ViewModel TaskDefinitions[i].ModifiedTime = DateTime.Now; } } - + // 保存所有受影响的任务定义 foreach (var taskDef in TaskDefinitions) { await _storageService.SaveTaskDefinitionAsync(taskDef); } - + _logger.LogInformation("任务定义列表顺序已更新并保存"); } catch (Exception ex) @@ -91,7 +87,7 @@ public partial class GearTaskListPageViewModel : ViewModel _logger.LogError(ex, "保存任务定义列表顺序时发生错误"); } } - + /// /// 当前任务树集合变化时的处理(用于拖拽后自动保存) /// @@ -121,17 +117,17 @@ public partial class GearTaskListPageViewModel : ViewModel { // 从 JSON 文件加载任务定义 var loadedTasks = await _storageService.LoadAllTaskDefinitionsAsync(); - + // 按order字段排序 var sortedTasks = loadedTasks.OrderBy(t => t.Order).ToList(); - + foreach (var task in sortedTasks) { TaskDefinitions.Add(task); // 为每个任务定义设置属性变化监听 SetupTaskDefinitionPropertyChanged(task); } - + // 如果没有加载到任何任务,创建一个示例任务 if (TaskDefinitions.Count == 0) { @@ -145,7 +141,7 @@ public partial class GearTaskListPageViewModel : ViewModel await CreateSampleTaskAsync(); } } - + /// /// 创建示例任务 /// @@ -156,16 +152,16 @@ public partial class GearTaskListPageViewModel : ViewModel { sampleTask.RootTask.AddChild(new GearTaskViewModel("采集任务1") { TaskType = "采集任务", Description = "采集莲花" }); sampleTask.RootTask.AddChild(new GearTaskViewModel("战斗任务1") { TaskType = "战斗任务", Description = "击败史莱姆" }); - + var subGroup = new GearTaskViewModel("子任务组", true); subGroup.AddChild(new GearTaskViewModel("传送任务1") { TaskType = "传送任务", Description = "传送到蒙德" }); subGroup.AddChild(new GearTaskViewModel("交互任务1") { TaskType = "交互任务", Description = "与NPC对话" }); sampleTask.RootTask.AddChild(subGroup); } - + TaskDefinitions.Add(sampleTask); SetupTaskDefinitionPropertyChanged(sampleTask); - + // 保存示例任务到文件 await _storageService.SaveTaskDefinitionAsync(sampleTask); } @@ -180,16 +176,16 @@ public partial class GearTaskListPageViewModel : ViewModel { task.IsSelected = false; } - + // 设置当前选中项 if (value != null) { value.IsSelected = true; } - + // 先解除之前的事件绑定 CurrentTaskTreeRoot.Children.CollectionChanged -= OnCurrentTaskTreeChanged; - + // 设置当前任务树根节点 if (value?.RootTask != null) { @@ -199,7 +195,7 @@ public partial class GearTaskListPageViewModel : ViewModel { CurrentTaskTreeRoot = new GearTaskViewModel(); } - + // 重新绑定事件 CurrentTaskTreeRoot.Children.CollectionChanged += OnCurrentTaskTreeChanged; } @@ -221,17 +217,17 @@ public partial class GearTaskListPageViewModel : ViewModel { var editViewModel = App.GetService(); if (editViewModel == null) return; - + editViewModel.Name = $"新任务组{TaskDefinitions.Count + 1}"; editViewModel.Description = ""; - + var editWindow = App.GetService(); if (editWindow == null) return; - + editWindow.ViewModel.Name = editViewModel.Name; editWindow.ViewModel.Description = editViewModel.Description; editWindow.Owner = Application.Current.MainWindow; - + if (editWindow.ShowDialog() == true) { var newTask = new GearTaskDefinitionViewModel(editWindow.ViewModel.Name, editWindow.ViewModel.Description); @@ -240,7 +236,7 @@ public partial class GearTaskListPageViewModel : ViewModel TaskDefinitions.Add(newTask); SetupTaskDefinitionPropertyChanged(newTask); SelectedTaskDefinition = newTask; - + // 自动保存到文件 await _storageService.SaveTaskDefinitionAsync(newTask); } @@ -253,10 +249,10 @@ public partial class GearTaskListPageViewModel : ViewModel private async Task DeleteTaskDefinition(GearTaskDefinitionViewModel? taskDefinition) { if (taskDefinition == null) return; - - var result = MessageBox.Show($"确定要删除任务定义 '{taskDefinition.Name}' 吗?", "确认删除", + + var result = MessageBox.Show($"确定要删除任务定义 '{taskDefinition.Name}' 吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question); - + if (result == MessageBoxResult.Yes) { var taskName = taskDefinition.Name; @@ -265,7 +261,7 @@ public partial class GearTaskListPageViewModel : ViewModel { SelectedTaskDefinition = TaskDefinitions.FirstOrDefault(); } - + // 删除对应的 JSON 文件 await _storageService.DeleteTaskDefinitionAsync(taskName); } @@ -278,29 +274,29 @@ public partial class GearTaskListPageViewModel : ViewModel private async Task EditSelectedTaskDefinition() { if (SelectedTaskDefinition == null) return; - + var editViewModel = App.GetService(); if (editViewModel == null) return; - + editViewModel.Name = SelectedTaskDefinition.Name; editViewModel.Description = SelectedTaskDefinition.Description; - + var editWindow = App.GetService(); if (editWindow == null) return; - + editWindow.ViewModel.Name = editViewModel.Name; editWindow.ViewModel.Description = editViewModel.Description; editWindow.Owner = Application.Current.MainWindow; - + if (editWindow.ShowDialog() == true) { SelectedTaskDefinition.Name = editWindow.ViewModel.Name; SelectedTaskDefinition.Description = editWindow.ViewModel.Description; SelectedTaskDefinition.ModifiedTime = DateTime.Now; - + // 自动保存到文件 await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); - + _logger.LogInformation("编辑了任务定义: {Name}", SelectedTaskDefinition.Name); } } @@ -312,12 +308,11 @@ public partial class GearTaskListPageViewModel : ViewModel private async Task DeleteSelectedTaskDefinition() { if (SelectedTaskDefinition == null) return; - + await DeleteTaskDefinition(SelectedTaskDefinition); } - /// /// 添加任务节点 /// @@ -330,22 +325,59 @@ public partial class GearTaskListPageViewModel : ViewModel return; } - var newTask = new GearTaskViewModel($"新任务 {DateTime.Now:HHmmss}") + // 如果没有指定任务类型,默认为Javascript + taskType ??= "Javascript"; + + GearTaskViewModel newTask; + + // 如果是JS脚本类型,使用JS脚本选择窗口 + if (taskType == "Javascript") { - TaskType = "任务类型", - Description = "新创建的任务" - }; + var jsSelectionWindow = new JsScriptSelectionWindow + { + Owner = Application.Current.MainWindow + }; + + if (jsSelectionWindow.ShowDialog() == true && jsSelectionWindow.ViewModel.SelectedScript != null) + { + var selectedScript = jsSelectionWindow.ViewModel.SelectedScript; + newTask = new GearTaskViewModel(selectedScript.DisplayName) + { + TaskType = "Javascript", + Description = selectedScript.Description ?? "JS脚本任务" + }; + } + else + { + return; // 用户取消了操作 + } + } + else + { + // 其他类型使用原有的对话框 + var dialogResult = AddTaskNodeDialog.ShowDialog(taskType, Application.Current.MainWindow); + if (dialogResult == null) + { + return; // 用户取消了操作 + } + + newTask = new GearTaskViewModel(dialogResult.TaskName) + { + TaskType = dialogResult.TaskType, + Description = dialogResult.TaskDescription + }; + } // 如果有选中的节点,则在选中节点下新增 // 如果未选择节点,则在根节点下直接新增 var targetParent = SelectedTaskNode ?? SelectedTaskDefinition.RootTask; targetParent.AddChild(newTask); - + // 展开父节点 targetParent.IsExpanded = true; SelectedTaskDefinition.ModifiedTime = DateTime.Now; - + // 自动保存到文件 await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } @@ -371,12 +403,12 @@ public partial class GearTaskListPageViewModel : ViewModel // 如果未选择节点,则在根节点下直接新增 var targetParent = SelectedTaskNode ?? SelectedTaskDefinition.RootTask; targetParent.AddChild(newGroup); - + // 展开父节点 targetParent.IsExpanded = true; SelectedTaskDefinition.ModifiedTime = DateTime.Now; - + // 自动保存到文件 await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } @@ -389,14 +421,14 @@ public partial class GearTaskListPageViewModel : ViewModel { if (taskNode == null || SelectedTaskDefinition?.RootTask == null) return; - var result = MessageBox.Show($"确定要删除任务 '{taskNode.Name}' 吗?", "确认删除", + var result = MessageBox.Show($"确定要删除任务 '{taskNode.Name}' 吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question); - + if (result == MessageBoxResult.Yes) { RemoveTaskFromTree(SelectedTaskDefinition.RootTask, taskNode); SelectedTaskDefinition.ModifiedTime = DateTime.Now; - + // 自动保存到文件 await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } @@ -433,13 +465,13 @@ public partial class GearTaskListPageViewModel : ViewModel { TaskDefinitions.Clear(); var loadedTasks = await _storageService.LoadAllTaskDefinitionsAsync(); - + foreach (var task in loadedTasks) { TaskDefinitions.Add(task); SetupTaskDefinitionPropertyChanged(task); } - + _logger.LogInformation("从JSON文件重新加载了 {Count} 个任务定义", loadedTasks.Count); } catch (Exception ex) @@ -467,7 +499,7 @@ public partial class GearTaskListPageViewModel : ViewModel } } }; - + // 为根任务及其所有子任务设置监听器 if (taskDefinition.RootTask != null) { @@ -492,13 +524,13 @@ public partial class GearTaskListPageViewModel : ViewModel _logger.LogError(ex, "自动保存任务定义 {TaskName} 时发生错误", parentDefinition.Name); } }; - + // 为子任务设置监听器 foreach (var child in task.Children) { SetupTaskPropertyChangeListener(child, parentDefinition); } - + // 监听子任务集合变化 task.Children.CollectionChanged += async (sender, e) => { @@ -509,7 +541,7 @@ public partial class GearTaskListPageViewModel : ViewModel SetupTaskPropertyChangeListener(newTask, parentDefinition); } } - + // 任何集合变化都触发保存(包括拖拽重排序) try { @@ -527,5 +559,4 @@ public partial class GearTaskListPageViewModel : ViewModel /// /// 刷新当前任务树显示 /// - } \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Windows/AddTaskNodeDialogViewModel.cs b/BetterGenshinImpact/ViewModel/Windows/AddTaskNodeDialogViewModel.cs new file mode 100644 index 00000000..792ce1e7 --- /dev/null +++ b/BetterGenshinImpact/ViewModel/Windows/AddTaskNodeDialogViewModel.cs @@ -0,0 +1,67 @@ +using System; +using System.ComponentModel.DataAnnotations; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace BetterGenshinImpact.ViewModel.Windows; + +public partial class AddTaskNodeDialogViewModel : ObservableValidator +{ + [ObservableProperty] + [Required(ErrorMessage = "任务名称不能为空")] + private string _taskName = ""; + + [ObservableProperty] + private string _taskDescription = ""; + + [ObservableProperty] + private string _taskType = ""; + + public event EventHandler? RequestClose; + + public AddTaskNodeDialogViewModel() + { + } + + public AddTaskNodeDialogViewModel(string taskType) + { + TaskType = taskType; + TaskName = GetDefaultTaskName(taskType); + } + + private string GetDefaultTaskName(string taskType) + { + return taskType switch + { + "Javascript" => "新建JS脚本", + "Pathing" => "新建地图追踪任务", + "KeyMouse" => "新建键鼠脚本", + "Shell" => "新建Shell脚本", + "CSharp" => "新建C#方法", + _ => "新建任务" + }; + } + + [RelayCommand] + private void Confirm() + { + ValidateAllProperties(); + if (HasErrors) + { + return; + } + + if (string.IsNullOrWhiteSpace(TaskName)) + { + return; + } + + RequestClose?.Invoke(this, true); + } + + [RelayCommand] + private void Cancel() + { + RequestClose?.Invoke(this, false); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Windows/JsScriptSelectionViewModel.cs b/BetterGenshinImpact/ViewModel/Windows/JsScriptSelectionViewModel.cs new file mode 100644 index 00000000..06e3916f --- /dev/null +++ b/BetterGenshinImpact/ViewModel/Windows/JsScriptSelectionViewModel.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using BetterGenshinImpact.Core.Script; +using BetterGenshinImpact.Core.Script.Project; +using BetterGenshinImpact.ViewModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Extensions.Logging; + +namespace BetterGenshinImpact.ViewModel.Windows; + +public partial class JsScriptSelectionViewModel : ViewModel +{ + private readonly ILogger _logger = App.GetLogger(); + + [ObservableProperty] + private ObservableCollection _jsScripts = []; + + [ObservableProperty] + private JsScriptInfo? _selectedScript; + + [ObservableProperty] + private string _readmeContent = string.Empty; + + [ObservableProperty] + private string _mainJsContent = string.Empty; + + [ObservableProperty] + private int _selectedTabIndex = 0; + + public JsScriptSelectionViewModel() + { + LoadJsScripts(); + } + + private void LoadJsScripts() + { + try + { + var jsPath = Path.Combine(ScriptRepoUpdater.CenterRepoPath, "repo", "js"); + if (!Directory.Exists(jsPath)) + { + _logger.LogWarning($"JS脚本目录不存在: {jsPath}"); + return; + } + + var scriptDirectories = Directory.GetDirectories(jsPath); + var scripts = new ObservableCollection(); + + foreach (var scriptDir in scriptDirectories) + { + try + { + var manifestPath = Path.Combine(scriptDir, "manifest.json"); + if (File.Exists(manifestPath)) + { + var manifestContent = File.ReadAllText(manifestPath); + var manifest = Manifest.FromJson(manifestContent); + + var scriptInfo = new JsScriptInfo + { + FolderName = Path.GetFileName(scriptDir), + FolderPath = scriptDir, + Manifest = manifest + }; + + scripts.Add(scriptInfo); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, $"加载JS脚本失败: {scriptDir}"); + } + } + + JsScripts = scripts; + } + catch (Exception ex) + { + _logger.LogError(ex, "加载JS脚本列表失败"); + } + } + + partial void OnSelectedScriptChanged(JsScriptInfo? value) + { + if (value != null) + { + // 清空之前的内容 + ReadmeContent = string.Empty; + MainJsContent = string.Empty; + + // 根据当前选中的标签页加载内容 + LoadCurrentTabContent(); + } + } + + partial void OnSelectedTabIndexChanged(int value) + { + if (SelectedScript != null) + { + LoadCurrentTabContent(); + } + } + + private async void LoadCurrentTabContent() + { + if (SelectedScript == null) return; + + switch (SelectedTabIndex) + { + case 0: // README.md + if (string.IsNullOrEmpty(ReadmeContent)) + { + await Task.Run(LoadReadmeContent); + } + break; + case 1: // main.js + if (string.IsNullOrEmpty(MainJsContent)) + { + await Task.Run(LoadMainJsContent); + } + break; + } + } + + private void LoadReadmeContent() + { + if (SelectedScript == null) return; + + try + { + var readmePath = Path.Combine(SelectedScript.FolderPath, "README.md"); + if (File.Exists(readmePath)) + { + ReadmeContent = File.ReadAllText(readmePath); + } + else + { + ReadmeContent = "README.md 文件不存在"; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "加载README.md失败"); + ReadmeContent = $"加载README.md失败: {ex.Message}"; + } + } + + private void LoadMainJsContent() + { + if (SelectedScript == null) return; + + try + { + var mainJsPath = Path.Combine(SelectedScript.FolderPath, SelectedScript.Manifest.Main); + if (File.Exists(mainJsPath)) + { + MainJsContent = File.ReadAllText(mainJsPath); + } + else + { + MainJsContent = "main.js 文件不存在"; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "加载main.js失败"); + MainJsContent = $"加载main.js失败: {ex.Message}"; + } + } + + [RelayCommand] + private void RefreshScripts() + { + LoadJsScripts(); + } +} + +public class JsScriptInfo +{ + public string FolderName { get; set; } = string.Empty; + public string FolderPath { get; set; } = string.Empty; + public Manifest Manifest { get; set; } = new(); + + public string DisplayName => $"{FolderName} - {Manifest.Name}"; + public string Description => Manifest.ShortDescription; +} \ No newline at end of file