mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-11-01 13:13:21 -07:00
optimize<*>: remove deprecated apis (older than .NET 6)
This commit is contained in:
parent
890ff29ac7
commit
338f91357e
8 changed files with 677 additions and 749 deletions
|
@ -1,186 +1,178 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 用于取消命令执行的上下文对象
|
||||
/// </summary>
|
||||
public class Context {
|
||||
public bool IsCancelRequested { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命令接口
|
||||
/// </summary>
|
||||
public class Command {
|
||||
|
||||
/// <summary>
|
||||
/// 读取全部输出时的结果
|
||||
/// </summary>
|
||||
public class ReadToEndResult {
|
||||
public bool IsSuccess { get; set; }
|
||||
public string Output { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上下文
|
||||
/// </summary>
|
||||
public Context Ctx { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 运行路径
|
||||
/// </summary>
|
||||
public string Cwd { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 参数
|
||||
/// </summary>
|
||||
public string Args { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 是否忽略错误
|
||||
/// </summary>
|
||||
public bool DontRaiseError { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 使用标准错误输出
|
||||
/// </summary>
|
||||
public bool TraitErrorAsOutput { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 用于设置该进程独有的环境变量
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Envs { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// 运行
|
||||
/// </summary>
|
||||
public bool Exec() {
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Models.Preference.Instance.Git.Path;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
|
||||
|
||||
foreach (var kv in Envs) start.EnvironmentVariables[kv.Key] = kv.Value;
|
||||
|
||||
var progressFilter = new Regex(@"\s\d+%\s");
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var isCancelled = false;
|
||||
|
||||
proc.OutputDataReceived += (o, e) => {
|
||||
if (Ctx != null && Ctx.IsCancelRequested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
#if NET48
|
||||
if (!proc.HasExited) proc.Kill();
|
||||
#else
|
||||
if (!proc.HasExited) proc.Kill(true);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Data == null) return;
|
||||
OnReadline(e.Data);
|
||||
};
|
||||
proc.ErrorDataReceived += (o, e) => {
|
||||
if (Ctx != null && Ctx.IsCancelRequested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
#if NET48
|
||||
if (!proc.HasExited) proc.Kill();
|
||||
#else
|
||||
if (!proc.HasExited) proc.Kill(true);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.Data)) return;
|
||||
if (TraitErrorAsOutput) OnReadline(e.Data);
|
||||
|
||||
if (progressFilter.IsMatch(e.Data)) return;
|
||||
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return;
|
||||
errs.Add(e.Data);
|
||||
};
|
||||
|
||||
try {
|
||||
proc.Start();
|
||||
} catch (Exception e) {
|
||||
if (!DontRaiseError) Models.Exception.Raise(e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
|
||||
if (!isCancelled && exitCode != 0 && errs.Count > 0) {
|
||||
if (!DontRaiseError) Models.Exception.Raise(string.Join("\n", errs));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接读取全部标准输出
|
||||
/// </summary>
|
||||
public ReadToEndResult ReadToEnd() {
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Models.Preference.Instance.Git.Path;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
try {
|
||||
proc.Start();
|
||||
} catch (Exception e) {
|
||||
return new ReadToEndResult() {
|
||||
Output = "",
|
||||
Error = e.Message,
|
||||
IsSuccess = false,
|
||||
};
|
||||
}
|
||||
|
||||
var rs = new ReadToEndResult();
|
||||
rs.Output = proc.StandardOutput.ReadToEnd();
|
||||
rs.Error = proc.StandardError.ReadToEnd();
|
||||
|
||||
proc.WaitForExit();
|
||||
rs.IsSuccess = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用Exec时的读取函数
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public virtual void OnReadline(string line) { }
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 用于取消命令执行的上下文对象
|
||||
/// </summary>
|
||||
public class Context {
|
||||
public bool IsCancelRequested { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命令接口
|
||||
/// </summary>
|
||||
public class Command {
|
||||
|
||||
/// <summary>
|
||||
/// 读取全部输出时的结果
|
||||
/// </summary>
|
||||
public class ReadToEndResult {
|
||||
public bool IsSuccess { get; set; }
|
||||
public string Output { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上下文
|
||||
/// </summary>
|
||||
public Context Ctx { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 运行路径
|
||||
/// </summary>
|
||||
public string Cwd { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 参数
|
||||
/// </summary>
|
||||
public string Args { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 是否忽略错误
|
||||
/// </summary>
|
||||
public bool DontRaiseError { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 使用标准错误输出
|
||||
/// </summary>
|
||||
public bool TraitErrorAsOutput { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 用于设置该进程独有的环境变量
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Envs { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// 运行
|
||||
/// </summary>
|
||||
public bool Exec() {
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Models.Preference.Instance.Git.Path;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
|
||||
|
||||
foreach (var kv in Envs) start.EnvironmentVariables[kv.Key] = kv.Value;
|
||||
|
||||
var progressFilter = new Regex(@"\s\d+%\s");
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var isCancelled = false;
|
||||
|
||||
proc.OutputDataReceived += (o, e) => {
|
||||
if (Ctx != null && Ctx.IsCancelRequested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited) proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Data == null) return;
|
||||
OnReadline(e.Data);
|
||||
};
|
||||
proc.ErrorDataReceived += (o, e) => {
|
||||
if (Ctx != null && Ctx.IsCancelRequested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited) proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.Data)) return;
|
||||
if (TraitErrorAsOutput) OnReadline(e.Data);
|
||||
|
||||
if (progressFilter.IsMatch(e.Data)) return;
|
||||
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return;
|
||||
errs.Add(e.Data);
|
||||
};
|
||||
|
||||
try {
|
||||
proc.Start();
|
||||
} catch (Exception e) {
|
||||
if (!DontRaiseError) Models.Exception.Raise(e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
|
||||
if (!isCancelled && exitCode != 0 && errs.Count > 0) {
|
||||
if (!DontRaiseError) Models.Exception.Raise(string.Join("\n", errs));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接读取全部标准输出
|
||||
/// </summary>
|
||||
public ReadToEndResult ReadToEnd() {
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Models.Preference.Instance.Git.Path;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
try {
|
||||
proc.Start();
|
||||
} catch (Exception e) {
|
||||
return new ReadToEndResult() {
|
||||
Output = "",
|
||||
Error = e.Message,
|
||||
IsSuccess = false,
|
||||
};
|
||||
}
|
||||
|
||||
var rs = new ReadToEndResult();
|
||||
rs.Output = proc.StandardOutput.ReadToEnd();
|
||||
rs.Error = proc.StandardError.ReadToEnd();
|
||||
|
||||
proc.WaitForExit();
|
||||
rs.IsSuccess = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用Exec时的读取函数
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public virtual void OnReadline(string line) { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,80 +1,69 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#if NET6_0
|
||||
using System.Net.Http;
|
||||
#else
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
#endif
|
||||
|
||||
namespace SourceGit.Models {
|
||||
|
||||
/// <summary>
|
||||
/// Github开放API中Release信息格式
|
||||
/// </summary>
|
||||
public class Version {
|
||||
[JsonPropertyName("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; }
|
||||
[JsonPropertyName("target_commitish")]
|
||||
public string CommitSHA { get; set; }
|
||||
[JsonPropertyName("prerelease")]
|
||||
public bool PreRelease { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonPropertyName("body")]
|
||||
public string Body { get; set; }
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public string PublishTime {
|
||||
get { return CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); }
|
||||
}
|
||||
|
||||
public string IsPrerelease {
|
||||
get { return PreRelease ? "YES" : "NO"; }
|
||||
}
|
||||
|
||||
public static void Check(Action<Version> onUpgradable) {
|
||||
if (!Preference.Instance.General.CheckForUpdate) return;
|
||||
|
||||
var curDayOfYear = DateTime.Now.DayOfYear;
|
||||
var lastDayOfYear = Preference.Instance.General.LastCheckDay;
|
||||
if (lastDayOfYear != curDayOfYear) {
|
||||
Preference.Instance.General.LastCheckDay = curDayOfYear;
|
||||
Task.Run(async () => {
|
||||
try {
|
||||
#if NET6_0
|
||||
var req = new HttpClient();
|
||||
var rsp = await req.GetAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest");
|
||||
rsp.EnsureSuccessStatusCode();
|
||||
|
||||
var raw = await rsp.Content.ReadAsStringAsync();
|
||||
#else
|
||||
var web = new WebClient() { Encoding = Encoding.UTF8 };
|
||||
var raw = await web.DownloadStringTaskAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest");
|
||||
#endif
|
||||
var ver = JsonSerializer.Deserialize<Version>(raw);
|
||||
var cur = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
var matches = Regex.Match(ver.TagName, @"^v(\d+)\.(\d+).*");
|
||||
if (!matches.Success) return;
|
||||
|
||||
var major = int.Parse(matches.Groups[1].Value);
|
||||
var minor = int.Parse(matches.Groups[2].Value);
|
||||
if (major > cur.Major || (major == cur.Major && minor > cur.Minor)) {
|
||||
onUpgradable?.Invoke(ver);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace SourceGit.Models {
|
||||
|
||||
/// <summary>
|
||||
/// Github开放API中Release信息格式
|
||||
/// </summary>
|
||||
public class Version {
|
||||
[JsonPropertyName("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; }
|
||||
[JsonPropertyName("target_commitish")]
|
||||
public string CommitSHA { get; set; }
|
||||
[JsonPropertyName("prerelease")]
|
||||
public bool PreRelease { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonPropertyName("body")]
|
||||
public string Body { get; set; }
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public string PublishTime {
|
||||
get { return CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); }
|
||||
}
|
||||
|
||||
public string IsPrerelease {
|
||||
get { return PreRelease ? "YES" : "NO"; }
|
||||
}
|
||||
|
||||
public static void Check(Action<Version> onUpgradable) {
|
||||
if (!Preference.Instance.General.CheckForUpdate) return;
|
||||
|
||||
var curDayOfYear = DateTime.Now.DayOfYear;
|
||||
var lastDayOfYear = Preference.Instance.General.LastCheckDay;
|
||||
if (lastDayOfYear != curDayOfYear) {
|
||||
Preference.Instance.General.LastCheckDay = curDayOfYear;
|
||||
Task.Run(async () => {
|
||||
try {
|
||||
var req = new HttpClient();
|
||||
var rsp = await req.GetAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest");
|
||||
rsp.EnsureSuccessStatusCode();
|
||||
|
||||
var raw = await rsp.Content.ReadAsStringAsync();
|
||||
var ver = JsonSerializer.Deserialize<Version>(raw);
|
||||
var cur = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
var matches = Regex.Match(ver.TagName, @"^v(\d+)\.(\d+).*");
|
||||
if (!matches.Success) return;
|
||||
|
||||
var major = int.Parse(matches.Groups[1].Value);
|
||||
var minor = int.Parse(matches.Groups[2].Value);
|
||||
if (major > cur.Major || (major == cur.Major && minor > cur.Minor)) {
|
||||
onUpgradable?.Invoke(ver);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,124 +1,124 @@
|
|||
<controls:Window
|
||||
x:Class="SourceGit.Views.About"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="{DynamicResource Text.About}"
|
||||
Width="420" SizeToContent="Height"
|
||||
ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Help}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.About}"/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="Quit"
|
||||
Width="28"
|
||||
Padding="8"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<!-- Content -->
|
||||
<StackPanel Grid.Row="2" Orientation="Vertical">
|
||||
|
||||
<!-- LOGO -->
|
||||
<Path
|
||||
Margin="0,16,0,0"
|
||||
Width="64" Height="64"
|
||||
Data="{StaticResource Icon.Git}"
|
||||
Fill="{DynamicResource Brush.Logo}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock
|
||||
Margin="0,24,0,8"
|
||||
Text="{DynamicResource Text.About.Title}"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="18"/>
|
||||
|
||||
<!-- Version -->
|
||||
<TextBlock
|
||||
x:Name="version"
|
||||
Margin="0,0,0,8"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="11"
|
||||
Text="VERSION: v1.0 .NET: v5.0"/>
|
||||
|
||||
<DataGrid
|
||||
x:Name="hotkeys"
|
||||
Grid.Row="2"
|
||||
Margin="16,8"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
GridLinesVisibility="All"
|
||||
HorizontalGridLinesBrush="{DynamicResource Brush.Border0}"
|
||||
VerticalGridLinesBrush="{DynamicResource Brush.Border0}"
|
||||
HeadersVisibility="Column"
|
||||
RowHeight="24"
|
||||
ColumnHeaderHeight="24"
|
||||
IsHitTestVisible="False"
|
||||
BorderThickness="1,1,0,0"
|
||||
BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<DataGrid.ColumnHeaderStyle>
|
||||
<Style TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Border BorderThickness="0,0,1,1" BorderBrush="{DynamicResource Brush.Border0}" Background="{DynamicResource Brush.Window}">
|
||||
<TextBlock
|
||||
Text="{TemplateBinding Content}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="DemiBold"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.ColumnHeaderStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Width="100" Header="{DynamicResource Text.Hotkeys.Col.Key}" Binding="{Binding .Key}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
|
||||
<DataGridTextColumn Width="*" Header="{DynamicResource Text.Hotkeys.Col.Desc}" Binding="{Binding .Desc}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Official site -->
|
||||
<TextBlock HorizontalAlignment="Center" Margin="0,4,0,16">
|
||||
<TextBlock Text="© 2021" Margin="0" Padding="0"/>
|
||||
<Hyperlink NavigateUri="https://github.com/sourcegit-scm/sourcegit.git" RequestNavigate="OnRequestNavigate" ToolTip="https://github.com/sourcegit-scm/sourcegit.git">
|
||||
<Run Text="SourceGit."/>
|
||||
</Hyperlink>
|
||||
<TextBlock Text="All rights reserved." Margin="0" Padding="0"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:Window>
|
||||
<controls:Window
|
||||
x:Class="SourceGit.Views.About"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="{DynamicResource Text.About}"
|
||||
Width="420" SizeToContent="Height"
|
||||
ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Help}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.About}"/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="Quit"
|
||||
Width="28"
|
||||
Padding="8"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<!-- Content -->
|
||||
<StackPanel Grid.Row="2" Orientation="Vertical">
|
||||
|
||||
<!-- LOGO -->
|
||||
<Path
|
||||
Margin="0,16,0,0"
|
||||
Width="64" Height="64"
|
||||
Data="{StaticResource Icon.Git}"
|
||||
Fill="{DynamicResource Brush.Logo}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock
|
||||
Margin="0,24,0,8"
|
||||
Text="{DynamicResource Text.About.Title}"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="18"/>
|
||||
|
||||
<!-- Version -->
|
||||
<TextBlock
|
||||
x:Name="version"
|
||||
Margin="0,0,0,8"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="11"
|
||||
Text="VERSION: v1.0"/>
|
||||
|
||||
<DataGrid
|
||||
x:Name="hotkeys"
|
||||
Grid.Row="2"
|
||||
Margin="16,8"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
GridLinesVisibility="All"
|
||||
HorizontalGridLinesBrush="{DynamicResource Brush.Border0}"
|
||||
VerticalGridLinesBrush="{DynamicResource Brush.Border0}"
|
||||
HeadersVisibility="Column"
|
||||
RowHeight="24"
|
||||
ColumnHeaderHeight="24"
|
||||
IsHitTestVisible="False"
|
||||
BorderThickness="1,1,0,0"
|
||||
BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<DataGrid.ColumnHeaderStyle>
|
||||
<Style TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Border BorderThickness="0,0,1,1" BorderBrush="{DynamicResource Brush.Border0}" Background="{DynamicResource Brush.Window}">
|
||||
<TextBlock
|
||||
Text="{TemplateBinding Content}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="DemiBold"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.ColumnHeaderStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Width="100" Header="{DynamicResource Text.Hotkeys.Col.Key}" Binding="{Binding .Key}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
|
||||
<DataGridTextColumn Width="*" Header="{DynamicResource Text.Hotkeys.Col.Desc}" Binding="{Binding .Desc}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Official site -->
|
||||
<TextBlock HorizontalAlignment="Center" Margin="0,4,0,16">
|
||||
<TextBlock Text="© 2021" Margin="0" Padding="0"/>
|
||||
<Hyperlink NavigateUri="https://github.com/sourcegit-scm/sourcegit.git" RequestNavigate="OnRequestNavigate" ToolTip="https://github.com/sourcegit-scm/sourcegit.git">
|
||||
<Run Text="SourceGit."/>
|
||||
</Hyperlink>
|
||||
<TextBlock Text="All rights reserved." Margin="0" Padding="0"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:Window>
|
||||
|
|
|
@ -1,55 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
|
||||
/// <summary>
|
||||
/// 关于对话框
|
||||
/// </summary>
|
||||
public partial class About : Controls.Window {
|
||||
|
||||
public class Keymap {
|
||||
public string Key { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public Keymap(string k, string d) { Key = k; Desc = App.Text($"Hotkeys.{d}"); }
|
||||
}
|
||||
|
||||
public About() {
|
||||
InitializeComponent();
|
||||
|
||||
var asm = Assembly.GetExecutingAssembly().GetName();
|
||||
var framework = AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName;
|
||||
var dotnetVer = framework.Substring(framework.IndexOf("=") + 1);
|
||||
|
||||
version.Text = $"VERSION : v{asm.Version.Major}.{asm.Version.Minor} .NET : {dotnetVer}";
|
||||
|
||||
hotkeys.ItemsSource = new List<Keymap>() {
|
||||
new Keymap("CTRL + T", "NewTab"),
|
||||
new Keymap("CTRL + W", "CloseTab"),
|
||||
new Keymap("CTRL + TAB", "NextTab"),
|
||||
new Keymap("CTRL + [1-9]", "SwitchTo"),
|
||||
new Keymap("CTRL + F", "Search"),
|
||||
new Keymap("F5", "Refresh"),
|
||||
new Keymap("SPACE", "ToggleStage"),
|
||||
new Keymap("ESC", "CancelPopup"),
|
||||
};
|
||||
}
|
||||
|
||||
private void OnRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
|
||||
#if NET48
|
||||
Process.Start(e.Uri.AbsoluteUri);
|
||||
#else
|
||||
var info = new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}");
|
||||
info.CreateNoWindow = true;
|
||||
Process.Start(info);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
|
||||
/// <summary>
|
||||
/// 关于对话框
|
||||
/// </summary>
|
||||
public partial class About : Controls.Window {
|
||||
|
||||
public class Keymap {
|
||||
public string Key { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public Keymap(string k, string d) { Key = k; Desc = App.Text($"Hotkeys.{d}"); }
|
||||
}
|
||||
|
||||
public About() {
|
||||
InitializeComponent();
|
||||
|
||||
var asm = Assembly.GetExecutingAssembly().GetName();
|
||||
version.Text = $"VERSION : v{asm.Version.Major}.{asm.Version.Minor}";
|
||||
|
||||
hotkeys.ItemsSource = new List<Keymap>() {
|
||||
new Keymap("CTRL + T", "NewTab"),
|
||||
new Keymap("CTRL + W", "CloseTab"),
|
||||
new Keymap("CTRL + TAB", "NextTab"),
|
||||
new Keymap("CTRL + [1-9]", "SwitchTo"),
|
||||
new Keymap("CTRL + F", "Search"),
|
||||
new Keymap("F5", "Refresh"),
|
||||
new Keymap("SPACE", "ToggleStage"),
|
||||
new Keymap("ESC", "CancelPopup"),
|
||||
};
|
||||
}
|
||||
|
||||
private void OnRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
|
||||
var info = new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}");
|
||||
info.CreateNoWindow = true;
|
||||
Process.Start(info);
|
||||
}
|
||||
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,258 +1,230 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
#if NET6_0
|
||||
using System.Net.Http;
|
||||
#endif
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 头像控件
|
||||
/// </summary>
|
||||
public class Avatar : Image {
|
||||
|
||||
/// <summary>
|
||||
/// 显示FallbackLabel时的背景色
|
||||
/// </summary>
|
||||
private static readonly Brush[] BACKGROUND_BRUSHES = new Brush[] {
|
||||
new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90),
|
||||
new LinearGradientBrush(Colors.DodgerBlue, Colors.LightSkyBlue, 90),
|
||||
new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90),
|
||||
new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90),
|
||||
new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 头像资源本地缓存路径
|
||||
/// </summary>
|
||||
public static readonly string CACHE_PATH = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"SourceGit",
|
||||
"avatars");
|
||||
|
||||
/// <summary>
|
||||
/// 邮件属性定义
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty EmailProperty = DependencyProperty.Register(
|
||||
"Email",
|
||||
typeof(string),
|
||||
typeof(Avatar),
|
||||
new PropertyMetadata(null, OnEmailChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 邮件属性
|
||||
/// </summary>
|
||||
public string Email {
|
||||
get { return (string)GetValue(EmailProperty); }
|
||||
set { SetValue(EmailProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载头像失败时显示的Label属性定义
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register(
|
||||
"FallbackLabel",
|
||||
typeof(string),
|
||||
typeof(Avatar),
|
||||
new PropertyMetadata("?", OnFallbackLabelChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 下载头像失败时显示的Label属性
|
||||
/// </summary>
|
||||
public string FallbackLabel {
|
||||
get { return (string)GetValue(FallbackLabelProperty); }
|
||||
set { SetValue(FallbackLabelProperty, value); }
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<Avatar>> requesting = new Dictionary<string, List<Avatar>>();
|
||||
private static Dictionary<string, BitmapImage> loaded = new Dictionary<string, BitmapImage>();
|
||||
private static Task loader = null;
|
||||
|
||||
private int colorIdx = 0;
|
||||
private FormattedText label = null;
|
||||
|
||||
public Avatar() {
|
||||
SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality);
|
||||
SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Auto);
|
||||
Unloaded += (o, e) => Cancel(Email);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消一个下载任务
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
private void Cancel(string email) {
|
||||
if (!string.IsNullOrEmpty(email) && requesting.ContainsKey(email)) {
|
||||
if (requesting[email].Count <= 1) {
|
||||
requesting.Remove(email);
|
||||
} else {
|
||||
requesting[email].Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染实现
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
base.OnRender(dc);
|
||||
|
||||
if (Source == null && label != null) {
|
||||
var corner = Math.Max(2, Width / 16);
|
||||
var offsetX = (double)0;
|
||||
if (HorizontalAlignment == HorizontalAlignment.Right) {
|
||||
offsetX = -Width * 0.5;
|
||||
} else if (HorizontalAlignment == HorizontalAlignment.Left) {
|
||||
offsetX = Width * 0.5;
|
||||
}
|
||||
|
||||
Brush brush = BACKGROUND_BRUSHES[colorIdx];
|
||||
dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner);
|
||||
dc.DrawText(label, new Point(label.Width * -0.5 + offsetX, label.Height * -0.5));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示文本变化时触发
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnFallbackLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Avatar a = d as Avatar;
|
||||
if (a == null) return;
|
||||
|
||||
var placeholder = a.FallbackLabel.Length > 0 ? a.FallbackLabel.Substring(0, 1) : "?";
|
||||
|
||||
a.colorIdx = 0;
|
||||
a.label = new FormattedText(
|
||||
placeholder,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||||
a.Width * 0.65,
|
||||
Brushes.White,
|
||||
VisualTreeHelper.GetDpi(a).PixelsPerDip);
|
||||
|
||||
var chars = placeholder.ToCharArray();
|
||||
foreach (var ch in chars) a.colorIdx += Math.Abs(ch);
|
||||
a.colorIdx = a.colorIdx % BACKGROUND_BRUSHES.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 邮件变化时触发
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnEmailChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Avatar a = d as Avatar;
|
||||
if (a == null) return;
|
||||
|
||||
a.Cancel(e.OldValue as string);
|
||||
a.Source = null;
|
||||
a.InvalidateVisual();
|
||||
|
||||
var email = e.NewValue as string;
|
||||
if (string.IsNullOrEmpty(email)) return;
|
||||
|
||||
if (loaded.ContainsKey(email)) {
|
||||
a.Source = loaded[email];
|
||||
return;
|
||||
}
|
||||
|
||||
if (requesting.ContainsKey(email)) {
|
||||
requesting[email].Add(a);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim()));
|
||||
string md5 = "";
|
||||
for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2");
|
||||
md5 = md5.ToLower();
|
||||
|
||||
string filePath = Path.Combine(CACHE_PATH, md5);
|
||||
if (File.Exists(filePath)) {
|
||||
var img = new BitmapImage(new Uri(filePath));
|
||||
loaded.Add(email, img);
|
||||
a.Source = img;
|
||||
return;
|
||||
}
|
||||
|
||||
requesting.Add(email, new List<Avatar>());
|
||||
requesting[email].Add(a);
|
||||
|
||||
Action job = () => {
|
||||
if (!requesting.ContainsKey(email)) return;
|
||||
|
||||
try {
|
||||
#if NET6_0
|
||||
var req = new HttpClient().GetAsync(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404");
|
||||
req.Wait();
|
||||
|
||||
var rsp = req.Result;
|
||||
if (rsp != null && rsp.StatusCode == HttpStatusCode.OK) {
|
||||
var writer = File.OpenWrite(filePath);
|
||||
rsp.Content.CopyToAsync(writer).Wait();
|
||||
writer.Close();
|
||||
|
||||
a.Dispatcher.Invoke(() => {
|
||||
var img = new BitmapImage(new Uri(filePath));
|
||||
loaded.Add(email, img);
|
||||
|
||||
if (requesting.ContainsKey(email)) {
|
||||
foreach (var one in requesting[email]) one.Source = img;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!loaded.ContainsKey(email)) loaded.Add(email, null);
|
||||
}
|
||||
#else
|
||||
HttpWebRequest req = WebRequest.CreateHttp(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404");
|
||||
req.Timeout = 2000;
|
||||
req.Method = "GET";
|
||||
|
||||
HttpWebResponse rsp = req.GetResponse() as HttpWebResponse;
|
||||
if (rsp.StatusCode == HttpStatusCode.OK) {
|
||||
using (Stream reader = rsp.GetResponseStream())
|
||||
using (FileStream writer = File.OpenWrite(filePath)) {
|
||||
reader.CopyTo(writer);
|
||||
}
|
||||
|
||||
a.Dispatcher.Invoke(() => {
|
||||
var img = new BitmapImage(new Uri(filePath));
|
||||
loaded.Add(email, img);
|
||||
|
||||
if (requesting.ContainsKey(email)) {
|
||||
foreach (var one in requesting[email]) one.Source = img;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!loaded.ContainsKey(email)) loaded.Add(email, null);
|
||||
}
|
||||
#endif
|
||||
} catch {
|
||||
if (!loaded.ContainsKey(email)) loaded.Add(email, null);
|
||||
}
|
||||
|
||||
requesting.Remove(email);
|
||||
};
|
||||
|
||||
if (loader != null && !loader.IsCompleted) {
|
||||
loader = loader.ContinueWith(t => { job(); });
|
||||
} else {
|
||||
loader = Task.Run(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 头像控件
|
||||
/// </summary>
|
||||
public class Avatar : Image {
|
||||
|
||||
/// <summary>
|
||||
/// 显示FallbackLabel时的背景色
|
||||
/// </summary>
|
||||
private static readonly Brush[] BACKGROUND_BRUSHES = new Brush[] {
|
||||
new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90),
|
||||
new LinearGradientBrush(Colors.DodgerBlue, Colors.LightSkyBlue, 90),
|
||||
new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90),
|
||||
new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90),
|
||||
new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 头像资源本地缓存路径
|
||||
/// </summary>
|
||||
public static readonly string CACHE_PATH = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"SourceGit",
|
||||
"avatars");
|
||||
|
||||
/// <summary>
|
||||
/// 邮件属性定义
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty EmailProperty = DependencyProperty.Register(
|
||||
"Email",
|
||||
typeof(string),
|
||||
typeof(Avatar),
|
||||
new PropertyMetadata(null, OnEmailChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 邮件属性
|
||||
/// </summary>
|
||||
public string Email {
|
||||
get { return (string)GetValue(EmailProperty); }
|
||||
set { SetValue(EmailProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载头像失败时显示的Label属性定义
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register(
|
||||
"FallbackLabel",
|
||||
typeof(string),
|
||||
typeof(Avatar),
|
||||
new PropertyMetadata("?", OnFallbackLabelChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 下载头像失败时显示的Label属性
|
||||
/// </summary>
|
||||
public string FallbackLabel {
|
||||
get { return (string)GetValue(FallbackLabelProperty); }
|
||||
set { SetValue(FallbackLabelProperty, value); }
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<Avatar>> requesting = new Dictionary<string, List<Avatar>>();
|
||||
private static Dictionary<string, BitmapImage> loaded = new Dictionary<string, BitmapImage>();
|
||||
private static Task loader = null;
|
||||
|
||||
private int colorIdx = 0;
|
||||
private FormattedText label = null;
|
||||
|
||||
public Avatar() {
|
||||
SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality);
|
||||
SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Auto);
|
||||
Unloaded += (o, e) => Cancel(Email);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消一个下载任务
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
private void Cancel(string email) {
|
||||
if (!string.IsNullOrEmpty(email) && requesting.ContainsKey(email)) {
|
||||
if (requesting[email].Count <= 1) {
|
||||
requesting.Remove(email);
|
||||
} else {
|
||||
requesting[email].Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染实现
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
base.OnRender(dc);
|
||||
|
||||
if (Source == null && label != null) {
|
||||
var corner = Math.Max(2, Width / 16);
|
||||
var offsetX = (double)0;
|
||||
if (HorizontalAlignment == HorizontalAlignment.Right) {
|
||||
offsetX = -Width * 0.5;
|
||||
} else if (HorizontalAlignment == HorizontalAlignment.Left) {
|
||||
offsetX = Width * 0.5;
|
||||
}
|
||||
|
||||
Brush brush = BACKGROUND_BRUSHES[colorIdx];
|
||||
dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner);
|
||||
dc.DrawText(label, new Point(label.Width * -0.5 + offsetX, label.Height * -0.5));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示文本变化时触发
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnFallbackLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Avatar a = d as Avatar;
|
||||
if (a == null) return;
|
||||
|
||||
var placeholder = a.FallbackLabel.Length > 0 ? a.FallbackLabel.Substring(0, 1) : "?";
|
||||
|
||||
a.colorIdx = 0;
|
||||
a.label = new FormattedText(
|
||||
placeholder,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||||
a.Width * 0.65,
|
||||
Brushes.White,
|
||||
VisualTreeHelper.GetDpi(a).PixelsPerDip);
|
||||
|
||||
var chars = placeholder.ToCharArray();
|
||||
foreach (var ch in chars) a.colorIdx += Math.Abs(ch);
|
||||
a.colorIdx = a.colorIdx % BACKGROUND_BRUSHES.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 邮件变化时触发
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnEmailChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Avatar a = d as Avatar;
|
||||
if (a == null) return;
|
||||
|
||||
a.Cancel(e.OldValue as string);
|
||||
a.Source = null;
|
||||
a.InvalidateVisual();
|
||||
|
||||
var email = e.NewValue as string;
|
||||
if (string.IsNullOrEmpty(email)) return;
|
||||
|
||||
if (loaded.ContainsKey(email)) {
|
||||
a.Source = loaded[email];
|
||||
return;
|
||||
}
|
||||
|
||||
if (requesting.ContainsKey(email)) {
|
||||
requesting[email].Add(a);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim()));
|
||||
string md5 = "";
|
||||
for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2");
|
||||
md5 = md5.ToLower();
|
||||
|
||||
string filePath = Path.Combine(CACHE_PATH, md5);
|
||||
if (File.Exists(filePath)) {
|
||||
var img = new BitmapImage(new Uri(filePath));
|
||||
loaded.Add(email, img);
|
||||
a.Source = img;
|
||||
return;
|
||||
}
|
||||
|
||||
requesting.Add(email, new List<Avatar>());
|
||||
requesting[email].Add(a);
|
||||
|
||||
Action job = () => {
|
||||
if (!requesting.ContainsKey(email)) return;
|
||||
|
||||
try {
|
||||
var req = new HttpClient().GetAsync(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404");
|
||||
req.Wait();
|
||||
|
||||
var rsp = req.Result;
|
||||
if (rsp != null && rsp.StatusCode == HttpStatusCode.OK) {
|
||||
var writer = File.OpenWrite(filePath);
|
||||
rsp.Content.CopyToAsync(writer).Wait();
|
||||
writer.Close();
|
||||
|
||||
a.Dispatcher.Invoke(() => {
|
||||
var img = new BitmapImage(new Uri(filePath));
|
||||
loaded.Add(email, img);
|
||||
|
||||
if (requesting.ContainsKey(email)) {
|
||||
foreach (var one in requesting[email]) one.Source = img;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!loaded.ContainsKey(email)) loaded.Add(email, null);
|
||||
}
|
||||
} catch {
|
||||
if (!loaded.ContainsKey(email)) loaded.Add(email, null);
|
||||
}
|
||||
|
||||
requesting.Remove(email);
|
||||
};
|
||||
|
||||
if (loader != null && !loader.IsCompleted) {
|
||||
loader = loader.ContinueWith(t => { job(); });
|
||||
} else {
|
||||
loader = Task.Run(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,28 @@
|
|||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
/// <summary>
|
||||
/// 新版本提示窗口
|
||||
/// </summary>
|
||||
public partial class Upgrade : Controls.Window {
|
||||
public Models.Version Version { get; set; } = new Models.Version();
|
||||
|
||||
public Upgrade(Models.Version ver) {
|
||||
Version = ver;
|
||||
InitializeComponent();
|
||||
txtRelease.Text = App.Text("UpdateAvailable.Title", ver.Name);
|
||||
}
|
||||
|
||||
private void Download(object sender, RoutedEventArgs e) {
|
||||
var url = $"https://github.com/sourcegit-scm/sourcegit/releases/{Version.TagName}";
|
||||
#if NET48
|
||||
Process.Start(url);
|
||||
#else
|
||||
var info = new ProcessStartInfo("cmd", $"/c start {url}");
|
||||
info.CreateNoWindow = true;
|
||||
Process.Start(info);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
/// <summary>
|
||||
/// 新版本提示窗口
|
||||
/// </summary>
|
||||
public partial class Upgrade : Controls.Window {
|
||||
public Models.Version Version { get; set; } = new Models.Version();
|
||||
|
||||
public Upgrade(Models.Version ver) {
|
||||
Version = ver;
|
||||
InitializeComponent();
|
||||
txtRelease.Text = App.Text("UpdateAvailable.Title", ver.Name);
|
||||
}
|
||||
|
||||
private void Download(object sender, RoutedEventArgs e) {
|
||||
var url = $"https://github.com/sourcegit-scm/sourcegit/releases/{Version.TagName}";
|
||||
var info = new ProcessStartInfo("cmd", $"/c start {url}");
|
||||
info.CreateNoWindow = true;
|
||||
Process.Start(info);
|
||||
}
|
||||
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,15 +72,6 @@ namespace SourceGit.Views.Widgets {
|
|||
} else {
|
||||
searching = true;
|
||||
foreach (var c in cachedCommits) {
|
||||
#if NET48
|
||||
if (c.SHA.Contains(filter)
|
||||
|| c.Subject.Contains(filter)
|
||||
|| c.Message.Contains(filter)
|
||||
|| c.Author.Name.Contains(filter)
|
||||
|| c.Committer.Name.Contains(filter)) {
|
||||
visible.Add(c);
|
||||
}
|
||||
#else
|
||||
if (c.SHA.Contains(filter, StringComparison.Ordinal)
|
||||
|| c.Subject.Contains(filter, StringComparison.Ordinal)
|
||||
|| c.Message.Contains(filter, StringComparison.Ordinal)
|
||||
|
@ -88,7 +79,6 @@ namespace SourceGit.Views.Widgets {
|
|||
|| c.Committer.Name.Contains(filter, StringComparison.Ordinal)) {
|
||||
visible.Add(c);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -215,11 +215,7 @@ namespace SourceGit.Views.Widgets {
|
|||
|
||||
if (!added) Changes.Add(c);
|
||||
|
||||
#if NET48
|
||||
int sepIdx = c.Path.IndexOf("/", StringComparison.Ordinal);
|
||||
#else
|
||||
int sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal);
|
||||
#endif
|
||||
if (sepIdx < 0) {
|
||||
GetOrAddTreeNode(Nodes, c.Path, c, false);
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue