From 6f87a0c4d0ad2277af8671998cb872d2c8d29117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 17 Jun 2025 03:13:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=84=9A=E6=9C=AC=E4=BB=93=E5=BA=93V2=20(#1707?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add custom drawer control and integrate it into the UI * 更新仓库UI * feat: implement Git-based repository update mechanism and improve error handling * feat: add reset repository functionality with confirmation dialog * 修改打开队伍配置界面的重试次数和日志 * feat: add drawer open/close events and improve drawer closing logic * feat: enhance WebpagePanel navigation handling and improve initialization logic * feat: add drawer opened event handling and improve navigation completion logic * feat: implement dynamic height adjustment for WebpagePanel using Grid container * feat: update drawer dimensions and apply dynamic sizing based on position * feat: add CustomDrawer component and integrate with MapPathingViewModel for enhanced navigation * feat: integrate WebView2 for Markdown file navigation in MapPathingViewModel --- BetterGenshinImpact/App.xaml | 2 + .../BetterGenshinImpact.csproj | 1 + .../Core/Script/ScriptRepoUpdater.cs | 334 +++++++++----- .../Core/Script/WebView/RepoWebBridge.cs | 9 - .../GameTask/Common/Job/SwitchPartyTask.cs | 9 +- .../View/Controls/Drawer/CustomDrawer.cs | 410 ++++++++++++++++++ .../View/Controls/Drawer/DrawerStyles.xaml | 27 ++ .../View/Controls/Drawer/DrawerViewModel.cs | 71 +++ .../View/Controls/Webview/WebpagePanel.cs | 42 +- .../View/Pages/JsListPage.xaml | 235 +++++----- .../View/Pages/MapPathingPage.xaml | 220 ++++++---- .../View/Windows/ScriptRepoWindow.xaml | 86 ++++ .../View/Windows/ScriptRepoWindow.xaml.cs | 126 ++++++ .../ViewModel/MainWindowViewModel.cs | 2 +- .../ViewModel/Pages/JsListViewModel.cs | 231 +++++++++- .../Pages/KeyMouseRecordPageViewModel.cs | 2 +- .../ViewModel/Pages/MapPathingViewModel.cs | 240 +++++++++- .../ViewModel/Pages/ScriptControlViewModel.cs | 2 +- .../Pages/View/AutoFightViewModel.cs | 2 +- 19 files changed, 1714 insertions(+), 337 deletions(-) create mode 100644 BetterGenshinImpact/View/Controls/Drawer/CustomDrawer.cs create mode 100644 BetterGenshinImpact/View/Controls/Drawer/DrawerStyles.xaml create mode 100644 BetterGenshinImpact/View/Controls/Drawer/DrawerViewModel.cs create mode 100644 BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml create mode 100644 BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml.cs diff --git a/BetterGenshinImpact/App.xaml b/BetterGenshinImpact/App.xaml index 3aedc8a1..78242db4 100644 --- a/BetterGenshinImpact/App.xaml +++ b/BetterGenshinImpact/App.xaml @@ -11,7 +11,9 @@ + + /Assets/Fonts/MiSans-Regular.ttf#MiSans /Assets/Fonts/deluge-led.ttf#Deluge LED diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index ce9ed196..0988c397 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -48,6 +48,7 @@ + diff --git a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs index 1add05b0..3cbf7654 100644 --- a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs +++ b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs @@ -17,6 +17,8 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Windows; +using BetterGenshinImpact.View.Windows; +using LibGit2Sharp; using Vanara.PInvoke; using Wpf.Ui.Violeta.Controls; @@ -34,15 +36,15 @@ public class ScriptRepoUpdater : Singleton // 仓储临时目录 用于下载与解压 public static readonly string ReposTempPath = Path.Combine(ReposPath, "Temp"); - // 中央仓库信息地址 - public static readonly List CenterRepoInfoUrls = - [ - "https://raw.githubusercontent.com/babalae/bettergi-scripts-list/refs/heads/main/repo.json", - "https://r2-script.bettergi.com/github_mirror/repo.json", - ]; + // // 中央仓库信息地址 + // public static readonly List CenterRepoInfoUrls = + // [ + // "https://raw.githubusercontent.com/babalae/bettergi-scripts-list/refs/heads/main/repo.json", + // "https://r2-script.bettergi.com/github_mirror/repo.json", + // ]; // 中央仓库解压后文件夹名 - public static readonly string CenterRepoUnzipName = "bettergi-scripts-list-main"; + public static readonly string CenterRepoUnzipName = "bettergi-scripts-list-git"; public static readonly string CenterRepoPath = Path.Combine(ReposPath, CenterRepoUnzipName); @@ -56,103 +58,211 @@ public class ScriptRepoUpdater : Singleton private WebpageWindow? _webWindow; - public void AutoUpdate() + // [Obsolete] + // public void AutoUpdate() + // { + // var scriptConfig = TaskContext.Instance().Config.ScriptConfig; + // + // if (!Directory.Exists(ReposPath)) + // { + // Directory.CreateDirectory(ReposPath); + // } + // + // // 判断更新周期是否到达 + // if (DateTime.Now - scriptConfig.LastUpdateScriptRepoTime >= + // TimeSpan.FromDays(scriptConfig.AutoUpdateScriptRepoPeriod)) + // { + // // 更新仓库 + // Task.Run(async () => + // { + // try + // { + // var (repoPath, updated) = await UpdateCenterRepo(); + // Debug.WriteLine($"脚本仓库更新完成,路径:{repoPath}"); + // scriptConfig.LastUpdateScriptRepoTime = DateTime.Now; + // if (updated) + // { + // scriptConfig.ScriptRepoHintDotVisible = true; + // } + // } + // catch (Exception e) + // { + // _logger.LogDebug(e, $"脚本仓库更新失败:{e.Message}"); + // } + // }); + // } + // } + + + public async Task<(string, bool)> UpdateCenterRepoByGit(string repoUrl) { - var scriptConfig = TaskContext.Instance().Config.ScriptConfig; - - if (!Directory.Exists(ReposPath)) + if (string.IsNullOrEmpty(repoUrl)) { - Directory.CreateDirectory(ReposPath); + throw new ArgumentException("仓库URL不能为空", nameof(repoUrl)); } - // 判断更新周期是否到达 - if (DateTime.Now - scriptConfig.LastUpdateScriptRepoTime >= TimeSpan.FromDays(scriptConfig.AutoUpdateScriptRepoPeriod)) - { - // 更新仓库 - Task.Run(async () => - { - try - { - var (repoPath, updated) = await UpdateCenterRepo(); - Debug.WriteLine($"脚本仓库更新完成,路径:{repoPath}"); - scriptConfig.LastUpdateScriptRepoTime = DateTime.Now; - if (updated) - { - scriptConfig.ScriptRepoHintDotVisible = true; - } - } - catch (Exception e) - { - _logger.LogDebug(e, $"脚本仓库更新失败:{e.Message}"); - } - }); - } - } - - public async Task<(string, bool)> UpdateCenterRepo() - { - // 测速并获取信息 - var (fastUrl, jsonString) = await ProxySpeedTester.GetFastestUrlAsync(CenterRepoInfoUrls); - if (string.IsNullOrEmpty(jsonString)) - { - throw new Exception("从互联网下载最新的仓库信息失败"); - } - - var (time, url, file) = ParseJson(jsonString); - + var repoPath = Path.Combine(ReposPath, "bettergi-scripts-list-git"); var updated = false; - // 检查仓库是否存在,不存在则下载 - var needDownload = false; - if (Directory.Exists(CenterRepoPath)) + await Task.Run(() => { - var p = Directory.GetFiles(CenterRepoPath, "repo.json", SearchOption.AllDirectories).FirstOrDefault(); - if (p is null) + try { - needDownload = true; + if (!Directory.Exists(repoPath)) + { + // 如果仓库不存在,执行浅克隆操作 + _logger.LogInformation($"浅克隆仓库: {repoUrl} 到 {repoPath}"); + + // 使用浅克隆选项 + var options = new CloneOptions + { + Checkout = true, + IsBare = false, + RecurseSubmodules = false, // 不递归克隆子模块 + }; + options.FetchOptions.Depth = 1; // 浅克隆,只获取最新的提交 + // 克隆仓库 + Repository.Clone(repoUrl, repoPath, options); + updated = true; + } + else + { + // 仓库已经存在,执行拉取更新 + using var repo = new Repository(repoPath); + + // 检查远程URL是否需要更新 + var origin = repo.Network.Remotes["origin"]; + if (origin.Url != repoUrl) + { + // 远程URL已更改,需要更新 + _logger.LogInformation($"更新远程URL: 从 {origin.Url} 到 {repoUrl}"); + repo.Network.Remotes.Update("origin", r => r.Url = repoUrl); + } + + // 获取远程分支信息 + var remote = repo.Network.Remotes["origin"]; + var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification); + + // 使用浅拉取选项 + // var fetchOptions = new FetchOptions + // { + // Depth = 1 // 浅拉取,只获取最新的提交 + // }; + + Commands.Fetch(repo, remote.Name, refSpecs, null, "拉取最新更新"); + + // 获取当前分支 + var branch = repo.Branches["main"] ?? repo.Branches["master"]; + if (branch == null) + { + throw new Exception("未找到main或master分支"); + } + + // 如果是本地分支,需要设置上游分支 + if (!branch.IsRemote) + { + var trackingBranch = repo.Branches[$"origin/{branch.FriendlyName}"]; + if (trackingBranch != null && branch.TrackedBranch == null) + { + branch = repo.Branches.Update(branch, + b => b.TrackedBranch = trackingBranch.CanonicalName); + } + } + + // 检查是否有更新 + var currentCommitSha = repo.Head.Tip.Sha; + + // 合并或重置到最新 + if (branch.TrackedBranch != null) + { + var trackingBranch = branch.TrackedBranch; + var mergeResult = Commands.Pull( + repo, + new Signature("BetterGI", "auto@bettergi.com", DateTimeOffset.Now), + new PullOptions()); + + // 检查是否有更新 + updated = currentCommitSha != repo.Head.Tip.Sha; + } + } } - } - else - { - needDownload = true; - } + catch (Exception ex) + { + _logger.LogError(ex, "Git仓库更新失败"); + throw; + } + }); - if (needDownload) - { - await DownloadRepoAndUnzip(url); - updated = true; - } - - // 搜索本地的 repo.json - var localRepoJsonPath = Directory.GetFiles(CenterRepoPath, file, SearchOption.AllDirectories).FirstOrDefault(); - if (localRepoJsonPath is null) - { - throw new Exception("本地仓库缺少 repo.json"); - } - - var (time2, url2, file2) = ParseJson(await File.ReadAllTextAsync(localRepoJsonPath)); - - // 检查是否需要更新 - if (long.Parse(time) > long.Parse(time2)) - { - await DownloadRepoAndUnzip(url2); - updated = true; - } - - // 获取与 localRepoJsonPath 同名(无扩展名)的文件夹路径 - var folderName = Path.GetFileNameWithoutExtension(localRepoJsonPath); - var folderPath = Path.Combine(Path.GetDirectoryName(localRepoJsonPath)!, folderName); - if (!Directory.Exists(folderPath)) - { - throw new Exception("本地仓库文件夹不存在"); - } - - return (folderPath, updated); + return (repoPath, updated); } + + + // [Obsolete] + // public async Task<(string, bool)> UpdateCenterRepo() + // { + // // 测速并获取信息 + // var (fastUrl, jsonString) = await ProxySpeedTester.GetFastestUrlAsync(CenterRepoInfoUrls); + // if (string.IsNullOrEmpty(jsonString)) + // { + // throw new Exception("从互联网下载最新的仓库信息失败"); + // } + // + // var (time, url, file) = ParseJson(jsonString); + // + // var updated = false; + // + // // 检查仓库是否存在,不存在则下载 + // var needDownload = false; + // if (Directory.Exists(CenterRepoPath)) + // { + // var p = Directory.GetFiles(CenterRepoPath, "repo.json", SearchOption.AllDirectories).FirstOrDefault(); + // if (p is null) + // { + // needDownload = true; + // } + // } + // else + // { + // needDownload = true; + // } + // + // if (needDownload) + // { + // await DownloadRepoAndUnzip(url); + // updated = true; + // } + // + // // 搜索本地的 repo.json + // var localRepoJsonPath = Directory.GetFiles(CenterRepoPath, file, SearchOption.AllDirectories).FirstOrDefault(); + // if (localRepoJsonPath is null) + // { + // throw new Exception("本地仓库缺少 repo.json"); + // } + // + // var (time2, url2, file2) = ParseJson(await File.ReadAllTextAsync(localRepoJsonPath)); + // + // // 检查是否需要更新 + // if (long.Parse(time) > long.Parse(time2)) + // { + // await DownloadRepoAndUnzip(url2); + // updated = true; + // } + // + // // 获取与 localRepoJsonPath 同名(无扩展名)的文件夹路径 + // var folderName = Path.GetFileNameWithoutExtension(localRepoJsonPath); + // var folderPath = Path.Combine(Path.GetDirectoryName(localRepoJsonPath)!, folderName); + // if (!Directory.Exists(folderPath)) + // { + // throw new Exception("本地仓库文件夹不存在"); + // } + // + // return (folderPath, updated); + // } public string FindCenterRepoPath() { - var localRepoJsonPath = Directory.GetFiles(CenterRepoPath, "repo.json", SearchOption.AllDirectories).FirstOrDefault(); + var localRepoJsonPath = Directory.GetFiles(CenterRepoPath, "repo.json", SearchOption.AllDirectories) + .FirstOrDefault(); if (localRepoJsonPath is null) { throw new Exception("本地仓库缺少 repo.json"); @@ -197,7 +307,9 @@ public class ScriptRepoUpdater : Singleton // 获取文件名 var contentDisposition = res.Content.Headers.ContentDisposition; - var fileName = contentDisposition is { FileName: not null } ? contentDisposition.FileName.Trim('"') : "temp.zip"; + var fileName = contentDisposition is { FileName: not null } + ? contentDisposition.FileName.Trim('"') + : "temp.zip"; // 创建临时目录 if (!Directory.Exists(ReposTempPath)) @@ -250,7 +362,8 @@ public class ScriptRepoUpdater : Singleton var uiMessageBox = new Wpf.Ui.Controls.MessageBox { Title = "脚本订阅", - Content = $"检测到{(formClipboard ? "剪切板上存在" : "")}脚本订阅链接,解析后需要导入的脚本为:{pathJson}。\n是否导入并覆盖此文件或者文件夹下的脚本?", + Content = + $"检测到{(formClipboard ? "剪切板上存在" : "")}脚本订阅链接,解析后需要导入的脚本为:{pathJson}。\n是否导入并覆盖此文件或者文件夹下的脚本?", CloseButtonText = "关闭", PrimaryButtonText = "确认导入", Owner = Application.Current.MainWindow, @@ -322,28 +435,13 @@ public class ScriptRepoUpdater : Singleton scriptConfig.SubscribedScriptPaths.AddRange(paths); Toast.Information("获取最新仓库信息中..."); - - // 更新仓库 + string repoPath; try { - (repoPath, _) = await Task.Run(UpdateCenterRepo); + repoPath = FindCenterRepoPath(); } - catch (Exception e) - { - Toast.Warning("获取最新仓库信息失败,尝试使用本地已有仓库信息"); - try - { - repoPath = FindCenterRepoPath(); - } - catch - { - await MessageBox.ErrorAsync("本地无仓库信息,请至少成功更新一次脚本仓库信息!"); - return; - } - } - - if (string.IsNullOrEmpty(repoPath)) + catch { await MessageBox.ErrorAsync("本地无仓库信息,请至少成功更新一次脚本仓库信息!"); return; @@ -474,10 +572,13 @@ public class ScriptRepoUpdater : Singleton } // 使用路径分隔符分割路径 - string[] parts = path.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); + string[] parts = path.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, + StringSplitOptions.RemoveEmptyEntries); // 返回第一个文件夹和剩余路径 - return parts.Length > 0 ? (parts[0], string.Join(Path.DirectorySeparatorChar, parts.Skip(1))) : (string.Empty, string.Empty); + return parts.Length > 0 + ? (parts[0], string.Join(Path.DirectorySeparatorChar, parts.Skip(1))) + : (string.Empty, string.Empty); } public void OpenLocalRepoInWebView() @@ -493,7 +594,8 @@ public class ScriptRepoUpdater : Singleton _webWindow.Closed += (s, e) => _webWindow = null; _webWindow.Panel!.DownloadFolderPath = MapPathingViewModel.PathJsonPath; _webWindow.NavigateToFile(Global.Absolute(@"Assets\Web\ScriptRepo\index.html")); - _webWindow.Panel!.OnWebViewInitializedAction = () => _webWindow.Panel!.WebView.CoreWebView2.AddHostObjectToScript("repoWebBridge", new RepoWebBridge()); + _webWindow.Panel!.OnWebViewInitializedAction = () => + _webWindow.Panel!.WebView.CoreWebView2.AddHostObjectToScript("repoWebBridge", new RepoWebBridge()); _webWindow.Show(); } else @@ -502,6 +604,12 @@ public class ScriptRepoUpdater : Singleton } } + public void OpenScriptRepoWindow() + { + var scriptRepoWindow = new ScriptRepoWindow { Owner = Application.Current.MainWindow }; + scriptRepoWindow.ShowDialog(); + } + /// /// 处理带有 icon.ico 和 desktop.ini 的文件夹 /// diff --git a/BetterGenshinImpact/Core/Script/WebView/RepoWebBridge.cs b/BetterGenshinImpact/Core/Script/WebView/RepoWebBridge.cs index 1c211e6c..2023a7ae 100644 --- a/BetterGenshinImpact/Core/Script/WebView/RepoWebBridge.cs +++ b/BetterGenshinImpact/Core/Script/WebView/RepoWebBridge.cs @@ -20,15 +20,6 @@ public class RepoWebBridge { public async Task GetRepoJson() { - try - { - await ScriptRepoUpdater.Instance.UpdateCenterRepo(); - } - catch (Exception e) - { - Toast.Warning($"更新仓库信息失败:{e.Message}"); - } - try { if (!Directory.Exists(ScriptRepoUpdater.CenterRepoPath)) diff --git a/BetterGenshinImpact/GameTask/Common/Job/SwitchPartyTask.cs b/BetterGenshinImpact/GameTask/Common/Job/SwitchPartyTask.cs index 12faf95d..d513fa34 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/SwitchPartyTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/SwitchPartyTask.cs @@ -53,9 +53,9 @@ public class SwitchPartyTask // 考虑加载时间 2s,共检查 2.5s,如果失败则抛出异常 - for (int i = 0; i < 3; i++) // 检查 3 次 + for (int i = 0; i < 10; i++) // 检查 3 次 { - await Delay(850, ct); + await Delay(600, ct); using var raCheck = CaptureToRectArea(); if (Bv.IsInPartyViewUi(raCheck)) { @@ -68,11 +68,6 @@ public class SwitchPartyTask { break; // 页面已打开,跳出循环 } - - if (attempt < maxAttempts) - { - Logger.LogWarning("尝试打开队伍配置页面失败,正在重试..."); - } } if (!isOpened) diff --git a/BetterGenshinImpact/View/Controls/Drawer/CustomDrawer.cs b/BetterGenshinImpact/View/Controls/Drawer/CustomDrawer.cs new file mode 100644 index 00000000..c3e8fac4 --- /dev/null +++ b/BetterGenshinImpact/View/Controls/Drawer/CustomDrawer.cs @@ -0,0 +1,410 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.ComponentModel; + +namespace BetterGenshinImpact.View.Controls.Drawer; + +public class CustomDrawer : ContentControl +{ + #region 依赖属性 + + public static readonly DependencyProperty IsOpenProperty = + DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(CustomDrawer), + new PropertyMetadata(false, OnIsOpenChanged)); + + public static readonly DependencyProperty DrawerPositionProperty = + DependencyProperty.Register(nameof(DrawerPosition), typeof(DrawerPosition), typeof(CustomDrawer), + new PropertyMetadata(DrawerPosition.Right, OnDrawerPositionChanged)); + + public static readonly DependencyProperty OpenWidthProperty = + DependencyProperty.Register(nameof(OpenWidth), typeof(double), typeof(CustomDrawer), + new PropertyMetadata(400.0)); + + public static readonly DependencyProperty OpenHeightProperty = + DependencyProperty.Register(nameof(OpenHeight), typeof(double), typeof(CustomDrawer), + new PropertyMetadata(300.0)); + + public static readonly DependencyProperty AnimationDurationProperty = + DependencyProperty.Register(nameof(AnimationDuration), typeof(TimeSpan), typeof(CustomDrawer), + new PropertyMetadata(TimeSpan.FromMilliseconds(200))); + + public static readonly DependencyProperty BackgroundOpacityProperty = + DependencyProperty.Register(nameof(BackgroundOpacity), typeof(double), typeof(CustomDrawer), + new PropertyMetadata(0.6)); + + public static readonly DependencyProperty DrawerBackgroundProperty = + DependencyProperty.Register(nameof(DrawerBackground), typeof(Brush), typeof(CustomDrawer), + new PropertyMetadata(Brushes.Black)); + + #endregion + + #region 事件 + + /// + /// 抽屉打开后触发的事件 + /// + public event EventHandler Opened; + + /// + /// 抽屉关闭前触发的事件,可以取消关闭操作 + /// + public event CancelEventHandler Closing; + + /// + /// 引发 Opened 事件 + /// + protected virtual void OnOpened() + { + Opened?.Invoke(this, EventArgs.Empty); + } + + /// + /// 引发 Closing 事件 + /// + /// 如果取消关闭,则返回 true;否则返回 false + protected virtual bool OnClosing() + { + if (Closing != null) + { + CancelEventArgs args = new CancelEventArgs(); + Closing(this, args); + return args.Cancel; + } + return false; + } + + #endregion + + #region 属性 + + public bool IsOpen + { + get => (bool)GetValue(IsOpenProperty); + set => SetValue(IsOpenProperty, value); + } + + public DrawerPosition DrawerPosition + { + get => (DrawerPosition)GetValue(DrawerPositionProperty); + set => SetValue(DrawerPositionProperty, value); + } + + public double OpenWidth + { + get => (double)GetValue(OpenWidthProperty); + set => SetValue(OpenWidthProperty, value); + } + + public double OpenHeight + { + get => (double)GetValue(OpenHeightProperty); + set => SetValue(OpenHeightProperty, value); + } + + public TimeSpan AnimationDuration + { + get => (TimeSpan)GetValue(AnimationDurationProperty); + set => SetValue(AnimationDurationProperty, value); + } + + public double BackgroundOpacity + { + get => (double)GetValue(BackgroundOpacityProperty); + set => SetValue(BackgroundOpacityProperty, value); + } + + public Brush DrawerBackground + { + get => (Brush)GetValue(DrawerBackgroundProperty); + set => SetValue(DrawerBackgroundProperty, value); + } + + #endregion + + private Border _backgroundOverlay; + private Border _drawerContainer; + private Grid _mainGrid; + + static CustomDrawer() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDrawer), + new FrameworkPropertyMetadata(typeof(CustomDrawer))); + } + + public CustomDrawer() + { + this.Loaded += CustomDrawer_Loaded; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _mainGrid = GetTemplateChild("PART_MainGrid") as Grid; + _backgroundOverlay = GetTemplateChild("PART_BackgroundOverlay") as Border; + _drawerContainer = GetTemplateChild("PART_DrawerContainer") as Border; + + if (_backgroundOverlay != null) + { + _backgroundOverlay.MouseDown += BackgroundOverlay_MouseDown; + } + + UpdateDrawerPosition(); + UpdateOpenState(false); + } + + private void CustomDrawer_Loaded(object sender, RoutedEventArgs e) + { + UpdateOpenState(false); + } + + private void BackgroundOverlay_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + CloseDrawer(); + } + + private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CustomDrawer drawer) + { + bool newValue = (bool)e.NewValue; + bool oldValue = (bool)e.OldValue; + + // 如果是从打开到关闭状态,需要触发关闭前事件 + if (oldValue && !newValue) + { + bool cancel = drawer.OnClosing(); + if (cancel) + { + // 如果取消关闭,则恢复 IsOpen 为 true + drawer.IsOpen = true; + return; + } + } + + // 更新抽屉状态(动画) + drawer.UpdateOpenState(true); + + } + } + + private static void OnDrawerPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CustomDrawer drawer) + { + drawer.UpdateDrawerPosition(); + drawer.UpdateOpenState(false); + } + } + + private void UpdateDrawerPosition() + { + if (_drawerContainer == null) return; + + switch (DrawerPosition) + { + case DrawerPosition.Left: + _drawerContainer.HorizontalAlignment = HorizontalAlignment.Left; + _drawerContainer.VerticalAlignment = VerticalAlignment.Stretch; + _drawerContainer.Width = OpenWidth; + _drawerContainer.Height = double.NaN; + break; + case DrawerPosition.Right: + _drawerContainer.HorizontalAlignment = HorizontalAlignment.Right; + _drawerContainer.VerticalAlignment = VerticalAlignment.Stretch; + _drawerContainer.Width = OpenWidth; + _drawerContainer.Height = double.NaN; + break; + case DrawerPosition.Top: + _drawerContainer.HorizontalAlignment = HorizontalAlignment.Stretch; + _drawerContainer.VerticalAlignment = VerticalAlignment.Top; + _drawerContainer.Width = double.NaN; + _drawerContainer.Height = OpenHeight; + break; + case DrawerPosition.Bottom: + _drawerContainer.HorizontalAlignment = HorizontalAlignment.Stretch; + _drawerContainer.VerticalAlignment = VerticalAlignment.Bottom; + _drawerContainer.Width = double.NaN; + _drawerContainer.Height = OpenHeight; + break; + } + } + + private void UpdateOpenState(bool animate) + { + if (_drawerContainer == null || _backgroundOverlay == null) return; + + _backgroundOverlay.IsHitTestVisible = IsOpen; + + _drawerContainer.Opacity = 1; + + if (IsOpen) + { + Visibility = Visibility.Visible; + } + + // 每次更新状态时重新应用宽高 + switch (DrawerPosition) + { + case DrawerPosition.Left: + case DrawerPosition.Right: + _drawerContainer.Width = OpenWidth; + _drawerContainer.Height = double.NaN; + break; + case DrawerPosition.Top: + case DrawerPosition.Bottom: + _drawerContainer.Width = double.NaN; + _drawerContainer.Height = OpenHeight; + break; + } + + if (animate) + { + // 动画背景遮罩 + DoubleAnimation backgroundAnimation = new DoubleAnimation + { + To = IsOpen ? BackgroundOpacity : 0, + Duration = AnimationDuration + }; + _backgroundOverlay.BeginAnimation(OpacityProperty, backgroundAnimation); + + // 确保RenderTransform已设置 + if (_drawerContainer.RenderTransform == null || !(_drawerContainer.RenderTransform is TranslateTransform)) + { + _drawerContainer.RenderTransform = new TranslateTransform(); + } + + TranslateTransform transform = (TranslateTransform)_drawerContainer.RenderTransform; + + // 动画抽屉 + DoubleAnimation drawerAnimation = new DoubleAnimation + { + Duration = AnimationDuration, + // 弹性动画效果 + // EasingFunction = IsOpen + // ? new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.3 } + // : new ExponentialEase { EasingMode = EasingMode.EaseIn, Exponent = 6 } + }; + + // 如果是打开操作,在动画完成时触发Opened事件 + if (IsOpen) + { + drawerAnimation.Completed += (s, e) => OnOpened(); + } + + switch (DrawerPosition) + { + case DrawerPosition.Left: + // 打开时,先设置初始位置 + if (IsOpen) + { + transform.X = -OpenWidth; + } + + drawerAnimation.To = IsOpen ? 0 : -OpenWidth; + transform.BeginAnimation(TranslateTransform.XProperty, drawerAnimation); + break; + case DrawerPosition.Right: + // 打开时,先设置初始位置 + if (IsOpen) + { + transform.X = OpenWidth; + } + + drawerAnimation.To = IsOpen ? 0 : OpenWidth; + transform.BeginAnimation(TranslateTransform.XProperty, drawerAnimation); + break; + case DrawerPosition.Top: + // 打开时,先设置初始位置 + if (IsOpen) + { + transform.Y = -OpenHeight; + } + + drawerAnimation.To = IsOpen ? 0 : -OpenHeight; + transform.BeginAnimation(TranslateTransform.YProperty, drawerAnimation); + break; + case DrawerPosition.Bottom: + // 打开时,先设置初始位置 + if (IsOpen) + { + transform.Y = OpenHeight; + } + + drawerAnimation.To = IsOpen ? 0 : OpenHeight; + transform.BeginAnimation(TranslateTransform.YProperty, drawerAnimation); + break; + } + + if (!IsOpen) + { + drawerAnimation.Completed += (s, e) => + { + if (!IsOpen) + { + Visibility = Visibility.Collapsed; + } + }; + } + } + else + { + // 无动画直接设置 + _backgroundOverlay.Opacity = IsOpen ? BackgroundOpacity : 0; + + TranslateTransform transform = new TranslateTransform(); + _drawerContainer.RenderTransform = transform; + + switch (DrawerPosition) + { + case DrawerPosition.Left: + transform.X = IsOpen ? 0 : -OpenWidth; + break; + case DrawerPosition.Right: + transform.X = IsOpen ? 0 : OpenWidth; + break; + case DrawerPosition.Top: + transform.Y = IsOpen ? 0 : -OpenHeight; + break; + case DrawerPosition.Bottom: + transform.Y = IsOpen ? 0 : OpenHeight; + break; + } + + Visibility = IsOpen ? Visibility.Visible : Visibility.Collapsed; + + // 如果是无动画模式且正在打开,立即触发Opened事件 + if (IsOpen) + { + OnOpened(); + } + } + } + + /// + /// 关闭抽屉的方法,会触发 Closing 事件并可能被取消 + /// + /// 如果成功关闭返回 true,如果被取消返回 false + public bool CloseDrawer() + { + if (!IsOpen) + return true; + + if (OnClosing()) + return false; + + IsOpen = false; + return true; + } +} + +public enum DrawerPosition +{ + Left, + Right, + Top, + Bottom +} \ No newline at end of file diff --git a/BetterGenshinImpact/View/Controls/Drawer/DrawerStyles.xaml b/BetterGenshinImpact/View/Controls/Drawer/DrawerStyles.xaml new file mode 100644 index 00000000..947b7809 --- /dev/null +++ b/BetterGenshinImpact/View/Controls/Drawer/DrawerStyles.xaml @@ -0,0 +1,27 @@ + + + + \ No newline at end of file diff --git a/BetterGenshinImpact/View/Controls/Drawer/DrawerViewModel.cs b/BetterGenshinImpact/View/Controls/Drawer/DrawerViewModel.cs new file mode 100644 index 00000000..820e7bf8 --- /dev/null +++ b/BetterGenshinImpact/View/Controls/Drawer/DrawerViewModel.cs @@ -0,0 +1,71 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using System; +using System.ComponentModel; +using System.Windows.Input; + +namespace BetterGenshinImpact.View.Controls.Drawer; + +public partial class DrawerViewModel : ObservableObject +{ + [ObservableProperty] + private bool _isDrawerOpen; + + [ObservableProperty] + private object? _drawerContent; + + [ObservableProperty] + private DrawerPosition _drawerPosition = DrawerPosition.Right; + + [ObservableProperty] + private double _drawerWidth = 400; + + [ObservableProperty] + private double _drawerHeight = 300; + + [ObservableProperty] + private RelayCommand _onDrawerOpenedCommand; + + [ObservableProperty] + private RelayCommand _onDrawerClosingCommand; + + public void setDrawerOpenedAction(Action action) + { + OnDrawerOpenedCommand = new RelayCommand(action!); + } + + public void SetDrawerClosingAction(Action action) + { + OnDrawerClosingCommand = new RelayCommand(action!); + } + + [RelayCommand] + public void OpenDrawer(object content) + { + DrawerContent = content; + IsDrawerOpen = true; + } + + [RelayCommand] + public void CloseDrawer() + { + IsDrawerOpen = false; + } + + [RelayCommand] + public void ToggleDrawer(object? content = null) + { + if (IsDrawerOpen) + { + CloseDrawer(); + } + else + { + if (content != null) + { + DrawerContent = content; + } + IsDrawerOpen = true; + } + } +} diff --git a/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs b/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs index dfb50fcf..394e5556 100644 --- a/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs +++ b/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs @@ -4,11 +4,13 @@ using Microsoft.Web.WebView2.Wpf; using System; using System.Diagnostics; using System.IO; +using System.Net; using System.Security.AccessControl; using System.Text; using System.Threading; using System.Windows; using System.Windows.Controls; +using BetterGenshinImpact.Helpers; namespace BetterGenshinImpact.View.Controls.Webview; @@ -23,6 +25,8 @@ public class WebpagePanel : UserControl public string? DownloadFolderPath { get; set; } public Action? OnWebViewInitializedAction { get; set; } + + public Action? OnNavigationCompletedAction { get; set; } public WebpagePanel() { @@ -45,10 +49,37 @@ public class WebpagePanel : UserControl }; _webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; _webView.NavigationStarting += NavigationStarting_CancelNavigation; + _webView.NavigationCompleted += WebView_NavigationCompleted; + Content = _webView; } } - + + // public WebpagePanel(WebView2 webView2) + // { + // if (!IsWebView2Available()) + // { + // Content = CreateDownloadButton(); + // } + // else + // { + // EnsureWebView2DataFolder(); + // _webView = webView2; + // webView2.CreationProperties = new CoreWebView2CreationProperties + // { + // UserDataFolder = Path.Combine(new FileInfo(Environment.ProcessPath!).DirectoryName!, @"WebView2Data\\"), + // }; + // webView2.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; + // webView2.NavigationStarting += NavigationStarting_CancelNavigation; + // Content = webView2; + // } + // } + private void WebView_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e) + { + // 调用外部设置的导航完成 Action + OnNavigationCompletedAction?.Invoke(e); + } + private void WebView_CoreWebView2InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e) { if (e.IsSuccess) @@ -143,6 +174,15 @@ public class WebpagePanel : UserControl })); } + public void NavigateToMd(string md, string backgroundColor = "#2b2b2b") + { + md = WebUtility.HtmlEncode(md); + string md2Html = ResourceHelper.GetString($"pack://application:,,,/Assets/Strings/md2html.html", + Encoding.UTF8); + var html = md2Html.Replace("{{content}}", md).Replace("#202020", backgroundColor); + NavigateToHtml(html); + } + private void NavigationStarting_CancelNavigation(object? sender, CoreWebView2NavigationStartingEventArgs e) { if (e.Uri.StartsWith("data:")) // when using NavigateToString diff --git a/BetterGenshinImpact/View/Pages/JsListPage.xaml b/BetterGenshinImpact/View/Pages/JsListPage.xaml index 30322c99..b8613ad5 100644 --- a/BetterGenshinImpact/View/Pages/JsListPage.xaml +++ b/BetterGenshinImpact/View/Pages/JsListPage.xaml @@ -7,6 +7,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:pages="clr-namespace:BetterGenshinImpact.ViewModel.Pages" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" + xmlns:drawer="clr-namespace:BetterGenshinImpact.View.Controls.Drawer" d:DataContext="{d:DesignInstance Type=pages:JsListViewModel}" d:DesignHeight="600" d:DesignWidth="800" @@ -22,82 +23,93 @@ - - - - - - - - - - - - 可以通过 Javascript 调用 BetterGI 在原神中的各项能力。请在调度器中使用! - 点击查看 Javascript 脚本使用与编写教程 - - + + + + + + + + + + - - - - - - 脚本仓库 - - - - + + + 可以通过 Javascript 调用 BetterGI 在原神中的各项能力。请在调度器中使用! + + 点击查看 Javascript 脚本使用与编写教程 + + - + + + + + + 脚本仓库 + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterGenshinImpact/View/Pages/MapPathingPage.xaml b/BetterGenshinImpact/View/Pages/MapPathingPage.xaml index 0f893f25..64d0e092 100644 --- a/BetterGenshinImpact/View/Pages/MapPathingPage.xaml +++ b/BetterGenshinImpact/View/Pages/MapPathingPage.xaml @@ -10,6 +10,7 @@ xmlns:pages="clr-namespace:BetterGenshinImpact.ViewModel.Pages" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:vio="http://schemas.lepo.co/wpfui/2022/xaml/violeta" + xmlns:drawer="clr-namespace:BetterGenshinImpact.View.Controls.Drawer" d:DataContext="{d:DesignInstance Type=pages:MapPathingViewModel}" d:DesignHeight="600" d:DesignWidth="800" @@ -25,102 +26,135 @@ - - - - - - - - - + + + + + + + + + + - - - 可以实现自动采集、自动挖矿、自动锄地等功能。请在调度器中使用! - 点击查看地图追踪与录制使用教程 - - + + + 可以实现自动采集、自动挖矿、自动锄地等功能。请在调度器中使用! + + 点击查看地图追踪与录制使用教程 + + - - - - - 脚本仓库 - + + + + + 脚本仓库 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml b/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml new file mode 100644 index 00000000..cb194d67 --- /dev/null +++ b/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml.cs b/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml.cs new file mode 100644 index 00000000..7ae22deb --- /dev/null +++ b/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using System.Windows; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Script; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.Helpers; +using Wpf.Ui.Violeta.Controls; + +namespace BetterGenshinImpact.View.Windows; + +[ObservableObject] +public partial class ScriptRepoWindow +{ + // 更新渠道类 + public class RepoChannel + { + public string Name { get; set; } + public string Url { get; set; } + + public RepoChannel(string name, string url) + { + Name = name; + Url = url; + } + } + + // 渠道列表 + private ObservableCollection _repoChannels; + public ObservableCollection RepoChannels => _repoChannels; + + // 选中的渠道 + [ObservableProperty] private RepoChannel _selectedRepoChannel; + + public ScriptRepoWindow() + { + InitializeRepoChannels(); + InitializeComponent(); + DataContext = this; + } + + private void InitializeRepoChannels() + { + _repoChannels = new ObservableCollection + { + new RepoChannel("CNB", "https://cnb.cool/bettergi/bettergi-scripts-list"), + new RepoChannel("GitCode", "https://gitcode.com/huiyadanli/bettergi-scripts-list"), + new RepoChannel("Gitee", "https://gitee.com/babalae/bettergi-scripts-list"), + new RepoChannel("GitHub", "https://github.com/babalae/bettergi-scripts-list"), + }; + SelectedRepoChannel = _repoChannels[0]; + } + + [RelayCommand] + private async Task UpdateRepo() + { + try + { + // 使用选定渠道的URL进行更新 + string repoUrl = SelectedRepoChannel.Url; + + // 显示更新中提示 + Toast.Information("正在更新脚本仓库..."); + + // 执行更新 + var (repoPath, updated) = await ScriptRepoUpdater.Instance.UpdateCenterRepoByGit(repoUrl); + + // 更新结果提示 + if (updated) + { + Toast.Success("脚本仓库更新成功,有新内容"); + } + else + { + Toast.Success("脚本仓库已是最新"); + } + } + catch (Exception ex) + { + Toast.Error($"更新失败: {ex.Message}"); + } + } + + [RelayCommand] + private void OpenLocalScriptRepo() + { + TaskContext.Instance().Config.ScriptConfig.ScriptRepoHintDotVisible = false; + ScriptRepoUpdater.Instance.OpenLocalRepoInWebView(); + Close(); + } + + [RelayCommand] + private async Task ResetRepo() + { + // 添加确认对话框 + var result = await MessageBox.ShowAsync( + "确定要重置脚本仓库吗?无法正常更新时候可以使用本功能,重置后请重新更新脚本仓库。", + "确认重置", + MessageBoxButton.YesNo, + MessageBoxImage.Warning); + + if (result == MessageBoxResult.Yes) + { + try + { + if (Directory.Exists(ScriptRepoUpdater.CenterRepoPath)) + { + DirectoryHelper.DeleteReadOnlyDirectory(ScriptRepoUpdater.CenterRepoPath); + Toast.Success("脚本仓库已重置,请重新更新脚本仓库。"); + } + else + { + Toast.Information("脚本仓库不存在,无需重置"); + } + } + catch (Exception ex) + { + Toast.Error($"重置失败: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs index 1e139318..21d7d4ca 100644 --- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs @@ -149,7 +149,7 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel } // 更新仓库 - ScriptRepoUpdater.Instance.AutoUpdate(); + // ScriptRepoUpdater.Instance.AutoUpdate(); // 清理临时目录 TempManager.CleanUp(); diff --git a/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs index c5528bb6..889b6417 100644 --- a/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs @@ -9,15 +9,25 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; using BetterGenshinImpact.Helpers; +using BetterGenshinImpact.View.Controls.Drawer; +using BetterGenshinImpact.View.Controls.Webview; using BetterGenshinImpact.ViewModel.Message; using CommunityToolkit.Mvvm.Messaging; +using Microsoft.Web.WebView2.Wpf; using Wpf.Ui; -using Wpf.Ui.Controls; using Wpf.Ui.Violeta.Controls; +using Button = Wpf.Ui.Controls.Button; +using StackPanel = Wpf.Ui.Controls.StackPanel; +using TextBlock = Wpf.Ui.Controls.TextBlock; +using TextBox = Wpf.Ui.Controls.TextBox; namespace BetterGenshinImpact.ViewModel.Pages; @@ -26,13 +36,21 @@ public partial class JsListViewModel : ViewModel private readonly ILogger _logger = App.GetLogger(); private readonly string scriptPath = Global.ScriptPath(); - [ObservableProperty] - private ObservableCollection _scriptItems = []; + [ObservableProperty] private ObservableCollection _scriptItems = []; private readonly IScriptService _scriptService; public AllConfig Config { get; set; } + public DrawerViewModel DrawerVm { get; } = new DrawerViewModel(); + + private WebView2? _webView2; + + private WebpagePanel? _mdWebpagePanel; + + private TaskCompletionSource? _navigationCompletionSource; + private const int NavigationTimeoutMs = 10000; // 10秒超时 + public JsListViewModel(IScriptService scriptService, IConfigService configService) { _scriptService = scriptService; @@ -114,13 +132,214 @@ public partial class JsListViewModel : ViewModel [RelayCommand] public void OnGoToJsScriptUrl() { - Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/jsscript.html") { UseShellExecute = true }); + Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/jsscript.html") + { UseShellExecute = true }); } [RelayCommand] public void OnOpenLocalScriptRepo() { Config.ScriptConfig.ScriptRepoHintDotVisible = false; - ScriptRepoUpdater.Instance.OpenLocalRepoInWebView(); + ScriptRepoUpdater.Instance.OpenScriptRepoWindow(); } -} + + [RelayCommand] + private void OpenScriptDetailDrawer(object? scriptItem) + { + if (scriptItem == null) + { + return; + } + + if (scriptItem is ScriptProject scriptProject) + { + // 检查是否存在README.md或其他md文件 + var mdFilePath = FindMdFilePath(scriptProject); + + // 设置抽屉位置和大小 + DrawerVm.DrawerPosition = DrawerPosition.Right; + + if (!string.IsNullOrEmpty(mdFilePath)) + { + DrawerVm.DrawerWidth = 450; + // 注册抽屉关闭前事件 + DrawerVm.SetDrawerClosingAction(args => + { + if (_mdWebpagePanel != null) + { + _mdWebpagePanel.Visibility = Visibility.Hidden; + } + }); + DrawerVm.setDrawerOpenedAction(async () => + { + if (_mdWebpagePanel != null) + { + // 等待导航完成或超时 + try + { + await WaitForNavigationCompletedWithTimeout(); + _mdWebpagePanel.Visibility = Visibility.Visible; + _mdWebpagePanel.WebView.Focus(); + Debug.WriteLine("Navigation completed successfully"); + // 导航成功完成后执行其他操作 + } + catch (TimeoutException) + { + Toast.Error("Markdown内容加载超时"); + } + } + }); + } + else + { + DrawerVm.SetDrawerClosingAction(_ => { }); + DrawerVm.setDrawerOpenedAction(() => { }); + DrawerVm.DrawerWidth = 300; + } + + // 创建要在抽屉中显示的内容 + var content = CreateScriptDetailContent(scriptProject, mdFilePath); + + // 打开抽屉 + DrawerVm.OpenDrawer(content); + } + } + + private async Task WaitForNavigationCompletedWithTimeout() + { + var completedTask = await Task.WhenAny( + _navigationCompletionSource!.Task, + Task.Delay(NavigationTimeoutMs) + ); + + if (completedTask != _navigationCompletionSource.Task) + { + throw new TimeoutException("Navigation did not complete within the timeout period"); + } + } + + private object CreateScriptDetailContent(ScriptProject scriptProject, string? mdFilePath) + { + // 创建显示脚本详情的控件 + var border = new Border + { + Background = new SolidColorBrush(Color.FromRgb(0x2B, 0x2B, 0x2B)), + Padding = new Thickness(20) + }; + var panel = new StackPanel(); + border.Child = panel; + + // 假设scriptItem是你的脚本对象,根据实际类型进行调整 + panel.Children.Add(new TextBlock + { + Text = scriptProject.Manifest.Name, + FontSize = 20, + FontWeight = FontWeights.Bold, + Margin = new Thickness(0, 0, 0, 10) + }); + + + // 如果找到md文件,使用WebpagePanel显示 + if (!string.IsNullOrEmpty(mdFilePath)) + { + // 使用Grid作为容器来实现填充效果 + var grid = new Grid + { + Margin = new Thickness(0, 0, 0, 15) + }; + + _mdWebpagePanel = new WebpagePanel + { + Margin = new Thickness(0), + Visibility = Visibility.Hidden, + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + + _navigationCompletionSource = new TaskCompletionSource(); + _mdWebpagePanel.OnNavigationCompletedAction = (_) => + { + // 导航完成时设置任务结果 + _navigationCompletionSource.TrySetResult(true); + }; + _mdWebpagePanel.NavigateToMd(File.ReadAllText(mdFilePath)); + + grid.Children.Add(_mdWebpagePanel); + panel.Children.Add(grid); + + // 设置Grid高度以占满剩余空间 + panel.SizeChanged += (sender, args) => + { + // 计算其他元素使用的高度 + double otherElementsHeight = 0; + foreach (var child in panel.Children) + { + if (child != grid) + { + var frameworkElement = child as FrameworkElement; + if (frameworkElement != null) + { + otherElementsHeight += frameworkElement.ActualHeight + frameworkElement.Margin.Top + frameworkElement.Margin.Bottom; + } + } + } + + // 设置Grid高度为剩余空间 + grid.Height = Math.Max(400, panel.ActualHeight - otherElementsHeight - 15); // 设置最小高度为400 + }; + } + else + { + panel.Children.Add(new TextBlock + { + Text = $"版本: {scriptProject.Manifest.Version}", + Margin = new Thickness(0, 5, 0, 5) + }); + + panel.Children.Add(new TextBlock + { + Text = scriptProject.Manifest.Description, + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(0, 5, 0, 15) + }); + } + + // 添加操作按钮 + // var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal }; + // + // var runButton = new Button + // { + // Content = "执行脚本", + // Margin = new Thickness(0, 0, 10, 0) + // }; + // runButton.Click += async (s, e) => await OnStartRun(script); + // buttonPanel.Children.Add(runButton); + // + // var openFolderButton = new Button { Content = "打开目录" }; + // openFolderButton.Click += (s, e) => OnOpenScriptProjectFolder(script); + // buttonPanel.Children.Add(openFolderButton); + + // panel.Children.Add(buttonPanel); + + + return border; + } + + private static string? FindMdFilePath(ScriptProject script) + { + string[] possibleMdFiles = { "README.md", "readme.md" }; + string mdFilePath = null; + + foreach (var mdFile in possibleMdFiles) + { + string fullPath = Path.Combine(script.ProjectPath, mdFile); + if (File.Exists(fullPath)) + { + mdFilePath = fullPath; + break; + } + } + + return mdFilePath; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs index 393d5ca0..23b9ea27 100644 --- a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs @@ -231,6 +231,6 @@ public partial class KeyMouseRecordPageViewModel : ViewModel public void OnOpenLocalScriptRepo() { Config.ScriptConfig.ScriptRepoHintDotVisible = false; - ScriptRepoUpdater.Instance.OpenLocalRepoInWebView(); + ScriptRepoUpdater.Instance.OpenScriptRepoWindow(); } } diff --git a/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs index cee4d6f6..2e9ce69e 100644 --- a/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs @@ -1,4 +1,5 @@ -using BetterGenshinImpact.Core.Config; +using System; +using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Script.Group; using BetterGenshinImpact.Core.Script.Project; using BetterGenshinImpact.GameTask.AutoPathing; @@ -21,6 +22,14 @@ using Wpf.Ui.Violeta.Controls; using BetterGenshinImpact.View.Pages.View; using BetterGenshinImpact.ViewModel.Pages.View; using Wpf.Ui.Violeta.Win32; +using BetterGenshinImpact.View.Controls.Drawer; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.ComponentModel; +using BetterGenshinImpact.View.Controls.Webview; +using Microsoft.Web.WebView2.Wpf; +using BetterGenshinImpact.Helpers; namespace BetterGenshinImpact.ViewModel.Pages; @@ -39,6 +48,15 @@ public partial class MapPathingViewModel : ViewModel private readonly IScriptService _scriptService; public AllConfig Config { get; set; } + + // 添加抽屉ViewModel + public DrawerViewModel DrawerVm { get; } = new DrawerViewModel(); + + // 添加WebView2相关成员变量 + private WebView2? _webView2; + private WebpagePanel? _mdWebpagePanel; + private TaskCompletionSource? _navigationCompletionSource; + private const int NavigationTimeoutMs = 10000; // 10秒超时 /// public MapPathingViewModel(IScriptService scriptService, IConfigService configService) @@ -179,6 +197,222 @@ public partial class MapPathingViewModel : ViewModel public void OnOpenLocalScriptRepo() { Config.ScriptConfig.ScriptRepoHintDotVisible = false; - ScriptRepoUpdater.Instance.OpenLocalRepoInWebView(); + ScriptRepoUpdater.Instance.OpenScriptRepoWindow(); } -} + + [RelayCommand] + public void OnOpenPathingDetail() + { + var item = SelectNode; + if (item == null) + { + return; + } + + // 如果是目录,检查是否存在README.md + string? mdFilePath = null; + if (item.IsDirectory && !string.IsNullOrEmpty(item.FilePath)) + { + mdFilePath = FindMdFilePath(item.FilePath); + } + + // 设置抽屉位置和大小 + DrawerVm.DrawerPosition = DrawerPosition.Right; + + if (!string.IsNullOrEmpty(mdFilePath)) + { + DrawerVm.DrawerWidth = 450; + // 注册抽屉关闭前事件 + DrawerVm.SetDrawerClosingAction(args => + { + if (_mdWebpagePanel != null) + { + _mdWebpagePanel.Visibility = Visibility.Hidden; + } + }); + DrawerVm.setDrawerOpenedAction(async () => + { + if (_mdWebpagePanel != null) + { + // 等待导航完成或超时 + try + { + await WaitForNavigationCompletedWithTimeout(); + _mdWebpagePanel.Visibility = Visibility.Visible; + _mdWebpagePanel.WebView.Focus(); + Debug.WriteLine("Navigation completed successfully"); + } + catch (TimeoutException) + { + Toast.Error("Markdown内容加载超时"); + } + } + }); + } + else + { + DrawerVm.DrawerWidth = 350; + DrawerVm.SetDrawerClosingAction(_ => { }); + DrawerVm.setDrawerOpenedAction(() => { }); + } + + // 创建要在抽屉中显示的内容 + var content = CreatePathingDetailContent(item, mdFilePath); + + // 打开抽屉 + if (content != null) + { + DrawerVm.OpenDrawer(content); + } + } + + private async Task WaitForNavigationCompletedWithTimeout() + { + var completedTask = await Task.WhenAny( + _navigationCompletionSource!.Task, + Task.Delay(NavigationTimeoutMs) + ); + + if (completedTask != _navigationCompletionSource.Task) + { + throw new TimeoutException("Navigation did not complete within the timeout period"); + } + } + + private string? FindMdFilePath(string dirPath) + { + string[] possibleMdFiles = { "README.md", "readme.md" }; + + foreach (var mdFile in possibleMdFiles) + { + string fullPath = Path.Combine(dirPath, mdFile); + if (File.Exists(fullPath)) + { + return fullPath; + } + } + + return null; + } + + private object? CreatePathingDetailContent(FileTreeNode node, string? mdFilePath = null) + { + // 创建显示路径任务详情的控件 + var border = new Border + { + Background = new SolidColorBrush(Color.FromRgb(0x2B, 0x2B, 0x2B)), + Padding = new Thickness(20) + }; + + var panel = new StackPanel(); + border.Child = panel; + + // 添加标题 + panel.Children.Add(new TextBlock + { + Text = node.FileName, + FontSize = 20, + FontWeight = FontWeights.Bold, + Margin = new Thickness(0, 0, 0, 10) + }); + + // 如果找到md文件,使用WebpagePanel显示 + if (!string.IsNullOrEmpty(mdFilePath)) + { + // 使用Grid作为容器来实现填充效果 + var grid = new Grid + { + Margin = new Thickness(0, 0, 0, 15) + }; + + _mdWebpagePanel = new WebpagePanel + { + Margin = new Thickness(0), + Visibility = Visibility.Hidden, + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + + _navigationCompletionSource = new TaskCompletionSource(); + _mdWebpagePanel.OnNavigationCompletedAction = (_) => + { + // 导航完成时设置任务结果 + _navigationCompletionSource.TrySetResult(true); + }; + _mdWebpagePanel.NavigateToMd(File.ReadAllText(mdFilePath)); + + grid.Children.Add(_mdWebpagePanel); + panel.Children.Add(grid); + + // 设置Grid高度以占满剩余空间 + panel.SizeChanged += (sender, args) => + { + // 计算其他元素使用的高度 + double otherElementsHeight = 0; + foreach (var child in panel.Children) + { + if (child != grid) + { + var frameworkElement = child as FrameworkElement; + if (frameworkElement != null) + { + otherElementsHeight += frameworkElement.ActualHeight + frameworkElement.Margin.Top + frameworkElement.Margin.Bottom; + } + } + } + + // 设置Grid高度为剩余空间 + grid.Height = Math.Max(400, panel.ActualHeight - otherElementsHeight - 15); // 设置最小高度为400 + }; + } + else if (!node.IsDirectory && !string.IsNullOrEmpty(node.FilePath)) + { + // 如果是文件而不是目录,显示更多详情 + try + { + if (string.IsNullOrEmpty(node.Value?.Info.Description)) + { + return null; + } + + panel.Children.Add(new TextBlock + { + Text = $"{node.Value?.Info.Description}", + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(0, 5, 0, 5) + }); + } + catch (Exception ex) + { + panel.Children.Add(new TextBlock + { + Text = $"读取文件信息时出错: {ex.Message}", + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(0, 5, 0, 5) + }); + } + } + else + { + // 显示目录信息 + panel.Children.Add(new TextBlock + { + Text = "这是一个目录,包含多个地图追踪任务。", + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(0, 5, 0, 15) + }); + + // 添加子项信息 + if (node.Children.Count > 0) + { + panel.Children.Add(new TextBlock + { + Text = $"包含 {node.Children.Count} 个子项", + Margin = new Thickness(0, 5, 0, 5) + }); + } + } + + return border; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs index 34234995..81ea51a3 100644 --- a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs @@ -451,7 +451,7 @@ public partial class ScriptControlViewModel : ViewModel public void OnOpenLocalScriptRepo() { TaskContext.Instance().Config.ScriptConfig.ScriptRepoHintDotVisible = false; - ScriptRepoUpdater.Instance.OpenLocalRepoInWebView(); + ScriptRepoUpdater.Instance.OpenScriptRepoWindow(); } [RelayCommand] diff --git a/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs index 428271f4..d9e8f3f1 100644 --- a/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs @@ -79,7 +79,7 @@ public partial class AutoFightViewModel : ObservableObject, IViewModel public void OnOpenLocalScriptRepo() { Config.ScriptConfig.ScriptRepoHintDotVisible = false; - ScriptRepoUpdater.Instance.OpenLocalRepoInWebView(); + ScriptRepoUpdater.Instance.OpenScriptRepoWindow(); } [RelayCommand]