feature: supports show commit histories under selected folder (#1470)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2025-07-01 16:03:27 +08:00
parent f62eb88cc8
commit d4723eeea2
12 changed files with 516 additions and 83 deletions

View File

@@ -303,6 +303,7 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">Increase Number of Visible Lines</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">SELECT FILE TO VIEW CHANGES</x:String>
<x:String x:Key="Text.DiffWithMerger" xml:space="preserve">Open in Merge Tool</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">Dir Histories</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">Discard Changes</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">All local changes in working copy.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Changes:</x:String>

View File

@@ -307,6 +307,7 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">增加可见的行数</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">请选择需要对比的文件</x:String>
<x:String x:Key="Text.DiffWithMerger" xml:space="preserve">使用外部比对工具查看</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">目录内容变更历史</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">放弃更改确认</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">所有本地址未提交的修改。</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">变更 </x:String>

View File

@@ -307,6 +307,7 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">增加可見的行數</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">請選擇需要對比的檔案</x:String>
<x:String x:Key="Text.DiffWithMerger" xml:space="preserve">使用外部比對工具檢視</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">目錄内容變更歷史</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">捨棄變更</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">所有本機未提交的變更。</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">變更:</x:String>

View File

@@ -296,6 +296,85 @@ namespace SourceGit.ViewModels
});
}
public ContextMenu CreateChangeContextMenuByFolder(ChangeTreeNode node, List<Models.Change> changes)
{
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, node.FullPath);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = Directory.Exists(fullPath);
explore.Click += (_, ev) =>
{
Native.OS.OpenInFileManager(fullPath, true);
ev.Handled = true;
};
var history = new MenuItem();
history.Header = App.Text("DirHistories");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
App.ShowWindow(new DirHistories(_repo, node.FullPath, _commit.SHA), false);
ev.Handled = true;
};
var patch = new MenuItem();
patch.Header = App.Text("FileCM.SaveAsPatch");
patch.Icon = App.CreateMenuIcon("Icons.Diff");
patch.Click += async (_, e) =>
{
var storageProvider = App.GetStorageProvider();
if (storageProvider == null)
return;
var options = new FilePickerSaveOptions();
options.Title = App.Text("FileCM.SaveAsPatch");
options.DefaultExtension = ".patch";
options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }];
var baseRevision = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0];
var storageFile = await storageProvider.SaveFilePickerAsync(options);
if (storageFile != null)
{
var saveTo = storageFile.Path.LocalPath;
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo.FullPath, changes, baseRevision, _commit.SHA, saveTo));
if (succ)
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
}
e.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Click += (_, ev) =>
{
App.CopyText(node.FullPath);
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Click += (_, e) =>
{
App.CopyText(fullPath);
e.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(explore);
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(patch);
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
return menu;
}
public ContextMenu CreateChangeContextMenu(Models.Change change)
{
var diffWithMerger = new MenuItem();
@@ -428,8 +507,61 @@ namespace SourceGit.ViewModels
return menu;
}
public ContextMenu CreateRevisionFileContextMenuByFolder(string path)
{
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, path);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = Directory.Exists(fullPath);
explore.Click += (_, ev) =>
{
Native.OS.OpenInFileManager(fullPath, true);
ev.Handled = true;
};
var history = new MenuItem();
history.Header = App.Text("DirHistories");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
App.ShowWindow(new DirHistories(_repo, path, _commit.SHA), false);
ev.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Click += (_, ev) =>
{
App.CopyText(path);
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Click += (_, e) =>
{
App.CopyText(fullPath);
e.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(explore);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
return menu;
}
public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
{
if (file.Type == Models.ObjectType.Tree)
return CreateRevisionFileContextMenuByFolder(file.Path);
var menu = new ContextMenu();
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, file.Path);
var explore = new MenuItem();

View File

@@ -0,0 +1,90 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class DirHistories : ObservableObject
{
public string Title
{
get;
}
public bool IsLoading
{
get => _isLoading;
private set => SetProperty(ref _isLoading, value);
}
public List<Models.Commit> Commits
{
get => _commits;
private set => SetProperty(ref _commits, value);
}
public Models.Commit SelectedCommit
{
get => _selectedCommit;
set
{
if (SetProperty(ref _selectedCommit, value))
Detail.Commit = value;
}
}
public CommitDetail Detail
{
get => _detail;
private set => SetProperty(ref _detail, value);
}
public DirHistories(Repository repo, string dir, string revision = null)
{
if (!string.IsNullOrEmpty(revision))
Title = $"{dir} @ {revision}";
else
Title = dir;
_repo = repo;
_detail = new CommitDetail(repo);
Task.Run(() =>
{
var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {revision??string.Empty} -- \"{dir}\"", false).Result();
Dispatcher.UIThread.Invoke(() =>
{
Commits = commits;
IsLoading = false;
if (commits.Count > 0)
SelectedCommit = commits[0];
});
});
}
public void NavigateToCommit(Models.Commit commit)
{
_repo.NavigateToCommit(commit.SHA);
}
public string GetCommitFullMessage(Models.Commit commit)
{
var sha = commit.SHA;
if (_cachedCommitFullMessage.TryGetValue(sha, out var msg))
return msg;
msg = new Commands.QueryCommitFullMessage(_repo.FullPath, sha).Result();
_cachedCommitFullMessage[sha] = msg;
return msg;
}
private Repository _repo = null;
private bool _isLoading = true;
private List<Models.Commit> _commits = [];
private Models.Commit _selectedCommit = null;
private CommitDetail _detail = null;
private Dictionary<string, string> _cachedCommitFullMessage = new();
}
}

View File

@@ -238,6 +238,11 @@ namespace SourceGit.ViewModels
public class FileHistories : ObservableObject
{
public string Title
{
get;
}
public bool IsLoading
{
get => _isLoading;
@@ -264,6 +269,11 @@ namespace SourceGit.ViewModels
public FileHistories(Repository repo, string file, string commit = null)
{
if (!string.IsNullOrEmpty(commit))
Title = $"{file} @ {commit}";
else
Title = file;
_repo = repo;
Task.Run(() =>

View File

@@ -590,6 +590,7 @@ namespace SourceGit.ViewModels
if (_selectedUnstaged == null || _selectedUnstaged.Count == 0)
return null;
var hasSelectedFolder = !string.IsNullOrEmpty(selectedSingleFolder);
var menu = new ContextMenu();
if (_selectedUnstaged.Count == 1)
{
@@ -602,11 +603,8 @@ namespace SourceGit.ViewModels
explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
explore.Click += (_, e) =>
{
if (string.IsNullOrEmpty(selectedSingleFolder))
Native.OS.OpenInFileManager(path, true);
else
Native.OS.OpenInFileManager(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder), true);
var target = hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path;
Native.OS.OpenInFileManager(target, true);
e.Handled = true;
};
menu.Items.Add(explore);
@@ -747,23 +745,12 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
App.ShowWindow(new FileHistories(_repo, change.Path), false);
e.Handled = true;
};
menu.Items.Add(stage);
menu.Items.Add(discard);
menu.Items.Add(stash);
menu.Items.Add(patch);
menu.Items.Add(assumeUnchanged);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(new MenuItem() { Header = "-" });
var extension = Path.GetExtension(change.Path);
var hasExtra = false;
@@ -773,7 +760,7 @@ namespace SourceGit.ViewModels
addToIgnore.Header = App.Text("WorkingCopy.AddToGitIgnore");
addToIgnore.Icon = App.CreateMenuIcon("Icons.GitIgnore");
if (!string.IsNullOrEmpty(selectedSingleFolder))
if (hasSelectedFolder)
{
var ignoreFolder = new MenuItem();
ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder");
@@ -827,24 +814,21 @@ namespace SourceGit.ViewModels
menu.Items.Add(addToIgnore);
hasExtra = true;
}
else if (!string.IsNullOrEmpty(selectedSingleFolder))
else if (hasSelectedFolder)
{
var addToIgnore = new MenuItem();
addToIgnore.Header = App.Text("WorkingCopy.AddToGitIgnore");
addToIgnore.Icon = App.CreateMenuIcon("Icons.GitIgnore");
if (!string.IsNullOrEmpty(selectedSingleFolder))
var ignoreFolder = new MenuItem();
ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder");
ignoreFolder.Click += (_, e) =>
{
var ignoreFolder = new MenuItem();
ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder");
ignoreFolder.Click += (_, e) =>
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, $"{selectedSingleFolder}/"));
e.Handled = true;
};
addToIgnore.Items.Add(ignoreFolder);
}
if (_repo.CanCreatePopup())
_repo.ShowPopup(new AddToIgnore(_repo, $"{selectedSingleFolder}/"));
e.Handled = true;
};
addToIgnore.Items.Add(ignoreFolder);
menu.Items.Add(addToIgnore);
hasExtra = true;
@@ -981,32 +965,40 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
}
var history = new MenuItem();
history.Header = App.Text(hasSelectedFolder ? "DirHistories" : "FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
if (hasSelectedFolder)
App.ShowWindow(new DirHistories(_repo, selectedSingleFolder), false);
else
App.ShowWindow(new FileHistories(_repo, change.Path), false);
e.Handled = true;
};
var copy = new MenuItem();
copy.Header = App.Text("CopyPath");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Click += (_, e) =>
{
if (string.IsNullOrEmpty(selectedSingleFolder))
App.CopyText(change.Path);
else
App.CopyText(selectedSingleFolder);
App.CopyText(hasSelectedFolder ? selectedSingleFolder : change.Path);
e.Handled = true;
};
menu.Items.Add(copy);
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Click += (_, e) =>
{
if (string.IsNullOrEmpty(selectedSingleFolder))
App.CopyText(path);
else
App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder));
App.CopyText(hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path);
e.Handled = true;
};
menu.Items.Add(history);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copy);
menu.Items.Add(copyFullPath);
}
else
@@ -1073,7 +1065,7 @@ namespace SourceGit.ViewModels
return menu;
}
if (!string.IsNullOrEmpty(selectedSingleFolder))
if (hasSelectedFolder)
{
var dir = Path.Combine(_repo.FullPath, selectedSingleFolder);
var explore = new MenuItem();
@@ -1148,7 +1140,7 @@ namespace SourceGit.ViewModels
menu.Items.Add(stash);
menu.Items.Add(patch);
if (!string.IsNullOrEmpty(selectedSingleFolder))
if (hasSelectedFolder)
{
var ignoreFolder = new MenuItem();
ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder");
@@ -1167,6 +1159,17 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(addToIgnore);
var history = new MenuItem();
history.Header = App.Text("DirHistories");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
App.ShowWindow(new DirHistories(_repo, selectedSingleFolder), false);
e.Handled = true;
};
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
var copy = new MenuItem();
copy.Header = App.Text("CopyPath");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
@@ -1235,6 +1238,7 @@ namespace SourceGit.ViewModels
}
}
var hasSelectedFolder = !string.IsNullOrEmpty(selectedSingleFolder);
if (_selectedStaged.Count == 1)
{
var change = _selectedStaged[0];
@@ -1246,11 +1250,8 @@ namespace SourceGit.ViewModels
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.Click += (_, e) =>
{
if (string.IsNullOrEmpty(selectedSingleFolder))
Native.OS.OpenInFileManager(path, true);
else
Native.OS.OpenInFileManager(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder), true);
var target = hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path;
Native.OS.OpenInFileManager(target, true);
e.Handled = true;
};
@@ -1309,15 +1310,6 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
App.ShowWindow(new FileHistories(_repo, change.Path), false);
e.Handled = true;
};
menu.Items.Add(explore);
menu.Items.Add(openWith);
menu.Items.Add(new MenuItem() { Header = "-" });
@@ -1325,8 +1317,6 @@ namespace SourceGit.ViewModels
menu.Items.Add(stash);
menu.Items.Add(patch);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(new MenuItem() { Header = "-" });
var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled();
if (lfsEnabled)
@@ -1423,16 +1413,24 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
}
var history = new MenuItem();
history.Header = App.Text(hasSelectedFolder ? "DirHistories" : "FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
if (hasSelectedFolder)
App.ShowWindow(new DirHistories(_repo, selectedSingleFolder), false);
else
App.ShowWindow(new FileHistories(_repo, change.Path), false);
e.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Click += (_, e) =>
{
if (string.IsNullOrEmpty(selectedSingleFolder))
App.CopyText(change.Path);
else
App.CopyText(selectedSingleFolder);
App.CopyText(hasSelectedFolder ? selectedSingleFolder : change.Path);
e.Handled = true;
};
@@ -1441,20 +1439,19 @@ namespace SourceGit.ViewModels
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Click += (_, e) =>
{
if (string.IsNullOrEmpty(selectedSingleFolder))
App.CopyText(path);
else
App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder));
var target = hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path;
App.CopyText(target);
e.Handled = true;
};
menu.Items.Add(history);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
}
else
{
if (!string.IsNullOrEmpty(selectedSingleFolder))
if (hasSelectedFolder)
{
var dir = Path.Combine(_repo.FullPath, selectedSingleFolder);
var explore = new MenuItem();
@@ -1526,8 +1523,17 @@ namespace SourceGit.ViewModels
menu.Items.Add(ai);
}
if (!string.IsNullOrEmpty(selectedSingleFolder))
if (hasSelectedFolder)
{
var history = new MenuItem();
history.Header = App.Text(hasSelectedFolder ? "DirHistories" : "FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
App.ShowWindow(new DirHistories(_repo, selectedSingleFolder), false);
e.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
@@ -1546,6 +1552,8 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);

View File

@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
@@ -11,15 +12,25 @@ namespace SourceGit.Views
private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is ChangeCollectionView { SelectedChanges: { } selected } view &&
selected.Count == 1 &&
DataContext is ViewModels.CommitDetail vm)
e.Handled = true;
if (sender is not ChangeCollectionView view || DataContext is not ViewModels.CommitDetail vm)
return;
var changes = view.SelectedChanges ?? [];
var container = view.FindDescendantOfType<ChangeCollectionContainer>();
if (container is { SelectedItems.Count: 1, SelectedItem: ViewModels.ChangeTreeNode { IsFolder: true } node })
{
var menu = vm.CreateChangeContextMenu(selected[0]);
menu?.Open(view);
var menu = vm.CreateChangeContextMenuByFolder(node, changes);
menu.Open(view);
return;
}
e.Handled = true;
if (changes.Count == 1)
{
var menu = vm.CreateChangeContextMenu(changes[0]);
menu.Open(view);
}
}
}
}

