mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
Merge branch 'release/v8.12'
This commit is contained in:
commit
283c681a10
29 changed files with 314 additions and 126 deletions
|
@ -91,5 +91,5 @@ This app supports open repository in external tools listed in the table below.
|
||||||
Thanks to all the people who contribute.
|
Thanks to all the people who contribute.
|
||||||
|
|
||||||
<a href="https://github.com/sourcegit-scm/sourcegit/graphs/contributors">
|
<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>
|
</a>
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
8.11
|
8.12
|
2
build/resources/_common/usr/bin/sourcegit
Normal file
2
build/resources/_common/usr/bin/sourcegit
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
exec /opt/sourcegit/sourcegit
|
|
@ -1,5 +1,6 @@
|
||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="using:SourceGit"
|
||||||
x:Class="SourceGit.App"
|
x:Class="SourceGit.App"
|
||||||
Name="SourceGit"
|
Name="SourceGit"
|
||||||
RequestedThemeVariant="Dark">
|
RequestedThemeVariant="Dark">
|
||||||
|
@ -21,4 +22,16 @@
|
||||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||||
<StyleInclude Source="/Resources/Styles.axaml"/>
|
<StyleInclude Source="/Resources/Styles.axaml"/>
|
||||||
</Application.Styles>
|
</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>
|
</Application>
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
@ -18,9 +19,27 @@ using Avalonia.Threading;
|
||||||
|
|
||||||
namespace SourceGit
|
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
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
@ -67,6 +86,31 @@ namespace SourceGit
|
||||||
return builder;
|
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)
|
public static void RaiseException(string context, string message)
|
||||||
{
|
{
|
||||||
if (Current is App app && app._notificationReceiver != null)
|
if (Current is App app && app._notificationReceiver != null)
|
||||||
|
|
|
@ -62,14 +62,30 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
public class AutoFetch
|
public class AutoFetch
|
||||||
{
|
{
|
||||||
private const double INTERVAL = 10 * 60;
|
|
||||||
|
|
||||||
public static bool IsEnabled
|
public static bool IsEnabled
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
} = false;
|
} = 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
|
class Job
|
||||||
{
|
{
|
||||||
public Fetch Cmd = null;
|
public Fetch Cmd = null;
|
||||||
|
@ -104,7 +120,7 @@ namespace SourceGit.Commands
|
||||||
foreach (var job in uptodate)
|
foreach (var job in uptodate)
|
||||||
{
|
{
|
||||||
job.Cmd.Exec();
|
job.Cmd.Exec();
|
||||||
job.NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL);
|
job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(2000);
|
Thread.Sleep(2000);
|
||||||
|
@ -117,7 +133,7 @@ namespace SourceGit.Commands
|
||||||
var job = new Job
|
var job = new Job
|
||||||
{
|
{
|
||||||
Cmd = new Fetch(repo, "--all", true, null) { RaiseError = false },
|
Cmd = new Fetch(repo, "--all", true, null) { RaiseError = false },
|
||||||
NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL),
|
NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
|
||||||
};
|
};
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
@ -147,12 +163,13 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
if (_jobs.TryGetValue(repo, out var value))
|
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 Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
|
||||||
private static readonly object _lock = new object();
|
private static readonly object _lock = new object();
|
||||||
|
private static int _interval = 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace SourceGit.Models
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
|
@ -36,11 +36,11 @@ namespace SourceGit.Models
|
||||||
var email = data.Substring(nameEndIdx + 1);
|
var email = data.Substring(nameEndIdx + 1);
|
||||||
|
|
||||||
User user = new User() { Name = name, Email = email };
|
User user = new User() { Name = name, Email = email };
|
||||||
_caches.Add(data, user);
|
_caches.TryAdd(data, user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, User> _caches = new Dictionary<string, User>();
|
private static ConcurrentDictionary<string, User> _caches = new ConcurrentDictionary<string, User>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,8 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
||||||
name.StartsWith("refs/heads/", 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();
|
_updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ namespace SourceGit.Native
|
||||||
|
|
||||||
builder.With(new MacOSPlatformOptions()
|
builder.With(new MacOSPlatformOptions()
|
||||||
{
|
{
|
||||||
DisableNativeMenus = true,
|
|
||||||
DisableDefaultApplicationMenuItems = true,
|
DisableDefaultApplicationMenuItems = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
<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" 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.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.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.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>
|
<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.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.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" 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" 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.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.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.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.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.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" 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.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>
|
<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.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" 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.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.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.DefaultCloneDir" xml:space="preserve">Default Clone Dir</x:String>
|
||||||
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">User Email</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" 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.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.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" xml:space="preserve">Rebase Current Branch</x:String>
|
||||||
<x:String x:Key="Text.Rebase.AutoStash" xml:space="preserve">Stash & reapply local changes</x:String>
|
<x:String x:Key="Text.Rebase.AutoStash" xml:space="preserve">Stash & reapply local changes</x:String>
|
||||||
<x:String x:Key="Text.Rebase.On" xml:space="preserve">On :</x:String>
|
<x:String x:Key="Text.Rebase.On" xml:space="preserve">On :</x:String>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
<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" 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.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.Editor" xml:space="preserve">• 文本编辑器使用 </x:String>
|
||||||
<x:String x:Key="Text.About.Fonts" 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>
|
<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.SearchClear" xml:space="preserve">清空</x:String>
|
||||||
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">已选中 {0} 项提交</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" 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" 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.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.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.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.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.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" 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.Refresh" xml:space="preserve">重新加载仓库状态</x:String>
|
||||||
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" 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.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" 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.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.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.DefaultCloneDir" xml:space="preserve">默认克隆路径</x:String>
|
||||||
<x:String x:Key="Text.Preference.Git.Email" 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" xml:space="preserve">推送标签到远程仓库</x:String>
|
||||||
<x:String x:Key="Text.PushTag.Remote" 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.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" xml:space="preserve">变基(rebase)操作</x:String>
|
||||||
<x:String x:Key="Text.Rebase.AutoStash" xml:space="preserve">自动贮藏并恢复本地变更</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>
|
<x:String x:Key="Text.Rebase.On" xml:space="preserve">目标提交 :</x:String>
|
||||||
|
|
|
@ -230,16 +230,16 @@
|
||||||
<Setter Property="Background" Value="Red"/>
|
<Setter Property="Background" Value="Red"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Button.icon_button">
|
<Style Selector="Button.icon_button, RepeatButton.icon_button">
|
||||||
<Setter Property="BorderThickness" Value="0"/>
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
</Style>
|
</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="Background" Value="Transparent"/>
|
||||||
<Setter Property="Opacity" Value="0.8"/>
|
<Setter Property="Opacity" Value="0.8"/>
|
||||||
</Style>
|
</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"/>
|
<Setter Property="Opacity" Value="1"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -114,15 +113,7 @@ namespace SourceGit.ViewModels
|
||||||
CallUIThread(() =>
|
CallUIThread(() =>
|
||||||
{
|
{
|
||||||
var repo = Preference.AddRepository(path, Path.Combine(path, ".git"));
|
var repo = Preference.AddRepository(path, Path.Combine(path, ".git"));
|
||||||
var node = new RepositoryNode()
|
var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
|
||||||
{
|
|
||||||
Id = repo.FullPath,
|
|
||||||
Name = Path.GetFileName(repo.FullPath),
|
|
||||||
Bookmark = 0,
|
|
||||||
IsRepository = true,
|
|
||||||
};
|
|
||||||
Preference.AddNode(node);
|
|
||||||
|
|
||||||
_launcher.OpenRepositoryInTab(node, _page);
|
_launcher.OpenRepositoryInTab(node, _page);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_option.OrgPath))
|
if (string.IsNullOrEmpty(_option.OrgPath) || _option.OrgPath == "/dev/null")
|
||||||
Title = _option.Path;
|
Title = _option.Path;
|
||||||
else
|
else
|
||||||
Title = $"{_option.OrgPath} → {_option.Path}";
|
Title = $"{_option.OrgPath} → {_option.Path}";
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SourceGit.ViewModels
|
namespace SourceGit.ViewModels
|
||||||
|
@ -50,8 +49,13 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
public override Task<bool> Sure()
|
public override Task<bool> Sure()
|
||||||
{
|
{
|
||||||
|
bool needSort = _node.Name != _name;
|
||||||
_node.Name = _name;
|
_node.Name = _name;
|
||||||
_node.Bookmark = _bookmark;
|
_node.Bookmark = _bookmark;
|
||||||
|
|
||||||
|
if (needSort)
|
||||||
|
Preference.SortByRenamedNode(_node);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,11 +134,14 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
if (commits.Count == 0)
|
if (commits.Count == 0)
|
||||||
{
|
{
|
||||||
|
_repo.SearchResultSelectedCommit = null;
|
||||||
DetailContext = null;
|
DetailContext = null;
|
||||||
}
|
}
|
||||||
else if (commits.Count == 1)
|
else if (commits.Count == 1)
|
||||||
{
|
{
|
||||||
var commit = commits[0] as Models.Commit;
|
var commit = commits[0] as Models.Commit;
|
||||||
|
_repo.SearchResultSelectedCommit = commit;
|
||||||
|
|
||||||
AutoSelectedCommit = commit;
|
AutoSelectedCommit = commit;
|
||||||
NavigationId = _navigationId + 1;
|
NavigationId = _navigationId + 1;
|
||||||
|
|
||||||
|
@ -155,12 +158,15 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
else if (commits.Count == 2)
|
else if (commits.Count == 2)
|
||||||
{
|
{
|
||||||
|
_repo.SearchResultSelectedCommit = null;
|
||||||
|
|
||||||
var end = commits[0] as Models.Commit;
|
var end = commits[0] as Models.Commit;
|
||||||
var start = commits[1] as Models.Commit;
|
var start = commits[1] as Models.Commit;
|
||||||
DetailContext = new RevisionCompare(_repo.FullPath, start, end);
|
DetailContext = new RevisionCompare(_repo.FullPath, start, end);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_repo.SearchResultSelectedCommit = null;
|
||||||
DetailContext = new CountSelectedCommits() { Count = commits.Count };
|
DetailContext = new CountSelectedCommits() { Count = commits.Count };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,18 +28,10 @@ namespace SourceGit.ViewModels
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var gitDir = Path.GetFullPath(Path.Combine(_targetPath, ".git"));
|
var gitDir = Path.GetFullPath(Path.Combine(_targetPath, ".git"));
|
||||||
|
|
||||||
CallUIThread(() =>
|
CallUIThread(() =>
|
||||||
{
|
{
|
||||||
var repo = Preference.AddRepository(_targetPath, gitDir);
|
var repo = Preference.AddRepository(_targetPath, gitDir);
|
||||||
var node = new RepositoryNode()
|
Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
|
||||||
{
|
|
||||||
Id = repo.FullPath,
|
|
||||||
Name = Path.GetFileName(repo.FullPath),
|
|
||||||
Bookmark = 0,
|
|
||||||
IsRepository = true,
|
|
||||||
};
|
|
||||||
Preference.AddNode(node);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -106,8 +106,23 @@ namespace SourceGit.ViewModels
|
||||||
public void CloseTab(object param)
|
public void CloseTab(object param)
|
||||||
{
|
{
|
||||||
if (Pages.Count == 1)
|
if (Pages.Count == 1)
|
||||||
|
{
|
||||||
|
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();
|
App.Quit();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,15 +134,7 @@ namespace SourceGit.ViewModels
|
||||||
var activeIdx = Pages.IndexOf(_activePage);
|
var activeIdx = Pages.IndexOf(_activePage);
|
||||||
if (removeIdx == activeIdx)
|
if (removeIdx == activeIdx)
|
||||||
{
|
{
|
||||||
if (removeIdx == Pages.Count - 1)
|
ActivePage = Pages[removeIdx == Pages.Count - 1 ? removeIdx - 1 : removeIdx + 1];
|
||||||
{
|
|
||||||
ActivePage = Pages[removeIdx - 1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ActivePage = Pages[removeIdx + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseRepositoryInTab(page);
|
CloseRepositoryInTab(page);
|
||||||
Pages.RemoveAt(removeIdx);
|
Pages.RemoveAt(removeIdx);
|
||||||
OnPropertyChanged(nameof(Pages));
|
OnPropertyChanged(nameof(Pages));
|
||||||
|
|
|
@ -26,14 +26,8 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
public LauncherPage()
|
public LauncherPage()
|
||||||
{
|
{
|
||||||
_node = new RepositoryNode()
|
_node = new RepositoryNode() { Id = Guid.NewGuid().ToString() };
|
||||||
{
|
_data = Welcome.Instance;
|
||||||
Id = Guid.NewGuid().ToString(),
|
|
||||||
Name = "WelcomePage",
|
|
||||||
Bookmark = 0,
|
|
||||||
IsRepository = false,
|
|
||||||
};
|
|
||||||
_data = new Welcome();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LauncherPage(RepositoryNode node, Repository repo)
|
public LauncherPage(RepositoryNode node, Repository repo)
|
||||||
|
|
|
@ -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
|
public int ExternalMergeToolType
|
||||||
{
|
{
|
||||||
get => _externalMergeToolType;
|
get => _externalMergeToolType;
|
||||||
|
@ -346,6 +363,29 @@ namespace SourceGit.ViewModels
|
||||||
return FindNodeRecursive(id, _instance.RepositoryNodes);
|
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)
|
public static void MoveNode(RepositoryNode node, RepositoryNode to = null)
|
||||||
{
|
{
|
||||||
if (to == null && _instance._repositoryNodes.Contains(node))
|
if (to == null && _instance._repositoryNodes.Contains(node))
|
||||||
|
@ -362,6 +402,31 @@ namespace SourceGit.ViewModels
|
||||||
RemoveNodeRecursive(node, _instance._repositoryNodes);
|
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)
|
public static Repository FindRepository(string path)
|
||||||
{
|
{
|
||||||
foreach (var repo in _instance.Repositories)
|
foreach (var repo in _instance.Repositories)
|
||||||
|
@ -417,6 +482,21 @@ namespace SourceGit.ViewModels
|
||||||
return null;
|
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)
|
private static bool RemoveNodeRecursive(RepositoryNode node, AvaloniaList<RepositoryNode> collection)
|
||||||
{
|
{
|
||||||
if (collection.Contains(node))
|
if (collection.Contains(node))
|
||||||
|
|
|
@ -8,7 +8,6 @@ using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Platform;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
@ -222,6 +221,12 @@ namespace SourceGit.ViewModels
|
||||||
private set => SetProperty(ref _hasUnsolvedConflicts, value);
|
private set => SetProperty(ref _hasUnsolvedConflicts, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Models.Commit SearchResultSelectedCommit
|
||||||
|
{
|
||||||
|
get => _searchResultSelectedCommit;
|
||||||
|
set => SetProperty(ref _searchResultSelectedCommit, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void Open()
|
public void Open()
|
||||||
{
|
{
|
||||||
_watcher = new Models.Watcher(this);
|
_watcher = new Models.Watcher(this);
|
||||||
|
@ -1378,5 +1383,6 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
private InProgressContext _inProgressContext = null;
|
private InProgressContext _inProgressContext = null;
|
||||||
private bool _hasUnsolvedConflicts = false;
|
private bool _hasUnsolvedConflicts = false;
|
||||||
|
private Models.Commit _searchResultSelectedCommit = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,7 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
public class Welcome : ObservableObject
|
public class Welcome : ObservableObject
|
||||||
{
|
{
|
||||||
public bool IsClearSearchVisible
|
public static Welcome Instance => _instance;
|
||||||
{
|
|
||||||
get => !string.IsNullOrEmpty(_searchFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvaloniaList<RepositoryNode> RepositoryNodes
|
public AvaloniaList<RepositoryNode> RepositoryNodes
|
||||||
{
|
{
|
||||||
|
@ -25,10 +22,7 @@ namespace SourceGit.ViewModels
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _searchFilter, value))
|
if (SetProperty(ref _searchFilter, value))
|
||||||
{
|
|
||||||
Referesh();
|
Referesh();
|
||||||
OnPropertyChanged(nameof(IsClearSearchVisible));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +199,7 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Welcome _instance = new Welcome();
|
||||||
private string _searchFilter = string.Empty;
|
private string _searchFilter = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,21 +67,24 @@
|
||||||
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}"
|
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}"
|
||||||
Margin="0,0,0,8"/>
|
Margin="0,0,0,8"/>
|
||||||
|
|
||||||
<Grid RowDefinitions="20,20,20,20,20" ColumnDefinitions="150,*">
|
<Grid RowDefinitions="20,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="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.NewTab}" />
|
<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+W, macOS=⌘+W}" />
|
<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.CloseTab}" />
|
<TextBlock Grid.Row="1" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.NewTab}" />
|
||||||
|
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Shift+Ctrl+Tab, macOS=⌘+⌥+←}"/>
|
<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.GotoPrevTab}" />
|
<TextBlock Grid.Row="2" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.CloseTab}" />
|
||||||
|
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Tab, macOS=⌘+⌥+→}"/>
|
<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.GotoNextTab}" />
|
<TextBlock Grid.Row="3" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.GotoPrevTab}" />
|
||||||
|
|
||||||
<TextBlock Grid.Row="4" Grid.Column="0" Classes="monospace bold" Text="ESC"/>
|
<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.CancelPopup}" />
|
<TextBlock Grid.Row="4" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.GotoNextTab}" />
|
||||||
|
|
||||||
|
<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>
|
</Grid>
|
||||||
|
|
||||||
<TextBlock Text="{DynamicResource Text.Hotkeys.Repo}"
|
<TextBlock Text="{DynamicResource Text.Hotkeys.Repo}"
|
||||||
|
|
|
@ -2,6 +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:s="using:SourceGit"
|
||||||
xmlns:vm="using:SourceGit.ViewModels"
|
xmlns:vm="using:SourceGit.ViewModels"
|
||||||
xmlns:m="using:SourceGit.Models"
|
xmlns:m="using:SourceGit.Models"
|
||||||
xmlns:c="using:SourceGit.Converters"
|
xmlns:c="using:SourceGit.Converters"
|
||||||
|
@ -49,31 +50,31 @@
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Menu -->
|
<!-- 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>
|
<Button.Margin>
|
||||||
<OnPlatform Default="4,0,2,3" macOS="4,0,6,3"/>
|
<OnPlatform Default="4,0,2,3" macOS="4,0,6,3"/>
|
||||||
</Button.Margin>
|
</Button.Margin>
|
||||||
|
|
||||||
<Button.Flyout>
|
<Button.Flyout>
|
||||||
<MenuFlyout Placement="BottomEdgeAlignedLeft" VerticalOffset="-8">
|
<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>
|
<MenuItem.Icon>
|
||||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Settings2}"/>
|
<Path Width="14" Height="14" Data="{StaticResource Icons.Settings2}"/>
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="{DynamicResource Text.Hotkeys}" Click="OpenHotkeys">
|
<MenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Hotkeys}"/>
|
<Path Width="14" Height="14" Data="{StaticResource Icons.Hotkeys}"/>
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="-"/>
|
<MenuItem Header="-"/>
|
||||||
<MenuItem Header="{DynamicResource Text.SelfUpdate}" Click="Check4Update">
|
<MenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Path Width="14" Height="14" Data="{StaticResource Icons.SoftwareUpdate}"/>
|
<Path Width="14" Height="14" Data="{StaticResource Icons.SoftwareUpdate}"/>
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="-"/>
|
<MenuItem Header="-"/>
|
||||||
<MenuItem Header="{DynamicResource Text.About}" Click="OpenAboutDialog">
|
<MenuItem Header="{DynamicResource Text.About}" Command="{x:Static s:App.OpenAboutCommand}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Info}"/>
|
<Path Width="14" Height="14" Data="{StaticResource Icons.Info}"/>
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
|
@ -85,9 +86,9 @@
|
||||||
|
|
||||||
<!-- Pages Tabs-->
|
<!-- Pages Tabs-->
|
||||||
<Grid x:Name="launcherTabsContainer" Grid.Column="1" Height="30" ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Bottom" SizeChanged="UpdateScrollIndicator">
|
<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}"/>
|
<Path Width="8" Height="14" Stretch="Fill" Data="{StaticResource Icons.TriangleLeft}"/>
|
||||||
</Button>
|
</RepeatButton>
|
||||||
|
|
||||||
<ScrollViewer Grid.Column="1"
|
<ScrollViewer Grid.Column="1"
|
||||||
x:Name="launcherTabsScroller"
|
x:Name="launcherTabsScroller"
|
||||||
|
@ -95,7 +96,8 @@
|
||||||
VerticalScrollBarVisibility="Disabled"
|
VerticalScrollBarVisibility="Disabled"
|
||||||
DoubleTapped="MaximizeOrRestoreWindow"
|
DoubleTapped="MaximizeOrRestoreWindow"
|
||||||
PointerPressed="BeginMoveWindow"
|
PointerPressed="BeginMoveWindow"
|
||||||
PointerWheelChanged="ScrollTabs">
|
PointerWheelChanged="ScrollTabs"
|
||||||
|
ScrollChanged="OnTabsScrollChanged">
|
||||||
<StackPanel x:Name="launcherTabsBar" Orientation="Horizontal" SizeChanged="UpdateScrollIndicator">
|
<StackPanel x:Name="launcherTabsBar" Orientation="Horizontal" SizeChanged="UpdateScrollIndicator">
|
||||||
<ListBox Classes="launcher_page_tabbar"
|
<ListBox Classes="launcher_page_tabbar"
|
||||||
ItemsSource="{Binding Pages}"
|
ItemsSource="{Binding Pages}"
|
||||||
|
@ -239,9 +241,9 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</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}"/>
|
<Path Width="8" Height="14" Stretch="Fill" Data="{StaticResource Icons.TriangleRight}"/>
|
||||||
</Button>
|
</RepeatButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Caption Buttons (Windows/Linux)-->
|
<!-- Caption Buttons (Windows/Linux)-->
|
||||||
|
|
|
@ -98,6 +98,15 @@ namespace SourceGit.Views
|
||||||
protected override void OnKeyDown(KeyEventArgs e)
|
protected override void OnKeyDown(KeyEventArgs e)
|
||||||
{
|
{
|
||||||
var vm = DataContext as ViewModels.Launcher;
|
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)) ||
|
if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Meta)) ||
|
||||||
(!OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Control)))
|
(!OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Control)))
|
||||||
{
|
{
|
||||||
|
@ -223,7 +232,7 @@ namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
if (e.Delta.Y < 0)
|
if (e.Delta.Y < 0)
|
||||||
launcherTabsScroller.LineRight();
|
launcherTabsScroller.LineRight();
|
||||||
else
|
else if (e.Delta.Y > 0)
|
||||||
launcherTabsScroller.LineLeft();
|
launcherTabsScroller.LineLeft();
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
@ -256,6 +265,15 @@ namespace SourceGit.Views
|
||||||
e.Handled = true;
|
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)
|
private void SetupDragAndDrop(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Border border)
|
if (sender is Border border)
|
||||||
|
@ -337,33 +355,6 @@ namespace SourceGit.Views
|
||||||
OnPopupCancel(sender, e);
|
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 bool _pressedTab = false;
|
||||||
private Point _pressedTabPosition = new Point();
|
private Point _pressedTabPosition = new Point();
|
||||||
private bool _startDrag = false;
|
private bool _startDrag = false;
|
||||||
|
|
|
@ -231,7 +231,7 @@
|
||||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/>
|
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/>
|
||||||
</TabItem.Header>
|
</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"
|
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||||
Text="{DynamicResource Text.Preference.Git.Path}"
|
Text="{DynamicResource Text.Preference.Git.Path}"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
|
@ -351,6 +351,41 @@
|
||||||
<CheckBox Grid.Row="7" Grid.Column="1"
|
<CheckBox Grid.Row="7" Grid.Column="1"
|
||||||
Content="{DynamicResource Text.Preference.Git.AutoFetch}"
|
Content="{DynamicResource Text.Preference.Git.AutoFetch}"
|
||||||
IsChecked="{Binding GitAutoFetch, Mode=TwoWay}"/>
|
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>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
|
|
@ -403,6 +403,7 @@
|
||||||
<DataGrid Grid.Row="1"
|
<DataGrid Grid.Row="1"
|
||||||
ItemsSource="{Binding SearchedCommits}"
|
ItemsSource="{Binding SearchedCommits}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
|
SelectedItem="{Binding SearchResultSelectedCommit, Mode=OneWay}"
|
||||||
CanUserReorderColumns="False"
|
CanUserReorderColumns="False"
|
||||||
CanUserResizeColumns="False"
|
CanUserResizeColumns="False"
|
||||||
CanUserSortColumns="False"
|
CanUserSortColumns="False"
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
</TextBox.InnerLeftContent>
|
</TextBox.InnerLeftContent>
|
||||||
|
|
||||||
<TextBox.InnerRightContent>
|
<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}"/>
|
<Path Width="16" Height="16" Margin="0,0,0,0" Data="{StaticResource Icons.Clear}" Fill="{DynamicResource Brush.FG1}"/>
|
||||||
</Button>
|
</Button>
|
||||||
</TextBox.InnerRightContent>
|
</TextBox.InnerRightContent>
|
||||||
|
|
|
@ -257,14 +257,7 @@ namespace SourceGit.Views
|
||||||
Dispatcher.UIThread.Invoke(() =>
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
{
|
{
|
||||||
var repo = ViewModels.Preference.AddRepository(root, gitDir);
|
var repo = ViewModels.Preference.AddRepository(root, gitDir);
|
||||||
var node = new ViewModels.RepositoryNode()
|
var node = ViewModels.Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, parent);
|
||||||
{
|
|
||||||
Id = repo.FullPath,
|
|
||||||
Name = Path.GetFileName(repo.FullPath),
|
|
||||||
Bookmark = 0,
|
|
||||||
IsRepository = true,
|
|
||||||
};
|
|
||||||
ViewModels.Preference.AddNode(node, parent);
|
|
||||||
launcher.OpenRepositoryInTab(node, page);
|
launcher.OpenRepositoryInTab(node, page);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue