Merge branch 'release/v8.12'

This commit is contained in:
leo 2024-05-13 09:20:12 +08:00
commit 283c681a10
29 changed files with 314 additions and 126 deletions

View file

@ -91,5 +91,5 @@ This app supports open repository in external tools listed in the table below.
Thanks to all the people who contribute.
<a href="https://github.com/sourcegit-scm/sourcegit/graphs/contributors">
<img src="https://contrib.rocks/image?repo=sourcegit-scm/sourcegit&t=2" />
<img src="https://contrib.rocks/image?repo=sourcegit-scm/sourcegit&columns=10&t=3" />
</a>

View file

@ -1 +1 @@
8.11
8.12

View file

@ -0,0 +1,2 @@
#!/bin/bash
exec /opt/sourcegit/sourcegit

View file

@ -1,5 +1,6 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="using:SourceGit"
x:Class="SourceGit.App"
Name="SourceGit"
RequestedThemeVariant="Dark">
@ -21,4 +22,16 @@
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
<StyleInclude Source="/Resources/Styles.axaml"/>
</Application.Styles>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/>
<NativeMenuItem Header="{DynamicResource Text.Hotkeys.Menu}" Command="{x:Static s:App.OpenHotkeysCommand}"/>
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}"/>
<NativeMenuItemSeparator/>
<NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/>
<NativeMenuItemSeparator/>
<NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}"/>
</NativeMenu>
</NativeMenu.Menu>
</Application>

View file

@ -5,6 +5,7 @@ using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
@ -18,9 +19,27 @@ using Avalonia.Threading;
namespace SourceGit
{
public class SimpleCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
public SimpleCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter) => _action != null;
public void Execute(object parameter) => _action?.Invoke();
private Action _action = null;
}
public partial class App : Application
{
[STAThread]
public static void Main(string[] args)
{
@ -67,6 +86,31 @@ namespace SourceGit
return builder;
}
public static readonly SimpleCommand OpenPreferenceCommand = new SimpleCommand(() =>
{
var dialog = new Views.Preference();
dialog.ShowDialog(GetTopLevel() as Window);
});
public static readonly SimpleCommand OpenHotkeysCommand = new SimpleCommand(() =>
{
var dialog = new Views.Hotkeys();
dialog.ShowDialog(GetTopLevel() as Window);
});
public static readonly SimpleCommand OpenAboutCommand = new SimpleCommand(() =>
{
var dialog = new Views.About();
dialog.ShowDialog(GetTopLevel() as Window);
});
public static readonly SimpleCommand CheckForUpdateCommand = new SimpleCommand(() =>
{
Check4Update(true);
});
public static readonly SimpleCommand QuitCommand = new SimpleCommand(Quit);
public static void RaiseException(string context, string message)
{
if (Current is App app && app._notificationReceiver != null)

View file

@ -62,14 +62,30 @@ namespace SourceGit.Commands
public class AutoFetch
{
private const double INTERVAL = 10 * 60;
public static bool IsEnabled
{
get;
set;
} = false;
public static int Interval
{
get => _interval;
set
{
if (value < 1)
return;
_interval = value;
lock (_lock)
{
foreach (var job in _jobs)
{
job.Value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(_interval));
}
}
}
}
class Job
{
public Fetch Cmd = null;
@ -104,7 +120,7 @@ namespace SourceGit.Commands
foreach (var job in uptodate)
{
job.Cmd.Exec();
job.NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL);
job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
Thread.Sleep(2000);
@ -117,7 +133,7 @@ namespace SourceGit.Commands
var job = new Job
{
Cmd = new Fetch(repo, "--all", true, null) { RaiseError = false },
NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL),
NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
};
lock (_lock)
@ -147,12 +163,13 @@ namespace SourceGit.Commands
{
if (_jobs.TryGetValue(repo, out var value))
{
value.NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL);
value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
}
}
private static readonly Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
private static readonly object _lock = new object();
private static int _interval = 10;
}
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Concurrent;
namespace SourceGit.Models
{
@ -36,11 +36,11 @@ namespace SourceGit.Models
var email = data.Substring(nameEndIdx + 1);
User user = new User() { Name = name, Email = email };
_caches.Add(data, user);
_caches.TryAdd(data, user);
return user;
}
}
private static Dictionary<string, User> _caches = new Dictionary<string, User>();
private static ConcurrentDictionary<string, User> _caches = new ConcurrentDictionary<string, User>();
}
}

