mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
Merge branch 'develop' into graph_thickness
This commit is contained in:
commit
2c310e8cd4
17 changed files with 614 additions and 509 deletions
|
@ -4,7 +4,7 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class Commit : Command
|
public class Commit : Command
|
||||||
{
|
{
|
||||||
public Commit(string repo, string message, bool amend, bool allowEmpty = false)
|
public Commit(string repo, string message, bool autoStage, bool amend, bool allowEmpty = false)
|
||||||
{
|
{
|
||||||
var file = Path.GetTempFileName();
|
var file = Path.GetTempFileName();
|
||||||
File.WriteAllText(file, message);
|
File.WriteAllText(file, message);
|
||||||
|
@ -12,6 +12,8 @@ namespace SourceGit.Commands
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = $"commit --file=\"{file}\"";
|
Args = $"commit --file=\"{file}\"";
|
||||||
|
if (autoStage)
|
||||||
|
Args += " --all";
|
||||||
if (amend)
|
if (amend)
|
||||||
Args += " --amend --no-edit";
|
Args += " --amend --no-edit";
|
||||||
if (allowEmpty)
|
if (allowEmpty)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
namespace SourceGit.Converters
|
namespace SourceGit.Converters
|
||||||
{
|
{
|
||||||
|
@ -24,5 +25,8 @@ namespace SourceGit.Converters
|
||||||
|
|
||||||
public static readonly FuncValueConverter<int, bool> IsSubjectLengthGood =
|
public static readonly FuncValueConverter<int, bool> IsSubjectLengthGood =
|
||||||
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preference.Instance.SubjectGuideLength);
|
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preference.Instance.SubjectGuideLength);
|
||||||
|
|
||||||
|
public static readonly FuncValueConverter<int, Thickness> ToTreeMargin =
|
||||||
|
new FuncValueConverter<int, Thickness>(v => new Thickness(v * 16, 0, 0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -543,6 +543,8 @@
|
||||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">Ignore files in the same folder</x:String>
|
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">Ignore files in the same folder</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignore this file only</x:String>
|
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignore this file only</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String>
|
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String>
|
||||||
|
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String>
|
||||||
|
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">You can stage this file now.</x:String>
|
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">You can stage this file now.</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
|
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT & PUSH</x:String>
|
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT & PUSH</x:String>
|
||||||
|
|
|
@ -545,6 +545,8 @@
|
||||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同目录下所有文件</x:String>
|
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同目录下所有文件</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本文件</x:String>
|
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本文件</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String>
|
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String>
|
||||||
|
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自动暂存(--all)</x:String>
|
||||||
|
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自动将修改过和删除的文件加入暂存区,但新增文件需要手动添加。</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">现在您已可将其加入暂存区中</x:String>
|
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">现在您已可将其加入暂存区中</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
|
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交并推送</x:String>
|
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交并推送</x:String>
|
||||||
|
|
|
@ -545,6 +545,8 @@
|
||||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同路徑下所有檔案</x:String>
|
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同路徑下所有檔案</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本檔案</x:String>
|
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本檔案</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修補(--amend)</x:String>
|
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修補(--amend)</x:String>
|
||||||
|
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自動暫存(--all)</x:String>
|
||||||
|
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自動將修改過和刪除的檔案加入暫存區,但新增檔案需要手動添加。</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">現在您已可將其加入暫存區中</x:String>
|
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">現在您已可將其加入暫存區中</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
|
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
|
||||||
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交併推送</x:String>
|
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交併推送</x:String>
|
||||||
|
|
|
@ -772,6 +772,7 @@
|
||||||
<Popup Name="PART_Popup"
|
<Popup Name="PART_Popup"
|
||||||
WindowManagerAddShadowHint="False"
|
WindowManagerAddShadowHint="False"
|
||||||
Placement="RightEdgeAlignedTop"
|
Placement="RightEdgeAlignedTop"
|
||||||
|
MaxHeight="400"
|
||||||
IsLightDismissEnabled="False"
|
IsLightDismissEnabled="False"
|
||||||
HorizontalOffset="-4"
|
HorizontalOffset="-4"
|
||||||
VerticalOffset="-4"
|
VerticalOffset="-4"
|
||||||
|
@ -793,7 +794,13 @@
|
||||||
<ItemsPresenter Name="PART_ItemsPresenter"
|
<ItemsPresenter Name="PART_ItemsPresenter"
|
||||||
ItemsPanel="{TemplateBinding ItemsPanel}"
|
ItemsPanel="{TemplateBinding ItemsPanel}"
|
||||||
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
|
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
|
||||||
Grid.IsSharedSizeScope="True" />
|
Grid.IsSharedSizeScope="True">
|
||||||
|
<ItemsPresenter.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel Orientation="Vertical"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsPresenter.ItemsPanel>
|
||||||
|
</ItemsPresenter>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,122 +1,63 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
namespace SourceGit.ViewModels
|
namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
public enum BranchTreeNodeType
|
|
||||||
{
|
|
||||||
DetachedHead,
|
|
||||||
Remote,
|
|
||||||
Folder,
|
|
||||||
Branch,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BranchTreeNode : ObservableObject
|
public class BranchTreeNode : ObservableObject
|
||||||
{
|
{
|
||||||
public const double DEFAULT_CORNER = 4.0;
|
public string Name { get; private set; } = string.Empty;
|
||||||
|
public object Backend { get; private set; } = null;
|
||||||
public string Name { get; set; }
|
public int Depth { get; set; } = 0;
|
||||||
public BranchTreeNodeType Type { get; set; }
|
public bool IsFiltered { get; set; } = false;
|
||||||
public object Backend { get; set; }
|
public bool IsSelected { get; set; } = false;
|
||||||
public bool IsFiltered { get; set; }
|
public List<BranchTreeNode> Children { get; private set; } = new List<BranchTreeNode>();
|
||||||
public List<BranchTreeNode> Children { get; set; } = new List<BranchTreeNode>();
|
|
||||||
|
|
||||||
public bool IsUpstreamTrackStatusVisible
|
|
||||||
{
|
|
||||||
get => IsBranch && !string.IsNullOrEmpty((Backend as Models.Branch).UpstreamTrackStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UpstreamTrackStatus
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Branch ? (Backend as Models.Branch).UpstreamTrackStatus : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsRemote
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Remote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsFolder
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsBranch
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Branch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsDetachedHead
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.DetachedHead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCurrent
|
|
||||||
{
|
|
||||||
get => IsBranch && (Backend as Models.Branch).IsCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSelected
|
|
||||||
{
|
|
||||||
get => _isSelected;
|
|
||||||
set => SetProperty(ref _isSelected, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsExpanded
|
public bool IsExpanded
|
||||||
{
|
{
|
||||||
get => _isExpanded;
|
get => _isExpanded;
|
||||||
set => SetProperty(ref _isExpanded, value);
|
set => SetProperty(ref _isExpanded, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Tooltip
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Backend is Models.Branch b)
|
|
||||||
return b.FriendlyName;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CornerRadius CornerRadius
|
public CornerRadius CornerRadius
|
||||||
{
|
{
|
||||||
get => _cornerRadius;
|
get => _cornerRadius;
|
||||||
set => SetProperty(ref _cornerRadius, value);
|
set => SetProperty(ref _cornerRadius, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCornerRadius(ref BranchTreeNode prev)
|
public bool IsBranch
|
||||||
{
|
{
|
||||||
if (_isSelected && prev != null && prev.IsSelected)
|
get => Backend is Models.Branch;
|
||||||
{
|
|
||||||
var prevTop = prev.CornerRadius.TopLeft;
|
|
||||||
prev.CornerRadius = new CornerRadius(prevTop, 0);
|
|
||||||
CornerRadius = new CornerRadius(0, DEFAULT_CORNER);
|
|
||||||
}
|
|
||||||
else if (CornerRadius.TopLeft != DEFAULT_CORNER ||
|
|
||||||
CornerRadius.BottomLeft != DEFAULT_CORNER)
|
|
||||||
{
|
|
||||||
CornerRadius = new CornerRadius(DEFAULT_CORNER);
|
|
||||||
}
|
|
||||||
|
|
||||||
prev = this;
|
|
||||||
|
|
||||||
if (!IsBranch && IsExpanded)
|
|
||||||
{
|
|
||||||
foreach (var child in Children)
|
|
||||||
child.UpdateCornerRadius(ref prev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isSelected = false;
|
public bool IsUpstreamTrackStatusVisible
|
||||||
|
{
|
||||||
|
get => Backend is Models.Branch { IsLocal: true } branch && !string.IsNullOrEmpty(branch.UpstreamTrackStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UpstreamTrackStatus
|
||||||
|
{
|
||||||
|
get => Backend is Models.Branch branch ? branch.UpstreamTrackStatus : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontWeight NameFontWeight
|
||||||
|
{
|
||||||
|
get => Backend is Models.Branch { IsCurrent: true } ? FontWeight.Bold : FontWeight.Regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Tooltip
|
||||||
|
{
|
||||||
|
get => Backend is Models.Branch b ? b.FriendlyName : null;
|
||||||
|
}
|
||||||
|
|
||||||
private bool _isExpanded = false;
|
private bool _isExpanded = false;
|
||||||
private CornerRadius _cornerRadius = new CornerRadius(DEFAULT_CORNER);
|
private CornerRadius _cornerRadius = new CornerRadius(4);
|
||||||
|
|
||||||
public class Builder
|
public class Builder
|
||||||
{
|
{
|
||||||
|
@ -133,7 +74,6 @@ namespace SourceGit.ViewModels
|
||||||
var node = new BranchTreeNode()
|
var node = new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = remote.Name,
|
Name = remote.Name,
|
||||||
Type = BranchTreeNodeType.Remote,
|
|
||||||
Backend = remote,
|
Backend = remote,
|
||||||
IsExpanded = bForceExpanded || _expanded.Contains(path),
|
IsExpanded = bForceExpanded || _expanded.Contains(path),
|
||||||
};
|
};
|
||||||
|
@ -176,9 +116,13 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
|
if (node.Backend is Models.Branch)
|
||||||
|
continue;
|
||||||
|
|
||||||
var path = prefix + "/" + node.Name;
|
var path = prefix + "/" + node.Name;
|
||||||
if (node.Type != BranchTreeNodeType.Branch && node.IsExpanded)
|
if (node.IsExpanded)
|
||||||
_expanded.Add(path);
|
_expanded.Add(path);
|
||||||
|
|
||||||
CollectExpandedNodes(node.Children, path);
|
CollectExpandedNodes(node.Children, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +135,6 @@ namespace SourceGit.ViewModels
|
||||||
roots.Add(new BranchTreeNode()
|
roots.Add(new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = branch.Name,
|
Name = branch.Name,
|
||||||
Type = BranchTreeNodeType.Branch,
|
|
||||||
Backend = branch,
|
Backend = branch,
|
||||||
IsExpanded = false,
|
IsExpanded = false,
|
||||||
IsFiltered = isFiltered,
|
IsFiltered = isFiltered,
|
||||||
|
@ -215,7 +158,6 @@ namespace SourceGit.ViewModels
|
||||||
lastFolder = new BranchTreeNode()
|
lastFolder = new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = BranchTreeNodeType.Folder,
|
|
||||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||||
};
|
};
|
||||||
roots.Add(lastFolder);
|
roots.Add(lastFolder);
|
||||||
|
@ -226,7 +168,6 @@ namespace SourceGit.ViewModels
|
||||||
var cur = new BranchTreeNode()
|
var cur = new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = BranchTreeNodeType.Folder,
|
|
||||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||||
};
|
};
|
||||||
lastFolder.Children.Add(cur);
|
lastFolder.Children.Add(cur);
|
||||||
|
@ -238,10 +179,9 @@ namespace SourceGit.ViewModels
|
||||||
sepIdx = branch.Name.IndexOf('/', start);
|
sepIdx = branch.Name.IndexOf('/', start);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFolder.Children.Add(new BranchTreeNode()
|
lastFolder?.Children.Add(new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = Path.GetFileName(branch.Name),
|
Name = Path.GetFileName(branch.Name),
|
||||||
Type = branch.IsHead ? BranchTreeNodeType.DetachedHead : BranchTreeNodeType.Branch,
|
|
||||||
Backend = branch,
|
Backend = branch,
|
||||||
IsExpanded = false,
|
IsExpanded = false,
|
||||||
IsFiltered = isFiltered,
|
IsFiltered = isFiltered,
|
||||||
|
@ -252,16 +192,13 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
nodes.Sort((l, r) =>
|
nodes.Sort((l, r) =>
|
||||||
{
|
{
|
||||||
if (l.Type == BranchTreeNodeType.DetachedHead)
|
if (l.Backend is Models.Branch { IsHead: true })
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
if (l.Type == r.Type)
|
|
||||||
{
|
|
||||||
return l.Name.CompareTo(r.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)l.Type - (int)r.Type;
|
if (l.Backend is Models.Branch)
|
||||||
|
return r.Backend is Models.Branch ? string.Compare(l.Name, r.Name, StringComparison.Ordinal) : 1;
|
||||||
|
|
||||||
|
return r.Backend is Models.Branch ? -1 : string.Compare(l.Name, r.Name, StringComparison.Ordinal);
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
|
|
|
@ -64,6 +64,12 @@ namespace SourceGit.ViewModels
|
||||||
set;
|
set;
|
||||||
} = true;
|
} = true;
|
||||||
|
|
||||||
|
public bool AutoStageBeforeCommit
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = false;
|
||||||
|
|
||||||
public AvaloniaList<string> Filters
|
public AvaloniaList<string> Filters
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
@ -1905,7 +1911,7 @@ namespace SourceGit.ViewModels
|
||||||
visibles.Add(b);
|
visibles.Add(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Run(visibles, remotes, visibles.Count <= 20);
|
builder.Run(visibles, remotes, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
return Task.Run(() =>
|
return Task.Run(() =>
|
||||||
{
|
{
|
||||||
var succ = new Commands.Commit(_repo.FullPath, _message, true, true).Exec();
|
var succ = new Commands.Commit(_repo.FullPath, _message, false, true, true).Exec();
|
||||||
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
||||||
return succ;
|
return succ;
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
var succ = new Commands.Reset(_repo.FullPath, Parent.SHA, "--soft").Exec();
|
var succ = new Commands.Reset(_repo.FullPath, Parent.SHA, "--soft").Exec();
|
||||||
if (succ)
|
if (succ)
|
||||||
succ = new Commands.Commit(_repo.FullPath, _message, true).Exec();
|
succ = new Commands.Commit(_repo.FullPath, _message, false, true).Exec();
|
||||||
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
||||||
return succ;
|
return succ;
|
||||||
});
|
});
|
||||||
|
|
|
@ -77,6 +77,12 @@ namespace SourceGit.ViewModels
|
||||||
private set => SetProperty(ref _isCommitting, value);
|
private set => SetProperty(ref _isCommitting, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AutoStageBeforeCommit
|
||||||
|
{
|
||||||
|
get => _repo.Settings.AutoStageBeforeCommit;
|
||||||
|
set => _repo.Settings.AutoStageBeforeCommit = value;
|
||||||
|
}
|
||||||
|
|
||||||
public bool UseAmend
|
public bool UseAmend
|
||||||
{
|
{
|
||||||
get => _useAmend;
|
get => _useAmend;
|
||||||
|
@ -1216,7 +1222,13 @@ namespace SourceGit.ViewModels
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_staged.Count == 0)
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
App.RaiseException(_repo.FullPath, "No files added to commit!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AutoStageBeforeCommit && _staged.Count == 0)
|
||||||
{
|
{
|
||||||
App.RaiseException(_repo.FullPath, "No files added to commit!");
|
App.RaiseException(_repo.FullPath, "No files added to commit!");
|
||||||
return;
|
return;
|
||||||
|
@ -1234,9 +1246,10 @@ namespace SourceGit.ViewModels
|
||||||
IsCommitting = true;
|
IsCommitting = true;
|
||||||
_repo.SetWatcherEnabled(false);
|
_repo.SetWatcherEnabled(false);
|
||||||
|
|
||||||
|
var autoStage = AutoStageBeforeCommit;
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend).Exec();
|
var succ = new Commands.Commit(_repo.FullPath, _commitMessage, autoStage, _useAmend).Exec();
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
if (succ)
|
if (succ)
|
||||||
|
|
108
src/Views/BranchTree.axaml
Normal file
108
src/Views/BranchTree.axaml
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<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:c="using:SourceGit.Converters"
|
||||||
|
xmlns:v="using:SourceGit.Views"
|
||||||
|
xmlns:vm="using:SourceGit.ViewModels"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="SourceGit.Views.BranchTree"
|
||||||
|
x:Name="ThisControl">
|
||||||
|
<DataGrid x:Name="BranchesPresenter"
|
||||||
|
ItemsSource="{Binding #ThisControl.Rows}"
|
||||||
|
Background="Transparent"
|
||||||
|
RowHeight="24"
|
||||||
|
CanUserReorderColumns="False"
|
||||||
|
CanUserResizeColumns="False"
|
||||||
|
CanUserSortColumns="False"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
HeadersVisibility="None"
|
||||||
|
SelectionChanged="OnNodesSelectionChanged"
|
||||||
|
ContextRequested="OnTreeContextRequested">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow" x:DataType="vm:BranchTreeNode">
|
||||||
|
<Setter Property="CornerRadius" Value="{Binding CornerRadius}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||||
|
<Setter Property="ClipToBounds" Value="True" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Grid.repository_leftpanel DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||||
|
<Setter Property="Opacity" Value=".5"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid.repository_leftpanel DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||||
|
<Setter Property="Opacity" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||||
|
<Setter Property="Opacity" Value=".65"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||||
|
<Setter Property="Opacity" Value=".8"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Width="*">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:BranchTreeNode">
|
||||||
|
<Grid Height="24"
|
||||||
|
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||||
|
ColumnDefinitions="16,20,*,Auto,Auto"
|
||||||
|
Background="Transparent"
|
||||||
|
DoubleTapped="OnDoubleTappedBranchNode"
|
||||||
|
ToolTip.Tip="{Binding Tooltip}">
|
||||||
|
|
||||||
|
<!-- Tree Expander -->
|
||||||
|
<ToggleButton Grid.Column="0"
|
||||||
|
Classes="tree_expander"
|
||||||
|
Focusable="False"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
IsChecked="{Binding IsExpanded}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
IsVisible="{Binding !IsBranch}"/>
|
||||||
|
|
||||||
|
<!-- Icon -->
|
||||||
|
<v:BranchTreeNodeIcon Grid.Column="1"
|
||||||
|
Node="{Binding}"
|
||||||
|
IsExpanded="{Binding IsExpanded}"/>
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Text="{Binding Name}"
|
||||||
|
Classes="monospace"
|
||||||
|
FontWeight="{Binding NameFontWeight}"/>
|
||||||
|
|
||||||
|
<!-- Tracking status -->
|
||||||
|
<Border Grid.Column="3"
|
||||||
|
Margin="8,0"
|
||||||
|
Height="18"
|
||||||
|
CornerRadius="9"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource Brush.Badge}"
|
||||||
|
IsVisible="{Binding IsUpstreamTrackStatusVisible}">
|
||||||
|
<TextBlock Classes="monospace" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding UpstreamTrackStatus}" Foreground="{DynamicResource Brush.BadgeFG}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Filter Toggle Button -->
|
||||||
|
<ToggleButton Grid.Column="4"
|
||||||
|
Classes="filter"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
Background="Transparent"
|
||||||
|
IsCheckedChanged="OnToggleFilter"
|
||||||
|
IsVisible="{Binding IsBranch}"
|
||||||
|
IsChecked="{Binding IsFiltered}"
|
||||||
|
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</UserControl>
|
||||||
|
|
347
src/Views/BranchTree.axaml.cs
Normal file
347
src/Views/BranchTree.axaml.cs
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Controls.Shapes;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
|
namespace SourceGit.Views
|
||||||
|
{
|
||||||
|
public class BranchTreeNodeIcon : UserControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<ViewModels.BranchTreeNode> NodeProperty =
|
||||||
|
AvaloniaProperty.Register<BranchTreeNodeIcon, ViewModels.BranchTreeNode>(nameof(Node));
|
||||||
|
|
||||||
|
public ViewModels.BranchTreeNode Node
|
||||||
|
{
|
||||||
|
get => GetValue(NodeProperty);
|
||||||
|
set => SetValue(NodeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsExpandedProperty =
|
||||||
|
AvaloniaProperty.Register<BranchTreeNodeIcon, bool>(nameof(IsExpanded));
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => GetValue(IsExpandedProperty);
|
||||||
|
set => SetValue(IsExpandedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BranchTreeNodeIcon()
|
||||||
|
{
|
||||||
|
NodeProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||||
|
IsExpandedProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateContent()
|
||||||
|
{
|
||||||
|
var node = Node;
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
Content = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Backend is Models.Remote)
|
||||||
|
{
|
||||||
|
CreateContent(12, new Thickness(0,2,0,0), "Icons.Remote");
|
||||||
|
}
|
||||||
|
else if (node.Backend is Models.Branch branch)
|
||||||
|
{
|
||||||
|
if (branch.IsCurrent)
|
||||||
|
CreateContent(12, new Thickness(0,2,0,0), "Icons.Check");
|
||||||
|
else
|
||||||
|
CreateContent(12, new Thickness(2,0,0,0), "Icons.Branch");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (node.IsExpanded)
|
||||||
|
CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Open");
|
||||||
|
else
|
||||||
|
CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Fill");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateContent(double size, Thickness margin, string iconKey)
|
||||||
|
{
|
||||||
|
var geo = this.FindResource(iconKey) as StreamGeometry;
|
||||||
|
if (geo == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Content = new Path()
|
||||||
|
{
|
||||||
|
Width = size,
|
||||||
|
Height = size,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Margin = margin,
|
||||||
|
Data = geo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class BranchTree : UserControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<List<ViewModels.BranchTreeNode>> NodesProperty =
|
||||||
|
AvaloniaProperty.Register<BranchTree, List<ViewModels.BranchTreeNode>>(nameof(Nodes));
|
||||||
|
|
||||||
|
public List<ViewModels.BranchTreeNode> Nodes
|
||||||
|
{
|
||||||
|
get => GetValue(NodesProperty);
|
||||||
|
set => SetValue(NodesProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<ViewModels.BranchTreeNode> Rows
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
} = new AvaloniaList<ViewModels.BranchTreeNode>();
|
||||||
|
|
||||||
|
public static readonly RoutedEvent<RoutedEventArgs> SelectionChangedEvent =
|
||||||
|
RoutedEvent.Register<BranchTree, RoutedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||||
|
|
||||||
|
public event EventHandler<RoutedEventArgs> SelectionChanged
|
||||||
|
{
|
||||||
|
add { AddHandler(SelectionChangedEvent, value); }
|
||||||
|
remove { RemoveHandler(SelectionChangedEvent, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly RoutedEvent<RoutedEventArgs> RowsChangedEvent =
|
||||||
|
RoutedEvent.Register<BranchTree, RoutedEventArgs>(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||||
|
|
||||||
|
public event EventHandler<RoutedEventArgs> RowsChanged
|
||||||
|
{
|
||||||
|
add { AddHandler(RowsChangedEvent, value); }
|
||||||
|
remove { RemoveHandler(RowsChangedEvent, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public BranchTree()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnselectAll()
|
||||||
|
{
|
||||||
|
BranchesPresenter.SelectedItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSizeChanged(SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnSizeChanged(e);
|
||||||
|
|
||||||
|
if (Bounds.Height >= 23.0)
|
||||||
|
BranchesPresenter.Height = Bounds.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
|
||||||
|
if (change.Property == NodesProperty)
|
||||||
|
{
|
||||||
|
Rows.Clear();
|
||||||
|
|
||||||
|
if (Nodes is { Count: > 0 })
|
||||||
|
{
|
||||||
|
var rows = new List<ViewModels.BranchTreeNode>();
|
||||||
|
MakeRows(rows, Nodes, 0);
|
||||||
|
Rows.AddRange(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||||
|
}
|
||||||
|
else if (change.Property == IsVisibleProperty)
|
||||||
|
{
|
||||||
|
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNodesSelectionChanged(object _, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var repo = DataContext as ViewModels.Repository;
|
||||||
|
if (repo?.Settings == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var item in e.AddedItems)
|
||||||
|
{
|
||||||
|
if (item is ViewModels.BranchTreeNode node)
|
||||||
|
node.IsSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in e.RemovedItems)
|
||||||
|
{
|
||||||
|
if (item is ViewModels.BranchTreeNode node)
|
||||||
|
node.IsSelected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected = BranchesPresenter.SelectedItems;
|
||||||
|
if (selected == null || selected.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||||
|
repo.NavigateToCommit(branch.Head);
|
||||||
|
|
||||||
|
var prev = null as ViewModels.BranchTreeNode;
|
||||||
|
foreach (var row in Rows)
|
||||||
|
{
|
||||||
|
if (row.IsSelected)
|
||||||
|
{
|
||||||
|
if (prev is { IsSelected: true })
|
||||||
|
{
|
||||||
|
var prevTop = prev.CornerRadius.TopLeft;
|
||||||
|
prev.CornerRadius = new CornerRadius(prevTop, 0);
|
||||||
|
row.CornerRadius = new CornerRadius(0, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.CornerRadius = new CornerRadius(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTreeContextRequested(object _1, ContextRequestedEventArgs _2)
|
||||||
|
{
|
||||||
|
var repo = DataContext as ViewModels.Repository;
|
||||||
|
if (repo?.Settings == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var selected = BranchesPresenter.SelectedItems;
|
||||||
|
if (selected == null || selected.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote })
|
||||||
|
{
|
||||||
|
var menu = repo.CreateContextMenuForRemote(remote);
|
||||||
|
this.OpenContextMenu(menu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var branches = new List<Models.Branch>();
|
||||||
|
foreach (var item in selected)
|
||||||
|
{
|
||||||
|
if (item is ViewModels.BranchTreeNode node)
|
||||||
|
CollectBranchesInNode(branches, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branches.Count == 1)
|
||||||
|
{
|
||||||
|
var branch = branches[0];
|
||||||
|
var menu = branch.IsLocal ?
|
||||||
|
repo.CreateContextMenuForLocalBranch(branch) :
|
||||||
|
repo.CreateContextMenuForRemoteBranch(branch);
|
||||||
|
this.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
else if (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, branches[0].IsLocal);
|
||||||
|
ev.Handled = true;
|
||||||
|
};
|
||||||
|
menu.Items.Add(deleteMulti);
|
||||||
|
this.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoubleTappedBranchNode(object sender, TappedEventArgs _)
|
||||||
|
{
|
||||||
|
if (sender is Grid { DataContext: ViewModels.BranchTreeNode node })
|
||||||
|
{
|
||||||
|
if (node.Backend is Models.Branch branch)
|
||||||
|
{
|
||||||
|
if (branch.IsCurrent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (DataContext is ViewModels.Repository { Settings: not null } repo)
|
||||||
|
repo.CheckoutBranch(branch);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.IsExpanded = !node.IsExpanded;
|
||||||
|
|
||||||
|
var rows = Rows;
|
||||||
|
var depth = node.Depth;
|
||||||
|
var idx = rows.IndexOf(node);
|
||||||
|
if (idx == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (node.IsExpanded)
|
||||||
|
{
|
||||||
|
var subtree = new List<ViewModels.BranchTreeNode>();
|
||||||
|
MakeRows(subtree, node.Children, depth + 1);
|
||||||
|
rows.InsertRange(idx + 1, subtree);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var removeCount = 0;
|
||||||
|
for (int i = idx + 1; i < rows.Count; i++)
|
||||||
|
{
|
||||||
|
var row = rows[i];
|
||||||
|
if (row.Depth <= depth)
|
||||||
|
break;
|
||||||
|
|
||||||
|
removeCount++;
|
||||||
|
}
|
||||||
|
rows.RemoveRange(idx + 1, removeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToggleFilter(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
if (toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||||
|
repo.UpdateFilter(branch.FullName, toggle.IsChecked == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MakeRows(List<ViewModels.BranchTreeNode> rows, List<ViewModels.BranchTreeNode> nodes, int depth)
|
||||||
|
{
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
node.Depth = depth;
|
||||||
|
rows.Add(node);
|
||||||
|
|
||||||
|
if (!node.IsExpanded || node.Backend is Models.Branch)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
MakeRows(rows, node.Children, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CollectBranchesInNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
|
||||||
|
{
|
||||||
|
if (node.Backend is Models.Branch branch && !outs.Contains(branch))
|
||||||
|
{
|
||||||
|
outs.Add(branch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var sub in node.Children)
|
||||||
|
CollectBranchesInNode(outs, sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:m="using:SourceGit.Models"
|
|
||||||
xmlns:vm="using:SourceGit.ViewModels"
|
xmlns:vm="using:SourceGit.ViewModels"
|
||||||
xmlns:v="using:SourceGit.Views"
|
|
||||||
xmlns:c="using:SourceGit.Converters"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="SourceGit.Views.DeleteBranch"
|
x:Class="SourceGit.Views.DeleteBranch"
|
||||||
x:DataType="vm:DeleteBranch">
|
x:DataType="vm:DeleteBranch">
|
||||||
|
@ -18,7 +15,7 @@
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Text="{DynamicResource Text.DeleteBranch.Branch}"/>
|
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Text="{DynamicResource Text.DeleteBranch.Branch}"/>
|
||||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
|
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
|
||||||
<Path Width="14" Height="14" Margin="8,0" Data="{StaticResource Icons.Branch}"/>
|
<Path Width="14" Height="14" Margin="8,0" Data="{StaticResource Icons.Branch}"/>
|
||||||
<TextBlock Text="{Binding Target.FriendlyName}}"/>
|
<TextBlock Text="{Binding Target.FriendlyName}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Border Grid.Row="1" Grid.Column="1" Height="32" IsVisible="{Binding !Target.IsLocal}">
|
<Border Grid.Row="1" Grid.Column="1" Height="32" IsVisible="{Binding !Target.IsLocal}">
|
||||||
|
|
|
@ -236,77 +236,14 @@
|
||||||
<ToggleButton Grid.Row="0" Classes="group_expander" IsChecked="{Binding IsLocalBranchGroupExpanded, Mode=TwoWay}">
|
<ToggleButton Grid.Row="0" Classes="group_expander" IsChecked="{Binding IsLocalBranchGroupExpanded, Mode=TwoWay}">
|
||||||
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<TreeView Grid.Row="1"
|
<v:BranchTree Grid.Row="1"
|
||||||
x:Name="localBranchTree"
|
x:Name="localBranchTree"
|
||||||
Margin="8,0,4,0"
|
Height="0"
|
||||||
SelectionMode="Multiple"
|
Margin="8,0,4,0"
|
||||||
ItemsSource="{Binding LocalBranchTrees}"
|
Nodes="{Binding LocalBranchTrees}"
|
||||||
IsVisible="{Binding IsLocalBranchGroupExpanded}"
|
IsVisible="{Binding IsLocalBranchGroupExpanded}"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
SelectionChanged="OnLocalBranchTreeSelectionChanged"
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
RowsChanged="OnBranchTreeRowsChanged"/>
|
||||||
ContextRequested="OnLocalBranchContextMenuRequested"
|
|
||||||
SelectionChanged="OnLocalBranchTreeSelectionChanged"
|
|
||||||
PropertyChanged="OnLeftSidebarTreeViewPropertyChanged">
|
|
||||||
<TreeView.Styles>
|
|
||||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value="1"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".8"/>
|
|
||||||
</Style>
|
|
||||||
</TreeView.Styles>
|
|
||||||
<TreeView.ItemTemplate>
|
|
||||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
|
||||||
<Grid Height="24" ColumnDefinitions="20,*,Auto,Auto" Background="Transparent" DoubleTapped="OnDoubleTappedBranchNode" ToolTip.Tip="{Binding Tooltip}">
|
|
||||||
<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">
|
|
||||||
<Path.IsVisible>
|
|
||||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
|
||||||
<Binding Path="!IsFolder"/>
|
|
||||||
<Binding Path="!IsCurrent"/>
|
|
||||||
</MultiBinding>
|
|
||||||
</Path.IsVisible>
|
|
||||||
</Path>
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
|
||||||
Text="{Binding Name}"
|
|
||||||
Classes="monospace"
|
|
||||||
FontWeight="{Binding IsCurrent, Converter={x:Static c:BoolConverters.BoldIfTrue}}"/>
|
|
||||||
|
|
||||||
<Border Grid.Column="2" Margin="8,0" Height="18" CornerRadius="9" VerticalAlignment="Center" Background="{DynamicResource Brush.Badge}" IsVisible="{Binding IsUpstreamTrackStatusVisible}">
|
|
||||||
<TextBlock Classes="monospace" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding UpstreamTrackStatus}" Foreground="{DynamicResource Brush.BadgeFG}"/>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<ToggleButton Grid.Column="3"
|
|
||||||
Classes="filter"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Background="Transparent"
|
|
||||||
IsVisible="{Binding IsBranch}"
|
|
||||||
Checked="OnToggleFilter"
|
|
||||||
Unchecked="OnToggleFilter"
|
|
||||||
IsChecked="{Binding IsFiltered}"
|
|
||||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
|
||||||
</Grid>
|
|
||||||
</TreeDataTemplate>
|
|
||||||
</TreeView.ItemTemplate>
|
|
||||||
</TreeView>
|
|
||||||
|
|
||||||
<!-- Remotes -->
|
<!-- Remotes -->
|
||||||
<ToggleButton Grid.Row="2" Classes="group_expander" IsChecked="{Binding IsRemoteGroupExpanded, Mode=TwoWay}">
|
<ToggleButton Grid.Row="2" Classes="group_expander" IsChecked="{Binding IsRemoteGroupExpanded, Mode=TwoWay}">
|
||||||
|
@ -317,64 +254,14 @@
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<TreeView Grid.Row="3"
|
<v:BranchTree Grid.Row="3"
|
||||||
x:Name="remoteBranchTree"
|
x:Name="remoteBranchTree"
|
||||||
Margin="8,0,4,0"
|
Height="0"
|
||||||
SelectionMode="Multiple"
|
Margin="8,0,4,0"
|
||||||
ItemsSource="{Binding RemoteBranchTrees}"
|
Nodes="{Binding RemoteBranchTrees}"
|
||||||
IsVisible="{Binding IsRemoteGroupExpanded}"
|
IsVisible="{Binding IsRemoteGroupExpanded}"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
SelectionChanged="OnRemoteBranchTreeSelectionChanged"
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
RowsChanged="OnBranchTreeRowsChanged"/>
|
||||||
ContextRequested="OnRemoteBranchContextMenuRequested"
|
|
||||||
SelectionChanged="OnRemoteBranchTreeSelectionChanged"
|
|
||||||
PropertyChanged="OnLeftSidebarTreeViewPropertyChanged">
|
|
||||||
<TreeView.Styles>
|
|
||||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value="1"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".8"/>
|
|
||||||
</Style>
|
|
||||||
</TreeView.Styles>
|
|
||||||
|
|
||||||
<TreeView.ItemTemplate>
|
|
||||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
|
||||||
<Grid Height="24" ColumnDefinitions="20,*,Auto" Background="Transparent" DoubleTapped="OnDoubleTappedBranchNode" ToolTip.Tip="{Binding Tooltip}">
|
|
||||||
<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"/>
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Name}" Classes="monospace"/>
|
|
||||||
|
|
||||||
<ToggleButton Grid.Column="2"
|
|
||||||
Classes="filter"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Background="Transparent"
|
|
||||||
Checked="OnToggleFilter"
|
|
||||||
Unchecked="OnToggleFilter"
|
|
||||||
IsVisible="{Binding IsBranch}"
|
|
||||||
IsChecked="{Binding IsFiltered}"
|
|
||||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
|
||||||
</Grid>
|
|
||||||
</TreeDataTemplate>
|
|
||||||
</TreeView.ItemTemplate>
|
|
||||||
</TreeView>
|
|
||||||
|
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
|
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
|
||||||
|
@ -388,6 +275,7 @@
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<DataGrid Grid.Row="5"
|
<DataGrid Grid.Row="5"
|
||||||
x:Name="tagsList"
|
x:Name="tagsList"
|
||||||
|
Height="0"
|
||||||
Margin="8,0,4,0"
|
Margin="8,0,4,0"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
ItemsSource="{Binding VisibleTags}"
|
ItemsSource="{Binding VisibleTags}"
|
||||||
|
@ -455,8 +343,8 @@
|
||||||
<ToggleButton Classes="filter"
|
<ToggleButton Classes="filter"
|
||||||
Margin="0,0,8,0"
|
Margin="0,0,8,0"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Checked="OnToggleFilter"
|
Checked="OnToggleTagFilter"
|
||||||
Unchecked="OnToggleFilter"
|
Unchecked="OnToggleTagFilter"
|
||||||
IsChecked="{Binding IsFiltered}"
|
IsChecked="{Binding IsFiltered}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
@ -491,7 +379,7 @@
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<DataGrid Grid.Row="7"
|
<DataGrid Grid.Row="7"
|
||||||
x:Name="submoduleList"
|
x:Name="submoduleList"
|
||||||
MaxHeight="200"
|
Height="0"
|
||||||
Margin="8,0,4,0"
|
Margin="8,0,4,0"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
ItemsSource="{Binding Submodules}"
|
ItemsSource="{Binding Submodules}"
|
||||||
|
@ -574,6 +462,7 @@
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<DataGrid Grid.Row="9"
|
<DataGrid Grid.Row="9"
|
||||||
x:Name="worktreeList"
|
x:Name="worktreeList"
|
||||||
|
Height="0"
|
||||||
Margin="8,0,4,0"
|
Margin="8,0,4,0"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
ItemsSource="{Binding Worktrees}"
|
ItemsSource="{Binding Worktrees}"
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using AvaloniaEdit.Utils;
|
|
||||||
|
|
||||||
namespace SourceGit.Views
|
namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
|
@ -21,11 +18,7 @@ namespace SourceGit.Views
|
||||||
protected override void OnLoaded(RoutedEventArgs e)
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnLoaded(e);
|
base.OnLoaded(e);
|
||||||
|
UpdateLeftSidebarLayout();
|
||||||
if (DataContext is ViewModels.Repository repo && !repo.IsSearching)
|
|
||||||
{
|
|
||||||
UpdateLeftSidebarLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenWithExternalTools(object sender, RoutedEventArgs e)
|
private void OpenWithExternalTools(object sender, RoutedEventArgs e)
|
||||||
|
@ -60,24 +53,23 @@ namespace SourceGit.Views
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OpenStatistics(object sender, RoutedEventArgs e)
|
private async void OpenStatistics(object _, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is ViewModels.Repository repo)
|
if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner)
|
||||||
{
|
{
|
||||||
var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) };
|
var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) };
|
||||||
await dialog.ShowDialog(TopLevel.GetTopLevel(this) as Window);
|
await dialog.ShowDialog(owner);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
var grid = sender as Grid;
|
if (e.Property == IsVisibleProperty && sender is Grid { IsVisible: true})
|
||||||
if (e.Property == IsVisibleProperty && grid.IsVisible)
|
|
||||||
txtSearchCommitsBox.Focus();
|
txtSearchCommitsBox.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSearchKeyDown(object sender, KeyEventArgs e)
|
private void OnSearchKeyDown(object _, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Key == Key.Enter)
|
if (e.Key == Key.Enter)
|
||||||
{
|
{
|
||||||
|
@ -90,199 +82,39 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
|
if (sender is DataGrid { SelectedItem: Models.Commit commit } && DataContext is ViewModels.Repository repo)
|
||||||
{
|
{
|
||||||
if (DataContext is ViewModels.Repository repo)
|
repo.NavigateToCommit(commit.SHA);
|
||||||
{
|
|
||||||
var commit = datagrid.SelectedItem as Models.Commit;
|
|
||||||
repo.NavigateToCommit(commit.SHA);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLocalBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void OnBranchTreeRowsChanged(object _, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
UpdateLeftSidebarLayout();
|
||||||
{
|
e.Handled = true;
|
||||||
remoteBranchTree.UnselectAll();
|
|
||||||
tagsList.SelectedItem = null;
|
|
||||||
|
|
||||||
ViewModels.BranchTreeNode prev = null;
|
|
||||||
foreach (var node in repo.LocalBranchTrees)
|
|
||||||
node.UpdateCornerRadius(ref prev);
|
|
||||||
|
|
||||||
if (tree.SelectedItems.Count == 1)
|
|
||||||
{
|
|
||||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
|
||||||
if (node.IsBranch)
|
|
||||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRemoteBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void OnLocalBranchTreeSelectionChanged(object _1, RoutedEventArgs _2)
|
||||||
{
|
|
||||||
if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
localBranchTree.UnselectAll();
|
|
||||||
tagsList.SelectedItem = null;
|
|
||||||
|
|
||||||
ViewModels.BranchTreeNode prev = null;
|
|
||||||
foreach (var node in repo.RemoteBranchTrees)
|
|
||||||
node.UpdateCornerRadius(ref prev);
|
|
||||||
|
|
||||||
if (tree.SelectedItems.Count == 1)
|
|
||||||
{
|
|
||||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
|
||||||
if (node.IsBranch)
|
|
||||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLocalBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
|
||||||
{
|
{
|
||||||
remoteBranchTree.UnselectAll();
|
remoteBranchTree.UnselectAll();
|
||||||
tagsList.SelectedItem = null;
|
tagsList.SelectedItem = null;
|
||||||
|
|
||||||
var repo = DataContext as ViewModels.Repository;
|
|
||||||
var tree = sender as TreeView;
|
|
||||||
if (tree.SelectedItems.Count == 0)
|
|
||||||
{
|
|
||||||
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 item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRemoteBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
private void OnRemoteBranchTreeSelectionChanged(object _1, RoutedEventArgs _2)
|
||||||
{
|
{
|
||||||
localBranchTree.UnselectAll();
|
localBranchTree.UnselectAll();
|
||||||
tagsList.SelectedItem = null;
|
tagsList.SelectedItem = null;
|
||||||
|
|
||||||
var repo = DataContext as ViewModels.Repository;
|
|
||||||
var tree = sender as TreeView;
|
|
||||||
if (tree.SelectedItems.Count == 0)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tree.SelectedItems.Count == 1)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
item.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
var menu = repo.CreateContextMenuForRemoteBranch(branches[0]);
|
|
||||||
item.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (branches.Count > 1)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDoubleTappedBranchNode(object sender, TappedEventArgs e)
|
private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs _)
|
||||||
{
|
{
|
||||||
if (!ViewModels.PopupHost.CanCreatePopup())
|
if (sender is DataGrid { SelectedItem: Models.Tag tag })
|
||||||
return;
|
|
||||||
|
|
||||||
if (sender is Grid grid && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var node = grid.DataContext as ViewModels.BranchTreeNode;
|
|
||||||
if (node == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (node.IsBranch)
|
|
||||||
{
|
|
||||||
var branch = node.Backend as Models.Branch;
|
|
||||||
if (branch.IsCurrent)
|
|
||||||
return;
|
|
||||||
|
|
||||||
repo.CheckoutBranch(branch);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node.IsExpanded = !node.IsExpanded;
|
|
||||||
UpdateLeftSidebarLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
|
|
||||||
{
|
{
|
||||||
localBranchTree.UnselectAll();
|
localBranchTree.UnselectAll();
|
||||||
remoteBranchTree.UnselectAll();
|
remoteBranchTree.UnselectAll();
|
||||||
|
|
||||||
var tag = datagrid.SelectedItem as Models.Tag;
|
|
||||||
if (DataContext is ViewModels.Repository repo)
|
if (DataContext is ViewModels.Repository repo)
|
||||||
repo.NavigateToCommit(tag.SHA);
|
repo.NavigateToCommit(tag.SHA);
|
||||||
}
|
}
|
||||||
|
@ -300,25 +132,11 @@ namespace SourceGit.Views
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnToggleFilter(object sender, RoutedEventArgs e)
|
private void OnToggleTagFilter(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is ToggleButton toggle)
|
if (sender is ToggleButton { DataContext: Models.Tag tag } toggle && DataContext is ViewModels.Repository repo)
|
||||||
{
|
{
|
||||||
var filter = string.Empty;
|
repo.UpdateFilter(tag.Name, toggle.IsChecked == true);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(filter) && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
repo.UpdateFilter(filter, toggle.IsChecked == true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
@ -338,10 +156,10 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
|
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||||
{
|
{
|
||||||
var submodule = datagrid.SelectedItem as string;
|
var submodule = grid.SelectedItem as string;
|
||||||
(DataContext as ViewModels.Repository).OpenSubmodule(submodule);
|
repo.OpenSubmodule(submodule);
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
@ -349,11 +167,11 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e)
|
private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||||
{
|
{
|
||||||
var worktree = datagrid.SelectedItem as Models.Worktree;
|
var worktree = grid.SelectedItem as Models.Worktree;
|
||||||
var menu = repo.CreateContextMenuForWorktree(worktree);
|
var menu = repo.CreateContextMenuForWorktree(worktree);
|
||||||
datagrid.OpenContextMenu(menu);
|
grid.OpenContextMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
@ -361,42 +179,16 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private void OnDoubleTappedWorktree(object sender, TappedEventArgs e)
|
private void OnDoubleTappedWorktree(object sender, TappedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||||
{
|
{
|
||||||
var worktree = datagrid.SelectedItem as Models.Worktree;
|
var worktree = grid.SelectedItem as Models.Worktree;
|
||||||
(DataContext as ViewModels.Repository).OpenWorktree(worktree);
|
repo.OpenWorktree(worktree);
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CollectBranchesFromNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
|
private void OnLeftSidebarDataGridPropertyChanged(object _, AvaloniaPropertyChangedEventArgs e)
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLeftSidebarTreeViewPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Property == TreeView.ItemsSourceProperty || e.Property == TreeView.IsVisibleProperty)
|
|
||||||
{
|
|
||||||
UpdateLeftSidebarLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLeftSidebarDataGridPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty)
|
if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty)
|
||||||
{
|
{
|
||||||
|
@ -414,8 +206,8 @@ namespace SourceGit.Views
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5;
|
var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5;
|
||||||
var localBranchRows = vm.IsLocalBranchGroupExpanded ? GetTreeRowsCount(vm.LocalBranchTrees) : 0;
|
var localBranchRows = vm.IsLocalBranchGroupExpanded ? localBranchTree.Rows.Count : 0;
|
||||||
var remoteBranchRows = vm.IsRemoteGroupExpanded ? GetTreeRowsCount(vm.RemoteBranchTrees) : 0;
|
var remoteBranchRows = vm.IsRemoteGroupExpanded ? remoteBranchTree.Rows.Count : 0;
|
||||||
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;
|
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;
|
||||||
var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0;
|
var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0;
|
||||||
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0;
|
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0;
|
||||||
|
@ -434,7 +226,7 @@ namespace SourceGit.Views
|
||||||
else
|
else
|
||||||
height = Math.Max(200, test);
|
height = Math.Max(200, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
leftHeight -= height;
|
leftHeight -= height;
|
||||||
tagsList.Height = height;
|
tagsList.Height = height;
|
||||||
hasOverflow = (desiredBranches + desiredSubmodule + desiredWorktree) > leftHeight;
|
hasOverflow = (desiredBranches + desiredSubmodule + desiredWorktree) > leftHeight;
|
||||||
|
@ -522,19 +314,8 @@ namespace SourceGit.Views
|
||||||
remoteBranchTree.Height = height;
|
remoteBranchTree.Height = height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private int GetTreeRowsCount(List<ViewModels.BranchTreeNode> nodes)
|
leftSidebarGroups.InvalidateMeasure();
|
||||||
{
|
|
||||||
int count = nodes.Count;
|
|
||||||
|
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
|
||||||
if (!node.IsBranch && node.IsExpanded)
|
|
||||||
count += GetTreeRowsCount(node.Children);
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@
|
||||||
<v:CommitMessageTextBox Grid.Row="2" Text="{Binding CommitMessage, Mode=TwoWay}"/>
|
<v:CommitMessageTextBox Grid.Row="2" Text="{Binding CommitMessage, Mode=TwoWay}"/>
|
||||||
|
|
||||||
<!-- Commit Options -->
|
<!-- Commit Options -->
|
||||||
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
|
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto">
|
||||||
<Button Grid.Column="0"
|
<Button Grid.Column="0"
|
||||||
Classes="icon_button"
|
Classes="icon_button"
|
||||||
Width="14" Height="14"
|
Width="14" Height="14"
|
||||||
|
@ -184,15 +184,23 @@
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<CheckBox Grid.Column="1"
|
<CheckBox Grid.Column="1"
|
||||||
|
Height="24"
|
||||||
|
Margin="12,0,0,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
IsChecked="{Binding AutoStageBeforeCommit, Mode=TwoWay}"
|
||||||
|
Content="{DynamicResource Text.WorkingCopy.AutoStage}"
|
||||||
|
ToolTip.Tip="{DynamicResource Text.WorkingCopy.AutoStage.Tip}"/>
|
||||||
|
|
||||||
|
<CheckBox Grid.Column="2"
|
||||||
Height="24"
|
Height="24"
|
||||||
Margin="12,0,0,0"
|
Margin="12,0,0,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
IsChecked="{Binding UseAmend, Mode=TwoWay}"
|
IsChecked="{Binding UseAmend, Mode=TwoWay}"
|
||||||
Content="{DynamicResource Text.WorkingCopy.Amend}"/>
|
Content="{DynamicResource Text.WorkingCopy.Amend}"/>
|
||||||
|
|
||||||
<v:LoadingIcon Grid.Column="3" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/>
|
<v:LoadingIcon Grid.Column="4" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/>
|
||||||
|
|
||||||
<Button Grid.Column="4"
|
<Button Grid.Column="5"
|
||||||
Classes="flat primary"
|
Classes="flat primary"
|
||||||
Content="{DynamicResource Text.WorkingCopy.Commit}"
|
Content="{DynamicResource Text.WorkingCopy.Commit}"
|
||||||
Height="28"
|
Height="28"
|
||||||
|
@ -202,7 +210,7 @@
|
||||||
HotKey="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"
|
HotKey="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"
|
||||||
ToolTip.Tip="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"/>
|
ToolTip.Tip="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"/>
|
||||||
|
|
||||||
<Button Grid.Column="5"
|
<Button Grid.Column="6"
|
||||||
Classes="flat"
|
Classes="flat"
|
||||||
Content="{DynamicResource Text.WorkingCopy.CommitAndPush}"
|
Content="{DynamicResource Text.WorkingCopy.CommitAndPush}"
|
||||||
Height="28"
|
Height="28"
|
||||||
|
|
Loading…
Reference in a new issue