View File

@@ -0,0 +1,129 @@
<v:ChromelessWindow 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.DirHistories"
x:DataType="vm:DirHistories"
x:Name="ThisControl"
Icon="/App.ico"
Title="{DynamicResource Text.DirHistories}"
MinWidth="1280" MinHeight="720">
<Grid RowDefinitions="Auto,28,1,*">
<!-- TitleBar -->
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
<!-- Bottom border -->
<Border Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"/>
<Path Width="12" Height="12"
Margin="10,0,0,0"
HorizontalAlignment="Left"
Data="{StaticResource Icons.Histories}"
IsVisible="{OnPlatform True, macOS=False}"/>
<TextBlock Classes="bold"
Text="{DynamicResource Text.DirHistories}"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsHitTestVisible="False"/>
<!-- Caption Buttons (Windows/Linux) -->
<v:CaptionButtons HorizontalAlignment="Right" IsVisible="{OnPlatform True, macOS=False}"/>
</Grid>
<!-- Info -->
<Grid Grid.Row="1" ColumnDefinitions="Auto,*" Background="{DynamicResource Brush.Popup}">
<Path Grid.Column="0" Width="14" Height="14" Margin="8,0,6,0" Data="{StaticResource Icons.Folder.Open}"/>
<TextBlock Grid.Column="1" Text="{Binding Title, Mode=OneWay}"/>
</Grid>
<!-- Line -->
<Rectangle Grid.Row="2" Height="0.8" HorizontalAlignment="Stretch" VerticalAlignment="Top" Fill="{DynamicResource Brush.Border0}"/>
<!-- Body -->
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" MinWidth="300" MaxWidth="600"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Commits -->
<ListBox Grid.Column="0"
Background="{DynamicResource Brush.Contents}"
BorderThickness="1"
Margin="8,4,4,8"
BorderBrush="{DynamicResource Brush.Border2}"
ItemsSource="{Binding Commits}"
SelectedItem="{Binding SelectedCommit, Mode=TwoWay}"
SelectionMode="Single"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Height" Value="50"/>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="m:Commit">
<Border BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="0,0,0,1" Padding="4">
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,96">
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0" TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Column="2"
Classes="primary"
Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
Cursor="Hand"
Background="Transparent"
Foreground="DarkOrange"
TextDecorations="Underline"
Margin="8,0,0,0"
PointerPressed="OnPressCommitSHA"/>
<TextBlock Grid.Column="3" Classes="primary" Text="{Binding AuthorTimeShortStr}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right"/>
</Grid>
<Border Grid.Row="1" Background="Transparent" DataContextChanged="OnCommitSubjectDataContextChanged" PointerMoved="OnCommitSubjectPointerMoved">
<TextBlock Classes="primary" Text="{Binding Subject}" VerticalAlignment="Bottom"/>
</Border>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Grid.Column="1"
MinWidth="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="Transparent"
BorderThickness="1,0,0,0"
BorderBrush="{DynamicResource Brush.Border0}"/>
<!-- Commit Detail -->
<Border Grid.Column="2" Padding="0,4,4,8">
<ContentControl Content="{Binding Detail, Mode=OneWay}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:CommitDetail">
<v:CommitDetail/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Border>
</Grid>
</Grid>
</v:ChromelessWindow>