View file

@ -180,7 +180,8 @@ namespace SourceGit.Models
}
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
name.StartsWith("refs/remotes/", StringComparison.Ordinal))
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
{
_updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
}

View file

@ -22,7 +22,6 @@ namespace SourceGit.Native
builder.With(new MacOSPlatformOptions()
{
DisableNativeMenus = true,
DisableDefaultApplicationMenuItems = true,
});
}

View file

@ -1,7 +1,8 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:String x:Key="Text.About" xml:space="preserve">About</x:String>
<x:String x:Key="Text.About.Menu" xml:space="preserve">About SourceGit</x:String>
<x:String x:Key="Text.About.BuildWith" xml:space="preserve">• Build with </x:String>
<x:String x:Key="Text.About.Copyright" xml:space="preserve">Copyright © 2024 sourcegit-scm.</x:String>
<x:String x:Key="Text.About.Copyright" xml:space="preserve">© 2024 sourcegit-scm</x:String>
<x:String x:Key="Text.About.Editor" xml:space="preserve">• TextEditor from </x:String>
<x:String x:Key="Text.About.Fonts" xml:space="preserve">• Monospace fonts come from </x:String>
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Source code can be found at </x:String>
@ -210,12 +211,14 @@
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">CLEAR</x:String>
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">SELECTED {0} COMMITS</x:String>
<x:String x:Key="Text.Hotkeys" xml:space="preserve">HotKeys</x:String>
<x:String x:Key="Text.Hotkeys.Menu" xml:space="preserve">About HotKeys</x:String>
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">GLOBAL</x:String>
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">Cancel current popup</x:String>
<x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">Close current page</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">Go to previous page</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">Go to next page</x:String>
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Create new page</x:String>
<x:String x:Key="Text.Hotkeys.Global.OpenPreference" xml:space="preserve">Open preference dialog</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">REPOSITORY</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">Force to reload this repository</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Stage/Unstage selected changes</x:String>
@ -272,6 +275,8 @@
<x:String x:Key="Text.Preference.General.UseFixedTabWidth" xml:space="preserve">Use fixed tab width in titlebar</x:String>
<x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetch" xml:space="preserve">Fetch remotes automatically</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetchInterval" xml:space="preserve">Auto Fetch Interval</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetchIntervalSuffix" xml:space="preserve">Minute(s)</x:String>
<x:String x:Key="Text.Preference.Git.CRLF" xml:space="preserve">Enable Auto CRLF</x:String>
<x:String x:Key="Text.Preference.Git.DefaultCloneDir" xml:space="preserve">Default Clone Dir</x:String>
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">User Email</x:String>
@ -310,6 +315,7 @@
<x:String x:Key="Text.PushTag" xml:space="preserve">Push Tag To Remote</x:String>
<x:String x:Key="Text.PushTag.Remote" xml:space="preserve">Remote :</x:String>
<x:String x:Key="Text.PushTag.Tag" xml:space="preserve">Tag :</x:String>
<x:String x:Key="Text.Quit" xml:space="preserve">Quit</x:String>
<x:String x:Key="Text.Rebase" xml:space="preserve">Rebase Current Branch</x:String>
<x:String x:Key="Text.Rebase.AutoStash" xml:space="preserve">Stash &amp; reapply local changes</x:String>
<x:String x:Key="Text.Rebase.On" xml:space="preserve">On :</x:String>

View file

