refactor<*>: rewrite all codes...

This commit is contained in:
leo 2021-04-29 20:05:55 +08:00
parent 89ff8aa744
commit 30ab8ae954
342 changed files with 17208 additions and 19633 deletions

88
src/Views/About.xaml Normal file
View file

@ -0,0 +1,88 @@
<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="{StaticResource Text.About}"
Height="280" Width="400"
ResizeMode="NoResize">
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
</WindowChrome.WindowChrome>
<controls:WindowBorder>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar -->
<Grid Grid.Row="0" Background="{StaticResource 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="{StaticResource Text.About}"/>
<!-- Close -->
<controls:IconButton
Grid.Column="3"
Click="Quit"
Padding="9"
Icon="{StaticResource Icon.Close}"
HoverBackground="Red"
WindowChrome.IsHitTestVisibleInChrome="True"/>
</Grid>
<!-- Content -->
<StackPanel Grid.Row="1" Orientation="Vertical">
<!-- LOGO -->
<Path
Margin="0,16,0,0"
Width="64" Height="64"
Data="{StaticResource Icon.Git}"
Fill="{StaticResource Brush.Logo}"/>
<!-- Title -->
<TextBlock
Margin="0,24,0,8"
Text="{StaticResource Text.About.Title}"
HorizontalAlignment="Center"
FontSize="18" FontWeight="Bold"/>
<!-- Version -->
<TextBlock
x:Name="version"
Margin="0,0,0,8"
HorizontalAlignment="Center"
FontSize="11"
Text="VERSION: v1.0 .NET: v5.0"/>
<!-- Official site -->
<TextBlock HorizontalAlignment="Center" Margin="0,16,0,4">
<Hyperlink NavigateUri="https://gitee.com/sourcegit/SourceGit.git" RequestNavigate="OnRequestNavigate">
<Run Text="https://gitee.com/sourcegit/SourceGit.git"/>
</Hyperlink>
</TextBlock>
<!-- Copyrights -->
<TextBlock
Margin="0,4"
Text="Copyright © sourcegit 2021. All rights reserved."
HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</controls:WindowBorder>
</Window>

33
src/Views/About.xaml.cs Normal file
View file

@ -0,0 +1,33 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
namespace SourceGit.Views {
/// <summary>
/// 关于对话框
/// </summary>
public partial class About : Window {
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}";
}
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();
}
}
}

136
src/Views/Blame.xaml Normal file
View file

@ -0,0 +1,136 @@
<Window x:Class="SourceGit.Views.Blame"
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"
Title="{StaticResource Text.Blame}"
WindowStartupLocation="CenterOwner"
Height="600" Width="800">
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
</WindowChrome.WindowChrome>
<controls:WindowBorder>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title Bar -->
<Grid Grid.Row="0" Background="{StaticResource 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.Commit}"/>
<!-- Title -->
<TextBlock Grid.Column="1" Text="{StaticResource Text.Blame}"/>
<!-- Window Commands -->
<StackPanel Grid.Column="3" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<controls:IconButton Click="Minimize" Width="28" Padding="8" Icon="{StaticResource Icon.Minimize}" HoverBackground="#40000000" Opacity="1"/>
<controls:IconButton Click="MaximizeOrRestore" Width="28" Padding="8" Icon="{StaticResource Icon.Maximize}" HoverBackground="#40000000" Opacity="1"/>
<controls:IconButton Click="Quit" Width="28" Padding="8" Icon="{StaticResource Icon.Close}" HoverBackground="Red" Opacity="1"/>
</StackPanel>
</Grid>
<!-- Body -->
<Border Grid.Row="1">
<Grid Margin="4,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="txtFile" FontSize="11" Foreground="{StaticResource Brush.FG2}" FontFamily="Consolas"/>
<TextBlock Grid.Column="1" HorizontalAlignment="Right" Foreground="{StaticResource Brush.FG2}" FontFamily="11" Text="{StaticResource Text.Blame.Tip}"/>
</Grid>
</Border>
<!-- Viewer -->
<DataGrid
Grid.Row="2"
x:Name="blame"
GridLinesVisibility="Vertical"
VerticalGridLinesBrush="{StaticResource Brush.Border2}"
BorderBrush="{StaticResource Brush.Border2}"
BorderThickness="1"
FrozenColumnCount="1"
RowHeight="16"
SelectionUnit="FullRow"
SelectionMode="Single"
FontFamily="Consolas"
SizeChanged="OnViewerSizeChanged">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="RequestBringIntoView" Handler="OnViewerRequestBringIntoView"/>
<EventSetter Event="ContextMenuOpening" Handler="OnViewerContextMenuOpening"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding Line.LineNumber}" ElementStyle="{StaticResource Style.TextBlock.LineNumber}"/>
<DataGridTemplateColumn Width="SizeToCells" MinWidth="1" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding BG}" BorderThickness="0">
<TextBlock Text="{Binding Line.Content}" Background="Transparent" Foreground="{StaticResource Brush.FG1}" FontFamily="Consolas" FontSize="12" Margin="0" Padding="2,0,0,0"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel
x:Name="notSupport"
Grid.Row="2"
Orientation="Vertical"
VerticalAlignment="Center" HorizontalAlignment="Center"
Background="{StaticResource Brush.Window}"
Visibility="Collapsed">
<Path Width="64" Height="64" Data="{StaticResource Icon.Error}" Fill="{StaticResource Brush.FG2}"/>
<TextBlock Text="{StaticResource Text.BlameTypeNotSupported}" Margin="0,16,0,0" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
<!-- Loading -->
<controls:Loading x:Name="loading" Grid.Row="2" Width="48" Height="48" IsAnimating="True"/>
<!-- Popup to show commit info -->
<Popup x:Name="popup" Grid.Row="2" Placement="MousePoint" IsOpen="False" StaysOpen="False" Focusable="True">
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.Popup}">
<Grid Margin="4">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{StaticResource Text.Blame.SHA}" Foreground="{StaticResource Brush.FG2}" Margin="4,0"/>
<Label Grid.Row="0" Grid.Column="1" x:Name="commitID" Margin="8,0,4,0" Padding="0" VerticalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{StaticResource Text.Blame.Author}" Foreground="{StaticResource Brush.FG2}" Margin="4,0"/>
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="authorName" Margin="8,0,4,0"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{StaticResource Text.Blame.ModifyTime}" Foreground="{StaticResource Brush.FG2}" Margin="4,0"/>
<TextBlock Grid.Row="2" Grid.Column="1" x:Name="authorTime" Margin="8,0,4,0"/>
</Grid>
</Border>
</Popup>
</Grid>
</controls:WindowBorder>
</Window>

195
src/Views/Blame.xaml.cs Normal file
View file

@ -0,0 +1,195 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace SourceGit.Views {
/// <summary>
/// 逐行追溯
/// </summary>
public partial class Blame : Window {
private static readonly Brush[] BG = new Brush[] {
Brushes.Transparent,
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
};
private string repo = null;
private string lastSHA = null;
private int lastBG = 1;
public class Record : INotifyPropertyChanged {
private Brush bg = null;
public event PropertyChangedEventHandler PropertyChanged;
public Models.BlameLine Line { get; set; }
public Brush OrgBG { get; set; }
public Brush BG {
get { return bg; }
set {
if (value != bg) {
bg = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("BG"));
}
}
}
}
public ObservableCollection<Record> Records { get; set; }
public Blame(string repo, string file, string revision) {
InitializeComponent();
this.repo = repo;
Records = new ObservableCollection<Record>();
txtFile.Text = $"{file}@{revision.Substring(0, 8)}";
Task.Run(() => {
var lfs = new Commands.IsLFSFiltered(repo, file).Result();
if (lfs) {
Dispatcher.Invoke(() => {
loading.IsAnimating = false;
loading.Visibility = Visibility.Collapsed;
notSupport.Visibility = Visibility.Visible;
});
return;
}
var rs = new Commands.Blame(repo, file, revision).Result();
if (rs.IsBinary) {
Dispatcher.Invoke(() => {
loading.IsAnimating = false;
loading.Visibility = Visibility.Collapsed;
notSupport.Visibility = Visibility.Visible;
});
} else {
foreach (var line in rs.Lines) {
var r = new Record();
r.Line = line;
r.BG = GetBG(line.CommitSHA);
r.OrgBG = r.BG;
Records.Add(r);
}
Dispatcher.Invoke(() => {
loading.IsAnimating = false;
loading.Visibility = Visibility.Collapsed;
var formatted = new FormattedText(
$"{Records.Count}",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(blame.FontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
12.0,
Brushes.Black,
VisualTreeHelper.GetDpi(this).PixelsPerDip);
var lineNumberWidth = formatted.Width + 16;
var minWidth = blame.ActualWidth - lineNumberWidth;
if (Records.Count * 16 > blame.ActualHeight) minWidth -= 8;
blame.Columns[0].Width = lineNumberWidth;
blame.Columns[1].MinWidth = minWidth;
blame.ItemsSource = Records;
blame.UpdateLayout();
});
}
});
}
private Brush GetBG(string sha) {
if (lastSHA != sha) {
lastSHA = sha;
lastBG = 1 - lastBG;
}
return BG[lastBG];
}
#region WINDOW_COMMANDS
private void Minimize(object sender, RoutedEventArgs e) {
SystemCommands.MinimizeWindow(this);
}
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
if (WindowState == WindowState.Normal) {
SystemCommands.MaximizeWindow(this);
} else {
SystemCommands.RestoreWindow(this);
}
}
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
#endregion
#region EVENTS
private T GetVisualChild<T>(DependencyObject parent) where T : Visual {
T child = null;
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++) {
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null) {
child = GetVisualChild<T>(v);
}
if (child != null) {
break;
}
}
return child;
}
private void OnViewerSizeChanged(object sender, SizeChangedEventArgs e) {
var total = blame.ActualWidth;
var offset = blame.NonFrozenColumnsViewportHorizontalOffset;
var minWidth = total - offset;
var scroller = GetVisualChild<ScrollViewer>(blame);
if (scroller != null && scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
blame.Columns[1].MinWidth = minWidth;
blame.Columns[1].Width = DataGridLength.SizeToCells;
blame.UpdateLayout();
}
private void OnViewerRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
e.Handled = true;
}
private void OnViewerContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var record = (sender as DataGridRow).DataContext as Record;
if (record == null) return;
foreach (var r in Records) {
if (r.Line.CommitSHA == record.Line.CommitSHA) {
r.BG = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
} else {
r.BG = r.OrgBG;
}
}
Hyperlink link = new Hyperlink(new Run(record.Line.CommitSHA));
link.ToolTip = App.Text("Goto");
link.Click += (o, e) => {
Models.Watcher.Get(repo).NavigateTo(record.Line.CommitSHA);
e.Handled = true;
};
commitID.Content = link;
authorName.Text = record.Line.Author;
authorTime.Text = record.Line.Time;
popup.IsOpen = true;
ev.Handled = true;
}
#endregion
}
}

View file

@ -0,0 +1,202 @@
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;
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("?"));
/// <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;
/// <summary>
/// 渲染实现
/// </summary>
/// <param name="dc"></param>
protected override void OnRender(DrawingContext dc) {
base.OnRender(dc);
if (Source == null) {
var placeholder = FallbackLabel.Length > 0 ? FallbackLabel.Substring(0, 1) : "?";
var formatted = new FormattedText(
placeholder,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
Width * 0.65,
Brushes.White,
VisualTreeHelper.GetDpi(this).PixelsPerDip);
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;
}
var chars = placeholder.ToCharArray();
var sum = 0;
foreach (var ch in chars) sum += Math.Abs(ch);
Brush brush = BACKGROUND_BRUSHES[sum % BACKGROUND_BRUSHES.Length];
dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner);
dc.DrawText(formatted, new Point(formatted.Width * -0.5 + offsetX, formatted.Height * -0.5));
}
}
/// <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;
var oldEmail = e.OldValue as string;
if (!string.IsNullOrEmpty(oldEmail) && requesting.ContainsKey(oldEmail)) {
if (requesting[oldEmail].Count <= 1) {
requesting.Remove(oldEmail);
} else {
requesting[oldEmail].Remove(a);
}
}
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 = () => {
try {
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);
}
} 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);
}
}
}
}

View file

@ -0,0 +1,52 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// 徽章
/// </summary>
public class Badge : Border {
private TextBlock label = null;
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
"Label",
typeof(string),
typeof(Border),
new PropertyMetadata("", OnLabelChanged));
public string Label {
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public Badge() {
Width = double.NaN;
Height = 18;
CornerRadius = new CornerRadius(9);
VerticalAlignment = VerticalAlignment.Center;
Background = FindResource("Brush.Badge") as Brush;
Visibility = Visibility.Collapsed;
label = new TextBlock();
label.FontSize = 10;
label.HorizontalAlignment = HorizontalAlignment.Center;
label.Margin = new Thickness(9, 0, 9, 0);
Child = label;
}
private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
Badge badge = d as Badge;
if (badge != null) {
var text = e.NewValue as string;
if (string.IsNullOrEmpty(text) || text == "0") {
badge.Visibility = Visibility.Collapsed;
} else {
badge.label.Text = text;
badge.Visibility = Visibility.Visible;
}
}
}
}
}

View file

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace SourceGit.Views.Controls {
/// <summary>
/// 标签页图标
/// </summary>
public class Bookmark : Border {
private Path icon = null;
public static readonly Brush[] COLORS = new Brush[] {
Brushes.Transparent,
Brushes.White,
Brushes.Red,
Brushes.Orange,
Brushes.Yellow,
Brushes.ForestGreen,
Brushes.Purple,
Brushes.DeepSkyBlue,
Brushes.Magenta,
};
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register("Color", typeof(int), typeof(Bookmark), new PropertyMetadata(0, UpdateBookmark));
public int Color {
get { return (int)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public static readonly DependencyProperty IsNewPageProperty =
DependencyProperty.Register("IsNewPage", typeof(bool), typeof(Bookmark), new PropertyMetadata(false, UpdateBookmark));
public bool IsNewPage {
get { return (bool)GetValue(IsNewPageProperty); }
set { SetValue(IsNewPageProperty, value); }
}
public static readonly DependencyProperty HideOnZeroProperty =
DependencyProperty.Register("HideOnZero", typeof(bool), typeof(Bookmark), new PropertyMetadata(false, UpdateBookmark));
public bool HideOnZero {
get { return (bool)GetValue(HideOnZeroProperty); }
set { SetValue(HideOnZeroProperty, value); }
}
public Bookmark() {
icon = new Path();
Child = icon;
UpdateBookmark(this, new DependencyPropertyChangedEventArgs());
}
private static void UpdateBookmark(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var mark = d as Bookmark;
if (mark == null) return;
if (mark.HideOnZero && mark.Color == 0) {
mark.Visibility = Visibility.Collapsed;
return;
}
if (!mark.IsNewPage) {
if (mark.Color == 0) {
mark.icon.Fill = mark.FindResource("Brush.FG1") as Brush;
mark.icon.Data = mark.FindResource("Icon.Git") as Geometry;
} else {
mark.icon.Fill = COLORS[mark.Color % COLORS.Length];
mark.icon.Data = mark.FindResource("Icon.Bookmark") as Geometry;
}
} else {
mark.icon.Fill = mark.FindResource("Brush.FG1") as Brush;
mark.icon.Data = mark.FindResource("Icon.NewPage") as Geometry;
}
mark.Visibility = Visibility.Visible;
}
}
}

View file

@ -0,0 +1,105 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Shapes;
namespace SourceGit.Views.Controls {
/// <summary>
/// 用于切换变更显示模式的按钮
/// </summary>
public class ChangeDisplaySwitcher : Button {
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register(
"Mode",
typeof(Models.Change.DisplayMode),
typeof(ChangeDisplaySwitcher),
new PropertyMetadata(Models.Change.DisplayMode.Tree, OnModeChanged));
public Models.Change.DisplayMode Mode {
get { return (Models.Change.DisplayMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
public static readonly RoutedEvent ModeChangedEvent = EventManager.RegisterRoutedEvent(
"ModeChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ChangeDisplaySwitcher));
public event RoutedEventHandler ModeChanged {
add { AddHandler(ModeChangedEvent, value); }
remove { RemoveHandler(ModeChangedEvent, value); }
}
private Path icon = null;
public ChangeDisplaySwitcher() {
icon = new Path();
icon.Fill = FindResource("Brush.FG2") as Brush;
icon.Data = FindResource("Icon.Tree") as Geometry;
Content = icon;
Style = FindResource("Style.Button") as Style;
BorderThickness = new Thickness(0);
ToolTip = App.Text("ChangeDisplayMode");
Click += OnClicked;
}
private void OnClicked(object sender, RoutedEventArgs e) {
if (ContextMenu != null) {
ContextMenu.IsOpen = true;
e.Handled = true;
return;
}
var menu = new ContextMenu();
menu.Placement = PlacementMode.Bottom;
menu.PlacementTarget = this;
menu.StaysOpen = false;
menu.Focusable = true;
FillMenu(menu, "ChangeDisplayMode.Tree", "Icon.Tree", Models.Change.DisplayMode.Tree);
FillMenu(menu, "ChangeDisplayMode.List", "Icon.List", Models.Change.DisplayMode.List);
FillMenu(menu, "ChangeDisplayMode.Grid", "Icon.Grid", Models.Change.DisplayMode.Grid);
ContextMenu = menu;
ContextMenu.IsOpen = true;
e.Handled = true;
}
private void FillMenu(ContextMenu menu, string header, string icon, Models.Change.DisplayMode useMode) {
var iconMode = new Path();
iconMode.Width = 12;
iconMode.Height = 12;
iconMode.Fill = FindResource("Brush.FG2") as Brush;
iconMode.Data = FindResource(icon) as Geometry;
var item = new MenuItem();
item.Icon = iconMode;
item.Header = App.Text(header);
item.Click += (o, e) => Mode = useMode;
menu.Items.Add(item);
}
private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var elem = d as ChangeDisplaySwitcher;
if (elem != null) {
switch (elem.Mode) {
case Models.Change.DisplayMode.Tree:
elem.icon.Data = elem.FindResource("Icon.Tree") as Geometry;
break;
case Models.Change.DisplayMode.List:
elem.icon.Data = elem.FindResource("Icon.List") as Geometry;
break;
case Models.Change.DisplayMode.Grid:
elem.icon.Data = elem.FindResource("Icon.Grid") as Geometry;
break;
}
elem.RaiseEvent(new RoutedEventArgs(ModeChangedEvent));
}
}
}
}

View file

@ -0,0 +1,111 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// 变更状态图标
/// </summary>
class ChangeStatusIcon : FrameworkElement {
public static readonly DependencyProperty ChangeProperty = DependencyProperty.Register(
"Change",
typeof(Models.Change),
typeof(ChangeStatusIcon),
new PropertyMetadata(null, ForceDirty));
public Models.Change Change {
get { return (Models.Change)GetValue(ChangeProperty); }
set { SetValue(ChangeProperty, value); }
}
public static readonly DependencyProperty IsLocalChangeProperty = DependencyProperty.Register(
"IsLocalChange",
typeof(bool),
typeof(ChangeStatusIcon),
new PropertyMetadata(false, ForceDirty));
public bool IsLocalChange {
get { return (bool)GetValue(IsLocalChangeProperty); }
set { SetValue(IsLocalChangeProperty, value); }
}
private Brush background;
private FormattedText label;
public ChangeStatusIcon() {
HorizontalAlignment = HorizontalAlignment.Center;
VerticalAlignment = VerticalAlignment.Center;
}
protected override void OnRender(DrawingContext dc) {
if (background == null || label == null) return;
var corner = Math.Max(2, Width / 16);
dc.DrawRoundedRectangle(background, null, new Rect(0, 0, Width, Height), corner, corner);
dc.DrawText(label, new Point((Width - label.Width) * 0.5, 0));
}
private static void ForceDirty(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var icon = d as ChangeStatusIcon;
if (icon == null) return;
if (icon.Change == null) {
icon.background = null;
icon.label = null;
return;
}
string txt;
if (icon.IsLocalChange) {
if (icon.Change.IsConflit) {
icon.background = Brushes.OrangeRed;
txt = "!";
} else {
icon.background = GetBackground(icon.Change.WorkTree);
txt = GetLabel(icon.Change.WorkTree);
}
} else {
icon.background = GetBackground(icon.Change.Index);
txt = GetLabel(icon.Change.Index);
}
icon.label = new FormattedText(
txt,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
icon.Width * 0.8,
new SolidColorBrush(Color.FromRgb(241,241,241)),
VisualTreeHelper.GetDpi(icon).PixelsPerDip);
icon.InvalidateVisual();
}
private static Brush GetBackground(Models.Change.Status status) {
switch (status) {
case Models.Change.Status.Modified: return new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90);
case Models.Change.Status.Added: return new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90);
case Models.Change.Status.Deleted: return new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90);
case Models.Change.Status.Renamed: return new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90);
case Models.Change.Status.Copied: return new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90);
case Models.Change.Status.Unmerged: return new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90);
case Models.Change.Status.Untracked: return new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90);
default: return Brushes.Transparent;
}
}
private static string GetLabel(Models.Change.Status status) {
switch (status) {
case Models.Change.Status.Modified: return "±";
case Models.Change.Status.Added: return "✚";
case Models.Change.Status.Deleted: return "▬";
case Models.Change.Status.Renamed: return "➔";
case Models.Change.Status.Copied: return "❏";
case Models.Change.Status.Unmerged: return "U";
case Models.Change.Status.Untracked: return "?";
default: return "?";
}
}
}
}

