refactor: rewrite & redesign the ux for AddWorktree popup (#205)

This commit is contained in:
leo 2024-06-28 12:07:33 +08:00
parent a260c89c0d
commit db8e534a0a
No known key found for this signature in database
5 changed files with 100 additions and 54 deletions

View file

@ -8,6 +8,9 @@
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Source code can be found at </x:String> <x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Source code can be found at </x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">Opensource &amp; Free Git GUI Client</x:String> <x:String x:Key="Text.About.SubTitle" xml:space="preserve">Opensource &amp; Free Git GUI Client</x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">Add Worktree</x:String> <x:String x:Key="Text.AddWorktree" xml:space="preserve">Add Worktree</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">What to Checkout:</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Existing Branch</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Create New Branch</x:String>
<x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">Location:</x:String> <x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">Location:</x:String>
<x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">Path for this worktree. Relative path is supported.</x:String> <x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">Path for this worktree. Relative path is supported.</x:String>
<x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">Branch Name:</x:String> <x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">Branch Name:</x:String>

View file

@ -11,9 +11,12 @@
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• 项目源代码地址 </x:String> <x:String x:Key="Text.About.SourceCode" xml:space="preserve">• 项目源代码地址 </x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">开源免费的Git客户端</x:String> <x:String x:Key="Text.About.SubTitle" xml:space="preserve">开源免费的Git客户端</x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">新增工作树</x:String> <x:String x:Key="Text.AddWorktree" xml:space="preserve">新增工作树</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">检出分支方式 </x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">已有分支</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">创建新分支</x:String>
<x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">工作树路径 </x:String> <x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">工作树路径 </x:String>
<x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">填写该工作树的路径。支持相对路径。</x:String> <x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">填写该工作树的路径。支持相对路径。</x:String>
<x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">自定义分支名 </x:String> <x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">分支名 </x:String>
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">选填。默认使用目标文件夹名称。</x:String> <x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">选填。默认使用目标文件夹名称。</x:String>
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">跟踪分支</x:String> <x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">跟踪分支</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">设置上游跟踪分支</x:String> <x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">设置上游跟踪分支</x:String>

View file

@ -11,9 +11,12 @@
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• 專案原始碼地址 </x:String> <x:String x:Key="Text.About.SourceCode" xml:space="preserve">• 專案原始碼地址 </x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">開源免費的Git客戶端</x:String> <x:String x:Key="Text.About.SubTitle" xml:space="preserve">開源免費的Git客戶端</x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">新增工作樹</x:String> <x:String x:Key="Text.AddWorktree" xml:space="preserve">新增工作樹</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">檢出分支方式 </x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">已有分支</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">創建新分支</x:String>
<x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">工作樹路徑 </x:String> <x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">工作樹路徑 </x:String>
<x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">填寫該工作樹的路徑。支援相對路徑。</x:String> <x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">填寫該工作樹的路徑。支援相對路徑。</x:String>
<x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">自定义分支名 </x:String> <x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">分支名 </x:String>
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">選填。 預設使用目標資料夾名稱。</x:String> <x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">選填。 預設使用目標資料夾名稱。</x:String>
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">跟蹤分支</x:String> <x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">跟蹤分支</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">設置上游跟蹤分支</x:String> <x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">設置上游跟蹤分支</x:String>

View file

@ -1,29 +1,51 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SourceGit.ViewModels namespace SourceGit.ViewModels
{ {
public partial class AddWorktree : Popup public class AddWorktree : Popup
{ {
[GeneratedRegex(@"^[\w\-/\.]+$")]
private static partial Regex REG_NAME();
[Required(ErrorMessage = "Worktree path is required!")] [Required(ErrorMessage = "Worktree path is required!")]
[CustomValidation(typeof(AddWorktree), nameof(ValidateWorktreePath))] [CustomValidation(typeof(AddWorktree), nameof(ValidateWorktreePath))]
public string Path public string Path
{ {
get => _path; get => _path;
set => SetProperty(ref _path, value, true); set => SetProperty(ref _path, value);
} }
[CustomValidation(typeof(AddWorktree), nameof(ValidateBranchName))] public bool UseExistingBranch
public string CustomName
{ {
get => _customName; get => _useExistingBranch;
set => SetProperty(ref _customName, value, true); set
{
if (SetProperty(ref _useExistingBranch, value, true))
{
if (value)
SelectedBranch = LocalBranches.Count > 0 ? LocalBranches[0] : string.Empty;
else
SelectedBranch = string.Empty;
}
}
}
public List<string> LocalBranches
{
get;
private set;
}
public List<string> RemoteBranches
{
get;
private set;
}
public string SelectedBranch
{
get => _selectedBranch;
set => SetProperty(ref _selectedBranch, value);
} }
public bool SetTrackingBranch public bool SetTrackingBranch
@ -32,12 +54,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _setTrackingBranch, value); set => SetProperty(ref _setTrackingBranch, value);
} }
public List<string> TrackingBranches
{
get;
private set;
}
public string SelectedTrackingBranch public string SelectedTrackingBranch
{ {
get; get;
@ -48,15 +64,23 @@ namespace SourceGit.ViewModels
{ {
_repo = repo; _repo = repo;
TrackingBranches = new List<string>(); LocalBranches = new List<string>();
RemoteBranches = new List<string>();
foreach (var branch in repo.Branches) foreach (var branch in repo.Branches)
{ {
if (!branch.IsLocal) if (branch.IsLocal)
TrackingBranches.Add($"{branch.Remote}/{branch.Name}"); LocalBranches.Add(branch.Name);
else
RemoteBranches.Add($"{branch.Remote}/{branch.Name}");
} }
if (TrackingBranches.Count > 0) if (LocalBranches.Count > 0)
SelectedTrackingBranch = TrackingBranches[0]; SelectedBranch = LocalBranches[0];
else
SelectedBranch = string.Empty;
if (RemoteBranches.Count > 0)
SelectedTrackingBranch = RemoteBranches[0];
else else
SelectedTrackingBranch = string.Empty; SelectedTrackingBranch = string.Empty;
@ -81,25 +105,6 @@ namespace SourceGit.ViewModels
if (folders.Length > 0) if (folders.Length > 0)
return new ValidationResult("Given path is not empty!!!"); return new ValidationResult("Given path is not empty!!!");
} }
return ValidationResult.Success;
}
public static ValidationResult ValidateBranchName(string name, ValidationContext ctx)
{
if (string.IsNullOrEmpty(name))
return ValidationResult.Success;
var creator = ctx.ObjectInstance as AddWorktree;
if (creator == null)
return new ValidationResult("Missing runtime context to create branch!");
foreach (var b in creator._repo.Branches)
{
var test = b.IsLocal ? b.Name : $"{b.Remote}/{b.Name}";
if (test == name)
return new ValidationResult("A branch with same name already exists!");
}
return ValidationResult.Success; return ValidationResult.Success;
} }
@ -113,7 +118,7 @@ namespace SourceGit.ViewModels
return Task.Run(() => return Task.Run(() =>
{ {
var succ = new Commands.Worktree(_repo.FullPath).Add(_path, _customName, tracking, SetProgressDescription); var succ = new Commands.Worktree(_repo.FullPath).Add(_path, _selectedBranch, tracking, SetProgressDescription);
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });
@ -121,7 +126,8 @@ namespace SourceGit.ViewModels
private Repository _repo = null; private Repository _repo = null;
private string _path = string.Empty; private string _path = string.Empty;
private string _customName = string.Empty; private bool _useExistingBranch = true;
private string _selectedBranch = string.Empty;
private bool _setTrackingBranch = false; private bool _setTrackingBranch = false;
} }
} }

View file

@ -13,7 +13,7 @@
<TextBlock FontSize="18" <TextBlock FontSize="18"
Classes="bold" Classes="bold"
Text="{DynamicResource Text.AddWorktree}"/> Text="{DynamicResource Text.AddWorktree}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,Auto" ColumnDefinitions="120,*"> <Grid Margin="0,16,0,0" RowDefinitions="32,32,32,Auto,32" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0" <TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0" Margin="0,0,8,0"
@ -30,20 +30,47 @@
</Button> </Button>
</TextBox.InnerRightContent> </TextBox.InnerRightContent>
</TextBox> </TextBox>
<TextBlock Grid.Row="1" Grid.Column="0" <TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.AddWorktree.WhatToCheckout}"/>
<StackPanel Grid.Row="1" Grid.Column="1" Height="32" Orientation="Horizontal">
<RadioButton Content="{DynamicResource Text.AddWorktree.WhatToCheckout.Existing}"
GroupName="LocalChanges"
IsChecked="{Binding UseExistingBranch, Mode=TwoWay}" />
<RadioButton Content="{DynamicResource Text.AddWorktree.WhatToCheckout.CreateNew}"
GroupName="LocalChanges"
Margin="8,0,0,0" />
</StackPanel>
<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0" Margin="0,0,8,0"
Text="{DynamicResource Text.AddWorktree.Name}"/> Text="{DynamicResource Text.AddWorktree.Name}"/>
<TextBox Grid.Row="1" Grid.Column="1" <ComboBox Grid.Row="2" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding LocalBranches}"
SelectedItem="{Binding SelectedBranch, Mode=TwoWay}"
IsEnabled="{Binding UseExistingBranch, Mode=OneWay}"
IsVisible="{Binding UseExistingBranch, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<Path Margin="0,0,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Branch}"/>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Grid.Row="2" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding CustomName, Mode=TwoWay}" Text="{Binding SelectedBranch, Mode=TwoWay}"
Watermark="{DynamicResource Text.AddWorktree.Name.Placeholder}"/> Watermark="{DynamicResource Text.AddWorktree.Name.Placeholder}"
IsEnabled="{Binding !UseExistingBranch, Mode=OneWay}"
<CheckBox Grid.Row="2" Grid.Column="1" IsVisible="{Binding !UseExistingBranch, Mode=OneWay}"/>
Content="{DynamicResource Text.AddWorktree.Tracking.Toggle}"
IsChecked="{Binding SetTrackingBranch, Mode=TwoWay}"/>
<Border Grid.Row="3" Grid.Column="0" <Border Grid.Row="3" Grid.Column="0"
Height="32" Height="32"
@ -55,7 +82,7 @@
<ComboBox Grid.Row="3" Grid.Column="1" <ComboBox Grid.Row="3" Grid.Column="1"
Height="28" Padding="8,0" Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding TrackingBranches}" ItemsSource="{Binding RemoteBranches}"
SelectedItem="{Binding SelectedTrackingBranch, Mode=TwoWay}" SelectedItem="{Binding SelectedTrackingBranch, Mode=TwoWay}"
IsVisible="{Binding SetTrackingBranch, Mode=OneWay}"> IsVisible="{Binding SetTrackingBranch, Mode=OneWay}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
@ -67,6 +94,10 @@
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<CheckBox Grid.Row="4" Grid.Column="1"
Content="{DynamicResource Text.AddWorktree.Tracking.Toggle}"
IsChecked="{Binding SetTrackingBranch, Mode=TwoWay}"/>
</Grid> </Grid>
</StackPanel> </StackPanel>
</UserControl> </UserControl>