mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-11-01 13:13:21 -07:00
feature: allow deleting multiple branches at one time (#137)
This commit is contained in:
parent
99794e7ff7
commit
6fe96d629a
13 changed files with 492 additions and 87 deletions
99
src/Converters/BranchTreeNodeConverters.cs
Normal file
99
src/Converters/BranchTreeNodeConverters.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
public static class BranchTreeNodeConverters
|
||||
{
|
||||
public static readonly CornerRadius DEFAULT = new CornerRadius(4);
|
||||
|
||||
public static readonly FuncMultiValueConverter<object, CornerRadius> ToCornerRadius =
|
||||
new FuncMultiValueConverter<object, CornerRadius>(v =>
|
||||
{
|
||||
if (v == null)
|
||||
return DEFAULT;
|
||||
|
||||
var array = new List<object>();
|
||||
array.AddRange(v);
|
||||
if (array.Count != 2)
|
||||
return DEFAULT;
|
||||
|
||||
var item = array[1] as TreeViewItem;
|
||||
if (item == null || !item.IsSelected)
|
||||
return DEFAULT;
|
||||
|
||||
var prev = GetPrevTreeViewItem(item);
|
||||
var next = GetNextTreeViewItem(item, true);
|
||||
|
||||
double top = 4, bottom = 4;
|
||||
if (prev != null && prev.IsSelected)
|
||||
top = 0;
|
||||
if (next != null && next.IsSelected)
|
||||
bottom = 0;
|
||||
|
||||
return new CornerRadius(top, bottom);
|
||||
});
|
||||
|
||||
private static TreeViewItem GetPrevTreeViewItem(TreeViewItem item)
|
||||
{
|
||||
if (item.Parent is TreeView tree)
|
||||
{
|
||||
var idx = tree.IndexFromContainer(item);
|
||||
if (idx == 0)
|
||||
return null;
|
||||
|
||||
var prev = tree.ContainerFromIndex(idx - 1) as TreeViewItem;
|
||||
if (prev != null && prev.IsExpanded && prev.ItemCount > 0)
|
||||
return prev.ContainerFromIndex(prev.ItemCount - 1) as TreeViewItem;
|
||||
|
||||
return prev;
|
||||
}
|
||||
else if (item.Parent is TreeViewItem parentItem)
|
||||
{
|
||||
var idx = parentItem.IndexFromContainer(item);
|
||||
if (idx == 0)
|
||||
return parentItem;
|
||||
|
||||
var prev = parentItem.ContainerFromIndex(idx - 1) as TreeViewItem;
|
||||
if (prev != null && prev.IsExpanded && prev.ItemCount > 0)
|
||||
return prev.ContainerFromIndex(prev.ItemCount - 1) as TreeViewItem;
|
||||
|
||||
return prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeViewItem GetNextTreeViewItem(TreeViewItem item, bool intoSelf = false)
|
||||
{
|
||||
if (intoSelf && item.IsExpanded && item.ItemCount > 0)
|
||||
return item.ContainerFromIndex(0) as TreeViewItem;
|
||||
|
||||
if (item.Parent is TreeView tree)
|
||||
{
|
||||
var idx = tree.IndexFromContainer(item);
|
||||
if (idx == tree.ItemCount - 1)
|
||||
return null;
|
||||
|
||||
return tree.ContainerFromIndex(idx + 1) as TreeViewItem;
|
||||
}
|
||||
else if (item.Parent is TreeViewItem parentItem)
|
||||
{
|
||||
var idx = parentItem.IndexFromContainer(item);
|
||||
if (idx == parentItem.ItemCount - 1)
|
||||
return GetNextTreeViewItem(parentItem);
|
||||
|
||||
return parentItem.ContainerFromIndex(idx + 1) as TreeViewItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@
|
|||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">Copy Branch Name</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Delete" xml:space="preserve">Delete${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.DeleteMultiBranches" xml:space="preserve">Delete selected {0} branches</x:String>
|
||||
<x:String x:Key="Text.BranchCM.DiscardAll" xml:space="preserve">Discard all changes</x:String>
|
||||
<x:String x:Key="Text.BranchCM.FastForward" xml:space="preserve">Fast-Forward to${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Finish" xml:space="preserve">Git Flow - Finish${0}$</x:String>
|
||||
|
@ -128,6 +129,9 @@
|
|||
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch :</x:String>
|
||||
<x:String x:Key="Text.DeleteBranch.IsRemoteTip" xml:space="preserve">You are about to delete a remote branch!!!</x:String>
|
||||
<x:String x:Key="Text.DeleteBranch.WithTrackingRemote" xml:space="preserve">Also delete remote branch${0}$</x:String>
|
||||
<x:String x:Key="Text.DeleteMultiBranch" xml:space="preserve">Delete Multiple Branches</x:String>
|
||||
<x:String x:Key="Text.DeleteMultiBranch.Targets" xml:space="preserve">Targets : </x:String>
|
||||
<x:String x:Key="Text.DeleteMultiBranch.Tip" xml:space="preserve">You are trying to delete multiple branches at one time. Be sure to double-check before taking action!</x:String>
|
||||
<x:String x:Key="Text.DeleteRemote" xml:space="preserve">Delete Remote</x:String>
|
||||
<x:String x:Key="Text.DeleteRemote.Remote" xml:space="preserve">Remote :</x:String>
|
||||
<x:String x:Key="Text.DeleteRepositoryNode.Target" xml:space="preserve">Target :</x:String>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">检出(checkout)${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">复制分支名</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Delete" xml:space="preserve">删除${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.DeleteMultiBranches" xml:space="preserve">删除选中的 {0} 个分支</x:String>
|
||||
<x:String x:Key="Text.BranchCM.DiscardAll" xml:space="preserve">放弃所有更改</x:String>
|
||||
<x:String x:Key="Text.BranchCM.FastForward" xml:space="preserve">快进(fast-forward)到${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Finish" xml:space="preserve">GIT工作流 - 完成${0}$</x:String>
|
||||
|
@ -128,6 +129,9 @@
|
|||
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">分支名 :</x:String>
|
||||
<x:String x:Key="Text.DeleteBranch.IsRemoteTip" xml:space="preserve">您正在删除远程上的分支,请务必小心!!!</x:String>
|
||||
<x:String x:Key="Text.DeleteBranch.WithTrackingRemote" xml:space="preserve">同时删除远程分支${0}$</x:String>
|
||||
<x:String x:Key="Text.DeleteMultiBranch" xml:space="preserve">删除多个分支</x:String>
|
||||
<x:String x:Key="Text.DeleteMultiBranch.Targets" xml:space="preserve">分支列表 :</x:String>
|
||||
<x:String x:Key="Text.DeleteMultiBranch.Tip" xml:space="preserve">您正在尝试一次性删除多个分支,请务必仔细检查后再执行操作!</x:String>
|
||||
<x:String x:Key="Text.DeleteRemote" xml:space="preserve">删除远程确认</x:String>
|
||||
<x:String x:Key="Text.DeleteRemote.Remote" xml:space="preserve">远程名 :</x:String>
|
||||
<x:String x:Key="Text.DeleteRepositoryNode.Target" xml:space="preserve">目标 :</x:String>
|
||||
|
|
|
@ -486,37 +486,15 @@
|
|||
</Style>
|
||||
|
||||
<Style Selector="ListBox.page_switcher ListBoxItem">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid>
|
||||
<ContentPresenter Name="PART_ContentPresenter"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
Content="{TemplateBinding Content}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
|
||||
|
||||
<Border Background="{DynamicResource Brush.FG2}"
|
||||
Width="4"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||
IsHitTestVisible="False"
|
||||
IsVisible="{TemplateBinding IsSelected}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="ListBox.page_switcher ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.SwitcherBG}"/>
|
||||
<Setter Property="Margin" Value="4,0,2,0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.page_switcher ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.SwitcherHover}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.page_switcher ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.SwitcherBG}"/>
|
||||
<Style Selector="ListBox.page_switcher ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ContextMenu">
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
<Color x:Key="Color.Decorator">#FF6F6F6F</Color>
|
||||
<Color x:Key="Color.DecoratorIcon">#FFF8F8F8</Color>
|
||||
<Color x:Key="Color.Conflict">#FF836C2E</Color>
|
||||
<Color x:Key="Color.SwitcherHover">#FFDFDFDF</Color>
|
||||
<Color x:Key="Color.SwitcherBG">#FFCFCFCF</Color>
|
||||
<Color x:Key="Color.Border0">#FFCFCFCF</Color>
|
||||
<Color x:Key="Color.Border1">#FF898989</Color>
|
||||
<Color x:Key="Color.Border2">#FFCFCFCF</Color>
|
||||
|
@ -42,8 +40,6 @@
|
|||
<Color x:Key="Color.Decorator">#FF505050</Color>
|
||||
<Color x:Key="Color.DecoratorIcon">#FFF8F8F8</Color>
|
||||
<Color x:Key="Color.Conflict">#FFFAFAD2</Color>
|
||||
<Color x:Key="Color.SwitcherHover">#FF323232</Color>
|
||||
<Color x:Key="Color.SwitcherBG">#FF3F3F3F</Color>
|
||||
<Color x:Key="Color.Border0">#FF181818</Color>
|
||||
<Color x:Key="Color.Border1">#FF7C7C7C</Color>
|
||||
<Color x:Key="Color.Border2">#FF404040</Color>
|
||||
|
@ -70,8 +66,6 @@
|
|||
<SolidColorBrush x:Key="Brush.Decorator" Color="{DynamicResource Color.Decorator}"/>
|
||||
<SolidColorBrush x:Key="Brush.DecoratorIcon" Color="{DynamicResource Color.DecoratorIcon}"/>
|
||||
<SolidColorBrush x:Key="Brush.Conflict" Color="{DynamicResource Color.Conflict}"/>
|
||||
<SolidColorBrush x:Key="Brush.SwitcherHover" Color="{DynamicResource Color.SwitcherHover}"/>
|
||||
<SolidColorBrush x:Key="Brush.SwitcherBG" Color="{DynamicResource Color.SwitcherBG}"/>
|
||||
<SolidColorBrush x:Key="Brush.Border0" Color="{DynamicResource Color.Border0}"/>
|
||||
<SolidColorBrush x:Key="Brush.Border1" Color="{DynamicResource Color.Border1}"/>
|
||||
<SolidColorBrush x:Key="Brush.Border2" Color="{DynamicResource Color.Border2}"/>
|
||||
|
|
53
src/ViewModels/DeleteMultipleBranches.cs
Normal file
53
src/ViewModels/DeleteMultipleBranches.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class DeleteMultipleBranches : Popup
|
||||
{
|
||||
public List<Models.Branch> Targets
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public DeleteMultipleBranches(Repository repo, List<Models.Branch> branches, bool isLocal)
|
||||
{
|
||||
_repo = repo;
|
||||
_isLocal = isLocal;
|
||||
Targets = branches;
|
||||
View = new Views.DeleteMultipleBranches() { DataContext = this };
|
||||
}
|
||||
|
||||
public override Task<bool> Sure()
|
||||
{
|
||||
_repo.SetWatcherEnabled(false);
|
||||
ProgressDescription = "Deleting multiple branches...";
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
if (_isLocal)
|
||||
{
|
||||
foreach (var target in Targets)
|
||||
{
|
||||
SetProgressDescription($"Deleting local branch : {target.Name}");
|
||||
Commands.Branch.DeleteLocal(_repo.FullPath, target.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var target in Targets)
|
||||
{
|
||||
SetProgressDescription($"Deleting remote branch : {target.Remote}/{target.Name}");
|
||||
Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name);
|
||||
}
|
||||
}
|
||||
|
||||
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private Repository _repo = null;
|
||||
private bool _isLocal = false;
|
||||
}
|
||||
}
|
|
@ -722,6 +722,12 @@ namespace SourceGit.ViewModels
|
|||
PopupHost.ShowAndStartPopup(new Checkout(this, branch));
|
||||
}
|
||||
|
||||
public void DeleteMultipleBranches(List<Models.Branch> branches, bool isLocal)
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new DeleteMultipleBranches(this, branches, isLocal));
|
||||
}
|
||||
|
||||
public void CreateNewTag()
|
||||
{
|
||||
var current = Branches.Find(x => x.IsCurrent);
|
||||
|
|
|
@ -8,6 +8,8 @@ namespace SourceGit.Views
|
|||
{
|
||||
public static void OpenContextMenu(this Control control, ContextMenu menu)
|
||||
{
|
||||
if (menu == null) return;
|
||||
|
||||
menu.PlacementTarget = control;
|
||||
menu.Closing += OnContextMenuClosing; // Clear context menu because it is dynamic.
|
||||
|
||||
|
|
88
src/Views/DeleteMultipleBranches.axaml
Normal file
88
src/Views/DeleteMultipleBranches.axaml
Normal file
|
@ -0,0 +1,88 @@
|
|||
<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.DeleteMultipleBranches"
|
||||
x:DataType="vm:DeleteMultipleBranches">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.DeleteMultiBranch}" />
|
||||
|
||||
<Grid Margin="0,16,8,0" RowDefinitions="Auto,Auto" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Text="{DynamicResource Text.DeleteMultiBranch.Targets}" />
|
||||
<Border Grid.Row="0" Grid.Column="1"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border1}"
|
||||
CornerRadius="4"
|
||||
Padding="4">
|
||||
<DataGrid MaxHeight="200"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
ItemsSource="{Binding Targets}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<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="4,0,8,0" Data="{StaticResource Icons.Branch}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" ClipToBounds="True"
|
||||
Classes="monospace" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||||
Margin="0,8,0,0"
|
||||
Text="{DynamicResource Text.DeleteMultiBranch.Tip}"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
FontStyle="Italic" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
13
src/Views/DeleteMultipleBranches.axaml.cs
Normal file
13
src/Views/DeleteMultipleBranches.axaml.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class DeleteMultipleBranches : UserControl
|
||||
{
|
||||
public DeleteMultipleBranches()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left Normal Mode -->
|
||||
<Grid Grid.Column="0" RowDefinitions="28,Auto,5,28,28,Auto,28,*,28,Auto,28,Auto" Margin="0,0,0,4" IsVisible="{Binding !IsSearching}">
|
||||
<Grid Grid.Column="0" Classes="repository_leftpanel" RowDefinitions="28,Auto,5,28,28,Auto,28,*,28,Auto,28,Auto" Margin="0,0,0,4" IsVisible="{Binding !IsSearching}">
|
||||
<!-- WorkingCopy -->
|
||||
<TextBlock Grid.Row="0" Classes="group_header_label" Text="{DynamicResource Text.Repository.Workspace}"/>
|
||||
<ListBox Grid.Row="1" Classes="page_switcher" Background="Transparent" SelectedIndex="{Binding SelectedViewIndex, Mode=TwoWay}">
|
||||
|
@ -119,14 +119,14 @@
|
|||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBoxItem Height="28" Padding="0">
|
||||
<ListBoxItem Height="28">
|
||||
<Grid Margin="16,0,0,0" Height="28" ColumnDefinitions="20,*">
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Data="{StaticResource Icons.Histories}"/>
|
||||
<TextBlock Grid.Column="1" Classes="monospace" Text="{DynamicResource Text.Histories}"/>
|
||||
</Grid>
|
||||
</ListBoxItem>
|
||||
|
||||
<ListBoxItem Height="28" Padding="0">
|
||||
<ListBoxItem Height="28">
|
||||
<Grid Margin="16,0,0,0" Height="28" ColumnDefinitions="20,*,Auto">
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Data="{StaticResource Icons.Send}"/>
|
||||
<TextBlock Grid.Column="1" Classes="monospace" Text="{DynamicResource Text.WorkingCopy}"/>
|
||||
|
@ -142,7 +142,7 @@
|
|||
</Grid>
|
||||
</ListBoxItem>
|
||||
|
||||
<ListBoxItem Height="28" Padding="0">
|
||||
<ListBoxItem Height="28">
|
||||
<Grid Margin="16,0,0,0" Height="28" ColumnDefinitions="20,*,Auto">
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Data="{StaticResource Icons.Stashes}"/>
|
||||
<TextBlock Grid.Column="1" Classes="monospace" Text="{DynamicResource Text.Stashes}"/>
|
||||
|
@ -198,19 +198,37 @@
|
|||
<TreeView Grid.Row="5"
|
||||
x:Name="localBranchTree"
|
||||
MaxHeight="400"
|
||||
Margin="4,0,2,0"
|
||||
SelectionMode="Multiple"
|
||||
ItemsSource="{Binding LocalBranchTrees}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
LostFocus="OnLocalBranchTreeLostFocus"
|
||||
ContextRequested="OnLocalBranchContextMenuRequested"
|
||||
SelectionChanged="OnLocalBranchTreeSelectionChanged">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
<Setter Property="CornerRadius">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static c:BranchTreeNodeConverters.ToCornerRadius}">
|
||||
<Binding Path="$parent[v:Repository].RefereshLocalBranchSelectionToken"/>
|
||||
<Binding Path="$self"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
||||
<Grid Height="24" ColumnDefinitions="20,*,Auto,Auto" Background="Transparent" ContextRequested="OnLocalBranchContextMenuRequested" DoubleTapped="OnDoubleTappedLocalBranchNode">
|
||||
<Grid Height="24" ColumnDefinitions="20,*,Auto,Auto" Background="Transparent" DoubleTapped="OnDoubleTappedLocalBranchNode">
|
||||
<Path Grid.Column="0" Classes="folder_icon" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,1,0,0" IsVisible="{Binding IsFolder}"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Check}" IsVisible="{Binding IsCurrent}" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="2,0,0,0" Data="{StaticResource Icons.Branch}" VerticalAlignment="Center">
|
||||
|
@ -250,20 +268,38 @@
|
|||
</Grid>
|
||||
<TreeView Grid.Row="7"
|
||||
x:Name="remoteBranchTree"
|
||||
Margin="4,0,2,0"
|
||||
SelectionMode="Multiple"
|
||||
ItemsSource="{Binding RemoteBranchTrees}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
LostFocus="OnRemoteBranchTreeLostFocus"
|
||||
ContextRequested="OnRemoteBranchContextMenuRequested"
|
||||
SelectionChanged="OnRemoteBranchTreeSelectionChanged">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
<Setter Property="CornerRadius">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static c:BranchTreeNodeConverters.ToCornerRadius}">
|
||||
<Binding Path="$parent[v:Repository].RefereshRemoteBranchSelectionToken"/>
|
||||
<Binding Path="$self"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
||||
<Grid Height="24" ColumnDefinitions="20,*,Auto" Background="Transparent" ContextRequested="OnRemoteBranchContextMenuRequested">
|
||||
<Grid Height="24" ColumnDefinitions="20,*,Auto" Background="Transparent">
|
||||
<Path Grid.Column="0" Classes="folder_icon" Width="10" Height="10" HorizontalAlignment="Left" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Remote}" IsVisible="{Binding IsRemote}" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="2,0,0,0" Data="{StaticResource Icons.Branch}" IsVisible="{Binding IsBranch}" VerticalAlignment="Center"/>
|
||||
|
@ -294,7 +330,9 @@
|
|||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid Grid.Row="9"
|
||||
x:Name="tagsList"
|
||||
MaxHeight="200"
|
||||
Margin="4,0,2,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Tags}"
|
||||
SelectionMode="Single"
|
||||
|
@ -307,10 +345,27 @@
|
|||
RowHeight="24"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
LostFocus="OnTagDataGridLostFocus"
|
||||
IsVisible="{Binding IsTagGroupExpanded, Mode=OneWay}"
|
||||
SelectionChanged="OnTagDataGridSelectionChanged"
|
||||
ContextRequested="OnTagContextRequested">
|
||||
<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="Grid.repository_leftpanel DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
|
@ -364,6 +419,7 @@
|
|||
</ToggleButton>
|
||||
<DataGrid Grid.Row="11"
|
||||
MaxHeight="200"
|
||||
Margin="4,0,6,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Submodules}"
|
||||
SelectionMode="Single"
|
||||
|
@ -378,6 +434,24 @@
|
|||
VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnSubmoduleContextRequested"
|
||||
IsVisible="{Binding IsSubmoduleGroupExpanded, 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>
|
||||
|
@ -390,7 +464,7 @@
|
|||
<DataGridTemplateColumn Width="*" Header="NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" Classes="monospace"/>
|
||||
<TextBlock Text="{Binding}" ClipToBounds="True" Classes="monospace"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia;
|
||||
|
@ -6,6 +7,7 @@ using Avalonia.Controls;
|
|||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
|
@ -56,6 +58,24 @@ namespace SourceGit.Views
|
|||
|
||||
public partial class Repository : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<ulong> RefereshLocalBranchSelectionTokenProperty =
|
||||
AvaloniaProperty.Register<Repository, ulong>(nameof(RefereshLocalBranchSelectionToken), 0);
|
||||
|
||||
public ulong RefereshLocalBranchSelectionToken
|
||||
{
|
||||
get => GetValue(RefereshLocalBranchSelectionTokenProperty);
|
||||
set => SetValue(RefereshLocalBranchSelectionTokenProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ulong> RefereshRemoteBranchSelectionTokenProperty =
|
||||
AvaloniaProperty.Register<Repository, ulong>(nameof(RefereshRemoteBranchSelectionToken), 0);
|
||||
|
||||
public ulong RefereshRemoteBranchSelectionToken
|
||||
{
|
||||
get => GetValue(RefereshRemoteBranchSelectionTokenProperty);
|
||||
set => SetValue(RefereshRemoteBranchSelectionTokenProperty, value);
|
||||
}
|
||||
|
||||
public Repository()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
@ -71,33 +91,20 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
private void OnLocalBranchTreeLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TreeView tree)
|
||||
tree.UnselectAll();
|
||||
}
|
||||
|
||||
private void OnRemoteBranchTreeLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TreeView tree)
|
||||
tree.UnselectAll();
|
||||
}
|
||||
|
||||
private void OnTagDataGridLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is DataGrid datagrid)
|
||||
datagrid.SelectedItem = null;
|
||||
}
|
||||
|
||||
private void OnLocalBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is TreeView tree && tree.SelectedItem != null)
|
||||
{
|
||||
remoteBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
var next = RefereshLocalBranchSelectionToken + 1;
|
||||
SetCurrentValue(RefereshLocalBranchSelectionTokenProperty, next);
|
||||
|
||||
if (tree.SelectedItems.Count == 1)
|
||||
{
|
||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
||||
if (node.IsBranch && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
||||
}
|
||||
}
|
||||
|
@ -108,10 +115,15 @@ namespace SourceGit.Views
|
|||
if (sender is TreeView tree && tree.SelectedItem != null)
|
||||
{
|
||||
localBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
var next = RefereshRemoteBranchSelectionToken + 1;
|
||||
SetCurrentValue(RefereshRemoteBranchSelectionTokenProperty, next);
|
||||
|
||||
if (tree.SelectedItems.Count == 1)
|
||||
{
|
||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
||||
if (node.IsBranch && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
||||
}
|
||||
}
|
||||
|
@ -121,31 +133,29 @@ namespace SourceGit.Views
|
|||
{
|
||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
|
||||
{
|
||||
localBranchTree.UnselectAll();
|
||||
remoteBranchTree.UnselectAll();
|
||||
|
||||
var tag = datagrid.SelectedItem as Models.Tag;
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
repo.NavigateToCommit(tag.SHA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
var grid = sender as Grid;
|
||||
if (e.Property == IsVisibleProperty && grid.IsVisible)
|
||||
{
|
||||
txtSearchCommitsBox.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
repo.StartSearchCommits();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
@ -171,10 +181,8 @@ namespace SourceGit.Views
|
|||
if (toggle.DataContext is ViewModels.BranchTreeNode node)
|
||||
{
|
||||
if (node.IsBranch)
|
||||
{
|
||||
filter = (node.Backend as Models.Branch).FullName;
|
||||
}
|
||||
}
|
||||
else if (toggle.DataContext is Models.Tag tag)
|
||||
{
|
||||
filter = tag.Name;
|
||||
|
@ -192,15 +200,38 @@ namespace SourceGit.Views
|
|||
private void OnLocalBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
remoteBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
if (sender is Grid grid && grid.DataContext is ViewModels.BranchTreeNode node)
|
||||
var repo = DataContext as ViewModels.Repository;
|
||||
var tree = sender as TreeView;
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
foreach (var item in tree.SelectedItems)
|
||||
CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode);
|
||||
|
||||
if (branches.Count == 1)
|
||||
{
|
||||
if (node.IsBranch && DataContext is ViewModels.Repository repo)
|
||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
||||
if (item != null)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForLocalBranch(node.Backend as Models.Branch);
|
||||
grid.OpenContextMenu(menu);
|
||||
var menu = repo.CreateContextMenuForLocalBranch(branches[0]);
|
||||
item.OpenContextMenu(menu);
|
||||
}
|
||||
}
|
||||
else if (branches.Count > 1 && branches.Find(x => x.IsCurrent) == null)
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
var deleteMulti = new MenuItem();
|
||||
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
||||
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
||||
deleteMulti.Click += (_, ev) =>
|
||||
{
|
||||
repo.DeleteMultipleBranches(branches, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(deleteMulti);
|
||||
tree.OpenContextMenu(menu);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
@ -208,20 +239,55 @@ namespace SourceGit.Views
|
|||
private void OnRemoteBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
localBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
if (sender is Grid grid && grid.DataContext is ViewModels.BranchTreeNode node && DataContext is ViewModels.Repository repo)
|
||||
var repo = DataContext as ViewModels.Repository;
|
||||
var tree = sender as TreeView;
|
||||
|
||||
if (tree.SelectedItems.Count == 1)
|
||||
{
|
||||
if (node.IsRemote)
|
||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
||||
if (node != null && node.IsRemote)
|
||||
{
|
||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
||||
if (item != null && item.DataContext == node)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForRemote(node.Backend as Models.Remote);
|
||||
grid.OpenContextMenu(menu);
|
||||
item.OpenContextMenu(menu);
|
||||
}
|
||||
else if (node.IsBranch)
|
||||
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
foreach (var item in tree.SelectedItems)
|
||||
CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode);
|
||||
|
||||
if (branches.Count == 1)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForRemoteBranch(node.Backend as Models.Branch);
|
||||
grid.OpenContextMenu(menu);
|
||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
||||
if (item != null)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForRemoteBranch(branches[0]);
|
||||
item.OpenContextMenu(menu);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
var deleteMulti = new MenuItem();
|
||||
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
||||
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
||||
deleteMulti.Click += (_, ev) =>
|
||||
{
|
||||
repo.DeleteMultipleBranches(branches, false);
|
||||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(deleteMulti);
|
||||
tree.OpenContextMenu(menu);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
@ -304,5 +370,23 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectBranchesFromNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
|
||||
{
|
||||
if (node == null || node.IsRemote)
|
||||
return;
|
||||
|
||||
if (node.IsFolder)
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
CollectBranchesFromNode(outs, child);
|
||||
}
|
||||
else
|
||||
{
|
||||
var b = node.Backend as Models.Branch;
|
||||
if (b != null && !outs.Contains(b))
|
||||
outs.Add(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,6 +137,9 @@ namespace SourceGit.Views
|
|||
var width = textView.Bounds.Width;
|
||||
foreach (var line in textView.VisualLines)
|
||||
{
|
||||
if (line.FirstDocumentLine == null)
|
||||
continue;
|
||||
|
||||
var index = line.FirstDocumentLine.LineNumber;
|
||||
if (index > _editor.DiffData.Lines.Count)
|
||||
break;
|
||||
|
@ -517,6 +520,9 @@ namespace SourceGit.Views
|
|||
var infos = _editor.IsOld ? _editor.DiffData.Old : _editor.DiffData.New;
|
||||
foreach (var line in textView.VisualLines)
|
||||
{
|
||||
if (line.FirstDocumentLine == null)
|
||||
continue;
|
||||
|
||||
var index = line.FirstDocumentLine.LineNumber;
|
||||
if (index > infos.Count)
|
||||
break;
|
||||
|
|
Loading…
Reference in a new issue