View file

@ -0,0 +1,312 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// 提交线路图
/// </summary>
public class CommitGraph : FrameworkElement {
public static readonly Brush[] COLORS = new Brush[] {
Brushes.Orange,
Brushes.ForestGreen,
Brushes.Gold,
Brushes.Magenta,
Brushes.Red,
Brushes.Gray,
Brushes.Turquoise,
Brushes.Olive,
};
public static readonly double UNIT_WIDTH = 12;
public static readonly double HALF_WIDTH = 6;
public static readonly double UNIT_HEIGHT = 24;
public static readonly double HALF_HEIGHT = 12;
public class Line {
public List<Point> Points = new List<Point>();
public int Color = 0;
}
public class LineHelper {
public string Next;
public bool IsMerged;
public double LastX;
public double LastY;
public Line Line;
public LineHelper(string next, bool isMerged, int color, Point start) {
Next = next;
IsMerged = isMerged;
LastX = start.X;
LastY = start.Y;
Line = new Line();
Line.Color = color % COLORS.Length;
Line.Points.Add(start);
}
public void Add(double x, double y, bool isEnd = false) {
if (x > LastX) {
Line.Points.Add(new Point(LastX, LastY));
Line.Points.Add(new Point(x, y - HALF_HEIGHT));
} else if (x < LastX) {
Line.Points.Add(new Point(LastX, LastY + HALF_HEIGHT));
Line.Points.Add(new Point(x, y));
}
LastX = x;
LastY = y;
if (isEnd) {
var last = Line.Points.Last();
if (LastX != last.X || LastY != last.Y) Line.Points.Add(new Point(LastX, LastY));
}
}
}
public class Link {
public Point Start;
public Point Control;
public Point End;
public int Color;
}
public class Dot {
public Point Center;
public int Color;
}
public class Data {
public List<Line> Lines = new List<Line>();
public List<Link> Links = new List<Link>();
public List<Dot> Dots = new List<Dot>();
}
private Data data = null;
private double startY = 0;
public CommitGraph() {
IsHitTestVisible = false;
ClipToBounds = true;
}
public void SetOffset(double offset) {
startY = offset;
InvalidateVisual();
}
public void SetData(List<Models.Commit> commits, bool isSearchResult = false) {
if (isSearchResult) {
foreach (var c in commits) c.Margin = new Thickness(0);
data = null;
return;
}
var temp = new Data();
var unsolved = new List<LineHelper>();
var mapUnsolved = new Dictionary<string, LineHelper>();
var ended = new List<LineHelper>();
var offsetY = -HALF_HEIGHT;
var colorIdx = 0;
foreach (var commit in commits) {
var major = null as LineHelper;
var isMerged = commit.IsMerged;
var oldCount = unsolved.Count;
// 更新Y坐标
offsetY += UNIT_HEIGHT;
// 找到第一个依赖于本提交的树,将其他依赖于本提交的树标记为终止,并对已存在的线路调整(防止线重合)
double offsetX = -HALF_WIDTH;
foreach (var l in unsolved) {
if (l.Next == commit.SHA) {
if (major == null) {
offsetX += UNIT_WIDTH;
major = l;
if (commit.Parents.Count > 0) {
major.Next = commit.Parents[0];
if (!mapUnsolved.ContainsKey(major.Next)) mapUnsolved.Add(major.Next, major);
} else {
major.Next = "ENDED";
ended.Add(l);
}
major.Add(offsetX, offsetY);
} else {
ended.Add(l);
}
isMerged = isMerged || l.IsMerged;
} else {
if (!mapUnsolved.ContainsKey(l.Next)) mapUnsolved.Add(l.Next, l);
offsetX += UNIT_WIDTH;
l.Add(offsetX, offsetY);
}
}
// 处理本提交为非当前分支HEAD的情况创建新依赖线路
if (major == null && commit.Parents.Count > 0) {
offsetX += UNIT_WIDTH;
major = new LineHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY));
unsolved.Add(major);
colorIdx++;
}
// 确定本提交的点的位置
Point position = new Point(offsetX, offsetY);
if (major != null) {
major.IsMerged = isMerged;
position.X = major.LastX;
position.Y = offsetY;
temp.Dots.Add(new Dot() { Center = position, Color = major.Line.Color });
} else {
temp.Dots.Add(new Dot() { Center = position, Color = 0 });
}
// 处理本提交的其他依赖
for (int j = 1; j < commit.Parents.Count; j++) {
var parent = commit.Parents[j];
if (mapUnsolved.ContainsKey(parent)) {
var l = mapUnsolved[parent];
var link = new Link();
link.Start = position;
link.End = new Point(l.LastX, offsetY + HALF_HEIGHT);
link.Control = new Point(link.End.X, link.Start.Y);
link.Color = l.Line.Color;
temp.Links.Add(link);
} else {
offsetX += UNIT_WIDTH;
unsolved.Add(new LineHelper(commit.Parents[j], isMerged, colorIdx, position));
colorIdx++;
}
}
// 处理已终止的线
foreach (var l in ended) {
l.Add(position.X, position.Y, true);
temp.Lines.Add(l.Line);
unsolved.Remove(l);
}
// 加入本次提交
commit.IsMerged = isMerged;
commit.Margin = new Thickness(Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH), 0, 0, 0);
// 清理
ended.Clear();
mapUnsolved.Clear();
}
// 处理尚未终结的线
for (int i = 0; i < unsolved.Count; i++) {
var path = unsolved[i];
var endY = (commits.Count - 0.5) * UNIT_HEIGHT;
if (path.Line.Points.Count == 1 && path.Line.Points[0].Y == endY) continue;
path.Add((i + 0.5) * UNIT_WIDTH, endY, true);
temp.Lines.Add(path.Line);
}
unsolved.Clear();
// 排序
temp.Lines.Sort((l, h) => l.Points[0].Y.CompareTo(h.Points[0].Y));
Dispatcher.Invoke(() => {
data = temp;
InvalidateVisual();
});
}
protected override void OnRender(DrawingContext dc) {
if (data == null) return;
var top = startY;
var bottom = startY + ActualHeight;
dc.PushTransform(new TranslateTransform(0, -startY));
// 绘制曲线
foreach (var line in data.Lines) {
var last = line.Points[0];
var size = line.Points.Count;
if (line.Points[size - 1].Y < top) continue;
if (last.Y > bottom) continue;
var geo = new StreamGeometry();
var pen = new Pen(COLORS[line.Color], 2);
using (var ctx = geo.Open()) {
ctx.BeginFigure(last, false, false);
var ended = false;
for (int i = 1; i < size; i++) {
var cur = line.Points[i];
if (cur.Y > bottom) {
cur.Y = bottom;
ended = true;
}
if (cur.X > last.X) {
ctx.QuadraticBezierTo(new Point(cur.X, last.Y), cur, true, false);
} else if (cur.X < last.X) {
if (i < size - 1) {
cur.Y += HALF_HEIGHT;
var midY = (last.Y + cur.Y) / 2;
var midX = (last.X + cur.X) / 2;
ctx.PolyQuadraticBezierTo(new Point[] {
new Point(last.X, midY),
new Point(midX, midY),
new Point(cur.X, midY),
cur}, true, false);
} else {
ctx.QuadraticBezierTo(new Point(last.X, cur.Y), cur, true, false);
}
} else {
ctx.LineTo(cur, true, false);
}
if (ended) break;
last = cur;
}
}
geo.Freeze();
dc.DrawGeometry(null, pen, geo);
}
// 绘制合并线
foreach (var link in data.Links) {
if (link.End.Y < top) continue;
if (link.Start.Y > bottom) break;
var geo = new StreamGeometry();
var pen = new Pen(COLORS[link.Color], 2);
using (var ctx = geo.Open()) {
ctx.BeginFigure(link.Start, false, false);
ctx.QuadraticBezierTo(link.Control, link.End, true, false);
}
geo.Freeze();
dc.DrawGeometry(null, pen, geo);
}
// 绘制点
foreach (var dot in data.Dots) {
if (dot.Center.Y < top) continue;
if (dot.Center.Y > bottom) break;
dc.DrawEllipse(COLORS[dot.Color], null, dot.Center, 3, 3);
}
}
}
}

View file

@ -0,0 +1,80 @@
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// DragDropAdorner容器
/// </summary>
public class DragDropAdornerLayer : Grid {
private static AdornerLayer host = null;
private static bool enableFeedback = false;
public DragDropAdornerLayer() {
Loaded += (o, e) => host = AdornerLayer.GetAdornerLayer(this);
PreviewGiveFeedback += OnPreviewGiveFeedback;
}
public static void Add(Adorner adorner) {
host.Add(adorner);
enableFeedback = true;
}
public static void Remove(Adorner adorner) {
host.Remove(adorner);
enableFeedback = false;
}
private static void OnPreviewGiveFeedback(object sender, GiveFeedbackEventArgs e) {
if (enableFeedback) host.Update();
e.Handled = true;
}
}
/// <summary>
/// 展示正在拖拽的视图
/// </summary>
public class DragDropAdorner : Adorner {
private Size renderSize;
private Brush renderBrush;
public struct PInPoint {
public int X;
public int Y;
public PInPoint(int x, int y) { X = x; Y = y; }
public PInPoint(double x, double y) { X = (int)x; Y = (int)y; }
}
[DllImport("user32.dll")]
static extern void GetCursorPos(ref PInPoint p);
public DragDropAdorner(FrameworkElement elem) : base(elem) {
renderSize = elem.RenderSize;
renderBrush = new VisualBrush(elem);
IsHitTestVisible = false;
DragDropAdornerLayer.Add(this);
}
public void Remove() {
DragDropAdornerLayer.Remove(this);
}
protected override void OnRender(DrawingContext dc) {
base.OnRender(dc);
PInPoint p = new PInPoint();
GetCursorPos(ref p);
Point pos = PointFromScreen(new Point(p.X, p.Y));
Rect rect = new Rect(pos.X, pos.Y, renderSize.Width, renderSize.Height);
dc.PushOpacity(1);
dc.DrawRectangle(renderBrush, null, rect);
dc.DrawRectangle(null, new Pen(Brushes.DeepSkyBlue, 2), rect);
}
}
}

View file

@ -0,0 +1,34 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// 简化只有一个Icon的Button
/// </summary>
public class IconButton : Button {
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
"Icon",
typeof(Geometry),
typeof(IconButton),
new PropertyMetadata(null));
public Geometry Icon {
get { return (Geometry)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty HoverBackgroundProperty = DependencyProperty.Register(
"HoverBackground",
typeof(Brush),
typeof(IconButton),
new PropertyMetadata(Brushes.Transparent));
public Brush HoverBackground {
get { return (Brush)GetValue(HoverBackgroundProperty); }
set { SetValue(HoverBackgroundProperty, value); }
}
}
}

View file

@ -0,0 +1,51 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SourceGit.Views.Controls {
/// <summary>
/// 加载中图标
/// </summary>
public class Loading : UserControl {
private Path icon = null;
public static readonly DependencyProperty IsAnimatingProperty = DependencyProperty.Register(
"IsAnimating",
typeof(bool),
typeof(Loading),
new PropertyMetadata(false, OnIsAnimatingChanged));
public bool IsAnimating {
get { return (bool)GetValue(IsAnimatingProperty); }
set { SetValue(IsAnimatingProperty, value); }
}
public Loading() {
icon = new Path();
icon.Data = FindResource("Icon.Loading") as Geometry;
icon.RenderTransformOrigin = new Point(.5, .5);
icon.RenderTransform = new RotateTransform(0);
icon.Width = double.NaN;
icon.Height = double.NaN;
AddChild(icon);
}
private static void OnIsAnimatingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var loading = d as Loading;
if (loading == null) return;
if (loading.IsAnimating) {
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
loading.icon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
} else {
loading.icon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
}
}
}
}

View file

@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.Views.Controls {
/// <summary>
/// 用于方便切换子页面的组件
/// </summary>
public class PageContainer : Grid {
private Dictionary<string, UIElement> pages;
private string front;
public PageContainer() {
pages = new Dictionary<string, UIElement>();
front = null;
Loaded += OnLoaded;
}
public void Add(string id, UIElement view) {
view.Visibility = Visibility.Collapsed;
pages.Add(id, view);
Children.Add(view);
}
public UIElement Get(string id) {
if (pages.ContainsKey(id)) return pages[id];
return null;
}
public void Goto(string id) {
if (!pages.ContainsKey(id)) return;
if (!string.IsNullOrEmpty(front)) {
if (front == id) return;
pages[front].Visibility = Visibility.Collapsed;
}
front = id;
pages[front].Visibility = Visibility.Visible;
}
public void Remove(string id) {
if (!pages.ContainsKey(id)) return;
if (front == id) front = null;
Children.Remove(pages[id]);
pages.Remove(id);
}
private void OnLoaded(object sender, RoutedEventArgs e) {
foreach (var child in Children) {
var elem = child as UIElement;
var id = elem.Uid;
if (string.IsNullOrEmpty(id)) continue;
pages.Add(id, elem);
front = id;
}
if (!string.IsNullOrEmpty(front)) {
pages[front].Visibility = Visibility.Visible;
}
}
}
}

View file

@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Controls {
/// <summary>
/// 可显示弹出面板的容器接口
/// </summary>
public interface IPopupContainer {
void Show(PopupWidget widget);
void ShowAndStart(PopupWidget widget);
void UpdateProgress(string message);
}
/// <summary>
/// 可弹出面板
/// </summary>
public class PopupWidget : UserControl {
private static Dictionary<string, IPopupContainer> containers = new Dictionary<string, IPopupContainer>();
private static string currentContainer = null;
private IPopupContainer mine = null;
/// <summary>
/// 注册一个弹出容器
/// </summary>
/// <param name="id">页面ID</param>
/// <param name="container">容器实例</param>
public static void RegisterContainer(string id, IPopupContainer container) {
if (containers.ContainsKey(id)) containers[id] = container;
else containers.Add(id, container);
}
/// <summary>
/// 删除一个弹出容器
/// </summary>
/// <param name="id">容器ID</param>
public static void UnregisterContainer(string id) {
if (containers.ContainsKey(id)) containers.Remove(id);
}
/// <summary>
/// 设置当前的弹出容器
/// </summary>
/// <param name="id">容器ID</param>
public static void SetCurrentContainer(string id) {
currentContainer = id;
}
/// <summary>
/// 显示
/// </summary>
public void Show() {
if (string.IsNullOrEmpty(currentContainer) || !containers.ContainsKey(currentContainer)) return;
mine = containers[currentContainer];
mine.Show(this);
}
/// <summary>
/// 显示并直接点击开始
/// </summary>
public void ShowAndStart() {
if (string.IsNullOrEmpty(currentContainer) || !containers.ContainsKey(currentContainer)) return;
mine = containers[currentContainer];
mine.ShowAndStart(this);
}
/// <summary>
/// 窗体标题
/// </summary>
/// <returns>返回具体的标题</returns>
public virtual string GetTitle() {
return "TITLE";
}
/// <summary>
/// 点击确定时的回调,由程序自己
/// </summary>
/// <returns>返回一个任务任务预期返回类型为bool表示是否关闭Popup</returns>
public virtual Task<bool> Start() {
return null;
}
/// <summary>
/// 更新进度显示
/// </summary>
/// <param name="message"></param>
protected void UpdateProgress(string message) {
mine?.UpdateProgress(message);
}
}
}

View file

@ -0,0 +1,109 @@
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// 扩展默认TextBox
/// </summary>
public class TextEdit : TextBox {
private bool isPlaceholderShow = false;
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register(
"Placeholder",
typeof(string),
typeof(TextEdit),
new PropertyMetadata(""));
public string Placeholder {
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.Register(
"PlaceholderBaseline",
typeof(AlignmentY),
typeof(TextEdit),
new PropertyMetadata(AlignmentY.Center));
public AlignmentY PlaceholderBaseline {
get { return (AlignmentY)GetValue(PlaceholderBaselineProperty); }
set { SetValue(PlaceholderBaselineProperty, value); }
}
public TextEdit() {
TextChanged += OnTextChanged;
SelectionChanged += OnSelectionChanged;
}
protected override void OnRender(DrawingContext dc) {
base.OnRender(dc);
if (string.IsNullOrEmpty(Text) && !string.IsNullOrEmpty(Placeholder)) {
isPlaceholderShow = true;
var text = new FormattedText(
Placeholder,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily, FontStyle, FontWeight, FontStretches.Normal),
FontSize,
FindResource("Brush.FG2") as Brush,
VisualTreeHelper.GetDpi(this).PixelsPerDip);
switch (PlaceholderBaseline) {
case AlignmentY.Top:
dc.DrawText(text, new Point(4, 4));
break;
case AlignmentY.Center:
dc.DrawText(text, new Point(4, ActualHeight * .5 - text.Height * .5));
break;
default:
dc.DrawText(text, new Point(4, ActualHeight - text.Height - 4));
break;
}
} else {
isPlaceholderShow = false;
}
}
private void OnTextChanged(object sender, TextChangedEventArgs e) {
if (string.IsNullOrEmpty(Text) || isPlaceholderShow) InvalidateVisual();
}
private void OnSelectionChanged(object sender, RoutedEventArgs e) {
if (!IsFocused) return;
if (Mouse.LeftButton == MouseButtonState.Pressed && SelectionLength > 0) {
var p = Mouse.GetPosition(this);
if (p.X <= 8) {
LineLeft();
} else if (p.X >= ActualWidth - 8) {
LineRight();
}
if (p.Y <= 8) {
LineUp();
} else if (p.Y >= ActualHeight - 8) {
LineDown();
}
} else {
var rect = GetRectFromCharacterIndex(CaretIndex);
if (rect.Left <= 0) {
ScrollToHorizontalOffset(HorizontalOffset + rect.Left);
} else if (rect.Right >= ActualWidth) {
ScrollToHorizontalOffset(HorizontalOffset + rect.Right);
}
if (rect.Top <= 0) {
ScrollToVerticalOffset(VerticalOffset + rect.Top);
} else if (rect.Bottom >= ActualHeight) {
ScrollToVerticalOffset(VerticalOffset + rect.Bottom);
}
}
}
}
}

228
src/Views/Controls/Tree.cs Normal file
View file

@ -0,0 +1,228 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// 树
/// </summary>
public class Tree : TreeView {
public static readonly DependencyProperty MultiSelectionProperty = DependencyProperty.Register(
"MultiSelection",
typeof(bool),
typeof(Tree),
new PropertyMetadata(false));
public bool MultiSelection {
get { return (bool)GetValue(MultiSelectionProperty); }
set { SetValue(MultiSelectionProperty, value); }
}
public static readonly DependencyProperty IndentProperty = DependencyProperty.Register(
"Indent",
typeof(double),
typeof(TreeItem),
new PropertyMetadata(16.0));
public double Indent {
get { return (double)GetValue(IndentProperty); }
set { SetValue(IndentProperty, value); }
}
public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent(
"SelectionChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(Tree));
public event RoutedEventHandler SelectionChanged {
add { AddHandler(SelectionChangedEvent, value); }
remove { RemoveHandler(SelectionChangedEvent, value); }
}
public List<object> Selected {
get;
set;
}
public Tree() {
Selected = new List<object>();
PreviewMouseDown += OnPreviewMouseDown;
}
public TreeItem FindItem(DependencyObject elem) {
if (elem == null) return null;
if (elem is TreeItem) return elem as TreeItem;
if (elem is Tree) return null;
return FindItem(VisualTreeHelper.GetParent(elem));
}
public void SelectAll() {
SelectAllChildren(this);
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
}
public void UnselectAll() {
if (Selected.Count == 0) return;
UnselectAllChildren(this);
Selected.Clear();
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
}
protected override DependencyObject GetContainerForItemOverride() {
return new TreeItem(0, Indent);
}
protected override bool IsItemItsOwnContainerOverride(object item) {
return item is TreeItem;
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) {
base.OnItemsSourceChanged(oldValue, newValue);
if (Selected.Count > 0) {
Selected.Clear();
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
}
}
private TreeItem FindItemByDataContext(ItemsControl control, object data) {
if (control == null) return null;
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
if (control.Items[i] == data) return child;
var found = FindItemByDataContext(child, data);
if (found != null) return found;
}
return null;
}
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e) {
var hit = VisualTreeHelper.HitTest(this, e.GetPosition(this));
if (hit == null || hit.VisualHit == null) return;
var item = FindItem(hit.VisualHit);
if (item == null) return;
if (!MultiSelection) {
if (item.IsChecked) return;
AddSelected(item, true);
return;
}
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
if (item.IsChecked) {
RemoveSelected(item);
} else {
AddSelected(item, false);
}
} else if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && Selected.Count > 0) {
var last = FindItemByDataContext(this, Selected.Last());
if (last == item) return;
var lastPos = last.PointToScreen(new Point(0, 0));
var curPos = item.PointToScreen(new Point(0, 0));
if (lastPos.Y > curPos.Y) {
SelectRange(this, item, last);
} else {
SelectRange(this, last, item);
}
AddSelected(item, false);
} else if (e.RightButton == MouseButtonState.Pressed) {
if (item.IsChecked) return;
AddSelected(item, true);
} else {
if (item.IsChecked && Selected.Count == 1) return;
AddSelected(item, true);
}
}
private void AddSelected(TreeItem item, bool removeOthers) {
if (removeOthers && Selected.Count > 0) {
UnselectAllChildren(this);
Selected.Clear();
}
item.IsChecked = true;
Selected.Add(item.DataContext);
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
}
private void RemoveSelected(TreeItem item) {
item.IsChecked = false;
Selected.Remove(item.DataContext);
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
}
private void SelectAllChildren(ItemsControl control) {
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
if (child == null) continue;
child.IsChecked = true;
Selected.Add(control.Items[i]);
SelectAllChildren(child);
}
}
private void UnselectAllChildren(ItemsControl control) {
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
if (child == null) continue;
if (child.IsChecked) child.IsChecked = false;
UnselectAllChildren(child);
}
}
private int SelectRange(ItemsControl control, TreeItem from, TreeItem to, int matches = 0) {
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
if (child == null) continue;
if (matches == 1) {
if (child == to) return 2;
Selected.Add(control.Items[i]);
child.IsChecked = true;
if (TryEndRangeSelection(child, to)) return 2;
} else if (child == from) {
matches = 1;
if (TryEndRangeSelection(child, to)) return 2;
} else {
matches = SelectRange(child, from, to, matches);
if (matches == 2) return 2;
}
}
return matches;
}
private bool TryEndRangeSelection(ItemsControl control, TreeItem end) {
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
if (child == null) continue;
if (child == end) {
return true;
} else {
Selected.Add(control.Items[i]);
child.IsChecked = true;
var ended = TryEndRangeSelection(child, end);
if (ended) return true;
}
}
return false;
}
}
}

View file

@ -0,0 +1,41 @@
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.Views.Controls {
/// <summary>
/// 树节点
/// </summary>
public class TreeItem : TreeViewItem {
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register(
"IsChecked",
typeof(bool),
typeof(TreeItem),
new PropertyMetadata(false));
public bool IsChecked {
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
private int depth = 0;
private double indent = 16;
public TreeItem(int depth, double indent) {
this.depth = depth;
this.indent = indent;
Padding = new Thickness(indent * depth, 0, 0, 0);
RequestBringIntoView += (o, e) => e.Handled = true;
}
protected override DependencyObject GetContainerForItemOverride() {
return new TreeItem(depth + 1, indent);
}
protected override bool IsItemItsOwnContainerOverride(object item) {
return item is TreeItem;
}
}
}

View file

@ -0,0 +1,36 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SourceGit.Views.Controls {
/// <summary>
/// 主窗体Border
/// </summary>
public class WindowBorder : Border {
public WindowBorder() {
Background = FindResource("Brush.Window") as Brush;
BorderBrush = FindResource("Brush.Border0") as Brush;
BorderThickness = new Thickness(1);
Margin = new Thickness(0);
Loaded += (o, e) => {
var owner = Parent as Window;
if (owner != null) {
owner.StateChanged += (o1, e1) => {
if (owner.WindowState == WindowState.Maximized) {
BorderThickness = new Thickness(0);
Margin = new Thickness(
(SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width) / 2
);
} else {
BorderThickness = new Thickness(1);
Margin = new Thickness(0);
}
};
}
};
}
}
}

View file

@ -0,0 +1,18 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace SourceGit.Views.Converters {
public class BoolToCollapsed : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace SourceGit.Views.Converters {
public class BranchToName : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var b = value as Models.Branch;
if (b == null) return "";
return string.IsNullOrEmpty(b.Remote) ? b.Name : $"{b.Remote}/{b.Name}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace SourceGit.Views.Converters {
public class InverseBool : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Globalization;
using System.IO;
using System.Windows.Data;
namespace SourceGit.Views.Converters {
/// <summary>
/// 将路径转换为纯文件名
/// </summary>
public class PureFileName : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return Path.GetFileName(value as string);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Globalization;
using System.IO;
using System.Windows.Data;
namespace SourceGit.Views.Converters {
/// <summary>
/// 将路径转换为纯目录
/// </summary>
public class PureFolderName : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return Path.GetDirectoryName(value as string);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,98 @@
<Window x:Class="SourceGit.Views.FolderBrowser"
x:Name="me"
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"
Title="{StaticResource Text.FolderDialog}"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Height="400" Width="400">
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
</WindowChrome.WindowChrome>
<controls:WindowBorder>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
<RowDefinition Height="48"/>
</Grid.RowDefinitions>
<!-- Title Bar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.TitleBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- ICON -->
<Path Grid.Column="0" Width="16" Height="16" Margin="6,0" Data="{StaticResource Icon.Folder.Open}"/>
<!-- Title -->
<TextBlock Grid.Column="1" Text="{Binding ElementName=me, Path=Description}"/>
<!-- Close -->
<controls:IconButton
Grid.Column="3"
Click="Quit"
Width="32" Height="32"
Padding="9"
WindowChrome.IsHitTestVisibleInChrome="True"
Icon="{StaticResource Icon.Close}"
HoverBackground="Red"/>
</Grid>
<!-- Selected -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="8,4">
<TextBlock Text="{StaticResource Text.FolderDialog.Selected}"/>
<TextBlock x:Name="txtSelected" Text="NONE" Foreground="{StaticResource Brush.FG2}" Margin="8,0,0,0"/>
</StackPanel>
<!-- File System Tree -->
<Border
Grid.Row="2"
Margin="8,0"
Background="{StaticResource Brush.Contents}"
BorderBrush="{StaticResource Brush.Border0}"
BorderThickness="1">
<controls:Tree x:Name="tree" FontFamily="Consolas" ItemsSource="{Binding ElementName=me, Path=Nodes}" SelectionChanged="OnTreeSelectionChanged">
<controls:Tree.ItemContainerStyle>
<Style TargetType="{x:Type controls:TreeItem}" BasedOn="{StaticResource Style.TreeItem}">
<EventSetter Event="Expanded" Handler="OnTreeNodeExpanded"/>
</Style>
</controls:Tree.ItemContainerStyle>
<controls:Tree.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Height="24">
<Path x:Name="Icon" Width="14" Height="14" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
<TextBlock Text="{Binding Name}" Margin="6,0,0,0"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="True">
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</controls:Tree.ItemTemplate>
</controls:Tree>
</Border>
<!-- Options -->
<Border Grid.Row="3">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="btnSure" IsEnabled="False" Width="100" Height="32" Content="{StaticResource Text.Sure}" Background="{StaticResource Brush.Accent1}" BorderBrush="{StaticResource Brush.FG1}"/>
<Button Click="Quit" Width="100" Height="32" Margin="8,0,0,0" Content="{StaticResource Text.Cancel}"/>
</StackPanel>
</Border>
</Grid>
</controls:WindowBorder>
</Window>

View file

@ -0,0 +1,102 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Windows;
namespace SourceGit.Views {
/// <summary>
/// 目录选择对话框
/// </summary>
public partial class FolderBrowser : Window {
/// <summary>
/// 目录树节点.
/// </summary>
public class Node : INotifyPropertyChanged {
public string Name { get; set; }
public string Path { get; set; }
public ObservableCollection<Node> Children { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public Node(string name, string path) {
Name = name;
Path = path;
Children = new ObservableCollection<Node>();
}
public void CollectChildren() {
Children.Clear();
try {
var dir = new DirectoryInfo(Path);
var subs = dir.GetDirectories();
foreach (var sub in subs) {
if ((sub.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
Children.Add(new Node(sub.Name, sub.FullName));
}
} catch { }
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Children"));
}
}
public string Description { get; set; }
public ObservableCollection<Node> Nodes { get; set; }
public FolderBrowser(string description, Action<string> onOK) {
Description = description;
Nodes = new ObservableCollection<Node>();
var drives = DriveInfo.GetDrives();
foreach (var d in drives) {
var node = new Node(d.Name, d.Name);
node.CollectChildren();
Nodes.Add(node);
}
InitializeComponent();
btnSure.Click += (o, e) => {
if (tree.Selected.Count == 0) return;
var node = tree.Selected[0] as Node;
onOK?.Invoke(node.Path);
Close();
};
}
public static void Open(Window owner, string description, Action<string> onOK) {
var dialog = new FolderBrowser(description, onOK);
if (owner == null) dialog.Owner = Application.Current.MainWindow;
else dialog.Owner = owner;
dialog.ShowDialog();
}
private void OnTreeNodeExpanded(object sender, RoutedEventArgs e) {
var item = sender as Controls.TreeItem;
if (item == null) return;
var node = item.DataContext as Node;
if (node == null) return;
foreach (var c in node.Children) c.CollectChildren();
e.Handled = true;
}
private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) {
if (tree.Selected.Count == 0) {
btnSure.IsEnabled = false;
txtSelected.Text = "NONE";
} else {
btnSure.IsEnabled = true;
txtSelected.Text = (tree.Selected[0] as Node).Path;
}
}
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
}
}

125
src/Views/Histories.xaml Normal file
View file

@ -0,0 +1,125 @@
<Window x:Class="SourceGit.Views.Histories"
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"
xmlns:models="clr-namespace:SourceGit.Models"
xmlns:widgets="clr-namespace:SourceGit.Views.Widgets"
mc:Ignorable="d"
Title="{StaticResource Text.FileHistory}"
WindowStartupLocation="CenterOwner"
MinHeight="600" MinWidth="800">
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
</WindowChrome.WindowChrome>
<controls:WindowBorder>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title Bar -->
<Grid Grid.Row="0" Background="{StaticResource 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.Histories}"/>
<!-- Title -->
<TextBlock Grid.Column="1" Text="{StaticResource Text.FileHistory}"/>
<!-- Window Commands -->
<StackPanel Grid.Column="3" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<controls:IconButton Click="Minimize" Width="28" Padding="8" Icon="{StaticResource Icon.Minimize}" HoverBackground="#40000000" Opacity="1"/>
<controls:IconButton Click="MaximizeOrRestore" Width="28" Padding="8" Icon="{StaticResource Icon.Maximize}" HoverBackground="#40000000" Opacity="1"/>
<controls:IconButton Click="Quit" Width="28" Padding="8" Icon="{StaticResource Icon.Close}" HoverBackground="Red" Opacity="1"/>
</StackPanel>
</Grid>
<!-- Body -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" MinWidth="300" MaxWidth="600"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Commit List -->
<DataGrid
x:Name="commitList"
Grid.Column="0"
Background="{StaticResource Brush.Contents}"
SelectionMode="Single"
SelectionUnit="FullRow"
SelectedCellsChanged="OnCommitSelectedChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type models:Commit}">
<Border BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1" Padding="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="36"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:Avatar
Grid.Column="0"
HorizontalAlignment="Center"
Width="32" Height="32"
Email="{Binding Author.Email}"
FallbackLabel="{Binding Author.Name}"/>
<Grid Grid.Column="1" Margin="8,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="72"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="11">
<Hyperlink NavigateUri="{Binding SHA}" RequestNavigate="GotoCommit" Foreground="DarkOrange" ToolTip="GOTO COMMIT">
<Run Text="{Binding ShortSHA, Mode=OneWay}"/>
</Hyperlink>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding Author.Name}" FontSize="11" Foreground="{StaticResource Brush.FG2}"/>
<TextBlock Grid.Column="2" Text="{Binding Author.Time}" FontSize="11" Foreground="{StaticResource Brush.FG2}"/>
</Grid>
<TextBlock Grid.Row="1" Text="{Binding Subject}" Margin="0,6,0,0"/>
</Grid>
</Grid>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- Loading -->
<controls:Loading Grid.Column="0" x:Name="loading" Width="48" Height="48" IsAnimating="True"/>
<!-- Splitter -->
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" Background="Transparent"/>
<!-- Diff Viewer -->
<widgets:DiffViewer Grid.Column="2" x:Name="diffViewer"/>
</Grid>
</Grid>
</controls:WindowBorder>
</Window>

View file

@ -0,0 +1,70 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
namespace SourceGit.Views {
/// <summary>
/// 文件历史
/// </summary>
public partial class Histories : Window {
private string repo = null;
private string file = null;
public Histories(string repo, string file) {
this.repo = repo;
this.file = file;
InitializeComponent();
Task.Run(() => {
var commits = new Commands.Commits(repo, $"-n 10000 -- \"{file}\"").Result();
Dispatcher.Invoke(() => {
loading.IsAnimating = false;
loading.Visibility = Visibility.Collapsed;
commitList.ItemsSource = commits;
commitList.SelectedIndex = 0;
});
});
}
#region WINDOW_COMMANDS
private void Minimize(object sender, RoutedEventArgs e) {
SystemCommands.MinimizeWindow(this);
}
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
if (WindowState == WindowState.Normal) {
SystemCommands.MaximizeWindow(this);
} else {
SystemCommands.RestoreWindow(this);
}
}
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
#endregion
#region EVENTS
private void OnCommitSelectedChanged(object sender, SelectedCellsChangedEventArgs e) {
var commit = (sender as DataGrid).SelectedItem as Models.Commit;
if (commit == null) return;
var start = $"{commit.SHA}^";
if (commit.Parents.Count == 0) start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
diffViewer.Diff(repo, new Widgets.DiffViewer.Option() {
RevisionRange = new string[] { start, commit.SHA },
Path = file
});
}
private void GotoCommit(object sender, RequestNavigateEventArgs e) {
Models.Watcher.Get(repo).NavigateTo(e.Uri.OriginalString);
e.Handled = true;
}
#endregion
}
}

65
src/Views/Launcher.xaml Normal file
View file

@ -0,0 +1,65 @@
<Window x:Class="SourceGit.Views.Launcher"
x:Name="me"
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"
xmlns:widgets="clr-namespace:SourceGit.Views.Widgets"
xmlns:models="clr-namespace:SourceGit.Models"
mc:Ignorable="d"
UseLayoutRounding="True"
Title="{StaticResource Text.About.Title}"
MinWidth="1280" MinHeight="720"
Width="{Binding Source={x:Static models:Preference.Instance}, Path=Window.Width, Mode=TwoWay}"
Height="{Binding Source={x:Static models:Preference.Instance}, Path=Window.Height, Mode=TwoWay}">
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
</WindowChrome.WindowChrome>
<controls:WindowBorder>
<controls:DragDropAdornerLayer>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Titlebar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.TitleBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<!-- Bottom border -->
<Rectangle Grid.Column="0" Grid.ColumnSpan="2" Fill="{StaticResource Brush.Border0}" Height="1" VerticalAlignment="Bottom"/>
<!-- Tabs -->
<widgets:PageTabBar
Grid.Column="0"
x:Name="tabs"
TabAdd="OnTabAdding"
TabSelected="OnTabSelected"
TabClosed="OnTabClosed"/>
<!-- Right controls -->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Height="27" WindowChrome.IsHitTestVisibleInChrome="True">
<controls:IconButton Click="OpenPreference" Width="28" Padding="6" Icon="{StaticResource Icon.Preference}" ToolTip="{StaticResource Text.Launcher.Preference}"/>
<controls:IconButton Click="OpenAbout" Width="28" Padding="6" Icon="{StaticResource Icon.Help}" ToolTip="{StaticResource Text.Launcher.About}"/>
<controls:IconButton Click="Minimize" Width="28" Padding="8" Icon="{StaticResource Icon.Minimize}" HoverBackground="#40000000" Opacity="1"/>
<controls:IconButton Click="MaximizeOrRestore" Width="28" Padding="8" Icon="{StaticResource Icon.Maximize}" HoverBackground="#40000000" Opacity="1"/>
<controls:IconButton Click="Quit" Width="28" Padding="8" Icon="{StaticResource Icon.Close}" HoverBackground="Red" Opacity="1"/>
</StackPanel>
</Grid>
<!-- Contents -->
<controls:PageContainer x:Name="container" Grid.Row="1"/>
<!-- Alerts -->
<widgets:Exceptions
Grid.Row="1"
HorizontalAlignment="Right" VerticalAlignment="Top"
Width="330" Height="Auto"/>
</controls:DragDropAdornerLayer>
</controls:WindowBorder>
</Window>

View file

@ -0,0 +1,97 @@
using System;
using System.Threading.Tasks;
using System.Windows;
namespace SourceGit.Views {
/// <summary>
/// 主窗体
/// </summary>
public partial class Launcher : Window {
public Launcher() {
Models.Watcher.Opened += OpenRepository;
InitializeComponent();
OnTabAdding(null, null);
}
#region OPEN_REPO
private void OpenRepository(Models.Repository repo) {
if (tabs.Goto(repo.Path)) return;
Task.Run(() => {
var cmd = new Commands.Config(repo.Path);
repo.GitFlow.Feature = cmd.Get("gitflow.prefix.feature");
repo.GitFlow.Release = cmd.Get("gitflow.prefix.release");
repo.GitFlow.Hotfix = cmd.Get("gitflow.prefix.hotfix");
});
Commands.AutoFetch.Start(repo.Path);
var page = new Widgets.Dashboard(repo);
var tab = new Widgets.PageTabItem(repo.Name, false, repo.Bookmark, repo.Path);
container.Add(repo.Path, page);
Controls.PopupWidget.RegisterContainer(repo.Path, page);
var front = container.Get(tabs.Current);
if (front == null || front is Widgets.Dashboard) {
tabs.Add(repo.Path, tab);
} else {
tabs.Replace(tabs.Current, repo.Path, tab);
}
}
#endregion
#region RIGHT_COMMANDS
private void OpenPreference(object sender, RoutedEventArgs e) {
var dialog = new Preference() { Owner = this };
dialog.ShowDialog();
}
private void OpenAbout(object sender, RoutedEventArgs e) {
var dialog = new About() { Owner = this };
dialog.ShowDialog();
}
private void Minimize(object sender, RoutedEventArgs e) {
SystemCommands.MinimizeWindow(this);
}
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
if (WindowState == WindowState.Normal) {
SystemCommands.MaximizeWindow(this);
} else {
SystemCommands.RestoreWindow(this);
}
}
private void Quit(object sender, RoutedEventArgs e) {
Application.Current.Shutdown();
}
#endregion
#region TAB_OPERATION
private void OnTabAdding(object sender, RoutedEventArgs e) {
var id = Guid.NewGuid().ToString();
var tab = new Widgets.PageTabItem(App.Text("PageSwitcher.Welcome.Title"), true, 0, App.Text("PageSwitcher.Welcome.Tip"));
var page = new Widgets.Welcome();
container.Add(id, page);
tabs.Add(id, tab);
Controls.PopupWidget.RegisterContainer(id, page);
}
private void OnTabSelected(object sender, Widgets.PageTabBar.TabEventArgs e) {
container.Goto(e.TabId);
Controls.PopupWidget.SetCurrentContainer(e.TabId);
}
private void OnTabClosed(object sender, Widgets.PageTabBar.TabEventArgs e) {
Models.Watcher.Close(e.TabId);
Commands.AutoFetch.Stop(e.TabId);
container.Remove(e.TabId);
Controls.PopupWidget.UnregisterContainer(e.TabId);
}
#endregion
}
}

View file

@ -0,0 +1,69 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.AddSubmodule"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.URL}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
x:Name="txtURL"
Height="24"
Placeholder="{StaticResource Text.RepositoryURL}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="URL" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:GitURL/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.ParentFolder}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="1" Grid.Column="1"
x:Name="txtPath"
Height="24"
Placeholder="{StaticResource Text.ParentFolder.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="Path" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:SubmodulePath/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<CheckBox
Grid.Row="2" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkNested"
IsChecked="True"
Content="{StaticResource Text.Submodule.FetchNested}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,40 @@
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// 新增子模块面板
/// </summary>
public partial class AddSubmodule : Controls.PopupWidget {
private string repo = null;
public string URL { get; set; }
public string Path { get; set; }
public AddSubmodule(string repo) {
this.repo = repo;
InitializeComponent();
}
public override string GetTitle() {
return App.Text("Submodule.Add");
}
public override Task<bool> Start() {
txtURL.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtURL)) return null;
txtPath.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtPath)) return null;
var recursive = chkNested.IsChecked == true;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
var succ = new Commands.Submodule(repo).Add(URL, Path, recursive, UpdateProgress);
Models.Watcher.SetEnabled(repo, true);
return succ;
});
}
}
}

View file

@ -0,0 +1,94 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Apply"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:converters="clr-namespace:SourceGit.Views.Converters"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:models="clr-namespace:SourceGit.Models"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="800" Height="Auto">
<Grid>
<Grid.Resources>
<converters:InverseBool x:Key="InverseBool"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Apply.File}"
HorizontalAlignment="Right"/>
<Grid
Grid.Row="0" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<controls:TextEdit
Grid.Column="0"
x:Name="txtPath"
Height="24"
Placeholder="{StaticResource Text.Apply.File.Placeholder}">
<controls:TextEdit.Text>
<Binding Path="File" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:PatchFile/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<Button
Grid.Column="1"
Click="OpenFileBrowser"
Height="24" Width="24"
Margin="2,0,0,0">
<Path Width="14" Height="14" Data="{StaticResource Icon.Folder}"/>
</Button>
</Grid>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Apply.WS}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="1" Grid.Column="1"
x:Name="cmbWSOption"
ItemsSource="{Binding Source={x:Static models:WhitespaceOption.Supported}}"
IsEnabled="{Binding ElementName=chkIngoreWS, Path=IsChecked, Converter={StaticResource InverseBool}}"
SelectedIndex="0"
Height="24"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<TextBlock Text="{Binding Name}" Margin="4,0"/>
<TextBlock Text="{Binding Desc}" Margin="4,0" FontSize="11" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox
Grid.Row="2" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkIngoreWS"
IsChecked="True"
Content="{StaticResource Text.Apply.IgnoreWS}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,50 @@
using Microsoft.Win32;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// 应用补丁
/// </summary>
public partial class Apply : Controls.PopupWidget {
private string repo = null;
public string File { get; set; }
public Apply(string repo) {
this.repo = repo;
InitializeComponent();
}
public override string GetTitle() {
return App.Text("Apply.Title");
}
public override Task<bool> Start() {
txtPath.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtPath)) return null;
var ignoreWS = chkIngoreWS.IsChecked == true;
var wsMode = (cmbWSOption.SelectedItem as Models.WhitespaceOption).Arg;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
var succ = new Commands.Apply(repo, File, ignoreWS, wsMode).Exec();
Models.Watcher.SetEnabled(repo, true);
return succ;
});
}
private void OpenFileBrowser(object sender, System.Windows.RoutedEventArgs e) {
var dialog = new OpenFileDialog();
dialog.Filter = "Patch File|*.patch";
dialog.Title = App.Text("Apply.File.Placeholder");
dialog.InitialDirectory = repo;
dialog.CheckFileExists = true;
if (dialog.ShowDialog() == true) {
File = dialog.FileName;
txtPath.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
}
}
}

View file

@ -0,0 +1,41 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.CherryPick"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="800" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.CherryPick.Commit}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Commit}"/>
<TextBlock x:Name="txtCommit" Margin="8,0,0,0"/>
</StackPanel>
<CheckBox
Grid.Row="1" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkCommit"
IsChecked="True"
Content="{StaticResource Text.CherryPick.CommitChanges}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,35 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 遴选面板
/// </summary>
public partial class CherryPick : Controls.PopupWidget {
private string repo = null;
private string commit = null;
public CherryPick(string repo, Models.Commit commit) {
this.repo = repo;
this.commit = commit.SHA;
InitializeComponent();
txtCommit.Text = $"{commit.ShortSHA} {commit.Subject}";
}
public override string GetTitle() {
return App.Text("CherryPick.Title");
}
public override Task<bool> Start() {
var noCommits = chkCommit.IsChecked != true;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.CherryPick(repo, commit, noCommits).Exec();
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

128
src/Views/Popups/Clone.xaml Normal file
View file

@ -0,0 +1,128 @@
<controls:PopupWidget x:Class="SourceGit.Views.Popups.Clone"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Text="{StaticResource Text.Clone.RemoteURL}"
Margin="0,0,4,0"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
x:Name="txtUrl"
Height="24"
Placeholder="{StaticResource Text.Clone.RemoteURL.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="Uri" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:GitURL/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<TextBlock
Grid.Row="1" Grid.Column="0"
Text="{StaticResource Text.Clone.Folder}"
Margin="0,0,4,0"
HorizontalAlignment="Right"/>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<controls:TextEdit
Grid.Column="0"
x:Name="txtFolder"
Height="24"
Placeholder="{StaticResource Text.Clone.Folder.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="Folder" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:CloneDir/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<controls:IconButton
Grid.Column="1"
Click="OnFolderSelectorClick"
Width="24" Height="24"
Margin="2,0,0,0" Padding="4"
BorderBrush="{StaticResource Brush.Border1}"
BorderThickness="1"
Icon="{StaticResource Icon.Folder.Open}"/>
</Grid>
<TextBlock
Grid.Row="2" Grid.Column="0"
Text="{StaticResource Text.Clone.LocalName}"
Margin="0,0,4,0"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="2" Grid.Column="1"
Height="24"
x:Name="txtLocal"
Placeholder="{StaticResource Text.Clone.LocalName.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="LocalName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:LocalRepositoryName/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<TextBlock
Grid.Row="3" Grid.Column="0"
Text="{StaticResource Text.Clone.RemoteName}"
Margin="0,0,4,0"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="3" Grid.Column="1"
x:Name="txtRemote"
Height="24"
Placeholder="{StaticResource Text.Clone.RemoteName.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="RemoteName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:RemoteName x:Name="ruleRemote"/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<TextBlock
Grid.Row="4" Grid.Column="0"
Text="{StaticResource Text.Clone.AdditionalParam}"
Margin="0,0,4,0"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="4" Grid.Column="1"
Height="24"
Placeholder="{StaticResource Text.Clone.AdditionalParam.Placeholder}"
Text="{Binding ElementName=me, Path=ExtraArgs, Mode=TwoWay}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,63 @@
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// 克隆
/// </summary>
public partial class Clone : Controls.PopupWidget {
public string Uri { get; set; }
public string Folder { get; set; }
public string LocalName { get; set; }
public string RemoteName { get; set; }
public string ExtraArgs { get; set; }
public Clone() {
Folder = Models.Preference.Instance.Git.DefaultCloneDir;
InitializeComponent();
ruleRemote.IsOptional = true;
}
public override string GetTitle() {
return App.Text("Clone");
}
public override Task<bool> Start() {
var checks = new Controls.TextEdit[] { txtUrl, txtFolder, txtLocal, txtRemote };
foreach (var edit in checks) {
edit.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(edit)) return null;
}
return Task.Run(() => {
var extras = string.IsNullOrEmpty(ExtraArgs) ? "" : ExtraArgs;
if (!string.IsNullOrEmpty(RemoteName)) extras += $" --origin {RemoteName}";
var succ = new Commands.Clone(Folder, Uri, LocalName, extras, UpdateProgress).Exec();
if (!succ) return false;
var path = Folder;
if (!string.IsNullOrEmpty(LocalName)) {
path += $"/{LocalName}";
} else {
var idx = Uri.LastIndexOfAny(new char[] { '\\', '/' });
var name = Uri.Substring(idx + 1);
path += $"/{name.Replace(".git", "")}";
}
var repo = Models.Preference.Instance.AddRepository(path, path + "/.git", "");
if (repo != null) Dispatcher.Invoke(() => Models.Watcher.Open(repo));
return true;
});
}
private void OnFolderSelectorClick(object sender, System.Windows.RoutedEventArgs e) {
FolderBrowser.Open(null, App.Text("Clone.Folder.Placeholder"), path => {
Folder = path;
txtFolder.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
});
}
}
}

View file

@ -0,0 +1,44 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Configure"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Configure.User}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
Text="{Binding ElementName=me, Path=UserName, Mode=TwoWay}"
Height="24"
Placeholder="{StaticResource Text.Configure.User.Placeholder}"/>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Configure.Email}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="1" Grid.Column="1"
Text="{Binding ElementName=me, Path=UserEmail, Mode=TwoWay}"
Height="24"
Placeholder="{StaticResource Text.Configure.Email.Placeholder}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,39 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 仓库配置
/// </summary>
public partial class Configure : Controls.PopupWidget {
private string repo = null;
public string UserName { get; set; }
public string UserEmail { get; set; }
public Configure(string repo) {
this.repo = repo;
var cmd = new Commands.Config(repo);
UserName = cmd.Get("user.name");
UserEmail = cmd.Get("user.email");
InitializeComponent();
}
public override string GetTitle() {
return App.Text("Configure");
}
public override Task<bool> Start() {
return Task.Run(() => {
var cmd = new Commands.Config(repo);
var oldUser = cmd.Get("user.name");
if (oldUser != UserName) cmd.Set("user.name", UserName);
var oldEmail = cmd.Get("user.email");
if (oldEmail != UserEmail) cmd.Set("user.email", UserEmail);
return true;
});
}
}
}

View file

