refactor: rewrite Open Local Repository feature

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-04-13 15:16:22 +08:00
parent 3db91ed5ae
commit c1a5e986bf
9 changed files with 274 additions and 70 deletions

View File

@@ -594,6 +594,10 @@
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">Open Data Storage Directory</x:String>
<x:String x:Key="Text.OpenFile" xml:space="preserve">Open File</x:String>
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Open in External Merge Tool</x:String>
<x:String x:Key="Text.OpenLocalRepository" xml:space="preserve">Open Local Repository</x:String>
<x:String x:Key="Text.OpenLocalRepository.Bookmark" xml:space="preserve">Bookmark:</x:String>
<x:String x:Key="Text.OpenLocalRepository.Group" xml:space="preserve">Group:</x:String>
<x:String x:Key="Text.OpenLocalRepository.Path" xml:space="preserve">Folder:</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Create New Tab</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Close Tab</x:String>

View File

@@ -598,6 +598,10 @@
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">浏览应用数据目录</x:String>
<x:String x:Key="Text.OpenFile" xml:space="preserve">打开文件</x:String>
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">使用外部对比工具查看</x:String>
<x:String x:Key="Text.OpenLocalRepository" xml:space="preserve">打开本地仓库</x:String>
<x:String x:Key="Text.OpenLocalRepository.Bookmark" xml:space="preserve">书签 </x:String>
<x:String x:Key="Text.OpenLocalRepository.Group" xml:space="preserve">分组 </x:String>
<x:String x:Key="Text.OpenLocalRepository.Path" xml:space="preserve">仓库位置 </x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">选填。</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">新建空白页</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">关闭标签页</x:String>

View File

@@ -598,6 +598,10 @@
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">瀏覽程式資料目錄</x:String>
<x:String x:Key="Text.OpenFile" xml:space="preserve">開啟檔案</x:String>
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">使用外部比對工具檢視</x:String>
<x:String x:Key="Text.OpenLocalRepository" xml:space="preserve">開啟本機存放庫</x:String>
<x:String x:Key="Text.OpenLocalRepository.Bookmark" xml:space="preserve">書籤:</x:String>
<x:String x:Key="Text.OpenLocalRepository.Group" xml:space="preserve">群組:</x:String>
<x:String x:Key="Text.OpenLocalRepository.Path" xml:space="preserve">存放庫位置:</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">選填。</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">新增分頁</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">關閉分頁</x:String>

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class OpenLocalRepository : Popup
{
[Required(ErrorMessage = "Repository folder is required")]
[CustomValidation(typeof(OpenLocalRepository), nameof(ValidateRepoPath))]
public string RepoPath
{
get => _repoPath;
set => SetProperty(ref _repoPath, value, true);
}
public List<RepositoryNode> Groups
{
get;
}
public RepositoryNode Group
{
get => _group;
set => SetProperty(ref _group, value);
}
public List<int> Bookmarks
{
get;
}
public int Bookmark
{
get => _bookmark;
set => SetProperty(ref _bookmark, value);
}
public OpenLocalRepository(string pageId, RepositoryNode group)
{
_pageId = pageId;
_group = group;
Groups = new List<RepositoryNode>();
CollectGroups(Groups, Preferences.Instance.RepositoryNodes);
if (Groups.Count > 0 && _group == null)
Group = Groups[0];
Bookmarks = new List<int>();
for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++)
Bookmarks.Add(i);
}
public static ValidationResult ValidateRepoPath(string folder, ValidationContext _)
{
if (!Directory.Exists(folder))
return new ValidationResult("Given path can NOT be found");
return ValidationResult.Success;
}
public override async Task<bool> Sure()
{
var isBare = await new Commands.IsBareRepository(_repoPath).GetResultAsync();
var repoRoot = _repoPath;
if (!isBare)
{
var test = await new Commands.QueryRepositoryRootPath(_repoPath).GetResultAsync();
if (test.IsSuccess && !string.IsNullOrWhiteSpace(test.StdOut))
{
repoRoot = test.StdOut.Trim();
}
else
{
var launcher = App.GetLauncher();
foreach (var page in launcher.Pages)
{
if (page.Node.Id.Equals(_pageId, StringComparison.Ordinal))
{
page.Popup = new Init(page.Node.Id, _repoPath, _group, test.StdErr);
break;
}
}
return false;
}
}
var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(repoRoot, _group, true);
node.Bookmark = _bookmark;
await node.UpdateStatusAsync(false, null);
Welcome.Instance.Refresh();
node.Open();
return true;
}
private void CollectGroups(List<RepositoryNode> outs, List<RepositoryNode> collections)
{
foreach (var node in collections)
{
if (!node.IsRepository)
{
outs.Add(node);
CollectGroups(outs, node.SubNodes);
}
}
}
private string _pageId = string.Empty;
private string _repoPath = string.Empty;
private RepositoryNode _group = null;
private int _bookmark = 0;
}
}

View File

