diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 2d774e9..6a8648f 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -1,6 +1,7 @@ name: Build Project on: + workflow_dispatch: push: branches: [ main ] pull_request: diff --git a/Actions/ToggleWorkflowAction.cs b/Actions/ToggleWorkflowAction.cs new file mode 100644 index 0000000..3dac4fb --- /dev/null +++ b/Actions/ToggleWorkflowAction.cs @@ -0,0 +1,177 @@ +using ClassIsland.Core.Abstractions.Automation; +using ClassIsland.Core.Abstractions.Services; +using ClassIsland.Core.Attributes; +using ClassIsland.Shared; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using SystemTools.Settings; +using Workflow = ClassIsland.Core.Models.Automation.Workflow; + +namespace SystemTools.Actions; + +[ActionInfo("SystemTools.ToggleWorkflow", "开关自动化", "\uE9A8", false)] +public class ToggleWorkflowAction(ILogger logger) : ActionBase +{ + private readonly ILogger _logger = logger; + + private static readonly ConcurrentDictionary PreviousSnapshots = new(); + + protected override async Task OnInvoke() + { + try + { + _logger.LogDebug("ToggleWorkflowAction OnInvoke 开始"); + + if (Settings == null) + { + _logger.LogWarning("设置为空,无法执行"); + return; + } + + var automationService = IAppHost.TryGetService(); + if (automationService?.Workflows == null) + { + _logger.LogError("无法获取自动化服务"); + throw new InvalidOperationException("无法获取自动化服务,请确保 ClassIsland 已正确加载。"); + } + + var targetWorkflow = FindTargetWorkflow(automationService); + if (targetWorkflow == null) + { + _logger.LogWarning("未找到目标自动化: Index={Index}, Name={Name}", + Settings.TargetWorkflowIndex, Settings.TargetWorkflowName); + throw new InvalidOperationException($"未找到指定的自动化方案: {Settings.TargetWorkflowName}"); + } + + var actionSet = targetWorkflow.ActionSet; + var currentStatus = actionSet.IsEnabled; + + if (IsRevertable) + { + PreviousSnapshots[ActionSet.Guid] = new OriginalStateSnapshot( + actionSet.Name, + automationService.Workflows.IndexOf(targetWorkflow), + currentStatus); + _logger.LogDebug("已保存自动化 \"{WorkflowName}\" 的原始状态快照", actionSet.Name); + } + + var (targetStatus, operationDescription) = Settings.EnableMode switch + { + true => (true, "启用"), + false => (false, "禁用"), + _ => (!currentStatus, !currentStatus ? "启用" : "禁用") + }; + + if (currentStatus == targetStatus) + { + _logger.LogInformation("自动化 \"{WorkflowName}\" 已经是{Operation}状态,无需操作", + actionSet.Name, operationDescription); + } + else + { + _logger.LogInformation("正在{Operation}自动化 \"{WorkflowName}\" (原始: {OriginalStatus} -> 目标: {TargetStatus})", + operationDescription, actionSet.Name, currentStatus, targetStatus); + + actionSet.IsEnabled = targetStatus; + automationService.SaveConfig($"通过行动{operationDescription}自动化 \"{actionSet.Name}\""); + + _logger.LogInformation("自动化 \"{WorkflowName}\" 已成功{Operation}", + actionSet.Name, operationDescription); + } + + await base.OnInvoke(); + _logger.LogDebug("ToggleWorkflowAction OnInvoke 完成"); + } + catch (Exception ex) + { + _logger.LogError(ex, "ToggleWorkflowAction 执行失败"); + throw; + } + } + + protected override async Task OnRevert() + { + try + { + await base.OnRevert(); + + if (!PreviousSnapshots.TryRemove(ActionSet.Guid, out var snapshot)) + { + _logger.LogWarning("未找到触发前状态,跳过恢复。ActionSet={ActionSetGuid}", ActionSet.Guid); + return; + } + + var automationService = IAppHost.TryGetService(); + if (automationService?.Workflows == null) + { + _logger.LogError("无法获取自动化服务,恢复失败"); + return; + } + + var targetWorkflow = FindTargetWorkflow(automationService); + if (targetWorkflow == null) + { + _logger.LogWarning("恢复时未找到目标自动化: {Name}", snapshot.WorkflowName); + return; + } + + var actionSet = targetWorkflow.ActionSet; + actionSet.IsEnabled = snapshot.IsEnabled; + automationService.SaveConfig($"通过行动恢复自动化 \"{actionSet.Name}\" 到原始状态({snapshot.IsEnabled})"); + + _logger.LogInformation("已恢复自动化 \"{WorkflowName}\" 为触发前状态。ActionSet={ActionSetGuid}", + actionSet.Name, ActionSet.Guid); + } + catch (Exception ex) + { + _logger.LogError(ex, "ToggleWorkflowAction 恢复失败"); + throw; + } + } + + private Workflow? FindTargetWorkflow(IAutomationService automationService) + { + Workflow? targetWorkflow = null; + + // 1. 尝试通过索引查找 + if (Settings.TargetWorkflowIndex >= 0 && Settings.TargetWorkflowIndex < automationService.Workflows.Count) + { + targetWorkflow = automationService.Workflows[Settings.TargetWorkflowIndex]; + _logger.LogDebug("通过索引 {Index} 找到自动化: {Name}", + Settings.TargetWorkflowIndex, targetWorkflow.ActionSet.Name); + } + + // 2. 如果索引找不到,尝试通过名称查找 + if (targetWorkflow == null && !string.IsNullOrEmpty(Settings.TargetWorkflowName)) + { + targetWorkflow = automationService.Workflows + .FirstOrDefault(w => w.ActionSet.Name == Settings.TargetWorkflowName); + + if (targetWorkflow != null) + { + _logger.LogDebug("通过名称 \"{Name}\" 找到自动化", Settings.TargetWorkflowName); + Settings.TargetWorkflowIndex = automationService.Workflows.IndexOf(targetWorkflow); + } + else + { + _logger.LogWarning("通过名称 \"{Name}\" 未找到自动化", Settings.TargetWorkflowName); + } + } + + if (targetWorkflow == null) + { + _logger.LogWarning("未能找到任何匹配的目标自动化 (Index={Index}, Name={Name})", + Settings.TargetWorkflowIndex, Settings.TargetWorkflowName); + } + + return targetWorkflow; + } + + private readonly record struct OriginalStateSnapshot( + string WorkflowName, + int WorkflowIndex, + bool IsEnabled); +} diff --git a/Controls/ToggleWorkflowSettingsControl.cs b/Controls/ToggleWorkflowSettingsControl.cs new file mode 100644 index 0000000..74e6ca6 --- /dev/null +++ b/Controls/ToggleWorkflowSettingsControl.cs @@ -0,0 +1,257 @@ +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Media; +using ClassIsland.Core; +using ClassIsland.Core.Abstractions.Controls; +using ClassIsland.Core.Abstractions.Services; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using SystemTools.Settings; +using ClassIsland.Shared; +using Workflow = ClassIsland.Core.Models.Automation.Workflow; + +namespace SystemTools.Controls; + +/// +/// 切换自动化启用状态的设置控件 +/// +public class ToggleWorkflowSettingsControl : ActionSettingsControlBase +{ + private ComboBox _workflowComboBox; + private ComboBox _modeComboBox; + private CheckBox _revertCheckBox; + private ObservableCollection _workflows = []; + private TextBlock _infoTextBlock; + + public ToggleWorkflowSettingsControl() + { + var panel = new StackPanel { Spacing = 10, Margin = new(10) }; + + // 标题 + panel.Children.Add(new TextBlock + { + Text = "切换自动化状态", + FontWeight = FontWeight.Bold, + FontSize = 14 + }); + + // 说明文字 + panel.Children.Add(new TextBlock + { + Text = "选择一个自动化方案并设置要执行的操作。当触发器支持恢复时,可以自动还原状态。", + TextWrapping = TextWrapping.Wrap, + Opacity = 0.8 + }); + + // 自动化选择 + panel.Children.Add(new TextBlock + { + Text = "目标自动化:", + Margin = new(0, 10, 0, 0) + }); + + _workflowComboBox = new ComboBox + { + PlaceholderText = "请选择自动化方案", + HorizontalAlignment = HorizontalAlignment.Stretch + }; + panel.Children.Add(_workflowComboBox); + + // 操作模式选择 + panel.Children.Add(new TextBlock + { + Text = "操作模式:", + Margin = new(0, 10, 0, 0) + }); + + _modeComboBox = new ComboBox + { + HorizontalAlignment = HorizontalAlignment.Stretch + }; + _modeComboBox.Items.Add(new ComboBoxItem { Content = "切换(启用↔禁用)", Tag = null }); + _modeComboBox.Items.Add(new ComboBoxItem { Content = "启用", Tag = true }); + _modeComboBox.Items.Add(new ComboBoxItem { Content = "禁用", Tag = false }); + _modeComboBox.SelectedIndex = 0; + panel.Children.Add(_modeComboBox); + + // 恢复选项 + _revertCheckBox = new CheckBox + { + Content = "触发器恢复时自动还原原状态", + IsChecked = true, + Margin = new(0, 10, 0, 0) + }; + panel.Children.Add(_revertCheckBox); + + // 恢复说明 + panel.Children.Add(new TextBlock + { + Text = "提示:当触发器支持恢复(如\"上课时\"在放学时恢复),勾选此项会自动将自动化恢复到触发前的状态。", + TextWrapping = TextWrapping.Wrap, + FontSize = 12, + Opacity = 0.7, + Margin = new(0, 0, 0, 0) + }); + + // 信息提示区域 + _infoTextBlock = new TextBlock + { + TextWrapping = TextWrapping.Wrap, + Margin = new(0, 10, 0, 0), + IsVisible = false + }; + panel.Children.Add(_infoTextBlock); + + // 刷新按钮 + var refreshButton = new Button + { + Content = "刷新自动化列表", + HorizontalAlignment = HorizontalAlignment.Left, + Margin = new(0, 10, 0, 0) + }; + refreshButton.Click += (_, _) => LoadWorkflows(); + panel.Children.Add(refreshButton); + + Content = panel; + + // 加载自动化列表 + LoadWorkflows(); + } + + protected override void OnInitialized() + { + base.OnInitialized(); + + // 绑定选择变更事件 + _workflowComboBox.SelectionChanged += OnWorkflowSelectionChanged; + _modeComboBox.SelectionChanged += OnModeSelectionChanged; + _revertCheckBox.IsCheckedChanged += OnRevertCheckBoxChanged; + + // 恢复设置值 + RestoreSettings(); + } + + private void LoadWorkflows() + { + try + { + var automationService = IAppHost.TryGetService(); + if (automationService?.Workflows == null) + { + _infoTextBlock.Text = "无法获取自动化服务,请确保 ClassIsland 已正确加载。"; + _infoTextBlock.Foreground = Brushes.Orange; + _infoTextBlock.IsVisible = true; + return; + } + + _workflows = automationService.Workflows; + _workflowComboBox.Items.Clear(); + + foreach (var workflow in _workflows) + { + var actionSet = workflow.ActionSet; + var statusText = actionSet.IsEnabled ? "[已启用]" : "[已禁用]"; + var item = new ComboBoxItem + { + Content = $"{actionSet.Name} {statusText}", + Tag = workflow + }; + _workflowComboBox.Items.Add(item); + } + + if (_workflowComboBox.Items.Count == 0) + { + _workflowComboBox.PlaceholderText = "暂无自动化方案"; + _infoTextBlock.Text = "当前配置文件中没有任何自动化方案,请先创建自动化。"; + _infoTextBlock.Foreground = Brushes.Gray; + _infoTextBlock.IsVisible = true; + } + else + { + _infoTextBlock.IsVisible = false; + } + + // 恢复之前的选择 + RestoreSettings(); + } + catch (Exception ex) + { + _infoTextBlock.Text = $"加载自动化列表失败: {ex.Message}"; + _infoTextBlock.Foreground = Brushes.Red; + _infoTextBlock.IsVisible = true; + } + } + + private void RestoreSettings() + { + if (Settings == null) return; + + // 恢复自动化选择 + if (Settings.TargetWorkflowIndex >= 0 && Settings.TargetWorkflowIndex < _workflowComboBox.Items.Count) + { + _workflowComboBox.SelectedIndex = Settings.TargetWorkflowIndex; + } + else if (!string.IsNullOrEmpty(Settings.TargetWorkflowName)) + { + // 尝试通过名称查找 + for (int i = 0; i < _workflowComboBox.Items.Count; i++) + { + if (_workflowComboBox.Items[i] is ComboBoxItem item && + item.Tag is Workflow workflow && + workflow.ActionSet.Name == Settings.TargetWorkflowName) + { + _workflowComboBox.SelectedIndex = i; + break; + } + } + } + + // 恢复操作模式 + var modeIndex = Settings.EnableMode switch + { + null => 0, // 切换 + true => 1, // 启用 + false => 2 // 禁用 + }; + _modeComboBox.SelectedIndex = modeIndex; + + // 恢复复选框 + _revertCheckBox.IsChecked = Settings.RevertToOriginal; + } + + private void OnWorkflowSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (_workflowComboBox.SelectedItem is ComboBoxItem item && item.Tag is Workflow workflow) + { + Settings.TargetWorkflowName = workflow.ActionSet.Name; + Settings.TargetWorkflowIndex = _workflowComboBox.SelectedIndex; + + // 更新信息显示 + var status = workflow.ActionSet.IsEnabled ? "已启用" : "已禁用"; + _infoTextBlock.Text = $"当前状态: {status} | 行动组: {workflow.ActionSet.Name}"; + _infoTextBlock.Foreground = workflow.ActionSet.IsEnabled ? Brushes.Green : Brushes.Gray; + _infoTextBlock.IsVisible = true; + } + } + + private void OnModeSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (_modeComboBox.SelectedItem is ComboBoxItem item && item.Tag is bool mode) + { + Settings.EnableMode = mode; + } + else + { + Settings.EnableMode = null; // 切换模式 + } + } + + private void OnRevertCheckBoxChanged(object? sender, EventArgs e) + { + if (_revertCheckBox.IsChecked.HasValue) + { + Settings.RevertToOriginal = _revertCheckBox.IsChecked.Value; + } + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 58ac528..e0bd5b5 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,4 +1,4 @@ -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Threading; using AvaloniaEdit.Utils; using ClassIsland.Core; @@ -297,6 +297,8 @@ private void RegisterBaseActions(IServiceCollection services) RegisterActionIfEnabled(services, config, "SystemTools.OpenAppSettings"); RegisterActionIfEnabled(services, config, "SystemTools.OpenProfileEditor"); RegisterActionIfEnabled(services, config, "SystemTools.OpenClassSwapWindow"); + RegisterActionIfEnabled(services, config, + "SystemTools.ToggleWorkflow"); } private void RegisterBaseTriggers(IServiceCollection services) @@ -513,7 +515,7 @@ private void BuildBaseActionTree() if (HasAnyActionEnabled(config, "SystemTools.ClearAllNotifications", "SystemTools.RestartAsAdmin", "SystemTools.LoadTemporaryClassPlan", "SystemTools.OpenAppSettings", - "SystemTools.OpenProfileEditor", "SystemTools.OpenClassSwapWindow")) + "SystemTools.OpenProfileEditor", "SystemTools.OpenClassSwapWindow","SystemTools.ToggleWorkflow")) { IActionService.ActionMenuTree["SystemTools 行动"].Add(new ActionMenuTreeGroup("ClassIsland…", "\uE5CB")); BuildClassIslandMenu(config); @@ -851,6 +853,8 @@ private void BuildClassIslandMenu(MainConfigData config) items.Add(new ActionMenuTreeItem("SystemTools.OpenProfileEditor", "打开档案编辑", "\uE699")); if (config.IsActionEnabled("SystemTools.OpenClassSwapWindow")) items.Add(new ActionMenuTreeItem("SystemTools.OpenClassSwapWindow", "打开换课窗口", "\uE13B")); + if (config.IsActionEnabled("SystemTools.ToggleWorkflow")) + items.Add(new ActionMenuTreeItem("SystemTools.ToggleWorkflow", "开关自动化", "\uE9A8")); if (items.Count > 0) { diff --git a/Settings/ToggleWorkflowSettings.cs b/Settings/ToggleWorkflowSettings.cs new file mode 100644 index 0000000..41c55eb --- /dev/null +++ b/Settings/ToggleWorkflowSettings.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace SystemTools.Settings; + +/// +/// 切换自动化启用状态的设置 +/// +public class ToggleWorkflowSettings +{ + /// + /// 目标自动化的名称(用于显示) + /// + [JsonPropertyName("targetWorkflowName")] + public string TargetWorkflowName { get; set; } = string.Empty; + + /// + /// 目标自动化在列表中的索引 + /// + [JsonPropertyName("targetWorkflowIndex")] + public int TargetWorkflowIndex { get; set; } = -1; + + /// + /// 操作模式:true=启用, false=禁用, null=切换 + /// + [JsonPropertyName("enableMode")] + public bool? EnableMode { get; set; } = null; + + /// + /// 是否在触发器恢复时自动恢复原状态 + /// + [JsonPropertyName("revertToOriginal")] + public bool RevertToOriginal { get; set; } = true; +} diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index 8d480a7..b39978b 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Net.Http; using System.Security.Cryptography; using Avalonia.Threading; @@ -212,6 +212,7 @@ public void InitializeFeatureItems() ("SystemTools.OpenAppSettings", "打开应用设置", "ClassIsland"), ("SystemTools.OpenProfileEditor", "打开档案编辑", "ClassIsland"), ("SystemTools.OpenClassSwapWindow", "打开换课窗口", "ClassIsland"), + ("SystemTools.ToggleWorkflow", "开关自动化", "ClassIsland"), }; if (Settings.EnableFloatingWindowFeature)