diff --git a/src/Resources/Locales/en_US.xaml b/src/Resources/Locales/en_US.xaml index 310e403b..ee6eac93 100644 --- a/src/Resources/Locales/en_US.xaml +++ b/src/Resources/Locales/en_US.xaml @@ -298,8 +298,8 @@ SourceGit Open Local Repository Clone Remote Repository - REPOSITORIES - DRAG-DROP YOUR FOLDER + Bookmarks + Histories Add Folder Add Sub-Folder Rename diff --git a/src/Resources/Locales/zh_CN.xaml b/src/Resources/Locales/zh_CN.xaml index a5984029..89561ac1 100644 --- a/src/Resources/Locales/zh_CN.xaml +++ b/src/Resources/Locales/zh_CN.xaml @@ -297,8 +297,8 @@ 欢迎使用本软件 打开本地仓库 克隆远程仓库 - 仓库列表 - 支持拖放操作 + 收藏/书签 + 最近使用 新建分组 新建子分组 重命名 diff --git a/src/Views/Widgets/Welcome.xaml b/src/Views/Widgets/Welcome.xaml index 55d457d8..0bd310b3 100644 --- a/src/Views/Widgets/Welcome.xaml +++ b/src/Views/Widgets/Welcome.xaml @@ -1,31 +1,30 @@ - - - - - - - - - - + + + + + + + + - - + - - - - - - - + + + + + + + - + - - + + + - - + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + DragEnter="OnTreeBookmarksDragEnter" + DragLeave="OnTreeBookmarksDragLeave" + DragOver="OnTreeBookmarksDragOver" + Drop="OnTreeBookmarksDrop"> - + @@ -135,14 +185,14 @@ Color="{Binding Bookmark}" IsNewPage="False"/> - + - - - + + + diff --git a/src/Views/Widgets/Welcome.xaml.cs b/src/Views/Widgets/Welcome.xaml.cs index eb255e83..30ea8923 100644 --- a/src/Views/Widgets/Welcome.xaml.cs +++ b/src/Views/Widgets/Welcome.xaml.cs @@ -14,92 +14,117 @@ namespace SourceGit.Views.Widgets { /// 新标签页 /// public partial class Welcome : UserControl, Controls.IPopupContainer { - + /// /// 树节点数据 /// public class Node : Controls.BindableBase { - public string Id { get; set; } - public string ParentId { get; set; } - + public string Id { + get; + set; + } + public string ParentId { + get; + set; + } + private string name; public string Name { get => name; set => SetProperty(ref name, value); } - - public bool IsGroup { get; set; } - + + public bool IsGroup { + get; + set; + } + private bool isEditing = false; public bool IsEditing { get => isEditing; set => SetProperty(ref isEditing, value); } - - public bool IsExpanded { get; set; } + + public bool IsExpanded { + get; + set; + } private int bookmark = 0; public int Bookmark { get => bookmark; set => SetProperty(ref bookmark, value); } - - public List Children { get; set; } + + public List Children { + get; + set; + } } - + /// /// 仓库节点编辑事件参数 /// public event Action OnNodeEdited; + private bool clearBookmark = false; + public Welcome() { InitializeComponent(); UpdateTree(); } - + #region POPUP_CONTAINER public void Show(Controls.PopupWidget widget) { popup.Show(widget); } - + public void ShowAndStart(Controls.PopupWidget widget) { popup.ShowAndStart(widget); } - + public void UpdateProgress(string message) { popup.UpdateProgress(message); } #endregion - + #region FUNC_EVENTS private void OnOpenClicked(object sender, RoutedEventArgs e) { var dialog = new Controls.FolderDialog(); - if (dialog.ShowDialog() == true) CheckAndOpen(dialog.SelectedPath); + + if (dialog.ShowDialog() == true) { + CheckAndOpen(dialog.SelectedPath); + } } - + private void OnCloneClicked(object sender, RoutedEventArgs e) { - if (MakeSureReady()) new Popups.Clone().Show(); + if (MakeSureReady()) { + new Popups.Clone().Show(); + } } - + private void OnTreeNodeStatusChange(object sender, RoutedEventArgs e) { var node = (sender as Controls.TreeItem).DataContext as Node; + if (node != null) { var group = Models.Preference.Instance.FindGroup(node.Id); group.IsExpanded = node.IsExpanded; e.Handled = true; } } - + private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs e) { var node = (sender as Controls.TreeItem).DataContext as Node; + if (node != null && !node.IsGroup) { CheckAndOpen(node.Id); e.Handled = true; } } - + private void OnTreeContextMenuOpening(object sender, ContextMenuEventArgs e) { - var item = tree.FindItem(e.OriginalSource as DependencyObject); + var item = treeHistory.FindItem(e.OriginalSource as DependencyObject); + if (item == null) { var addFolder = new MenuItem(); addFolder.Header = App.Text("Welcome.NewFolder"); @@ -108,16 +133,20 @@ namespace SourceGit.Views.Widgets { UpdateTree(group.Id); ev.Handled = true; }; - var menu = new ContextMenu(); menu.Items.Add(addFolder); menu.IsOpen = true; e.Handled = true; - } else { + } + else { var node = item.DataContext as Node; - if (node == null) return; - + + if (node == null) { + return; + } + var menu = new ContextMenu(); + if (!node.IsGroup) { var open = new MenuItem(); open.Header = App.Text("RepoCM.Open"); @@ -125,73 +154,71 @@ namespace SourceGit.Views.Widgets { CheckAndOpen(node.Id); ev.Handled = true; }; - var explore = new MenuItem(); explore.Header = App.Text("RepoCM.Explore"); explore.Click += (o, ev) => { Process.Start("explorer", node.Id); ev.Handled = true; }; - var iconBookmark = FindResource("Icon.Bookmark") as Geometry; var bookmark = new MenuItem(); bookmark.Header = App.Text("RepoCM.Bookmark"); + for (int i = 0; i < Controls.Bookmark.COLORS.Length; i++) { var icon = new System.Windows.Shapes.Path(); icon.Data = iconBookmark; icon.Fill = Controls.Bookmark.COLORS[i]; icon.Width = 8; - var mark = new MenuItem(); mark.Icon = icon; mark.Header = $"{i}"; - var refIdx = i; mark.Click += (o, ev) => { var repo = Models.Preference.Instance.FindRepository(node.Id); + if (repo != null) { repo.Bookmark = refIdx; - node.Bookmark = refIdx; - OnNodeEdited?.Invoke(node); + UpdateTree(); } + ev.Handled = true; }; - bookmark.Items.Add(mark); } - + menu.Items.Add(open); menu.Items.Add(explore); menu.Items.Add(bookmark); - } else { + } + else { var addSubFolder = new MenuItem(); addSubFolder.Header = App.Text("Welcome.NewSubFolder"); addSubFolder.Click += (o, ev) => { var parent = Models.Preference.Instance.FindGroup(node.Id); - if (parent != null) parent.IsExpanded = true; - + + if (parent != null) { + parent.IsExpanded = true; + } + var group = Models.Preference.Instance.AddGroup("New Group", node.Id); UpdateTree(group.Id); ev.Handled = true; }; - menu.Items.Add(addSubFolder); } - + var rename = new MenuItem(); rename.Header = App.Text("Welcome.Rename"); rename.Click += (o, ev) => { UpdateTree(node.Id); ev.Handled = true; }; - var delete = new MenuItem(); delete.Header = App.Text("Welcome.Delete"); delete.Click += (o, ev) => { DeleteNode(node); ev.Handled = true; }; - menu.Items.Add(rename); menu.Items.Add(delete); menu.IsOpen = true; @@ -199,93 +226,188 @@ namespace SourceGit.Views.Widgets { } } #endregion - + #region DRAP_DROP_EVENTS - private void OnPageDragEnter(object sender, DragEventArgs e) { - if (e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetDataPresent(typeof(Node))) { - dropArea.Visibility = Visibility.Visible; + private void OnPageMouseDown(object sender, MouseButtonEventArgs e) { + var itemHistory = treeHistory.FindItem(e.OriginalSource as DependencyObject); + + if (itemHistory == null) { + treeHistory.UnselectAll(); } + + var itemBookmark = treeBookmarks.FindItem(e.OriginalSource as DependencyObject); + + if (itemBookmark == null) { + treeBookmarks.UnselectAll(); + } + + clearBookmark = false; } - - private void OnPageDragLeave(object sender, DragEventArgs e) { - dropArea.Visibility = Visibility.Hidden; - } - - private void OnPageDrop(object sender, DragEventArgs e) { - dropArea.Visibility = Visibility.Hidden; - } - - private void OnTreeMouseMove(object sender, MouseEventArgs e) { - if (e.LeftButton != MouseButtonState.Pressed) return; - - var item = tree.FindItem(e.OriginalSource as DependencyObject); - if (item == null) return; - - tree.UnselectAll(); - + + private void OnPageMouseMove(object sender, MouseEventArgs e) { + if (e.LeftButton != MouseButtonState.Pressed) { + return; + } + + var item = treeHistory.FindItem(e.OriginalSource as DependencyObject); + + if (item == null) { + return; + } + + treeHistory.UnselectAll(); var adorner = new Controls.DragDropAdorner(item); DragDrop.DoDragDrop(item, item.DataContext, DragDropEffects.Move); adorner.Remove(); } - - private void OnTreeDragOver(object sender, DragEventArgs e) { - if (!e.Data.GetDataPresent(DataFormats.FileDrop) && !e.Data.GetDataPresent(typeof(Node))) return; - - var item = tree.FindItem(e.OriginalSource as DependencyObject); - if (item == null) return; - - var node = item.DataContext as Node; - if (node.IsGroup && !item.IsExpanded) item.IsExpanded = true; - e.Handled = true; - } - - private void OnTreeDrop(object sender, DragEventArgs e) { + + private void OnPageDrop(object sender, DragEventArgs e) { bool rebuild = false; - dropArea.Visibility = Visibility.Hidden; - - var parent = ""; - var to = tree.FindItem(e.OriginalSource as DependencyObject); - if (to != null) { - var dst = to.DataContext as Node; - parent = dst.IsGroup ? dst.Id : dst.ParentId; - } - + if (e.Data.GetDataPresent(DataFormats.FileDrop)) { - if (!MakeSureReady()) return; - + if (!MakeSureReady()) { + return; + } + var paths = e.Data.GetData(DataFormats.FileDrop) as string[]; + foreach (var path in paths) { var dir = new Commands.QueryGitDir(path).Result(); + if (dir != null) { var root = new Commands.GetRepositoryRootPath(path).Result(); - Models.Preference.Instance.AddRepository(root, dir, parent); + Models.Preference.Instance.AddRepository(root, dir, ""); CheckAndOpen(path); rebuild = true; } } - } else if (e.Data.GetDataPresent(typeof(Node))) { - var src = e.Data.GetData(typeof(Node)) as Node; - if (src.IsGroup) { - if (!Models.Preference.Instance.IsSubGroup(src.Id, parent)) { - Models.Preference.Instance.FindGroup(src.Id).Parent = parent; - rebuild = true; + } + else if (e.Data.GetDataPresent(typeof(Node))) { + var node = e.Data.GetData(typeof(Node)) as Node; + + if (node.IsGroup) { + e.Handled = true; + return; + } + else { + var repo = Models.Preference.Instance.FindRepository(node.Id); + + if (repo != null && repo.Bookmark != 0 && clearBookmark) { + repo.Bookmark = 0; } - } else { - Models.Preference.Instance.FindRepository(src.Id).GroupId = parent; + clearBookmark = false; rebuild = true; } } + + if (rebuild) { + UpdateTree(); + } + + e.Handled = true; + } + + private void OnTreeBookmarksDragEnter(object sender, DragEventArgs e) { + if (e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetDataPresent(typeof(Node))) { + dropArea.Visibility = Visibility.Visible; + } + } + + private void OnTreeBookmarksDragLeave(object sender, DragEventArgs e) { + dropArea.Visibility = Visibility.Hidden; + } + + private void OnTreeBookmarksDragOver(object sender, DragEventArgs e) { + if (!e.Data.GetDataPresent(DataFormats.FileDrop) && !e.Data.GetDataPresent(typeof(Node))) { + return; + } + + var item = treeBookmarks.FindItem(e.OriginalSource as DependencyObject); + + if (item == null) { + return; + } + + var node = item.DataContext as Node; + + if (node.IsGroup && !item.IsExpanded) { + item.IsExpanded = true; + } - if (rebuild) UpdateTree(); + clearBookmark = true; + + e.Handled = true; + } + + private void OnTreeBookmarksDrop(object sender, DragEventArgs e) { + bool rebuild = false; + var parent = ""; + + clearBookmark = false; + dropArea.Visibility = Visibility.Hidden; + var to = treeBookmarks.FindItem(e.OriginalSource as DependencyObject); + + if (to != null) { + var dst = to.DataContext as Node; + parent = dst.IsGroup ? dst.Id : dst.ParentId; + } + + if (e.Data.GetDataPresent(DataFormats.FileDrop)) { + if (!MakeSureReady()) { + return; + } + + var paths = e.Data.GetData(DataFormats.FileDrop) as string[]; + + foreach (var path in paths) { + var dir = new Commands.QueryGitDir(path).Result(); + + if (dir != null) { + var root = new Commands.GetRepositoryRootPath(path).Result(); + Models.Preference.Instance.AddRepository(root, dir, parent).Bookmark = 1; // 默认添加的标签; + // CheckAndOpen(path); + rebuild = true; + } + } + } + else if (e.Data.GetDataPresent(typeof(Node))) { + var node = e.Data.GetData(typeof(Node)) as Node; + + if (node.IsGroup) { + if (!Models.Preference.Instance.IsSubGroup(node.Id, parent)) { + Models.Preference.Instance.FindGroup(node.Id).Parent = parent; + rebuild = true; + } + } + else { + var repo = Models.Preference.Instance.FindRepository(node.Id); + + if (repo != null) { + repo.GroupId = parent; + + if (repo.Bookmark == 0) { + repo.Bookmark = 1; + } + } + + rebuild = true; + } + } + + if (rebuild) { + UpdateTree(); + } + e.Handled = true; } #endregion - + #region DATA private void UpdateTree(string editingNodeId = null) { var groupNodes = new Dictionary(); - var nodes = new List(); - + var nodesHistory = new List(); + var nodesBookmarks = new List(); + foreach (var group in Models.Preference.Instance.Groups) { Node node = new Node() { Id = group.Id, @@ -297,20 +419,18 @@ namespace SourceGit.Views.Widgets { Bookmark = 0, Children = new List(), }; - groupNodes.Add(node.Id, node); } - - nodes.Clear(); - + foreach (var kv in groupNodes) { if (groupNodes.ContainsKey(kv.Value.ParentId)) { groupNodes[kv.Value.ParentId].Children.Add(kv.Value); - } else { - nodes.Add(kv.Value); + } + else { + nodesBookmarks.Add(kv.Value); } } - + foreach (var repo in Models.Preference.Instance.Repositories) { Node node = new Node() { Id = repo.Path, @@ -322,109 +442,138 @@ namespace SourceGit.Views.Widgets { Bookmark = repo.Bookmark, Children = new List(), }; - - if (groupNodes.ContainsKey(repo.GroupId)) { - groupNodes[repo.GroupId].Children.Add(node); - } else { - nodes.Add(node); + nodesHistory.Add(node); + + if (repo.Bookmark != 0) { + if (groupNodes.ContainsKey(repo.GroupId)) { + groupNodes[repo.GroupId].Children.Add(node); + } + else { + nodesBookmarks.Add(node); + } } + + OnNodeEdited?.Invoke(node); } - - tree.ItemsSource = nodes; + + treeHistory.ItemsSource = nodesHistory; + treeBookmarks.ItemsSource = nodesBookmarks; } - + private void DeleteNode(Node node) { if (node.IsGroup) { Models.Preference.Instance.RemoveGroup(node.Id); - } else { + } + else { Models.Preference.Instance.RemoveRepository(node.Id); } - + UpdateTree(); } - + private bool MakeSureReady() { if (!Models.Preference.Instance.IsReady) { Models.Exception.Raise(App.Text("NotConfigured")); return false; } + return true; } - + private void CheckAndOpen(string path) { - if (!MakeSureReady()) return; - + if (!MakeSureReady()) { + return; + } + if (!Directory.Exists(path)) { Models.Exception.Raise(App.Text("PathNotFound", path)); return; } - + var root = new Commands.GetRepositoryRootPath(path).Result(); + if (root == null) { new Popups.Init(path).Show(); return; } - + var gitDir = new Commands.QueryGitDir(root).Result(); var repo = Models.Preference.Instance.AddRepository(root, gitDir, ""); Models.Watcher.Open(repo); + treeHistory.UnselectAll(); + treeBookmarks.UnselectAll(); } - + public void UpdateNodes(string id, int bookmark, IEnumerable nodes = null) { - if (nodes == null) nodes = tree.ItemsSource.OfType(); + if (nodes == null) { + nodes = treeHistory.ItemsSource.OfType(); + } + foreach (var node in nodes) { if (!node.IsGroup) { if (node.Id == id) { - node.Bookmark = bookmark; + Models.Preference.Instance.FindRepository(node.Id).Bookmark = bookmark; + UpdateTree(); break; } - } else if (node.Children.Count > 0) { + } + else if (node.Children.Count > 0) { UpdateNodes(id, bookmark, node.Children); } } - } #endregion - + #region RENAME_NODES private void RenameStart(object sender, RoutedEventArgs e) { var edit = sender as Controls.TextEdit; - if (edit == null || !edit.IsVisible) return; - + + if (edit == null || !edit.IsVisible) { + return; + } + edit.SelectAll(); edit.Focus(); } - + private void RenameKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Escape) { UpdateTree(); e.Handled = true; - } else if (e.Key == Key.Enter) { + } + else if (e.Key == Key.Enter) { RenameEnd(sender, e); e.Handled = true; } } - + private void RenameEnd(object sender, RoutedEventArgs e) { var edit = sender as Controls.TextEdit; - if (edit == null) return; - + + if (edit == null) { + return; + } + if (string.IsNullOrWhiteSpace(edit.Text)) { UpdateTree(); e.Handled = false; return; } - + var node = edit.DataContext as Node; + if (node != null) { node.Name = edit.Text; node.IsEditing = false; + if (node.IsGroup) { Models.Preference.Instance.RenameGroup(node.Id, edit.Text); - } else { + } + else { Models.Preference.Instance.RenameRepository(node.Id, node.Name); OnNodeEdited?.Invoke(node); } + e.Handled = false; } }