From d5033b64f0a2160984e06cf38eda645c91e41e5c Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Fri, 27 Jul 2018 10:12:12 +0100 Subject: [PATCH 1/5] Spike of the Open from GitHub functionality --- .../GitHubCommand.cs | 2 - .../UI/ViewModels/HelloWorldViewModel.cs | 96 +++++++++++++++++-- .../UI/ViewModels/IHelloWorldViewModel.cs | 17 +++- .../UI/Views/HelloWorldView.xaml | 65 ++++++++----- 4 files changed, 144 insertions(+), 36 deletions(-) diff --git a/src/GitHub.VisualStudio.Contrib/GitHubCommand.cs b/src/GitHub.VisualStudio.Contrib/GitHubCommand.cs index 597d1bf..4c2823e 100644 --- a/src/GitHub.VisualStudio.Contrib/GitHubCommand.cs +++ b/src/GitHub.VisualStudio.Contrib/GitHubCommand.cs @@ -36,8 +36,6 @@ internal override void MenuItemCallback(object sender, EventArgs e) async Task ShowOpenFromGitHubAsync() { var pane = await toolWindowManager.Value.ShowGitHubPane(); - await pane.ShowPullRequests(); - if (pane.Content is INavigationViewModel navigationViewModel) { var viewModel = factory.CreateViewModel(); diff --git a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs index c1ca5c1..e2fc58c 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs @@ -1,9 +1,10 @@ using System; +using System.IO; using System.Reactive.Linq; using System.ComponentModel.Composition; using ReactiveUI; +using GitHub.Services; using GitHub.ViewModels.GitHubPane; -using GitHub.VisualStudio.Contrib.Console; namespace GitHub.VisualStudio.Contrib.UI.ViewModels { @@ -11,27 +12,108 @@ namespace GitHub.VisualStudio.Contrib.UI.ViewModels [PartCreationPolicy(CreationPolicy.NonShared)] public class HelloWorldViewModel : PanePageViewModelBase, IHelloWorldViewModel { + readonly IGitHubContextService contextService; + readonly ITeamExplorerContext teamExplorerContext; + readonly IRepositoryCloneService repositoryCloneService; + + GitHubContext context; + string targetUrl; + string blobName; + string defaultPath; Uri webUrl; [ImportingConstructor] - public HelloWorldViewModel(IConsoleContext console) + public HelloWorldViewModel( + IGitHubContextService contextService, + ITeamExplorerContext teamExplorerContext, + IRepositoryCloneService repositoryCloneService, + IGitHubServiceProvider serviceProvider) { - SayHello = ReactiveCommand.Create(); - SayHello.Subscribe(_ => console.WriteLine("Hello, World!")); + this.contextService = contextService; + this.teamExplorerContext = teamExplorerContext; + + Title = "GitHub URL"; + + GoTo = ReactiveCommand.Create(); + GoTo.Subscribe(_ => + { + var localPath = teamExplorerContext.ActiveRepository?.LocalPath; + contextService.TryOpenFile(localPath, Context); + }); + + Clone = ReactiveCommand.Create(); + Clone.Subscribe(_ => + { + // await repositoryCloneService.CloneRepository(cloneUrl, repositoryDirName, targetDir); + }); + + Open = ReactiveCommand.Create(); + Open.Subscribe(_ => + { + var dte = serviceProvider.GetService(); + dte.ExecuteCommand("File.OpenFolder", DefaultPath); + dte.ExecuteCommand("View.TfsTeamExplorer"); + contextService.TryOpenFile(DefaultPath, Context); + }); + + Context = contextService.FindContextFromClipboard(); + TargetUrl = Context?.Url; - WebUrl = new Uri("https://github.com/editor-tools/GitHub.VisualStudio.Contrib"); + this.WhenAnyValue(x => x.TargetUrl).Subscribe(u => + { + Context = contextService.FindContextFromUrl(u); + }); + + this.WhenAnyValue(x => x.Context).Subscribe(c => + { + BlobName = c?.BlobName; + + DefaultPath = + repositoryCloneService.DefaultClonePath is string home && + context?.Owner is string owner && + context?.RepositoryName is string repositoryName ? + Path.Combine(home, owner, repositoryName) : null; + }); Done = ReactiveCommand.Create(); } - public IReactiveCommand SayHello { get; } + public string TargetUrl + { + get { return targetUrl; } + set { this.RaiseAndSetIfChanged(ref targetUrl, value); } + } - public IObservable Done { get; } + public GitHubContext Context + { + get { return context; } + private set { this.RaiseAndSetIfChanged(ref context, value); } + } + + public string BlobName + { + get { return blobName; } + private set { this.RaiseAndSetIfChanged(ref blobName, value); } + } + + public string DefaultPath + { + get { return defaultPath; } + private set { this.RaiseAndSetIfChanged(ref defaultPath, value); } + } public Uri WebUrl { get { return webUrl; } private set { this.RaiseAndSetIfChanged(ref webUrl, value); } } + + public IReactiveCommand GoTo { get; } + + public IReactiveCommand Clone { get; } + + public IReactiveCommand Open { get; } + + public IObservable Done { get; } } } \ No newline at end of file diff --git a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/IHelloWorldViewModel.cs b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/IHelloWorldViewModel.cs index b7e083d..2825038 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/IHelloWorldViewModel.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/IHelloWorldViewModel.cs @@ -1,4 +1,5 @@ -using GitHub.ViewModels; +using GitHub.Services; +using GitHub.ViewModels; using GitHub.ViewModels.Dialog; using GitHub.ViewModels.GitHubPane; using ReactiveUI; @@ -7,6 +8,18 @@ namespace GitHub.VisualStudio.Contrib.UI.ViewModels { public interface IHelloWorldViewModel : IViewModel, IDialogContentViewModel, IPanePageViewModel, IOpenInBrowser { - IReactiveCommand SayHello { get; } + IReactiveCommand GoTo { get; } + + IReactiveCommand Clone { get; } + + IReactiveCommand Open { get; } + + string TargetUrl { get; set; } + + string BlobName { get; } + + string DefaultPath { get; } + + GitHubContext Context { get; } } } \ No newline at end of file diff --git a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml index c002927..76b62dc 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml +++ b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml @@ -9,38 +9,53 @@ xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" DataContext="{Binding ViewModel}" mc:Ignorable="d"> - - - - - - - - - - - - + + + + + + + + + + + - - + From 1d097ccac9ea04f370276b1445746fc53bc21dbc Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Fri, 27 Jul 2018 12:44:35 +0100 Subject: [PATCH 2/5] Bind Clone/Open panel visibility to commands Only show the options when commands are available. --- .../UI/ViewModels/HelloWorldViewModel.cs | 7 ++++--- .../UI/Views/HelloWorldView.xaml | 2 ++ .../UI/Views/HelloWorldView.xaml.cs | 7 ++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs index e2fc58c..f9ceeab 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs @@ -14,7 +14,6 @@ public class HelloWorldViewModel : PanePageViewModelBase, IHelloWorldViewModel { readonly IGitHubContextService contextService; readonly ITeamExplorerContext teamExplorerContext; - readonly IRepositoryCloneService repositoryCloneService; GitHubContext context; string targetUrl; @@ -41,13 +40,15 @@ public HelloWorldViewModel( contextService.TryOpenFile(localPath, Context); }); - Clone = ReactiveCommand.Create(); + Clone = ReactiveCommand.Create(this.WhenAnyValue(x => + x.DefaultPath).Select(d => d is string dir && !Directory.Exists(d))); Clone.Subscribe(_ => { // await repositoryCloneService.CloneRepository(cloneUrl, repositoryDirName, targetDir); }); - Open = ReactiveCommand.Create(); + Open = ReactiveCommand.Create(this.WhenAnyValue(x => + x.DefaultPath).Select(d => d is string dir && Directory.Exists(d))); Open.Subscribe(_ => { var dte = serviceProvider.GetService(); diff --git a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml index 76b62dc..21578f9 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml +++ b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml @@ -41,6 +41,7 @@ @@ -49,6 +50,7 @@ diff --git a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs index 8338aac..694def5 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs @@ -16,10 +16,15 @@ public partial class HelloWorldView : GenericHelloWorldView { public HelloWorldView() { - this.InitializeComponent(); + InitializeComponent(); this.WhenActivated(d => { + this.WhenAnyObservable(x => x.ViewModel.Clone.CanExecuteObservable) + .BindTo(this, x => x.ClonePanel.Visibility); + + this.WhenAnyObservable(x => x.ViewModel.Open.CanExecuteObservable) + .BindTo(this, x => x.OpenPanel.Visibility); }); } } From 443faa3f99a2d7b8f90e93245b22700171ac6bcb Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Fri, 27 Jul 2018 12:51:52 +0100 Subject: [PATCH 3/5] Only show Go To command when targeting a blob --- .../UI/ViewModels/HelloWorldViewModel.cs | 2 +- src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml | 1 + .../UI/Views/HelloWorldView.xaml.cs | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs index f9ceeab..fc4c203 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs @@ -33,7 +33,7 @@ public HelloWorldViewModel( Title = "GitHub URL"; - GoTo = ReactiveCommand.Create(); + GoTo = ReactiveCommand.Create(this.WhenAnyValue(x => x.BlobName).Select(b => b != null)); GoTo.Subscribe(_ => { var localPath = teamExplorerContext.ActiveRepository?.LocalPath; diff --git a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml index 21578f9..d499371 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml +++ b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml @@ -33,6 +33,7 @@ Content="Open from GitHub" /> diff --git a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs index 694def5..34fe32f 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/Views/HelloWorldView.xaml.cs @@ -20,6 +20,9 @@ public HelloWorldView() this.WhenActivated(d => { + this.WhenAnyObservable(x => x.ViewModel.GoTo.CanExecuteObservable) + .BindTo(this, x => x.GoToPanel.Visibility); + this.WhenAnyObservable(x => x.ViewModel.Clone.CanExecuteObservable) .BindTo(this, x => x.ClonePanel.Visibility); From f349ac89007a307c717baca0a1d99ed4811a40b0 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Fri, 27 Jul 2018 13:45:04 +0100 Subject: [PATCH 4/5] Show Go To when target and active repo are same Hide Open and Clone when target and active repo are same. --- .../UI/ViewModels/HelloWorldViewModel.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs index fc4c203..7f4256c 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs @@ -4,6 +4,7 @@ using System.ComponentModel.Composition; using ReactiveUI; using GitHub.Services; +using GitHub.Primitives; using GitHub.ViewModels.GitHubPane; namespace GitHub.VisualStudio.Contrib.UI.ViewModels @@ -19,6 +20,7 @@ public class HelloWorldViewModel : PanePageViewModelBase, IHelloWorldViewModel string targetUrl; string blobName; string defaultPath; + Uri repositoryUrl; Uri webUrl; [ImportingConstructor] @@ -33,22 +35,32 @@ public HelloWorldViewModel( Title = "GitHub URL"; - GoTo = ReactiveCommand.Create(this.WhenAnyValue(x => x.BlobName).Select(b => b != null)); + // Is the target URL pointing at the active repository + var isActiveRepositoryObservable = + this.WhenAnyValue(x => x.RepositoryUrl).Select(r => r is Uri targetUrl && + teamExplorerContext.ActiveRepository?.CloneUrl is UriString activeUrl && + UriString.RepositoryUrlsAreEqual(activeUrl, targetUrl.ToString())); + + GoTo = ReactiveCommand.Create( + this.WhenAnyValue(x => x.BlobName).Select(b => b != null) + .CombineLatest(isActiveRepositoryObservable, (a, b) => a && b)); GoTo.Subscribe(_ => { var localPath = teamExplorerContext.ActiveRepository?.LocalPath; contextService.TryOpenFile(localPath, Context); }); - Clone = ReactiveCommand.Create(this.WhenAnyValue(x => - x.DefaultPath).Select(d => d is string dir && !Directory.Exists(d))); + Clone = ReactiveCommand.Create( + this.WhenAnyValue(x => x.DefaultPath).Select(d => d is string dir && !Directory.Exists(d)) + .CombineLatest(isActiveRepositoryObservable, (a, b) => a && !b)); Clone.Subscribe(_ => { // await repositoryCloneService.CloneRepository(cloneUrl, repositoryDirName, targetDir); }); - Open = ReactiveCommand.Create(this.WhenAnyValue(x => - x.DefaultPath).Select(d => d is string dir && Directory.Exists(d))); + Open = ReactiveCommand.Create( + this.WhenAnyValue(x => x.DefaultPath).Select(d => d is string dir && Directory.Exists(d)) + .CombineLatest(isActiveRepositoryObservable, (a, b) => a && !b)); Open.Subscribe(_ => { var dte = serviceProvider.GetService(); @@ -68,6 +80,7 @@ public HelloWorldViewModel( this.WhenAnyValue(x => x.Context).Subscribe(c => { BlobName = c?.BlobName; + RepositoryUrl = c?.Url?.ToRepositoryUrl(); DefaultPath = repositoryCloneService.DefaultClonePath is string home && @@ -109,6 +122,12 @@ public Uri WebUrl private set { this.RaiseAndSetIfChanged(ref webUrl, value); } } + public Uri RepositoryUrl + { + get { return repositoryUrl; } + private set { this.RaiseAndSetIfChanged(ref repositoryUrl, value); } + } + public IReactiveCommand GoTo { get; } public IReactiveCommand Clone { get; } From 65f8a65d4ccdaf14b3168a4ca17882b17054b9eb Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Fri, 27 Jul 2018 14:37:28 +0100 Subject: [PATCH 5/5] Wire up basic clone functionality --- .../UI/ViewModels/HelloWorldViewModel.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs index 7f4256c..2541abf 100644 --- a/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs +++ b/src/GitHub.VisualStudio.Contrib/UI/ViewModels/HelloWorldViewModel.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Reactive.Linq; +using System.Threading.Tasks; using System.ComponentModel.Composition; using ReactiveUI; using GitHub.Services; @@ -15,6 +16,7 @@ public class HelloWorldViewModel : PanePageViewModelBase, IHelloWorldViewModel { readonly IGitHubContextService contextService; readonly ITeamExplorerContext teamExplorerContext; + readonly IRepositoryCloneService repositoryCloneService; GitHubContext context; string targetUrl; @@ -32,6 +34,7 @@ public HelloWorldViewModel( { this.contextService = contextService; this.teamExplorerContext = teamExplorerContext; + this.repositoryCloneService = repositoryCloneService; Title = "GitHub URL"; @@ -50,13 +53,9 @@ public HelloWorldViewModel( contextService.TryOpenFile(localPath, Context); }); - Clone = ReactiveCommand.Create( - this.WhenAnyValue(x => x.DefaultPath).Select(d => d is string dir && !Directory.Exists(d)) - .CombineLatest(isActiveRepositoryObservable, (a, b) => a && !b)); - Clone.Subscribe(_ => - { - // await repositoryCloneService.CloneRepository(cloneUrl, repositoryDirName, targetDir); - }); + Clone = ReactiveCommand.CreateAsyncTask( + (this).WhenAnyValue(x => x.DefaultPath).Select(d => d is string dir && !Directory.Exists(d)) + .CombineLatest(isActiveRepositoryObservable, (a, b) => a && !b), DoCloneAsync); Open = ReactiveCommand.Create( this.WhenAnyValue(x => x.DefaultPath).Select(d => d is string dir && Directory.Exists(d)) @@ -92,6 +91,15 @@ repositoryCloneService.DefaultClonePath is string home && Done = ReactiveCommand.Create(); } + async Task DoCloneAsync(object args) + { + var repositoryName = Path.GetFileName(DefaultPath); + var repositoryPath = Path.GetDirectoryName(DefaultPath); + await repositoryCloneService.CloneRepository(RepositoryUrl.ToString(), repositoryName, repositoryPath); + Open.Execute(null); + return null; + } + public string TargetUrl { get { return targetUrl; }