@ -0,0 +1,77 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.CreateBranch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:converters="clr-namespace:SourceGit.Views.Converters"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.Resources>
<converters:InverseBool x:Key="InverseBool"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.CreateBranch.BasedOn}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path x:Name="iconBased" Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtBased" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.CreateBranch.Name}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="1" Grid.Column="1"
x:Name="txtBranchName"
Height="24"
Placeholder="{StaticResource Text.CreateBranch.Name.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="BranchName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:BranchName x:Name="ruleBranch"/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<TextBlock
Grid.Row="2" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.CreateBranch.LocalChanges}"
HorizontalAlignment="Right"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<RadioButton Content="{StaticResource Text.CreateBranch.LocalChanges.StashAndReply}" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me}"/>
<RadioButton Content="{StaticResource Text.CreateBranch.LocalChanges.Discard}" Margin="8,0,0,0" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me, Mode=OneWay, Converter={StaticResource InverseBool}}"/>
</StackPanel>
<CheckBox
Grid.Row="3" Grid.Column="1"
x:Name="chkCheckout"
IsChecked="True"
Content="{StaticResource Text.CreateBranch.Checkout}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,83 @@
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
namespace SourceGit.Views.Popups {
/// <summary>
/// 新建分支面板
/// </summary>
public partial class CreateBranch : Controls.PopupWidget {
private string repo = null;
private string basedOn = null;
public string BranchName { get; set; }
public bool AutoStash { get; set; }
public CreateBranch(Models.Repository repo, Models.Branch branch) {
this.repo = repo.Path;
this.basedOn = branch.Head;
InitializeComponent();
ruleBranch.Repo = repo;
iconBased.Data = FindResource("Icon.Branch") as Geometry;
txtBased.Text = !string.IsNullOrEmpty(branch.Remote) ? $"{branch.Remote}/{branch.Name}" : branch.Name;
}
public CreateBranch(Models.Repository repo, Models.Commit commit) {
this.repo = repo.Path;
this.basedOn = commit.SHA;
InitializeComponent();
ruleBranch.Repo = repo;
iconBased.Data = FindResource("Icon.Commit") as Geometry;
txtBased.Text = $"{commit.ShortSHA} {commit.Subject}";
}
public CreateBranch(Models.Repository repo, Models.Tag tag) {
this.repo = repo.Path;
this.basedOn = tag.SHA;
InitializeComponent();
ruleBranch.Repo = repo;
iconBased.Data = FindResource("Icon.Tag") as Geometry;
txtBased.Text = tag.Name;
}
public override string GetTitle() {
return App.Text("CreateBranch");
}
public override Task<bool> Start() {
txtBranchName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtBranchName)) return null;
var checkout = chkCheckout.IsChecked == true;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
if (checkout) {
if (AutoStash) {
var changes = new Commands.LocalChanges(repo).Result();
if (changes.Count > 0) {
if (!new Commands.Stash(repo).Push(null, "NEWBRANCH_AUTO_STASH", true)) {
return false;
}
} else {
AutoStash = true;
}
}
new Commands.Checkout(repo).Branch(BranchName, basedOn);
if (AutoStash) new Commands.Stash(repo).Pop("stash@{0}");
} else {
new Commands.Branch(repo, BranchName).Create(basedOn);
}
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,69 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.CreateTag"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="800" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="64"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.CreateTag.BasedOn}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path x:Name="iconBased" Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtBased" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.CreateTag.Name}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="1" Grid.Column="1"
x:Name="txtTagName"
Height="24"
Placeholder="{StaticResource Text.CreateTag.Name.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="TagName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:TagName x:Name="ruleTag"/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<TextBlock
Grid.Row="2" Grid.Column="0"
Margin="0,6,8,0"
Text="{StaticResource Text.CreateTag.Message}"
HorizontalAlignment="Right" VerticalAlignment="Top"/>
<controls:TextEdit
Grid.Row="2" Grid.Column="1"
Text="{Binding ElementName=me, Path=Message, Mode=TwoWay}"
Height="56" Padding="0,2"
AcceptsReturn="True"
Placeholder="{StaticResource Text.CreateTag.Message.Placeholder}"
PlaceholderBaseline="Top"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,55 @@
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
namespace SourceGit.Views.Popups {
/// <summary>
/// 创建分支面板
/// </summary>
public partial class CreateTag : Controls.PopupWidget {
private string repo = null;
private string basedOn = null;
public string TagName { get; set; }
public string Message { get; set; }
public CreateTag(Models.Repository repo, Models.Branch branch) {
this.repo = repo.Path;
this.basedOn = branch.Head;
InitializeComponent();
ruleTag.Tags = new Commands.Tags(repo.Path).Result();
iconBased.Data = FindResource("Icon.Branch") as Geometry;
txtBased.Text = !string.IsNullOrEmpty(branch.Remote) ? $"{branch.Remote}/{branch.Name}" : branch.Name;
}
public CreateTag(Models.Repository repo, Models.Commit commit) {
this.repo = repo.Path;
this.basedOn = commit.SHA;
InitializeComponent();
ruleTag.Tags = new Commands.Tags(repo.Path).Result();
iconBased.Data = FindResource("Icon.Commit") as Geometry;
txtBased.Text = $"{commit.ShortSHA} {commit.Subject}";
}
public override string GetTitle() {
return App.Text("CreateTag");
}
public override Task<bool> Start() {
txtTagName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtTagName)) return null;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Tag(repo).Add(TagName, basedOn, Message);
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,33 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.DeleteBranch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.DeleteBranch.Branch}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtTarget" Margin="8,0,0,0"/>
</StackPanel>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,40 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 删除分支确认
/// </summary>
public partial class DeleteBranch : Controls.PopupWidget {
private string repo = null;
private string branch = null;
private string remote = null;
public DeleteBranch(string repo, string branch, string remote = null) {
this.repo = repo;
this.branch = branch;
this.remote = remote;
InitializeComponent();
if (string.IsNullOrEmpty(remote)) txtTarget.Text = branch;
else txtTarget.Text = $"{remote}/{branch}";
}
public override string GetTitle() {
return App.Text("DeleteBranch");
}
public override Task<bool> Start() {
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
if (string.IsNullOrEmpty(remote)) {
new Commands.Branch(repo, branch).Delete();
} else {
new Commands.Push(repo, remote, branch).Exec();
}
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,33 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.DeleteRemote"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.DeleteRemote.Remote}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Remote}"/>
<TextBlock x:Name="txtTarget" Margin="8,0,0,0"/>
</StackPanel>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,33 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 删除远程确认
/// </summary>
public partial class DeleteRemote : Controls.PopupWidget {
private string repo = null;
private string remote = null;
public DeleteRemote(string repo, string remote) {
this.repo = repo;
this.remote = remote;
InitializeComponent();
txtTarget.Text = remote;
}
public override string GetTitle() {
return App.Text("DeleteRemote");
}
public override Task<bool> Start() {
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Remote(repo).Delete(remote);
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,33 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.DeleteSubmodule"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.DeleteSubmodule.Path}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Submodule}"/>
<TextBlock x:Name="txtPath" Margin="8,0,0,0"/>
</StackPanel>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,33 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 删除子模块面板
/// </summary>
public partial class DeleteSubmodule : Controls.PopupWidget {
private string repo = null;
private string submodule = null;
public DeleteSubmodule(string repo, string submodule) {
this.repo = repo;
this.submodule = submodule;
InitializeComponent();
txtPath.Text = submodule;
}
public override string GetTitle() {
return App.Text("DeleteSubmodule");
}
public override Task<bool> Start() {
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Submodule(repo).Delete(submodule);
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,41 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.DeleteTag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.DeleteTag.Tag}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Tag}"/>
<TextBlock x:Name="txtTag" Margin="8,0,0,0"/>
</StackPanel>
<CheckBox
Grid.Row="1" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkPush"
IsChecked="True"
Content="{StaticResource Text.DeleteTag.WithRemote}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,35 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 删除标签
/// </summary>
public partial class DeleteTag : Controls.PopupWidget {
private string repo = null;
private string tag = null;
public DeleteTag(string repo, string tag) {
this.repo = repo;
this.tag = tag;
InitializeComponent();
txtTag.Text = tag;
}
public override string GetTitle() {
return App.Text("DeleteTag");
}
public override Task<bool> Start() {
var push = chkPush.IsChecked == true;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Tag(repo).Delete(tag, push);
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,39 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Discard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Discard.Changes}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal">
<Path x:Name="icon" Width="12" Height="12" Data="{StaticResource Icon.File}"/>
<TextBlock x:Name="txtTip" Margin="4,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="1"
Margin="0,4,0,0"
Text="{StaticResource Text.Discard.Warning}"
Foreground="{StaticResource Brush.FG2}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Media;
namespace SourceGit.Views.Popups {
/// <summary>
/// 忽略变更
/// </summary>
public partial class Discard : Controls.PopupWidget {
private string repo = null;
private List<Models.Change> changes = null;
public Discard(string repo, List<Models.Change> changes) {
this.repo = repo;
this.changes = changes;
InitializeComponent();
if (changes == null || changes.Count == 0) {
icon.Data = FindResource("Icon.Folder") as Geometry;
txtTip.Text = App.Text("Discard.All");
} else if (changes.Count == 1) {
txtTip.Text = changes[0].Path;
} else {
txtTip.Text = App.Text("Discard.Total", changes.Count);
}
}
public override string GetTitle() {
return App.Text("Discard");
}
public override Task<bool> Start() {
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Discard(repo, changes).Exec();
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,57 @@
<controls:PopupWidget x:Class="SourceGit.Views.Popups.Fetch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:converters="clr-namespace:SourceGit.Views.Converters"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.Resources>
<converters:InverseBool x:Key="InverseBool"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Fetch.Remote}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="0" Grid.Column="1"
x:Name="remotes"
VerticalAlignment="Center"
IsEnabled="{Binding ElementName=chkFetchAll, Path=IsChecked, Converter={StaticResource InverseBool}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Width="14" Height="14" Data="{StaticResource Icon.Remote}"/>
<TextBlock Text="{Binding Name}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Row="1" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkFetchAll"
IsChecked="True"
Content="{StaticResource Text.Fetch.AllRemotes}"/>
<CheckBox Grid.Row="2" Grid.Column="1"
x:Name="chkPrune"
IsChecked="True"
Content="{StaticResource Text.Fetch.Prune}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,41 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 拉取更新
/// </summary>
public partial class Fetch : Controls.PopupWidget {
private string repo = null;
public Fetch(Models.Repository repo, string preferRemote) {
this.repo = repo.Path;
InitializeComponent();
remotes.ItemsSource = repo.Remotes;
if (preferRemote != null) {
remotes.SelectedIndex = repo.Remotes.FindIndex(x => x.Name == preferRemote);
chkFetchAll.IsChecked = false;
} else {
remotes.SelectedIndex = 0;
chkFetchAll.IsChecked = true;
}
}
public override string GetTitle() {
return App.Text("Fetch.Title");
}
public override Task<bool> Start() {
var prune = chkPrune.IsChecked == true;
var remote = (remotes.SelectedItem as Models.Remote).Name;
if (chkFetchAll.IsChecked == true) remote = "--all";
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
var succ = new Commands.Fetch(repo, remote, prune, UpdateProgress).Exec();
Models.Watcher.SetEnabled(repo, true);
return succ;
});
}
}
}

View file

@ -0,0 +1,35 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.GitFlowFinish"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="800" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
x:Name="txtPrefix"
HorizontalAlignment="Right"/>
<Path
Grid.Row="0" Grid.Column="1"
Width="14" Height="14"
Data="{StaticResource Icon.Branch}"/>
<TextBlock
Grid.Row="0" Grid.Column="2"
Margin="4,0,0,0"
x:Name="txtName"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,57 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 完成GitFlow分支开发
/// </summary>
public partial class GitFlowFinish : Controls.PopupWidget {
private string repo = null;
private string name = null;
private Models.GitFlowBranchType type = Models.GitFlowBranchType.None;
public GitFlowFinish(Models.Repository repo, string branch, Models.GitFlowBranchType type) {
this.repo = repo.Path;
this.type = type;
InitializeComponent();
txtName.Text = branch;
switch (type) {
case Models.GitFlowBranchType.Feature:
txtPrefix.Text = App.Text("GitFlow.Feature");
name = branch.Substring(repo.GitFlow.Feature.Length);
break;
case Models.GitFlowBranchType.Release:
txtPrefix.Text = App.Text("GitFlow.Release");
name = branch.Substring(repo.GitFlow.Release.Length);
break;
case Models.GitFlowBranchType.Hotfix:
txtPrefix.Text = App.Text("GitFlow.Hotfix");
name = branch.Substring(repo.GitFlow.Hotfix.Length);
break;
}
}
public override string GetTitle() {
switch (type) {
case Models.GitFlowBranchType.Feature:
return App.Text("GitFlow.FinishFeature");
case Models.GitFlowBranchType.Release:
return App.Text("GitFlow.FinishRelease");
case Models.GitFlowBranchType.Hotfix:
return App.Text("GitFlow.FinishHotfix");
default:
return "";
}
}
public override Task<bool> Start() {
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.GitFlow(repo).Finish(type, name);
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,41 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.GitFlowStart"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
x:Name="txtPrefix"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
x:Name="txtBranchName"
Height="24"
Placeholder="{StaticResource Text.GitFlow.StartPlaceholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="BranchName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:BranchName x:Name="ruleBranch"/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,62 @@
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// Git-Flow start命令操作面板
/// </summary>
public partial class GitFlowStart : Controls.PopupWidget {
private string repo = null;
private Models.GitFlowBranchType type = Models.GitFlowBranchType.None;
public string BranchName { get; set; }
public GitFlowStart(Models.Repository repo, Models.GitFlowBranchType type) {
this.repo = repo.Path;
this.type = type;
InitializeComponent();
ruleBranch.Repo = repo;
switch (type) {
case Models.GitFlowBranchType.Feature:
ruleBranch.Prefix = repo.GitFlow.Feature;
txtPrefix.Text = repo.GitFlow.Feature;
break;
case Models.GitFlowBranchType.Release:
ruleBranch.Prefix = repo.GitFlow.Release;
txtPrefix.Text = repo.GitFlow.Release;
break;
case Models.GitFlowBranchType.Hotfix:
ruleBranch.Prefix = repo.GitFlow.Hotfix;
txtPrefix.Text = repo.GitFlow.Hotfix;
break;
}
}
public override string GetTitle() {
switch (type) {
case Models.GitFlowBranchType.Feature:
return App.Text("GitFlow.StartFeatureTitle");
case Models.GitFlowBranchType.Release:
return App.Text("GitFlow.StartReleaseTitle");
case Models.GitFlowBranchType.Hotfix:
return App.Text("GitFlow.StartHotfixTitle");
default:
return "";
}
}
public override Task<bool> Start() {
txtBranchName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtBranchName)) return null;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.GitFlow(repo).Start(type, BranchName);
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,43 @@
<controls:PopupWidget x:Class="SourceGit.Views.Popups.Init"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Text="{StaticResource Text.Init.Path}"
HorizontalAlignment="Right"/>
<Path
Grid.Row="0" Grid.Column="1"
Width="12" Height="12"
Margin="8,0"
Data="{StaticResource Icon.Folder.Fill}"
Fill="Goldenrod"/>
<TextBlock
Grid.Row="0" Grid.Column="2"
Text="{Binding ElementName=me, Path=WorkDir}"/>
<TextBlock
Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
Text="{StaticResource Text.Init.Tip}"
Foreground="{StaticResource Brush.FG2}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,31 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 初始化Git仓库确认框
/// </summary>
public partial class Init : Controls.PopupWidget {
public string WorkDir { get; set; }
public Init(string dir) {
WorkDir = dir;
InitializeComponent();
}
public override string GetTitle() {
return App.Text("Init");
}
public override Task<bool> Start() {
return Task.Run(() => {
var succ = new Commands.Init(WorkDir).Exec();
if (!succ) return false;
var repo = Models.Preference.Instance.AddRepository(WorkDir, WorkDir + "\\.git", "");
Models.Watcher.Open(repo);
return true;
});
}
}
}

View file

@ -0,0 +1,102 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.InitGitFlow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="8"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.GitFlow.ProductionBranch}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
x:Name="txtMaster"
Height="24"
Placeholder="master"
Text="master"/>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.GitFlow.DevelopBranch}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="1" Grid.Column="1"
x:Name="txtDevelop"
Height="24"
Placeholder="develop"
Text="develop"/>
<Rectangle
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Height="1"
Fill="{StaticResource Brush.Border2}"/>
<TextBlock
Grid.Row="3" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.GitFlow.FeaturePrefix}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="3" Grid.Column="1"
x:Name="txtFeature"
Height="24"
Placeholder="feature/"
Text="feature/"/>
<TextBlock
Grid.Row="4" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.GitFlow.ReleasePrefix}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="4" Grid.Column="1"
x:Name="txtRelease"
Height="24"
Placeholder="release/"
Text="release/"/>
<TextBlock
Grid.Row="5" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.GitFlow.HotfixPrefix}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="5" Grid.Column="1"
x:Name="txtHotfix"
Height="24"
Placeholder="hotfix/"
Text="hotfix/"/>
<TextBlock
Grid.Row="6" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.GitFlow.TagPrefix}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="6" Grid.Column="1"
x:Name="txtTag"
Height="24"
Placeholder="{StaticResource Text.Optional}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,50 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 初始化Git-Flow
/// </summary>
public partial class InitGitFlow : Controls.PopupWidget {
private Models.Repository repo = null;
public InitGitFlow(Models.Repository repo) {
this.repo = repo;
InitializeComponent();
}
public override string GetTitle() {
return App.Text("GitFlow.Init");
}
public override Task<bool> Start() {
var master = txtMaster.Text;
var dev = txtDevelop.Text;
var feature = txtFeature.Text;
var release = txtRelease.Text;
var hotfix = txtHotfix.Text;
var version = txtTag.Text;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo.Path, false);
var succ = new Commands.GitFlow(repo.Path).Init(master, dev, feature, release, hotfix, version);
var cmd = new Commands.Config(repo.Path);
if (succ) {
repo.GitFlow.Feature = cmd.Get("gitflow.prefix.feature");
repo.GitFlow.Release = cmd.Get("gitflow.prefix.release");
repo.GitFlow.Hotfix = cmd.Get("gitflow.prefix.hotfix");
} else {
cmd.Set("gitflow.branch.master", null);
cmd.Set("gitflow.branch.develop", null);
cmd.Set("gitflow.prefix.feature", null);
cmd.Set("gitflow.prefix.bugfix", null);
cmd.Set("gitflow.prefix.release", null);
cmd.Set("gitflow.prefix.hotfix", null);
cmd.Set("gitflow.prefix.support", null);
cmd.Set("gitflow.prefix.versiontag", null);
}
Models.Watcher.SetEnabled(repo.Path, true);
return true;
});
}
}
}

View file

@ -0,0 +1,71 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Merge"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:models="clr-namespace:SourceGit.Models"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Merge.Source}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtSource" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Merge.Into}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="1" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtInto" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="2" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Merge.Mode}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="2" Grid.Column="1"
x:Name="cmbMode"
ItemsSource="{Binding Source={x:Static models:MergeOption.Supported}}"
SelectedIndex="0"
Height="24"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<TextBlock Text="{Binding Name}" Margin="4,0"/>
<TextBlock Text="{Binding Desc}" Margin="4,0" FontSize="11" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,35 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 合并操作界面
/// </summary>
public partial class Merge : Controls.PopupWidget {
private string repo = null;
private string source = null;
public Merge(string repo, string source, string dest) {
this.repo = repo;
this.source = source;
InitializeComponent();
txtSource.Text = source;
txtInto.Text = dest;
}
public override string GetTitle() {
return App.Text("Merge");
}
public override Task<bool> Start() {
var mode = (cmbMode.SelectedItem as Models.MergeOption).Arg;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Merge(repo, source, mode).Exec();
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,93 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Pull"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:converters="clr-namespace:SourceGit.Views.Converters"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<UserControl.Resources>
<converters:BranchToName x:Key="BranchToName"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Pull.Remote}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="0" Grid.Column="1"
x:Name="cmbRemotes"
Height="24"
VerticalAlignment="Center"
SelectionChanged="OnRemoteSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Width="14" Height="14" Data="{StaticResource Icon.Remote}"/>
<TextBlock Text="{Binding Name}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Pull.Branch}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="1" Grid.Column="1"
x:Name="cmbBranches"
Height="24"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock Text="{Binding ., Converter={StaticResource BranchToName}}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Row="2" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Pull.Into}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="2" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtInto" Margin="8,0,0,0"/>
</StackPanel>
<CheckBox Grid.Row="3" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkUseRebase"
IsChecked="True"
Content="{StaticResource Text.Pull.UseRebase}"/>
<CheckBox Grid.Row="4" Grid.Column="1"
x:Name="chkAutoStash"
IsChecked="True"
Content="{StaticResource Text.Pull.AutoStash}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,68 @@
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// 拉回
/// </summary>
public partial class Pull : Controls.PopupWidget {
private Models.Repository repo = null;
private Models.Branch prefered = null;
public Pull(Models.Repository repo, Models.Branch preferRemoteBranch) {
this.repo = repo;
this.prefered = preferRemoteBranch;
InitializeComponent();
var current = repo.Branches.Find(x => x.IsCurrent);
txtInto.Text = current.Name;
if (prefered == null && !string.IsNullOrEmpty(current.Upstream)) {
prefered = repo.Branches.Find(x => x.FullName == current.Upstream);
}
cmbRemotes.ItemsSource = repo.Remotes;
if (prefered != null) {
cmbRemotes.SelectedItem = repo.Remotes.Find(x => x.Name == prefered.Remote);
} else {
cmbRemotes.SelectedItem = repo.Remotes[0];
}
}
public override string GetTitle() {
return App.Text("Pull.Title");
}
public override Task<bool> Start() {
var branch = cmbBranches.SelectedItem as Models.Branch;
if (branch == null) return null;
var rebase = chkUseRebase.IsChecked == true;
var autoStash = chkAutoStash.IsChecked == true;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo.Path, false);
var succ = new Commands.Pull(repo.Path, branch.Remote, branch.Name, rebase, autoStash, UpdateProgress).Exec();
Models.Watcher.SetEnabled(repo.Path, true);
return succ;
});
}
private void OnRemoteSelectionChanged(object sender, SelectionChangedEventArgs e) {
var remote = cmbRemotes.SelectedItem as Models.Remote;
if (remote == null) return;
var branches = repo.Branches.Where(x => x.Remote == remote.Name).ToList();
cmbBranches.ItemsSource = branches;
if (prefered != null && remote.Name == prefered.Remote) {
cmbBranches.SelectedItem = branches.Find(x => x.FullName == prefered.FullName);
} else {
cmbBranches.SelectedItem = branches[0];
}
}
}
}