View File

@@ -0,0 +1,44 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
namespace SourceGit.Views
{
public partial class DirHistories : ChromelessWindow
{
public DirHistories()
{
InitializeComponent();
}
private void OnPressCommitSHA(object sender, PointerPressedEventArgs e)
{
if (sender is TextBlock { DataContext: Models.Commit commit } &&
DataContext is ViewModels.DirHistories vm)
{
vm.NavigateToCommit(commit);
}
e.Handled = true;
}
private void OnCommitSubjectDataContextChanged(object sender, EventArgs e)
{
if (sender is Border border)
ToolTip.SetTip(border, null);
}
private void OnCommitSubjectPointerMoved(object sender, PointerEventArgs e)
{
if (sender is Border { DataContext: Models.Commit commit } border &&
DataContext is ViewModels.DirHistories vm)
{
var tooltip = ToolTip.GetTip(border);
if (tooltip == null)
ToolTip.SetTip(border, vm.GetCommitFullMessage(commit));
}
}
}
}

View File

@@ -13,7 +13,7 @@
Icon="/App.ico"
Title="{DynamicResource Text.FileHistory}"
MinWidth="1280" MinHeight="720">
<Grid RowDefinitions="Auto,*">
<Grid RowDefinitions="Auto,28,1,*">
<!-- TitleBar -->
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
<!-- Bottom border -->
@@ -37,8 +37,17 @@
<v:CaptionButtons HorizontalAlignment="Right" IsVisible="{OnPlatform True, macOS=False}"/>
</Grid>
<!-- Info -->
<Grid Grid.Row="1" ColumnDefinitions="Auto,*" Background="{DynamicResource Brush.Popup}">
<Path Grid.Column="0" Width="14" Height="14" Margin="8,0,6,0" Data="{StaticResource Icons.File}"/>
<TextBlock Grid.Column="1" Text="{Binding Title, Mode=OneWay}"/>
</Grid>
<!-- Line -->
<Rectangle Grid.Row="2" Height="0.8" HorizontalAlignment="Stretch" VerticalAlignment="Top" Fill="{DynamicResource Brush.Border0}"/>
<!-- Body -->
<Grid Grid.Row="1">
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" MinWidth="300" MaxWidth="600"/>
<ColumnDefinition Width="4"/>

View File

@@ -281,11 +281,8 @@ namespace SourceGit.Views
if (DataContext is ViewModels.CommitDetail vm &&
sender is Grid { DataContext: ViewModels.RevisionFileTreeNode { Backend: { } obj } } grid)
{
if (obj.Type != Models.ObjectType.Tree)
{
var menu = vm.CreateRevisionFileContextMenu(obj);
menu?.Open(grid);
}
var menu = vm.CreateRevisionFileContextMenu(obj);
menu.Open(grid);
}
e.Handled = true;