@ -1,7 +1,8 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:String x:Key="Text.About" xml:space="preserve">关于软件</x:String>
<x:String x:Key="Text.About.Menu" xml:space="preserve">关于本软件</x:String>
<x:String x:Key="Text.About.BuildWith" xml:space="preserve">• 项目依赖于 </x:String>
<x:String x:Key="Text.About.Copyright" xml:space="preserve">Copyright © 2024 sourcegit-scm.</x:String>
<x:String x:Key="Text.About.Copyright" xml:space="preserve">© 2024 sourcegit-scm</x:String>
<x:String x:Key="Text.About.Editor" xml:space="preserve">• 文本编辑器使用 </x:String>
<x:String x:Key="Text.About.Fonts" xml:space="preserve">• 等宽字体来自于 </x:String>
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• 项目源代码地址 </x:String>
@ -210,12 +211,14 @@
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">清空</x:String>
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">已选中 {0} 项提交</x:String>
<x:String x:Key="Text.Hotkeys" xml:space="preserve">快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Menu" xml:space="preserve">显示快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">全局快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">取消弹出面板</x:String>
<x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">关闭当前页面</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">切换到上一个页面</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">切换到下一个页面</x:String>
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">新建页面</x:String>
<x:String x:Key="Text.Hotkeys.Global.OpenPreference" xml:space="preserve">打开偏好设置面板</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">仓库页面快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">重新加载仓库状态</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">将选中的变更暂存或从暂存列表中移除</x:String>
@ -272,6 +275,8 @@
<x:String x:Key="Text.Preference.General.UseFixedTabWidth" xml:space="preserve">使用固定宽度的标题栏标签</x:String>
<x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT配置</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetch" xml:space="preserve">启用定时自动拉取远程更新</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetchInterval" xml:space="preserve">自动拉取间隔</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetchIntervalSuffix" xml:space="preserve">分钟</x:String>
<x:String x:Key="Text.Preference.Git.CRLF" xml:space="preserve">自动换行转换</x:String>
<x:String x:Key="Text.Preference.Git.DefaultCloneDir" xml:space="preserve">默认克隆路径</x:String>
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">邮箱</x:String>
@ -310,6 +315,7 @@
<x:String x:Key="Text.PushTag" xml:space="preserve">推送标签到远程仓库</x:String>
<x:String x:Key="Text.PushTag.Remote" xml:space="preserve">远程仓库 </x:String>
<x:String x:Key="Text.PushTag.Tag" xml:space="preserve">标签 </x:String>
<x:String x:Key="Text.Quit" xml:space="preserve">退出</x:String>
<x:String x:Key="Text.Rebase" xml:space="preserve">变基(rebase)操作</x:String>
<x:String x:Key="Text.Rebase.AutoStash" xml:space="preserve">自动贮藏并恢复本地变更</x:String>
<x:String x:Key="Text.Rebase.On" xml:space="preserve">目标提交 </x:String>

View file

@ -230,16 +230,16 @@
<Setter Property="Background" Value="Red"/>
</Style>
<Style Selector="Button.icon_button">
<Style Selector="Button.icon_button, RepeatButton.icon_button">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style Selector="Button.icon_button /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="Button.icon_button /template/ ContentPresenter#PART_ContentPresenter, RepeatButton.icon_button /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Opacity" Value="0.8"/>
</Style>
<Style Selector="Button.icon_button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="Button.icon_button:pointerover /template/ ContentPresenter#PART_ContentPresenter, RepeatButton.icon_button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Opacity" Value="1"/>
</Style>

View file

@ -1,5 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
@ -114,15 +113,7 @@ namespace SourceGit.ViewModels
CallUIThread(() =>
{
var repo = Preference.AddRepository(path, Path.Combine(path, ".git"));
var node = new RepositoryNode()
{
Id = repo.FullPath,
Name = Path.GetFileName(repo.FullPath),
Bookmark = 0,
IsRepository = true,
};
Preference.AddNode(node);
var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
_launcher.OpenRepositoryInTab(node, _page);
});

View file

@ -133,7 +133,7 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Post(() =>
{
if (string.IsNullOrEmpty(_option.OrgPath))
if (string.IsNullOrEmpty(_option.OrgPath) || _option.OrgPath == "/dev/null")
Title = _option.Path;
else
Title = $"{_option.OrgPath} → {_option.Path}";

View file

@ -1,5 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
@ -50,8 +49,13 @@ namespace SourceGit.ViewModels
public override Task<bool> Sure()
{
bool needSort = _node.Name != _name;
_node.Name = _name;
_node.Bookmark = _bookmark;
if (needSort)
Preference.SortByRenamedNode(_node);
return null;
}

View file

@ -134,11 +134,14 @@ namespace SourceGit.ViewModels
{
if (commits.Count == 0)
{
_repo.SearchResultSelectedCommit = null;
DetailContext = null;
}
else if (commits.Count == 1)
{
var commit = commits[0] as Models.Commit;
_repo.SearchResultSelectedCommit = commit;
AutoSelectedCommit = commit;
NavigationId = _navigationId + 1;
@ -155,12 +158,15 @@ namespace SourceGit.ViewModels
}
else if (commits.Count == 2)
{
_repo.SearchResultSelectedCommit = null;
var end = commits[0] as Models.Commit;
var start = commits[1] as Models.Commit;
DetailContext = new RevisionCompare(_repo.FullPath, start, end);
}
else
{
_repo.SearchResultSelectedCommit = null;
DetailContext = new CountSelectedCommits() { Count = commits.Count };
}
}

View file

@ -28,18 +28,10 @@ namespace SourceGit.ViewModels
return false;
var gitDir = Path.GetFullPath(Path.Combine(_targetPath, ".git"));
CallUIThread(() =>
{
var repo = Preference.AddRepository(_targetPath, gitDir);
var node = new RepositoryNode()
{
Id = repo.FullPath,
Name = Path.GetFileName(repo.FullPath),
Bookmark = 0,
IsRepository = true,
};
Preference.AddNode(node);
Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
});
return true;

View file

@ -107,7 +107,22 @@ namespace SourceGit.ViewModels
{
if (Pages.Count == 1)
{
App.Quit();
var last = Pages[0];
if (last.Data is Repository repo)
{
Commands.AutoFetch.RemoveRepository(repo.FullPath);
repo.Close();
last.Node = new RepositoryNode() { Id = Guid.NewGuid().ToString() };
last.Data = Welcome.Instance;
GC.Collect();
}
else
{
App.Quit();
}
return;
}
@ -119,15 +134,7 @@ namespace SourceGit.ViewModels
var activeIdx = Pages.IndexOf(_activePage);
if (removeIdx == activeIdx)
{
if (removeIdx == Pages.Count - 1)
{
ActivePage = Pages[removeIdx - 1];
}
else
{
ActivePage = Pages[removeIdx + 1];
}
ActivePage = Pages[removeIdx == Pages.Count - 1 ? removeIdx - 1 : removeIdx + 1];
CloseRepositoryInTab(page);
Pages.RemoveAt(removeIdx);
OnPropertyChanged(nameof(Pages));

View file

@ -26,14 +26,8 @@ namespace SourceGit.ViewModels
public LauncherPage()
{
_node = new RepositoryNode()
{
Id = Guid.NewGuid().ToString(),
Name = "WelcomePage",
Bookmark = 0,
IsRepository = false,
};
_data = new Welcome();
_node = new RepositoryNode() { Id = Guid.NewGuid().ToString() };
_data = Welcome.Instance;
}
public LauncherPage(RepositoryNode node, Repository repo)

View file

@ -232,6 +232,23 @@ namespace SourceGit.ViewModels
}
}
public int? GitAutoFetchInterval
{
get => Commands.AutoFetch.Interval;
set
{
if (value is null or < 1)
{
return;
}
if (Commands.AutoFetch.Interval != value)
{
Commands.AutoFetch.Interval = (int)value;
OnPropertyChanged(nameof(GitAutoFetchInterval));
}
}
}
public int ExternalMergeToolType
{
get => _externalMergeToolType;
@ -346,6 +363,29 @@ namespace SourceGit.ViewModels
return FindNodeRecursive(id, _instance.RepositoryNodes);
}
public static RepositoryNode FindOrAddNodeByRepositoryPath(string repo, RepositoryNode parent)
{
var node = FindNodeRecursive(repo, _instance.RepositoryNodes);
if (node == null)
{
node = new RepositoryNode()
{
Id = repo,
Name = Path.GetFileName(repo),
Bookmark = 0,
IsRepository = true,
};
AddNode(node, parent);
}
else
{
MoveNode(node, parent);
}
return node;
}
public static void MoveNode(RepositoryNode node, RepositoryNode to = null)
{
if (to == null && _instance._repositoryNodes.Contains(node))
@ -362,6 +402,31 @@ namespace SourceGit.ViewModels
RemoveNodeRecursive(node, _instance._repositoryNodes);
}
public static void SortByRenamedNode(RepositoryNode node)
{
var container = FindNodeContainer(node, _instance._repositoryNodes);
if (container == null)
return;
var list = new List<RepositoryNode>();
list.AddRange(container);
list.Sort((l, r) =>
{
if (l.IsRepository != r.IsRepository)
{
return l.IsRepository ? 1 : -1;
}
else
{
return l.Name.CompareTo(r.Name);
}
});
container.Clear();
foreach (var one in list)
container.Add(one);
}
public static Repository FindRepository(string path)
{
foreach (var repo in _instance.Repositories)
@ -417,6 +482,21 @@ namespace SourceGit.ViewModels
return null;
}
private static AvaloniaList<RepositoryNode> FindNodeContainer(RepositoryNode node, AvaloniaList<RepositoryNode> collection)
{
foreach (var sub in collection)
{
if (node == sub)
return collection;
var subCollection = FindNodeContainer(node, sub.SubNodes);
if (subCollection != null)
return subCollection;
}
return null;
}
private static bool RemoveNodeRecursive(RepositoryNode node, AvaloniaList<RepositoryNode> collection)
{
if (collection.Contains(node))

View file

@ -8,7 +8,6 @@ using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
@ -222,6 +221,12 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _hasUnsolvedConflicts, value);
}
public Models.Commit SearchResultSelectedCommit
{
get => _searchResultSelectedCommit;
set => SetProperty(ref _searchResultSelectedCommit, value);
}
public void Open()
{
_watcher = new Models.Watcher(this);
@ -1378,5 +1383,6 @@ namespace SourceGit.ViewModels
private InProgressContext _inProgressContext = null;
private bool _hasUnsolvedConflicts = false;
private Models.Commit _searchResultSelectedCommit = null;
}
}

View file

@ -9,10 +9,7 @@ namespace SourceGit.ViewModels
{
public class Welcome : ObservableObject
{
public bool IsClearSearchVisible
{
get => !string.IsNullOrEmpty(_searchFilter);
}
public static Welcome Instance => _instance;
public AvaloniaList<RepositoryNode> RepositoryNodes
{
@ -25,10 +22,7 @@ namespace SourceGit.ViewModels
set
{
if (SetProperty(ref _searchFilter, value))
{
Referesh();
OnPropertyChanged(nameof(IsClearSearchVisible));
}
}
}
@ -205,6 +199,7 @@ namespace SourceGit.ViewModels
}
}
private static Welcome _instance = new Welcome();
private string _searchFilter = string.Empty;
}
}

View file

@ -67,21 +67,24 @@
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}"
Margin="0,0,0,8"/>
<Grid RowDefinitions="20,20,20,20,20" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+T, macOS=⌘+T}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.NewTab}" />
<Grid RowDefinitions="20,20,20,20,20,20" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Shift+T, macOS=⌘+,}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.OpenPreference}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+T, macOS=⌘+T}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.NewTab}" />
<TextBlock Grid.Row="1" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+W, macOS=⌘+W}" />
<TextBlock Grid.Row="1" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.CloseTab}" />
<TextBlock Grid.Row="2" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+W, macOS=⌘+W}" />
<TextBlock Grid.Row="2" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.CloseTab}" />
<TextBlock Grid.Row="2" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Shift+Ctrl+Tab, macOS=⌘+⌥+←}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.GotoPrevTab}" />
<TextBlock Grid.Row="3" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Shift+Ctrl+Tab, macOS=⌘+⌥+←}"/>
<TextBlock Grid.Row="3" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.GotoPrevTab}" />
<TextBlock Grid.Row="3" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Tab, macOS=⌘+⌥+→}"/>
<TextBlock Grid.Row="3" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.GotoNextTab}" />
<TextBlock Grid.Row="4" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Tab, macOS=⌘+⌥+→}"/>
<TextBlock Grid.Row="4" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.GotoNextTab}" />
<TextBlock Grid.Row="4" Grid.Column="0" Classes="monospace bold" Text="ESC"/>
<TextBlock Grid.Row="4" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.CancelPopup}" />
<TextBlock Grid.Row="5" Grid.Column="0" Classes="monospace bold" Text="ESC"/>
<TextBlock Grid.Row="5" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.CancelPopup}" />
</Grid>
<TextBlock Text="{DynamicResource Text.Hotkeys.Repo}"

View file