100
src/Views/Popups/Push.xaml Normal file
View file

@ -0,0 +1,100 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Push"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:converters="clr-namespace:SourceGit.Views.Converters"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<UserControl.Resources>
<converters:BranchToName x:Key="BranchToName"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Push.Local}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="0" Grid.Column="1"
x:Name="cmbLocalBranches"
Height="24"
VerticalAlignment="Center"
SelectionChanged="OnLocalSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock Text="{Binding Name}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Push.Remote}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="1" Grid.Column="1"
x:Name="cmbRemotes"
Height="24"
VerticalAlignment="Center"
SelectionChanged="OnRemoteSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Width="14" Height="14" Data="{StaticResource Icon.Remote}"/>
<TextBlock Text="{Binding Name}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Row="2" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Push.To}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="2" Grid.Column="1"
x:Name="cmbRemoteBranches"
Height="24"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock Text="{Binding ., Converter={StaticResource BranchToName}}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Row="3" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkAllTags"
IsChecked="True"
Content="{StaticResource Text.Push.WithAllTags}"/>
<CheckBox Grid.Row="4" Grid.Column="1"
x:Name="chkForce"
IsChecked="True"
Content="{StaticResource Text.Push.Force}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,108 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// 推送
/// </summary>
public partial class Push : Controls.PopupWidget {
private Models.Repository repo = null;
public Push(Models.Repository repo, Models.Branch localBranch) {
this.repo = repo;
InitializeComponent();
var localBranches = repo.Branches.Where(x => x.IsLocal).ToList();
cmbLocalBranches.ItemsSource = localBranches;
if (localBranch != null) cmbLocalBranches.SelectedItem = localBranch;
else cmbLocalBranches.SelectedItem = localBranches.Find(x => x.IsCurrent);
}
public override string GetTitle() {
return App.Text("Push.Title");
}
public override Task<bool> Start() {
var localBranch = cmbLocalBranches.SelectedItem as Models.Branch;
if (localBranch == null) return null;
var remoteBranch = cmbRemoteBranches.SelectedItem as Models.Branch;
if (remoteBranch == null) return null;
var withTags = chkAllTags.IsChecked == true;
var force = chkForce.IsChecked == true;
var track = string.IsNullOrEmpty(localBranch.Upstream);
return Task.Run(() => {
Models.Watcher.SetEnabled(repo.Path, false);
var succ = new Commands.Push(
repo.Path,
localBranch.Name,
remoteBranch.Remote,
remoteBranch.Name.Replace(" (new)", ""),
withTags,
force,
track,
UpdateProgress).Exec();
Models.Watcher.SetEnabled(repo.Path, true);
return succ;
});
}
private void OnLocalSelectionChanged(object sender, SelectionChangedEventArgs e) {
var local = cmbLocalBranches.SelectedItem as Models.Branch;
if (local == null) return;
cmbRemotes.ItemsSource = null;
cmbRemotes.ItemsSource = repo.Remotes;
if (!string.IsNullOrEmpty(local.Upstream)) {
cmbRemotes.SelectedItem = repo.Remotes.Find(x => local.Upstream.StartsWith($"refs/remotes/{x.Name}/"));
} else {
cmbRemotes.SelectedItem = repo.Remotes[0];
}
}
private void OnRemoteSelectionChanged(object sender, SelectionChangedEventArgs e) {
var local = cmbLocalBranches.SelectedItem as Models.Branch;
if (local == null) return;
var remote = cmbRemotes.SelectedItem as Models.Remote;
if (remote == null) return;
var remoteBranches = new List<Models.Branch>();
remoteBranches.AddRange(repo.Branches.Where(x => x.Remote == remote.Name));
cmbRemoteBranches.ItemsSource = null;
if (!string.IsNullOrEmpty(local.Upstream)) {
foreach (var b in remoteBranches) {
if (b.FullName == local.Upstream) {
cmbRemoteBranches.ItemsSource = remoteBranches;
cmbRemoteBranches.SelectedItem = b;
return;
}
}
}
var match = $"refs/remotes/{remote.Name}/{local.Name}";
foreach (var b in remoteBranches) {
if (b.FullName == match) {
cmbRemoteBranches.ItemsSource = remoteBranches;
cmbRemoteBranches.SelectedItem = b;
return;
}
}
var prefer = new Models.Branch() {
Remote = remote.Name,
Name = $"{local.Name} (new)"
};
remoteBranches.Add(prefer);
cmbRemoteBranches.ItemsSource = remoteBranches;
cmbRemoteBranches.SelectedItem = prefer;
}
}
}

View file

@ -0,0 +1,53 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.PushTag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.PushTag.Tag}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Tag}"/>
<TextBlock x:Name="txtTag" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.PushTag.Remote}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="1" Grid.Column="1"
x:Name="cmbRemotes"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Width="14" Height="14" Data="{StaticResource Icon.Remote}"/>
<TextBlock Text="{Binding Name}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,38 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 推送标签确认面板
/// </summary>
public partial class PushTag : Controls.PopupWidget {
private string repo = null;
private string tag = null;
public PushTag(Models.Repository repo, string tag) {
this.repo = repo.Path;
this.tag = tag;
InitializeComponent();
txtTag.Text = tag;
cmbRemotes.ItemsSource = repo.Remotes;
cmbRemotes.SelectedIndex = 0;
}
public override string GetTitle() {
return App.Text("PushTag");
}
public override Task<bool> Start() {
var remote = cmbRemotes.SelectedItem as Models.Remote;
if (remote == null) return null;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Push(repo, remote.Name, tag, false).Exec();
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,54 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Rebase"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Rebase.Target}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtCurrent" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Rebase.On}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="1" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path x:Name="iconBased" Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtOn" Margin="8,0,0,0"/>
</StackPanel>
<CheckBox
Grid.Row="2" Grid.Column="1"
x:Name="chkAutoStash"
IsChecked="True"
Content="{StaticResource Text.Rebase.AutoStash}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,48 @@
using System.Threading.Tasks;
using System.Windows.Media;
namespace SourceGit.Views.Popups {
/// <summary>
/// 变基操作面板
/// </summary>
public partial class Rebase : Controls.PopupWidget {
private string repo = null;
private string on = null;
public Rebase(string repo, string current, Models.Branch branch) {
this.repo = repo;
this.on = branch.Head;
InitializeComponent();
txtCurrent.Text = current;
txtOn.Text = !string.IsNullOrEmpty(branch.Remote) ? $"{branch.Remote}/{branch.Name}" : branch.Name;
iconBased.Data = FindResource("Icon.Branch") as Geometry;
}
public Rebase(string repo, string current, Models.Commit commit) {
this.repo = repo;
this.on = commit.SHA;
InitializeComponent();
txtCurrent.Text = current;
txtOn.Text = $"{commit.ShortSHA} {commit.Subject}";
iconBased.Data = FindResource("Icon.Branch") as Geometry;
}
public override string GetTitle() {
return App.Text("Rebase");
}
public override Task<bool> Start() {
var autoStash = chkAutoStash.IsChecked == true;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Rebase(repo, on, autoStash).Exec();
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,61 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Remote"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Remote.Name}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
x:Name="txtName"
Height="24"
Placeholder="{StaticResource Text.Remote.Name.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="RemoteName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:RemoteName x:Name="ruleName"/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Remote.URL}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="1" Grid.Column="1"
x:Name="txtUrl"
Height="24"
Placeholder="{StaticResource Text.Remote.URL.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="RemoteURL" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:GitURL x:Name="ruleURL"/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,59 @@
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// 远程信息编辑面板
/// </summary>
public partial class Remote : Controls.PopupWidget {
private Models.Repository repo = null;
private Models.Remote remote = null;
public string RemoteName { get; set; }
public string RemoteURL { get; set; }
public Remote(Models.Repository repo, Models.Remote remote) {
this.repo = repo;
this.remote = remote;
if (remote != null) {
RemoteName = remote.Name;
RemoteURL = remote.URL;
}
InitializeComponent();
ruleName.Repo = repo;
}
public override string GetTitle() {
return App.Text(remote == null ? "Remote.AddTitle" : "Remote.EditTitle");
}
public override Task<bool> Start() {
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtName)) return null;
txtUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtUrl)) return null;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo.Path, false);
if (remote == null) {
var succ = new Commands.Remote(repo.Path).Add(RemoteName, RemoteURL);
if (succ) new Commands.Fetch(repo.Path, RemoteName, true, UpdateProgress).Exec();
} else {
if (remote.URL != RemoteURL) {
new Commands.Remote(repo.Path).SetURL(remote.Name, RemoteURL);
}
if (remote.Name != RemoteName) {
new Commands.Remote(repo.Path).Rename(remote.Name, RemoteName);
}
}
Models.Watcher.SetEnabled(repo.Path, true);
return true;
});
}
}
}

View file