@@ -128,19 +128,6 @@ namespace SourceGit.ViewModels
return rs.StdOut.Trim();
}
public void InitRepository(string path, RepositoryNode parent, string reason)
{
if (!Preferences.Instance.IsGitConfigured())
{
Models.Notification.Send(null, App.Text("NotConfigured"), true);
return;
}
var activePage = App.GetLauncher().ActivePage;
if (activePage != null && activePage.CanCreatePopup())
activePage.Popup = new Init(activePage.Node.Id, path, parent, reason);
}
public async Task AddRepositoryAsync(string path, RepositoryNode parent, bool moveNode, bool open)
{
var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, parent, moveNode);
@@ -163,6 +150,19 @@ namespace SourceGit.ViewModels
activePage.Popup = new Clone(activePage.Node.Id);
}
public void OpenLocalRepository()
{
if (!Preferences.Instance.IsGitConfigured())
{
Models.Notification.Send(null, App.Text("NotConfigured"), true);
return;
}
var activePage = App.GetLauncher().ActivePage;
if (activePage != null && activePage.CanCreatePopup())
activePage.Popup = new OpenLocalRepository(activePage.Node.Id, null);
}
public void OpenTerminal()
{
if (!Preferences.Instance.IsGitConfigured())

View File

@@ -0,0 +1,77 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.OpenLocalRepository"
x:DataType="vm:OpenLocalRepository">
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
<StackPanel Orientation="Horizontal">
<Path Width="16" Height="16"
Margin="0,2,0,0"
Data="{StaticResource Icons.Folder.Open}"/>
<TextBlock FontSize="18"
Margin="8,0,0,0"
Classes="bold"
Text="{DynamicResource Text.OpenLocalRepository}"/>
</StackPanel>
<Grid Margin="8,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.OpenLocalRepository.Path}"/>
<TextBox Grid.Row="0" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding RepoPath, Mode=TwoWay}">
<TextBox.InnerRightContent>
<Button Classes="icon_button" Width="28" Height="28" Margin="4,0,0,0" Click="OnSelectRepositoryFolder">
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.OpenLocalRepository.Group}"/>
<ComboBox Grid.Row="1" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding Groups}"
SelectedItem="{Binding Group, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="vm:RepositoryNode">
<TextBlock Text="{Binding Name, Mode=OneWay}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.OpenLocalRepository.Bookmark}"/>
<ComboBox Grid.Row="2" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center"
ItemsSource="{Binding Bookmarks}"
SelectedItem="{Binding Bookmark, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Height="20">
<Path Width="12" Height="12"
Fill="{Binding Converter={x:Static c:IntConverters.ToBookmarkBrush}}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Data="{StaticResource Icons.Bookmark}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
namespace SourceGit.Views
{
public partial class OpenLocalRepository : UserControl
{
public OpenLocalRepository()
{
InitializeComponent();
}
private async void OnSelectRepositoryFolder(object _1, RoutedEventArgs e)
{
if (DataContext is not ViewModels.OpenLocalRepository vm)
return;
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel == null)
return;
var preference = ViewModels.Preferences.Instance;
var workspace = preference.GetActiveWorkspace();
var initDir = workspace.DefaultCloneDir;
if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir))
initDir = preference.GitDefaultCloneDir;
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
if (Directory.Exists(initDir))
{
var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir);
options.SuggestedStartLocation = folder;
}
try
{
var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options);
if (selected.Count == 1)
{
var folder = selected[0];
vm.RepoPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString();
}
}
catch (Exception exception)
{
Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true);
}
e.Handled = true;
}
}
}

View File

@@ -19,7 +19,7 @@
<Path Width="16" Height="16" Data="{StaticResource Icons.Clone}" Margin="0,4,0,0"/>
</Button>
<Button Classes="icon_button" Width="32" Click="OpenLocalRepository" ToolTip.Tip="{DynamicResource Text.Welcome.OpenOrInit}">
<Button Classes="icon_button" Width="32" Command="{Binding OpenLocalRepository}" ToolTip.Tip="{DynamicResource Text.Welcome.OpenOrInit}">
<Path Width="14" Height="14" Data="{StaticResource Icons.Folder.Open}" Margin="0,2,0,0"/>
</Button>

View File

@@ -1,9 +1,4 @@
using System;
using System.IO;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
namespace SourceGit.Views
{
@@ -13,56 +8,5 @@ namespace SourceGit.Views
{
InitializeComponent();
}
private async void OpenLocalRepository(object _1, RoutedEventArgs e)
{
var activePage = App.GetLauncher().ActivePage;
if (activePage == null || !activePage.CanCreatePopup())
return;
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel == null)
return;
var preference = ViewModels.Preferences.Instance;
var workspace = preference.GetActiveWorkspace();
var initDir = workspace.DefaultCloneDir;
if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir))
initDir = preference.GitDefaultCloneDir;
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
if (Directory.Exists(initDir))
{
var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir);
options.SuggestedStartLocation = folder;
}
try
{
var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options);
if (selected.Count == 1)
{
var folder = selected[0];
var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString();
var repoPath = await ViewModels.Welcome.Instance.GetRepositoryRootAsync(folderPath);
if (!string.IsNullOrEmpty(repoPath))
{
await ViewModels.Welcome.Instance.AddRepositoryAsync(repoPath, null, false, true);
ViewModels.Welcome.Instance.Refresh();
}
else if (Directory.Exists(folderPath))
{
var test = await new Commands.QueryRepositoryRootPath(folderPath).GetResultAsync();
ViewModels.Welcome.Instance.InitRepository(folderPath, null, test.StdErr);
}
}
}
catch (Exception exception)
{
Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true);
}
e.Handled = true;
}
}
}