From 750ca8ec61ad7a4600f9622caa178d92223e4d88 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 21 Apr 2025 14:44:02 +0800 Subject: [PATCH] refactor: use custom view locator to create new window/dialog (#1216) Signed-off-by: leo (cherry picked from commit 3e6f2b25f15b263e2b84922abc5cf6d621d62a83) --- src/App.Commands.cs | 6 +- src/App.axaml.cs | 38 +++++++++--- src/ViewModels/AIAssistant.cs | 80 +++++++++++++++++++++++++ src/ViewModels/CommitDetail.cs | 12 ++-- src/ViewModels/Histories.cs | 6 +- src/ViewModels/Launcher.cs | 2 +- src/ViewModels/Repository.cs | 16 ++--- src/ViewModels/WorkingCopy.cs | 41 +++---------- src/Views/AIAssistant.axaml | 13 ++-- src/Views/AIAssistant.axaml.cs | 72 ++-------------------- src/Views/CommitMessageTextBox.axaml.cs | 7 +-- src/Views/Launcher.axaml.cs | 2 +- src/Views/LauncherPage.axaml.cs | 2 +- src/Views/Preferences.axaml.cs | 4 +- src/Views/RepositoryToolbar.axaml.cs | 21 +++---- 15 files changed, 158 insertions(+), 164 deletions(-) create mode 100644 src/ViewModels/AIAssistant.cs diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 85a75829..22e9fb51 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -37,10 +37,10 @@ namespace SourceGit } } - public static readonly Command OpenPreferencesCommand = new Command(_ => OpenDialog(new Views.Preferences())); - public static readonly Command OpenHotkeysCommand = new Command(_ => OpenDialog(new Views.Hotkeys())); + public static readonly Command OpenPreferencesCommand = new Command(_ => ShowWindow(new Views.Preferences(), false)); + public static readonly Command OpenHotkeysCommand = new Command(_ => ShowWindow(new Views.Hotkeys(), false)); public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir)); - public static readonly Command OpenAboutCommand = new Command(_ => OpenDialog(new Views.About())); + public static readonly Command OpenAboutCommand = new Command(_ => ShowWindow(new Views.About(), false)); public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true)); public static readonly Command QuitCommand = new Command(_ => Quit(0)); public static readonly Command CopyTextBlockCommand = new Command(p => diff --git a/src/App.axaml.cs b/src/App.axaml.cs index c659388a..de6e94ff 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -105,10 +105,36 @@ namespace SourceGit #endregion #region Utility Functions - public static void OpenDialog(Window window) + public static void ShowWindow(object data, bool showAsDialog) { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) - window.ShowDialog(owner); + if (data is Views.ChromelessWindow window) + { + if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + window.ShowDialog(owner); + else + window.Show(); + + return; + } + + var dataTypeName = data.GetType().FullName; + if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal)) + return; + + var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views."); + var viewType = Type.GetType(viewTypeName); + if (viewType == null || !viewType.IsSubclassOf(typeof(Views.ChromelessWindow))) + return; + + window = Activator.CreateInstance(viewType) as Views.ChromelessWindow; + if (window != null) + { + window.DataContext = data; + if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + window.ShowDialog(owner); + else + window.Show(); + } } public static void RaiseException(string context, string message) @@ -598,11 +624,7 @@ namespace SourceGit { Dispatcher.UIThread.Post(() => { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) - { - var dialog = new Views.SelfUpdate() { DataContext = new ViewModels.SelfUpdate() { Data = data } }; - dialog.ShowDialog(owner); - } + ShowWindow(new ViewModels.SelfUpdate() { Data = data }, true); }); } diff --git a/src/ViewModels/AIAssistant.cs b/src/ViewModels/AIAssistant.cs new file mode 100644 index 00000000..8756a30b --- /dev/null +++ b/src/ViewModels/AIAssistant.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +using Avalonia.Threading; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class AIAssistant : ObservableObject + { + public bool IsGenerating + { + get => _isGenerating; + private set => SetProperty(ref _isGenerating, value); + } + + public string Text + { + get => _text; + private set => SetProperty(ref _text, value); + } + + public AIAssistant(Repository repo, Models.OpenAIService service, List changes, Action onApply) + { + _repo = repo; + _service = service; + _changes = changes; + _onApply = onApply; + _cancel = new CancellationTokenSource(); + + Gen(); + } + + public void Regen() + { + if (_cancel is { IsCancellationRequested: false }) + _cancel.Cancel(); + + Gen(); + } + + public void Apply() + { + _onApply?.Invoke(Text); + } + + public void Cancel() + { + _cancel?.Cancel(); + } + + private void Gen() + { + Text = string.Empty; + IsGenerating = true; + + _cancel = new CancellationTokenSource(); + Task.Run(() => + { + new Commands.GenerateCommitMessage(_service, _repo.FullPath, _changes, _cancel.Token, message => + { + Dispatcher.UIThread.Invoke(() => Text = message); + }).Exec(); + + Dispatcher.UIThread.Invoke(() => IsGenerating = false); + }, _cancel.Token); + } + + private readonly Repository _repo = null; + private Models.OpenAIService _service = null; + private List _changes = null; + private Action _onApply = null; + private CancellationTokenSource _cancel = null; + private bool _isGenerating = false; + private string _text = string.Empty; + } +} diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 81c09472..0c3ad23f 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -332,8 +332,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _commit.SHA) }; - window.Show(); + App.ShowWindow(new FileHistories(_repo, change.Path, _commit.SHA), false); ev.Handled = true; }; @@ -343,8 +342,7 @@ namespace SourceGit.ViewModels blame.IsEnabled = change.Index != Models.ChangeState.Deleted; blame.Click += (_, ev) => { - var window = new Views.Blame() { DataContext = new Blame(_repo.FullPath, change.Path, _commit.SHA) }; - window.Show(); + App.ShowWindow(new Blame(_repo.FullPath, change.Path, _commit.SHA), false); ev.Handled = true; }; @@ -508,8 +506,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _commit.SHA) }; - window.Show(); + App.ShowWindow(new FileHistories(_repo, file.Path, _commit.SHA), false); ev.Handled = true; }; @@ -519,8 +516,7 @@ namespace SourceGit.ViewModels blame.IsEnabled = file.Type == Models.ObjectType.Blob; blame.Click += (_, ev) => { - var window = new Views.Blame() { DataContext = new Blame(_repo.FullPath, file.Path, _commit.SHA) }; - window.Show(); + App.ShowWindow(new Blame(_repo.FullPath, file.Path, _commit.SHA), false); ev.Handled = true; }; diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index a4a3c515..27457959 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -570,11 +570,7 @@ namespace SourceGit.ViewModels return; } - App.OpenDialog(new Views.InteractiveRebase() - { - DataContext = new InteractiveRebase(_repo, current, commit) - }); - + App.ShowWindow(new InteractiveRebase(_repo, current, commit), true); e.Handled = true; }; diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 9ae99b33..84ba96e4 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -380,7 +380,7 @@ namespace SourceGit.ViewModels configure.Header = App.Text("Workspace.Configure"); configure.Click += (_, e) => { - App.OpenDialog(new Views.ConfigureWorkspace() { DataContext = new ConfigureWorkspace() }); + App.ShowWindow(new ConfigureWorkspace(), true); e.Handled = true; }; menu.Items.Add(configure); diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 3ce40751..04f938ed 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1405,8 +1405,7 @@ namespace SourceGit.ViewModels { locks.Click += (_, e) => { - var dialog = new Views.LFSLocks() { DataContext = new LFSLocks(this, _remotes[0].Name) }; - App.OpenDialog(dialog); + App.ShowWindow(new LFSLocks(this, _remotes[0].Name), true); e.Handled = true; }; } @@ -1419,8 +1418,7 @@ namespace SourceGit.ViewModels lockRemote.Header = remoteName; lockRemote.Click += (_, e) => { - var dialog = new Views.LFSLocks() { DataContext = new LFSLocks(this, remoteName) }; - App.OpenDialog(dialog); + App.ShowWindow(new LFSLocks(this, remoteName), true); e.Handled = true; }; locks.Items.Add(lockRemote); @@ -1706,10 +1704,7 @@ namespace SourceGit.ViewModels compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.OpenDialog(new Views.BranchCompare() - { - DataContext = new BranchCompare(_fullpath, branch, _currentBranch) - }); + App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch), false); }; menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(compareWithHead); @@ -1989,10 +1984,7 @@ namespace SourceGit.ViewModels compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.OpenDialog(new Views.BranchCompare() - { - DataContext = new BranchCompare(_fullpath, branch, _currentBranch) - }); + App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch), false); }; menu.Items.Add(compareWithHead); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 65759412..29c8a13f 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -323,10 +323,7 @@ namespace SourceGit.ViewModels public void OpenAssumeUnchanged() { - App.OpenDialog(new Views.AssumeUnchangedManager() - { - DataContext = new AssumeUnchangedManager(_repo) - }); + App.ShowWindow(new AssumeUnchangedManager(_repo), true); } public void StashAll(bool autoStart) @@ -726,8 +723,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) }; - window.Show(); + App.ShowWindow(new FileHistories(_repo, change.Path), false); e.Handled = true; }; @@ -1093,8 +1089,7 @@ namespace SourceGit.ViewModels { ai.Click += (_, e) => { - var dialog = new Views.AIAssistant(services[0], _repo.FullPath, this, _selectedStaged); - App.OpenDialog(dialog); + App.ShowWindow(new AIAssistant(_repo, services[0], _selectedStaged, t => CommitMessage = t), true); e.Handled = true; }; } @@ -1108,8 +1103,7 @@ namespace SourceGit.ViewModels item.Header = service.Name; item.Click += (_, e) => { - var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _selectedStaged); - App.OpenDialog(dialog); + App.ShowWindow(new AIAssistant(_repo, dup, _selectedStaged, t => CommitMessage = t), true); e.Handled = true; }; @@ -1193,8 +1187,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) }; - window.Show(); + App.ShowWindow(new FileHistories(_repo, change.Path), false); e.Handled = true; }; @@ -1490,8 +1483,7 @@ namespace SourceGit.ViewModels if (services.Count == 1) { - var dialog = new Views.AIAssistant(services[0], _repo.FullPath, this, _staged); - App.OpenDialog(dialog); + App.ShowWindow(new AIAssistant(_repo, services[0], _staged, t => CommitMessage = t), true); return null; } @@ -1503,8 +1495,7 @@ namespace SourceGit.ViewModels item.Header = service.Name; item.Click += (_, e) => { - var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _staged); - App.OpenDialog(dialog); + App.ShowWindow(new AIAssistant(_repo, dup, _staged, t => CommitMessage = t), true); e.Handled = true; }; @@ -1705,14 +1696,7 @@ namespace SourceGit.ViewModels if (!string.IsNullOrEmpty(_filter) && _staged.Count > _visibleStaged.Count && !confirmWithFilter) { var confirmMessage = App.Text("WorkingCopy.ConfirmCommitWithFilter", _staged.Count, _visibleStaged.Count, _staged.Count - _visibleStaged.Count); - App.OpenDialog(new Views.ConfirmCommit() - { - DataContext = new ConfirmCommit(confirmMessage, () => - { - DoCommit(autoStage, autoPush, allowEmpty, true); - }) - }); - + App.ShowWindow(new ConfirmCommit(confirmMessage, () => DoCommit(autoStage, autoPush, allowEmpty, true)), true); return; } @@ -1720,14 +1704,7 @@ namespace SourceGit.ViewModels { if ((autoStage && _count == 0) || (!autoStage && _staged.Count == 0)) { - App.OpenDialog(new Views.ConfirmEmptyCommit() - { - DataContext = new ConfirmEmptyCommit(_count > 0, stageAll => - { - DoCommit(stageAll, autoPush, true, confirmWithFilter); - }) - }); - + App.ShowWindow(new ConfirmEmptyCommit(_count > 0, stageAll => DoCommit(stageAll, autoPush, true, confirmWithFilter)), true); return; } } diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml index e07c3a3e..c9a37f3b 100644 --- a/src/Views/AIAssistant.axaml +++ b/src/Views/AIAssistant.axaml @@ -7,6 +7,7 @@ xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="120" x:Class="SourceGit.Views.AIAssistant" + x:DataType="vm:AIAssistant" x:Name="ThisControl" Icon="/App.ico" Title="{DynamicResource Text.AIAssistant}" @@ -48,20 +49,22 @@ - +