@ -0,0 +1,55 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.RenameBranch"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.RenameBranch.Target}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtTarget" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.RenameBranch.Name}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="1" Grid.Column="1"
x:Name="txtNewName"
Height="24"
Placeholder="{StaticResource Text.RenameBranch.Name.Placeholder}">
<controls:TextEdit.Text>
<Binding ElementName="me" Path="NewName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<validations:BranchName x:Name="ruleBranch"/>
</Binding.ValidationRules>
</Binding>
</controls:TextEdit.Text>
</controls:TextEdit>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,40 @@
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.Views.Popups {
/// <summary>
/// 本地分支改名
/// </summary>
public partial class RenameBranch : Controls.PopupWidget {
private string repo = null;
private string target = null;
public string NewName { get; set; }
public RenameBranch(Models.Repository repo, string target) {
this.repo = repo.Path;
this.target = target;
InitializeComponent();
ruleBranch.Repo = repo;
txtTarget.Text = target;
}
public override string GetTitle() {
return App.Text("RenameBranch");
}
public override Task<bool> Start() {
txtNewName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtNewName)) return null;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Branch(repo, target).Rename(NewName);
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,72 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Reset"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:models="clr-namespace:SourceGit.Models"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Reset.Target}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Branch}"/>
<TextBlock x:Name="txtCurrent" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="1" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Reset.MoveTo}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="1" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Commit}"/>
<TextBlock x:Name="txtMoveTo" Margin="8,0,0,0"/>
</StackPanel>
<TextBlock
Grid.Row="2" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Reset.Mode}"
HorizontalAlignment="Right"/>
<ComboBox
Grid.Row="2" Grid.Column="1"
x:Name="cmbMode"
ItemsSource="{Binding Source={x:Static models:ResetMode.Supported}}"
SelectedIndex="0"
Height="24"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Width="12" Height="12" Margin="4,0,0,0" Fill="{Binding Color}" Data="M 0,0 A 180,180 180 1 1 1,1 Z"/>
<TextBlock Text="{Binding Name}" Margin="4,0"/>
<TextBlock Text="{Binding Desc}" Margin="4,0" FontSize="11" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,35 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 重置面板
/// </summary>
public partial class Reset : Controls.PopupWidget {
private string repo = null;
private string revision = null;
public Reset(string repo, string current, Models.Commit to) {
this.repo = repo;
this.revision = to.SHA;
InitializeComponent();
txtCurrent.Text = current;
txtMoveTo.Text = $"{to.ShortSHA} {to.Subject}";
}
public override string GetTitle() {
return App.Text("Reset");
}
public override Task<bool> Start() {
var mode = cmbMode.SelectedItem as Models.ResetMode;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Reset(repo, revision, mode.Arg).Exec();
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,41 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Revert"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Revert.Commit}"
HorizontalAlignment="Right"/>
<StackPanel
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="14" Height="14" Data="{StaticResource Icon.Commit}"/>
<TextBlock x:Name="txtCommit" Margin="8,0,0,0"/>
</StackPanel>
<CheckBox
Grid.Row="1" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkCommit"
IsChecked="True"
Content="{StaticResource Text.Revert.CommitChanges}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,35 @@
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 撤销面板
/// </summary>
public partial class Revert : Controls.PopupWidget {
private string repo = null;
private string commit = null;
public Revert(string repo, Models.Commit commit) {
this.repo = repo;
this.commit = commit.SHA;
InitializeComponent();
txtCommit.Text = $"{commit.ShortSHA} {commit.Subject}";
}
public override string GetTitle() {
return App.Text("Revert");
}
public override Task<bool> Start() {
var commitChanges = chkCommit.IsChecked == true;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
new Commands.Revert(repo, commit, commitChanges).Exec();
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

View file

@ -0,0 +1,38 @@
<controls:PopupWidget
x:Class="SourceGit.Views.Popups.Stash"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
d:DesignWidth="500" Height="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
Text="{StaticResource Text.Stash.Message}"
HorizontalAlignment="Right"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
x:Name="txtMessage"
Height="24"
Placeholder="{StaticResource Text.Stash.Message.Placeholder}"/>
<CheckBox
Grid.Row="1" Grid.Column="1"
Margin="0,4,0,0"
x:Name="chkIncludeUntracked"
IsChecked="True"
Content="{StaticResource Text.Stash.IncludeUntracked}"/>
</Grid>
</controls:PopupWidget>

View file

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SourceGit.Views.Popups {
/// <summary>
/// 贮藏
/// </summary>
public partial class Stash : Controls.PopupWidget {
private string repo = null;
private List<string> files = null;
public Stash(string repo, List<string> files) {
this.repo = repo;
this.files = files;
InitializeComponent();
chkIncludeUntracked.IsEnabled = files == null || files.Count == 0;
}
public override string GetTitle() {
return App.Text("Stash.Title");
}
public override Task<bool> Start() {
var includeUntracked = chkIncludeUntracked.IsChecked == true;
var message = txtMessage.Text;
return Task.Run(() => {
Models.Watcher.SetEnabled(repo, false);
if (files == null || files.Count == 0) {
new Commands.Stash(repo).Push(null, message, includeUntracked);
} else {
for (int i = 0; i < files.Count; i += 10) {
var count = Math.Min(10, files.Count - i);
var step = files.GetRange(i, count);
new Commands.Stash(repo).Push(step, message, includeUntracked);
}
}
Models.Watcher.SetEnabled(repo, true);
return true;
});
}
}
}

307
src/Views/Preference.xaml Normal file
View file

@ -0,0 +1,307 @@
<Window x:Class="SourceGit.Views.Preference"
x:Name="me"
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"
xmlns:models="clr-namespace:SourceGit.Models"
mc:Ignorable="d"
Title="{StaticResource Text.Preference}"
Width="500" SizeToContent="Height"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize">
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
</WindowChrome.WindowChrome>
<controls:WindowBorder>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.TitleBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Icon -->
<Path Grid.Column="0" Width="14" Height="14" Margin="6,0" Data="{StaticResource Icon.Preference}"/>
<!-- Title -->
<TextBlock Grid.Column="1" Text="{StaticResource Text.Preference}"/>
<!-- Close -->
<controls:IconButton
Grid.Column="3"
Click="Quit"
Padding="9"
Icon="{StaticResource Icon.Close}"
HoverBackground="Red"
WindowChrome.IsHitTestVisibleInChrome="True"/>
</Grid>
<!-- Body -->
<Grid Grid.Row="1" Margin="16,8">
<Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="8"/>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="8"/>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="6"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="128"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- General Group -->
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal">
<TextBlock
Text="{StaticResource Text.Preference.General}"
FontSize="16" FontWeight="DemiBold"
Foreground="{StaticResource Brush.FG2}"/>
<TextBlock
Text="{StaticResource Text.Preference.RestartRequired}"
Foreground="{StaticResource Brush.FG2}"
Margin="16,0"/>
</StackPanel>
<!-- Language -->
<TextBlock
Grid.Row="1" Grid.Column="0"
Text="{StaticResource Text.Preference.Locale}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<ComboBox
Grid.Row="1" Grid.Column="1"
Height="24"
ItemsSource="{Binding Source={x:Static models:Locale.Supported}}"
DisplayMemberPath="Name"
SelectedValuePath="Resource"
SelectedValue="{Binding Source={x:Static models:Preference.Instance}, Path=General.Locale, Mode=TwoWay}"/>
<!-- Avatar -->
<TextBlock
Grid.Row="2" Grid.Column="0"
Text="{StaticResource Text.Preference.AvatarServer}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<ComboBox
Grid.Row="2" Grid.Column="1"
Height="24"
ItemsSource="{Binding Source={x:Static models:AvatarServer.Supported}}"
DisplayMemberPath="Name"
SelectedValuePath="Url"
SelectedValue="{Binding Source={x:Static models:Preference.Instance}, Path=General.AvatarServer, Mode=TwoWay}"/>
<!-- Use Dark Theme -->
<CheckBox
Grid.Row="3" Grid.Column="1"
Content="{StaticResource Text.Preference.UseDark}"
IsChecked="{Binding Source={x:Static models:Preference.Instance}, Path=General.UseDarkTheme, Mode=TwoWay}"/>
<!-- Enable Check For Update -->
<CheckBox
Grid.Row="4" Grid.Column="1"
Content="{StaticResource Text.Preference.CheckUpdate}"
IsChecked="{Binding Source={x:Static models:Preference.Instance}, Path=General.CheckForUpdate, Mode=TwoWay}"/>
<!-- Auto Fetch -->
<CheckBox
Grid.Row="5" Grid.Column="1"
Content="{StaticResource Text.Preference.AutoFetch}"
IsChecked="{Binding Source={x:Static models:Preference.Instance}, Path=General.AutoFetchRemotes, Mode=TwoWay}"/>
<!-- Git Group -->
<TextBlock
Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2"
Text="{StaticResource Text.Preference.Git}"
FontSize="16" FontWeight="DemiBold"
Foreground="{StaticResource Brush.FG2}"/>
<!-- Git Executable Path -->
<TextBlock
Grid.Row="8" Grid.Column="0"
Text="{StaticResource Text.Preference.Git.Path}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<Grid Grid.Row="8" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<controls:TextEdit
Grid.Column="0"
x:Name="editGitPath"
Height="24"
Text="{Binding Source={x:Static models:Preference.Instance}, Path=Git.Path, Mode=TwoWay}"
Placeholder="{StaticResource Text.Preference.Git.Path.Placeholder}"/>
<controls:IconButton
Grid.Column="1"
Click="SelectGitPath"
Width="24" Height="24"
Margin="4,0,0,0" Padding="4"
BorderThickness="1" BorderBrush="{StaticResource Brush.Border1}"
Icon="{StaticResource Icon.Folder.Open}"/>
</Grid>
<!-- Default Clone Dir -->
<TextBlock
Grid.Row="9" Grid.Column="0"
Text="{StaticResource Text.Preference.Git.Dir}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<Grid Grid.Row="9" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<controls:TextEdit
Grid.Column="0"
x:Name="txtGitCloneDir"
Height="24"
Placeholder="{StaticResource Text.Preference.Git.Dir.Placeholder}"
Text="{Binding Source={x:Static models:Preference.Instance}, Path=Git.DefaultCloneDir, Mode=TwoWay}"/>
<controls:IconButton
Grid.Column="1"
Click="SelectGitCloneDir"
Width="24" Height="24"
Margin="4,0,0,0" Padding="4"
BorderThickness="1" BorderBrush="{StaticResource Brush.Border1}"
Icon="{StaticResource Icon.Folder.Open}"/>
</Grid>
<!-- User -->
<TextBlock
Grid.Row="10" Grid.Column="0"
Text="{StaticResource Text.Preference.Git.User}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<controls:TextEdit
Grid.Row="10" Grid.Column="1"
Height="24"
Text="{Binding ElementName=me, Path=User, Mode=TwoWay}"
Placeholder="{StaticResource Text.Preference.Git.User.Placeholder}"/>
<!-- Email -->
<TextBlock
Grid.Row="11" Grid.Column="0"
Text="{StaticResource Text.Preference.Git.Email}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<controls:TextEdit
Grid.Row="11" Grid.Column="1"
Height="24"
Text="{Binding ElementName=me, Path=Email, Mode=TwoWay}"
Placeholder="{StaticResource Text.Preference.Git.Email.Placeholder}"/>
<!-- CRLF -->
<TextBlock
Grid.Row="12" Grid.Column="0"
Text="{StaticResource Text.Preference.Git.CRLF}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<ComboBox
Grid.Row="12" Grid.Column="1"
Height="24"
ItemsSource="{Binding Source={x:Static models:CRLFOption.Supported}}"
SelectedValuePath="Value"
SelectedValue="{Binding ElementName=me, Path=CRLF, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<TextBlock Text="{Binding Display}" Margin="2,0"/>
<TextBlock Text="{Binding Desc}" Margin="8,0,0,0" FontSize="10" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Merge Tool Group -->
<TextBlock
Grid.Row="14" Grid.Column="0" Grid.ColumnSpan="2"
Text="{StaticResource Text.Preference.Merger}"
FontSize="16" FontWeight="DemiBold"
Foreground="{StaticResource Brush.FG2}"/>
<!-- Merge Tool Type -->
<TextBlock
Grid.Row="15" Grid.Column="0"
Text="{StaticResource Text.Preference.Merger.Type}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<ComboBox
Grid.Row="15" Grid.Column="1"
Height="24"
ItemsSource="{Binding Source={x:Static models:MergeTool.Supported}}"
DisplayMemberPath="Name"
SelectedValuePath="Type"
SelectedValue="{Binding Source={x:Static models:Preference.Instance}, Path=MergeTool.Type, Mode=TwoWay}"
SelectionChanged="MergeToolChanged"/>
<!-- Merge Tool Executable Path -->
<TextBlock
Grid.Row="16" Grid.Column="0"
Text="{StaticResource Text.Preference.Merger.Path}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<Grid Grid.Row="16" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<controls:TextEdit
Grid.Column="0"
Height="24"
x:Name="txtMergeExec"
Placeholder="{StaticResource Text.Preference.Merger.Path.Placeholder}"
Text="{Binding Source={x:Static models:Preference.Instance}, Path=MergeTool.Path, Mode=TwoWay}"/>
<controls:IconButton
Grid.Column="1"
Click="SelectMergeTool"
Width="24" Height="24"
Margin="4,0,0,0" Padding="4"
BorderThickness="1" BorderBrush="{StaticResource Brush.Border1}"
Icon="{StaticResource Icon.Folder.Open}"/>
</Grid>
<!-- Merge Tool Command -->
<TextBlock
Grid.Row="17" Grid.Column="0"
Text="{StaticResource Text.Preference.Merger.Cmd}"
HorizontalAlignment="Right"
Margin="0,0,6,0"/>
<TextBlock
Grid.Row="17" Grid.Column="1"
x:Name="txtMergeCmd"
Text="{Binding ElementName=me, Path=MergeCmd}"
Foreground="{StaticResource Brush.FG2}"/>
</Grid>
</Grid>
</controls:WindowBorder>
</Window>

View file

@ -0,0 +1,101 @@
using Microsoft.Win32;
using System;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.Views {
/// <summary>
/// 设置面板
/// </summary>
public partial class Preference : Window {
public string User { get; set; }
public string Email { get; set; }
public string CRLF { get; set; }
public string MergeCmd { get; set; }
public Preference() {
User = new Commands.Config().Get("user.name");
Email = new Commands.Config().Get("user.email");
CRLF = new Commands.Config().Get("core.autocrlf");
if (string.IsNullOrEmpty(CRLF)) CRLF = "false";
var merger = Models.MergeTool.Supported.Find(x => x.Type == Models.Preference.Instance.MergeTool.Type);
if (merger != null) MergeCmd = merger.Cmd;
InitializeComponent();
}
#region EVENTS
private void SelectGitPath(object sender, RoutedEventArgs e) {
var dialog = new OpenFileDialog();
dialog.Filter = "Git Executable|git.exe";
dialog.FileName = "git.exe";
dialog.Title = App.Text("Preference.Dialog.GitExe");
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
dialog.CheckFileExists = true;
if (dialog.ShowDialog() == true) {
Models.Preference.Instance.Git.Path = dialog.FileName;
editGitPath?.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
}
private void SelectGitCloneDir(object sender, RoutedEventArgs e) {
FolderBrowser.Open(this, App.Text("Preference.Dialog.GitDir"), path => {
Models.Preference.Instance.Git.DefaultCloneDir = path;
txtGitCloneDir?.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
});
}
private void SelectMergeTool(object sender, RoutedEventArgs e) {
var type = Models.Preference.Instance.MergeTool.Type;
var tool = Models.MergeTool.Supported.Find(x => x.Type == type);
if (tool == null || tool.Type == 0) return;
var dialog = new OpenFileDialog();
dialog.Filter = $"{tool.Name} Executable|{tool.Exec}";
dialog.Title = App.Text("Preference.Dialog.Merger", tool.Name);
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
dialog.CheckFileExists = true;
if (dialog.ShowDialog() == true) {
Models.Preference.Instance.MergeTool.Path = dialog.FileName;
txtMergeExec?.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
}
private void MergeToolChanged(object sender, SelectionChangedEventArgs e) {
var selector = sender as ComboBox;
var type = (int)selector.SelectedValue;
var tool = Models.MergeTool.Supported.Find(x => x.Type == type);
if (tool == null) return;
Models.Preference.Instance.MergeTool.Path = tool.Finder();
MergeCmd = tool.Cmd;
txtMergeExec?.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
txtMergeCmd?.GetBindingExpression(TextBlock.TextProperty).UpdateTarget();
e.Handled = true;
}
private void Quit(object sender, RoutedEventArgs e) {
var cmd = new Commands.Config();
var oldUser = cmd.Get("user.name");
if (oldUser != User) cmd.Set("user.name", User);
var oldEmail = cmd.Get("user.email");
if (oldEmail != Email) cmd.Set("user.email", Email);
var oldCRLF = cmd.Get("core.autocrlf");
if (oldCRLF != CRLF) cmd.Set("core.autocrlf", CRLF);
Models.Preference.Save();
Close();
}
#endregion
}
}

115
src/Views/Upgrade.xaml Normal file
View file

@ -0,0 +1,115 @@
<Window x:Class="SourceGit.Views.Upgrade"
x:Name="me"
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"
Title="{StaticResource Text.UpdateAvailable}"
WindowStartupLocation="CenterOwner"
Height="400" Width="500"
ResizeMode="NoResize">
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
</WindowChrome.WindowChrome>
<controls:WindowBorder>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<!-- Title Bar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.TitleBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Icon -->
<Path Grid.Column="0" Width="16" Height="16" Margin="6,0" Data="{StaticResource Icon.Fetch}"/>
<!-- Title -->
<TextBlock Grid.Column="1" Text="{StaticResource Text.UpdateAvailable}"/>
<!-- Close -->
<controls:IconButton
Grid.Column="3"
Click="Quit"
Width="32" Height="32"
Padding="9"
Icon="{StaticResource Icon.Close}"
HoverBackground="Red"
WindowChrome.IsHitTestVisibleInChrome="True"/>
</Grid>
<!-- Body -->
<StackPanel Grid.Row="1" Orientation="Vertical" Margin="8,16,8,0">
<!-- Title -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Path Width="20" Height="20" Data="{StaticResource Icon.Git}" Fill="{StaticResource Brush.Logo}"/>
<TextBlock x:Name="txtRelease" Margin="8,0,0,0" FontSize="18" FontWeight="Bold" Text="Release 1.0 Is Out!!!"/>
</StackPanel>
<!-- Release Info -->
<Grid Margin="0,12,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{StaticResource Text.UpdateAvailable.Time}" FontWeight="Bold"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ElementName=me, Path=Version.PublishTime}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{StaticResource Text.UpdateAvailable.Based}" FontWeight="Bold"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding ElementName=me, Path=Version.CommitSHA}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{StaticResource Text.UpdateAvailable.IsPreRelease}" FontWeight="Bold"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ElementName=me, Path=Version.IsPrerelease}"/>
</Grid>
</StackPanel>
<!-- CHANGELOG -->
<Border Grid.Row="2" Margin="8" Background="{StaticResource Brush.Contents}" BorderBrush="{StaticResource Brush.Border1}" BorderThickness="1">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TextBlock
FontSize="10pt"
FontFamily="Consolas"
Padding="8"
VerticalAlignment="Top"
Foreground="{StaticResource Brush.FG2}"
Text="{Binding ElementName=me, Path=Version.Body}"/>
</ScrollViewer>
</Border>
<!-- Options -->
<StackPanel Grid.Row="4" HorizontalAlignment="Center" VerticalAlignment="Top" Orientation="Horizontal">
<Button
Click="Download"
Width="100" Height="24"
Content="{StaticResource Text.UpdateAvailable.Download}"
Background="{StaticResource Brush.Accent1}"
BorderBrush="{StaticResource Brush.FG1}"
BorderThickness="1"/>
<Button
Click="Quit"
Width="100" Height="24"
Margin="8,0,0,0"
Content="{StaticResource Text.Cancel}"/>
</StackPanel>
</Grid>
</controls:WindowBorder>
</Window>

34
src/Views/Upgrade.xaml.cs Normal file
View file

@ -0,0 +1,34 @@
using System.Diagnostics;
using System.Windows;
namespace SourceGit.Views {
/// <summary>
/// 新版本提示窗口
/// </summary>
public partial class Upgrade : 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);
}
public static void Open(Window owner, Models.Version ver) {
var dialog = new Upgrade(ver) { Owner = owner };
dialog.ShowDialog();
}
private void Download(object sender, RoutedEventArgs e) {
var info = new ProcessStartInfo("cmd", $"/c start https://gitee.com/sourcegit/SourceGit/releases/{Version.TagName}");
info.CreateNoWindow = true;
Process.Start(info);
e.Handled = true;
}
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
}
}

View file

@ -0,0 +1,27 @@
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class BranchName : ValidationRule {
private static readonly Regex REG_FORMAT = new Regex(@"^[\w\-/\.]+$");
public Models.Repository Repo { get; set; }
public string Prefix { get; set; } = "";
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var name = value as string;
if (string.IsNullOrEmpty(name)) new ValidationResult(false, App.Text("EmptyBranchName"));
if (!REG_FORMAT.IsMatch(name)) return new ValidationResult(false, App.Text("BadBranchName"));
name = Prefix + name;
foreach (var t in Repo.Branches) {
if (t.Name == name) {
return new ValidationResult(false, App.Text("DuplicatedBranchName"));
}
}
return ValidationResult.ValidResult;
}
}
}

View file

@ -0,0 +1,13 @@
using System.Globalization;
using System.IO;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class CloneDir : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
return Directory.Exists(value as string)
? ValidationResult.ValidResult
: new ValidationResult(false, App.Text("BadCloneFolder"));
}
}
}

View file

@ -0,0 +1,13 @@
using System.Globalization;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class CommitMessage : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var subject = value as string;
return string.IsNullOrWhiteSpace(subject)
? new ValidationResult(false, App.Text("EmptyCommitMessage"))
: ValidationResult.ValidResult;
}
}
}

View file

@ -0,0 +1,18 @@
using System;
using System.Globalization;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class GitURL : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
string url = value as string;
bool valid = !string.IsNullOrEmpty(url)
&& (url.StartsWith("http://", StringComparison.Ordinal)
|| url.StartsWith("https://", StringComparison.Ordinal)
|| url.StartsWith("git@", StringComparison.Ordinal)
|| url.StartsWith("file://", StringComparison.Ordinal));
return valid ? ValidationResult.ValidResult : new ValidationResult(false, App.Text("BadRemoteUri"));
}
}
}

View file

@ -0,0 +1,16 @@
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class LocalRepositoryName : ValidationRule {
private static readonly Regex REG_FORMAT = new Regex(@"^[\w\-]+$");
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var name = value as string;
if (string.IsNullOrEmpty(name)) return ValidationResult.ValidResult;
if (!REG_FORMAT.IsMatch(name)) return new ValidationResult(false, App.Text("BadLocalName"));
return ValidationResult.ValidResult;
}
}
}

View file

@ -0,0 +1,13 @@
using System.Globalization;
using System.IO;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class PatchFile : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
return File.Exists(value as string)
? ValidationResult.ValidResult
: new ValidationResult(false, App.Text("BadPatchFile"));
}
}
}

View file

@ -0,0 +1,31 @@
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class RemoteName : ValidationRule {
private static readonly Regex REG_FORMAT = new Regex(@"^[\w\-\.]+$");
public Models.Repository Repo { get; set; }
public bool IsOptional { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var name = value as string;
if (string.IsNullOrEmpty(name)) {
return IsOptional ? ValidationResult.ValidResult : new ValidationResult(false, App.Text("EmptyRemoteName"));
}
if (!REG_FORMAT.IsMatch(name)) return new ValidationResult(false, App.Text("BadRemoteName"));
if (Repo != null) {
foreach (var t in Repo.Remotes) {
if (t.Name == name) {
return new ValidationResult(false, App.Text("DuplicatedRemoteName"));
}
}
}
return ValidationResult.ValidResult;
}
}
}

View file

@ -0,0 +1,17 @@
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class SubmodulePath : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var path = value as string;
if (string.IsNullOrEmpty(path)) return ValidationResult.ValidResult;
var regex = new Regex(@"^[\w\-\._/]+$");
var succ = regex.IsMatch(path.Trim());
return !succ ? new ValidationResult(false, App.Text("BadSubmodulePath")) : ValidationResult.ValidResult;
}
}
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace SourceGit.Views.Validations {
public class TagName : ValidationRule {
private static readonly Regex REG_FORMAT = new Regex(@"^[\w\-\.]+$");
public List<Models.Tag> Tags { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var name = value as string;
if (string.IsNullOrEmpty(name)) new ValidationResult(false, App.Text("EmptyTagName"));
if (!REG_FORMAT.IsMatch(name)) return new ValidationResult(false, App.Text("BadTagName"));
foreach (var t in Tags) {
if (t.Name == name) {
return new ValidationResult(false, App.Text("DuplicatedTagName"));
}
}
return ValidationResult.ValidResult;
}
}
}

View file

@ -0,0 +1,196 @@
<UserControl x:Class="SourceGit.Views.Widgets.CommitChanges"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:converters="clr-namespace:SourceGit.Views.Converters"
xmlns:models="clr-namespace:SourceGit.Models"
xmlns:widgets="clr-namespace:SourceGit.Views.Widgets"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:PureFileName x:Key="PureFileName"/>
<converters:PureFolderName x:Key="PureFolderName"/>
<Style x:Key="Style.DataGridRow.Changes" TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="RequestBringIntoView" Handler="OnRequestBringIntoView"/>
<EventSetter Event="ContextMenuOpening" Handler="OnDataGridContextMenuOpening"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="400"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<Border
Grid.Column="0" Grid.ColumnSpan="2"
BorderBrush="{StaticResource Brush.Border2}"
BorderThickness="1"/>
<Path
Grid.Column="0"
Width="14" Height="14"
Fill="{StaticResource Brush.FG2}"
Data="{StaticResource Icon.Search}"
IsHitTestVisible="False"/>
<controls:TextEdit
Grid.Column="1"
Height="24"
Margin="0"
Placeholder="{StaticResource Text.CommitViewer.Changes.Search}"
BorderThickness="0"
TextChanged="SearchFilterChanged"/>
<controls:ChangeDisplaySwitcher
Grid.Column="2"
x:Name="modeSwitcher"
Margin="4,0,0,0" Width="18" Height="18"
Mode="{Binding Source={x:Static models:Preference.Instance}, Path=Window.ChangeInCommitInfo, Mode=TwoWay}"
ModeChanged="OnDisplayModeChanged"/>
</Grid>
<Border
Grid.Row="1"
BorderBrush="{StaticResource Brush.Border2}"
BorderThickness="1"
Background="{StaticResource Brush.Contents}">
<Grid>
<controls:Tree
x:Name="modeTree"
FontFamily="Consolas"
SelectionChanged="OnTreeSelectionChanged">
<controls:Tree.ItemContainerStyle>
<Style TargetType="{x:Type controls:TreeItem}" BasedOn="{StaticResource Style.TreeItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<EventSetter Event="ContextMenuOpening" Handler="OnTreeContextMenuOpening"/>
</Style>
</controls:Tree.ItemContainerStyle>
<controls:Tree.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:ChangeStatusIcon
Grid.Column="0"
Width="14" Height="14"
IsLocalChange="False"
Change="{Binding Change}"/>
<Path
Grid.Column="0"
x:Name="IconFolder"
Width="14" Height="14"
Fill="Goldenrod"
Data="{StaticResource Icon.Folder.Fill}"/>
<TextBlock
Grid.Column="1"
Text="{Binding Path, Converter={StaticResource PureFileName}}"
Margin="4,0,0,0"
FontSize="11"/>
</Grid>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsFolder}" Value="False">
<Setter TargetName="IconFolder" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsExpanded}" Value="True">
<Setter TargetName="IconFolder" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</controls:Tree.ItemTemplate>
</controls:Tree>
<DataGrid
x:Name="modeList"
RowHeight="24"
SelectionMode="Single"
SelectionUnit="FullRow"
SelectionChanged="OnListSelectionChanged"
RowStyle="{StaticResource Style.DataGridRow.Changes}">
<DataGrid.Columns>
<DataGridTemplateColumn Width="22" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type models:Change}">
<controls:ChangeStatusIcon Width="14" Height="14" IsLocalChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock FontFamily="Consolas" Margin="2,0,0,0" Text="{Binding Path}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid
x:Name="modeGrid"
RowHeight="24"
SelectionMode="Single"
SelectionUnit="FullRow"
SelectionChanged="OnGridSelectionChanged"
RowStyle="{StaticResource Style.DataGridRow.Changes}">
<DataGrid.Columns>
<DataGridTemplateColumn Width="22" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type models:Change}">
<controls:ChangeStatusIcon Width="14" Height="14" IsLocalChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock FontFamily="Consolas" Margin="2,0,0,0" Text="{Binding Path, Converter={StaticResource PureFileName}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock FontFamily="Consolas" Margin="4,0,0,0" Text="{Binding Path, Converter={StaticResource PureFolderName}}" Foreground="{StaticResource Brush.FG2}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Border>
</Grid>
<GridSplitter
Grid.Column="1"
Width="1"
HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="Transparent"/>
<widgets:DiffViewer
Grid.Column="2"
x:Name="diffViewer"
Margin="4,0,0,0"/>
</Grid>
</UserControl>

