feature: add worktree support (#205)

This commit is contained in:
leo 2024-06-27 18:25:16 +08:00
parent 43af8c49a1
commit 8a8aabede3
No known key found for this signature in database
23 changed files with 959 additions and 21 deletions

View file

@ -0,0 +1,71 @@
<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:m="using:SourceGit.Models"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
xmlns:vm="using:SourceGit.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.AddWorktree"
x:DataType="vm:AddWorktree">
<StackPanel Orientation="Vertical" Margin="8,0">
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.AddWorktree}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,Auto" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.AddWorktree.Location}"/>
<TextBox Grid.Row="0" Grid.Column="1"
x:Name="TxtLocation"
Height="28"
CornerRadius="3"
Text="{Binding FullPath, Mode=TwoWay}">
<TextBox.InnerRightContent>
<Button Classes="icon_button" Width="28" Height="28" Margin="4,0,0,0" Click="SelectLocation">
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.AddWorktree.Name}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding CustomName, Mode=TwoWay}"
Watermark="{DynamicResource Text.AddWorktree.Name.Placeholder}"/>
<CheckBox Grid.Row="2" Grid.Column="1"
Content="{DynamicResource Text.AddWorktree.Tracking.Toggle}"
IsChecked="{Binding SetTrackingBranch, Mode=TwoWay}"/>
<Border Grid.Row="3" Grid.Column="0"
Height="32"
IsVisible="{Binding SetTrackingBranch, Mode=OneWay}">
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.AddWorktree.Tracking}"/>
</Border>
<ComboBox Grid.Row="3" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding TrackingBranches}"
SelectedItem="{Binding SelectedTrackingBranch, Mode=TwoWay}"
IsVisible="{Binding SetTrackingBranch, 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>
</Grid>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,28 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
namespace SourceGit.Views
{
public partial class AddWorktree : UserControl
{
public AddWorktree()
{
InitializeComponent();
}
private async void SelectLocation(object sender, RoutedEventArgs e)
{
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
var toplevel = TopLevel.GetTopLevel(this);
if (toplevel == null)
return;
var selected = await toplevel.StorageProvider.OpenFolderPickerAsync(options);
if (selected.Count == 1)
TxtLocation.Text = selected[0].Path.LocalPath;
e.Handled = true;
}
}
}

View file

@ -0,0 +1,41 @@
<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:m="using:SourceGit.Models"
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.LockWorktree"
x:DataType="vm:LockWorktree">
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.LockWorktree}"/>
<Grid Margin="8,16,0,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right"
Margin="8,0"
Text="{DynamicResource Text.LockWorktree.Target}"/>
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Worktree}"/>
<TextBlock Grid.Column="1" Classes="monospace" Margin="8,0,0,0" TextTrimming="CharacterEllipsis">
<Run Text="{Binding Target.FullPath}"/>
<Run Text="{Binding Target.Name}" Foreground="{DynamicResource Brush.FG2}"/>
</TextBlock>
</Grid>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right"
Margin="8,0"
Text="{DynamicResource Text.LockWorktree.Reason}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Height="26"
CornerRadius="3"
Text="{Binding Reason, Mode=TwoWay}"
Watermark="{DynamicResource Text.LockWorktree.Reason.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
</Grid>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace SourceGit.Views
{
public partial class LockWorktree : UserControl
{
public LockWorktree()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,15 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.PruneWorktrees">
<StackPanel Orientation="Vertical" Margin="8,0">
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.PruneWorktrees}"/>
<TextBlock Text="{DynamicResource Text.PruneWorktrees.Tip}"
Margin="0,16,0,0"
HorizontalAlignment="Center"/>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace SourceGit.Views
{
public partial class PruneWorktrees : UserControl
{
public PruneWorktrees()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,34 @@
<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:m="using:SourceGit.Models"
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.RemoveWorktree"
x:DataType="vm:RemoveWorktree">
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.RemoveWorktree}"/>
<Grid Margin="8,16,0,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right"
Margin="8,0"
Text="{DynamicResource Text.RemoveWorktree.Target}"/>
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Worktree}"/>
<TextBlock Grid.Column="1" Classes="monospace" Margin="8,0,0,0" TextTrimming="CharacterEllipsis">
<Run Text="{Binding Target.FullPath}"/>
<Run Text="{Binding Target.Name}" Foreground="{DynamicResource Brush.FG2}"/>
</TextBlock>
</Grid>
<CheckBox Grid.Row="1" Grid.Column="1"
Content="{DynamicResource Text.RemoveWorktree.Force}"
IsChecked="{Binding Force, Mode=TwoWay}"/>
</Grid>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace SourceGit.Views
{
public partial class RemoveWorktree : UserControl
{
public RemoveWorktree()
{
InitializeComponent();
}
}
}

View file

@ -119,7 +119,7 @@
</Grid>
<!-- Dashboard -->
<Grid Grid.Row="1" Margin="0,0,0,8" RowDefinitions="Auto,Auto,28,Auto,28,*,28,Auto,28,Auto" IsVisible="{Binding !IsSearching}">
<Grid Grid.Row="1" Margin="0,0,0,8" RowDefinitions="Auto,Auto,28,Auto,28,*,28,Auto,28,Auto,28,Auto" IsVisible="{Binding !IsSearching}">
<!-- Page Switcher for Right Panel -->
<Border Grid.Row="0" Margin="8,0,4,0" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}" CornerRadius="6">
<Border CornerRadius="6" ClipToBounds="True">
@ -463,7 +463,7 @@
Command="{Binding UpdateSubmodules}"
IsVisible="{Binding Submodules, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"
ToolTip.Tip="{DynamicResource Text.Repository.Submodules.Update}">
<Path x:Name="iconSubmoduleUpdate" Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
<Path Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
</Button>
<Button Grid.Column="3"
Classes="icon_button"
@ -531,6 +531,96 @@
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- Worktrees -->
<ToggleButton Grid.Row="10" Classes="group_expander" IsChecked="{Binding IsWorktreeGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Worktrees}"/>
<TextBlock Grid.Column="1" Text="{Binding Worktrees, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
<Button Grid.Column="2"
Classes="icon_button"
Width="14"
Margin="8,0"
Command="{Binding PruneWorktrees}"
IsVisible="{Binding Worktrees, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"
ToolTip.Tip="{DynamicResource Text.Repository.Worktrees.Prune}">
<Path x:Name="icon" Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
</Button>
<Button Grid.Column="3"
Classes="icon_button"
Width="14"
Margin="0,0,8,0"
Command="{Binding AddWorktree}"
ToolTip.Tip="{DynamicResource Text.Repository.Worktrees.Add}">
<Path Width="12" Height="12" Data="{StaticResource Icons.Worktree.Add}"/>
</Button>
</Grid>
</ToggleButton>
<DataGrid Grid.Row="11"
MaxHeight="200"
Margin="8,0,4,0"
Background="Transparent"
ItemsSource="{Binding Worktrees}"
SelectionMode="Single"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
ContextRequested="OnWorktreeContextRequested"
DoubleTapped="OnDoubleTappedWorktree"
IsVisible="{Binding IsWorktreeGroupExpanded, Mode=OneWay}">
<DataGrid.Styles>
<Style Selector="DataGridRow">
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="DataGridRow /template/ Border#RowBorder">
<Setter Property="ClipToBounds" Value="True" />
</Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
</Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Path Width="10" Height="10" Margin="8,0,0,0" Data="{StaticResource Icons.Worktree}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="FullPath">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Margin="8,0,0,0" TextTrimming="CharacterEllipsis">
<Run Text="{Binding FullPath}"/>
<Run Text="{Binding Name}" Foreground="{DynamicResource Brush.FG2}"/>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FullPath">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Path Width="10" Height="10" Margin="4,0,8,0" Data="{StaticResource Icons.Lock}" Fill="{DynamicResource Brush.FG2}" IsVisible="{Binding IsLocked}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
<!-- Commit Search Panel -->

View file

@ -315,6 +315,40 @@ namespace SourceGit.Views
e.Handled = true;
}
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
{
var submodule = datagrid.SelectedItem as string;
(DataContext as ViewModels.Repository).OpenSubmodule(submodule);
}
e.Handled = true;
}
private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
{
var worktree = datagrid.SelectedItem as Models.Worktree;
var menu = repo.CreateContextMenuForWorktree(worktree);
datagrid.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnDoubleTappedWorktree(object sender, TappedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
{
var worktree = datagrid.SelectedItem as Models.Worktree;
(DataContext as ViewModels.Repository).OpenWorktree(worktree);
}
e.Handled = true;
}
private void CollectBranchesFromNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
{
if (node == null || node.IsRemote)
@ -332,16 +366,5 @@ namespace SourceGit.Views
outs.Add(b);
}
}
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
{
var submodule = datagrid.SelectedItem as string;
(DataContext as ViewModels.Repository).OpenSubmodule(submodule);
}
e.Handled = true;
}
}
}