@ -2,6 +2,7 @@
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:s="using:SourceGit"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:m="using:SourceGit.Models"
xmlns:c="using:SourceGit.Converters"
@ -49,31 +50,31 @@
</Border>
<!-- Menu -->
<Button Grid.Column="{OnPlatform 0, macOS=2}" Classes="icon_button" VerticalAlignment="Bottom">
<Button Grid.Column="0" Classes="icon_button" VerticalAlignment="Bottom" IsVisible="{OnPlatform True, macOS=False}">
<Button.Margin>
<OnPlatform Default="4,0,2,3" macOS="4,0,6,3"/>
</Button.Margin>
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft" VerticalOffset="-8">
<MenuItem Header="{DynamicResource Text.Preference}" Click="OpenPreference">
<MenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" InputGesture="Ctrl+Shift+P">
<MenuItem.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.Settings2}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.Hotkeys}" Click="OpenHotkeys">
<MenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}">
<MenuItem.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.Hotkeys}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="-"/>
<MenuItem Header="{DynamicResource Text.SelfUpdate}" Click="Check4Update">
<MenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}">
<MenuItem.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.SoftwareUpdate}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="-"/>
<MenuItem Header="{DynamicResource Text.About}" Click="OpenAboutDialog">
<MenuItem Header="{DynamicResource Text.About}" Command="{x:Static s:App.OpenAboutCommand}">
<MenuItem.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.Info}"/>
</MenuItem.Icon>
@ -85,9 +86,9 @@
<!-- Pages Tabs-->
<Grid x:Name="launcherTabsContainer" Grid.Column="1" Height="30" ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Bottom" SizeChanged="UpdateScrollIndicator">
<Button x:Name="leftScrollIndicator" Grid.Column="0" Classes="icon_button" Width="18" Height="30" Click="ScrollTabsLeft">
<RepeatButton x:Name="leftScrollIndicator" Grid.Column="0" Classes="icon_button" Width="18" Height="30" Click="ScrollTabsLeft">
<Path Width="8" Height="14" Stretch="Fill" Data="{StaticResource Icons.TriangleLeft}"/>
</Button>
</RepeatButton>
<ScrollViewer Grid.Column="1"
x:Name="launcherTabsScroller"
@ -95,7 +96,8 @@
VerticalScrollBarVisibility="Disabled"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"
PointerWheelChanged="ScrollTabs">
PointerWheelChanged="ScrollTabs"
ScrollChanged="OnTabsScrollChanged">
<StackPanel x:Name="launcherTabsBar" Orientation="Horizontal" SizeChanged="UpdateScrollIndicator">
<ListBox Classes="launcher_page_tabbar"
ItemsSource="{Binding Pages}"
@ -239,9 +241,9 @@
</StackPanel>
</ScrollViewer>
<Button x:Name="rightScrollIndicator" Grid.Column="2" Classes="icon_button" Width="18" Height="30" Click="ScrollTabsRight">
<RepeatButton x:Name="rightScrollIndicator" Grid.Column="2" Classes="icon_button" Width="18" Height="30" Click="ScrollTabsRight">
<Path Width="8" Height="14" Stretch="Fill" Data="{StaticResource Icons.TriangleRight}"/>
</Button>
</RepeatButton>
</Grid>
<!-- Caption Buttons (Windows/Linux)-->

View file