View file

@ -0,0 +1,310 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.Views.Widgets {
/// <summary>
/// 显示提交中的变更列表
/// </summary>
public partial class CommitChanges : UserControl {
private string repo = null;
private List<Models.Commit> range = null;
private List<Models.Change> cachedChanges = new List<Models.Change>();
private string filter = null;
public class ChangeNode {
public string Path { get; set; } = "";
public Models.Change Change { get; set; } = null;
public bool IsExpanded { get; set; } = false;
public bool IsFolder => Change == null;
public List<ChangeNode> Children { get; set; } = new List<ChangeNode>();
}
public CommitChanges() {
InitializeComponent();
}
public void SetData(string repo, List<Models.Commit> range, List<Models.Change> changes) {
this.repo = repo;
this.range = range;
this.cachedChanges = changes;
UpdateVisible();
}
public void UpdateVisible() {
Task.Run(() => {
// 筛选出可见的列表
List<Models.Change> visible;
if (string.IsNullOrEmpty(filter)) {
visible = cachedChanges;
} else {
visible = cachedChanges.Where(x => x.Path.ToUpper().Contains(filter)).ToList();
}
// 排序
visible.Sort((l, r) => l.Path.CompareTo(r.Path));
// 生成树节点
var nodes = new List<ChangeNode>();
var folders = new Dictionary<string, ChangeNode>();
var expanded = visible.Count <= 50;
foreach (var c in visible) {
var sepIdx = c.Path.IndexOf('/');
if (sepIdx == -1) {
nodes.Add(new ChangeNode() {
Path = c.Path,
Change = c,
IsExpanded = false
});
} else {
ChangeNode lastFolder = null;
var start = 0;
while (sepIdx != -1) {
var folder = c.Path.Substring(0, sepIdx);
if (folders.ContainsKey(folder)) {
lastFolder = folders[folder];
} else if (lastFolder == null) {
lastFolder = new ChangeNode() {
Path = folder,
Change = null,
IsExpanded = expanded
};
nodes.Add(lastFolder);
folders.Add(folder, lastFolder);
} else {
var cur = new ChangeNode() {
Path = folder,
Change = null,
IsExpanded = expanded
};
folders.Add(folder, cur);
lastFolder.Children.Add(cur);
lastFolder = cur;
}
start = sepIdx + 1;
sepIdx = c.Path.IndexOf('/', start);
}
lastFolder.Children.Add(new ChangeNode() {
Path = c.Path,
Change = c,
IsExpanded = false
});
}
}
folders.Clear();
SortFileNodes(nodes);
Dispatcher.Invoke(() => {
modeTree.ItemsSource = nodes;
modeList.ItemsSource = visible;
modeGrid.ItemsSource = visible;
UpdateMode();
});
});
}
private void SortFileNodes(List<ChangeNode> nodes) {
nodes.Sort((l, r) => {
if (l.IsFolder == r.IsFolder) {
return l.Path.CompareTo(r.Path);
} else {
return l.IsFolder ? -1 : 1;
}
});
foreach (var node in nodes) {
if (node.Children.Count > 1) SortFileNodes(node.Children);
}
}
private void UpdateMode() {
var mode = modeSwitcher.Mode;
if (modeTree != null) {
if (mode == Models.Change.DisplayMode.Tree) {
modeTree.Visibility = Visibility.Visible;
} else {
modeTree.Visibility = Visibility.Collapsed;
}
}
if (modeList != null) {
if (mode == Models.Change.DisplayMode.List) {
modeList.Visibility = Visibility.Visible;
modeList.Columns[1].Width = DataGridLength.SizeToCells;
modeList.Columns[1].Width = DataGridLength.Auto;
} else {
modeList.Visibility = Visibility.Collapsed;
}
}
if (modeGrid != null) {
if (mode == Models.Change.DisplayMode.Grid) {
modeGrid.Visibility = Visibility.Visible;
modeGrid.Columns[1].Width = DataGridLength.SizeToCells;
modeGrid.Columns[1].Width = DataGridLength.Auto;
modeGrid.Columns[2].Width = DataGridLength.SizeToCells;
modeGrid.Columns[2].Width = DataGridLength.Auto;
} else {
modeGrid.Visibility = Visibility.Collapsed;
}
}
}
private void OpenChangeDiff(Models.Change change) {
var revisions = new string[] { "", "" };
if (range.Count == 2) {
revisions[0] = range[0].SHA;
revisions[1] = range[1].SHA;
} else {
revisions[0] = $"{range[0].SHA}^";
revisions[1] = range[0].SHA;
if (range[0].Parents.Count == 0) revisions[0] = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
}
diffViewer.Diff(repo, new DiffViewer.Option() {
RevisionRange = revisions,
Path = change.Path,
OrgPath = change.OriginalPath
});
}
private void OpenChangeContextMenu(Models.Change change) {
var menu = new ContextMenu();
var path = change.Path;
if (change.Index != Models.Change.Status.Deleted) {
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Click += (o, ev) => {
var viewer = new Views.Histories(repo, path);
viewer.Show();
ev.Handled = true;
};
var blame = new MenuItem();
blame.Header = App.Text("Blame");
blame.Visibility = range.Count == 1 ? Visibility.Visible : Visibility.Collapsed;
blame.Click += (obj, ev) => {
var viewer = new Blame(repo, path, range[0].SHA);
viewer.Show();
ev.Handled = true;
};
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Click += (o, ev) => {
var full = Path.GetFullPath(repo + "\\" + path);
Process.Start("explorer", $"/select,{full}");
ev.Handled = true;
};
var saveAs = new MenuItem();
saveAs.Header = App.Text("SaveAs");
saveAs.Visibility = range.Count == 1 ? Visibility.Visible : Visibility.Collapsed;
saveAs.Click += (obj, ev) => {
FolderBrowser.Open(null, App.Text("SaveFileTo"), saveTo => {
var full = Path.Combine(saveTo, Path.GetFileName(path));
new Commands.SaveRevisionFile(repo, path, range[0].SHA, full).Exec();
});
ev.Handled = true;
};
menu.Items.Add(history);
menu.Items.Add(blame);
menu.Items.Add(explore);
menu.Items.Add(saveAs);
}
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Click += (obj, ev) => {
Clipboard.SetText(path);
};
menu.Items.Add(copyPath);
menu.IsOpen = true;
}
private void OnDisplayModeChanged(object sender, RoutedEventArgs e) {
UpdateMode();
}
private void SearchFilterChanged(object sender, TextChangedEventArgs e) {
var edit = sender as Controls.TextEdit;
filter = edit.Text.ToUpper();
UpdateVisible();
}
private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
e.Handled = true;
}
private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) {
if (Models.Preference.Instance.Window.ChangeInCommitInfo != Models.Change.DisplayMode.Tree) return;
diffViewer.Reset();
if (modeTree.Selected.Count == 0) return;
var change = (modeTree.Selected[0] as ChangeNode).Change;
if (change == null) return;
OpenChangeDiff(change);
}
private void OnListSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (Models.Preference.Instance.Window.ChangeInCommitInfo != Models.Change.DisplayMode.List) return;
diffViewer.Reset();
var change = (sender as DataGrid).SelectedItem as Models.Change;
if (change == null) return;
OpenChangeDiff(change);
}
private void OnGridSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (Models.Preference.Instance.Window.ChangeInCommitInfo != Models.Change.DisplayMode.Grid) return;
diffViewer.Reset();
var change = (sender as DataGrid).SelectedItem as Models.Change;
if (change == null) return;
OpenChangeDiff(change);
}
private void OnTreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
var item = sender as Controls.TreeItem;
if (item == null) return;
var node = item.DataContext as ChangeNode;
if (node == null || node.IsFolder) return;
OpenChangeContextMenu(node.Change);
e.Handled = true;
}
private void OnDataGridContextMenuOpening(object sender, ContextMenuEventArgs e) {
var row = sender as DataGridRow;
if (row == null) return;
var change = row.Item as Models.Change;
if (change == null) return;
OpenChangeContextMenu(change);
e.Handled = true;
}
}
}

View file

@ -0,0 +1,344 @@
<UserControl x:Class="SourceGit.Views.Widgets.CommitDetail"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
xmlns:converters="clr-namespace:SourceGit.Views.Converters"
xmlns:models="clr-namespace:SourceGit.Models"
xmlns:widgets="clr-namespace:SourceGit.Views.Widgets"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style x:Key="Style.DataGridRow.TextPreview" TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="RequestBringIntoView" Handler="OnRequestBringIntoView"/>
</Style>
<converters:PureFileName x:Key="PureFileName"/>
</UserControl.Resources>
<TabControl>
<TabItem Header="{StaticResource Text.CommitViewer.Info}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Author & Committer -->
<Grid Grid.Row="0" Margin="0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="96"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="96"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Author Avatar -->
<controls:Avatar
Grid.Column="0"
x:Name="avatarAuthor"
Width="64" Height="64"
HorizontalAlignment="Right"/>
<!-- Author Info -->
<StackPanel Grid.Column="1" Margin="16,0,8,0" Orientation="Vertical">
<TextBlock Text="{StaticResource Text.CommitViewer.Info.Author}" Foreground="{StaticResource Brush.FG2}"/>
<StackPanel Orientation="Horizontal" Margin="0,12,0,8">
<controls:TextEdit x:Name="txtAuthorName" IsReadOnly="True" BorderThickness="0"/>
<controls:TextEdit x:Name="txtAuthorEmail" IsReadOnly="True" BorderThickness="0" Foreground="{StaticResource Brush.FG2}" Margin="4,0,0,0"/>
</StackPanel>
<controls:TextEdit x:Name="txtAuthorTime" IsReadOnly="True" BorderThickness="0" Foreground="{StaticResource Brush.FG2}" FontSize="10"/>
</StackPanel>
<!-- Committer Avatar -->
<controls:Avatar
Grid.Column="2"
x:Name="avatarCommitter"
Width="64" Height="64"
HorizontalAlignment="Right"/>
<!-- Committer Info -->
<StackPanel x:Name="committerInfoPanel" Grid.Column="3" Margin="16,0,8,0" Orientation="Vertical">
<TextBlock Text="{StaticResource Text.CommitViewer.Info.Committer}" Foreground="{StaticResource Brush.FG2}"/>
<StackPanel Orientation="Horizontal" Margin="0,12,0,6">
<controls:TextEdit x:Name="txtCommitterName" IsReadOnly="True" BorderThickness="0"/>
<controls:TextEdit x:Name="txtCommitterEmail" IsReadOnly="True" BorderThickness="0" Foreground="{StaticResource Brush.FG2}" Margin="4,0,0,0"/>
</StackPanel>
<controls:TextEdit x:Name="txtCommitterTime" IsReadOnly="True" BorderThickness="0" Foreground="{StaticResource Brush.FG2}" FontSize="10"/>
</StackPanel>
</Grid>
<!-- Line -->
<Rectangle Grid.Row="1" Height="1" Margin="8" Fill="{StaticResource Brush.Border2}" VerticalAlignment="Center"/>
<!-- Base Information -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" x:Name="rowParents"/>
<RowDefinition Height="Auto" x:Name="rowRefs"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="96"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- SHA -->
<TextBlock
Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right"
Text="{StaticResource Text.CommitViewer.Info.SHA}"
Foreground="{StaticResource Brush.FG2}"/>
<controls:TextEdit
Grid.Row="0" Grid.Column="1"
Height="24"
x:Name="txtSHA"
IsReadOnly="True"
BorderThickness="0"
FontFamily="Consolas"
Margin="11,0,0,0"/>
<!-- PARENTS -->
<TextBlock
Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right"
Text="{StaticResource Text.CommitViewer.Info.Parents}"
Foreground="{StaticResource Brush.FG2}"/>
<ItemsControl Grid.Row="1" Grid.Column="1" x:Name="listParents" Height="24" Margin="13,0,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Margin="0,0,8,0" FontFamily="Consolas">
<Hyperlink RequestNavigate="OnNavigateParent" NavigateUri="{Binding .}" ToolTip="{StaticResource Text.Goto}">
<Run Text="{Binding .}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- REFS -->
<TextBlock
Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right"
Text="{StaticResource Text.CommitViewer.Info.Refs}"
Foreground="{StaticResource Brush.FG2}"/>
<ItemsControl Grid.Row="2" Grid.Column="1" x:Name="listRefs" Height="24" Margin="11,0,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type models:Decorator}">
<StackPanel Orientation="Horizontal" Height="16" Margin="2,0">
<Border Background="{StaticResource Brush.Decorator}">
<Path x:Name="Icon" Margin="4,0" Width="8" Height="8" Data="{StaticResource Icon.Branch}"/>
</Border>
<Border x:Name="Color" Background="#FFFFB835">
<TextBlock Text="{Binding Name}" FontSize="11" Margin="4,0" Foreground="Black"/>
</Border>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="{x:Static models:DecoratorType.CurrentBranchHead}">
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Check}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static models:DecoratorType.RemoteBranchHead}">
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Remote}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static models:DecoratorType.Tag}">
<Setter TargetName="Color" Property="Background" Value="#FF02C302"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Tag}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Message -->
<TextBlock
Grid.Row="3" Grid.Column="0"
Margin="0,4,0,0"
HorizontalAlignment="Right" VerticalAlignment="Top"
Text="{StaticResource Text.CommitViewer.Info.Message}"
Foreground="{StaticResource Brush.FG2}"/>
<controls:TextEdit
Grid.Row="3" Grid.Column="1"
x:Name="txtMessage"
IsReadOnly="true"
FontFamily="Consolas"
BorderThickness="0"
TextWrapping="Wrap"
Margin="11,5,16,0"
VerticalAlignment="Top"/>
</Grid>
<!-- Line -->
<Rectangle Grid.Row="3" Height="1" Margin="8" Fill="{StaticResource Brush.Border2}" VerticalAlignment="Center"/>
<!-- Change List -->
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="96"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,4,0,0"
HorizontalAlignment="Right" VerticalAlignment="Top"
Text="{StaticResource Text.CommitViewer.Info.Changed}"
Foreground="{StaticResource Brush.FG2}"/>
<DataGrid
Grid.Column="1"
x:Name="changeList"
RowHeight="24"
Margin="11,0,0,2">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow.TextPreview}">
<EventSetter Event="ContextMenuOpening" Handler="OnChangeListContextMenuOpening"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width="22" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<controls:ChangeStatusIcon Width="14" Height="14" IsLocalChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock FontFamily="Consolas" Margin="2,0,0,0" Text="{Binding Path}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</TabItem>
<!-- Change Details -->
<TabItem Header="{StaticResource Text.CommitViewer.Changes}">
<widgets:CommitChanges x:Name="changeContainer"/>
</TabItem>
<!-- Revision Files -->
<TabItem Header="{StaticResource Text.CommitViewer.Files}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="400"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{StaticResource Brush.Contents}" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="1">
<controls:Tree
x:Name="treeFiles"
FontFamily="Consolas"
SelectionChanged="OnFilesSelectionChanged"
ContextMenuOpening="OnFilesContextMenuOpening">
<controls:Tree.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Height="24">
<Path x:Name="Icon" Width="14" Height="14" Data="{StaticResource Icon.File}"/>
<TextBlock Margin="6,0,0,0" FontSize="11" Text="{Binding Path, Converter={StaticResource PureFileName}}"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsFolder}" Value="True">
<Setter TargetName="Icon" Property="Fill" Value="Goldenrod"/>
<Setter TargetName="Icon" Property="Opacity" Value="1"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFolder}" Value="True"/>
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFolder}" Value="True"/>
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</MultiDataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</controls:Tree.ItemTemplate>
</controls:Tree>
</Border>
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" Background="Transparent"/>
<Border Grid.Column="2" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="1" Margin="2,0">
<Grid>
<Grid x:Name="layerTextPreview" Visibility="Collapsed" SizeChanged="OnTextPreviewSizeChanged">
<DataGrid
x:Name="txtPreviewData"
FontFamily="Consolas"
RowHeight="16"
RowStyle="{StaticResource Style.DataGridRow.TextPreview}"
FrozenColumnCount="1"
ContextMenuOpening="OnTextPreviewContextMenuOpening"
SelectionMode="Extended"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Number}" ElementStyle="{StaticResource Style.TextBlock.LineNumber}"/>
<DataGridTextColumn Binding="{Binding Data}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
</DataGrid.Columns>
</DataGrid>
<Rectangle x:Name="txtPreviewSplitter" Width="1" Fill="{StaticResource Brush.Border2}" HorizontalAlignment="Left"/>
</Grid>
<ScrollViewer
x:Name="layerImagePreview"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
Visibility="Collapsed">
<Image
x:Name="imgPreviewData"
Width="Auto" Height="Auto"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ScrollViewer>
<StackPanel
x:Name="layerRevisionPreview"
Orientation="Vertical"
VerticalAlignment="Center" HorizontalAlignment="Center"
Visibility="Collapsed">
<Path x:Name="iconRevisionPreview" Width="64" Height="64" Data="{StaticResource Icon.Submodule}" Fill="{StaticResource Brush.FG2}"/>
<TextBlock x:Name="txtRevisionPreview" Margin="0,16,0,0" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
<StackPanel
x:Name="layerBinaryPreview"
Orientation="Vertical"
VerticalAlignment="Center" HorizontalAlignment="Center"
Visibility="Collapsed">
<Path Width="64" Height="64" Data="{StaticResource Icon.Error}" Fill="{StaticResource Brush.FG2}"/>
<TextBlock Margin="0,16,0,0" Text="{StaticResource Text.BinaryNotSupported}" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</TabItem>
</TabControl>
</UserControl>

Some files were not shown because too many files have changed in this diff Show more