@ -98,6 +98,15 @@ namespace SourceGit.Views
protected override void OnKeyDown(KeyEventArgs e)
{
var vm = DataContext as ViewModels.Launcher;
// Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar)
if (!OperatingSystem.IsMacOS() && e.KeyModifiers == (KeyModifiers.Control | KeyModifiers.Shift) && e.Key == Key.P)
{
App.OpenPreferenceCommand.Execute(null);
e.Handled = true;
return;
}
if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Meta)) ||
(!OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Control)))
{
@ -223,7 +232,7 @@ namespace SourceGit.Views
{
if (e.Delta.Y < 0)
launcherTabsScroller.LineRight();
else
else if (e.Delta.Y > 0)
launcherTabsScroller.LineLeft();
e.Handled = true;
}
@ -256,6 +265,15 @@ namespace SourceGit.Views
e.Handled = true;
}
private void OnTabsScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (sender is ScrollViewer scrollViewer)
{
leftScrollIndicator.IsEnabled = scrollViewer.Offset.X > 0;
rightScrollIndicator.IsEnabled = scrollViewer.Offset.X < scrollViewer.Extent.Width - scrollViewer.Viewport.Width;
}
}
private void SetupDragAndDrop(object sender, RoutedEventArgs e)
{
if (sender is Border border)
@ -337,33 +355,6 @@ namespace SourceGit.Views
OnPopupCancel(sender, e);
}
private async void OpenPreference(object sender, RoutedEventArgs e)
{
var dialog = new Preference();
await dialog.ShowDialog(this);
e.Handled = true;
}
private async void OpenHotkeys(object sender, RoutedEventArgs e)
{
var dialog = new Hotkeys();
await dialog.ShowDialog(this);
e.Handled = true;
}
private void Check4Update(object sender, RoutedEventArgs e)
{
App.Check4Update(true);
e.Handled = true;
}
private async void OpenAboutDialog(object sender, RoutedEventArgs e)
{
var dialog = new About();
await dialog.ShowDialog(this);
e.Handled = true;
}
private bool _pressedTab = false;
private Point _pressedTabPosition = new Point();
private bool _startDrag = false;

View file

@ -231,7 +231,7 @@
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/>
</TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,Auto,32,32,32,32,32" ColumnDefinitions="Auto,*">
<Grid Margin="8" RowDefinitions="32,32,Auto,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.Path}"
HorizontalAlignment="Right"
@ -351,6 +351,41 @@
<CheckBox Grid.Row="7" Grid.Column="1"
Content="{DynamicResource Text.Preference.Git.AutoFetch}"
IsChecked="{Binding GitAutoFetch, Mode=TwoWay}"/>
<TextBlock Grid.Row="8" Grid.Column="0"
IsVisible="{Binding GitAutoFetch}"
Text="{DynamicResource Text.Preference.Git.AutoFetchInterval}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<Grid Grid.Row="8" Grid.Column="1" Height="32" ColumnDefinitions="*,Auto" IsVisible="{Binding GitAutoFetch}">
<NumericUpDown Grid.Column="0"
Minimum="1" Maximum="60" Increment="1"
Height="28"
Padding="4"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}"
CornerRadius="3"
ParsingNumberStyle="Integer"
FormatString="0"
Value="{Binding GitAutoFetchInterval, Mode=TwoWay, FallbackValue=10}">
<NumericUpDown.Styles>
<Style Selector="NumericUpDown /template/ ButtonSpinner#PART_Spinner">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
</Style>
<Style Selector="NumericUpDown /template/ TextBox#PART_TextBox">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="CornerRadius" Value="3,0,0,3"/>
</Style>
</NumericUpDown.Styles>
</NumericUpDown>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Margin="5,0,0,0"
Text="{DynamicResource Text.Preference.Git.AutoFetchIntervalSuffix}" />
</Grid>
</Grid>
</TabItem>

View file

@ -403,6 +403,7 @@
<DataGrid Grid.Row="1"
ItemsSource="{Binding SearchedCommits}"
SelectionMode="Single"
SelectedItem="{Binding SearchResultSelectedCommit, Mode=OneWay}"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"

View file

@ -42,7 +42,7 @@
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button" IsVisible="{Binding IsClearSearchVisible}" Command="{Binding ClearSearchFilter}">
<Button Classes="icon_button" IsVisible="{Binding SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Command="{Binding ClearSearchFilter}">
<Path Width="16" Height="16" Margin="0,0,0,0" Data="{StaticResource Icons.Clear}" Fill="{DynamicResource Brush.FG1}"/>
</Button>
</TextBox.InnerRightContent>

View file

@ -257,14 +257,7 @@ namespace SourceGit.Views
Dispatcher.UIThread.Invoke(() =>
{
var repo = ViewModels.Preference.AddRepository(root, gitDir);
var node = new ViewModels.RepositoryNode()
{
Id = repo.FullPath,
Name = Path.GetFileName(repo.FullPath),
Bookmark = 0,
IsRepository = true,
};
ViewModels.Preference.AddNode(node, parent);
var node = ViewModels.Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, parent);
launcher.OpenRepositoryInTab(node, page);
});
});