From 33c824d68e0dc657db8f3823fa566d99ed205644 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 17 Feb 2015 18:18:58 +0100 Subject: [PATCH 01/35] Add home section showing repo information --- .../Base/TeamExplorerGitAwareItem.cs | 177 ++++++++++++++++++ .../Base/TeamExplorerNavigationItemBase.cs | 116 +----------- .../Base/TeamExplorerSectionBase.cs | 21 ++- .../GitHub.VisualStudio.csproj | 8 + src/GitHub.VisualStudio/Services/Services.cs | 41 ++-- .../TeamExplorerHome/GitHubHomeSection.cs | 54 ++++++ .../UI/Views/GitHubHomeContent.xaml | 33 ++++ .../UI/Views/GitHubHomeContent.xaml.cs | 28 +++ 8 files changed, 354 insertions(+), 124 deletions(-) create mode 100644 src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs create mode 100644 src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs create mode 100644 src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml create mode 100644 src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs b/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs new file mode 100644 index 000000000..95beb5dfa --- /dev/null +++ b/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs @@ -0,0 +1,177 @@ +using Microsoft.TeamFoundation.Client; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using NullGuard; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GitHub.Extensions; + +namespace GitHub.VisualStudio.Base +{ + /// + /// This is a temporary base class that raises + /// events with git repo information based on + /// the solution open and close events. Once + /// TFSContext is git-aware, this can be killed + /// + public class TeamExplorerGitAwareItem : TeamExplorerItemBase + { + bool disposed = false; + + protected void Initialize() + { + // temporary hack to update navigation item by tracking the solution + SubscribeSolutionEvents(); + } + + /* Listen to solution events so we can use the solution + to locate the github repo it's on and update navigation + items accordingly. This is temporary until the + TFS api supports git repos. */ + + uint cookie = 0; + void SubscribeSolutionEvents() + { + Debug.Assert(ServiceProvider != null, "ServiceProvider must be set before subscribing to solution events"); + if (cookie > 0) + return; + + var solService = ServiceProvider.GetSolution(); + if (!ErrorHandler.Succeeded(solService.AdviseSolutionEvents(new SolutionEventListener(SolutionOpen), out cookie))) + { + Debug.Assert(false, "Unable to start listening for solution events"); + } + } + + void UnsubscribeSolutionEvents() + { + Debug.Assert(ServiceProvider != null, "ServiceProvider must be set before subscribing to solution events"); + if (cookie == 0) + return; + + var solService = ServiceProvider.GetSolution(); + if (!ErrorHandler.Succeeded(solService.UnadviseSolutionEvents(cookie))) + { + Debug.Assert(false, "Unable to stop listening for solution events"); + } + cookie = 0; + } + + void SolutionOpen() + { + var tc = new TeamContext(); + var solution = ServiceProvider.GetSolution(); + var repo = Services.GetRepoFromSolution(solution); + if (repo == null) + tc.HasTeamProject = false; + else + { + tc.TeamProjectUri = Services.GetUriFromRepository(repo); + if (tc.TeamProjectUri == null) + { + tc.TeamProjectName = null; + tc.HasTeamProject = false; + } + else + { + tc.TeamProjectName = tc.TeamProjectUri.GetUser() + "/" + tc.TeamProjectUri.GetRepo(); + tc.HasTeamProject = true; + } + } + + ContextChanged(this, new ContextChangedEventArgs(CurrentContext, tc, false, true, false)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposed) + return; + + if (disposing) + UnsubscribeSolutionEvents(); + + disposed = true; + } + + class SolutionEventListener : IVsSolutionEvents + { + Action callback; + public SolutionEventListener(Action callback) + { + this.callback = callback; + } + + public int OnAfterCloseSolution(object pUnkReserved) + { + return 0; + } + + public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) + { + return 0; + } + + public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) + { + return 0; + } + + public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) + { + if (callback != null) + callback(); + return 0; + } + + public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) + { + return 0; + } + + public int OnBeforeCloseSolution(object pUnkReserved) + { + return 0; + } + + public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) + { + return 0; + } + + public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) + { + return 0; + } + + public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) + { + return 0; + } + + public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) + { + return 0; + } + } + + class TeamContext : ITeamFoundationContext + { + public bool HasCollection { get; set; } + public bool HasTeam { get; set; } + public bool HasTeamProject { get; set; } + public Guid TeamId { get; set; } + public string TeamName { get; set; } + public TfsTeamProjectCollection TeamProjectCollection { get; set; } + [AllowNull] + public string TeamProjectName { get; set; } + [AllowNull] + public Uri TeamProjectUri { get; set; } + } + } +} diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs index 0e9a00b6c..ba2510195 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs @@ -16,22 +16,19 @@ using GitHub.Services; namespace GitHub.VisualStudio { - public class TeamExplorerNavigationItemBase : TeamExplorerItemBase, ITeamExplorerNavigationItem2, INotifyPropertySource + public class TeamExplorerNavigationItemBase : TeamExplorerGitAwareItem, ITeamExplorerNavigationItem2, INotifyPropertySource { [AllowNull] public ISimpleApiClient SimpleApiClient { get; private set; } readonly ISimpleApiClientFactory apiFactory; - bool disposed = false; public TeamExplorerNavigationItemBase(IServiceProvider serviceProvider, ISimpleApiClientFactory apiFactory) : base() { this.ServiceProvider = serviceProvider; this.apiFactory = apiFactory; - - // temporary hack to update navigation item by tracking the solution - SubscribeSolutionEvents(); + Initialize(); } int argbColor; @@ -101,116 +98,11 @@ namespace GitHub.VisualStudio b.OpenUrl(wiki); } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposed) - return; - - if (disposing) - UnsubscribeSolutionEvents(); - - disposed = true; - - } - - /* Listen to solution events so we can use the solution - to locate the github repo it's on and update navigation - items accordingly. This is temporary until the - TFS api supports git repos. */ - - uint cookie = 0; - void SubscribeSolutionEvents() - { - Debug.Assert(ServiceProvider != null, "ServiceProvider must be set before subscribing to solution events"); - if (cookie > 0) - return; - - var solService = ServiceProvider.GetSolution(); - if (!ErrorHandler.Succeeded(solService.AdviseSolutionEvents(new SolutionEventListener(SolutionOpen), out cookie))) { - Debug.Assert(false, "Unable to start listening for solution events"); - } - } - - void UnsubscribeSolutionEvents() - { - Debug.Assert(ServiceProvider != null, "ServiceProvider must be set before subscribing to solution events"); - if (cookie == 0) - return; - - var solService = ServiceProvider.GetSolution(); - if (!ErrorHandler.Succeeded(solService.UnadviseSolutionEvents(cookie))) { - Debug.Assert(false, "Unable to stop listening for solution events"); - } - cookie = 0; - } - - void SolutionOpen() + protected override void ContextChanged(object sender, ContextChangedEventArgs e) { SimpleApiClient = null; - ContextChanged(this, new ContextChangedEventArgs(CurrentContext, CurrentContext, false, true, false)); - } - - class SolutionEventListener : IVsSolutionEvents - { - Action callback; - public SolutionEventListener(Action callback) - { - this.callback = callback; - } - - public int OnAfterCloseSolution(object pUnkReserved) - { - return 0; - } - - public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) - { - return 0; - } - - public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) - { - return 0; - } - - public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) - { - if (callback != null) - callback(); - return 0; - } - - public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) - { - return 0; - } - - public int OnBeforeCloseSolution(object pUnkReserved) - { - return 0; - } - - public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) - { - return 0; - } - - public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) - { - return 0; - } - - public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) - { - return 0; - } - - public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) - { - return 0; - } + base.ContextChanged(sender, e); } } } diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs index dde83c8e7..b9bdadd86 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs @@ -2,10 +2,11 @@ using Microsoft.TeamFoundation.Controls; using NullGuard; using System; +using Microsoft.TeamFoundation.Client; namespace GitHub.VisualStudio { - public class TeamExplorerSectionBase : TeamExplorerBase, ITeamExplorerSection, INotifyPropertySource + public class TeamExplorerSectionBase : TeamExplorerGitAwareItem, ITeamExplorerSection, INotifyPropertySource { bool isBusy; public bool IsBusy @@ -21,12 +22,16 @@ namespace GitHub.VisualStudio set { isExpanded = value; this.RaisePropertyChange(); } } + // When this class goes back to inheriting from TeamExplorerBase, + // this property should be restored + /* bool isVisible; public bool IsVisible { get { return isVisible; } set { isVisible = value; this.RaisePropertyChange(); } } + */ object sectionContent; [AllowNull] @@ -48,6 +53,7 @@ namespace GitHub.VisualStudio { } + [return: AllowNull] public virtual object GetExtensibilityService(Type serviceType) { return null; @@ -56,6 +62,7 @@ namespace GitHub.VisualStudio public virtual void Initialize(object sender, SectionInitializeEventArgs e) { ServiceProvider = e.ServiceProvider; + Initialize(); } public virtual void Loaded(object sender, SectionLoadedEventArgs e) @@ -69,5 +76,17 @@ namespace GitHub.VisualStudio public virtual void SaveContext(object sender, SectionSaveContextEventArgs e) { } + + protected override void ContextChanged(object sender, ContextChangedEventArgs e) + { + if (e.TeamProjectChanged) + { + if (e.NewContext != null && e.NewContext.HasTeamProject) + IsVisible = true; + else + IsVisible = false; + } + base.ContextChanged(sender, e); + } } } diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 69371f6a6..809f18b3b 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -182,6 +182,7 @@ Properties\SolutionInfo.cs + @@ -205,6 +206,7 @@ + @@ -217,6 +219,9 @@ CreateIssueDialog.xaml + + GitHubHomeContent.xaml + LoginCommandDialog.xaml @@ -279,6 +284,9 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/GitHub.VisualStudio/Services/Services.cs b/src/GitHub.VisualStudio/Services/Services.cs index b3c913caf..87da6bc24 100644 --- a/src/GitHub.VisualStudio/Services/Services.cs +++ b/src/GitHub.VisualStudio/Services/Services.cs @@ -47,19 +47,38 @@ namespace GitHub.VisualStudio return null; using (var repo = new Repository(repoPath)) { - var remote = repo.Network.Remotes.FirstOrDefault(x => x.Name.Equals("origin", StringComparison.Ordinal)); - if (remote == null) - return null; - Uri uri; - var url = remote.Url; - // fixup ssh urls - if (url.StartsWith("git@github.com:", StringComparison.Ordinal)) - url = url.Replace("git@github.com:", "https://github.com/"); - if (!Uri.TryCreate(url, UriKind.Absolute, out uri)) - return null; - return uri; + return GetUriFromRepository(repo); } } + [return: AllowNull] + public static Repository GetRepoFromSolution(IVsSolution solution) + { + string solutionDir, solutionFile, userFile; + if (!ErrorHandler.Succeeded(solution.GetSolutionInfo(out solutionDir, out solutionFile, out userFile))) + return null; + if (solutionDir == null) + return null; + var repoPath = Repository.Discover(solutionDir); + if (repoPath == null) + return null; + return new Repository(repoPath); + } + + [return: AllowNull] + public static Uri GetUriFromRepository(Repository repo) + { + var remote = repo.Network.Remotes.FirstOrDefault(x => x.Name.Equals("origin", StringComparison.Ordinal)); + if (remote == null) + return null; + Uri uri; + var url = remote.Url; + // fixup ssh urls + if (url.StartsWith("git@github.com:", StringComparison.Ordinal)) + url = url.Replace("git@github.com:", "https://github.com/"); + if (!Uri.TryCreate(url, UriKind.Absolute, out uri)) + return null; + return uri; + } } } diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs b/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs new file mode 100644 index 000000000..864c22719 --- /dev/null +++ b/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs @@ -0,0 +1,54 @@ +using GitHub.VisualStudio.UI.Views; +using Microsoft.TeamFoundation.Controls; +using NullGuard; +using System; +using Microsoft.TeamFoundation.Client; + +namespace GitHub.VisualStudio +{ + [TeamExplorerSection(GitHubHomeSectionId, TeamExplorerPageIds.Home, 10)] + public class GitHubHomeSection : TeamExplorerSectionBase + { + public const string GitHubHomeSectionId = "72008232-2104-4FA0-A189-61B0C6F91198"; + + protected GitHubHomeContent View + { + get { return this.SectionContent as GitHubHomeContent; } + set { this.SectionContent = value; } + } + + string repoName = String.Empty; + public string RepoName + { + get { return "Repository: " + repoName; } + set { repoName = value; this.RaisePropertyChange(); } + } + + string repoUrl = String.Empty; + public string RepoUrl + { + get { return "Remote: " + repoUrl; } + set { repoUrl = value; this.RaisePropertyChange(); } + } + + public GitHubHomeSection() + { + Title = "GitHub"; + // only when the repo is hosted on github.com + IsVisible = false; + IsExpanded = true; + View = new GitHubHomeContent(); + View.ViewModel = this; + } + + protected override void ContextChanged(object sender, ContextChangedEventArgs e) + { + base.ContextChanged(sender, e); + if (e.NewContext != null && e.NewContext.HasTeamProject) + { + RepoName = e.NewContext.TeamProjectName; + RepoUrl = e.NewContext.TeamProjectUri.ToString(); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml new file mode 100644 index 000000000..88b5da95d --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs new file mode 100644 index 000000000..39250d85c --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs @@ -0,0 +1,28 @@ +using System.Windows; +using System.Windows.Controls; + +namespace GitHub.VisualStudio.UI.Views +{ + /// + /// Interaction logic for GitHubHomeSection.xaml + /// + public partial class GitHubHomeContent : UserControl + { + public GitHubHomeContent() + { + InitializeComponent(); + } + + public GitHubHomeSection ViewModel + { + get { return (GitHubHomeSection)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register( + "ViewModel", + typeof(GitHubHomeSection), + typeof(GitHubHomeContent)); + } +} From f188f0da482aa6f0df616a2e71a84ed0057378f2 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 19 Feb 2015 16:36:15 +0100 Subject: [PATCH 02/35] Async fixes --- src/GitHub.Exports/Services/WikiProbe.cs | 2 +- src/GitHub.Extensions/TaskExtensions.cs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/GitHub.Exports/Services/WikiProbe.cs b/src/GitHub.Exports/Services/WikiProbe.cs index 951c11d35..619ec12d5 100644 --- a/src/GitHub.Exports/Services/WikiProbe.cs +++ b/src/GitHub.Exports/Services/WikiProbe.cs @@ -40,7 +40,7 @@ namespace GitHub.Services var ret = await httpClient .Send(request, CancellationToken.None) - .Catch(ex => null); + .Catch(); if (ret == null) return WikiProbeResult.Failed; diff --git a/src/GitHub.Extensions/TaskExtensions.cs b/src/GitHub.Extensions/TaskExtensions.cs index cb1d4ccab..9cfffa4ae 100644 --- a/src/GitHub.Extensions/TaskExtensions.cs +++ b/src/GitHub.Extensions/TaskExtensions.cs @@ -10,9 +10,18 @@ namespace GitHub.Extensions public static class TaskExtensions { [return: AllowNull] - public static async Task Catch(this Task source, Func handler) + public static async Task Catch(this Task source, Func handler = null) { - return await source.ContinueWith(t => handler(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + try + { + return await source; + } + catch (Exception ex) + { + if (handler != null) + return handler(ex); + return default(T); + } } } } From b3fa798524ff239e1a0aa41bbd01c565df931675 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 21:48:46 +0100 Subject: [PATCH 03/35] Add an empty wpf app for design-time resources VS can't seem to handle loading resources at design-time from referenced libraries without an actual WPF app with an App.xaml in the solution. All other hacks failed to bleh, just stuff one in. --- src/DesignTimeStyleHelper/App.config | 6 + src/DesignTimeStyleHelper/App.xaml | 14 +++ src/DesignTimeStyleHelper/App.xaml.cs | 17 +++ .../DesignTimeStyleHelper.csproj | 117 ++++++++++++++++++ src/DesignTimeStyleHelper/MainWindow.xaml | 12 ++ src/DesignTimeStyleHelper/MainWindow.xaml.cs | 28 +++++ .../Properties/AssemblyInfo.cs | 55 ++++++++ .../Properties/Resources.Designer.cs | 71 +++++++++++ .../Properties/Resources.resx | 117 ++++++++++++++++++ .../Properties/Settings.Designer.cs | 30 +++++ .../Properties/Settings.settings | 7 ++ 11 files changed, 474 insertions(+) create mode 100644 src/DesignTimeStyleHelper/App.config create mode 100644 src/DesignTimeStyleHelper/App.xaml create mode 100644 src/DesignTimeStyleHelper/App.xaml.cs create mode 100644 src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj create mode 100644 src/DesignTimeStyleHelper/MainWindow.xaml create mode 100644 src/DesignTimeStyleHelper/MainWindow.xaml.cs create mode 100644 src/DesignTimeStyleHelper/Properties/AssemblyInfo.cs create mode 100644 src/DesignTimeStyleHelper/Properties/Resources.Designer.cs create mode 100644 src/DesignTimeStyleHelper/Properties/Resources.resx create mode 100644 src/DesignTimeStyleHelper/Properties/Settings.Designer.cs create mode 100644 src/DesignTimeStyleHelper/Properties/Settings.settings diff --git a/src/DesignTimeStyleHelper/App.config b/src/DesignTimeStyleHelper/App.config new file mode 100644 index 000000000..8e1564635 --- /dev/null +++ b/src/DesignTimeStyleHelper/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/App.xaml b/src/DesignTimeStyleHelper/App.xaml new file mode 100644 index 000000000..166aee795 --- /dev/null +++ b/src/DesignTimeStyleHelper/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/src/DesignTimeStyleHelper/App.xaml.cs b/src/DesignTimeStyleHelper/App.xaml.cs new file mode 100644 index 000000000..41d83b6db --- /dev/null +++ b/src/DesignTimeStyleHelper/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace DesignTimeStyleHelper +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj b/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj new file mode 100644 index 000000000..4b92c082e --- /dev/null +++ b/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj @@ -0,0 +1,117 @@ + + + + + Debug + AnyCPU + {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0} + WinExe + Properties + DesignTimeStyleHelper + DesignTimeStyleHelper + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + {158b05e8-fdbc-4d71-b871-c96e28d5adf5} + GitHub.UI.Reactive + + + {346384dd-2445-4a28-af22-b45f3957bd89} + GitHub.UI + + + {11569514-5ae5-4b5b-92a2-f10b0967de5f} + GitHub.VisualStudio + + + + + \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml b/src/DesignTimeStyleHelper/MainWindow.xaml new file mode 100644 index 000000000..710828d75 --- /dev/null +++ b/src/DesignTimeStyleHelper/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml.cs b/src/DesignTimeStyleHelper/MainWindow.xaml.cs new file mode 100644 index 000000000..74f0b4659 --- /dev/null +++ b/src/DesignTimeStyleHelper/MainWindow.xaml.cs @@ -0,0 +1,28 @@ +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.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace DesignTimeStyleHelper +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/src/DesignTimeStyleHelper/Properties/AssemblyInfo.cs b/src/DesignTimeStyleHelper/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ccdfc3b4f --- /dev/null +++ b/src/DesignTimeStyleHelper/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DesignTimeStyleHelper")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DesignTimeStyleHelper")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/DesignTimeStyleHelper/Properties/Resources.Designer.cs b/src/DesignTimeStyleHelper/Properties/Resources.Designer.cs new file mode 100644 index 000000000..7d10cee3a --- /dev/null +++ b/src/DesignTimeStyleHelper/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DesignTimeStyleHelper.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DesignTimeStyleHelper.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/src/DesignTimeStyleHelper/Properties/Resources.resx b/src/DesignTimeStyleHelper/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/src/DesignTimeStyleHelper/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/Properties/Settings.Designer.cs b/src/DesignTimeStyleHelper/Properties/Settings.Designer.cs new file mode 100644 index 000000000..a3392b1a2 --- /dev/null +++ b/src/DesignTimeStyleHelper/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DesignTimeStyleHelper.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/src/DesignTimeStyleHelper/Properties/Settings.settings b/src/DesignTimeStyleHelper/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/src/DesignTimeStyleHelper/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From d97c92633ff5e8786e40fefc54fa1e364e14f899 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 21:52:25 +0100 Subject: [PATCH 04/35] Add separate lib to hold mef interfaces with reactive dependencies --- .../ITwoFactorChallengeHandler.cs | 10 + .../Caches/IHostCache.cs | 21 ++ .../GitHub.Exports.Reactive.csproj | 104 ++++++ .../Models/IAccount.cs | 38 +++ .../Models/IRepositoryHost.cs | 29 ++ .../Properties/AssemblyInfo.cs | 6 + .../Services/IApiClient.cs | 33 ++ .../Services/IAvatarProvider.cs | 21 ++ .../UI/ICreateRepoViewModel.cs | 35 ++ .../Validation/ReactivePropertyValidator.cs | 309 ++++++++++++++++++ src/GitHub.Exports.Reactive/packages.config | 13 + 11 files changed, 619 insertions(+) create mode 100644 src/GitHub.Exports.Reactive/Authentication/ITwoFactorChallengeHandler.cs create mode 100644 src/GitHub.Exports.Reactive/Caches/IHostCache.cs create mode 100644 src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj create mode 100644 src/GitHub.Exports.Reactive/Models/IAccount.cs create mode 100644 src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs create mode 100644 src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs create mode 100644 src/GitHub.Exports.Reactive/Services/IApiClient.cs create mode 100644 src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs create mode 100644 src/GitHub.Exports.Reactive/UI/ICreateRepoViewModel.cs create mode 100644 src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs create mode 100644 src/GitHub.Exports.Reactive/packages.config diff --git a/src/GitHub.Exports.Reactive/Authentication/ITwoFactorChallengeHandler.cs b/src/GitHub.Exports.Reactive/Authentication/ITwoFactorChallengeHandler.cs new file mode 100644 index 000000000..3d8f3c0c5 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Authentication/ITwoFactorChallengeHandler.cs @@ -0,0 +1,10 @@ +using System; +using Octokit; + +namespace GitHub.Authentication +{ + public interface ITwoFactorChallengeHandler + { + IObservable HandleTwoFactorException(TwoFactorRequiredException exception); + } +} diff --git a/src/GitHub.Exports.Reactive/Caches/IHostCache.cs b/src/GitHub.Exports.Reactive/Caches/IHostCache.cs new file mode 100644 index 000000000..2b6eed792 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Caches/IHostCache.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using Octokit; + +namespace GitHub +{ + /// + /// Per host cache data. + /// + public interface IHostCache : IDisposable + { + IObservable GetUser(); + IObservable InsertUser(User user); + IObservable> GetAllOrganizations(); + IObservable InsertOrganization(Organization organization); + IObservable InvalidateOrganization(Organization organization); + IObservable InvalidateOrganization(IAccount organization); + IObservable InvalidateAll(); + } +} diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj new file mode 100644 index 000000000..a16273184 --- /dev/null +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1} + Library + Properties + GitHub.Exports.Reactive + GitHub.Exports.Reactive + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Octokit.0.6.2\lib\net45\Octokit.dll + + + + ..\..\packages\reactiveui-core.6.3.1\lib\Net45\ReactiveUI.dll + + + ..\..\packages\Splat.1.6.0\lib\Net45\Splat.dll + + + + + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + + + ..\..\packages\Rx-XAML.2.2.5\lib\net45\System.Reactive.Windows.Threading.dll + + + + + + + + + + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + {9aea02db-02b5-409c-b0ca-115d05331a6b} + GitHub.Exports + + + {6afe2e2d-6db0-4430-a2ea-f5f5388d2f78} + GitHub.Extensions + + + + + + \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/Models/IAccount.cs b/src/GitHub.Exports.Reactive/Models/IAccount.cs new file mode 100644 index 000000000..5bd93eb82 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/IAccount.cs @@ -0,0 +1,38 @@ +using GitHub.Models; +using Octokit; +using ReactiveUI; + +namespace GitHub +{ + public interface IAccount + { + string Email { get; } + int Id { get; } + bool IsEnterprise { get; } + bool IsGitHub { get; } + bool IsLocal { get; } + bool IsOnFreePlan { get; } + bool HasMaximumPrivateRepositories { get; } + bool IsUser { get; } + /// + /// True if the user is an admin on the host (GitHub or Enterprise). + /// + /// + /// Do not confuse this with "IsStaff". This is true if the user is an admin + /// on the site. IsStaff is true if that site is github.com. + /// + bool IsSiteAdmin { get; } + /// + /// Returns true if the user is a member of the GitHub staff. + /// + bool IsGitHubStaff { get; } + IRepositoryHost Host { get; } + string Login { get; } + string Name { get; } + int OwnedPrivateRepos { get; } + long PrivateReposInPlan { get; } + + void Update(User ghUser); + void Update(Organization org); + } +} diff --git a/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs b/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs new file mode 100644 index 000000000..53c7863ea --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs @@ -0,0 +1,29 @@ +using System; +using System.Reactive; +using ReactiveUI; +using GitHub.Authentication; + +namespace GitHub.Models +{ + public interface IRepositoryHost + { + HostAddress Address { get; } + IApiClient ApiClient { get; } + IHostCache Cache { get; } + bool IsGitHub { get; } + bool IsLoggedIn { get; } + bool IsLoggingIn { get; } + bool IsEnterprise { get; } + bool IsLocal { get; } + ReactiveList Organizations { get; } + ReactiveList Accounts { get; } + string Title { get; } + IAccount User { get; } + + IObservable LogIn(string usernameOrEmail, string password); + IObservable LogInFromCache(); + IObservable LogOut(); + IObservable Refresh(); + IObservable Refresh(Func> refreshTrackedRepositoriesFunc); + } +} diff --git a/src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs b/src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..a969fcf41 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("GitHub.Exports.Reactive")] +[assembly: AssemblyDescription("GitHub interfaces for mef exports with reactive dependencies")] +[assembly: Guid("e4ed0537-d1d9-44b6-9212-3096d7c3f7a1")] diff --git a/src/GitHub.Exports.Reactive/Services/IApiClient.cs b/src/GitHub.Exports.Reactive/Services/IApiClient.cs new file mode 100644 index 000000000..68a6dde6a --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IApiClient.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using GitHub.Authentication; +using Octokit; + +namespace GitHub +{ + public interface IApiClient + { + HostAddress HostAddress { get; } + IObservable AddSshKey(SshKey newKey); + IObservable CreateRepository(Repository repo, string login, bool isUser); + IObservable GetSshKeys(); + IObservable GetUser(); + IObservable GetAllUsersForAllOrganizations(); + IObservable GetOrganization(string login); + IObservable GetOrganizations(); + IObservable GetMembersOfOrganization(string organizationName); + IObservable GetRepository(string owner, string name); + IObservable> GetUserRepositories(int currentUserId); + IObservable GetCurrentUserRepositoriesStreamed(); + IObservable GetOrganizationRepositoriesStreamed(string login); + IObservable GetOrCreateApplicationAuthenticationCode( + Func> twoFactorChallengeHander = null, + bool useOldScopes = false); + IObservable GetOrCreateApplicationAuthenticationCode( + string authenticationCode, + bool useOldScopes = false); + IObservable> GetEmails(); + ITwoFactorChallengeHandler TwoFactorChallengeHandler { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs b/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs new file mode 100644 index 000000000..607719c68 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs @@ -0,0 +1,21 @@ +using System; +using System.Reactive; +using System.Windows.Media.Imaging; +using Octokit; + +namespace GitHub.Services +{ + public interface IHostAvatarProvider + { + IAvatarProvider Get(string gitHubBaseUrl); + } + + public interface IAvatarProvider + { + BitmapImage DefaultUserBitmapImage { get; } + BitmapImage DefaultOrgBitmapImage { get; } + IObservable GetAvatar(Account apiAccount); + IObservable InvalidateAvatar(Account apiAccount); + IObservable GetAvatar(string email); + } +} diff --git a/src/GitHub.Exports.Reactive/UI/ICreateRepoViewModel.cs b/src/GitHub.Exports.Reactive/UI/ICreateRepoViewModel.cs new file mode 100644 index 000000000..bd1f3c90b --- /dev/null +++ b/src/GitHub.Exports.Reactive/UI/ICreateRepoViewModel.cs @@ -0,0 +1,35 @@ +using GitHub.Validation; +using ReactiveUI; +using System; +using System.Reactive; +using System.Windows.Input; + +namespace GitHub.UI +{ + public interface ICreateRepoViewModel + { + string RepositoryName { get; } + string SafeRepositoryName { get; } + bool ShowRepositoryNameWarning { get; } + string RepositoryNameWarningText { get; } + ReactivePropertyValidator RepositoryNameValidator { get; } + + string Description { get; set; } + + ReactiveList Accounts { get; } + IAccount SelectedAccount { get; } + + bool KeepPrivate { get; set; } + bool CanKeepPrivate { get; } + bool ShowUpgradeToMicroPlanWarning { get; } + bool ShowUpgradePlanWarning { get; } + + bool IsPublishing { get; } + ReactiveCommand CreateRepository { get; } + ReactiveCommand UpgradeAccountPlan { get; } + ReactiveCommand Reset { get; } + + ICommand OkCmd { get; } + ICommand CancelCmd { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs b/src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs new file mode 100644 index 000000000..7f4aca332 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reactive.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using GitHub.Extensions; +using ReactiveUI; + +namespace GitHub.Validation +{ + public class ReactivePropertyValidationResult + { + public bool IsValid { get; private set; } + public ValidationStatus Status { get; private set; } + public string Message { get; private set; } + + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "It is immutable")] + public static readonly ReactivePropertyValidationResult Success = new ReactivePropertyValidationResult(ValidationStatus.Valid); + + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "It is immutable")] + public static readonly ReactivePropertyValidationResult Unvalidated = new ReactivePropertyValidationResult(); + + public ReactivePropertyValidationResult() + : this(ValidationStatus.Unvalidated, "") + { + } + + public ReactivePropertyValidationResult(ValidationStatus validationStatus) + : this(validationStatus, "") + { + } + + public ReactivePropertyValidationResult(ValidationStatus validationStatus, string message) + { + Status = validationStatus; + IsValid = validationStatus != ValidationStatus.Invalid; + Message = message; + } + } + + public enum ValidationStatus + { + Unvalidated = 0, + Invalid = 1, + Valid = 2, + } + + public abstract class ReactivePropertyValidator : ReactiveObject + { + public static ReactivePropertyValidator For(TObj This, Expression> property) + { + return new ReactivePropertyValidator(This, property); + } + + public abstract ReactivePropertyValidationResult ValidationResult { get; protected set; } + + public abstract bool IsValidating { get; } + + protected ReactivePropertyValidator() + { + } + + public abstract Task ExecuteAsync(); + + public abstract Task ResetAsync(); + } + + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + public class ReactivePropertyValidator : ReactivePropertyValidator + { + readonly ReactiveCommand validateCommand; + ReactivePropertyValidationResult validationResult; + + public override ReactivePropertyValidationResult ValidationResult + { + get { return validationResult; } + protected set { this.RaiseAndSetIfChanged(ref validationResult, value); } + } + + public override Task ExecuteAsync() + { + return validateCommand.ExecuteAsyncTask(new ValidationParameter()); + } + + public override Task ResetAsync() + { + return validateCommand.ExecuteAsyncTask(new ValidationParameter { RequiresReset = true }); + } + + readonly List>> validators = + new List>>(); + + readonly ObservableAsPropertyHelper isValidating; + public override bool IsValidating + { + get { return isValidating.Value; } + } + + public ReactivePropertyValidator(IObservable signal) + { + validateCommand = ReactiveCommand.CreateAsyncObservable(param => + { + var validationParams = (ValidationParameter)param; + + if (validationParams.RequiresReset) + { + return Observable.Return(ReactivePropertyValidationResult.Unvalidated); + } + + TProp value = validationParams.PropertyValue; + + var currentValidators = validators.ToList(); + + // HEAR YE, HEAR YE + + // This .ToList() is here to ignore changes to the validator collection, + // and thus avoid fantastically vague exceptions about + // "Collection was modified, enumeration operation may not execute" + // bubbling up to tear the application down + + // Thus, the collection will be correct when the command executes, + // which should be fine until we need to do more complex validation + + if (!currentValidators.Any()) + return Observable.Return(ReactivePropertyValidationResult.Unvalidated); + + return currentValidators.ToObservable() + .SelectMany(v => v(value)) + .FirstOrDefaultAsync(x => x.Status == ValidationStatus.Invalid) + .Select(x => x == null ? ReactivePropertyValidationResult.Success : x); + }); + + isValidating = validateCommand.IsExecuting.ToProperty(this, x => x.IsValidating); + + validateCommand.Subscribe(x => ValidationResult = x); + signal.Subscribe(x => validateCommand.Execute(new ValidationParameter { PropertyValue = x, RequiresReset = false })); + } + + public ReactivePropertyValidator IfTrue(Func predicate, string errorMessage) + { + return Add(predicate, errorMessage); + } + + public ReactivePropertyValidator IfFalse(Func predicate, string errorMessage) + { + return Add(x => !predicate(x), errorMessage); + } + + ReactivePropertyValidator Add(Func predicate, string errorMessage) + { + return Add(x => predicate(x) ? errorMessage : null); + } + + public ReactivePropertyValidator Add(Func predicateWithMessage) + { + validators.Add(value => Observable.Defer(() => Observable.Return(Validate(value, predicateWithMessage)))); + return this; + } + + public ReactivePropertyValidator IfTrueAsync(Func> predicate, string errorMessage) + { + AddAsync(x => predicate(x).Select(result => result ? errorMessage : null)); + return this; + } + + public ReactivePropertyValidator IfFalseAsync(Func> predicate, string errorMessage) + { + AddAsync(x => predicate(x).Select(result => result ? null : errorMessage)); + return this; + } + + public ReactivePropertyValidator AddAsync(Func> predicateWithMessage) + { + validators.Add(value => Observable.Defer(() => + { + return predicateWithMessage(value) + .Select(result => String.IsNullOrEmpty(result) + ? ReactivePropertyValidationResult.Success + : new ReactivePropertyValidationResult(ValidationStatus.Invalid, result)); + + })); + + return this; + } + + static ReactivePropertyValidationResult Validate(TProp value, Func predicateWithMessage) + { + var result = predicateWithMessage(value); + + if (String.IsNullOrEmpty(result)) + return ReactivePropertyValidationResult.Success; + + return new ReactivePropertyValidationResult(ValidationStatus.Invalid, result); + } + + class ValidationParameter + { + public TProp PropertyValue { get; set; } + public bool RequiresReset { get; set; } + } + } + + public class ReactivePropertyValidator : ReactivePropertyValidator + { + protected ReactivePropertyValidator() + : base(Observable.Empty()) + { + } + + public ReactivePropertyValidator(TObj This, Expression> property) + : base(This.WhenAny(property, x => x.Value)) { } + } + + public static class ReactivePropertyValidatorExtensions + { + public static ReactivePropertyValidator IfMatch(this ReactivePropertyValidator This, string pattern, string errorMessage) + { + var regex = new Regex(pattern); + + return This.IfTrue(regex.IsMatch, errorMessage); + } + + public static ReactivePropertyValidator IfNotMatch(this ReactivePropertyValidator This, string pattern, string errorMessage) + { + var regex = new Regex(pattern); + + return This.IfFalse(regex.IsMatch, errorMessage); + } + + public static ReactivePropertyValidator IfNullOrEmpty(this ReactivePropertyValidator This, string errorMessage) + { + return This.IfTrue(String.IsNullOrEmpty, errorMessage); + } + + public static ReactivePropertyValidator IfNotUri(this ReactivePropertyValidator This, string errorMessage) + { + return This.IfFalse(s => + { + Uri uri; + return Uri.TryCreate(s, UriKind.Absolute, out uri); + }, errorMessage); + } + + public static ReactivePropertyValidator IfSameAsHost(this ReactivePropertyValidator This, Uri compareToHost, string errorMessage) + { + return This.IfTrue(s => + { + Uri uri; + var isUri = Uri.TryCreate(s, UriKind.Absolute, out uri); + return isUri && uri.IsSameHost(compareToHost); + + }, errorMessage); + } + + public static ReactivePropertyValidator IfContainsInvalidPathChars(this ReactivePropertyValidator This, string errorMessage) + { + return This.IfTrue(str => + { + // easiest check to make + if (str.ContainsAny(Path.GetInvalidPathChars())) + { + return true; + } + + string driveLetter; + + try + { + // if for whatever reason you don't have an absolute path + // hopefully you've remembered to use `IfPathNotRooted` + // in your validator + driveLetter = Path.GetPathRoot(str); + } + catch (ArgumentException) + { + // Path.GetPathRoot does some fun things + // around legal combinations of characters that we miss + // by simply checking against an array of legal characters + return true; + } + + if (driveLetter == null) + { + return false; + } + + // lastly, check each directory name doesn't contain + // any invalid filename characters + var foldersInPath = str.Substring(driveLetter.Length); + return foldersInPath.Split(new[] { '\\', '/' }, StringSplitOptions.None) + .Any(x => x.ContainsAny(Path.GetInvalidFileNameChars())); + }, errorMessage); + } + + public static ReactivePropertyValidator IfPathNotRooted(this ReactivePropertyValidator This, string errorMessage) + { + return This.IfFalse(Path.IsPathRooted, errorMessage); + } + + public static ReactivePropertyValidator IfUncPath(this ReactivePropertyValidator This, string errorMessage) + { + return This.IfTrue(str => str.StartsWith(@"\\", StringComparison.Ordinal), errorMessage); + } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/packages.config b/src/GitHub.Exports.Reactive/packages.config new file mode 100644 index 000000000..b806e6dd8 --- /dev/null +++ b/src/GitHub.Exports.Reactive/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file From 97b9bd017b48598f95d928da2c9e1598a297d9b0 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 21:54:04 +0100 Subject: [PATCH 05/35] Shared resource dictionaries --- src/GitHub.UI.Reactive/SharedDictionary.xaml | 6 +++ .../Helpers/SharedDictionaryManager.cs | 41 +++++++++++++++++++ src/GitHub.UI/SharedDictionary.xaml | 9 ++++ src/GitHub.VisualStudio/SharedDictionary.xaml | 3 ++ 4 files changed, 59 insertions(+) create mode 100644 src/GitHub.UI.Reactive/SharedDictionary.xaml create mode 100644 src/GitHub.UI/Helpers/SharedDictionaryManager.cs create mode 100644 src/GitHub.UI/SharedDictionary.xaml create mode 100644 src/GitHub.VisualStudio/SharedDictionary.xaml diff --git a/src/GitHub.UI.Reactive/SharedDictionary.xaml b/src/GitHub.UI.Reactive/SharedDictionary.xaml new file mode 100644 index 000000000..e679e9299 --- /dev/null +++ b/src/GitHub.UI.Reactive/SharedDictionary.xaml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/GitHub.UI/Helpers/SharedDictionaryManager.cs b/src/GitHub.UI/Helpers/SharedDictionaryManager.cs new file mode 100644 index 000000000..16a09abb6 --- /dev/null +++ b/src/GitHub.UI/Helpers/SharedDictionaryManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace GitHub.UI.Helpers +{ + public static class SharedDictionaryManager + { + static ResourceDictionary mergedDictionaries = new ResourceDictionary(); + static HashSet dictionaries = new HashSet(); + + public static ResourceDictionary Load(string assemblyname) + { + if (!dictionaries.Contains(assemblyname)) + { + Uri loc; + //if (System.Reflection.Assembly.GetCallingAssembly().GetName().Name == assemblyname) + // loc = new Uri("/SharedDictionary.xaml", UriKind.Relative); + //else + loc = new Uri(string.Format(CultureInfo.InvariantCulture, "/{0};component/SharedDictionary.xaml", assemblyname), UriKind.Relative); + dictionaries.Add(assemblyname); + var dic = (ResourceDictionary)Application.LoadComponent(loc); + mergedDictionaries.MergedDictionaries.Add(dic); + } + return mergedDictionaries; + } + + public static ResourceDictionary SharedDictionary + { + get + { + return mergedDictionaries; + } + } + } +} diff --git a/src/GitHub.UI/SharedDictionary.xaml b/src/GitHub.UI/SharedDictionary.xaml new file mode 100644 index 000000000..d4bd2f910 --- /dev/null +++ b/src/GitHub.UI/SharedDictionary.xaml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/SharedDictionary.xaml b/src/GitHub.VisualStudio/SharedDictionary.xaml new file mode 100644 index 000000000..a92d1519a --- /dev/null +++ b/src/GitHub.VisualStudio/SharedDictionary.xaml @@ -0,0 +1,3 @@ + + \ No newline at end of file From ff6a2f4bbaba80a6df4f070191e960263ae27866 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 21:56:06 +0100 Subject: [PATCH 06/35] Add VS logger --- src/GitHub.VisualStudio/Services/Logger.cs | 99 ++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/GitHub.VisualStudio/Services/Logger.cs diff --git a/src/GitHub.VisualStudio/Services/Logger.cs b/src/GitHub.VisualStudio/Services/Logger.cs new file mode 100644 index 000000000..2e803781c --- /dev/null +++ b/src/GitHub.VisualStudio/Services/Logger.cs @@ -0,0 +1,99 @@ +using EnvDTE; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.VisualStudio +{ + public class VSTraceListener : TraceListener + { + public override void Write(string message) + { + VsOutputLogger.Write(message); + } + + public override void WriteLine(string message) + { + VsOutputLogger.WriteLine(message); + } + } + + + public static class VsOutputLogger + { + static Lazy> logger = new Lazy>(() => GetWindow().OutputString); + + static Action Logger + { + get { return logger.Value; } + } + + static VsOutputLogger() + { + Debug.Listeners.Add(new VSTraceListener()); + } + + public static void SetLogger(Action log) + { + logger = new Lazy>(() => log); + } + + public static void Write(string format, params object[] args) + { + var message = string.Format(CultureInfo.CurrentCulture, format, args); + Write(message); + } + + public static void Write(string message) + { + Logger(message); + } + + public static void WriteLine(string message) + { + Logger(message + Environment.NewLine); + } + + static OutputWindowPane GetWindow() + { + var dte = Services.Dte2; + return dte.ToolWindows.OutputWindow.ActivePane; + } + + public static void LogToGeneralOutput(string msg) + { + Guid generalPaneGuid = VSConstants.GUID_OutWindowGeneralPane; // P.S. There's also the GUID_OutWindowDebugPane available. + IVsOutputWindowPane generalPane; + + int hr = Services.OutputWindow.GetPane(ref generalPaneGuid, out generalPane); + if (ErrorHandler.Failed(hr)) + { + hr = Services.OutputWindow.CreatePane(ref generalPaneGuid, "Log", 1, 0); + if (ErrorHandler.Succeeded(hr)) + hr = Services.OutputWindow.GetPane(ref generalPaneGuid, out generalPane); + } + + Debug.Assert(ErrorHandler.Succeeded(hr), "Failed to get log window"); + + if (ErrorHandler.Succeeded(hr)) + { + hr = generalPane.OutputString(msg); + Debug.Assert(ErrorHandler.Succeeded(hr), "Failed to print to log window"); + } + + if (ErrorHandler.Succeeded(hr)) + { + hr = generalPane.Activate(); // Brings this pane into view + Debug.Assert(ErrorHandler.Succeeded(hr), "Failed to activate log window"); + } + } + + + } +} From 6bc727a77e12fed7f150d7dcefa53337c9f10e78 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 21:58:14 +0100 Subject: [PATCH 07/35] UIController manages the UI loading logic A little state machine to manage the UI logic of the login->2fa, create and clone workflows. --- src/GitHub.App/Controllers/UIController.cs | 156 +++++++++++++++++++++ src/GitHub.Exports/UI/IUIController.cs | 25 ++++ 2 files changed, 181 insertions(+) create mode 100644 src/GitHub.App/Controllers/UIController.cs create mode 100644 src/GitHub.Exports/UI/IUIController.cs diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs new file mode 100644 index 000000000..907871247 --- /dev/null +++ b/src/GitHub.App/Controllers/UIController.cs @@ -0,0 +1,156 @@ +using GitHub.Services; +using GitHub.UI; +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using Stateless; +using ReactiveUI; +using GitHub.Models; +using GitHub.Authentication; +using System.Diagnostics; + +namespace GitHub.VisualStudio.UI +{ + [Export(typeof(IUIController))] + public class UIController : IUIController, IDisposable + { + + enum UIState { Start, Auth, TwoFA, Create, Clone, End } + enum Trigger { Auth = 1, Create = 2, Clone = 3, Next, Previous } + + readonly ExportFactoryProvider factory; + + CompositeDisposable disposables = new CompositeDisposable(); + Subject transition; + UIControllerFlow currentFlow; + StateMachine machine; + + [ImportingConstructor] + public UIController(IUIProvider uiProvider, IRepositoryHosts hosts) + { + factory = uiProvider.GetService(); + + machine = new StateMachine(UIState.Start); + + machine.Configure(UIState.Start) + .Permit(Trigger.Auth, UIState.Auth) + .PermitIf(Trigger.Create, UIState.Create, () => hosts.IsLoggedInToAnyHost) + .PermitIf(Trigger.Create, UIState.Auth, () => !hosts.IsLoggedInToAnyHost) + .PermitIf(Trigger.Clone, UIState.Clone, () => hosts.IsLoggedInToAnyHost) + .PermitIf(Trigger.Clone, UIState.Auth, () => !hosts.IsLoggedInToAnyHost); + + machine.Configure(UIState.Auth) + .OnEntry(() => + { + var twofa = uiProvider.GetService(); + twofa.WhenAny(x => x.IsShowing, x => x.Value) + .Where(x => x) + .Subscribe(_ => + { + Fire(Trigger.Next); + }); + + var d = factory.LoginViewModelFactory.CreateExport(); + disposables.Add(d); + var view = uiProvider.GetService>(); + view.ViewModel = d.Value; + + d.Value.AuthenticationResults.Subscribe(result => + { + if (result == AuthenticationResult.Success) + Fire(Trigger.Next); + }); + transition.OnNext(view); + }) + .Permit(Trigger.Next, UIState.TwoFA); + + machine.Configure(UIState.TwoFA) + .SubstateOf(UIState.Auth) + .OnEntry(() => + { + var d = uiProvider.GetService(); + var view = uiProvider.GetService>(); + view.ViewModel = d; + transition.OnNext(view); + }) + .PermitIf(Trigger.Next, UIState.End, () => currentFlow == UIControllerFlow.Authentication) + .PermitIf(Trigger.Next, UIState.Create, () => currentFlow == UIControllerFlow.Create) + .PermitIf(Trigger.Next, UIState.Clone, () => currentFlow == UIControllerFlow.Clone); + + machine.Configure(UIState.Create) + .OnEntry(() => + { + var d = uiProvider.GetService(); + var view = uiProvider.GetService>(); + view.ViewModel = d; + transition.OnNext(view); + }) + .Permit(Trigger.Next, UIState.End); + + machine.Configure(UIState.Clone) + .OnEntry(() => + { + var d = uiProvider.GetService(); + + var view = uiProvider.GetService>(); + view.ViewModel = d; + transition.OnNext(view); + }) + .Permit(Trigger.Next, UIState.End); + + machine.Configure(UIState.End) + .OnEntry(() => + { + transition.OnCompleted(); + transition.Dispose(); + }) + .Permit(Trigger.Next, UIState.Start); + } + + void Fire(Trigger next) + { + Debug.WriteLine("Firing {0}", next); + machine.Fire(next); + } + + public IObservable SelectFlow(UIControllerFlow choice) + { + currentFlow = choice; + transition = new Subject(); + transition.Subscribe((o) => { }, _ => Fire(Trigger.Next)); + return transition; + } + + public void Start() + { + Fire((Trigger)(int)currentFlow); + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + disposables.Dispose(); + transition.Dispose(); + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + } +} diff --git a/src/GitHub.Exports/UI/IUIController.cs b/src/GitHub.Exports/UI/IUIController.cs new file mode 100644 index 000000000..68b522981 --- /dev/null +++ b/src/GitHub.Exports/UI/IUIController.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.UI +{ + public interface IUIController + { + //IObservable Transition { get; } + IObservable SelectFlow(UIControllerFlow choice); + void Start(); + } + + public enum UIControllerFlow + { + None = 0, + Authentication = 1, + Create = 2, + Clone = 3 + } + + +} From 8344cfcc22e09abbece8d29d0f6fe0bb29f69bad Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 22:03:00 +0100 Subject: [PATCH 08/35] UIProvider is our IServiceProvider implementation --- src/GitHub.Exports/Services/IUIProvider.cs | 18 +++++++ .../Services/UIProvider.cs | 53 ++++++++++++------- 2 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 src/GitHub.Exports/Services/IUIProvider.cs diff --git a/src/GitHub.Exports/Services/IUIProvider.cs b/src/GitHub.Exports/Services/IUIProvider.cs new file mode 100644 index 000000000..b1eba83da --- /dev/null +++ b/src/GitHub.Exports/Services/IUIProvider.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition.Hosting; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.Services +{ + public interface IUIProvider + { + ExportProvider ExportProvider { get; } + object GetService(Type t); + T GetService(); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] + Ret GetService() where Ret : class; + } +} diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index 52014eebf..df9e47aa9 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -1,9 +1,14 @@ using GitHub.Infrastructure; +using GitHub.Services; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using NullGuard; using ReactiveUI; using Splat; using System; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reactive.Concurrency; @@ -11,42 +16,54 @@ using System.Windows; namespace GitHub.VisualStudio { - [Export] + [Export(typeof(IUIProvider))] + [Export(typeof(IServiceProvider))] [PartCreationPolicy(CreationPolicy.Shared)] - public class UIProvider + public class UIProvider : IServiceProvider, IUIProvider { - [Import(typeof(IServiceProvider))] - public MefServiceProvider ServiceProvider { get; set; } + [AllowNull] + public ExportProvider ExportProvider { get; private set; } - public UIProvider() + readonly IServiceProvider serviceProvider; + + [ImportingConstructor] + public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) { + this.serviceProvider = serviceProvider; + + var componentModel = serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; + Debug.Assert(componentModel != null, "Service of type SComponentModel not found"); + ExportProvider = componentModel.DefaultExportProvider; + ModeDetector.OverrideModeDetector(new AppModeDetector()); RxApp.MainThreadScheduler = new DispatcherScheduler(Application.Current.Dispatcher); } - public void EnsureProvider(ExportProvider provider) - { - if (ServiceProvider.ExportProvider == null) - ServiceProvider.ExportProvider = provider; - } - } - - [Export(typeof(IServiceProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class MefServiceProvider : IServiceProvider - { - public ExportProvider ExportProvider { get; set; } - public object GetService(Type serviceType) { string contract = AttributedModelServices.GetContractName(serviceType); var instance = ExportProvider.GetExportedValues(contract).FirstOrDefault(); + if (instance != null) + return instance; + + instance = serviceProvider.GetService(serviceType); if (instance != null) return instance; throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not locate any instances of contract {0}.", contract)); } + + public T GetService() + { + return (T)GetService(typeof(T)); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] + public Ret GetService() where Ret : class + { + return GetService() as Ret; + } } } From 1852ed41eca013954561d1ab55674328b9873c2f Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 22:07:34 +0100 Subject: [PATCH 09/35] Language nazi on the prowl --- src/GitHub.App/Caches/SharedCache.cs | 4 ++-- src/GitHub.UI/Helpers/GitHubBrushes.cs | 28 +++++++++++++------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/GitHub.App/Caches/SharedCache.cs b/src/GitHub.App/Caches/SharedCache.cs index cdf823df6..423ff8d75 100644 --- a/src/GitHub.App/Caches/SharedCache.cs +++ b/src/GitHub.App/Caches/SharedCache.cs @@ -108,9 +108,9 @@ namespace GitHub TSource source) { var member = expression.Body as MemberExpression; - Debug.Assert(member != null, "Expression should be a property and not method or some other shit."); + Debug.Assert(member != null, "Expression should be a property and not method or some other thing."); var property = member.Member as PropertyInfo; - Debug.Assert(property != null, "Expression should be a property and not field or some other shit."); + Debug.Assert(property != null, "Expression should be a property and not field or some other thing."); var propertySetterAction = new Action(value => property.SetValue(source, value)); return Tuple.Create(property.Name, propertySetterAction); } diff --git a/src/GitHub.UI/Helpers/GitHubBrushes.cs b/src/GitHub.UI/Helpers/GitHubBrushes.cs index 055d8cf59..ed39066be 100644 --- a/src/GitHub.UI/Helpers/GitHubBrushes.cs +++ b/src/GitHub.UI/Helpers/GitHubBrushes.cs @@ -6,46 +6,46 @@ namespace GitHub.UI public static class GitHubBrushes { [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush AccentBrush = CreateBrush("#4ea6ea"); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush NewChangeBrush = CreateBrush(Color.FromArgb(255, 88, 202, 48)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush DeletedChangeBrush = CreateBrush(Color.FromArgb(255, 234, 40, 41)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush RenamedChangeBrush = CreateBrush(Color.FromArgb(255, 240, 180, 41)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush ConflictedChangeBrush = CreateBrush(Color.FromRgb(0x6e, 0x54, 0x94)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush DefaultChangeBrush = CreateBrush(Color.FromArgb(255, 0x33, 0x33, 0x33)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush GitRepoForegroundBrush = CreateBrush(Color.FromRgb(0x66, 0x66, 0x66)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush NonGitRepoForegroundBrush = CreateBrush(Color.FromRgb(0x99, 0x99, 0x99)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush NewCommitBackgroundBrush = CreateBrush(Color.FromRgb(0xFF, 0xFB, 0x85)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush CommitBackgroundBrush = CreateBrush(Color.FromRgb(0xed, 0xed, 0xed)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush CommitMessageTooLongBrush = CreateBrush(Color.FromRgb(0x99, 0x99, 0x99)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush NoChangeBrush = CreateBrush(Color.FromRgb(0xEE, 0xEE, 0xEE)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush DiffStatNewChangeBrush = CreateBrush(Color.FromRgb(0x6C, 0xC6, 0x44)); [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", - Justification = "We freeze these brushes so that shit is effectively immutable.")] + Justification = "We freeze these brushes so they're is effectively immutable.")] public static readonly SolidColorBrush DiffStatDeletedChangeBrush = CreateBrush(Color.FromRgb(0xD7, 0x43, 0x1B)); public static SolidColorBrush CreateBrush(Color color) From 3b1703d570422e88986cb3379da06fc757710e29 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 22:10:14 +0100 Subject: [PATCH 10/35] Decouple UI logic from backend --- .../TwoFactorChallengeHandler.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs index 5f8107fbb..fc99942c7 100644 --- a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs +++ b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs @@ -2,32 +2,34 @@ using System.ComponentModel.Composition; using System.Reactive.Linq; using GitHub.ViewModels; -using Microsoft.VisualStudio.PlatformUI; using Octokit; using ReactiveUI; +using GitHub.UI; namespace GitHub.Authentication { [Export(typeof(ITwoFactorChallengeHandler))] public class TwoFactorChallengeHandler : ITwoFactorChallengeHandler { - readonly IServiceProvider serviceProvider; + //readonly IServiceProvider serviceProvider; + readonly Lazy lazyTwoFactorDialog; [ImportingConstructor] - public TwoFactorChallengeHandler(IServiceProvider serviceProvider) + public TwoFactorChallengeHandler(Lazy twoFactorDialog) { - this.serviceProvider = serviceProvider; + //this.serviceProvider = serviceProvider; + this.lazyTwoFactorDialog = twoFactorDialog; } public IObservable HandleTwoFactorException(TwoFactorRequiredException exception) { - var twoFactorDialog = (TwoFactorDialogViewModel)serviceProvider.GetService(typeof(TwoFactorDialogViewModel)); - var twoFactorView = (IViewFor)serviceProvider.GetService(typeof(IViewFor)); + var twoFactorDialog = lazyTwoFactorDialog.Value as TwoFactorDialogViewModel; + //var twoFactorView = (IViewFor)serviceProvider.GetService(typeof(IViewFor)); return Observable.Start(() => { - twoFactorView.ViewModel = twoFactorDialog; - ((DialogWindow)twoFactorView).Show(); + //twoFactorView.ViewModel = twoFactorDialog; + //((DialogWindow)twoFactorView).Show(); var userError = new TwoFactorRequiredUserError(exception); return twoFactorDialog.Show(userError) @@ -37,8 +39,9 @@ namespace GitHub.Authentication : Observable.Throw(exception)); }, RxApp.MainThreadScheduler) .SelectMany(x => x) - .Finally(() => - ((DialogWindow)twoFactorView).Hide()); + //.Finally(() => + // ((DialogWindow)twoFactorView).Hide()); + ; } } } \ No newline at end of file From 0823ace0d0c05b9034802172fdc0de65b2010a23 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 22:13:06 +0100 Subject: [PATCH 11/35] Move interfaces out to separate assemblies --- src/GitHub.App/Authentication/IAccount.cs | 38 --- .../ITwoFactorChallengeHandler.cs | 10 - src/GitHub.App/Caches/IHostCache.cs | 21 -- .../Models/DisconnectedRepositoryHosts.cs | 3 - src/GitHub.App/Models/IRepositoryHost.cs | 29 -- src/GitHub.App/Models/IRepositoryHosts.cs | 1 - .../Models/LocalRepositoriesHost.cs | 1 - src/GitHub.App/Models/RepositoryHost.cs | 1 - src/GitHub.App/Models/RepositoryHosts.cs | 1 - src/GitHub.App/Services/IApiClient.cs | 33 -- .../Validation/ReactivePropertyValidator.cs | 309 ------------------ 11 files changed, 447 deletions(-) delete mode 100644 src/GitHub.App/Authentication/IAccount.cs delete mode 100644 src/GitHub.App/Authentication/ITwoFactorChallengeHandler.cs delete mode 100644 src/GitHub.App/Caches/IHostCache.cs delete mode 100644 src/GitHub.App/Models/IRepositoryHost.cs delete mode 100644 src/GitHub.App/Services/IApiClient.cs delete mode 100644 src/GitHub.UI.Reactive/Assets/Controls/Validation/ReactivePropertyValidator.cs diff --git a/src/GitHub.App/Authentication/IAccount.cs b/src/GitHub.App/Authentication/IAccount.cs deleted file mode 100644 index f76cbf0f1..000000000 --- a/src/GitHub.App/Authentication/IAccount.cs +++ /dev/null @@ -1,38 +0,0 @@ -using GitHub.Models; -using Octokit; -using ReactiveUI; - -namespace GitHub -{ - public interface IAccount : IReactiveObject - { - string Email { get; } - int Id { get; } - bool IsEnterprise { get; } - bool IsGitHub { get; } - bool IsLocal { get; } - bool IsOnFreePlan { get; } - bool HasMaximumPrivateRepositories { get; } - bool IsUser { get; } - /// - /// True if the user is an admin on the host (GitHub or Enterprise). - /// - /// - /// Do not confuse this with "IsStaff". This is true if the user is an admin - /// on the site. IsStaff is true if that site is github.com. - /// - bool IsSiteAdmin { get; } - /// - /// Returns true if the user is a member of the GitHub staff. - /// - bool IsGitHubStaff { get; } - IRepositoryHost Host { get; } - string Login { get; } - string Name { get; } - int OwnedPrivateRepos { get; } - long PrivateReposInPlan { get; } - - void Update(User ghUser); - void Update(Organization org); - } -} diff --git a/src/GitHub.App/Authentication/ITwoFactorChallengeHandler.cs b/src/GitHub.App/Authentication/ITwoFactorChallengeHandler.cs deleted file mode 100644 index 3d8f3c0c5..000000000 --- a/src/GitHub.App/Authentication/ITwoFactorChallengeHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using Octokit; - -namespace GitHub.Authentication -{ - public interface ITwoFactorChallengeHandler - { - IObservable HandleTwoFactorException(TwoFactorRequiredException exception); - } -} diff --git a/src/GitHub.App/Caches/IHostCache.cs b/src/GitHub.App/Caches/IHostCache.cs deleted file mode 100644 index 2b6eed792..000000000 --- a/src/GitHub.App/Caches/IHostCache.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reactive; -using Octokit; - -namespace GitHub -{ - /// - /// Per host cache data. - /// - public interface IHostCache : IDisposable - { - IObservable GetUser(); - IObservable InsertUser(User user); - IObservable> GetAllOrganizations(); - IObservable InsertOrganization(Organization organization); - IObservable InvalidateOrganization(Organization organization); - IObservable InvalidateOrganization(IAccount organization); - IObservable InvalidateAll(); - } -} diff --git a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs b/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs index e9c16c8ca..10111d3ad 100644 --- a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs +++ b/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs @@ -1,13 +1,10 @@  using System; -using System.Diagnostics; using System.Reactive; using System.Reactive.Linq; -using Akavache; using GitHub.Authentication; using NullGuard; using ReactiveUI; -using GitHub.Exports; namespace GitHub.Models { diff --git a/src/GitHub.App/Models/IRepositoryHost.cs b/src/GitHub.App/Models/IRepositoryHost.cs deleted file mode 100644 index 15957cb14..000000000 --- a/src/GitHub.App/Models/IRepositoryHost.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Reactive; -using ReactiveUI; -using GitHub.Exports; - -namespace GitHub.Models -{ - public interface IRepositoryHost : IReactiveObject - { - HostAddress Address { get; } - IApiClient ApiClient { get; } - IHostCache Cache { get; } - bool IsGitHub { get; } - bool IsLoggedIn { get; } - bool IsLoggingIn { get; } - bool IsEnterprise { get; } - bool IsLocal { get; } - ReactiveList Organizations { get; } - ReactiveList Accounts { get; } - string Title { get; } - IAccount User { get; } - - IObservable LogIn(string usernameOrEmail, string password); - IObservable LogInFromCache(); - IObservable LogOut(); - IObservable Refresh(); - IObservable Refresh(Func> refreshTrackedRepositoriesFunc); - } -} diff --git a/src/GitHub.App/Models/IRepositoryHosts.cs b/src/GitHub.App/Models/IRepositoryHosts.cs index d974886ff..a7c6f1c59 100644 --- a/src/GitHub.App/Models/IRepositoryHosts.cs +++ b/src/GitHub.App/Models/IRepositoryHosts.cs @@ -1,7 +1,6 @@ using System; using GitHub.Authentication; using ReactiveUI; -using GitHub.Exports; namespace GitHub.Models { diff --git a/src/GitHub.App/Models/LocalRepositoriesHost.cs b/src/GitHub.App/Models/LocalRepositoriesHost.cs index 5199adefb..46faa8f3e 100644 --- a/src/GitHub.App/Models/LocalRepositoriesHost.cs +++ b/src/GitHub.App/Models/LocalRepositoriesHost.cs @@ -3,7 +3,6 @@ using System.Reactive; using System.Reactive.Linq; using GitHub.Authentication; using ReactiveUI; -using GitHub.Exports; namespace GitHub.Models { diff --git a/src/GitHub.App/Models/RepositoryHost.cs b/src/GitHub.App/Models/RepositoryHost.cs index f90c5606d..f534d615d 100644 --- a/src/GitHub.App/Models/RepositoryHost.cs +++ b/src/GitHub.App/Models/RepositoryHost.cs @@ -15,7 +15,6 @@ using NLog; using Octokit; using ReactiveUI; using Authorization = Octokit.Authorization; -using GitHub.Exports; namespace GitHub.Models { diff --git a/src/GitHub.App/Models/RepositoryHosts.cs b/src/GitHub.App/Models/RepositoryHosts.cs index 9197ad8b4..6bf866f12 100644 --- a/src/GitHub.App/Models/RepositoryHosts.cs +++ b/src/GitHub.App/Models/RepositoryHosts.cs @@ -7,7 +7,6 @@ using Akavache; using GitHub.Authentication; using GitHub.Extensions.Reactive; using ReactiveUI; -using GitHub.Exports; namespace GitHub.Models { diff --git a/src/GitHub.App/Services/IApiClient.cs b/src/GitHub.App/Services/IApiClient.cs deleted file mode 100644 index 68a6dde6a..000000000 --- a/src/GitHub.App/Services/IApiClient.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using GitHub.Authentication; -using Octokit; - -namespace GitHub -{ - public interface IApiClient - { - HostAddress HostAddress { get; } - IObservable AddSshKey(SshKey newKey); - IObservable CreateRepository(Repository repo, string login, bool isUser); - IObservable GetSshKeys(); - IObservable GetUser(); - IObservable GetAllUsersForAllOrganizations(); - IObservable GetOrganization(string login); - IObservable GetOrganizations(); - IObservable GetMembersOfOrganization(string organizationName); - IObservable GetRepository(string owner, string name); - IObservable> GetUserRepositories(int currentUserId); - IObservable GetCurrentUserRepositoriesStreamed(); - IObservable GetOrganizationRepositoriesStreamed(string login); - IObservable GetOrCreateApplicationAuthenticationCode( - Func> twoFactorChallengeHander = null, - bool useOldScopes = false); - IObservable GetOrCreateApplicationAuthenticationCode( - string authenticationCode, - bool useOldScopes = false); - IObservable> GetEmails(); - ITwoFactorChallengeHandler TwoFactorChallengeHandler { get; } - } -} diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ReactivePropertyValidator.cs b/src/GitHub.UI.Reactive/Assets/Controls/Validation/ReactivePropertyValidator.cs deleted file mode 100644 index 8de9f15e9..000000000 --- a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ReactivePropertyValidator.cs +++ /dev/null @@ -1,309 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Reactive.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using GitHub.Extensions; -using ReactiveUI; - -namespace GitHub.UI -{ - public class ReactivePropertyValidationResult - { - public bool IsValid { get; private set; } - public ValidationStatus Status { get; private set; } - public string Message { get; private set; } - - [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "It is immutable")] - public static readonly ReactivePropertyValidationResult Success = new ReactivePropertyValidationResult(ValidationStatus.Valid); - - [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "It is immutable")] - public static readonly ReactivePropertyValidationResult Unvalidated = new ReactivePropertyValidationResult(); - - public ReactivePropertyValidationResult() - : this(ValidationStatus.Unvalidated, "") - { - } - - public ReactivePropertyValidationResult(ValidationStatus validationStatus) - : this(validationStatus, "") - { - } - - public ReactivePropertyValidationResult(ValidationStatus validationStatus, string message) - { - Status = validationStatus; - IsValid = validationStatus != ValidationStatus.Invalid; - Message = message; - } - } - - public enum ValidationStatus - { - Unvalidated = 0, - Invalid = 1, - Valid = 2, - } - - public abstract class ReactivePropertyValidator : ReactiveObject - { - public static ReactivePropertyValidator For(TObj This, Expression> property) - { - return new ReactivePropertyValidator(This, property); - } - - public abstract ReactivePropertyValidationResult ValidationResult { get; protected set; } - - public abstract bool IsValidating { get; } - - protected ReactivePropertyValidator() - { - } - - public abstract Task ExecuteAsync(); - - public abstract Task ResetAsync(); - } - - [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] - public class ReactivePropertyValidator : ReactivePropertyValidator - { - readonly ReactiveCommand validateCommand; - ReactivePropertyValidationResult validationResult; - - public override ReactivePropertyValidationResult ValidationResult - { - get { return validationResult; } - protected set { this.RaiseAndSetIfChanged(ref validationResult, value); } - } - - public override Task ExecuteAsync() - { - return validateCommand.ExecuteAsyncTask(new ValidationParameter()); - } - - public override Task ResetAsync() - { - return validateCommand.ExecuteAsyncTask(new ValidationParameter { RequiresReset = true }); - } - - readonly List>> validators = - new List>>(); - - readonly ObservableAsPropertyHelper isValidating; - public override bool IsValidating - { - get { return isValidating.Value; } - } - - public ReactivePropertyValidator(IObservable signal) - { - validateCommand = ReactiveCommand.CreateAsyncObservable(param => - { - var validationParams = (ValidationParameter)param; - - if (validationParams.RequiresReset) - { - return Observable.Return(ReactivePropertyValidationResult.Unvalidated); - } - - TProp value = validationParams.PropertyValue; - - var currentValidators = validators.ToList(); - - // HEAR YE, HEAR YE - - // This .ToList() is here to ignore changes to the validator collection, - // and thus avoid fantastically vague exceptions about - // "Collection was modified, enumeration operation may not execute" - // bubbling up to tear the application down - - // Thus, the collection will be correct when the command executes, - // which should be fine until we need to do more complex validation - - if (!currentValidators.Any()) - return Observable.Return(ReactivePropertyValidationResult.Unvalidated); - - return currentValidators.ToObservable() - .SelectMany(v => v(value)) - .FirstOrDefaultAsync(x => x.Status == ValidationStatus.Invalid) - .Select(x => x == null ? ReactivePropertyValidationResult.Success : x); - }); - - isValidating = validateCommand.IsExecuting.ToProperty(this, x => x.IsValidating); - - validateCommand.Subscribe(x => ValidationResult = x); - signal.Subscribe(x => validateCommand.Execute(new ValidationParameter { PropertyValue = x, RequiresReset = false })); - } - - public ReactivePropertyValidator IfTrue(Func predicate, string errorMessage) - { - return Add(predicate, errorMessage); - } - - public ReactivePropertyValidator IfFalse(Func predicate, string errorMessage) - { - return Add(x => !predicate(x), errorMessage); - } - - ReactivePropertyValidator Add(Func predicate, string errorMessage) - { - return Add(x => predicate(x) ? errorMessage : null); - } - - public ReactivePropertyValidator Add(Func predicateWithMessage) - { - validators.Add(value => Observable.Defer(() => Observable.Return(Validate(value, predicateWithMessage)))); - return this; - } - - public ReactivePropertyValidator IfTrueAsync(Func> predicate, string errorMessage) - { - AddAsync(x => predicate(x).Select(result => result ? errorMessage : null)); - return this; - } - - public ReactivePropertyValidator IfFalseAsync(Func> predicate, string errorMessage) - { - AddAsync(x => predicate(x).Select(result => result ? null : errorMessage)); - return this; - } - - public ReactivePropertyValidator AddAsync(Func> predicateWithMessage) - { - validators.Add(value => Observable.Defer(() => - { - return predicateWithMessage(value) - .Select(result => String.IsNullOrEmpty(result) - ? ReactivePropertyValidationResult.Success - : new ReactivePropertyValidationResult(ValidationStatus.Invalid, result)); - - })); - - return this; - } - - static ReactivePropertyValidationResult Validate(TProp value, Func predicateWithMessage) - { - var result = predicateWithMessage(value); - - if (String.IsNullOrEmpty(result)) - return ReactivePropertyValidationResult.Success; - - return new ReactivePropertyValidationResult(ValidationStatus.Invalid, result); - } - - class ValidationParameter - { - public TProp PropertyValue { get; set; } - public bool RequiresReset { get; set; } - } - } - - public class ReactivePropertyValidator : ReactivePropertyValidator - { - protected ReactivePropertyValidator() - : base(Observable.Empty()) - { - } - - public ReactivePropertyValidator(TObj This, Expression> property) - : base(This.WhenAny(property, x => x.Value)) { } - } - - public static class ReactivePropertyValidatorExtensions - { - public static ReactivePropertyValidator IfMatch(this ReactivePropertyValidator This, string pattern, string errorMessage) - { - var regex = new Regex(pattern); - - return This.IfTrue(regex.IsMatch, errorMessage); - } - - public static ReactivePropertyValidator IfNotMatch(this ReactivePropertyValidator This, string pattern, string errorMessage) - { - var regex = new Regex(pattern); - - return This.IfFalse(regex.IsMatch, errorMessage); - } - - public static ReactivePropertyValidator IfNullOrEmpty(this ReactivePropertyValidator This, string errorMessage) - { - return This.IfTrue(String.IsNullOrEmpty, errorMessage); - } - - public static ReactivePropertyValidator IfNotUri(this ReactivePropertyValidator This, string errorMessage) - { - return This.IfFalse(s => - { - Uri uri; - return Uri.TryCreate(s, UriKind.Absolute, out uri); - }, errorMessage); - } - - public static ReactivePropertyValidator IfSameAsHost(this ReactivePropertyValidator This, Uri compareToHost, string errorMessage) - { - return This.IfTrue(s => - { - Uri uri; - var isUri = Uri.TryCreate(s, UriKind.Absolute, out uri); - return isUri && uri.IsSameHost(compareToHost); - - }, errorMessage); - } - - public static ReactivePropertyValidator IfContainsInvalidPathChars(this ReactivePropertyValidator This, string errorMessage) - { - return This.IfTrue(str => - { - // easiest check to make - if (str.ContainsAny(Path.GetInvalidPathChars())) - { - return true; - } - - string driveLetter; - - try - { - // if for whatever reason you don't have an absolute path - // hopefully you've remembered to use `IfPathNotRooted` - // in your validator - driveLetter = Path.GetPathRoot(str); - } - catch (ArgumentException) - { - // Path.GetPathRoot does some fun things - // around legal combinations of characters that we miss - // by simply checking against an array of legal characters - return true; - } - - if (driveLetter == null) - { - return false; - } - - // lastly, check each directory name doesn't contain - // any invalid filename characters - var foldersInPath = str.Substring(driveLetter.Length); - return foldersInPath.Split(new[] { '\\', '/' }, StringSplitOptions.None) - .Any(x => x.ContainsAny(Path.GetInvalidFileNameChars())); - }, errorMessage); - } - - public static ReactivePropertyValidator IfPathNotRooted(this ReactivePropertyValidator This, string errorMessage) - { - return This.IfFalse(Path.IsPathRooted, errorMessage); - } - - public static ReactivePropertyValidator IfUncPath(this ReactivePropertyValidator This, string errorMessage) - { - return This.IfTrue(str => str.StartsWith(@"\\", StringComparison.Ordinal), errorMessage); - } - } -} \ No newline at end of file From daca32e4774d750abfbc36d982b2ef05efde881e Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 22:13:46 +0100 Subject: [PATCH 12/35] Kill unused usercontrols --- .../UI/Views/CreateIssueDialog.xaml | 25 ---- .../UI/Views/CreateIssueDialog.xaml.cs | 21 ---- .../UI/Views/LoginCommandDialog.xaml | 16 --- .../UI/Views/LoginCommandDialog.xaml.cs | 18 --- .../UI/Views/TwoFactorView.xaml | 109 ------------------ .../UI/Views/TwoFactorView.xaml.cs | 65 ----------- 6 files changed, 254 deletions(-) delete mode 100644 src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml delete mode 100644 src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml.cs delete mode 100644 src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml delete mode 100644 src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs delete mode 100644 src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml delete mode 100644 src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml.cs diff --git a/src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml b/src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml deleted file mode 100644 index f7906f612..000000000 --- a/src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml.cs b/src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml.cs deleted file mode 100644 index 3bd2313b7..000000000 --- a/src/GitHub.VisualStudio/UI/Views/CreateIssueDialog.xaml.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.VisualStudio.PlatformUI; - -namespace GitHub.VisualStudio.UI.Views -{ - /// - /// Interaction logic for CreateIssueDialog.xaml - /// - public partial class CreateIssueDialog : DialogWindow - { - public CreateIssueDialog() - { - InitializeComponent(); - } - - public void ShowModal(string subject) - { - subjectTextBox.Text = subject; - ShowModal(); - } - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml b/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml deleted file mode 100644 index 35f01763d..000000000 --- a/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs b/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs deleted file mode 100644 index 8ea4596ea..000000000 --- a/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using GitHub.Exports; -using Microsoft.VisualStudio.PlatformUI; - -namespace GitHub.VisualStudio.UI.Views -{ - /// - /// Interaction logic for LoginCommandDialog.xaml - /// - public partial class LoginCommandDialog : DialogWindow - { - public LoginCommandDialog(ILoginDialog loginControlViewModel) - { - InitializeComponent(); - - loginControl.ViewModel = loginControlViewModel; - } - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml b/src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml deleted file mode 100644 index 491ffdfba..000000000 --- a/src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml.cs b/src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml.cs deleted file mode 100644 index 4192ed9d5..000000000 --- a/src/GitHub.VisualStudio/UI/Views/TwoFactorView.xaml.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Reactive.Linq; -using System.Windows; -using System.Windows.Input; -using GitHub.ViewModels; -using Microsoft.VisualStudio.PlatformUI; -using ReactiveUI; - -namespace GitHub.Views -{ - /// - /// Interaction logic for PasswordView.xaml - /// - [Export(typeof(IViewFor))] - public partial class TwoFactorView : DialogWindow, IViewFor - { - public TwoFactorView() - { - InitializeComponent(); - DataContextChanged += (s, e) => ViewModel = (TwoFactorDialogViewModel)e.NewValue; - //IsVisibleChanged += (s, e) => authenticationCode.EnsureFocus(); - - this.WhenActivated(d => - { - d(this.BindCommand(ViewModel, vm => vm.OkCommand, view => view.okButton)); - d(this.BindCommand(ViewModel, vm => vm.CancelCommand, view => view.cancelButton)); - d(this.BindCommand(ViewModel, vm => vm.ShowHelpCommand, view => view.helpButton)); - d(this.BindCommand(ViewModel, vm => vm.ResendCodeCommand, view => view.resendCodeButton)); - - d(this.Bind(ViewModel, vm => vm.AuthenticationCode, view => view.authenticationCode.Text)); - d(this.OneWayBind(ViewModel, vm => vm.IsAuthenticationCodeSent, - view => view.authenticationSentLabel.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.IsSms, view => view.resendCodeButton.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.Description, view => view.description.Text)); - d(MessageBus.Current.Listen() - .Where(x => ViewModel.IsShowing && x.Key == Key.Escape && !x.Handled) - .Subscribe(async key => - { - key.Handled = true; - await ViewModel.CancelCommand.ExecuteAsync(); - })); - }); - } - - public TwoFactorDialogViewModel ViewModel - { - get { return (TwoFactorDialogViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register( - "ViewModel", - typeof(TwoFactorDialogViewModel), - typeof(TwoFactorView), - new PropertyMetadata(null)); - - object IViewFor.ViewModel - { - get { return ViewModel; } - set { ViewModel = (TwoFactorDialogViewModel)value; } - } - } -} From 7d864f16dfe19a13bcc9407a40cd972ee6e46e7e Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 21:56:23 +0100 Subject: [PATCH 13/35] Temporary TE section for triggering clone/create While we don't have the definitive classes/interfaces for the GitHub Connect section in Team Explorer, use a mock one that triggers our clone/create dialogs (with optional auth) --- .../PlaceholderGitHubSection.cs | 70 +++++++++++++++++++ .../UI/Views/GitHubConnectContent.xaml | 33 +++++++++ .../UI/Views/GitHubConnectContent.xaml.cs | 57 +++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs create mode 100644 src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml create mode 100644 src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs diff --git a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs new file mode 100644 index 000000000..9b7c6b1d7 --- /dev/null +++ b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs @@ -0,0 +1,70 @@ +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using GitHub.VisualStudio.UI.Views; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio.Shell; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.VisualStudio.TeamExplorerConnect +{ + [TeamExplorerSection(PlaceholderGitHubSectionId, TeamExplorerPageIds.Connect, 10)] + public class PlaceholderGitHubSection : TeamExplorerSectionBase + { + public const string PlaceholderGitHubSectionId = "519B47D3-F2A9-4E19-8491-8C9FA25ABE97"; + + protected GitHubConnectContent View + { + get { return this.SectionContent as GitHubConnectContent; } + set { this.SectionContent = value; } + } + + [ImportingConstructor] + public PlaceholderGitHubSection([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + { + + this.Title = "GitHub"; + this.IsVisible = true; + this.IsEnabled = true; + this.IsExpanded = true; + View = new GitHubConnectContent(); + View.ViewModel = this; + } + + public void DoCreate() + { + // this is done here and not via the constructor so nothing gets loaded + // until we get here + var ui = ServiceProvider.GetExportedValue(); + + var factory = ui.GetService(); + var d = factory.UIControllerFactory.CreateExport(); + var creation = d.Value.SelectFlow(GitHub.UI.UIControllerFlow.Create); + var x = new WindowController(creation); + creation.Subscribe(_ => { }, _ => x.Close()); + x.Show(); + } + + public void DoClone() + { + // this is done here and not via the constructor so nothing gets loaded + // until we get here + var ui = ServiceProvider.GetExportedValue(); + + var factory = ui.GetService(); + var d = factory.UIControllerFactory.CreateExport(); + var creation = d.Value.SelectFlow(GitHub.UI.UIControllerFlow.Clone); + creation.Subscribe(_ => { }, _ => d.Dispose()); + var x = new WindowController(creation); + x.Show(); + + d.Value.Start(); + } + } +} diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml b/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml new file mode 100644 index 000000000..167b4a711 --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + Clone + + Create + + + diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs b/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs new file mode 100644 index 000000000..7bccc0bb1 --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs @@ -0,0 +1,57 @@ +using GitHub.VisualStudio.TeamExplorerConnect; +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.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace GitHub.VisualStudio.UI.Views +{ + /// + /// This is a temporary placeholder class until the VS Connect section + /// is done + /// + public partial class GitHubConnectContent : UserControl + { + public GitHubConnectContent() + { + InitializeComponent(); + } + + private void cloneLink_Click(object sender, RoutedEventArgs e) + { + ViewModel.DoClone(); + } + + private void createLink_Click(object sender, RoutedEventArgs e) + { + ViewModel.DoCreate(); + } + + private void m_Click(object sender, RoutedEventArgs e) + { + + } + + public PlaceholderGitHubSection ViewModel + { + get { return (PlaceholderGitHubSection)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register( + "ViewModel", + typeof(PlaceholderGitHubSection), + typeof(GitHubConnectContent)); + } +} From fd0868a7c3fc9e8f7146d268139e3eea8a2c30b1 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 22:15:48 +0100 Subject: [PATCH 14/35] Add clone and create dialogs... ... and everything that the UI depends on (translations, styles, controls, user errors, etc, etc) --- GitHubVS.sln | 12 + src/GitHub.Api/SimpleApiClient.cs | 3 +- .../TwoFactorChallengeHandler.cs | 4 +- src/GitHub.App/Controllers/UIController.cs | 12 +- src/GitHub.App/FodyWeavers.xml | 2 +- src/GitHub.App/GitHub.App.csproj | 29 +- src/GitHub.App/Images/default_org_avatar.png | Bin 0 -> 1692 bytes src/GitHub.App/Images/default_user_avatar.png | Bin 0 -> 1953 bytes src/GitHub.App/SampleData/SampleViewModels.cs | 194 +++++++++++ src/GitHub.App/Services/ErrorMap.cs | 40 +++ src/GitHub.App/Services/ErrorMessage.cs | 34 ++ .../Services/ErrorMessageTranslator.cs | 69 ++++ src/GitHub.App/Services/StandardUserErrors.cs | 247 ++++++++++++- src/GitHub.App/Services/Translation.cs | 122 +++++++ ...PrivateRepositoryOnFreeAccountUserError.cs | 13 + ...PrivateRepositoryQuotaExceededUserError.cs | 29 ++ .../UserErrors/PublishRepositoryUserError.cs | 42 +++ .../ViewModels/CloneRepoViewModel.cs | 26 ++ .../ViewModels/CreateRepoViewModel.cs | 39 +++ .../ViewModels/LoginControlViewModel.cs | 8 +- .../ViewModels/TwoFactorDialogViewModel.cs | 12 +- src/GitHub.App/packages.config | 1 + .../Authentication/AuthenticationResult.cs | 8 +- .../AuthenticationResultExtensions.cs | 4 +- src/GitHub.Exports/GitHub.Exports.csproj | 15 +- src/GitHub.Exports/Helpers/ImageHelper.cs | 15 + src/GitHub.Exports/Models/IRepositoryModel.cs | 20 ++ .../Primitives/StringEquivalent.cs | 110 ++++++ src/GitHub.Exports/Primitives/UriString.cs | 276 +++++++++++++++ .../Services}/ExportFactoryProvider.cs | 11 +- src/GitHub.Exports/UI/ICloneDialog.cs | 12 - src/GitHub.Exports/UI/ICloneRepoViewModel.cs | 12 + .../{ILoginDialog.cs => ILoginViewModel.cs} | 9 +- src/GitHub.Exports/UI/ITwoFactorDialog.cs | 12 - src/GitHub.Exports/UI/ITwoFactorViewModel.cs | 18 + .../GitHub.Extensions.Reactive.csproj | 14 + .../RecoveryCommandWithIcon.cs | 15 + .../packages.config | 5 + .../GitHub.Extensions.csproj | 3 + .../Controls/Validation/UserErrorMessages.cs | 118 +++++++ .../Validation/UserErrorMessages.xaml | 109 ++++++ .../Controls/Validation/ValidationMessage.cs | 1 + .../Controls/FilteredComboBox.cs | 148 ++++++++ src/GitHub.UI.Reactive/FodyWeavers.xml | 2 +- .../GitHub.UI.Reactive.csproj | 14 +- src/GitHub.UI/Assets/Controls.xaml | 325 +----------------- .../Assets/Controls/GitHubLinkButton.xaml | 38 ++ .../Assets/Controls/LightListBox.xaml | 104 ++++++ .../Controls/ScrollViewerWithShadow.xaml | 75 ++++ src/GitHub.UI/Assets/TextBlocks.xaml | 81 +++-- .../Controls/AppendingPathTextBox.cs | 95 +++++ .../Controls/ComboBoxes/GitHubComboBox.cs | 24 ++ src/GitHub.UI/Controls/EmojiImage.cs | 62 ++++ src/GitHub.UI/Controls/FilterTextBox.cs | 88 +++++ .../Controls/HorizontalShadowDivider.xaml | 30 ++ .../Controls/HorizontalShadowDivider.xaml.cs | 15 + src/GitHub.UI/Controls/TrimmedTextBlock.cs | 87 +++++ src/GitHub.UI/GitHub.UI.csproj | 25 ++ src/GitHub.UI/Properties/AssemblyInfo.cs | 10 + src/GitHub.VisualStudio/Base/PackageBase.cs | 6 - .../GitHub.VisualStudio.csproj | 77 +++-- .../GitHub.VisualStudio.vsct | 27 +- src/GitHub.VisualStudio/GitHubPackage.cs | 77 ++--- src/GitHub.VisualStudio/Helpers/Constants.cs | 10 + src/GitHub.VisualStudio/PkgCmdID.cs | 3 +- src/GitHub.VisualStudio/Services/Services.cs | 67 +++- .../UI/Views/Controls/CloneRepoControl.xaml | 88 +++++ .../Views/Controls/CloneRepoControl.xaml.cs | 42 +++ .../UI/Views/Controls/CreateRepoControl.xaml | 190 ++++++++++ .../Views/Controls/CreateRepoControl.xaml.cs | 85 +++++ .../UI/Views/Controls/LoginControl.xaml | 11 +- .../UI/Views/Controls/LoginControl.xaml.cs | 21 +- .../UI/Views/Controls/TwoFactorControl.xaml | 109 ++++++ .../Views/Controls/TwoFactorControl.xaml.cs | 69 ++++ .../UI/WindowController.xaml | 14 + .../UI/WindowController.xaml.cs | 52 +++ 76 files changed, 3341 insertions(+), 555 deletions(-) create mode 100644 src/GitHub.App/Images/default_org_avatar.png create mode 100644 src/GitHub.App/Images/default_user_avatar.png create mode 100644 src/GitHub.App/SampleData/SampleViewModels.cs create mode 100644 src/GitHub.App/Services/ErrorMap.cs create mode 100644 src/GitHub.App/Services/ErrorMessage.cs create mode 100644 src/GitHub.App/Services/ErrorMessageTranslator.cs create mode 100644 src/GitHub.App/Services/Translation.cs create mode 100644 src/GitHub.App/UserErrors/PrivateRepositoryOnFreeAccountUserError.cs create mode 100644 src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs create mode 100644 src/GitHub.App/UserErrors/PublishRepositoryUserError.cs create mode 100644 src/GitHub.App/ViewModels/CloneRepoViewModel.cs create mode 100644 src/GitHub.App/ViewModels/CreateRepoViewModel.cs create mode 100644 src/GitHub.Exports/Helpers/ImageHelper.cs create mode 100644 src/GitHub.Exports/Models/IRepositoryModel.cs create mode 100644 src/GitHub.Exports/Primitives/StringEquivalent.cs create mode 100644 src/GitHub.Exports/Primitives/UriString.cs rename src/{GitHub.VisualStudio/Helpers => GitHub.Exports/Services}/ExportFactoryProvider.cs (59%) delete mode 100644 src/GitHub.Exports/UI/ICloneDialog.cs create mode 100644 src/GitHub.Exports/UI/ICloneRepoViewModel.cs rename src/GitHub.Exports/UI/{ILoginDialog.cs => ILoginViewModel.cs} (64%) delete mode 100644 src/GitHub.Exports/UI/ITwoFactorDialog.cs create mode 100644 src/GitHub.Exports/UI/ITwoFactorViewModel.cs create mode 100644 src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs create mode 100644 src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs create mode 100644 src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml create mode 100644 src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs create mode 100644 src/GitHub.UI/Assets/Controls/GitHubLinkButton.xaml create mode 100644 src/GitHub.UI/Assets/Controls/LightListBox.xaml create mode 100644 src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml create mode 100644 src/GitHub.UI/Controls/AppendingPathTextBox.cs create mode 100644 src/GitHub.UI/Controls/ComboBoxes/GitHubComboBox.cs create mode 100644 src/GitHub.UI/Controls/EmojiImage.cs create mode 100644 src/GitHub.UI/Controls/FilterTextBox.cs create mode 100644 src/GitHub.UI/Controls/HorizontalShadowDivider.xaml create mode 100644 src/GitHub.UI/Controls/HorizontalShadowDivider.xaml.cs create mode 100644 src/GitHub.UI/Controls/TrimmedTextBlock.cs create mode 100644 src/GitHub.VisualStudio/Helpers/Constants.cs create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml create mode 100644 src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs create mode 100644 src/GitHub.VisualStudio/UI/WindowController.xaml create mode 100644 src/GitHub.VisualStudio/UI/WindowController.xaml.cs diff --git a/GitHubVS.sln b/GitHubVS.sln index f293ea950..40c86501c 100644 --- a/GitHubVS.sln +++ b/GitHubVS.sln @@ -49,6 +49,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports", "src\GitHu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Api", "src\GitHub.Api\GitHub.Api.csproj", "{B389ADAF-62CC-486E-85B4-2D8B078DF763}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports.Reactive", "src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj", "{E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesignTimeStyleHelper", "src\DesignTimeStyleHelper\DesignTimeStyleHelper.csproj", "{B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -95,6 +99,14 @@ Global {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Debug|Any CPU.Build.0 = Debug|Any CPU {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|Any CPU.ActiveCfg = Release|Any CPU {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|Any CPU.Build.0 = Release|Any CPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Release|Any CPU.Build.0 = Release|Any CPU + {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/GitHub.Api/SimpleApiClient.cs b/src/GitHub.Api/SimpleApiClient.cs index f3ed9bc07..6fa8dc3e2 100644 --- a/src/GitHub.Api/SimpleApiClient.cs +++ b/src/GitHub.Api/SimpleApiClient.cs @@ -1,5 +1,4 @@ -using GitHub.Exports; -using GitHub.Extensions; +using GitHub.Extensions; using GitHub.Services; using Octokit; using System; diff --git a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs index fc99942c7..940929194 100644 --- a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs +++ b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs @@ -12,10 +12,10 @@ namespace GitHub.Authentication public class TwoFactorChallengeHandler : ITwoFactorChallengeHandler { //readonly IServiceProvider serviceProvider; - readonly Lazy lazyTwoFactorDialog; + readonly Lazy lazyTwoFactorDialog; [ImportingConstructor] - public TwoFactorChallengeHandler(Lazy twoFactorDialog) + public TwoFactorChallengeHandler(Lazy twoFactorDialog) { //this.serviceProvider = serviceProvider; this.lazyTwoFactorDialog = twoFactorDialog; diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs index 907871247..e2da37aa8 100644 --- a/src/GitHub.App/Controllers/UIController.cs +++ b/src/GitHub.App/Controllers/UIController.cs @@ -45,7 +45,7 @@ namespace GitHub.VisualStudio.UI machine.Configure(UIState.Auth) .OnEntry(() => { - var twofa = uiProvider.GetService(); + var twofa = uiProvider.GetService(); twofa.WhenAny(x => x.IsShowing, x => x.Value) .Where(x => x) .Subscribe(_ => @@ -55,7 +55,7 @@ namespace GitHub.VisualStudio.UI var d = factory.LoginViewModelFactory.CreateExport(); disposables.Add(d); - var view = uiProvider.GetService>(); + var view = uiProvider.GetService>(); view.ViewModel = d.Value; d.Value.AuthenticationResults.Subscribe(result => @@ -71,8 +71,8 @@ namespace GitHub.VisualStudio.UI .SubstateOf(UIState.Auth) .OnEntry(() => { - var d = uiProvider.GetService(); - var view = uiProvider.GetService>(); + var d = uiProvider.GetService(); + var view = uiProvider.GetService>(); view.ViewModel = d; transition.OnNext(view); }) @@ -93,9 +93,9 @@ namespace GitHub.VisualStudio.UI machine.Configure(UIState.Clone) .OnEntry(() => { - var d = uiProvider.GetService(); + var d = uiProvider.GetService(); - var view = uiProvider.GetService>(); + var view = uiProvider.GetService>(); view.ViewModel = d; transition.OnNext(view); }) diff --git a/src/GitHub.App/FodyWeavers.xml b/src/GitHub.App/FodyWeavers.xml index 9d96e0ada..3087ec8c4 100644 --- a/src/GitHub.App/FodyWeavers.xml +++ b/src/GitHub.App/FodyWeavers.xml @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 5081385b7..2df5187bc 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -11,6 +11,7 @@ GitHub.App v4.5 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 5547d2f4 4 true @@ -41,7 +42,9 @@ False ..\..\packages\akavache.sqlite3.4.1.0\lib\Portable-Net45+Win8+WP8+Wpa81\Akavache.Sqlite3.dll + + False ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll @@ -74,6 +77,9 @@ ..\..\packages\SQLitePCL.raw_basic.0.7.1\lib\net45\SQLitePCL.raw.dll + + ..\..\packages\Stateless.2.5.11.0\lib\portable-net40+sl50+win+wp80\Stateless.dll + @@ -107,14 +113,11 @@ Properties\SolutionInfo.cs - - - @@ -131,33 +134,48 @@ - + + + + - + + + + + + + + + + + {e4ed0537-d1d9-44b6-9212-3096d7c3f7a1} + GitHub.Exports.Reactive + {9aea02db-02b5-409c-b0ca-115d05331a6b} GitHub.Exports @@ -179,7 +197,6 @@ Rothko - diff --git a/src/GitHub.App/Images/default_org_avatar.png b/src/GitHub.App/Images/default_org_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..55d481cc02667d4ac65f9a714555071716a9fbe7 GIT binary patch literal 1692 zcmeAS@N?(olHy`uVBq!ia0vp^${@_a1SBW!R^bCuk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPAEg{v+u2}(t{7puX=A(aKG z`a!A1`K3k4z=%sz23b{L-^Aq1JP;qO z-q+X4Gq1QLF)umQ)5TT^Xo6m5W{Q=Yfw76Jv#FDzk%^(9p{t>jiGjJBv$3U{i?e}~ znSnV>uSMv>2~2MaLa#GUy`ZF!TL84#CABECEH%ZgC_h&L>}9J=+-|YNX&zK> z3U0SJ;?%1Tbc{YIVv!;mCIn19ASOK70y*%6pPC0u?M1+3y(RwCcVKQ2@N{tuskrrK zdUXE6K#{iB0qxtmEVi{w=$Y{4|9_6}9v&UL7KFSw_|xQkgt2vwbbQd|?vrb{@7mqJ z^?vug(i^+CC2g$us++d|m)>vjiE|DnOt3%wv}kM8+2_S#;IL2G@9$r6oM~atqHMRL`}glZJxgn9pu#N~*Gc;(9&A)( zT{y>8DeTEq)2-@88GY3rLIQV{*E9L(M$F+pq3ZHcl+o1i+XR)3Ay@P^lm@?_oVIng zq^HojORIfbK0iM+|JFpA?5D}s)o$yVHd&iR^(@oq%G(4~;c=1c73bY7LATWKt4+VM z942wB$Po>*k(>Typ4kia$tUlAvlqEvDPi}EkB#|d%r%}Z*RO{!yZO*k>4;`_m51OW z4pp_#$TSs6gS&a+Dm62bjZU-9_{J1^bmGYrr-c_D#!PS6(z(uGIc@rfB=>8bE{ksF zFzFO;zW+Y^?kZ7>F&adS3);5TG;mGjr-MgfX61&Z}r`a6y=q%|s5ongrSro|c zKi6O8yhd~M+A!zBb4)6Ww)CAKS2sV;=M)>erEOD6kK*7oi z3u_TCT)aRJ$Z64Ipj^D5iv=%GSB=ucK|};y6$=powaAL>M8)n8yWcr8-{m~d^FHtQ zzH=r&CThLsLf?fr9L`f3DVAe1)%DDG!`?j!W(PJcMNKNW3 zkQ`EL)3$a(VL04eqCu%bRkBaH8rVcuyD(&%$%3(QxUg`WMXfPHD3AzkF_?M8(Uw*s zV9@f2u}m3NW)VSpLu9%YQlv*IHR(nTM@tM}2ZY(Un1BgF)qu^EY(}^?9`Us<7hAiI zDMa8k1U2%AZ%nCVF@OlRLI9IYC26Q3R3HQ-gLDRq#aau{s34U>rBOf{31o5UG%l42 zyd6X=npLag%Eb|HV_`cUQIDb)E`^esno3TklVR%?3drGbTpBbQ2}6*OG&8EUk<3Wo ztb!OqG**KJHNa-TrKnDXx1l^D=JZVpCd)fnGxGMDunVKu)D{Xzrn*v^1i7z ziDZ^5hBezvkQtrjYJcO>f0yfO1(OAfEQYLx6i6Fkg-yWgl(~lYcwsQvETM=M!H8fA z-tXlNSNk3>Z@6sg?{X=a8H%g0|5NDMBdmK|%Xe*yZQeCLWX8JPinVpMKz9*`!~00Z z0;R2Yq-=e3pujtKEa9AZkN5{c-r9k-nHl#uHn5#9{?LKgt^&WZz6~w%hFu4SW8xYnPXQcT!XO z+0EZ8{e7R0jg7Hzp4jm6+8(rT!z1I7uk77jU341FjYjn;xX{uP`nVPE4UE6Yh>eXc zqReI8s;jTBZ)j+cf4_HiWpi_LS=n;8rK|Ag(X6ajnA(#kPo6zv&XHqZhyB`nqU;r}n{v2a&|b(NR&4M@PrHhlccg_(v!* z6G230&Ity9?vas^t5?0s2);2!<8WJ>TkH!Jx#UiN{|3*%Z)$34aPGmX@`oeS60cksvnm!2+zBlQWZQ~W0Os2D~tvl!93ncOXj=!Cek)Gb$-yej(vW)4Rm{`*1 zbUOQx3u7%UEvwi&dQN^F50^eaG@xG^#JY67wldq2OfKm{IYIU>6n$06iHX7}HrJTi+uL(+Wp}>r=(Vc?+W+C z(U2|Dl|tqI^ySNBGK0feUeUXhQXYD=gQHu($p70$$6wNxJsi*v z^LY2<6aydHa^qDZTM9x5~k!;%GszYPfPLnZ0B|5`OBC8%-#6y>T8JSy?@4hD=wDrniwaHM`{Ux{2V=kDCl8vFNIYhb&+QJ1&Litg zYpZ=|p1$0#qG(VVz}Rt%zBxs zS9ADOQ_17{o0r+^GTZw550C%y^l4+{?&@zBqfD=ln3oP6{<}TyuP9pmpvh9j@LLk} z=9WARwUnT%H(3I{5DWR8hdjJfMR3?gVk4t3}3D-_Kyp9fAY@Ewg^ zyKMLRqRu&aw%nuDg((U%;RpX;>cGC`RR`-5+6YsAgr7uP{Q`D>a4?5a`Hao{$uT`) h&kf+u_*H)C=7Bpe;m;TcwN { new AccountDesigner("GitHub") }; + } + + public string RepositoryName { get; private set; } + public string SafeRepositoryName { get; private set; } + public bool ShowRepositoryNameWarning { get; private set; } + public string RepositoryNameWarningText { get; private set; } + public ReactivePropertyValidator RepositoryNameValidator { get; private set; } + public string Description { get; set; } + public ReactivePropertyValidator SelectedAccountValidator { get; private set; } + public bool KeepPrivate { get; set; } + public bool CanKeepPrivate { get; private set; } + public bool ShowUpgradeToMicroPlanWarning { get; private set; } + public bool ShowUpgradePlanWarning { get; private set; } + public ReactiveCommand CreateRepository { get; private set; } + public bool IsPublishing { get; private set; } + public ReactiveCommand UpgradeAccountPlan { get; private set; } + public ReactiveCommand Reset { get; private set; } + public ReactiveList Accounts { get; private set; } + public IAccount SelectedAccount { get; private set; } + + public ICommand OkCmd { get; } + public ICommand CancelCmd { get; } + } + + [ExcludeFromCodeCoverage] + public sealed class AccountDesigner : ReactiveObject, IAccount + { + public AccountDesigner() + { + } + + public AccountDesigner(string name) + { + Name = name; + Avatar = new AvatarProviderDesigner().DefaultOrgBitmapImage; + IsGitHubStaff = false; + IsSiteAdmin = false; + } + + public object Avatar { get; set; } + public string Email { get; set; } + public int Id { get; set; } + public bool IsEnterprise { get; set; } + public bool IsGitHub { get; set; } + public bool IsLocal { get; set; } + public bool IsOnFreePlan { get; set; } + public bool HasMaximumPrivateRepositories { get; private set; } + public bool IsSelected { get; set; } + public bool IsUser { get; set; } + public bool IsSiteAdmin { get; private set; } + public bool IsGitHubStaff { get; private set; } + public IRepositoryHost Host { get; set; } + public string Login { get; set; } + public string Name { get; set; } + public int OwnedPrivateRepos { get; set; } + public long PrivateReposInPlan { get; set; } + + public void Update(User ghUser) + { + throw new NotImplementedException(); + } + + public void Update(Organization org) + { + throw new NotImplementedException(); + } + } + + + [ExcludeFromCodeCoverage] + public class AvatarProviderDesigner : IAvatarProvider + { + public AvatarProviderDesigner() + { + DefaultUserBitmapImage = ImageHelper.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"); + DefaultOrgBitmapImage = ImageHelper.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_org_avatar.png"); + } + + public BitmapImage DefaultUserBitmapImage { get; private set; } + public BitmapImage DefaultOrgBitmapImage { get; private set; } + + public IObservable GetAvatar(Account apiAccount) + { + throw new NotImplementedException(); + } + + public IObservable InvalidateAvatar(Account apiAccount) + { + throw new NotImplementedException(); + } + + public IObservable GetAvatar(string email) + { + throw new NotImplementedException(); + } + } + + [ExcludeFromCodeCoverage] + public class RepositoryModelDesigner : ReactiveObject, IRepositoryModel + { + public RepositoryModelDesigner() + { + } + + public RepositoryModelDesigner(string name) + { + Name = name; + } + + public RepositoryModelDesigner(string name, string owner) + { + Owner = owner; + Name = name; + OwnerWithSlash = owner + "/"; + NameWithOwner = OwnerWithSlash + name; + HasRemote = IsHosted = true; + AdditionalClones = new HashSet(); + ToolTip = "Repo Tooltip"; + IsPrivate = true; + CanViewOnHost = true; + } + + public DateTimeOffset? LastShadowBackupTime { get; set; } + public string LastShadowBackupSha1 { get; set; } + public bool IsSelected { get; set; } + public HostAddress HostAddress { get; private set; } + public int? Id { get; set; } + public bool IsLostOnDisk { get; set; } + public string LocalWorkingDirectory { get; set; } + public string LocalDotGitPath { get; set; } + public UriString CloneUrl { get; set; } + public UriString HttpsUrl { get; set; } + public UriString SshUrl { get; set; } + public UriString UpstreamCloneUrl { get; set; } + public bool HasRemote { get; set; } + public bool IsHosted { get; set; } + public bool HasLocal { get; set; } + public bool CanViewOnHost { get; private set; } + public IRepositoryHost RepositoryHost { get; set; } + + string IRepositoryModel.Owner + { + get { return Owner; } + set { Owner = value; } + } + + public int? OwnerId { get; set; } + + public string Owner { get; set; } + public string OwnerWithSlash { get; set; } + public string Name { get; set; } + public string NameWithOwner { get; set; } + public string Description { get; set; } + public string ToolTip { get; set; } + public Uri HostUri { get; set; } + public bool IsCollaborator { get; set; } + public bool IsCloning { get; set; } + public bool IsFork { get; private set; } + public bool HasDeployedGitIgnore { get; set; } + public string NonGitHubRemoteHost { get; set; } + public HashSet AdditionalClones { get; private set; } + + public bool IsPrivate { get; set; } + } +} diff --git a/src/GitHub.App/Services/ErrorMap.cs b/src/GitHub.App/Services/ErrorMap.cs new file mode 100644 index 000000000..ce67a5511 --- /dev/null +++ b/src/GitHub.App/Services/ErrorMap.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ReactiveUI; + +namespace GitHub.Services +{ + public class ErrorMap + { + public ErrorMap(ErrorMessage defaultMessage) : this(defaultMessage, null, null) + { + } + + public ErrorMap(ErrorMessage defaultMessage, IEnumerable translations, IEnumerable recoveryCommands) + { + this.defaultMessage = defaultMessage; + this.translations = translations; + RecoveryCommands = recoveryCommands; + } + + readonly ErrorMessage defaultMessage; + readonly IEnumerable translations; + public IEnumerable RecoveryCommands { get; private set; } + + public ErrorMessage GetErrorInfo(Exception exception) + { + if (exception != null && translations != null) + { + var translated = (from t in translations + let result = t.Translate(exception) + where result != null + select result).FirstOrDefault(); + + if (translated != null) + return translated; + } + return defaultMessage; + } + } +} diff --git a/src/GitHub.App/Services/ErrorMessage.cs b/src/GitHub.App/Services/ErrorMessage.cs new file mode 100644 index 000000000..df6ba7db3 --- /dev/null +++ b/src/GitHub.App/Services/ErrorMessage.cs @@ -0,0 +1,34 @@ +using System; +using System.Text.RegularExpressions; +using GitHub.Helpers; + +namespace GitHub.Services +{ + public class ErrorMessage + { + static readonly Regex placeholderRegex = new Regex(@"\$\d", RegexOptions.Compiled); + readonly Lazy headingHasPlaceholders; + readonly Lazy messageHasPlaceholders; + + public ErrorMessage(string heading, string message) + { + Heading = heading; + headingHasPlaceholders = new Lazy(() => placeholderRegex.IsMatch(heading)); + Message = message; + messageHasPlaceholders = new Lazy(() => placeholderRegex.IsMatch(message)); + } + + public string Heading { get; private set; } + public string Message { get; private set; } + + public bool HeadingHasPlaceholders + { + get { return headingHasPlaceholders.Value; } + } + + public bool MessageHasPlaceholders + { + get { return messageHasPlaceholders.Value; } + } + } +} diff --git a/src/GitHub.App/Services/ErrorMessageTranslator.cs b/src/GitHub.App/Services/ErrorMessageTranslator.cs new file mode 100644 index 000000000..b615af7a8 --- /dev/null +++ b/src/GitHub.App/Services/ErrorMessageTranslator.cs @@ -0,0 +1,69 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace GitHub.Services +{ + public class ErrorMessageTranslator + { + readonly IDictionary userErrorMappings; + + public ErrorMessageTranslator(IDictionary userErrors) + { + userErrorMappings = userErrors; + } + + public UserError GetUserError(ErrorType errorType, Exception exception, params object[] context) + { + var translation = GetUserErrorTranslation(errorType, exception, context); + return new UserError(translation.ErrorMessage, translation.CauseOrResolution, translation.RecoveryCommands, null, exception) + { + UserErrorIcon = StockUserErrorIcon.Error + }; + } + + public UserErrorTranslation GetUserErrorTranslation(ErrorType errorType, Exception exception, params object[] context) + { + ErrorMessage errorMessage = null; + ErrorMap errorMap; + if (userErrorMappings.TryGetValue(ErrorType.Global, out errorMap) && errorMap != null) + { + errorMessage = errorMap.GetErrorInfo(exception); + } + + if (errorMessage == null) + { + if (!userErrorMappings.TryGetValue(errorType, out errorMap) || errorMap == null) + throw new InvalidOperationException("This should never happen!"); + } + + errorMessage = errorMap.GetErrorInfo(exception) ?? new ErrorMessage("error", "Unknown error occurred"); + string details = errorMessage.Message; + string heading = errorMessage.Heading; + if (context != null && context.Any()) + { + heading = String.Format(CultureInfo.InvariantCulture, heading, context); + details = String.Format(CultureInfo.InvariantCulture, details, context); + } + + return new UserErrorTranslation(heading, details, errorMap.RecoveryCommands); + } + + public class UserErrorTranslation + { + public UserErrorTranslation(string errorMessage, string causeOrResolution, IEnumerable recoveryCommands) + { + ErrorMessage = errorMessage; + CauseOrResolution = causeOrResolution; + RecoveryCommands = recoveryCommands; + } + + public string ErrorMessage { get; private set; } + public string CauseOrResolution { get; private set; } + public IEnumerable RecoveryCommands { get; private set; } + } + + } +} diff --git a/src/GitHub.App/Services/StandardUserErrors.cs b/src/GitHub.App/Services/StandardUserErrors.cs index fe5fa96d3..30af0fdf9 100644 --- a/src/GitHub.App/Services/StandardUserErrors.cs +++ b/src/GitHub.App/Services/StandardUserErrors.cs @@ -1,7 +1,23 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; using System.Reactive.Linq; -using System.Windows; +using System.Text.RegularExpressions; +using GitHub.Models; +using NLog; +using Octokit; +using ApiClient = GitHub.Api.ApiClient; +using LogManager = NLog.LogManager; using ReactiveUI; +using GitHub.Extensions; +using GitHub.Info; +using GitHub.UserErrors; namespace GitHub.Services { @@ -14,6 +30,7 @@ namespace GitHub.Services BranchUnpublishFailed, BranchListFailed, CannotDropFolder, + CannotDropFolderUnauthorizedAccess, ClipboardFailed, ClonedFailed, CloneFailedNotLoggedIn, @@ -44,6 +61,7 @@ namespace GitHub.Services ShellFailed, CustomShellFailed, WorkingDirectoryDoesNotExist, + DefaultClonePathInvalid, MergeFailed, PowerShellNotFound, LoadingCommitsFailed, @@ -56,14 +74,227 @@ namespace GitHub.Services public static class StandardUserErrors { - public static IObservable ShowUserErrorMessage( - this Exception ex, ErrorType errorType, params object[] messageArgs) + internal static readonly Lazy Translator = new Lazy(() => new ErrorMessageTranslator(new Dictionary { - // TODO: Fix this. This is just placeholder logic. -@haacked - Console.WriteLine(errorType); - Console.WriteLine(messageArgs); - MessageBox.Show(ex.Message); - return Observable.Return(new RecoveryOptionResult()); + { + // Exceptions are matched against global *FIRST* before they are matched against + // specific operations. + ErrorType.Global, Map(null, + new Translation("Cannot find config file '(.*?)'", "The Git configuration file is missing.", "The global Git configuration file '$1' could not be found. Please open the options menu from the dashboard and update your name and email settings to create a new one."), + new Translation("Authentication failed", "Your credentials may be out of date. Please log out of the application and then log back in before retrying the operation."), + new Translation("Maximum login attempts exceeded", "Please log out of the application and then log back in before retrying the operation. You may need to wait a few minutes."), + new Translation("fatal: Authentication failed", "Authentication failed", "Your credentials may be out of date. Please log out of the application and then log back in before retrying the operation."), + new Translation(@"({""message"":""Bad credentials.?""}|Bad credentials.?)", "Authentication failed", "Your credentials may be out of date. Please log out of the application and then log back in before retrying the operation."), + new Translation("Directory not found", "The directory does not exist"), + new Translation("Access denied", "You do not have permissions to access this file or folder"), + new Translation("EPOLICYKEYAGE", "Unverified SSH Key", "{0}", ParseUnverifiedSshKeyMessageFromExceptionMessage)) + }, + { + ErrorType.BranchCreateFailed, Map(Defaults("Failed to create new branch"), + new Translation("No valid git object identified by '.+?' exists in the repository.", "Failed to create branch", "The current branch doesn’t have any commits.")) + }, + { + ErrorType.BranchDeleteFailed, Map(Defaults("error", "Failed to delete the branch."), + new Translation(@"fatal: .*? not found: did you run git update-server-info on the server\?", + "Failed to delete the branch", + "Please make sure the repository exists and that you have permissions to change it."), + new Translation("error: The requested URL returned error: 403 while accessing .*", + "Failed to delete the branch", + "Please make sure the repository exists and that you have permissions to change it."), + new Translation("fatal: Could not read from remote repository.", + "Failed to delete the branch", + "Please make sure the repository exists and that you have permissions to change it."), + new Translation(@".*?\(deletion of the current branch prohibited\).*", + "Cannot delete the default branch", + "To delete this branch, log in to " + ApiClient.GitHubDotComHostName + " and change " + + "the repository’s default branch to another branch first.")) + }, + { + ErrorType.BranchUnpublishFailed, Map(Defaults("error", "Failed to unpublish the branch."), + new Translation(@"fatal: .*? not found: did you run git update-server-info on the server\?", + "Failed to unpublish the branch", + "Please make sure the repository exists and that you have permissions to change it."), + new Translation("error: The requested URL returned error: 403 while accessing .*", + "Failed to unpublish the branch", + "Please make sure the repository exists and that you have permissions to change it."), + new Translation("fatal: Could not read from remote repository.", + "Failed to unpublish the branch", + "Please make sure the repository exists and that you have permissions to change it."), + new Translation(@".*?\(deletion of the current branch prohibited\).*", + "Cannot unpublish the default branch", + "To unpublish this branch, log in to " + ApiClient.GitHubDotComHostName + " and change " + + "the repository’s default branch to another branch first.")) + }, + { ErrorType.ClipboardFailed, Map(Defaults("Failed to copy text to the clipboard.")) }, + { + ErrorType.ClonedFailed, Map(Defaults("Failed to clone the repository '{0}'", "Please check your log file for more details, or email support if you are still having problems."), + new[] + { + new Translation(@"fatal: bad config file line (\d+) in (.+)", "Failed to clone the repository '{0}'", @"The config file '$2' is corrupted at line $1. You may need to open the file and try to fix any errors."), + new Translation("Process timed out", "Failed to clone the repository '{0}'", "The process timed out. The repository is in an unknown state and likely corrupted. Try deleting it and cloning again."), + new Translation("Local inaccessible repositories already exist.", "Failed to clone the repository '{0}'", "Local directories with this repository’s name already exist but can’t be accessed. Try deleting them and trying again."), + new Translation("Repo directory '(.*?)' already exists.", "Failed to clone the repository '{0}'", "Could not clone the repository '{0}' because the directory\n'$1' already exists and isn’t empty."), + new Translation("Local clone at '(.*?)' is corrupted", "Failed to clone the repository '{0}'", "Failed to clone the repository because a local one exists already at '$1', but is corrupted. You will need to open a shell to debug the state of this repo."), + new Translation("Your local changes to the following files would be overwritten by checkout", "Failed to check out branch", "Failed to check out branch because because local changes would be overwritten. Try committing changes and then checking out the branch again") + }) + }, + { ErrorType.CloneFailedNotLoggedIn, Map(Defaults("Clone failed", "Please login to your account before attempting to clone this repository.")) }, + { ErrorType.EnterpriseConnectFailed, Map(Defaults("Connecting to GitHub Enterprise instance failed", "Could not find a GitHub Enterprise instance at '{0}'. Double check the URL and your internet/intranet connection.")) }, + { ErrorType.LaunchEnterpriseConnectionFailed, Map(Defaults("Failed to launch the enterprise connection.")) }, + { ErrorType.LogFileError, Map(Defaults("Could not open the log file", "Could not find or open the log file.")) }, + { ErrorType.LoginFailed, Map(Defaults("login failed", "Unable to retrieve your user info from the server. A proxy server might be interfering with the request.")) }, + { ErrorType.RepoCreationAsPrivateNotAvailableForFreePlan, Map(Defaults("Failed to create private repository", "You are currently on a free plan and unable to create private repositories. Either make the repository public or upgrade your account on the website to a plan that allows for private repositories.")) }, + { ErrorType.RepoCreationFailed, Map(Defaults("Failed to create repository", "An error occurred while creating the repository. You might need to open a shell and debug the state of this repo.")) }, + { ErrorType.RepoExistsOnDisk, Map(Defaults("Failed to create repository", "There is already a repository named '{0}' in the directory\n'{1}'.")) }, + { ErrorType.RepositoryNotFoundOnDisk, Map(Defaults("Repository not found", "Could not find the repository '{0}' in the location '{1}'.\nDid you move it somewhere else on your filesystem?")) }, + { ErrorType.RepoExistsForUser, Map(Defaults("Failed to create repository", "There is already a repository named '{0}' in your GitHub account.")) }, + { ErrorType.RepoExistsInOrganization, Map(Defaults("Failed to create repository", "There is already a repository named '{0}' in the organization '{1}'.")) }, + { ErrorType.LoadingWorkingDirectoryFailed, Map(Defaults("Failed to refresh the working directory", "You might need to open a shell and debug the state of this repo.")) }, + { + ErrorType.RefreshFailed, Map(Defaults("Refresh failed", "Refresh failed unexpectedly. Please email support@github.com if this error persists."), + new Translation("Refresh failed", "Could not connect to the remote server. The server or your internect connection could be down")) }, + })); + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] + static readonly Logger log = LogManager.GetCurrentClassLogger(); + + public static IObservable ShowUserErrorMessage(this Exception exception, ErrorType errorType, params object[] messageArgs) + { + return exception.DisplayErrorMessage(errorType, messageArgs, null); + } + + public static IObservable ShowUserErrorMessage(ErrorType errorType, params object[] messageArgs) + { + return DisplayErrorMessage(null, errorType, messageArgs, null); + } + + public static IObservable ShowUserThatRepoAlreadyExists(string repositoryName, string fullPath) + { + return DisplayErrorMessage(ErrorType.RepoExistsOnDisk, new object[] { repositoryName, fullPath }, new[] { OpenPathInExplorer(fullPath), Cancel }); + } + + public static IObservable ShowCloneError(this Exception exception, + ErrorType errorType, + string displayName, + string repositoryLocalWorkingDirectory) + { + return exception.DisplayErrorMessage(errorType, new object[] { displayName }, null); + } + + public static IObservable ShowUserErrorThatRequiresNavigatingToBilling( + this Exception exception, + IAccount account) + { + var errorType = (exception is PrivateRepositoryQuotaExceededException && account.IsOnFreePlan) + ? ErrorType.RepoCreationAsPrivateNotAvailableForFreePlan + : ErrorType.RepoCreationOnGitHubFailed; + + return exception.DisplayErrorMessage( + errorType, + new object[] { }, + new[] { OpenBrowser("View Plans", account.Billing()), Cancel }); + } + + static IObservable DisplayErrorMessage(ErrorType errorType, object[] messageArgs, IEnumerable recoveryOptions) + { + return DisplayErrorMessage(null, errorType, messageArgs, recoveryOptions); + } + + static IObservable DisplayErrorMessage(this Exception exception, ErrorType errorType, object[] messageArgs, IEnumerable recoveryOptions) + { + var userError = Translator.Value.GetUserError(errorType, exception, messageArgs); + + if (recoveryOptions != null) + { + userError.RecoveryOptions.AddRange(recoveryOptions); + } + if (!userError.RecoveryOptions.Any()) + userError.RecoveryOptions.Add(Ok); + + return userError.Throw(); + } + + public static string ParseUnverifiedSshKeyMessageFromExceptionMessage(Exception exception) + { + var index = exception.Message.IndexOf("[EPOLICYKEYAGE]", StringComparison.OrdinalIgnoreCase); + return index != -1 ? + exception.Message.Remove(index).Trim() + : exception.Message; + } + + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + static IRecoveryCommand Ok + { + get + { + return new RecoveryCommandWithIcon("OK", "check", x => RecoveryOptionResult.CancelOperation) + { + IsDefault = true, + IsCancel = true + }; + } + } + + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + static IRecoveryCommand Cancel + { + get + { + return new RecoveryCommandWithIcon("Cancel", "x", x => RecoveryOptionResult.CancelOperation) + { + IsCancel = true + }; + } + } + + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + static IRecoveryCommand OpenPathInExplorer(string path) + { + return new RecoveryCommandWithIcon("Open in Explorer", "file_directory", x => + { + Process.Start(path); + return RecoveryOptionResult.CancelOperation; + }); + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "url")] + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + static IRecoveryCommand OpenBrowser(string text, string url) + { + return new RecoveryCommandWithIcon(text, "link_external", x => + { + //IoC.Get().OpenUrl(url); + return RecoveryOptionResult.CancelOperation; + }) + { + IsDefault = true, + }; + } + + static IObservable Throw(this UserError error) + { + //log.WarnException("Showing user error " + error.ErrorCauseOrResolution, error.InnerException); + + return UserError.Throw(error); + } + + static ErrorMessage Defaults(string heading, string description) + { + return new ErrorMessage(heading, description); + } + + static ErrorMessage Defaults(string description) + { + return new ErrorMessage("error", description); + } + + static ErrorMap Map(ErrorMessage defaultMessage, params Translation[] translations) + { + return new ErrorMap(defaultMessage, translations, null); + } + + static ErrorMap Map(ErrorMessage defaultMessage, IEnumerable recoveryCommands) + { + return new ErrorMap(defaultMessage, null, recoveryCommands); } } } diff --git a/src/GitHub.App/Services/Translation.cs b/src/GitHub.App/Services/Translation.cs new file mode 100644 index 000000000..a02ac7582 --- /dev/null +++ b/src/GitHub.App/Services/Translation.cs @@ -0,0 +1,122 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Octokit; +using GitHub.Extensions; + +namespace GitHub.Services +{ + public class Translation + { + readonly ErrorMessage defaultMessage; + readonly Func translator; + + public Translation(string original, string heading, string message) + { + Original = original; + defaultMessage = new ErrorMessage(heading, message); + } + + public Translation(string original, Func translator) + { + Original = original; + this.translator = translator; + } + + public Translation(string original, string heading, string messageFormatString, Func translator) : this(original, heading, messageFormatString) + { + this.translator = e => new ErrorMessage(heading, String.Format(CultureInfo.InvariantCulture, messageFormatString, translator(e))); + } + + public string Original { get; private set; } + + public ErrorMessage Translate(Exception exception) + { + if (exception == null) throw new ArgumentNullException("exception"); + + var match = Match(exception); + if (match == null) return null; + + if (translator == null) + { + var exceptionMessageLine = match.Item2; + if (exceptionMessageLine != null) + { + var heading = defaultMessage.HeadingHasPlaceholders + ? Regex.Replace(exceptionMessageLine, Original, defaultMessage.Heading) + : defaultMessage.Heading; + + var message = defaultMessage.MessageHasPlaceholders + ? Regex.Replace(exceptionMessageLine, Original, defaultMessage.Message) + : defaultMessage.Message; + return new ErrorMessage(heading, message); + } + + return defaultMessage; + } + + return translator(exception); + } + + // Returns a tuple indicating whether this translation is a match for the exception and the regex line if existing. + protected virtual Tuple Match(Exception exception) + { + string exceptionMessage = exception.Message; + + var apiException = exception as ApiValidationException; + if (apiException != null && apiException.ApiError != null && apiException.ApiError.Errors != null) + { + var error = apiException.ApiError.Errors.FirstOrDefault(); + if (error != null) + exceptionMessage = error.Message ?? exceptionMessage; + } + + if (Original == exceptionMessage) return new Tuple(this, null); + + var exceptionMessageLines = exceptionMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + if (exceptionMessageLines.Any(l => l == Original)) return new Tuple(this, null); + + try + { + // If the response is a JSON response from the API, let's + // look at all the messages for an exact match. + var githubError = + exceptionMessage.StartsWith('{') || exceptionMessage.StartsWith('[') + ? JsonConvert.DeserializeObject(exceptionMessage) + : null; + if (githubError != null + && (githubError.Message == Original + || (githubError.Errors != null && githubError.Errors.Any(e => e.Message == Original)))) + { + return new Tuple(this, null); + } + } + catch (Exception) + { + // Ignore. We were probably wrong about this being a proper GitHubError API response. + } + + var regexMatchingLine = exceptionMessageLines.FirstOrDefault(l => Regex.IsMatch(l, Original, RegexOptions.IgnoreCase)); + return regexMatchingLine != null ? new Tuple(this, regexMatchingLine) : null; + } + } + + public class Translation : Translation where TException : Exception + { + public Translation(string heading, string message) : base(null, heading, message) + { + } + + public Translation(string heading) : base(null, e => new ErrorMessage(heading, e.Message)) + { + } + + protected override Tuple Match(Exception exception) + { + if (exception is TException) return new Tuple(null, null); + return null; + } + } +} diff --git a/src/GitHub.App/UserErrors/PrivateRepositoryOnFreeAccountUserError.cs b/src/GitHub.App/UserErrors/PrivateRepositoryOnFreeAccountUserError.cs new file mode 100644 index 000000000..cb83fe264 --- /dev/null +++ b/src/GitHub.App/UserErrors/PrivateRepositoryOnFreeAccountUserError.cs @@ -0,0 +1,13 @@ +using ReactiveUI; + +namespace GitHub.UserErrors +{ + public class PrivateRepositoryOnFreeAccountUserError : PublishRepositoryUserError + { + public PrivateRepositoryOnFreeAccountUserError(string errorMessage, string errorCauseOrResolution = null) + : base(errorMessage, errorCauseOrResolution) + { + UserErrorIcon = StockUserErrorIcon.Error; + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs b/src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs new file mode 100644 index 000000000..30646ca77 --- /dev/null +++ b/src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs @@ -0,0 +1,29 @@ +using ReactiveUI; +using System; +using System.Globalization; + +namespace GitHub.UserErrors +{ + public class PrivateRepositoryQuotaExceededUserError : PublishRepositoryUserError + { + public PrivateRepositoryQuotaExceededUserError(IAccount account, string errorMessage, string errorCauseOrResolution = null) + : base(errorMessage, errorCauseOrResolution) + { + UserErrorIcon = StockUserErrorIcon.Error; + UsedPrivateSlots = account.OwnedPrivateRepos; + AvaliblePrivateSlots = account.PrivateReposInPlan; + } + + public long AvaliblePrivateSlots { get; set; } + + public int UsedPrivateSlots { get; set; } + + public static IObservable Throw(IAccount account) + { + var errorMessage = string.Format(CultureInfo.InvariantCulture, + "You are using {0} out of {1} private repositories.", account.OwnedPrivateRepos, account.PrivateReposInPlan); + + return UserError.Throw(new PrivateRepositoryQuotaExceededUserError(account, errorMessage)); + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/UserErrors/PublishRepositoryUserError.cs b/src/GitHub.App/UserErrors/PublishRepositoryUserError.cs new file mode 100644 index 000000000..cf3852d38 --- /dev/null +++ b/src/GitHub.App/UserErrors/PublishRepositoryUserError.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Octokit; +using ReactiveUI; +using GitHub.Services; + +namespace GitHub.UserErrors +{ + public class PublishRepositoryUserError : UserError + { + public PublishRepositoryUserError(string errorMessage, string errorCauseOrResolution = null) + : base(errorMessage, errorCauseOrResolution) + { + UserErrorIcon = StockUserErrorIcon.Error; + } + + public static IObservable Throw(Exception innerException = null) + { + var translation = StandardUserErrors.Translator.Value.GetUserErrorTranslation(ErrorType.RepoCreationOnGitHubFailed, innerException); + return UserError.Throw(new PublishRepositoryUserError(translation.ErrorMessage, translation.CauseOrResolution)); + } + + public static IObservable Throw(string errorMessage, string errorCauseOrResolution = null) + { + return UserError.Throw(new PublishRepositoryUserError(errorMessage, errorCauseOrResolution)); + } + + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Because no. It's only for that kind of exception")] + public static void ThrowPrivateQuotaExceeded(PrivateRepositoryQuotaExceededException exception, IAccount account) + { + if (account.IsOnFreePlan) + { + var translation = StandardUserErrors.Translator.Value.GetUserErrorTranslation(ErrorType.RepoCreationOnGitHubFailed, exception); + UserError.Throw(new PrivateRepositoryOnFreeAccountUserError(translation.ErrorMessage, translation.CauseOrResolution)); + } + else + { + PrivateRepositoryQuotaExceededUserError.Throw(account); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/CloneRepoViewModel.cs b/src/GitHub.App/ViewModels/CloneRepoViewModel.cs new file mode 100644 index 000000000..d18ade746 --- /dev/null +++ b/src/GitHub.App/ViewModels/CloneRepoViewModel.cs @@ -0,0 +1,26 @@ +using GitHub.Models; +using GitHub.UI; +using ReactiveUI; +using System; +using System.ComponentModel.Composition; +using System.Windows.Input; + +namespace GitHub.ViewModels +{ + [Export(typeof(ICloneRepoViewModel))] + public class CloneRepoViewModel : ICloneRepoViewModel + { + public ReactiveCommand CancelCommand { get; private set; } + public ICommand CancelCmd { get { return CancelCommand; } } + public IObservable Cancelling { get { return CancelCommand; } } + + public ReactiveCommand OkCommand { get; private set; } + public ICommand OkCmd { get { return OkCommand; } } + + [ImportingConstructor] + public CloneRepoViewModel(IRepositoryHosts hosts) + { + + } + } +} diff --git a/src/GitHub.App/ViewModels/CreateRepoViewModel.cs b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs new file mode 100644 index 000000000..708ce2ae6 --- /dev/null +++ b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs @@ -0,0 +1,39 @@ +using GitHub.UI; +using GitHub.Validation; +using ReactiveUI; +using System; +using System.ComponentModel.Composition; +using System.Reactive; +using System.Windows.Input; + +namespace GitHub.ViewModels +{ + [Export(typeof(ICreateRepoViewModel))] + public class CreateRepoViewModel : ICreateRepoViewModel + { + public ReactiveCommand CancelCommand { get; private set; } + public ICommand CancelCmd { get { return CancelCommand; } } + + public ReactiveCommand OkCommand { get; private set; } + public ICommand OkCmd { get { return OkCommand; } } + + public string RepositoryName { get; private set; } + public string SafeRepositoryName { get; private set; } + public bool ShowRepositoryNameWarning { get; private set; } + public string RepositoryNameWarningText { get; private set; } + public ReactivePropertyValidator RepositoryNameValidator { get; private set; } + public string Description { get; set; } + public ReactivePropertyValidator SelectedAccountValidator { get; private set; } + public bool KeepPrivate { get; set; } + public bool CanKeepPrivate { get; private set; } + public bool ShowUpgradeToMicroPlanWarning { get; private set; } + public bool ShowUpgradePlanWarning { get; private set; } + public ReactiveCommand CreateRepository { get; private set; } + public bool IsPublishing { get; private set; } + public ReactiveCommand UpgradeAccountPlan { get; private set; } + public ReactiveCommand Reset { get; private set; } + public ReactiveList Accounts { get; private set; } + public IAccount SelectedAccount { get; private set; } + + } +} diff --git a/src/GitHub.App/ViewModels/LoginControlViewModel.cs b/src/GitHub.App/ViewModels/LoginControlViewModel.cs index 3d2e642b6..cabbc733e 100644 --- a/src/GitHub.App/ViewModels/LoginControlViewModel.cs +++ b/src/GitHub.App/ViewModels/LoginControlViewModel.cs @@ -11,17 +11,17 @@ using GitHub.Info; using GitHub.Models; using GitHub.Services; using GitHub.Validation; -using GitHub.Exports; using NullGuard; using ReactiveUI; using System.Windows.Input; +using GitHub.UI; namespace GitHub.ViewModels { [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] - [Export(typeof(ILoginDialog))] + [Export(typeof(ILoginViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - public class LoginControlViewModel : ReactiveValidatableObject, ILoginDialog, IDisposable + public class LoginControlViewModel : ReactiveValidatableObject, ILoginViewModel, IDisposable { readonly Lazy lazyEnterpriseProbe; const string notEnterpriseServerError = "Not an Enterprise server. Please enter an Enterprise URL"; @@ -30,7 +30,7 @@ namespace GitHub.ViewModels public ICommand LoginCmd { get { return LoginCommand; } } public ReactiveCommand CancelCommand { get; private set; } public ICommand CancelCmd { get { return CancelCommand; } } - public IObservable CancelEvt { get { return CancelCommand; } } + public IObservable Cancelling { get { return CancelCommand; } } public ReactiveCommand ForgotPasswordCommand { get; private set; } public ReactiveCommand ShowDotComLoginCommand { get; set; } diff --git a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs index 996991bcd..f3a7827f4 100644 --- a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs +++ b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs @@ -8,13 +8,14 @@ using GitHub.Validation; using NullGuard; using Octokit; using ReactiveUI; -using GitHub.Exports; +using GitHub.UI; +using System.Windows.Input; namespace GitHub.ViewModels { - [Export(typeof(ITwoFactorDialog))] + [Export(typeof(ITwoFactorViewModel))] [PartCreationPolicy(CreationPolicy.Shared)] - public class TwoFactorDialogViewModel : ReactiveValidatableObject, ITwoFactorDialog + public class TwoFactorDialogViewModel : ReactiveValidatableObject, ITwoFactorViewModel { bool isAuthenticationCodeSent; string authenticationCode; @@ -106,6 +107,11 @@ namespace GitHub.ViewModels public ReactiveCommand ShowHelpCommand { get; private set; } public ReactiveCommand ResendCodeCommand { get; private set; } + public ICommand OkCmd { get { return OkCommand; } } + public ICommand CancelCmd { get { return CancelCommand; } } + public ICommand ShowHelpCmd { get { return ShowHelpCommand; } } + public ICommand ResendCodeCmd { get { return ResendCodeCommand; } } + public IObservable Show(TwoFactorRequiredUserError error) { TwoFactorType = error.TwoFactorType; diff --git a/src/GitHub.App/packages.config b/src/GitHub.App/packages.config index 5ae311b6a..fec3cf686 100644 --- a/src/GitHub.App/packages.config +++ b/src/GitHub.App/packages.config @@ -19,4 +19,5 @@ + \ No newline at end of file diff --git a/src/GitHub.Exports/Authentication/AuthenticationResult.cs b/src/GitHub.Exports/Authentication/AuthenticationResult.cs index 9457cb679..547f18537 100644 --- a/src/GitHub.Exports/Authentication/AuthenticationResult.cs +++ b/src/GitHub.Exports/Authentication/AuthenticationResult.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GitHub.Exports +namespace GitHub.Authentication { public enum AuthenticationResult { diff --git a/src/GitHub.Exports/Authentication/AuthenticationResultExtensions.cs b/src/GitHub.Exports/Authentication/AuthenticationResultExtensions.cs index 7a73a121d..3a57e63c6 100644 --- a/src/GitHub.Exports/Authentication/AuthenticationResultExtensions.cs +++ b/src/GitHub.Exports/Authentication/AuthenticationResultExtensions.cs @@ -1,6 +1,4 @@ -using GitHub.Exports; - -namespace GitHub.Authentication +namespace GitHub.Authentication { public static class AuthenticationResultExtensions diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 268831f0b..05079c555 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -42,6 +42,7 @@ ..\..\packages\Octokit.0.6.2\lib\net45\Octokit.dll + @@ -51,22 +52,30 @@ + + + + + + + - - - + + + + diff --git a/src/GitHub.Exports/Helpers/ImageHelper.cs b/src/GitHub.Exports/Helpers/ImageHelper.cs new file mode 100644 index 000000000..644e376f2 --- /dev/null +++ b/src/GitHub.Exports/Helpers/ImageHelper.cs @@ -0,0 +1,15 @@ +using System; +using System.Windows.Media.Imaging; + +namespace GitHub.Helpers +{ + public static class ImageHelper + { + public static BitmapImage CreateBitmapImage(string packUrl) + { + var bitmap = new BitmapImage(new Uri(packUrl)); + bitmap.Freeze(); + return bitmap; + } + } +} diff --git a/src/GitHub.Exports/Models/IRepositoryModel.cs b/src/GitHub.Exports/Models/IRepositoryModel.cs new file mode 100644 index 000000000..77285ee89 --- /dev/null +++ b/src/GitHub.Exports/Models/IRepositoryModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.Models +{ + public interface IRepositoryModel + { + HostAddress HostAddress { get; } + int? Id { get; } + string Owner { get; set; } + string Name { get; } + string NameWithOwner { get; } + string Description { get; } + Uri HostUri { get; } + bool IsPrivate { get; } + } +} diff --git a/src/GitHub.Exports/Primitives/StringEquivalent.cs b/src/GitHub.Exports/Primitives/StringEquivalent.cs new file mode 100644 index 000000000..6f9042ebf --- /dev/null +++ b/src/GitHub.Exports/Primitives/StringEquivalent.cs @@ -0,0 +1,110 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using GitHub.Helpers; + +namespace GitHub.Primitives +{ + [Serializable] + public abstract class StringEquivalent : ISerializable, IXmlSerializable where T : StringEquivalent + { + protected string Value; + + protected StringEquivalent(string value) + { + Value = value; + } + + protected StringEquivalent() + { + } + + public abstract T Combine(string addition); + + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Add doesn't make sense in the case of a string equivalent")] + public static T operator +(StringEquivalent a, string b) + { + return a.Combine(b); + } + + public static bool operator ==(StringEquivalent a, StringEquivalent b) + { + // If both are null, or both are same instance, return true. + if (ReferenceEquals(a, b)) + { + return true; + } + + // If one is null, but not both, return false. + if (((object)a == null) || ((object)b == null)) + { + return false; + } + + // Return true if the fields match: + return a.Value.Equals(b.Value, StringComparison.OrdinalIgnoreCase); + } + + public static bool operator !=(StringEquivalent a, StringEquivalent b) + { + return !(a == b); + } + + public override bool Equals(Object obj) + { + return obj != null && Equals(obj as T) || Equals(obj as string); + } + + public bool Equals(T stringEquivalent) + { + return this == stringEquivalent; + } + + public override int GetHashCode() + { + return (Value ?? "").GetHashCode(); + } + + public virtual bool Equals(string other) + { + return other != null && Value == other; + } + + public override string ToString() + { + return Value; + } + + protected StringEquivalent(SerializationInfo info) : this(info.GetValue("Value", typeof(string)) as string) + { + } + + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Value", Value); + } + + public XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + Value = reader.ReadString(); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteString(Value); + } + + public int Length + { + get { return Value != null ? Value.Length : 0; } + } + } +} diff --git a/src/GitHub.Exports/Primitives/UriString.cs b/src/GitHub.Exports/Primitives/UriString.cs new file mode 100644 index 000000000..df9f12ae2 --- /dev/null +++ b/src/GitHub.Exports/Primitives/UriString.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; +using GitHub.Extensions; +using GitHub.Helpers; + +namespace GitHub.Primitives +{ + /// + /// This class represents a URI given to us as a string and is implicitly + /// convertible to and from string. + /// + /// + /// This typically represents a URI from an external source such as user input, a + /// Git Repo Remote, or an API URL. We try to preserve the original form and let + /// downstream clients validate the URL. This class doesn't validate the URL. It just + /// performs a best-effort to parse the URI into bits important to us. For example, + /// we need to know the HOST so we can compare against GitHub.com, GH:E instances, etc. + /// + [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly", Justification = "GetObjectData is implemented in the base class")] + [Serializable] + public class UriString : StringEquivalent, IEquatable + { + //static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); + static readonly Regex sshRegex = new Regex(@"^.+@(?(\[.*?\]|[a-z0-9-.]+?))(:(?.*?))?(/(?.*)(\.git)?)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + readonly Uri url; + + public UriString(string uriString) : base(NormalizePath(uriString)) + { + if (uriString.Length == 0) return; + if (Uri.TryCreate(uriString, UriKind.Absolute, out url)) + { + if (!url.IsFile) + SetUri(url); + else + SetFilePath(url); + } + else if (!ParseScpSyntax(uriString)) + { + SetFilePath(uriString); + } + + if (RepositoryName != null) + { + NameWithOwner = Owner != null + ? string.Format(CultureInfo.InvariantCulture, "{0}/{1}", Owner, RepositoryName) + : RepositoryName; + } + } + + static UriString ToUriString(Uri uri) + { + return uri == null ? null : new UriString(uri.ToString()); + } + + void SetUri(Uri uri) + { + Host = uri.Host; + if (uri.Segments.Any()) + { + RepositoryName = GetRepositoryName(uri.Segments.Last()); + } + + if (uri.Segments.Length > 2) + { + Owner = (uri.Segments[uri.Segments.Length - 2] ?? "").TrimEnd('/').ToNullIfEmpty(); + } + + IsHypertextTransferProtocol = uri.IsHypertextTransferProtocol(); + + if (String.IsNullOrEmpty(uri.Query)) return; + + try + { + var query = ParseQueryString(uri); + + if (query.ContainsKey("branch") && !String.IsNullOrEmpty(query["branch"])) + { + Branch = query["branch"].Replace("%2F", "/"); + } + + if (query.ContainsKey("pr") && !String.IsNullOrEmpty(query["pr"])) + { + PullRequest = query["pr"].Replace("%2F", "/"); + } + + if (query.ContainsKey("filepath") && !String.IsNullOrEmpty(query["filepath"])) + { + RelativePathToOpen = query["filepath"].Replace("%2F", "/").Replace('/', '\\'); + } + } + catch //(Exception ex) + { + //log.WarnException("Failed to read URI query", ex); + } + } + + // This is not a complete query string parsing algorithm, but it's good enough for our needs. + static IDictionary ParseQueryString(Uri uri) + { + Debug.Assert(uri.Query.StartsWith('?'), + String.Format(CultureInfo.InvariantCulture, "Uri.Query doesn't start with '?': '{0}'", uri.Query)); + return uri.Query.Substring(1).Split(new[] {'&'}, StringSplitOptions.RemoveEmptyEntries) + .Select(pair => pair.Split('=')) + .ToDictionary(pair => pair.First(), pair => pair.Length > 1 ? pair[1] : null, + StringComparer.OrdinalIgnoreCase); + } + + void SetFilePath(Uri uri) + { + Host = ""; + Owner = ""; + RepositoryName = GetRepositoryName(uri.Segments.Last()); + IsFileUri = true; + } + + void SetFilePath(string path) + { + Host = ""; + Owner = ""; + RepositoryName = GetRepositoryName(path.Replace("/", @"\").RightAfterLast(@"\")); + IsFileUri = true; + } + + // For xml serialization + protected UriString() + { + } + + bool ParseScpSyntax(string scpString) + { + var match = sshRegex.Match(scpString); + if (match.Success) + { + Host = match.Groups["host"].Value.ToNullIfEmpty(); + Owner = match.Groups["owner"].Value.ToNullIfEmpty(); + RepositoryName = GetRepositoryName(match.Groups["repo"].Value); + return true; + } + return false; + } + + public string Branch { get; private set; } + public string PullRequest { get; set; } + public string Host { get; private set; } + + public string Owner { get; private set; } + + public string RepositoryName { get; private set; } + + public string NameWithOwner { get; private set; } + + public string RelativePathToOpen { get; private set; } + + public bool IsFileUri { get; private set; } + + public bool IsValidUri + { + get { return url != null; } + } + + public Uri ToUri() + { + if (url == null) + throw new InvalidOperationException("This Uri String is not a valid Uri"); + return url; + } + + /// + /// True if the URL is HTTP or HTTPS + /// + public bool IsHypertextTransferProtocol { get; private set; } + + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + public static implicit operator UriString(string value) + { + if (value == null) return null; + + return new UriString(value); + } + + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + public static implicit operator string(UriString uriString) + { + if (uriString == null) return null; + + return uriString.Value; + } + + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "No.")] + public override UriString Combine(string addition) + { + if (url != null) + { + var urlBuilder = new UriBuilder(url); + if (!String.IsNullOrEmpty(urlBuilder.Query)) + { + var query = urlBuilder.Query; + if (query.StartsWith("?", StringComparison.Ordinal)) + { + query = query.Substring(1); + } + + if (!addition.StartsWith("&", StringComparison.Ordinal) && query.Length > 0) + { + addition = "&" + addition; + } + urlBuilder.Query = query + addition; + } + else + { + var path = url.AbsolutePath; + if (path == "/") path = ""; + if (!addition.StartsWith("/", StringComparison.Ordinal)) addition = "/" + addition; + + urlBuilder.Path = path + addition; + } + return ToUriString(urlBuilder.Uri); + } + return String.Concat(Value, addition); + } + + public override string ToString() + { + // Makes this look better in the debugger. + return Value; + } + + protected UriString(SerializationInfo info, StreamingContext context) + : this(GetSerializedValue(info)) + { + } + + static string GetSerializedValue(SerializationInfo info) + { + // First try to get the current way it's serialized, then fall back to the older way it's serialized. + string value; + try + { + value = info.GetValue("Value", typeof(string)) as string; + } + catch (SerializationException) + { + value = info.GetValue("uriString", typeof(string)) as string; + } + + return value; + } + + static string NormalizePath(string path) + { + return path == null ? null : path.Replace('\\', '/'); + } + + static string GetRepositoryName(string repositoryNameSegment) + { + if (String.IsNullOrEmpty(repositoryNameSegment) + || repositoryNameSegment.Equals("/", StringComparison.Ordinal)) + { + return null; + } + + return repositoryNameSegment.TrimEnd('/').TrimEnd(".git"); + } + + bool IEquatable.Equals(UriString other) + { + return other != null && ToString().Equals(other.ToString()); + } + } +} diff --git a/src/GitHub.VisualStudio/Helpers/ExportFactoryProvider.cs b/src/GitHub.Exports/Services/ExportFactoryProvider.cs similarity index 59% rename from src/GitHub.VisualStudio/Helpers/ExportFactoryProvider.cs rename to src/GitHub.Exports/Services/ExportFactoryProvider.cs index 775929aa5..2b83e1277 100644 --- a/src/GitHub.VisualStudio/Helpers/ExportFactoryProvider.cs +++ b/src/GitHub.Exports/Services/ExportFactoryProvider.cs @@ -1,6 +1,7 @@ -using System.ComponentModel.Composition; +using GitHub.UI; +using System.ComponentModel.Composition; -namespace GitHub.Exports +namespace GitHub.Services { [Export] public class ExportFactoryProvider @@ -13,7 +14,11 @@ namespace GitHub.Exports } [Import(AllowRecomposition =true)] - public ExportFactory LoginViewModelFactory { get; set; } + public ExportFactory LoginViewModelFactory { get; set; } + + [Import(AllowRecomposition = true)] + public ExportFactory UIControllerFactory { get; set; } + /* [Import(AllowRecomposition = true)] public ExportFactory TwoFactorViewModelFactory { get; set; } diff --git a/src/GitHub.Exports/UI/ICloneDialog.cs b/src/GitHub.Exports/UI/ICloneDialog.cs deleted file mode 100644 index b7b786d18..000000000 --- a/src/GitHub.Exports/UI/ICloneDialog.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GitHub.Exports -{ - public interface ICloneDialog - { - } -} diff --git a/src/GitHub.Exports/UI/ICloneRepoViewModel.cs b/src/GitHub.Exports/UI/ICloneRepoViewModel.cs new file mode 100644 index 000000000..8adcfdd63 --- /dev/null +++ b/src/GitHub.Exports/UI/ICloneRepoViewModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Windows.Input; + +namespace GitHub.UI +{ + public interface ICloneRepoViewModel + { + ICommand OkCmd { get; } + ICommand CancelCmd { get; } + IObservable Cancelling { get; } + } +} diff --git a/src/GitHub.Exports/UI/ILoginDialog.cs b/src/GitHub.Exports/UI/ILoginViewModel.cs similarity index 64% rename from src/GitHub.Exports/UI/ILoginDialog.cs rename to src/GitHub.Exports/UI/ILoginViewModel.cs index 796dae55e..43cbc56ec 100644 --- a/src/GitHub.Exports/UI/ILoginDialog.cs +++ b/src/GitHub.Exports/UI/ILoginViewModel.cs @@ -1,15 +1,16 @@ -using System; +using GitHub.Authentication; +using System; using System.Windows.Input; -namespace GitHub.Exports +namespace GitHub.UI { - public interface ILoginDialog + public interface ILoginViewModel { string UsernameOrEmail { get; set; } string Password { get; set; } ICommand LoginCmd { get; } ICommand CancelCmd { get; } - IObservable CancelEvt { get; } + IObservable Cancelling { get; } IObservable AuthenticationResults { get; } } } diff --git a/src/GitHub.Exports/UI/ITwoFactorDialog.cs b/src/GitHub.Exports/UI/ITwoFactorDialog.cs deleted file mode 100644 index ed486d39f..000000000 --- a/src/GitHub.Exports/UI/ITwoFactorDialog.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GitHub.Exports -{ - public interface ITwoFactorDialog - { - } -} diff --git a/src/GitHub.Exports/UI/ITwoFactorViewModel.cs b/src/GitHub.Exports/UI/ITwoFactorViewModel.cs new file mode 100644 index 000000000..a739487dd --- /dev/null +++ b/src/GitHub.Exports/UI/ITwoFactorViewModel.cs @@ -0,0 +1,18 @@ +using System.Windows.Input; + +namespace GitHub.UI +{ + public interface ITwoFactorViewModel + { + ICommand OkCmd { get; } + ICommand CancelCmd { get; } + ICommand ShowHelpCmd { get; } + ICommand ResendCodeCmd { get; } + + bool IsShowing { get; } + bool IsSms { get; } + bool IsAuthenticationCodeSent { get; } + string Description { get; } + string AuthenticationCode { get; set; } + } +} diff --git a/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj b/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj index 41675b851..111c497cf 100644 --- a/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj +++ b/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj @@ -37,6 +37,12 @@ ..\..\packages\NullGuard.Fody.1.2.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll False + + ..\..\packages\reactiveui-core.6.3.1\lib\Net45\ReactiveUI.dll + + + ..\..\packages\Splat.1.6.0\lib\Net45\Splat.dll + @@ -50,6 +56,13 @@ + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + + + ..\..\packages\Rx-XAML.2.2.5\lib\net45\System.Reactive.Windows.Threading.dll + + @@ -57,6 +70,7 @@ + diff --git a/src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs b/src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs new file mode 100644 index 000000000..2657f694d --- /dev/null +++ b/src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs @@ -0,0 +1,15 @@ +using ReactiveUI; +using System; + +namespace GitHub.Extensions +{ + public class RecoveryCommandWithIcon : RecoveryCommand + { + public string Icon { get; private set; } + + public RecoveryCommandWithIcon(string commandName, string icon, Func handler = null) : base(commandName, handler) + { + Icon = icon; + } + } +} diff --git a/src/GitHub.Extensions.Reactive/packages.config b/src/GitHub.Extensions.Reactive/packages.config index 84ab4cc12..29dbd4bb0 100644 --- a/src/GitHub.Extensions.Reactive/packages.config +++ b/src/GitHub.Extensions.Reactive/packages.config @@ -2,7 +2,12 @@ + + + + + \ No newline at end of file diff --git a/src/GitHub.Extensions/GitHub.Extensions.csproj b/src/GitHub.Extensions/GitHub.Extensions.csproj index 14e0a5f27..754c98a95 100644 --- a/src/GitHub.Extensions/GitHub.Extensions.csproj +++ b/src/GitHub.Extensions/GitHub.Extensions.csproj @@ -33,6 +33,8 @@ prompt + + ..\..\packages\NullGuard.Fody.1.2.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll False @@ -42,6 +44,7 @@ ..\..\packages\Splat.1.4.2.1\lib\Net45\Splat.dll + diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs new file mode 100644 index 000000000..d3f024c5a --- /dev/null +++ b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs @@ -0,0 +1,118 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using GitHub.Extensions; +using ReactiveUI; +using GitHub.Extensions.Reactive; + +namespace GitHub.UI +{ + public class UserErrorMessages : UserControl + { + readonly IDisposable whenAnyShowingMessage; + readonly IDisposable whenAnyDataContext; + + public UserErrorMessages() + { + whenAnyShowingMessage = this.WhenAny(x => x.UserError, x => x.Value) + .Select(x => x != null) + .Subscribe(result => + { + IsShowingMessage = result; + }); + + whenAnyDataContext = this.WhenAny(x => x.UserError, x => x.Value) + .WhereNotNull() + .Subscribe(result => + { + DataContext = result; + ErrorMessageFontWeight = result.ErrorCauseOrResolution.IsNullOrEmpty() + ? FontWeights.Normal + : FontWeights.Bold; + }); + + Unloaded += (o, e) => + { + if (whenAnyShowingMessage != null) + { + whenAnyShowingMessage.Dispose(); + } + if (whenAnyDataContext != null) + { + whenAnyDataContext.Dispose(); + } + }; + } + + public static readonly DependencyProperty IconMarginProperty = DependencyProperty.Register("IconMargin", typeof(Thickness), typeof(UserErrorMessages), new PropertyMetadata(new Thickness(0,10,7,0))); + public Thickness IconMargin + { + get { return (Thickness)GetValue(IconMarginProperty); } + set { SetValue(IconMarginProperty, value); } + } + + public static readonly DependencyProperty MessageMarginProperty = DependencyProperty.Register("MessageMargin", typeof(Thickness), typeof(UserErrorMessages)); + public Thickness MessageMargin + { + get { return (Thickness)GetValue(MessageMarginProperty); } + set { SetValue(MessageMarginProperty, value); } + } + + public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(Octicon), typeof(UserErrorMessages), new PropertyMetadata(Octicon.stop)); + public Octicon Icon + { + get { return (Octicon)GetValue(IconProperty); } + set { SetValue(IconProperty, value); } + } + + public static readonly DependencyProperty FillProperty = DependencyProperty.Register("Fill", typeof(Brush), typeof(UserErrorMessages), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(0xe7, 0x4c, 0x3c)))); + public Brush Fill + { + get { return (Brush)GetValue(FillProperty); } + set { SetValue(FillProperty, value); } + } + + public static readonly DependencyProperty ErrorMessageFontWeightProperty = DependencyProperty.Register("ErrorMessageFontWeight", typeof(FontWeight), typeof(UserErrorMessages), new PropertyMetadata(FontWeights.Bold)); + public FontWeight ErrorMessageFontWeight + { + get { return (FontWeight)GetValue(ErrorMessageFontWeightProperty); } + set { SetValue(ErrorMessageFontWeightProperty, value); } + } + + public static readonly DependencyProperty IsShowingMessageProperty = DependencyProperty.Register("IsShowingMessage", typeof(bool), typeof(UserErrorMessages)); + public bool IsShowingMessage + { + get { return (bool)GetValue(IsShowingMessageProperty); } + private set { SetValue(IsShowingMessageProperty, value); } + } + + public static readonly DependencyProperty UserErrorProperty = DependencyProperty.Register("UserError", typeof(UserError), typeof(UserErrorMessages)); + public UserError UserError + { + get { return (UserError)GetValue(UserErrorProperty); } + set { SetValue(UserErrorProperty, value); } + } + + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", + Justification = "We're registering a handler for a type so this is appropriate.")] + public IDisposable RegisterHandler(IObservable clearWhen) where TUserError : UserError + { + if (IsVisible) + { + return UserError.RegisterHandler(userError => + { + UserError = userError; + return clearWhen + .Skip(1) + .Do(_ => UserError = null) + .Select(x => RecoveryOptionResult.CancelOperation); + }); + } + return Disposable.Empty; + } + } +} diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml new file mode 100644 index 000000000..c9bcf2f8e --- /dev/null +++ b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml @@ -0,0 +1,109 @@ + + + \ No newline at end of file diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs b/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs index d5c1bf019..1d3c4cf49 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs +++ b/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs @@ -6,6 +6,7 @@ using System.Windows.Controls.Primitives; using System.Windows.Media; using GitHub.Extensions.Reactive; using ReactiveUI; +using GitHub.Validation; namespace GitHub.UI { diff --git a/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs b/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs new file mode 100644 index 000000000..8542c7e95 --- /dev/null +++ b/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs @@ -0,0 +1,148 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using ReactiveUI; +using GitHub.Extensions.Reactive; + +namespace GitHub.UI +{ + public class FilteredComboBox : ComboBox + { + TextBox filterTextBox; + + public FilteredComboBox() + { + IsTextSearchEnabled = true; + + this.WhenAny(x => x.FilterText, x => x.Value) + .WhereNotNull() + .Throttle(TimeSpan.FromSeconds(0.2), RxApp.MainThreadScheduler) + .Subscribe(filterText => + { + Items.Filter += DoesItemStartWith(filterText); + }); + + this.WhenAny(x => x.FilterText, x => x.Value) + .Where(x => x == null) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(filterText => + { + Items.Filter += x => true; + }); + + this.WhenAny(x => x.FilterText, x => x.Value) + .Select(x => !string.IsNullOrEmpty(x)) + .Subscribe(isFiltered => + { + IsListFiltered = isFiltered; + }); + } + + public static readonly DependencyProperty FilterTextProperty = DependencyProperty.Register("FilterText", typeof(string), typeof(FilteredComboBox)); + + public string FilterText + { + get { return (string)GetValue(FilterTextProperty); } + set { SetValue(FilterTextProperty, value); } + } + + public static readonly DependencyProperty IsListFilteredProperty = DependencyProperty.Register("IsListFiltered", typeof(bool), typeof(FilteredComboBox)); + + public bool IsListFiltered + { + get { return (bool)GetValue(IsListFilteredProperty); } + set { SetValue(IsListFilteredProperty, value); } + } + + public override void OnApplyTemplate() + { + filterTextBox = GetTemplateChild("PART_FilterTextBox") as TextBox; + var popUp = GetTemplateChild("PART_Popup") as Popup; + if (popUp != null) + { + popUp.CustomPopupPlacementCallback = PlacePopup; + } + base.OnApplyTemplate(); + } + + /// + /// Override selection changed, because when the ItemSource is filtered + /// selection change is triggered, and the old selection is lost. + /// This allows us to remember the previous selection when no new selection has been made + /// and prevent a selection of null, when we had a previous selection. + /// + /// The selection changed arguments + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + var hasOldSelection = e.RemovedItems != null && e.RemovedItems.Count == 1; + var hasNewSelectedItem = e.AddedItems != null && e.AddedItems.Count == 1; + + if (hasOldSelection && !hasNewSelectedItem) + { + return; + } + base.OnSelectionChanged(e); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (filterTextBox != null && char.IsLetterOrDigit((char)e.Key)) + { + filterTextBox.Focus(); + Keyboard.Focus(filterTextBox); + } + else if (e.Key == Key.Enter && IsListFiltered && Items.Count > 0) + { + SelectedItem = Items.GetItemAt(0); + IsDropDownOpen = false; + e.Handled = true; + } + else + { + base.OnKeyDown(e); + } + } + + protected override void OnDropDownOpened(EventArgs e) + { + FilterText = ""; + SelectedIndex = -1; + base.OnDropDownOpened(e); + } + + Predicate DoesItemStartWith(string filterText) + { + return item => + { + var text = TryGetSearch(item); + + var comparison = IsTextSearchCaseSensitive + ? StringComparison.Ordinal + : StringComparison.OrdinalIgnoreCase; + + return text.StartsWith(filterText, comparison); + }; + } + + public CustomPopupPlacement[] PlacePopup(Size popupSize, Size targetSize, Point offset) + { + return new[] { new CustomPopupPlacement(new Point(0, targetSize.Height), PopupPrimaryAxis.Vertical) }; + } + + string TryGetSearch(object item) + { + string text = string.Empty; + var textPath = TextSearch.GetTextPath(this); + var propertyInfo = item.GetType().GetProperty(textPath); + if (propertyInfo != null) + { + text = propertyInfo.GetValue(item).ToString(); + } + return text; + } + } +} diff --git a/src/GitHub.UI.Reactive/FodyWeavers.xml b/src/GitHub.UI.Reactive/FodyWeavers.xml index dfd2172c0..4bc3f8b09 100644 --- a/src/GitHub.UI.Reactive/FodyWeavers.xml +++ b/src/GitHub.UI.Reactive/FodyWeavers.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj b/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj index 9f9ce9824..04af293e6 100644 --- a/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj +++ b/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj @@ -11,6 +11,7 @@ GitHub.UI.Reactive v4.5 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 9a81e774 4 true @@ -88,8 +89,9 @@ Properties\SolutionInfo.cs - + + @@ -99,15 +101,25 @@ MSBuild:Compile Designer + + MSBuild:Compile + MSBuild:Compile Designer + + MSBuild:Compile + + + {e4ed0537-d1d9-44b6-9212-3096d7c3f7a1} + GitHub.Exports.Reactive + {6559E128-8B40-49A5-85A8-05565ED0C7E3} GitHub.Extensions.Reactive diff --git a/src/GitHub.UI/Assets/Controls.xaml b/src/GitHub.UI/Assets/Controls.xaml index 13309de1e..591fc5567 100644 --- a/src/GitHub.UI/Assets/Controls.xaml +++ b/src/GitHub.UI/Assets/Controls.xaml @@ -6,6 +6,9 @@ mc:Ignorable="d"> + + + @@ -13,333 +16,13 @@ Styles for standard windows controls --> - - - - - - - - - - - - - - - - diff --git a/src/GitHub.UI/Assets/Controls/GitHubLinkButton.xaml b/src/GitHub.UI/Assets/Controls/GitHubLinkButton.xaml new file mode 100644 index 000000000..690b0b5f1 --- /dev/null +++ b/src/GitHub.UI/Assets/Controls/GitHubLinkButton.xaml @@ -0,0 +1,38 @@ + + + + \ No newline at end of file diff --git a/src/GitHub.UI/Assets/Controls/LightListBox.xaml b/src/GitHub.UI/Assets/Controls/LightListBox.xaml new file mode 100644 index 000000000..43828bb91 --- /dev/null +++ b/src/GitHub.UI/Assets/Controls/LightListBox.xaml @@ -0,0 +1,104 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml b/src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml new file mode 100644 index 000000000..383619508 --- /dev/null +++ b/src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml @@ -0,0 +1,75 @@ + + + + + \ No newline at end of file diff --git a/src/GitHub.UI/Assets/TextBlocks.xaml b/src/GitHub.UI/Assets/TextBlocks.xaml index eb2e25b6f..8fb5e4dd2 100644 --- a/src/GitHub.UI/Assets/TextBlocks.xaml +++ b/src/GitHub.UI/Assets/TextBlocks.xaml @@ -47,6 +47,7 @@ + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs new file mode 100644 index 000000000..5a64ddcee --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs @@ -0,0 +1,42 @@ +using GitHub.UI; +using GitHub.UI.Helpers; +using NullGuard; +using ReactiveUI; +using System.ComponentModel.Composition; +using System.Windows; + +namespace GitHub.VisualStudio.UI.Views.Controls +{ + /// + /// Interaction logic for CloneRepoControl.xaml + /// + [Export(typeof(IViewFor))] + public partial class CloneRepoControl : IViewFor + { + public CloneRepoControl() + { + SharedDictionaryManager.Load("GitHub.UI"); + SharedDictionaryManager.Load("GitHub.UI.Reactive"); + Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary); + InitializeComponent(); + } + + public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( + "ViewModel", typeof(ICloneRepoViewModel), typeof(LoginControl), new PropertyMetadata(null)); + + + object IViewFor.ViewModel + { + get { return ViewModel; } + set { ViewModel = (ICloneRepoViewModel)value; } + } + + public ICloneRepoViewModel ViewModel + { + [return: AllowNull] + get + { return (ICloneRepoViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + } +} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml new file mode 100644 index 000000000..21c7b947d --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create repository + + + + diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs new file mode 100644 index 000000000..549f9af4c --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs @@ -0,0 +1,85 @@ +using GitHub.Extensions.Reactive; +using GitHub.UI; +using GitHub.UI.Helpers; +using GitHub.UserErrors; +using NullGuard; +using ReactiveUI; +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Windows; + +namespace GitHub.VisualStudio.UI.Views.Controls +{ + /// + /// Interaction logic for CloneRepoControl.xaml + /// + [Export(typeof(IViewFor))] + public partial class CreateRepoControl : IViewFor + { + public CreateRepoControl() + { + SharedDictionaryManager.Load("GitHub.UI"); + SharedDictionaryManager.Load("GitHub.UI.Reactive"); + Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary); + + InitializeComponent(); + + IObservable clearErrorWhenChanged = this.WhenAny( + x => x.ViewModel.RepositoryName, + x => x.ViewModel.Description, + (x, y) => new { x, y }) + .WhereNotNull() + .Select(x => true); + + this.WhenActivated(d => + { + d(this.Bind(ViewModel, vm => vm.RepositoryName, v => v.nameText.Text)); + d(this.OneWayBind(ViewModel, vm => vm.RepositoryNameValidator, v => v.nameValidationMessage.ReactiveValidator)); + d(this.OneWayBind(ViewModel, vm => vm.RepositoryNameWarningText, v => v.safeRepositoryNameWarning.Text)); + d(this.OneWayBind(ViewModel, vm => vm.ShowRepositoryNameWarning, v => v.safeRepositoryNameWarning.ShowError)); + + d(this.Bind(ViewModel, vm => vm.Description, v => v.description.Text)); + d(this.Bind(ViewModel, vm => vm.KeepPrivate, v => v.makePrivate.IsChecked)); + d(this.OneWayBind(ViewModel, vm => vm.CanKeepPrivate, v => v.makePrivate.IsEnabled)); + + d(this.OneWayBind(ViewModel, vm => vm.ShowUpgradeToMicroPlanWarning, v => v.upgradeToMicroPlanWarning.Visibility)); + d(this.OneWayBind(ViewModel, vm => vm.ShowUpgradePlanWarning, v => v.upgradePlanWarning.Visibility)); + d(this.OneWayBind(ViewModel, vm => vm.SelectedAccount.OwnedPrivateRepos, v => v.ownedPrivateReposText.Text)); + d(this.OneWayBind(ViewModel, vm => vm.SelectedAccount.PrivateReposInPlan, v => v.privateReposInPlanText.Text)); + + d(this.OneWayBind(ViewModel, vm => vm.Accounts, v => v.accountsComboBox.ItemsSource)); + d(this.Bind(ViewModel, vm => vm.SelectedAccount, v => v.accountsComboBox.SelectedItem)); + + d(this.BindCommand(ViewModel, vm => vm.CreateRepository, v => v.createRepositoryButton)); + d(this.OneWayBind(ViewModel, vm => vm.IsPublishing, v => v.createRepositoryButton.ShowSpinner)); + d(this.BindCommand(ViewModel, vm => vm.UpgradeAccountPlan, v => v.upgradeToMicroLink)); + d(this.BindCommand(ViewModel, vm => vm.UpgradeAccountPlan, v => v.upgradeAccountLink)); + + d(this.OneWayBind(ViewModel, vm => vm.IsPublishing, v => v.nameText.IsEnabled, x => x == false)); + d(this.OneWayBind(ViewModel, vm => vm.IsPublishing, v => v.description.IsEnabled, x => x == false)); + d(this.OneWayBind(ViewModel, vm => vm.IsPublishing, v => v.accountsComboBox.IsEnabled, x => x == false)); + + d(userErrorMessages.RegisterHandler(clearErrorWhenChanged)); + }); + } + + public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( + "ViewModel", typeof(ICreateRepoViewModel), typeof(LoginControl), new PropertyMetadata(null)); + + + object IViewFor.ViewModel + { + get { return ViewModel; } + set { ViewModel = (ICreateRepoViewModel)value; } + } + + public ICreateRepoViewModel ViewModel + { + [return: AllowNull] + get + { return (ICreateRepoViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + } +} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml index 266ca384c..b12baa675 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml +++ b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml @@ -6,16 +6,13 @@ xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" mc:Ignorable="d" - d:DesignHeight="300" - d:DesignWidth="300"> + d:DesignHeight="541" + d:DesignWidth="345" Background="White"> - - - - - + + diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs index c6acca248..2c272748f 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs @@ -3,19 +3,28 @@ using GitHub.ViewModels; using NullGuard; using ReactiveUI; using GitHub.Exports; +using System.ComponentModel.Composition; +using GitHub.UI; +using GitHub.UI.Helpers; +using System.Diagnostics; namespace GitHub.VisualStudio.UI.Views.Controls { /// /// Interaction logic for LoginControl.xaml /// - public partial class LoginControl : IViewFor + [Export(typeof(IViewFor))] + public partial class LoginControl : IViewFor { public LoginControl() { + SharedDictionaryManager.Load("GitHub.UI"); + SharedDictionaryManager.Load("GitHub.UI.Reactive"); + Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary); + InitializeComponent(); - DataContextChanged += (s, e) => ViewModel = (ILoginDialog)e.NewValue; + DataContextChanged += (s, e) => ViewModel = (ILoginViewModel)e.NewValue; this.WhenActivated(d => { @@ -29,19 +38,19 @@ namespace GitHub.VisualStudio.UI.Views.Controls } public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( - "ViewModel", typeof(ILoginDialog), typeof(LoginControl), new PropertyMetadata(null)); + "ViewModel", typeof(ILoginViewModel), typeof(LoginControl), new PropertyMetadata(null)); object IViewFor.ViewModel { get { return ViewModel; } - set { ViewModel = (ILoginDialog)value; } + set { ViewModel = (ILoginViewModel)value; } } - public ILoginDialog ViewModel + public ILoginViewModel ViewModel { [return: AllowNull] - get { return (ILoginDialog)GetValue(ViewModelProperty); } + get { return (ILoginViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } } diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml new file mode 100644 index 000000000..764c1f454 --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs new file mode 100644 index 000000000..fabe000df --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs @@ -0,0 +1,69 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Input; +using ReactiveUI; +using GitHub.UI; +using GitHub.UI.Helpers; + +namespace GitHub.VisualStudio.UI.Views.Controls +{ + /// + /// Interaction logic for PasswordView.xaml + /// + [Export(typeof(IViewFor))] + public partial class TwoFactorControl : IViewFor + { + public TwoFactorControl() + { + SharedDictionaryManager.Load("GitHub.UI"); + SharedDictionaryManager.Load("GitHub.UI.Reactive"); + Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary); + + InitializeComponent(); + DataContextChanged += (s, e) => ViewModel = (ITwoFactorViewModel)e.NewValue; + //IsVisibleChanged += (s, e) => authenticationCode.EnsureFocus(); + + this.WhenActivated(d => + { + d(this.BindCommand(ViewModel, vm => vm.OkCmd, view => view.okButton)); + d(this.BindCommand(ViewModel, vm => vm.CancelCmd, view => view.cancelButton)); + d(this.BindCommand(ViewModel, vm => vm.ShowHelpCmd, view => view.helpButton)); + d(this.BindCommand(ViewModel, vm => vm.ResendCodeCmd, view => view.resendCodeButton)); + + d(this.Bind(ViewModel, vm => vm.AuthenticationCode, view => view.authenticationCode.Text)); + d(this.OneWayBind(ViewModel, vm => vm.IsAuthenticationCodeSent, + view => view.authenticationSentLabel.Visibility)); + d(this.OneWayBind(ViewModel, vm => vm.IsSms, view => view.resendCodeButton.Visibility)); + d(this.OneWayBind(ViewModel, vm => vm.Description, view => view.description.Text)); + d(MessageBus.Current.Listen() + .Where(x => ViewModel.IsShowing && x.Key == Key.Escape && !x.Handled) + .Subscribe(async key => + { + key.Handled = true; + await ((ReactiveCommand)ViewModel.CancelCmd).ExecuteAsync(); + })); + }); + } + + public ITwoFactorViewModel ViewModel + { + get { return (ITwoFactorViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register( + "ViewModel", + typeof(ITwoFactorViewModel), + typeof(TwoFactorControl), + new PropertyMetadata(null)); + + object IViewFor.ViewModel + { + get { return ViewModel; } + set { ViewModel = (ITwoFactorViewModel)value; } + } + } +} diff --git a/src/GitHub.VisualStudio/UI/WindowController.xaml b/src/GitHub.VisualStudio/UI/WindowController.xaml new file mode 100644 index 000000000..f3f655023 --- /dev/null +++ b/src/GitHub.VisualStudio/UI/WindowController.xaml @@ -0,0 +1,14 @@ + + + + + diff --git a/src/GitHub.VisualStudio/UI/WindowController.xaml.cs b/src/GitHub.VisualStudio/UI/WindowController.xaml.cs new file mode 100644 index 000000000..1d8ed6e28 --- /dev/null +++ b/src/GitHub.VisualStudio/UI/WindowController.xaml.cs @@ -0,0 +1,52 @@ +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Services; +using GitHub.UI; +using Microsoft.VisualStudio.PlatformUI; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace GitHub.VisualStudio.UI.Views +{ + public partial class WindowController : DialogWindow + { + IDisposable disposable; + + public WindowController(IObservable controls) + { + InitializeComponent(); + disposable = controls.Subscribe(c => + { + var control = c as UserControl; + Load(control); + }, + Close + ); + } + + protected override void OnClosed(EventArgs e) + { + disposable.Dispose(); + base.OnClosed(e); + } + + public void Load(UserControl control) + { + Container.Children.Clear(); + Container.Children.Add(control); + } + + } +} From c19487d95d4aea7a3647a7eb9dee36147936f58b Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 24 Feb 2015 22:26:46 +0100 Subject: [PATCH 15/35] Fix build --- src/GitHub.Exports/GitHub.Exports.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 05079c555..5ab3a17f5 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -47,6 +47,7 @@ + From c01900f602aab771d147379a46f8b73374522531 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 25 Feb 2015 00:29:46 +0100 Subject: [PATCH 16/35] Fix build again --- src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index a16273184..b6c802a10 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -58,6 +58,7 @@ ..\..\packages\Rx-XAML.2.2.5\lib\net45\System.Reactive.Windows.Threading.dll + From 8978c8d645b792c54a657b80d23bb423e3aac09b Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 25 Feb 2015 01:08:14 +0100 Subject: [PATCH 17/35] The build, it is fixed --- build.cmd | 2 +- src/GitHub.App/SampleData/SampleViewModels.cs | 4 ++-- src/GitHub.VisualStudio/GitHub.VisualStudio.csproj | 7 +------ 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/build.cmd b/build.cmd index f74c00d5a..7b7f67072 100644 --- a/build.cmd +++ b/build.cmd @@ -1 +1 @@ -powershell.exe .\build\cibuild.ps1 \ No newline at end of file +powershell.exe .\script\cibuild.ps1 \ No newline at end of file diff --git a/src/GitHub.App/SampleData/SampleViewModels.cs b/src/GitHub.App/SampleData/SampleViewModels.cs index 563294d40..ba38f61b2 100644 --- a/src/GitHub.App/SampleData/SampleViewModels.cs +++ b/src/GitHub.App/SampleData/SampleViewModels.cs @@ -47,8 +47,8 @@ namespace GitHub.SampleData public ReactiveList Accounts { get; private set; } public IAccount SelectedAccount { get; private set; } - public ICommand OkCmd { get; } - public ICommand CancelCmd { get; } + public ICommand OkCmd { get; private set; } + public ICommand CancelCmd { get; private set; } } [ExcludeFromCodeCoverage] diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index f1ef4c788..83217de0e 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -34,7 +34,7 @@ DEBUG;TRACE prompt 4 - false + true ..\..\script\GitHubVS.ruleset true true @@ -299,11 +299,6 @@ MSBuild:Compile - - MSBuild:Compile - Designer - true - From 44ea807952db7c09d8746ead01b319a2fe442d1a Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 25 Feb 2015 21:24:49 +0100 Subject: [PATCH 18/35] Move view models to ViewModels --- src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj | 2 +- .../{UI => ViewModels}/ICreateRepoViewModel.cs | 0 src/GitHub.Exports/GitHub.Exports.csproj | 6 +++--- .../{UI => ViewModels}/ICloneRepoViewModel.cs | 0 src/GitHub.Exports/{UI => ViewModels}/ILoginViewModel.cs | 0 .../{UI => ViewModels}/ITwoFactorViewModel.cs | 0 6 files changed, 4 insertions(+), 4 deletions(-) rename src/GitHub.Exports.Reactive/{UI => ViewModels}/ICreateRepoViewModel.cs (100%) rename src/GitHub.Exports/{UI => ViewModels}/ICloneRepoViewModel.cs (100%) rename src/GitHub.Exports/{UI => ViewModels}/ILoginViewModel.cs (100%) rename src/GitHub.Exports/{UI => ViewModels}/ITwoFactorViewModel.cs (100%) diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index b6c802a10..75a49b028 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -77,7 +77,7 @@ - + diff --git a/src/GitHub.Exports.Reactive/UI/ICreateRepoViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs similarity index 100% rename from src/GitHub.Exports.Reactive/UI/ICreateRepoViewModel.cs rename to src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 5ab3a17f5..f708225ed 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -72,9 +72,9 @@ - - - + + + diff --git a/src/GitHub.Exports/UI/ICloneRepoViewModel.cs b/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs similarity index 100% rename from src/GitHub.Exports/UI/ICloneRepoViewModel.cs rename to src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs diff --git a/src/GitHub.Exports/UI/ILoginViewModel.cs b/src/GitHub.Exports/ViewModels/ILoginViewModel.cs similarity index 100% rename from src/GitHub.Exports/UI/ILoginViewModel.cs rename to src/GitHub.Exports/ViewModels/ILoginViewModel.cs diff --git a/src/GitHub.Exports/UI/ITwoFactorViewModel.cs b/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs similarity index 100% rename from src/GitHub.Exports/UI/ITwoFactorViewModel.cs rename to src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs From f691718b9d3d9892892c62bbf834278e8c496d1b Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 25 Feb 2015 21:36:09 +0100 Subject: [PATCH 19/35] Refactor how views and viewmodels are exported Export all views and viewmodels with metadata and a common interface so we can easily add more. ExportFactoryProvider exposes lists of views and viewmodels that can be disposed of. Add some UI testing to the DesignTimeStyleHelper app, it's faster to iterate over the UI outside the extension itself. --- src/DesignTimeStyleHelper/App.xaml | 4 +- src/DesignTimeStyleHelper/App.xaml.cs | 106 +++++++++++++++++- .../DesignTimeStyleHelper.csproj | 27 +++++ src/DesignTimeStyleHelper/MainWindow.xaml | 5 +- src/DesignTimeStyleHelper/MainWindow.xaml.cs | 8 +- src/DesignTimeStyleHelper/packages.config | 4 + src/GitHub.App/Controllers/UIController.cs | 106 ++++++++++-------- .../ViewModels/CloneRepoViewModel.cs | 2 +- .../ViewModels/CreateRepoViewModel.cs | 2 +- .../ViewModels/LoginControlViewModel.cs | 3 +- .../ViewModels/TwoFactorDialogViewModel.cs | 2 +- .../GitHub.Exports.Reactive.csproj | 4 + .../ViewModels/ICreateRepoViewModel.cs | 2 +- src/GitHub.Exports/GitHub.Exports.csproj | 1 + .../Primitives/ExportMetadata.cs | 39 +++++++ .../Services/ExportFactoryProvider.cs | 33 ++++-- .../ViewModels/ICloneRepoViewModel.cs | 2 +- .../ViewModels/ILoginViewModel.cs | 2 +- .../ViewModels/ITwoFactorViewModel.cs | 2 +- src/GitHub.Exports/ViewModels/IViewModel.cs | 8 ++ .../Base/TeamExplorerSectionBase.cs | 6 + .../PlaceholderGitHubSection.cs | 1 + .../TeamExplorerHome/GitHubHomeSection.cs | 6 +- .../Views/Controls/CloneRepoControl.xaml.cs | 2 +- .../Views/Controls/CreateRepoControl.xaml.cs | 2 +- .../UI/Views/Controls/LoginControl.xaml.cs | 2 +- .../Views/Controls/TwoFactorControl.xaml.cs | 2 +- 27 files changed, 306 insertions(+), 77 deletions(-) create mode 100644 src/DesignTimeStyleHelper/packages.config create mode 100644 src/GitHub.Exports/Primitives/ExportMetadata.cs create mode 100644 src/GitHub.Exports/ViewModels/IViewModel.cs diff --git a/src/DesignTimeStyleHelper/App.xaml b/src/DesignTimeStyleHelper/App.xaml index 166aee795..81f0c67e1 100644 --- a/src/DesignTimeStyleHelper/App.xaml +++ b/src/DesignTimeStyleHelper/App.xaml @@ -6,8 +6,8 @@ - - + + diff --git a/src/DesignTimeStyleHelper/App.xaml.cs b/src/DesignTimeStyleHelper/App.xaml.cs index 41d83b6db..b0041dbf6 100644 --- a/src/DesignTimeStyleHelper/App.xaml.cs +++ b/src/DesignTimeStyleHelper/App.xaml.cs @@ -1,10 +1,20 @@ -using System; +using Microsoft.VisualStudio.ComponentModelHost; +using System; using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; using System.Configuration; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; +using System.ComponentModel.Composition.Primitives; +using Microsoft.VisualStudio.Shell; +using System.Globalization; +using GitHub.VisualStudio.TeamExplorerConnect; +using GitHub.Services; +using GitHub.VisualStudio; +using Moq; namespace DesignTimeStyleHelper { @@ -13,5 +23,99 @@ namespace DesignTimeStyleHelper /// public partial class App : Application { + public static CustomServiceProvider ServiceProvider { get; private set; } + + static App() + { + ServiceProvider = new CustomServiceProvider(); + } + } + + [Export(typeof(SVsServiceProvider))] + [Export(typeof(SComponentModel))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class CustomServiceProvider : SVsServiceProvider, IServiceProvider, + SComponentModel, IComponentModel + { + CompositionContainer container; + public CompositionContainer Container { get { return container; } } + AggregateCatalog catalog; + + public CustomServiceProvider() + { + catalog = new AggregateCatalog( + new AssemblyCatalog(typeof(GitHub.VisualStudio.Services).Assembly), // GitHub.VisualStudio + new AssemblyCatalog(typeof(GitHub.Api.ApiClient).Assembly), // GitHub.App + new AssemblyCatalog(typeof(GitHub.Api.SimpleApiClient).Assembly), // GitHub.Api + new AssemblyCatalog(typeof(Rothko.Environment).Assembly), // GitHub.Api + new AssemblyCatalog(typeof(GitHub.Services.EnterpriseProbeTask).Assembly) // GitHub.Exports + ); + container = new CompositionContainer(catalog, CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); + + DefaultExportProvider = container; + DefaultCompositionService = catalog.CreateCompositionService(); + + var batch = new CompositionBatch(); + batch.AddExportedValue(this); + batch.AddExportedValue(this); + batch.AddExportedValue(new PlaceholderGitHubSection(this)); + container.Compose(batch); + } + + + public object GetService(Type serviceType) + { + string contract = AttributedModelServices.GetContractName(serviceType); + var instance = container.GetExportedValues(contract).FirstOrDefault(); + + if (instance != null) + return instance; + + if (serviceType == typeof(IUIProvider)) + return new UIProvider(this); + if (serviceType == typeof(ExportFactoryProvider)) + return new ExportFactoryProvider(DefaultCompositionService); + + instance = Create(serviceType); + + if (instance != null) + return instance; + + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Could not locate any instances of contract {0}.", contract)); + } + + T Create() where T : class + { + return new Mock().Object; + } + + object Create(Type t) + { + var moq = typeof(Mock<>).MakeGenericType(t); + var ctor = moq.GetConstructor(new Type[] { }); + var m = ctor.Invoke(new object[] { }) as Mock; + return m.Object; + } + + + public ExportProvider DefaultExportProvider { get; set; } + public ComposablePartCatalog DefaultCatalog { get; set; } + public ICompositionService DefaultCompositionService { get; set; } + + public ComposablePartCatalog GetCatalog(string catalogName) + { + throw new NotImplementedException(); + } + + public IEnumerable GetExtensions() where T : class + { + throw new NotImplementedException(); + } + + public T GetService() where T : class + { + throw new NotImplementedException(); + } } } diff --git a/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj b/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj index 4b92c082e..31c9b08b6 100644 --- a/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj +++ b/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj @@ -34,7 +34,13 @@ 4 + + + + ..\..\packages\Moq.4.2.1312.1319\lib\net40\Moq.dll + + @@ -83,6 +89,7 @@ ResXFileCodeGenerator Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs @@ -93,6 +100,26 @@ + + {4a84e568-ca86-4510-8cd0-90d3ef9b65f9} + Rothko + + + {b389adaf-62cc-486e-85b4-2d8b078df763} + GitHub.Api + + + {1a1da411-8d1f-4578-80a6-04576bea2dc5} + GitHub.App + + + {9aea02db-02b5-409c-b0ca-115d05331a6b} + GitHub.Exports + + + {e4ed0537-d1d9-44b6-9212-3096d7c3f7a1} + GitHub.Exports.Reactive + {158b05e8-fdbc-4d71-b871-c96e28d5adf5} GitHub.UI.Reactive diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml b/src/DesignTimeStyleHelper/MainWindow.xaml index 710828d75..20329eddd 100644 --- a/src/DesignTimeStyleHelper/MainWindow.xaml +++ b/src/DesignTimeStyleHelper/MainWindow.xaml @@ -4,9 +4,10 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:DesignTimeStyleHelper" + mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> - + - + diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml.cs b/src/DesignTimeStyleHelper/MainWindow.xaml.cs index 74f0b4659..6532ca651 100644 --- a/src/DesignTimeStyleHelper/MainWindow.xaml.cs +++ b/src/DesignTimeStyleHelper/MainWindow.xaml.cs @@ -1,5 +1,7 @@ -using System; +using GitHub.VisualStudio.TeamExplorerConnect; +using System; using System.Collections.Generic; +using System.ComponentModel.Composition; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -23,6 +25,10 @@ namespace DesignTimeStyleHelper public MainWindow() { InitializeComponent(); + + var section = (PlaceholderGitHubSection)App.ServiceProvider.GetService(typeof(PlaceholderGitHubSection)); + container.Children.Add(section.SectionContent as UIElement); + } } } diff --git a/src/DesignTimeStyleHelper/packages.config b/src/DesignTimeStyleHelper/packages.config new file mode 100644 index 000000000..b04c51152 --- /dev/null +++ b/src/DesignTimeStyleHelper/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs index e2da37aa8..da9a00aca 100644 --- a/src/GitHub.App/Controllers/UIController.cs +++ b/src/GitHub.App/Controllers/UIController.cs @@ -11,14 +11,13 @@ using ReactiveUI; using GitHub.Models; using GitHub.Authentication; using System.Diagnostics; +using GitHub.Exports; namespace GitHub.VisualStudio.UI { [Export(typeof(IUIController))] public class UIController : IUIController, IDisposable { - - enum UIState { Start, Auth, TwoFA, Create, Clone, End } enum Trigger { Auth = 1, Create = 2, Clone = 3, Next, Previous } readonly ExportFactoryProvider factory; @@ -26,26 +25,41 @@ namespace GitHub.VisualStudio.UI CompositeDisposable disposables = new CompositeDisposable(); Subject transition; UIControllerFlow currentFlow; - StateMachine machine; + StateMachine machine; [ImportingConstructor] - public UIController(IUIProvider uiProvider, IRepositoryHosts hosts) + public UIController(IUIProvider uiProvider, IRepositoryHosts hosts, ExportFactoryProvider factory) { - factory = uiProvider.GetService(); - - machine = new StateMachine(UIState.Start); + this.factory = factory; - machine.Configure(UIState.Start) - .Permit(Trigger.Auth, UIState.Auth) - .PermitIf(Trigger.Create, UIState.Create, () => hosts.IsLoggedInToAnyHost) - .PermitIf(Trigger.Create, UIState.Auth, () => !hosts.IsLoggedInToAnyHost) - .PermitIf(Trigger.Clone, UIState.Clone, () => hosts.IsLoggedInToAnyHost) - .PermitIf(Trigger.Clone, UIState.Auth, () => !hosts.IsLoggedInToAnyHost); + machine = new StateMachine(UIViewType.Start); - machine.Configure(UIState.Auth) + machine.Configure(UIViewType.None) + .Permit(Trigger.Auth, UIViewType.Login) + .PermitIf(Trigger.Create, UIViewType.Create, () => hosts.IsLoggedInToAnyHost) + .PermitIf(Trigger.Create, UIViewType.Login, () => !hosts.IsLoggedInToAnyHost) + .PermitIf(Trigger.Clone, UIViewType.Clone, () => hosts.IsLoggedInToAnyHost) + .PermitIf(Trigger.Clone, UIViewType.Login, () => !hosts.IsLoggedInToAnyHost); + + machine.Configure(UIViewType.Login) .OnEntry(() => { - var twofa = uiProvider.GetService(); + var disposable = factory.GetViewModel(UIViewType.Login); + disposables.Add(disposable); + var viewModel = disposable.Value as ILoginViewModel; + + viewModel.AuthenticationResults.Subscribe(result => + { + if (result == AuthenticationResult.Success) + Fire(Trigger.Next); + }); + + disposable = factory.GetView(UIViewType.Login); + disposables.Add(disposable); + var view = disposable.Value; + view.ViewModel = viewModel; + + var twofa = factory.GetViewModel(UIViewType.TwoFactor).Value; twofa.WhenAny(x => x.IsShowing, x => x.Value) .Where(x => x) .Subscribe(_ => @@ -53,61 +67,59 @@ namespace GitHub.VisualStudio.UI Fire(Trigger.Next); }); - var d = factory.LoginViewModelFactory.CreateExport(); - disposables.Add(d); - var view = uiProvider.GetService>(); - view.ViewModel = d.Value; - - d.Value.AuthenticationResults.Subscribe(result => - { - if (result == AuthenticationResult.Success) - Fire(Trigger.Next); - }); transition.OnNext(view); }) - .Permit(Trigger.Next, UIState.TwoFA); + .Permit(Trigger.Next, UIViewType.TwoFactor); - machine.Configure(UIState.TwoFA) - .SubstateOf(UIState.Auth) + machine.Configure(UIViewType.TwoFactor) + .SubstateOf(UIViewType.Login) .OnEntry(() => { - var d = uiProvider.GetService(); - var view = uiProvider.GetService>(); - view.ViewModel = d; + var view = SetupView(UIViewType.TwoFactor); transition.OnNext(view); }) - .PermitIf(Trigger.Next, UIState.End, () => currentFlow == UIControllerFlow.Authentication) - .PermitIf(Trigger.Next, UIState.Create, () => currentFlow == UIControllerFlow.Create) - .PermitIf(Trigger.Next, UIState.Clone, () => currentFlow == UIControllerFlow.Clone); + .PermitIf(Trigger.Next, UIViewType.End, () => currentFlow == UIControllerFlow.Authentication) + .PermitIf(Trigger.Next, UIViewType.Create, () => currentFlow == UIControllerFlow.Create) + .PermitIf(Trigger.Next, UIViewType.Clone, () => currentFlow == UIControllerFlow.Clone); - machine.Configure(UIState.Create) + machine.Configure(UIViewType.Create) .OnEntry(() => { - var d = uiProvider.GetService(); - var view = uiProvider.GetService>(); - view.ViewModel = d; transition.OnNext(view); }) - .Permit(Trigger.Next, UIState.End); + .Permit(Trigger.Next, UIViewType.End); - machine.Configure(UIState.Clone) + machine.Configure(UIViewType.Clone) .OnEntry(() => { - var d = uiProvider.GetService(); - - var view = uiProvider.GetService>(); - view.ViewModel = d; + var view = SetupView(UIViewType.Clone); transition.OnNext(view); }) - .Permit(Trigger.Next, UIState.End); + .Permit(Trigger.Next, UIViewType.End); - machine.Configure(UIState.End) + machine.Configure(UIViewType.End) .OnEntry(() => { transition.OnCompleted(); transition.Dispose(); }) - .Permit(Trigger.Next, UIState.Start); + .Permit(Trigger.Next, UIViewType.Start); + } + + IViewFor SetupView(UIViewType viewType) + { + IViewModel disposable; + + disposable = factory.GetViewModel(viewType); + disposables.Add(disposable); + var viewModel = disposable.Value; + + disposable = factory.GetView(viewType); + disposables.Add(disposable); + var view = disposable.Value; + + view.ViewModel = viewModel; + return view; } void Fire(Trigger next) diff --git a/src/GitHub.App/ViewModels/CloneRepoViewModel.cs b/src/GitHub.App/ViewModels/CloneRepoViewModel.cs index d18ade746..6d723223c 100644 --- a/src/GitHub.App/ViewModels/CloneRepoViewModel.cs +++ b/src/GitHub.App/ViewModels/CloneRepoViewModel.cs @@ -7,7 +7,7 @@ using System.Windows.Input; namespace GitHub.ViewModels { - [Export(typeof(ICloneRepoViewModel))] + [ExportViewModel(ViewType=UIViewType.Clone)] public class CloneRepoViewModel : ICloneRepoViewModel { public ReactiveCommand CancelCommand { get; private set; } diff --git a/src/GitHub.App/ViewModels/CreateRepoViewModel.cs b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs index 708ce2ae6..0ccb40928 100644 --- a/src/GitHub.App/ViewModels/CreateRepoViewModel.cs +++ b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs @@ -8,7 +8,7 @@ using System.Windows.Input; namespace GitHub.ViewModels { - [Export(typeof(ICreateRepoViewModel))] + [ExportViewModel(ViewType=UIViewType.Create)] public class CreateRepoViewModel : ICreateRepoViewModel { public ReactiveCommand CancelCommand { get; private set; } diff --git a/src/GitHub.App/ViewModels/LoginControlViewModel.cs b/src/GitHub.App/ViewModels/LoginControlViewModel.cs index cabbc733e..7abd4358a 100644 --- a/src/GitHub.App/ViewModels/LoginControlViewModel.cs +++ b/src/GitHub.App/ViewModels/LoginControlViewModel.cs @@ -15,11 +15,12 @@ using NullGuard; using ReactiveUI; using System.Windows.Input; using GitHub.UI; +using GitHub.Exports; namespace GitHub.ViewModels { [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] - [Export(typeof(ILoginViewModel))] + [ExportViewModel(ViewType=UIViewType.Login)] [PartCreationPolicy(CreationPolicy.NonShared)] public class LoginControlViewModel : ReactiveValidatableObject, ILoginViewModel, IDisposable { diff --git a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs index f3a7827f4..2cf09b9ee 100644 --- a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs +++ b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs @@ -13,7 +13,7 @@ using System.Windows.Input; namespace GitHub.ViewModels { - [Export(typeof(ITwoFactorViewModel))] + [ExportViewModel(ViewType=UIViewType.TwoFactor)] [PartCreationPolicy(CreationPolicy.Shared)] public class TwoFactorDialogViewModel : ReactiveValidatableObject, ITwoFactorViewModel { diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index 75a49b028..5914a50c0 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -58,6 +58,10 @@ ..\..\packages\Rx-XAML.2.2.5\lib\net45\System.Reactive.Windows.Threading.dll + + False + C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Controls.dll + diff --git a/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs index bd1f3c90b..28d70abe6 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs @@ -6,7 +6,7 @@ using System.Windows.Input; namespace GitHub.UI { - public interface ICreateRepoViewModel + public interface ICreateRepoViewModel : IViewModel { string RepositoryName { get; } string SafeRepositoryName { get; } diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index f708225ed..8094779d5 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -77,6 +77,7 @@ + diff --git a/src/GitHub.Exports/Primitives/ExportMetadata.cs b/src/GitHub.Exports/Primitives/ExportMetadata.cs new file mode 100644 index 000000000..bda6b14f2 --- /dev/null +++ b/src/GitHub.Exports/Primitives/ExportMetadata.cs @@ -0,0 +1,39 @@ +using System; + +namespace GitHub.Exports { + + public enum UIViewType { + None, + Login, + TwoFactor, + Create, + Clone, + End = 100 + } + + + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] + public class ExportViewModelAttribute : ExportAttribute + { + public ExportViewModelAttribute() : base(typeof(IViewModel)) + { + } + + public UIViewType ViewType { get; set; } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] + public class ExportViewAttribute : ExportAttribute + { + public ExportViewAttribute() : base(typeof(IViewFor)) + { + } + + public UIViewType ViewType { get; set; } + } + + public interface IViewModelMetadata + { + public IUIViewType ViewType { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/ExportFactoryProvider.cs b/src/GitHub.Exports/Services/ExportFactoryProvider.cs index 2b83e1277..a482eedca 100644 --- a/src/GitHub.Exports/Services/ExportFactoryProvider.cs +++ b/src/GitHub.Exports/Services/ExportFactoryProvider.cs @@ -1,4 +1,7 @@ -using GitHub.UI; +using System; +using System.Diagnostics; +using System.Globalization; +using GitHub.UI; using System.ComponentModel.Composition; namespace GitHub.Services @@ -6,22 +9,30 @@ namespace GitHub.Services [Export] public class ExportFactoryProvider { - [ImportingConstructor] public ExportFactoryProvider(ICompositionService cc) { cc.SatisfyImportsOnce(this); } - [Import(AllowRecomposition =true)] - public ExportFactory LoginViewModelFactory { get; set; } + [ImportMany(AllowRecomposition = true)] + public IEnumerable> ViewModelFactory { get; set; } - [Import(AllowRecomposition = true)] - public ExportFactory UIControllerFactory { get; set; } - - /* - [Import(AllowRecomposition = true)] - public ExportFactory TwoFactorViewModelFactory { get; set; } - */ + [ImportMany(AllowRecomposition = true)] + public IEnumerable> ViewFactory { get; set; } + + public ExportLifetimeContext GetViewModel(UIViewType viewType) + { + var f = ViewModelFactory.FirstOrDefault(x => x.Metadata.ViewType == viewType); + Debug.Assert(f != null, string.Format("Could not locate view model for {0}.", viewtype)); + return f.CreateExport(); + } + + public ExportLifetimeContext GetView(UIViewType viewType) + { + var f = ViewFactory.FirstOrDefault(x => x.Metadata.ViewType == viewType); + Debug.Assert(f != null, string.Format("Could not locate view for {0}.", viewtype)); + return f.CreateExport(); + } } } diff --git a/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs b/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs index 8adcfdd63..8f332ad30 100644 --- a/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs +++ b/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs @@ -3,7 +3,7 @@ using System.Windows.Input; namespace GitHub.UI { - public interface ICloneRepoViewModel + public interface ICloneRepoViewModel : IViewModel { ICommand OkCmd { get; } ICommand CancelCmd { get; } diff --git a/src/GitHub.Exports/ViewModels/ILoginViewModel.cs b/src/GitHub.Exports/ViewModels/ILoginViewModel.cs index 43cbc56ec..cd4c12cec 100644 --- a/src/GitHub.Exports/ViewModels/ILoginViewModel.cs +++ b/src/GitHub.Exports/ViewModels/ILoginViewModel.cs @@ -4,7 +4,7 @@ using System.Windows.Input; namespace GitHub.UI { - public interface ILoginViewModel + public interface ILoginViewModel : IViewModel { string UsernameOrEmail { get; set; } string Password { get; set; } diff --git a/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs b/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs index a739487dd..dea30cd90 100644 --- a/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs +++ b/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs @@ -2,7 +2,7 @@ namespace GitHub.UI { - public interface ITwoFactorViewModel + public interface ITwoFactorViewModel : IViewModel { ICommand OkCmd { get; } ICommand CancelCmd { get; } diff --git a/src/GitHub.Exports/ViewModels/IViewModel.cs b/src/GitHub.Exports/ViewModels/IViewModel.cs new file mode 100644 index 000000000..df8a5f04d --- /dev/null +++ b/src/GitHub.Exports/ViewModels/IViewModel.cs @@ -0,0 +1,8 @@ +using System; + +namespace GitHub.ViewModels { + + public interface IViewModel { + + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs index b9bdadd86..6ff946a29 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs @@ -49,6 +49,12 @@ namespace GitHub.VisualStudio set { title = value; this.RaisePropertyChange(); } } + public TeamExplorerSectionBase(IServiceProvider serviceProvider) + : base() + { + this.ServiceProvider = serviceProvider; + } + public virtual void Cancel() { } diff --git a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs index 9b7c6b1d7..1924b2072 100644 --- a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs +++ b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs @@ -27,6 +27,7 @@ namespace GitHub.VisualStudio.TeamExplorerConnect [ImportingConstructor] public PlaceholderGitHubSection([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + : base(serviceProvider) { this.Title = "GitHub"; diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs b/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs index 864c22719..ba585c809 100644 --- a/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs +++ b/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs @@ -3,6 +3,8 @@ using Microsoft.TeamFoundation.Controls; using NullGuard; using System; using Microsoft.TeamFoundation.Client; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Shell; namespace GitHub.VisualStudio { @@ -31,7 +33,9 @@ namespace GitHub.VisualStudio set { repoUrl = value; this.RaisePropertyChange(); } } - public GitHubHomeSection() + [ImportingConstructor] + public GitHubHomeSection([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + : base(serviceProvider) { Title = "GitHub"; // only when the repo is hosted on github.com diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs index 5a64ddcee..668f5326f 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs @@ -10,7 +10,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// /// Interaction logic for CloneRepoControl.xaml /// - [Export(typeof(IViewFor))] + [ExportView(ViewType=UIViewType.Clone)] public partial class CloneRepoControl : IViewFor { public CloneRepoControl() diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs index 549f9af4c..fed9f9399 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs @@ -14,7 +14,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// /// Interaction logic for CloneRepoControl.xaml /// - [Export(typeof(IViewFor))] + [ExportView(ViewType=UIViewType.Create)] public partial class CreateRepoControl : IViewFor { public CreateRepoControl() diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs index 2c272748f..9875fd64d 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs @@ -13,7 +13,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// /// Interaction logic for LoginControl.xaml /// - [Export(typeof(IViewFor))] + [ExportView(ViewType=UIViewType.Login)] public partial class LoginControl : IViewFor { public LoginControl() diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs index fabe000df..545163eb0 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs @@ -12,7 +12,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// /// Interaction logic for PasswordView.xaml /// - [Export(typeof(IViewFor))] + [ExportView(ViewType=UIViewType.TwoFactor)] public partial class TwoFactorControl : IViewFor { public TwoFactorControl() From 5f02280b3e160e1897f24f6da238bc1ff94ee408 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 16:20:21 +0100 Subject: [PATCH 20/35] Reorganize files into more meaningful folders --- src/GitHub.App/{Services => Api}/ApiClient.cs | 2 +- .../{Services => Factories}/AccountFactory.cs | 11 +++++----- .../ApiClientFactory.cs | 10 ++++++--- .../{Caches => Factories}/HostCacheFactory.cs | 5 ++++- .../IBlobCacheFactory.cs | 2 +- .../IHostCacheFactory.cs | 5 +++-- .../RepositoryHostFactory.cs | 4 +++- .../SqlitePersistentBlobCacheFactory.cs | 3 +-- src/GitHub.App/GitHub.App.csproj | 20 ++++++++---------- src/GitHub.App/GlobalSuppressions.cs | Bin 0 -> 1390 bytes .../{Services => Api}/IApiClient.cs | 4 ++-- .../Factories}/IAccountFactory.cs | 7 +++--- .../Factories}/IApiClientFactory.cs | 5 ++++- .../Factories}/IRepositoryHostFactory.cs | 3 ++- .../GitHub.Exports.Reactive.csproj | 7 ++++-- .../{Primitives => Exports}/ExportMetadata.cs | 11 ++++++---- src/GitHub.Exports/GitHub.Exports.csproj | 2 ++ src/GitHub.Exports/UI/IView.cs | 7 ++++++ 18 files changed, 68 insertions(+), 40 deletions(-) rename src/GitHub.App/{Services => Api}/ApiClient.cs (99%) rename src/GitHub.App/{Services => Factories}/AccountFactory.cs (70%) rename src/GitHub.App/{Services => Factories}/ApiClientFactory.cs (85%) rename src/GitHub.App/{Caches => Factories}/HostCacheFactory.cs (95%) rename src/GitHub.App/{Caches => Factories}/IBlobCacheFactory.cs (82%) rename src/GitHub.App/{Caches => Factories}/IHostCacheFactory.cs (58%) rename src/GitHub.App/{Services => Factories}/RepositoryHostFactory.cs (94%) rename src/GitHub.App/{Caches => Factories}/SqlitePersistentBlobCacheFactory.cs (92%) create mode 100644 src/GitHub.App/GlobalSuppressions.cs rename src/GitHub.Exports.Reactive/{Services => Api}/IApiClient.cs (96%) rename src/{GitHub.App/Services => GitHub.Exports.Reactive/Factories}/IAccountFactory.cs (69%) rename src/{GitHub.App/Services => GitHub.Exports.Reactive/Factories}/IApiClientFactory.cs (59%) rename src/{GitHub.App/Services => GitHub.Exports.Reactive/Factories}/IRepositoryHostFactory.cs (72%) rename src/GitHub.Exports/{Primitives => Exports}/ExportMetadata.cs (64%) create mode 100644 src/GitHub.Exports/UI/IView.cs diff --git a/src/GitHub.App/Services/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs similarity index 99% rename from src/GitHub.App/Services/ApiClient.cs rename to src/GitHub.App/Api/ApiClient.cs index f5ef1f414..1c6c435f9 100644 --- a/src/GitHub.App/Services/ApiClient.cs +++ b/src/GitHub.App/Api/ApiClient.cs @@ -5,12 +5,12 @@ using System.Net; using System.Reactive.Linq; using GitHub.Authentication; using GitHub.Extensions; +using GitHub.Primitives; using NLog; using Octokit; using Octokit.Reactive; using ReactiveUI; using Authorization = Octokit.Authorization; -using LogManager = NLog.LogManager; namespace GitHub.Api { diff --git a/src/GitHub.App/Services/AccountFactory.cs b/src/GitHub.App/Factories/AccountFactory.cs similarity index 70% rename from src/GitHub.App/Services/AccountFactory.cs rename to src/GitHub.App/Factories/AccountFactory.cs index 59a144685..374c16476 100644 --- a/src/GitHub.App/Services/AccountFactory.cs +++ b/src/GitHub.App/Factories/AccountFactory.cs @@ -1,22 +1,23 @@ -using System; -using System.ComponentModel.Composition; +using System.ComponentModel.Composition; using GitHub.Models; +using Octokit; +using Account = GitHub.Models.Account; -namespace GitHub +namespace GitHub.Factories { [Export(typeof(IAccountFactory))] public class AccountFactory : IAccountFactory { public IAccount CreateAccount( IRepositoryHost repositoryHost, - Octokit.User user) + User user) { return new Account(repositoryHost, user); } public IAccount CreateAccount( IRepositoryHost repositoryHost, - Octokit.Organization organization) + Organization organization) { return new Account(repositoryHost, organization); } diff --git a/src/GitHub.App/Services/ApiClientFactory.cs b/src/GitHub.App/Factories/ApiClientFactory.cs similarity index 85% rename from src/GitHub.App/Services/ApiClientFactory.cs rename to src/GitHub.App/Factories/ApiClientFactory.cs index dbe10d31d..24024ccb0 100644 --- a/src/GitHub.App/Services/ApiClientFactory.cs +++ b/src/GitHub.App/Factories/ApiClientFactory.cs @@ -1,11 +1,15 @@ -using System; -using System.ComponentModel.Composition; +using System.ComponentModel.Composition; +using GitHub.Api; using GitHub.Authentication; +using GitHub.Caches; using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; using Octokit; using Octokit.Reactive; +using ApiClient = GitHub.Api.ApiClient; -namespace GitHub.Api +namespace GitHub.Factories { [Export(typeof(IApiClientFactory))] [PartCreationPolicy(CreationPolicy.Shared)] diff --git a/src/GitHub.App/Caches/HostCacheFactory.cs b/src/GitHub.App/Factories/HostCacheFactory.cs similarity index 95% rename from src/GitHub.App/Caches/HostCacheFactory.cs rename to src/GitHub.App/Factories/HostCacheFactory.cs index b5b52cd6c..27e3cfc50 100644 --- a/src/GitHub.App/Caches/HostCacheFactory.cs +++ b/src/GitHub.App/Factories/HostCacheFactory.cs @@ -1,9 +1,12 @@ using System; using System.ComponentModel.Composition; using System.IO; +using GitHub.Caches; +using GitHub.Info; +using GitHub.Primitives; using Rothko; -namespace GitHub +namespace GitHub.Factories { [Export(typeof(IHostCacheFactory))] [PartCreationPolicy(CreationPolicy.Shared)] diff --git a/src/GitHub.App/Caches/IBlobCacheFactory.cs b/src/GitHub.App/Factories/IBlobCacheFactory.cs similarity index 82% rename from src/GitHub.App/Caches/IBlobCacheFactory.cs rename to src/GitHub.App/Factories/IBlobCacheFactory.cs index ae97205be..3f068b7e5 100644 --- a/src/GitHub.App/Caches/IBlobCacheFactory.cs +++ b/src/GitHub.App/Factories/IBlobCacheFactory.cs @@ -1,6 +1,6 @@ using Akavache; -namespace GitHub +namespace GitHub.Factories { public interface IBlobCacheFactory { diff --git a/src/GitHub.App/Caches/IHostCacheFactory.cs b/src/GitHub.App/Factories/IHostCacheFactory.cs similarity index 58% rename from src/GitHub.App/Caches/IHostCacheFactory.cs rename to src/GitHub.App/Factories/IHostCacheFactory.cs index 8087c5ead..a5c717ce7 100644 --- a/src/GitHub.App/Caches/IHostCacheFactory.cs +++ b/src/GitHub.App/Factories/IHostCacheFactory.cs @@ -1,6 +1,7 @@ -using System; +using GitHub.Caches; +using GitHub.Primitives; -namespace GitHub +namespace GitHub.Factories { public interface IHostCacheFactory { diff --git a/src/GitHub.App/Services/RepositoryHostFactory.cs b/src/GitHub.App/Factories/RepositoryHostFactory.cs similarity index 94% rename from src/GitHub.App/Services/RepositoryHostFactory.cs rename to src/GitHub.App/Factories/RepositoryHostFactory.cs index fe1eda620..296f3028f 100644 --- a/src/GitHub.App/Services/RepositoryHostFactory.cs +++ b/src/GitHub.App/Factories/RepositoryHostFactory.cs @@ -1,7 +1,9 @@ using System.ComponentModel.Composition; +using GitHub.Caches; using GitHub.Models; +using GitHub.Primitives; -namespace GitHub +namespace GitHub.Factories { [Export(typeof(IRepositoryHostFactory))] [PartCreationPolicy(CreationPolicy.Shared)] diff --git a/src/GitHub.App/Caches/SqlitePersistentBlobCacheFactory.cs b/src/GitHub.App/Factories/SqlitePersistentBlobCacheFactory.cs similarity index 92% rename from src/GitHub.App/Caches/SqlitePersistentBlobCacheFactory.cs rename to src/GitHub.App/Factories/SqlitePersistentBlobCacheFactory.cs index a24a40ff1..242b0d4a3 100644 --- a/src/GitHub.App/Caches/SqlitePersistentBlobCacheFactory.cs +++ b/src/GitHub.App/Factories/SqlitePersistentBlobCacheFactory.cs @@ -1,9 +1,8 @@ using System.ComponentModel.Composition; using Akavache; -using Akavache.Sqlite3; using GitHub.Extensions; -namespace GitHub +namespace GitHub.Factories { [Export(typeof(IBlobCacheFactory))] [PartCreationPolicy(CreationPolicy.Shared)] diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 2df5187bc..7f31a6917 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -116,14 +116,15 @@ - - - + + + - + + @@ -137,20 +138,17 @@ - - - + + + - - - - + diff --git a/src/GitHub.App/GlobalSuppressions.cs b/src/GitHub.App/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..3210fb0c913df0c5a3ba5b4200225a3569fdfbc0 GIT binary patch literal 1390 zcmaKs-D}i96vgke;QuhJFBWWyK1s!gqWJM36uSsTe28hfjb@XwnJE3|)!(_(*<^RI zgw15;ew};nnf?1`w80wdY-PbN?2cWu+SXRsTh2?)JL~oEMNM3cXw}>wnoOACP*0KW8mf(a zX_Cr&RhWP-Qt(9rONV4T}TDJ{lHogUCb(+?^uq#*I)RxWF7W!Hst6WK}}v| z*TjRPQB1(9j@9YLRi-&gNfXi)k@6J}(r@CsWH-1vD(vFC;$%0Ev?$((t&+4$ohn|p zLz+^a5}(>N8<~<+C;iS9r@eGg&DfamEWMACda9Ao?anN5k?T)PXT2!D=C*>JXvfsi zbSiF(V#Hj+qnnv-q!cDrvUNi9kjB#O9?2fbq^eSX(pyHiI8a>hJ3G}*_b)R%$2y@{ z*hj~=uinhWJF4oYEM|-by9CV$sw-zy`j93%{N_r{O#feT`-;vyr~B1WL;sEXcTDOQ F?teMF?mPeh literal 0 HcmV?d00001 diff --git a/src/GitHub.Exports.Reactive/Services/IApiClient.cs b/src/GitHub.Exports.Reactive/Api/IApiClient.cs similarity index 96% rename from src/GitHub.Exports.Reactive/Services/IApiClient.cs rename to src/GitHub.Exports.Reactive/Api/IApiClient.cs index 68a6dde6a..b3e308d43 100644 --- a/src/GitHub.Exports.Reactive/Services/IApiClient.cs +++ b/src/GitHub.Exports.Reactive/Api/IApiClient.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using GitHub.Authentication; +using GitHub.Primitives; using Octokit; -namespace GitHub +namespace GitHub.Api { public interface IApiClient { diff --git a/src/GitHub.App/Services/IAccountFactory.cs b/src/GitHub.Exports.Reactive/Factories/IAccountFactory.cs similarity index 69% rename from src/GitHub.App/Services/IAccountFactory.cs rename to src/GitHub.Exports.Reactive/Factories/IAccountFactory.cs index 73be0ef03..db46ebb5f 100644 --- a/src/GitHub.App/Services/IAccountFactory.cs +++ b/src/GitHub.Exports.Reactive/Factories/IAccountFactory.cs @@ -1,15 +1,16 @@ using GitHub.Models; +using Octokit; -namespace GitHub +namespace GitHub.Factories { public interface IAccountFactory { IAccount CreateAccount( IRepositoryHost repositoryHost, - Octokit.User user); + User user); IAccount CreateAccount( IRepositoryHost repositoryHost, - Octokit.Organization organization); + Organization organization); } } diff --git a/src/GitHub.App/Services/IApiClientFactory.cs b/src/GitHub.Exports.Reactive/Factories/IApiClientFactory.cs similarity index 59% rename from src/GitHub.App/Services/IApiClientFactory.cs rename to src/GitHub.Exports.Reactive/Factories/IApiClientFactory.cs index 06f914bb8..0d338a8ef 100644 --- a/src/GitHub.App/Services/IApiClientFactory.cs +++ b/src/GitHub.Exports.Reactive/Factories/IApiClientFactory.cs @@ -1,4 +1,7 @@ -namespace GitHub +using GitHub.Api; +using GitHub.Primitives; + +namespace GitHub.Factories { public interface IApiClientFactory { diff --git a/src/GitHub.App/Services/IRepositoryHostFactory.cs b/src/GitHub.Exports.Reactive/Factories/IRepositoryHostFactory.cs similarity index 72% rename from src/GitHub.App/Services/IRepositoryHostFactory.cs rename to src/GitHub.Exports.Reactive/Factories/IRepositoryHostFactory.cs index 738161920..2a532306e 100644 --- a/src/GitHub.App/Services/IRepositoryHostFactory.cs +++ b/src/GitHub.Exports.Reactive/Factories/IRepositoryHostFactory.cs @@ -1,6 +1,7 @@ using GitHub.Models; +using GitHub.Primitives; -namespace GitHub +namespace GitHub.Factories { public interface IRepositoryHostFactory { diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index 5914a50c0..d712efd08 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -7,7 +7,7 @@ {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1} Library Properties - GitHub.Exports.Reactive + GitHub GitHub.Exports.Reactive v4.5 512 @@ -74,12 +74,15 @@ Properties\SolutionInfo.cs + + + - + diff --git a/src/GitHub.Exports/Primitives/ExportMetadata.cs b/src/GitHub.Exports/Exports/ExportMetadata.cs similarity index 64% rename from src/GitHub.Exports/Primitives/ExportMetadata.cs rename to src/GitHub.Exports/Exports/ExportMetadata.cs index bda6b14f2..67d69252c 100644 --- a/src/GitHub.Exports/Primitives/ExportMetadata.cs +++ b/src/GitHub.Exports/Exports/ExportMetadata.cs @@ -1,4 +1,7 @@ using System; +using System.ComponentModel.Composition; +using GitHub.UI; +using GitHub.ViewModels; namespace GitHub.Exports { @@ -13,7 +16,7 @@ namespace GitHub.Exports { [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] - public class ExportViewModelAttribute : ExportAttribute + public sealed class ExportViewModelAttribute : ExportAttribute { public ExportViewModelAttribute() : base(typeof(IViewModel)) { @@ -23,9 +26,9 @@ namespace GitHub.Exports { } [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] - public class ExportViewAttribute : ExportAttribute + public sealed class ExportViewAttribute : ExportAttribute { - public ExportViewAttribute() : base(typeof(IViewFor)) + public ExportViewAttribute() : base(typeof(IView)) { } @@ -34,6 +37,6 @@ namespace GitHub.Exports { public interface IViewModelMetadata { - public IUIViewType ViewType { get; } + UIViewType ViewType { get; } } } \ No newline at end of file diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 8094779d5..b4602bbb9 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -63,6 +63,7 @@ + @@ -72,6 +73,7 @@ + diff --git a/src/GitHub.Exports/UI/IView.cs b/src/GitHub.Exports/UI/IView.cs new file mode 100644 index 000000000..8f4bf916c --- /dev/null +++ b/src/GitHub.Exports/UI/IView.cs @@ -0,0 +1,7 @@ +namespace GitHub.UI +{ + public interface IView + { + object ViewModel { get; set; } + } +} From 715fefaa32e536c5f6170bc38a0df12eedd6ee65 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 16:45:58 +0100 Subject: [PATCH 21/35] Fix namespaces and using statements everywhere --- src/DesignTimeStyleHelper/App.xaml.cs | 21 ++++++--------- src/DesignTimeStyleHelper/MainWindow.xaml.cs | 18 ++----------- src/GitHub.Api/SimpleApiClient.cs | 10 +++---- src/GitHub.Api/SimpleApiClientFactory.cs | 11 +++----- .../TwoFactorChallengeHandler.cs | 1 - src/GitHub.App/Caches/HostCache.cs | 3 ++- src/GitHub.App/Caches/ILoginCache.cs | 3 ++- src/GitHub.App/Caches/ISharedCache.cs | 2 +- src/GitHub.App/Caches/LoginCache.cs | 3 ++- src/GitHub.App/Caches/SharedCache.cs | 2 +- src/GitHub.App/Controllers/UIController.cs | 24 ++++++++--------- src/GitHub.App/Info/EnvironmentExtensions.cs | 11 ++++---- src/GitHub.App/Info/GitHubUrls.cs | 1 + .../Models/DisconnectedRepositoryHosts.cs | 3 +++ src/GitHub.App/Models/IRepositoryHosts.cs | 2 ++ .../Models/LocalRepositoriesHost.cs | 3 +++ src/GitHub.App/Models/RepositoryHost.cs | 4 +++ src/GitHub.App/Models/RepositoryHosts.cs | 3 +++ src/GitHub.App/SampleData/SampleViewModels.cs | 13 +++++---- src/GitHub.App/Services/EnterpriseProbe.cs | 3 +-- src/GitHub.App/Services/ErrorMessage.cs | 1 - .../Services/ErrorMessageTranslator.cs | 4 +-- .../Services/GitHubCredentialStore.cs | 5 ++-- src/GitHub.App/Services/IEnterpriseProbe.cs | 4 --- src/GitHub.App/Services/StandardUserErrors.cs | 12 +++------ src/GitHub.App/Services/Translation.cs | 2 +- ...PrivateRepositoryQuotaExceededUserError.cs | 5 ++-- .../UserErrors/PublishRepositoryUserError.cs | 9 ++++--- .../ViewModels/CloneRepoViewModel.cs | 8 +++--- .../ViewModels/CreateRepoViewModel.cs | 9 ++++--- .../ViewModels/LoginControlViewModel.cs | 6 ++--- .../ViewModels/TwoFactorDialogViewModel.cs | 4 +-- .../Caches/IHostCache.cs | 3 ++- .../Models/IAccount.cs | 6 ++--- .../Models/IRepositoryHost.cs | 5 +++- .../ViewModels/ICreateRepoViewModel.cs | 9 ++++--- src/GitHub.Exports/Api/ISimpleApiClient.cs | 10 +++---- .../Api/ISimpleApiClientFactory.cs | 4 --- src/GitHub.Exports/GlobalSuppressions.cs | Bin 1654 -> 1610 bytes src/GitHub.Exports/Models/IRepositoryModel.cs | 5 +--- src/GitHub.Exports/Primitives/HostAddress.cs | 2 +- .../Primitives/StringEquivalent.cs | 1 - src/GitHub.Exports/Primitives/UriString.cs | 1 - .../Services/EnterpriseProbeTask.cs | 10 +++---- .../Services/ExportFactoryProvider.cs | 7 +++-- .../Services/IEnterpriseProbeTask.cs | 3 +-- src/GitHub.Exports/Services/IUIProvider.cs | 4 --- src/GitHub.Exports/Services/IWikiProbe.cs | 8 ++---- src/GitHub.Exports/UI/IUIController.cs | 4 --- .../ViewModels/ICloneRepoViewModel.cs | 2 +- .../ViewModels/ILoginViewModel.cs | 6 ++--- .../ViewModels/ITwoFactorViewModel.cs | 2 +- .../RecoveryCommandWithIcon.cs | 4 +-- src/GitHub.Extensions/TaskExtensions.cs | 7 ++--- .../Controls/Validation/UserErrorMessages.cs | 2 +- .../Controls/Validation/ValidationMessage.cs | 2 +- .../Controls/FilteredComboBox.cs | 3 +-- src/GitHub.VisualStudio/Base/PackageBase.cs | 8 +++--- .../Base/TeamExplorerBase.cs | 6 ++--- .../Base/TeamExplorerGitAwareItem.cs | 13 ++++----- .../Base/TeamExplorerItemBase.cs | 23 +++++++--------- .../Base/TeamExplorerNavigationItemBase.cs | 25 ++++++++---------- .../Base/TeamExplorerNavigationLinkBase.cs | 11 ++------ .../Base/TeamExplorerSectionBase.cs | 8 +++--- src/GitHub.VisualStudio/GitHubPackage.cs | 17 ++++++------ src/GitHub.VisualStudio/Helpers/Browser.cs | 14 +++++----- src/GitHub.VisualStudio/Helpers/Colors.cs | 4 --- src/GitHub.VisualStudio/Helpers/Constants.cs | 2 +- .../Helpers/INotifyPropertySource.cs | 8 +----- .../Helpers/PropertyNotifierExtensions.cs | 2 +- src/GitHub.VisualStudio/Services/Logger.cs | 14 ++++------ src/GitHub.VisualStudio/Services/Program.cs | 2 +- src/GitHub.VisualStudio/Services/Services.cs | 14 +++------- .../Services/UIProvider.cs | 18 ++++++------- .../PlaceholderGitHubSection.cs | 19 ++++++------- .../TeamExplorerHome/GitHubHomeSection.cs | 13 ++++----- .../TeamExplorerHome/GraphsNavigationItem.cs | 10 +++---- .../TeamExplorerHome/IssuesNavigationItem.cs | 10 +++---- .../PullRequestsNavigationItem.cs | 10 +++---- .../TeamExplorerHome/PulseNavigationItem.cs | 10 +++---- .../TeamExplorerHome/WikiNavigationItem.cs | 10 +++---- .../UI/Views/Controls/CloneRepoControl.xaml | 24 +++++++---------- .../Views/Controls/CloneRepoControl.xaml.cs | 9 ++++--- .../UI/Views/Controls/CreateRepoControl.xaml | 8 +++--- .../Views/Controls/CreateRepoControl.xaml.cs | 17 ++++++------ .../UI/Views/Controls/LoginControl.xaml | 12 +++------ .../UI/Views/Controls/LoginControl.xaml.cs | 6 ++--- .../UI/Views/Controls/TwoFactorControl.xaml | 5 ++++ .../Views/Controls/TwoFactorControl.xaml.cs | 5 ++-- .../UI/Views/GitHubConnectContent.xaml.cs | 16 ++--------- .../UI/Views/GitHubHomeContent.xaml.cs | 1 + .../UI/WindowController.xaml | 2 +- .../UI/WindowController.xaml.cs | 22 +++------------ 93 files changed, 306 insertions(+), 412 deletions(-) diff --git a/src/DesignTimeStyleHelper/App.xaml.cs b/src/DesignTimeStyleHelper/App.xaml.cs index b0041dbf6..179a98c0a 100644 --- a/src/DesignTimeStyleHelper/App.xaml.cs +++ b/src/DesignTimeStyleHelper/App.xaml.cs @@ -1,19 +1,14 @@ -using Microsoft.VisualStudio.ComponentModelHost; -using System; +using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; -using System.Configuration; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; using System.ComponentModel.Composition.Primitives; -using Microsoft.VisualStudio.Shell; using System.Globalization; +using System.Linq; +using System.Windows; using GitHub.VisualStudio.TeamExplorerConnect; -using GitHub.Services; -using GitHub.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; using Moq; namespace DesignTimeStyleHelper @@ -37,17 +32,17 @@ namespace DesignTimeStyleHelper public class CustomServiceProvider : SVsServiceProvider, IServiceProvider, SComponentModel, IComponentModel { - CompositionContainer container; + readonly CompositionContainer container; public CompositionContainer Container { get { return container; } } AggregateCatalog catalog; public CustomServiceProvider() { catalog = new AggregateCatalog( - new AssemblyCatalog(typeof(GitHub.VisualStudio.Services).Assembly), // GitHub.VisualStudio + new AssemblyCatalog(typeof(GitHub.VisualStudio.Services.Services).Assembly), // GitHub.VisualStudio new AssemblyCatalog(typeof(GitHub.Api.ApiClient).Assembly), // GitHub.App new AssemblyCatalog(typeof(GitHub.Api.SimpleApiClient).Assembly), // GitHub.Api - new AssemblyCatalog(typeof(Rothko.Environment).Assembly), // GitHub.Api + new AssemblyCatalog(typeof(Rothko.Environment).Assembly), // Rothko new AssemblyCatalog(typeof(GitHub.Services.EnterpriseProbeTask).Assembly) // GitHub.Exports ); container = new CompositionContainer(catalog, CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml.cs b/src/DesignTimeStyleHelper/MainWindow.xaml.cs index 6532ca651..5d9fa9113 100644 --- a/src/DesignTimeStyleHelper/MainWindow.xaml.cs +++ b/src/DesignTimeStyleHelper/MainWindow.xaml.cs @@ -1,19 +1,5 @@ -using GitHub.VisualStudio.TeamExplorerConnect; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; +using System.Windows; +using GitHub.VisualStudio.TeamExplorerConnect; namespace DesignTimeStyleHelper { diff --git a/src/GitHub.Api/SimpleApiClient.cs b/src/GitHub.Api/SimpleApiClient.cs index 6fa8dc3e2..c24bef7c9 100644 --- a/src/GitHub.Api/SimpleApiClient.cs +++ b/src/GitHub.Api/SimpleApiClient.cs @@ -1,10 +1,10 @@ -using GitHub.Extensions; -using GitHub.Services; -using Octokit; -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; - +using GitHub.Extensions; +using GitHub.Primitives; +using GitHub.Services; +using Octokit; namespace GitHub.Api { diff --git a/src/GitHub.Api/SimpleApiClientFactory.cs b/src/GitHub.Api/SimpleApiClientFactory.cs index 0632da8c5..408dca474 100644 --- a/src/GitHub.Api/SimpleApiClientFactory.cs +++ b/src/GitHub.Api/SimpleApiClientFactory.cs @@ -1,12 +1,9 @@ -using GitHub.Models; +using System; +using System.ComponentModel.Composition; +using GitHub.Models; +using GitHub.Primitives; using GitHub.Services; using Octokit; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GitHub.Api { diff --git a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs index 940929194..d7a894cb2 100644 --- a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs +++ b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs @@ -4,7 +4,6 @@ using System.Reactive.Linq; using GitHub.ViewModels; using Octokit; using ReactiveUI; -using GitHub.UI; namespace GitHub.Authentication { diff --git a/src/GitHub.App/Caches/HostCache.cs b/src/GitHub.App/Caches/HostCache.cs index f7b2a0592..6a6ab9f46 100644 --- a/src/GitHub.App/Caches/HostCache.cs +++ b/src/GitHub.App/Caches/HostCache.cs @@ -6,9 +6,10 @@ using System.Reactive.Linq; using Akavache; using GitHub.Extensions; using GitHub.Extensions.Reactive; +using GitHub.Models; using Octokit; -namespace GitHub +namespace GitHub.Caches { public class HostCache : IHostCache { diff --git a/src/GitHub.App/Caches/ILoginCache.cs b/src/GitHub.App/Caches/ILoginCache.cs index 1f29ba4c3..aecaa03a3 100644 --- a/src/GitHub.App/Caches/ILoginCache.cs +++ b/src/GitHub.App/Caches/ILoginCache.cs @@ -1,8 +1,9 @@ using System; using System.Reactive; using Akavache; +using GitHub.Primitives; -namespace GitHub +namespace GitHub.Caches { public interface ILoginCache : IDisposable { diff --git a/src/GitHub.App/Caches/ISharedCache.cs b/src/GitHub.App/Caches/ISharedCache.cs index c711692d3..0db4382f1 100644 --- a/src/GitHub.App/Caches/ISharedCache.cs +++ b/src/GitHub.App/Caches/ISharedCache.cs @@ -3,7 +3,7 @@ using System.Linq.Expressions; using System.Reactive; using Akavache; -namespace GitHub +namespace GitHub.Caches { /// /// A cache for data that's not host specific diff --git a/src/GitHub.App/Caches/LoginCache.cs b/src/GitHub.App/Caches/LoginCache.cs index fd528a885..40d57735b 100644 --- a/src/GitHub.App/Caches/LoginCache.cs +++ b/src/GitHub.App/Caches/LoginCache.cs @@ -5,9 +5,10 @@ using System.Reactive; using System.Reactive.Linq; using Akavache; using GitHub.Extensions; +using GitHub.Primitives; using NLog; -namespace GitHub +namespace GitHub.Caches { [Export(typeof(ILoginCache))] public sealed class LoginCache : ILoginCache diff --git a/src/GitHub.App/Caches/SharedCache.cs b/src/GitHub.App/Caches/SharedCache.cs index 423ff8d75..929e1e1ad 100644 --- a/src/GitHub.App/Caches/SharedCache.cs +++ b/src/GitHub.App/Caches/SharedCache.cs @@ -8,7 +8,7 @@ using System.Reflection; using Akavache; using ReactiveUI; -namespace GitHub +namespace GitHub.Caches { /// /// A cache for data that's not host specific diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs index da9a00aca..7211e7599 100644 --- a/src/GitHub.App/Controllers/UIController.cs +++ b/src/GitHub.App/Controllers/UIController.cs @@ -1,19 +1,19 @@ -using GitHub.Services; -using GitHub.UI; -using System; +using System; using System.ComponentModel.Composition; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Disposables; -using System.Reactive.Subjects; -using Stateless; -using ReactiveUI; -using GitHub.Models; -using GitHub.Authentication; using System.Diagnostics; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using GitHub.Authentication; using GitHub.Exports; +using GitHub.Models; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels; +using ReactiveUI; +using Stateless; -namespace GitHub.VisualStudio.UI +namespace GitHub.Controllers { [Export(typeof(IUIController))] public class UIController : IUIController, IDisposable diff --git a/src/GitHub.App/Info/EnvironmentExtensions.cs b/src/GitHub.App/Info/EnvironmentExtensions.cs index bd4a3e09e..4941e6921 100644 --- a/src/GitHub.App/Info/EnvironmentExtensions.cs +++ b/src/GitHub.App/Info/EnvironmentExtensions.cs @@ -1,25 +1,24 @@ using System.IO; -using Rothko; -using Environment = System.Environment; +using System; -namespace GitHub +namespace GitHub.Info { public static class EnvironmentExtensions { const string applicationName = "GitHub"; - public static string GetLocalGitHubApplicationDataPath(this IEnvironment environment) + public static string GetLocalGitHubApplicationDataPath(this Rothko.IEnvironment environment) { return Path.Combine(environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), applicationName); } - public static string GetApplicationDataPath(this IEnvironment environment) + public static string GetApplicationDataPath(this Rothko.IEnvironment environment) { return Path.Combine(environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), applicationName); } - public static string GetProgramFilesPath(this IEnvironment environment) + public static string GetProgramFilesPath(this Rothko.IEnvironment environment) { return environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); } diff --git a/src/GitHub.App/Info/GitHubUrls.cs b/src/GitHub.App/Info/GitHubUrls.cs index 7b35feadf..3849f9e04 100644 --- a/src/GitHub.App/Info/GitHubUrls.cs +++ b/src/GitHub.App/Info/GitHubUrls.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using GitHub.Models; namespace GitHub.Info { diff --git a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs b/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs index 10111d3ad..f9fcdcd7f 100644 --- a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs +++ b/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs @@ -2,7 +2,10 @@ using System; using System.Reactive; using System.Reactive.Linq; +using GitHub.Api; using GitHub.Authentication; +using GitHub.Caches; +using GitHub.Primitives; using NullGuard; using ReactiveUI; diff --git a/src/GitHub.App/Models/IRepositoryHosts.cs b/src/GitHub.App/Models/IRepositoryHosts.cs index a7c6f1c59..7a9e9a7b1 100644 --- a/src/GitHub.App/Models/IRepositoryHosts.cs +++ b/src/GitHub.App/Models/IRepositoryHosts.cs @@ -1,5 +1,7 @@ using System; using GitHub.Authentication; +using GitHub.Factories; +using GitHub.Primitives; using ReactiveUI; namespace GitHub.Models diff --git a/src/GitHub.App/Models/LocalRepositoriesHost.cs b/src/GitHub.App/Models/LocalRepositoriesHost.cs index 46faa8f3e..f78237e8b 100644 --- a/src/GitHub.App/Models/LocalRepositoriesHost.cs +++ b/src/GitHub.App/Models/LocalRepositoriesHost.cs @@ -1,7 +1,10 @@ using System; using System.Reactive; using System.Reactive.Linq; +using GitHub.Api; using GitHub.Authentication; +using GitHub.Caches; +using GitHub.Primitives; using ReactiveUI; namespace GitHub.Models diff --git a/src/GitHub.App/Models/RepositoryHost.cs b/src/GitHub.App/Models/RepositoryHost.cs index f534d615d..073fb2810 100644 --- a/src/GitHub.App/Models/RepositoryHost.cs +++ b/src/GitHub.App/Models/RepositoryHost.cs @@ -7,9 +7,13 @@ using System.Linq; using System.Net; using System.Reactive; using System.Reactive.Linq; +using GitHub.Api; using GitHub.Authentication; +using GitHub.Caches; using GitHub.Extensions; using GitHub.Extensions.Reactive; +using GitHub.Factories; +using GitHub.Primitives; using GitHub.Services; using NLog; using Octokit; diff --git a/src/GitHub.App/Models/RepositoryHosts.cs b/src/GitHub.App/Models/RepositoryHosts.cs index 6bf866f12..f95d6e3b0 100644 --- a/src/GitHub.App/Models/RepositoryHosts.cs +++ b/src/GitHub.App/Models/RepositoryHosts.cs @@ -5,7 +5,10 @@ using System.Reactive; using System.Reactive.Linq; using Akavache; using GitHub.Authentication; +using GitHub.Caches; using GitHub.Extensions.Reactive; +using GitHub.Factories; +using GitHub.Primitives; using ReactiveUI; namespace GitHub.Models diff --git a/src/GitHub.App/SampleData/SampleViewModels.cs b/src/GitHub.App/SampleData/SampleViewModels.cs index ba38f61b2..dc991e11b 100644 --- a/src/GitHub.App/SampleData/SampleViewModels.cs +++ b/src/GitHub.App/SampleData/SampleViewModels.cs @@ -1,19 +1,18 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reactive; +using System.Windows.Input; using System.Windows.Media.Imaging; using GitHub.Helpers; using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Validation; +using GitHub.ViewModels; using Octokit; using ReactiveUI; using Account = Octokit.Account; -using GitHub.Services; -using System.Collections.Generic; -using GitHub.Primitives; -using NullGuard; -using GitHub.UI; -using System.Windows.Input; -using GitHub.Validation; namespace GitHub.SampleData { diff --git a/src/GitHub.App/Services/EnterpriseProbe.cs b/src/GitHub.App/Services/EnterpriseProbe.cs index c08df94fe..a478e9ea0 100644 --- a/src/GitHub.App/Services/EnterpriseProbe.cs +++ b/src/GitHub.App/Services/EnterpriseProbe.cs @@ -1,9 +1,8 @@ using System; using System.ComponentModel.Composition; +using System.Reactive.Threading.Tasks; using GitHub.Models; using Octokit.Internal; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; namespace GitHub.Services { diff --git a/src/GitHub.App/Services/ErrorMessage.cs b/src/GitHub.App/Services/ErrorMessage.cs index df6ba7db3..27237a9fc 100644 --- a/src/GitHub.App/Services/ErrorMessage.cs +++ b/src/GitHub.App/Services/ErrorMessage.cs @@ -1,6 +1,5 @@ using System; using System.Text.RegularExpressions; -using GitHub.Helpers; namespace GitHub.Services { diff --git a/src/GitHub.App/Services/ErrorMessageTranslator.cs b/src/GitHub.App/Services/ErrorMessageTranslator.cs index b615af7a8..7a7d19e22 100644 --- a/src/GitHub.App/Services/ErrorMessageTranslator.cs +++ b/src/GitHub.App/Services/ErrorMessageTranslator.cs @@ -1,8 +1,8 @@ -using ReactiveUI; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using ReactiveUI; namespace GitHub.Services { diff --git a/src/GitHub.App/Services/GitHubCredentialStore.cs b/src/GitHub.App/Services/GitHubCredentialStore.cs index 19bc48d28..4bee84389 100644 --- a/src/GitHub.App/Services/GitHubCredentialStore.cs +++ b/src/GitHub.App/Services/GitHubCredentialStore.cs @@ -2,12 +2,13 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Akavache; +using GitHub.Caches; using GitHub.Extensions.Reactive; +using GitHub.Primitives; using NLog; using Octokit; -using LogManager = NLog.LogManager; -namespace GitHub +namespace GitHub.Services { public class GitHubCredentialStore : ICredentialStore { diff --git a/src/GitHub.App/Services/IEnterpriseProbe.cs b/src/GitHub.App/Services/IEnterpriseProbe.cs index ad4efa0f8..5bffde4fa 100644 --- a/src/GitHub.App/Services/IEnterpriseProbe.cs +++ b/src/GitHub.App/Services/IEnterpriseProbe.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GitHub.Services { diff --git a/src/GitHub.App/Services/StandardUserErrors.cs b/src/GitHub.App/Services/StandardUserErrors.cs index 30af0fdf9..52fd82f22 100644 --- a/src/GitHub.App/Services/StandardUserErrors.cs +++ b/src/GitHub.App/Services/StandardUserErrors.cs @@ -2,22 +2,16 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; using System.Linq; -using System.Net; using System.Net.Http; -using System.Reactive.Linq; -using System.Text.RegularExpressions; +using GitHub.Extensions; +using GitHub.Info; using GitHub.Models; using NLog; using Octokit; -using ApiClient = GitHub.Api.ApiClient; -using LogManager = NLog.LogManager; using ReactiveUI; -using GitHub.Extensions; -using GitHub.Info; -using GitHub.UserErrors; +using ApiClient = GitHub.Api.ApiClient; namespace GitHub.Services { diff --git a/src/GitHub.App/Services/Translation.cs b/src/GitHub.App/Services/Translation.cs index a02ac7582..5ee390000 100644 --- a/src/GitHub.App/Services/Translation.cs +++ b/src/GitHub.App/Services/Translation.cs @@ -2,9 +2,9 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using GitHub.Extensions; using Newtonsoft.Json; using Octokit; -using GitHub.Extensions; namespace GitHub.Services { diff --git a/src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs b/src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs index 30646ca77..12d63be52 100644 --- a/src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs +++ b/src/GitHub.App/UserErrors/PrivateRepositoryQuotaExceededUserError.cs @@ -1,6 +1,7 @@ -using ReactiveUI; using System; using System.Globalization; +using GitHub.Models; +using ReactiveUI; namespace GitHub.UserErrors { @@ -23,7 +24,7 @@ namespace GitHub.UserErrors var errorMessage = string.Format(CultureInfo.InvariantCulture, "You are using {0} out of {1} private repositories.", account.OwnedPrivateRepos, account.PrivateReposInPlan); - return UserError.Throw(new PrivateRepositoryQuotaExceededUserError(account, errorMessage)); + return Throw(new PrivateRepositoryQuotaExceededUserError(account, errorMessage)); } } } \ No newline at end of file diff --git a/src/GitHub.App/UserErrors/PublishRepositoryUserError.cs b/src/GitHub.App/UserErrors/PublishRepositoryUserError.cs index cf3852d38..8f3b0d145 100644 --- a/src/GitHub.App/UserErrors/PublishRepositoryUserError.cs +++ b/src/GitHub.App/UserErrors/PublishRepositoryUserError.cs @@ -1,8 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; +using GitHub.Models; +using GitHub.Services; using Octokit; using ReactiveUI; -using GitHub.Services; namespace GitHub.UserErrors { @@ -17,12 +18,12 @@ namespace GitHub.UserErrors public static IObservable Throw(Exception innerException = null) { var translation = StandardUserErrors.Translator.Value.GetUserErrorTranslation(ErrorType.RepoCreationOnGitHubFailed, innerException); - return UserError.Throw(new PublishRepositoryUserError(translation.ErrorMessage, translation.CauseOrResolution)); + return Throw(new PublishRepositoryUserError(translation.ErrorMessage, translation.CauseOrResolution)); } public static IObservable Throw(string errorMessage, string errorCauseOrResolution = null) { - return UserError.Throw(new PublishRepositoryUserError(errorMessage, errorCauseOrResolution)); + return Throw(new PublishRepositoryUserError(errorMessage, errorCauseOrResolution)); } [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Because no. It's only for that kind of exception")] @@ -31,7 +32,7 @@ namespace GitHub.UserErrors if (account.IsOnFreePlan) { var translation = StandardUserErrors.Translator.Value.GetUserErrorTranslation(ErrorType.RepoCreationOnGitHubFailed, exception); - UserError.Throw(new PrivateRepositoryOnFreeAccountUserError(translation.ErrorMessage, translation.CauseOrResolution)); + Throw(new PrivateRepositoryOnFreeAccountUserError(translation.ErrorMessage, translation.CauseOrResolution)); } else { diff --git a/src/GitHub.App/ViewModels/CloneRepoViewModel.cs b/src/GitHub.App/ViewModels/CloneRepoViewModel.cs index 6d723223c..5baa25604 100644 --- a/src/GitHub.App/ViewModels/CloneRepoViewModel.cs +++ b/src/GitHub.App/ViewModels/CloneRepoViewModel.cs @@ -1,9 +1,9 @@ -using GitHub.Models; -using GitHub.UI; -using ReactiveUI; -using System; +using System; using System.ComponentModel.Composition; using System.Windows.Input; +using GitHub.Exports; +using GitHub.Models; +using ReactiveUI; namespace GitHub.ViewModels { diff --git a/src/GitHub.App/ViewModels/CreateRepoViewModel.cs b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs index 0ccb40928..61fa64e75 100644 --- a/src/GitHub.App/ViewModels/CreateRepoViewModel.cs +++ b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs @@ -1,10 +1,11 @@ -using GitHub.UI; -using GitHub.Validation; -using ReactiveUI; -using System; +using System; using System.ComponentModel.Composition; using System.Reactive; +using System.Reactive.Linq; using System.Windows.Input; +using GitHub.Models; +using GitHub.Validation; +using ReactiveUI; namespace GitHub.ViewModels { diff --git a/src/GitHub.App/ViewModels/LoginControlViewModel.cs b/src/GitHub.App/ViewModels/LoginControlViewModel.cs index 7abd4358a..fb9bb346e 100644 --- a/src/GitHub.App/ViewModels/LoginControlViewModel.cs +++ b/src/GitHub.App/ViewModels/LoginControlViewModel.cs @@ -5,17 +5,17 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reactive.Linq; using System.Reactive.Subjects; +using System.Windows.Input; using GitHub.Authentication; +using GitHub.Exports; using GitHub.Extensions; using GitHub.Info; using GitHub.Models; +using GitHub.Primitives; using GitHub.Services; using GitHub.Validation; using NullGuard; using ReactiveUI; -using System.Windows.Input; -using GitHub.UI; -using GitHub.Exports; namespace GitHub.ViewModels { diff --git a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs index 2cf09b9ee..2310d8472 100644 --- a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs +++ b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs @@ -2,14 +2,14 @@ using System.ComponentModel.Composition; using System.ComponentModel.DataAnnotations; using System.Reactive.Linq; +using System.Windows.Input; using GitHub.Authentication; +using GitHub.Exports; using GitHub.Services; using GitHub.Validation; using NullGuard; using Octokit; using ReactiveUI; -using GitHub.UI; -using System.Windows.Input; namespace GitHub.ViewModels { diff --git a/src/GitHub.Exports.Reactive/Caches/IHostCache.cs b/src/GitHub.Exports.Reactive/Caches/IHostCache.cs index 2b6eed792..2495ffb96 100644 --- a/src/GitHub.Exports.Reactive/Caches/IHostCache.cs +++ b/src/GitHub.Exports.Reactive/Caches/IHostCache.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.Reactive; +using GitHub.Models; using Octokit; -namespace GitHub +namespace GitHub.Caches { /// /// Per host cache data. diff --git a/src/GitHub.Exports.Reactive/Models/IAccount.cs b/src/GitHub.Exports.Reactive/Models/IAccount.cs index 5bd93eb82..714a4a611 100644 --- a/src/GitHub.Exports.Reactive/Models/IAccount.cs +++ b/src/GitHub.Exports.Reactive/Models/IAccount.cs @@ -1,8 +1,6 @@ -using GitHub.Models; -using Octokit; -using ReactiveUI; +using Octokit; -namespace GitHub +namespace GitHub.Models { public interface IAccount { diff --git a/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs b/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs index 53c7863ea..96576d01d 100644 --- a/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs +++ b/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs @@ -1,7 +1,10 @@ using System; using System.Reactive; -using ReactiveUI; +using GitHub.Api; using GitHub.Authentication; +using GitHub.Caches; +using GitHub.Primitives; +using ReactiveUI; namespace GitHub.Models { diff --git a/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs index 28d70abe6..a186c85c6 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/ICreateRepoViewModel.cs @@ -1,10 +1,11 @@ -using GitHub.Validation; -using ReactiveUI; -using System; +using System; using System.Reactive; using System.Windows.Input; +using GitHub.Models; +using GitHub.Validation; +using ReactiveUI; -namespace GitHub.UI +namespace GitHub.ViewModels { public interface ICreateRepoViewModel : IViewModel { diff --git a/src/GitHub.Exports/Api/ISimpleApiClient.cs b/src/GitHub.Exports/Api/ISimpleApiClient.cs index 203c12356..e744d14bf 100644 --- a/src/GitHub.Exports/Api/ISimpleApiClient.cs +++ b/src/GitHub.Exports/Api/ISimpleApiClient.cs @@ -1,10 +1,8 @@ -using GitHub.Services; -using Octokit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; using System.Threading.Tasks; +using GitHub.Primitives; +using GitHub.Services; +using Octokit; namespace GitHub.Api { diff --git a/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs b/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs index 80943e315..9d20ab17f 100644 --- a/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs +++ b/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GitHub.Api { diff --git a/src/GitHub.Exports/GlobalSuppressions.cs b/src/GitHub.Exports/GlobalSuppressions.cs index 5e88255ce4e035bd1eb4cbf29f630aa25ca794a5..64d7e39cacca90d8e92321b00c3f71a0b0de489d 100644 GIT binary patch delta 49 zcmeyybBbrfIcCmMhGK?HhCGJ!$q$(w8LcNDWmcT5#>_XdjBm0N%NH1P66+HHo)8d{ delta 33 jcmX@b^NnZ2Ip)bqEE1FNFxxQdZ8m2SW`r``S(zCD$3O|{ diff --git a/src/GitHub.Exports/Models/IRepositoryModel.cs b/src/GitHub.Exports/Models/IRepositoryModel.cs index 77285ee89..668593def 100644 --- a/src/GitHub.Exports/Models/IRepositoryModel.cs +++ b/src/GitHub.Exports/Models/IRepositoryModel.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using GitHub.Primitives; namespace GitHub.Models { diff --git a/src/GitHub.Exports/Primitives/HostAddress.cs b/src/GitHub.Exports/Primitives/HostAddress.cs index 3d1c73fe6..d0b64929f 100644 --- a/src/GitHub.Exports/Primitives/HostAddress.cs +++ b/src/GitHub.Exports/Primitives/HostAddress.cs @@ -2,7 +2,7 @@ using System.Globalization; using GitHub.Extensions; -namespace GitHub +namespace GitHub.Primitives { public class HostAddress { diff --git a/src/GitHub.Exports/Primitives/StringEquivalent.cs b/src/GitHub.Exports/Primitives/StringEquivalent.cs index 6f9042ebf..207b84657 100644 --- a/src/GitHub.Exports/Primitives/StringEquivalent.cs +++ b/src/GitHub.Exports/Primitives/StringEquivalent.cs @@ -4,7 +4,6 @@ using System.Runtime.Serialization; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; -using GitHub.Helpers; namespace GitHub.Primitives { diff --git a/src/GitHub.Exports/Primitives/UriString.cs b/src/GitHub.Exports/Primitives/UriString.cs index df9f12ae2..09cca08df 100644 --- a/src/GitHub.Exports/Primitives/UriString.cs +++ b/src/GitHub.Exports/Primitives/UriString.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.Serialization; using System.Text.RegularExpressions; using GitHub.Extensions; -using GitHub.Helpers; namespace GitHub.Primitives { diff --git a/src/GitHub.Exports/Services/EnterpriseProbeTask.cs b/src/GitHub.Exports/Services/EnterpriseProbeTask.cs index 41e14f559..be9c5130d 100644 --- a/src/GitHub.Exports/Services/EnterpriseProbeTask.cs +++ b/src/GitHub.Exports/Services/EnterpriseProbeTask.cs @@ -1,13 +1,13 @@ using System; using System.ComponentModel.Composition; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Extensions; using GitHub.Models; using Octokit; using Octokit.Internal; -using System.Threading.Tasks; -using System.Net.Http; -using GitHub.Extensions; -using System.Threading; -using System.Net; namespace GitHub.Services { diff --git a/src/GitHub.Exports/Services/ExportFactoryProvider.cs b/src/GitHub.Exports/Services/ExportFactoryProvider.cs index a482eedca..ac031b031 100644 --- a/src/GitHub.Exports/Services/ExportFactoryProvider.cs +++ b/src/GitHub.Exports/Services/ExportFactoryProvider.cs @@ -1,8 +1,11 @@ -using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; using System.Diagnostics; using System.Globalization; +using System.Linq; +using GitHub.Exports; using GitHub.UI; -using System.ComponentModel.Composition; +using GitHub.ViewModels; namespace GitHub.Services { diff --git a/src/GitHub.Exports/Services/IEnterpriseProbeTask.cs b/src/GitHub.Exports/Services/IEnterpriseProbeTask.cs index 3081f51b3..2255a91a8 100644 --- a/src/GitHub.Exports/Services/IEnterpriseProbeTask.cs +++ b/src/GitHub.Exports/Services/IEnterpriseProbeTask.cs @@ -1,5 +1,4 @@ -using Octokit; -using System; +using System; using System.Threading.Tasks; namespace GitHub.Services diff --git a/src/GitHub.Exports/Services/IUIProvider.cs b/src/GitHub.Exports/Services/IUIProvider.cs index b1eba83da..6a0d6b2a2 100644 --- a/src/GitHub.Exports/Services/IUIProvider.cs +++ b/src/GitHub.Exports/Services/IUIProvider.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.ComponentModel.Composition.Hosting; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GitHub.Services { diff --git a/src/GitHub.Exports/Services/IWikiProbe.cs b/src/GitHub.Exports/Services/IWikiProbe.cs index 09255d90b..160f4df28 100644 --- a/src/GitHub.Exports/Services/IWikiProbe.cs +++ b/src/GitHub.Exports/Services/IWikiProbe.cs @@ -1,9 +1,5 @@ -using Octokit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Octokit; namespace GitHub.Services { diff --git a/src/GitHub.Exports/UI/IUIController.cs b/src/GitHub.Exports/UI/IUIController.cs index 68b522981..c8568cdf0 100644 --- a/src/GitHub.Exports/UI/IUIController.cs +++ b/src/GitHub.Exports/UI/IUIController.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GitHub.UI { diff --git a/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs b/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs index 8f332ad30..e62c2de2d 100644 --- a/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs +++ b/src/GitHub.Exports/ViewModels/ICloneRepoViewModel.cs @@ -1,7 +1,7 @@ using System; using System.Windows.Input; -namespace GitHub.UI +namespace GitHub.ViewModels { public interface ICloneRepoViewModel : IViewModel { diff --git a/src/GitHub.Exports/ViewModels/ILoginViewModel.cs b/src/GitHub.Exports/ViewModels/ILoginViewModel.cs index cd4c12cec..a6b6d09ce 100644 --- a/src/GitHub.Exports/ViewModels/ILoginViewModel.cs +++ b/src/GitHub.Exports/ViewModels/ILoginViewModel.cs @@ -1,8 +1,8 @@ -using GitHub.Authentication; -using System; +using System; using System.Windows.Input; +using GitHub.Authentication; -namespace GitHub.UI +namespace GitHub.ViewModels { public interface ILoginViewModel : IViewModel { diff --git a/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs b/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs index dea30cd90..57bbd4116 100644 --- a/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs +++ b/src/GitHub.Exports/ViewModels/ITwoFactorViewModel.cs @@ -1,6 +1,6 @@ using System.Windows.Input; -namespace GitHub.UI +namespace GitHub.ViewModels { public interface ITwoFactorViewModel : IViewModel { diff --git a/src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs b/src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs index 2657f694d..2a7e0689d 100644 --- a/src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs +++ b/src/GitHub.Extensions.Reactive/RecoveryCommandWithIcon.cs @@ -1,5 +1,5 @@ -using ReactiveUI; -using System; +using System; +using ReactiveUI; namespace GitHub.Extensions { diff --git a/src/GitHub.Extensions/TaskExtensions.cs b/src/GitHub.Extensions/TaskExtensions.cs index 9cfffa4ae..6b838d065 100644 --- a/src/GitHub.Extensions/TaskExtensions.cs +++ b/src/GitHub.Extensions/TaskExtensions.cs @@ -1,9 +1,6 @@ -using NullGuard; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; using System.Threading.Tasks; +using NullGuard; namespace GitHub.Extensions { diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs index d3f024c5a..16e844ee9 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs +++ b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.cs @@ -6,8 +6,8 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using GitHub.Extensions; -using ReactiveUI; using GitHub.Extensions.Reactive; +using ReactiveUI; namespace GitHub.UI { diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs b/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs index 1d3c4cf49..6b46fec2a 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs +++ b/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.cs @@ -5,8 +5,8 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using GitHub.Extensions.Reactive; -using ReactiveUI; using GitHub.Validation; +using ReactiveUI; namespace GitHub.UI { diff --git a/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs b/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs index 8542c7e95..d101666b7 100644 --- a/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs +++ b/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs @@ -1,12 +1,11 @@ using System; -using System.Linq; using System.Reactive.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; -using ReactiveUI; using GitHub.Extensions.Reactive; +using ReactiveUI; namespace GitHub.UI { diff --git a/src/GitHub.VisualStudio/Base/PackageBase.cs b/src/GitHub.VisualStudio/Base/PackageBase.cs index 69c3db19b..36b34e509 100644 --- a/src/GitHub.VisualStudio/Base/PackageBase.cs +++ b/src/GitHub.VisualStudio/Base/PackageBase.cs @@ -1,10 +1,10 @@ -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell; -using System; +using System; using System.ComponentModel.Design; using System.Diagnostics; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Base { public abstract class PackageBase : Package { diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs index a60e67512..70dcef629 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs @@ -1,8 +1,8 @@ -using Microsoft.TeamFoundation.Client; -using NullGuard; -using System; +using System; using System.ComponentModel; using System.Diagnostics; +using Microsoft.TeamFoundation.Client; +using NullGuard; namespace GitHub.VisualStudio.Base { diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs b/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs index 95beb5dfa..98f399456 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs @@ -1,14 +1,11 @@ -using Microsoft.TeamFoundation.Client; +using System; +using System.Diagnostics; +using GitHub.Extensions; +using GitHub.VisualStudio.Services; +using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; using NullGuard; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using GitHub.Extensions; namespace GitHub.VisualStudio.Base { diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs index 694ec5351..1c2c2798b 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs @@ -1,10 +1,8 @@ -using NullGuard; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.Composition; +using GitHub.Models; +using GitHub.VisualStudio.Helpers; +using NullGuard; +using Octokit; namespace GitHub.VisualStudio.Base { @@ -40,18 +38,15 @@ namespace GitHub.VisualStudio.Base { } } -} -namespace GitHub.VisualStudio.Exports -{ - [Export(typeof(Octokit.IGitHubClient))] - public class GHClient : Octokit.GitHubClient + [Export(typeof(IGitHubClient))] + public class GHClient : GitHubClient { [ImportingConstructor] - public GHClient(Models.IProgram program) + public GHClient(IProgram program) : base(program.ProductHeader) { } } -} +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs index ba2510195..dee699f35 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs @@ -1,20 +1,17 @@ -using Microsoft.TeamFoundation.Controls; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Drawing; -using NullGuard; -using GitHub.VisualStudio.Base; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.TeamFoundation.Client; -using GitHub.Api; -using Microsoft.VisualStudio; +using System; using System.Diagnostics; +using System.Drawing; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Primitives; using GitHub.Services; +using GitHub.VisualStudio.Helpers; +using GitHub.VisualStudio.Services; +using Microsoft.TeamFoundation.Client; +using Microsoft.TeamFoundation.Controls; +using NullGuard; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Base { public class TeamExplorerNavigationItemBase : TeamExplorerGitAwareItem, ITeamExplorerNavigationItem2, INotifyPropertySource { diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationLinkBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationLinkBase.cs index 5f04975cd..6f4d9f1a9 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationLinkBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationLinkBase.cs @@ -1,13 +1,6 @@ -using GitHub.VisualStudio.Base; -using Microsoft.TeamFoundation.Controls; -using NullGuard; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.TeamFoundation.Controls; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Base { public class TeamExplorerNavigationLinkBase : TeamExplorerItemBase, ITeamExplorerNavigationLink { diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs index 6ff946a29..3e23b4d55 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs @@ -1,10 +1,10 @@ -using GitHub.VisualStudio.Base; +using System; +using GitHub.VisualStudio.Helpers; +using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using NullGuard; -using System; -using Microsoft.TeamFoundation.Client; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Base { public class TeamExplorerSectionBase : TeamExplorerGitAwareItem, ITeamExplorerSection, INotifyPropertySource { diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index e922f5cfc..eb086c2f8 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -1,12 +1,11 @@ using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using GitHub.VisualStudio.UI.Views; -using Microsoft.VisualStudio.Shell; -using GitHub.Exports; using GitHub.Services; -using GitHub.Authentication; +using GitHub.UI; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.UI; +using Microsoft.VisualStudio.Shell; namespace GitHub.VisualStudio { @@ -57,7 +56,7 @@ namespace GitHub.VisualStudio AddTopLevelMenuItem(PkgCmdIDList.cloneRepoCommand, OnCloneRepo); } - void ShowDialog(GitHub.UI.UIControllerFlow flow) + void ShowDialog(UIControllerFlow flow) { var ui = GetExportedValue(); var disposable = ui.GetService().UIControllerFactory.CreateExport(); @@ -74,17 +73,17 @@ namespace GitHub.VisualStudio void OnCreateRepo(object sender, EventArgs e) { - ShowDialog(GitHub.UI.UIControllerFlow.Create); + ShowDialog(UIControllerFlow.Create); } void OnCloneRepo(object sender, EventArgs e) { - ShowDialog(GitHub.UI.UIControllerFlow.Clone); + ShowDialog(UIControllerFlow.Clone); } void OnLoginCommand(object sender, EventArgs e) { - ShowDialog(GitHub.UI.UIControllerFlow.Authentication); + ShowDialog(UIControllerFlow.Authentication); } } } diff --git a/src/GitHub.VisualStudio/Helpers/Browser.cs b/src/GitHub.VisualStudio/Helpers/Browser.cs index 35d8fdfef..5de7479b1 100644 --- a/src/GitHub.VisualStudio/Helpers/Browser.cs +++ b/src/GitHub.VisualStudio/Helpers/Browser.cs @@ -1,13 +1,15 @@ -using GitHub.Services; +using System; +using System.ComponentModel.Composition; +using System.IO; +using GitHub.Info; +using GitHub.Services; +using GitHub.VisualStudio.Services; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Rothko; -using System; -using System.ComponentModel.Composition; -using System.IO; -namespace GitHub.Exports +namespace GitHub.VisualStudio.Helpers { [Export(typeof(IBrowser))] public class Browser : IBrowser @@ -36,7 +38,7 @@ namespace GitHub.Exports */ var service = serviceProvider.GetService(typeof(SVsWebBrowsingService)) as IVsWebBrowsingService; if (service == null) - service = VisualStudio.Services.WebBrowsing; + service = Services.WebBrowsing; if (service != null) { diff --git a/src/GitHub.VisualStudio/Helpers/Colors.cs b/src/GitHub.VisualStudio/Helpers/Colors.cs index 001c24db9..710c6a4b1 100644 --- a/src/GitHub.VisualStudio/Helpers/Colors.cs +++ b/src/GitHub.VisualStudio/Helpers/Colors.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Media; namespace GitHub.VisualStudio.Helpers diff --git a/src/GitHub.VisualStudio/Helpers/Constants.cs b/src/GitHub.VisualStudio/Helpers/Constants.cs index b7a40d090..b4c227e3a 100644 --- a/src/GitHub.VisualStudio/Helpers/Constants.cs +++ b/src/GitHub.VisualStudio/Helpers/Constants.cs @@ -1,4 +1,4 @@ -namespace GitHub +namespace GitHub.VisualStudio.Helpers { public static class Constants { diff --git a/src/GitHub.VisualStudio/Helpers/INotifyPropertySource.cs b/src/GitHub.VisualStudio/Helpers/INotifyPropertySource.cs index dd91cf6ee..23d8dbe8e 100644 --- a/src/GitHub.VisualStudio/Helpers/INotifyPropertySource.cs +++ b/src/GitHub.VisualStudio/Helpers/INotifyPropertySource.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Helpers { public interface INotifyPropertySource { diff --git a/src/GitHub.VisualStudio/Helpers/PropertyNotifierExtensions.cs b/src/GitHub.VisualStudio/Helpers/PropertyNotifierExtensions.cs index 48fd7b40c..899e9aa40 100644 --- a/src/GitHub.VisualStudio/Helpers/PropertyNotifierExtensions.cs +++ b/src/GitHub.VisualStudio/Helpers/PropertyNotifierExtensions.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Helpers { public static class PropertyNotifierExtensions { diff --git a/src/GitHub.VisualStudio/Services/Logger.cs b/src/GitHub.VisualStudio/Services/Logger.cs index 2e803781c..76ab767ed 100644 --- a/src/GitHub.VisualStudio/Services/Logger.cs +++ b/src/GitHub.VisualStudio/Services/Logger.cs @@ -1,15 +1,11 @@ -using EnvDTE; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; -using System; -using System.Collections.Generic; +using System; using System.Diagnostics; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using EnvDTE; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Services { public class VSTraceListener : TraceListener { diff --git a/src/GitHub.VisualStudio/Services/Program.cs b/src/GitHub.VisualStudio/Services/Program.cs index 50c2689f6..ec245ac2d 100644 --- a/src/GitHub.VisualStudio/Services/Program.cs +++ b/src/GitHub.VisualStudio/Services/Program.cs @@ -4,7 +4,7 @@ using System.Reflection; using GitHub.Models; using Octokit; -namespace GitHub +namespace GitHub.VisualStudio.Services { // Represents the currently executing program. [Export(typeof(IProgram))] diff --git a/src/GitHub.VisualStudio/Services/Services.cs b/src/GitHub.VisualStudio/Services/Services.cs index 8d3dfd6c1..cf6113e2e 100644 --- a/src/GitHub.VisualStudio/Services/Services.cs +++ b/src/GitHub.VisualStudio/Services/Services.cs @@ -1,6 +1,7 @@ -using EnvDTE; +using System; +using System.Linq; +using EnvDTE; using EnvDTE80; -using GitHub.Api; using GitHub.Services; using LibGit2Sharp; using Microsoft.VisualStudio; @@ -9,15 +10,8 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; using NullGuard; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition.Hosting; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Services { public static class Services { diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index df9e47aa9..acf11b227 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -1,11 +1,4 @@ -using GitHub.Infrastructure; -using GitHub.Services; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell; -using NullGuard; -using ReactiveUI; -using Splat; -using System; +using System; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Diagnostics; @@ -13,8 +6,15 @@ using System.Globalization; using System.Linq; using System.Reactive.Concurrency; using System.Windows; +using GitHub.Infrastructure; +using GitHub.Services; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using NullGuard; +using ReactiveUI; +using Splat; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Services { [Export(typeof(IUIProvider))] [Export(typeof(IServiceProvider))] diff --git a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs index 1924b2072..9653dbc9c 100644 --- a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs +++ b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs @@ -1,16 +1,13 @@ -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Models; +using System; +using System.ComponentModel.Composition; using GitHub.Services; +using GitHub.UI; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Services; +using GitHub.VisualStudio.UI; using GitHub.VisualStudio.UI.Views; using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio.Shell; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace GitHub.VisualStudio.TeamExplorerConnect { @@ -46,7 +43,7 @@ namespace GitHub.VisualStudio.TeamExplorerConnect var factory = ui.GetService(); var d = factory.UIControllerFactory.CreateExport(); - var creation = d.Value.SelectFlow(GitHub.UI.UIControllerFlow.Create); + var creation = d.Value.SelectFlow(UIControllerFlow.Create); var x = new WindowController(creation); creation.Subscribe(_ => { }, _ => x.Close()); x.Show(); @@ -60,7 +57,7 @@ namespace GitHub.VisualStudio.TeamExplorerConnect var factory = ui.GetService(); var d = factory.UIControllerFactory.CreateExport(); - var creation = d.Value.SelectFlow(GitHub.UI.UIControllerFlow.Clone); + var creation = d.Value.SelectFlow(UIControllerFlow.Clone); creation.Subscribe(_ => { }, _ => d.Dispose()); var x = new WindowController(creation); x.Show(); diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs b/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs index ba585c809..694f768a0 100644 --- a/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs +++ b/src/GitHub.VisualStudio/TeamExplorerHome/GitHubHomeSection.cs @@ -1,12 +1,13 @@ -using GitHub.VisualStudio.UI.Views; -using Microsoft.TeamFoundation.Controls; -using NullGuard; -using System; -using Microsoft.TeamFoundation.Client; +using System; using System.ComponentModel.Composition; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; +using GitHub.VisualStudio.UI.Views; +using Microsoft.TeamFoundation.Client; +using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio.Shell; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.TeamExplorerHome { [TeamExplorerSection(GitHubHomeSectionId, TeamExplorerPageIds.Home, 10)] public class GitHubHomeSection : TeamExplorerSectionBase diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/GraphsNavigationItem.cs b/src/GitHub.VisualStudio/TeamExplorerHome/GraphsNavigationItem.cs index 8ba1fa3c4..df10eb057 100644 --- a/src/GitHub.VisualStudio/TeamExplorerHome/GraphsNavigationItem.cs +++ b/src/GitHub.VisualStudio/TeamExplorerHome/GraphsNavigationItem.cs @@ -1,14 +1,14 @@ using System; using System.ComponentModel.Composition; +using GitHub.Api; +using GitHub.Services; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio.Shell; -using GitHub.Api; -using GitHub.Services; -using GitHub.VisualStudio.Helpers; -using System.Diagnostics; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.TeamExplorerHome { [TeamExplorerNavigationItem(GraphsNavigationItemId, NavigationItemPriority.Graphs, diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/IssuesNavigationItem.cs b/src/GitHub.VisualStudio/TeamExplorerHome/IssuesNavigationItem.cs index d21c5171c..38984d87a 100644 --- a/src/GitHub.VisualStudio/TeamExplorerHome/IssuesNavigationItem.cs +++ b/src/GitHub.VisualStudio/TeamExplorerHome/IssuesNavigationItem.cs @@ -1,14 +1,14 @@ using System; using System.ComponentModel.Composition; +using GitHub.Api; +using GitHub.Services; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio.Shell; -using GitHub.Api; -using GitHub.Services; -using GitHub.VisualStudio.Helpers; -using System.Diagnostics; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.TeamExplorerHome { [TeamExplorerNavigationItem(IssuesNavigationItemId, NavigationItemPriority.Issues, diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/PullRequestsNavigationItem.cs b/src/GitHub.VisualStudio/TeamExplorerHome/PullRequestsNavigationItem.cs index 0ba1e8fa5..bfea00c2a 100644 --- a/src/GitHub.VisualStudio/TeamExplorerHome/PullRequestsNavigationItem.cs +++ b/src/GitHub.VisualStudio/TeamExplorerHome/PullRequestsNavigationItem.cs @@ -1,14 +1,14 @@ using System; using System.ComponentModel.Composition; +using GitHub.Api; +using GitHub.Services; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio.Shell; -using GitHub.Api; -using GitHub.Services; -using GitHub.VisualStudio.Helpers; -using System.Diagnostics; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.TeamExplorerHome { [TeamExplorerNavigationItem(PullRequestsNavigationItemId, NavigationItemPriority.PullRequests, diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/PulseNavigationItem.cs b/src/GitHub.VisualStudio/TeamExplorerHome/PulseNavigationItem.cs index af4038abb..018e2d982 100644 --- a/src/GitHub.VisualStudio/TeamExplorerHome/PulseNavigationItem.cs +++ b/src/GitHub.VisualStudio/TeamExplorerHome/PulseNavigationItem.cs @@ -1,14 +1,14 @@ using System; using System.ComponentModel.Composition; +using GitHub.Api; +using GitHub.Services; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio.Shell; -using GitHub.Api; -using GitHub.Services; -using GitHub.VisualStudio.Helpers; -using System.Diagnostics; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.TeamExplorerHome { [TeamExplorerNavigationItem(PulseNavigationItemId, NavigationItemPriority.Pulse, diff --git a/src/GitHub.VisualStudio/TeamExplorerHome/WikiNavigationItem.cs b/src/GitHub.VisualStudio/TeamExplorerHome/WikiNavigationItem.cs index 8fc0c9bbe..f7baf452a 100644 --- a/src/GitHub.VisualStudio/TeamExplorerHome/WikiNavigationItem.cs +++ b/src/GitHub.VisualStudio/TeamExplorerHome/WikiNavigationItem.cs @@ -1,14 +1,14 @@ using System; using System.ComponentModel.Composition; +using GitHub.Api; +using GitHub.Services; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio.Shell; -using GitHub.Api; -using GitHub.Services; -using GitHub.VisualStudio.Helpers; -using System.Diagnostics; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.TeamExplorerHome { [TeamExplorerNavigationItem(WikiNavigationItemId, NavigationItemPriority.Wiki, diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml index 8a61dbc19..4ee4441df 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml @@ -3,20 +3,14 @@ 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:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" + xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:SampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:GitHub="clr-namespace:GitHub.VisualStudio.Helpers" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Background="White"> - - - - - - - - @@ -41,11 +35,11 @@ - - - - - + + + + + diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs index 668f5326f..93768ece4 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs @@ -1,9 +1,10 @@ -using GitHub.UI; -using GitHub.UI.Helpers; +using System.Windows; +using GitHub.Exports; +using GitHub.UI; +using GitHub.ViewModels; using NullGuard; using ReactiveUI; -using System.ComponentModel.Composition; -using System.Windows; +using GitHub.UI.Helpers; namespace GitHub.VisualStudio.UI.Views.Controls { diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml index 21c7b947d..ae801da30 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml @@ -4,11 +4,11 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - xmlns:GitHub="clr-namespace:GitHub" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:GitHub="clr-namespace:GitHub.VisualStudio.Helpers" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="432" d:DesignWidth="354" Background="White"> diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs index fed9f9399..7a12e8492 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs @@ -1,13 +1,14 @@ -using GitHub.Extensions.Reactive; -using GitHub.UI; -using GitHub.UI.Helpers; -using GitHub.UserErrors; -using NullGuard; -using ReactiveUI; -using System; -using System.ComponentModel.Composition; +using System; using System.Reactive.Linq; using System.Windows; +using GitHub.Exports; +using GitHub.Extensions.Reactive; +using GitHub.UI; +using GitHub.UserErrors; +using GitHub.ViewModels; +using NullGuard; +using ReactiveUI; +using GitHub.UI.Helpers; namespace GitHub.VisualStudio.UI.Views.Controls { diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml index b12baa675..04bc445c3 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml +++ b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml @@ -4,18 +4,14 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" + xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:GitHub="clr-namespace:GitHub.VisualStudio.Helpers" mc:Ignorable="d" d:DesignHeight="541" d:DesignWidth="345" Background="White"> - - - - - - - - diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs index 545163eb0..c64cced65 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs @@ -1,10 +1,11 @@ using System; -using System.ComponentModel.Composition; using System.Reactive.Linq; using System.Windows; using System.Windows.Input; -using ReactiveUI; +using GitHub.Exports; using GitHub.UI; +using GitHub.ViewModels; +using ReactiveUI; using GitHub.UI.Helpers; namespace GitHub.VisualStudio.UI.Views.Controls diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs b/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs index 7bccc0bb1..969b242fc 100644 --- a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs @@ -1,18 +1,6 @@ -using GitHub.VisualStudio.TeamExplorerConnect; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; +using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; +using GitHub.VisualStudio.TeamExplorerConnect; namespace GitHub.VisualStudio.UI.Views { diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs index 39250d85c..a76bc9a5b 100644 --- a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Controls; +using GitHub.VisualStudio.TeamExplorerHome; namespace GitHub.VisualStudio.UI.Views { diff --git a/src/GitHub.VisualStudio/UI/WindowController.xaml b/src/GitHub.VisualStudio/UI/WindowController.xaml index f3f655023..61b6d42a2 100644 --- a/src/GitHub.VisualStudio/UI/WindowController.xaml +++ b/src/GitHub.VisualStudio/UI/WindowController.xaml @@ -1,4 +1,4 @@ - Date: Thu, 26 Feb 2015 16:53:51 +0100 Subject: [PATCH 22/35] Export 2fa viewmodel for the 2fa challenge handler --- src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs index 2310d8472..7fcaa3f00 100644 --- a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs +++ b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs @@ -14,6 +14,7 @@ using ReactiveUI; namespace GitHub.ViewModels { [ExportViewModel(ViewType=UIViewType.TwoFactor)] + [Export(typeof(ITwoFactorViewModel))] [PartCreationPolicy(CreationPolicy.Shared)] public class TwoFactorDialogViewModel : ReactiveValidatableObject, ITwoFactorViewModel { From 821663ffbbb49093e46a213b740c2b97512433f9 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 16:55:06 +0100 Subject: [PATCH 23/35] Fix ExportFactoryProvider build and code issues --- .../Services/ExportFactoryProvider.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/GitHub.Exports/Services/ExportFactoryProvider.cs b/src/GitHub.Exports/Services/ExportFactoryProvider.cs index ac031b031..d6abc2908 100644 --- a/src/GitHub.Exports/Services/ExportFactoryProvider.cs +++ b/src/GitHub.Exports/Services/ExportFactoryProvider.cs @@ -17,24 +17,27 @@ namespace GitHub.Services { cc.SatisfyImportsOnce(this); } - + + [Import(AllowRecomposition = true)] + public ExportFactory UIControllerFactory { get; set; } + [ImportMany(AllowRecomposition = true)] public IEnumerable> ViewModelFactory { get; set; } [ImportMany(AllowRecomposition = true)] - public IEnumerable> ViewFactory { get; set; } + public IEnumerable> ViewFactory { get; set; } public ExportLifetimeContext GetViewModel(UIViewType viewType) { var f = ViewModelFactory.FirstOrDefault(x => x.Metadata.ViewType == viewType); - Debug.Assert(f != null, string.Format("Could not locate view model for {0}.", viewtype)); + Debug.Assert(f != null, string.Format(CultureInfo.InvariantCulture, "Could not locate view model for {0}.", viewType)); return f.CreateExport(); } - public ExportLifetimeContext GetView(UIViewType viewType) + public ExportLifetimeContext GetView(UIViewType viewType) { var f = ViewFactory.FirstOrDefault(x => x.Metadata.ViewType == viewType); - Debug.Assert(f != null, string.Format("Could not locate view for {0}.", viewtype)); + Debug.Assert(f != null, string.Format(CultureInfo.InvariantCulture, "Could not locate view for {0}.", viewType)); return f.CreateExport(); } } From 2e52a3ff10c90d3f83451bb5ab54c39ef99f4377 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 16:55:31 +0100 Subject: [PATCH 24/35] Fix code analysis --- src/GitHub.Exports/ViewModels/IViewModel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/GitHub.Exports/ViewModels/IViewModel.cs b/src/GitHub.Exports/ViewModels/IViewModel.cs index df8a5f04d..64d32b490 100644 --- a/src/GitHub.Exports/ViewModels/IViewModel.cs +++ b/src/GitHub.Exports/ViewModels/IViewModel.cs @@ -1,8 +1,9 @@ using System; -namespace GitHub.ViewModels { - - public interface IViewModel { +namespace GitHub.ViewModels +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces")] + public interface IViewModel { } } \ No newline at end of file From dd120a49bd8ef798124b6166fa9df1c3d20d0a8a Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 16:57:16 +0100 Subject: [PATCH 25/35] Fix exporting of views --- src/GitHub.App/Controllers/UIController.cs | 35 +++++++++---------- .../Views/Controls/CloneRepoControl.xaml.cs | 11 ++++-- .../Views/Controls/CreateRepoControl.xaml.cs | 8 ++++- .../UI/Views/Controls/LoginControl.xaml.cs | 8 ++++- .../Views/Controls/TwoFactorControl.xaml.cs | 8 ++++- 5 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs index 7211e7599..150577e1a 100644 --- a/src/GitHub.App/Controllers/UIController.cs +++ b/src/GitHub.App/Controllers/UIController.cs @@ -32,7 +32,7 @@ namespace GitHub.Controllers { this.factory = factory; - machine = new StateMachine(UIViewType.Start); + machine = new StateMachine(UIViewType.None); machine.Configure(UIViewType.None) .Permit(Trigger.Auth, UIViewType.Login) @@ -44,9 +44,9 @@ namespace GitHub.Controllers machine.Configure(UIViewType.Login) .OnEntry(() => { - var disposable = factory.GetViewModel(UIViewType.Login); - disposables.Add(disposable); - var viewModel = disposable.Value as ILoginViewModel; + var dvm = factory.GetViewModel(UIViewType.Login); + disposables.Add(dvm); + var viewModel = dvm.Value as ILoginViewModel; viewModel.AuthenticationResults.Subscribe(result => { @@ -54,12 +54,12 @@ namespace GitHub.Controllers Fire(Trigger.Next); }); - disposable = factory.GetView(UIViewType.Login); - disposables.Add(disposable); - var view = disposable.Value; + var dv = factory.GetView(UIViewType.Login); + disposables.Add(dv); + var view = dv.Value; view.ViewModel = viewModel; - var twofa = factory.GetViewModel(UIViewType.TwoFactor).Value; + var twofa = factory.GetViewModel(UIViewType.TwoFactor).Value as ITwoFactorViewModel; twofa.WhenAny(x => x.IsShowing, x => x.Value) .Where(x => x) .Subscribe(_ => @@ -85,6 +85,7 @@ namespace GitHub.Controllers machine.Configure(UIViewType.Create) .OnEntry(() => { + var view = SetupView(UIViewType.Create); transition.OnNext(view); }) .Permit(Trigger.Next, UIViewType.End); @@ -103,20 +104,18 @@ namespace GitHub.Controllers transition.OnCompleted(); transition.Dispose(); }) - .Permit(Trigger.Next, UIViewType.Start); + .Permit(Trigger.Next, UIViewType.None); } - IViewFor SetupView(UIViewType viewType) + IView SetupView(UIViewType viewType) { - IViewModel disposable; - - disposable = factory.GetViewModel(viewType); - disposables.Add(disposable); - var viewModel = disposable.Value; + var dvm = factory.GetViewModel(viewType); + disposables.Add(dvm); + var viewModel = dvm.Value; - disposable = factory.GetView(viewType); - disposables.Add(disposable); - var view = disposable.Value; + var dv = factory.GetView(viewType); + disposables.Add(dv); + var view = dv.Value; view.ViewModel = viewModel; return view; diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs index 93768ece4..72bad05a9 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs @@ -12,7 +12,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// Interaction logic for CloneRepoControl.xaml /// [ExportView(ViewType=UIViewType.Clone)] - public partial class CloneRepoControl : IViewFor + public partial class CloneRepoControl : IViewFor, IView { public CloneRepoControl() { @@ -32,11 +32,16 @@ namespace GitHub.VisualStudio.UI.Views.Controls set { ViewModel = (ICloneRepoViewModel)value; } } + object IView.ViewModel + { + get { return ViewModel; } + set { ViewModel = (ICloneRepoViewModel)value; } + } + public ICloneRepoViewModel ViewModel { [return: AllowNull] - get - { return (ICloneRepoViewModel)GetValue(ViewModelProperty); } + get { return (ICloneRepoViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } } diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs index 7a12e8492..28f28cb05 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs @@ -16,7 +16,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// Interaction logic for CloneRepoControl.xaml /// [ExportView(ViewType=UIViewType.Create)] - public partial class CreateRepoControl : IViewFor + public partial class CreateRepoControl : IViewFor, IView { public CreateRepoControl() { @@ -75,6 +75,12 @@ namespace GitHub.VisualStudio.UI.Views.Controls set { ViewModel = (ICreateRepoViewModel)value; } } + object IView.ViewModel + { + get { return ViewModel; } + set { ViewModel = (ICreateRepoViewModel)value; } + } + public ICreateRepoViewModel ViewModel { [return: AllowNull] diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs index 60669c685..0591c1446 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs @@ -12,7 +12,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// Interaction logic for LoginControl.xaml /// [ExportView(ViewType=UIViewType.Login)] - public partial class LoginControl : IViewFor + public partial class LoginControl : IViewFor, IView { public LoginControl() { @@ -45,6 +45,12 @@ namespace GitHub.VisualStudio.UI.Views.Controls set { ViewModel = (ILoginViewModel)value; } } + object IView.ViewModel + { + get { return ViewModel; } + set { ViewModel = (ILoginViewModel)value; } + } + public ILoginViewModel ViewModel { [return: AllowNull] diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs index c64cced65..852d915de 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs @@ -14,7 +14,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls /// Interaction logic for PasswordView.xaml /// [ExportView(ViewType=UIViewType.TwoFactor)] - public partial class TwoFactorControl : IViewFor + public partial class TwoFactorControl : IViewFor, IView { public TwoFactorControl() { @@ -66,5 +66,11 @@ namespace GitHub.VisualStudio.UI.Views.Controls get { return ViewModel; } set { ViewModel = (ITwoFactorViewModel)value; } } + + object IView.ViewModel + { + get { return ViewModel; } + set { ViewModel = (ITwoFactorViewModel)value; } + } } } From c84501051bd17ded0324cf4b474445ae142a5714 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 16:57:50 +0100 Subject: [PATCH 26/35] Add composition service for testing --- src/DesignTimeStyleHelper/App.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DesignTimeStyleHelper/App.xaml.cs b/src/DesignTimeStyleHelper/App.xaml.cs index 179a98c0a..dc41ccda6 100644 --- a/src/DesignTimeStyleHelper/App.xaml.cs +++ b/src/DesignTimeStyleHelper/App.xaml.cs @@ -53,6 +53,7 @@ namespace DesignTimeStyleHelper var batch = new CompositionBatch(); batch.AddExportedValue(this); batch.AddExportedValue(this); + batch.AddExportedValue(DefaultCompositionService); batch.AddExportedValue(new PlaceholderGitHubSection(this)); container.Compose(batch); } From 8301bddcfba2fd356bdae7a0cba07f6cee825f78 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 17:21:16 +0100 Subject: [PATCH 27/35] Aaaaand it builds again --- src/DesignTimeStyleHelper/App.xaml.cs | 4 +++- src/GitHub.App/ViewModels/CreateRepoViewModel.cs | 3 +-- src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs | 1 - .../Base/TeamExplorerNavigationItemBase.cs | 1 - src/GitHub.VisualStudio/Helpers/Browser.cs | 1 - src/GitHub.VisualStudio/Services/Logger.cs | 2 +- src/GitHub.VisualStudio/Services/Program.cs | 2 +- src/GitHub.VisualStudio/Services/Services.cs | 2 +- src/GitHub.VisualStudio/Services/UIProvider.cs | 2 +- .../TeamExplorerConnect/PlaceholderGitHubSection.cs | 1 - .../UI/Views/Controls/CreateRepoControl.xaml.cs | 2 ++ 11 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/DesignTimeStyleHelper/App.xaml.cs b/src/DesignTimeStyleHelper/App.xaml.cs index dc41ccda6..6d7bf71da 100644 --- a/src/DesignTimeStyleHelper/App.xaml.cs +++ b/src/DesignTimeStyleHelper/App.xaml.cs @@ -10,6 +10,8 @@ using GitHub.VisualStudio.TeamExplorerConnect; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; using Moq; +using GitHub.Services; +using GitHub.VisualStudio; namespace DesignTimeStyleHelper { @@ -39,7 +41,7 @@ namespace DesignTimeStyleHelper public CustomServiceProvider() { catalog = new AggregateCatalog( - new AssemblyCatalog(typeof(GitHub.VisualStudio.Services.Services).Assembly), // GitHub.VisualStudio + new AssemblyCatalog(typeof(GitHub.VisualStudio.Services).Assembly), // GitHub.VisualStudio new AssemblyCatalog(typeof(GitHub.Api.ApiClient).Assembly), // GitHub.App new AssemblyCatalog(typeof(GitHub.Api.SimpleApiClient).Assembly), // GitHub.Api new AssemblyCatalog(typeof(Rothko.Environment).Assembly), // Rothko diff --git a/src/GitHub.App/ViewModels/CreateRepoViewModel.cs b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs index 61fa64e75..db70526fd 100644 --- a/src/GitHub.App/ViewModels/CreateRepoViewModel.cs +++ b/src/GitHub.App/ViewModels/CreateRepoViewModel.cs @@ -1,11 +1,10 @@ using System; -using System.ComponentModel.Composition; using System.Reactive; -using System.Reactive.Linq; using System.Windows.Input; using GitHub.Models; using GitHub.Validation; using ReactiveUI; +using GitHub.Exports; namespace GitHub.ViewModels { diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs b/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs index 98f399456..d90bb27b1 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItem.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using GitHub.Extensions; -using GitHub.VisualStudio.Services; using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs index dee699f35..734c1c598 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs @@ -6,7 +6,6 @@ using GitHub.Api; using GitHub.Primitives; using GitHub.Services; using GitHub.VisualStudio.Helpers; -using GitHub.VisualStudio.Services; using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using NullGuard; diff --git a/src/GitHub.VisualStudio/Helpers/Browser.cs b/src/GitHub.VisualStudio/Helpers/Browser.cs index 5de7479b1..32b68c6ba 100644 --- a/src/GitHub.VisualStudio/Helpers/Browser.cs +++ b/src/GitHub.VisualStudio/Helpers/Browser.cs @@ -3,7 +3,6 @@ using System.ComponentModel.Composition; using System.IO; using GitHub.Info; using GitHub.Services; -using GitHub.VisualStudio.Services; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; diff --git a/src/GitHub.VisualStudio/Services/Logger.cs b/src/GitHub.VisualStudio/Services/Logger.cs index 76ab767ed..a536229a1 100644 --- a/src/GitHub.VisualStudio/Services/Logger.cs +++ b/src/GitHub.VisualStudio/Services/Logger.cs @@ -5,7 +5,7 @@ using EnvDTE; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; -namespace GitHub.VisualStudio.Services +namespace GitHub.VisualStudio { public class VSTraceListener : TraceListener { diff --git a/src/GitHub.VisualStudio/Services/Program.cs b/src/GitHub.VisualStudio/Services/Program.cs index ec245ac2d..1df8e8c8c 100644 --- a/src/GitHub.VisualStudio/Services/Program.cs +++ b/src/GitHub.VisualStudio/Services/Program.cs @@ -4,7 +4,7 @@ using System.Reflection; using GitHub.Models; using Octokit; -namespace GitHub.VisualStudio.Services +namespace GitHub.VisualStudio { // Represents the currently executing program. [Export(typeof(IProgram))] diff --git a/src/GitHub.VisualStudio/Services/Services.cs b/src/GitHub.VisualStudio/Services/Services.cs index cf6113e2e..f6eebbf0f 100644 --- a/src/GitHub.VisualStudio/Services/Services.cs +++ b/src/GitHub.VisualStudio/Services/Services.cs @@ -11,7 +11,7 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; using NullGuard; -namespace GitHub.VisualStudio.Services +namespace GitHub.VisualStudio { public static class Services { diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs index acf11b227..d62df91c1 100644 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -14,7 +14,7 @@ using NullGuard; using ReactiveUI; using Splat; -namespace GitHub.VisualStudio.Services +namespace GitHub.VisualStudio { [Export(typeof(IUIProvider))] [Export(typeof(IServiceProvider))] diff --git a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs index 9653dbc9c..59a8221c1 100644 --- a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs +++ b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs @@ -3,7 +3,6 @@ using System.ComponentModel.Composition; using GitHub.Services; using GitHub.UI; using GitHub.VisualStudio.Base; -using GitHub.VisualStudio.Services; using GitHub.VisualStudio.UI; using GitHub.VisualStudio.UI.Views; using Microsoft.TeamFoundation.Controls; diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs index 28f28cb05..167e3d49f 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs @@ -26,6 +26,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls InitializeComponent(); + /* IObservable clearErrorWhenChanged = this.WhenAny( x => x.ViewModel.RepositoryName, x => x.ViewModel.Description, @@ -63,6 +64,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls d(userErrorMessages.RegisterHandler(clearErrorWhenChanged)); }); + */ } public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( From cb9b5fef37bd4d4aafb5bed8566e6d1790d36bcb Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 20:45:50 +0100 Subject: [PATCH 28/35] Add missing metadata attributes so imports work --- src/GitHub.Exports/Exports/ExportMetadata.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GitHub.Exports/Exports/ExportMetadata.cs b/src/GitHub.Exports/Exports/ExportMetadata.cs index 67d69252c..1abb1236e 100644 --- a/src/GitHub.Exports/Exports/ExportMetadata.cs +++ b/src/GitHub.Exports/Exports/ExportMetadata.cs @@ -14,7 +14,7 @@ namespace GitHub.Exports { End = 100 } - + [MetadataAttribute] [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] public sealed class ExportViewModelAttribute : ExportAttribute { @@ -25,6 +25,7 @@ namespace GitHub.Exports { public UIViewType ViewType { get; set; } } + [MetadataAttribute] [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] public sealed class ExportViewAttribute : ExportAttribute { From 39ab80dc70237b6b8820569cac0f10563ed4afa4 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 20:50:01 +0100 Subject: [PATCH 29/35] Shared exports aren't picked up by ExportFactory TwoFactorViewModel is shared so the challenge handler can get the instance, so it needs to be initialized differently, ExportFactory only picks up nonshared exports. --- src/GitHub.App/Controllers/UIController.cs | 40 +++++++++++++------ .../ViewModels/TwoFactorDialogViewModel.cs | 1 - .../Services/ExportFactoryProvider.cs | 1 + src/GitHub.Exports/UI/IUIController.cs | 3 +- .../UI/WindowController.xaml | 2 +- .../UI/WindowController.xaml.cs | 8 +--- 6 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs index 150577e1a..445509719 100644 --- a/src/GitHub.App/Controllers/UIController.cs +++ b/src/GitHub.App/Controllers/UIController.cs @@ -12,6 +12,7 @@ using GitHub.UI; using GitHub.ViewModels; using ReactiveUI; using Stateless; +using System.Windows.Controls; namespace GitHub.Controllers { @@ -21,16 +22,19 @@ namespace GitHub.Controllers enum Trigger { Auth = 1, Create = 2, Clone = 3, Next, Previous } readonly ExportFactoryProvider factory; + readonly IUIProvider uiProvider; CompositeDisposable disposables = new CompositeDisposable(); - Subject transition; + Subject transition; UIControllerFlow currentFlow; StateMachine machine; [ImportingConstructor] - public UIController(IUIProvider uiProvider, IRepositoryHosts hosts, ExportFactoryProvider factory) + public UIController(IUIProvider uiProvider, IRepositoryHosts hosts, + ExportFactoryProvider factory) { this.factory = factory; + this.uiProvider = uiProvider; machine = new StateMachine(UIViewType.None); @@ -59,15 +63,14 @@ namespace GitHub.Controllers var view = dv.Value; view.ViewModel = viewModel; - var twofa = factory.GetViewModel(UIViewType.TwoFactor).Value as ITwoFactorViewModel; + var twofa = uiProvider.GetService(); twofa.WhenAny(x => x.IsShowing, x => x.Value) .Where(x => x) .Subscribe(_ => { Fire(Trigger.Next); }); - - transition.OnNext(view); + LoadView(view); }) .Permit(Trigger.Next, UIViewType.TwoFactor); @@ -76,7 +79,7 @@ namespace GitHub.Controllers .OnEntry(() => { var view = SetupView(UIViewType.TwoFactor); - transition.OnNext(view); + LoadView(view); }) .PermitIf(Trigger.Next, UIViewType.End, () => currentFlow == UIControllerFlow.Authentication) .PermitIf(Trigger.Next, UIViewType.Create, () => currentFlow == UIControllerFlow.Create) @@ -86,7 +89,7 @@ namespace GitHub.Controllers .OnEntry(() => { var view = SetupView(UIViewType.Create); - transition.OnNext(view); + LoadView(view); }) .Permit(Trigger.Next, UIViewType.End); @@ -94,7 +97,7 @@ namespace GitHub.Controllers .OnEntry(() => { var view = SetupView(UIViewType.Clone); - transition.OnNext(view); + LoadView(view); }) .Permit(Trigger.Next, UIViewType.End); @@ -107,11 +110,22 @@ namespace GitHub.Controllers .Permit(Trigger.Next, UIViewType.None); } + private void LoadView(IView view) + { + transition.OnNext(view as UserControl); + } + IView SetupView(UIViewType viewType) { - var dvm = factory.GetViewModel(viewType); - disposables.Add(dvm); - var viewModel = dvm.Value; + IViewModel viewModel; + if (viewType == UIViewType.TwoFactor) + viewModel = uiProvider.GetService(); + else + { + var dvm = factory.GetViewModel(viewType); + disposables.Add(dvm); + viewModel = dvm.Value; + } var dv = factory.GetView(viewType); disposables.Add(dv); @@ -127,10 +141,10 @@ namespace GitHub.Controllers machine.Fire(next); } - public IObservable SelectFlow(UIControllerFlow choice) + public IObservable SelectFlow(UIControllerFlow choice) { currentFlow = choice; - transition = new Subject(); + transition = new Subject(); transition.Subscribe((o) => { }, _ => Fire(Trigger.Next)); return transition; } diff --git a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs index 7fcaa3f00..1140a79e6 100644 --- a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs +++ b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs @@ -13,7 +13,6 @@ using ReactiveUI; namespace GitHub.ViewModels { - [ExportViewModel(ViewType=UIViewType.TwoFactor)] [Export(typeof(ITwoFactorViewModel))] [PartCreationPolicy(CreationPolicy.Shared)] public class TwoFactorDialogViewModel : ReactiveValidatableObject, ITwoFactorViewModel diff --git a/src/GitHub.Exports/Services/ExportFactoryProvider.cs b/src/GitHub.Exports/Services/ExportFactoryProvider.cs index d6abc2908..a8c45c020 100644 --- a/src/GitHub.Exports/Services/ExportFactoryProvider.cs +++ b/src/GitHub.Exports/Services/ExportFactoryProvider.cs @@ -10,6 +10,7 @@ using GitHub.ViewModels; namespace GitHub.Services { [Export] + [PartCreationPolicy(CreationPolicy.Shared)] public class ExportFactoryProvider { [ImportingConstructor] diff --git a/src/GitHub.Exports/UI/IUIController.cs b/src/GitHub.Exports/UI/IUIController.cs index c8568cdf0..97dc1caa1 100644 --- a/src/GitHub.Exports/UI/IUIController.cs +++ b/src/GitHub.Exports/UI/IUIController.cs @@ -1,11 +1,12 @@ using System; +using System.Windows.Controls; namespace GitHub.UI { public interface IUIController { //IObservable Transition { get; } - IObservable SelectFlow(UIControllerFlow choice); + IObservable SelectFlow(UIControllerFlow choice); void Start(); } diff --git a/src/GitHub.VisualStudio/UI/WindowController.xaml b/src/GitHub.VisualStudio/UI/WindowController.xaml index 61b6d42a2..4d84a2128 100644 --- a/src/GitHub.VisualStudio/UI/WindowController.xaml +++ b/src/GitHub.VisualStudio/UI/WindowController.xaml @@ -9,6 +9,6 @@ mc:Ignorable="d" Title="WindowHolder" Height="655" Width="519" HasMinimizeButton="False" HasMaximizeButton="False"> - + diff --git a/src/GitHub.VisualStudio/UI/WindowController.xaml.cs b/src/GitHub.VisualStudio/UI/WindowController.xaml.cs index dc187842c..a213e3935 100644 --- a/src/GitHub.VisualStudio/UI/WindowController.xaml.cs +++ b/src/GitHub.VisualStudio/UI/WindowController.xaml.cs @@ -8,14 +8,10 @@ namespace GitHub.VisualStudio.UI { IDisposable disposable; - public WindowController(IObservable controls) + public WindowController(IObservable controls) { InitializeComponent(); - disposable = controls.Subscribe(c => - { - var control = c as UserControl; - Load(control); - }, + disposable = controls.Subscribe(c => Load(c), Close ); } From b26f524cb356baf74731f1d7fcda2654e328b8e7 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 20:51:05 +0100 Subject: [PATCH 30/35] Fix launching the create dialog --- .../TeamExplorerConnect/PlaceholderGitHubSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs index 59a8221c1..070b85938 100644 --- a/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs +++ b/src/GitHub.VisualStudio/TeamExplorerConnect/PlaceholderGitHubSection.cs @@ -46,6 +46,8 @@ namespace GitHub.VisualStudio.TeamExplorerConnect var x = new WindowController(creation); creation.Subscribe(_ => { }, _ => x.Close()); x.Show(); + + d.Value.Start(); } public void DoClone() From 25dc65afecedaa3c37c4025f717496409acf79dd Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 20:53:21 +0100 Subject: [PATCH 31/35] Fix silly copy paste --- .../UI/Views/Controls/CloneRepoControl.xaml.cs | 2 +- .../UI/Views/Controls/CreateRepoControl.xaml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs index 72bad05a9..8db4dc6fc 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CloneRepoControl.xaml.cs @@ -23,7 +23,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls } public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( - "ViewModel", typeof(ICloneRepoViewModel), typeof(LoginControl), new PropertyMetadata(null)); + "ViewModel", typeof(ICloneRepoViewModel), typeof(CloneRepoControl), new PropertyMetadata(null)); object IViewFor.ViewModel diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs index 167e3d49f..caabf2ab3 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/CreateRepoControl.xaml.cs @@ -68,7 +68,7 @@ namespace GitHub.VisualStudio.UI.Views.Controls } public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( - "ViewModel", typeof(ICreateRepoViewModel), typeof(LoginControl), new PropertyMetadata(null)); + "ViewModel", typeof(ICreateRepoViewModel), typeof(CreateRepoControl), new PropertyMetadata(null)); object IViewFor.ViewModel From 7086cae5d747c0ee677cf22593a28f8454845919 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 21:56:05 +0100 Subject: [PATCH 32/35] Fix ci build --- src/GitHub.Exports/GitHub.Exports.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index b4602bbb9..0e9410e67 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -43,6 +43,7 @@ ..\..\packages\Octokit.0.6.2\lib\net45\Octokit.dll + From e463818b61de052a549d95e0f47c43b1fe738ea3 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 21:58:18 +0100 Subject: [PATCH 33/35] Disable nullguard on GittHub.UI While doing UI work, the designer triggers nullguard everywhere and makes it extremely hard to prototype. Also might not be that useful on a control library, need to evaluate it here. --- src/GitHub.UI/Fakes/NullGuard.cs | 14 ++++++++++++++ src/GitHub.UI/FodyWeavers.xml | 3 +-- src/GitHub.UI/GitHub.UI.csproj | 17 ++--------------- src/GitHub.UI/packages.config | 5 ----- 4 files changed, 17 insertions(+), 22 deletions(-) create mode 100644 src/GitHub.UI/Fakes/NullGuard.cs delete mode 100644 src/GitHub.UI/packages.config diff --git a/src/GitHub.UI/Fakes/NullGuard.cs b/src/GitHub.UI/Fakes/NullGuard.cs new file mode 100644 index 000000000..4294f1500 --- /dev/null +++ b/src/GitHub.UI/Fakes/NullGuard.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NullGuard +{ + [AttributeUsage(AttributeTargets.All)] + internal sealed class AllowNullAttribute : Attribute + { + + } +} diff --git a/src/GitHub.UI/FodyWeavers.xml b/src/GitHub.UI/FodyWeavers.xml index dfd2172c0..6e2fa02e6 100644 --- a/src/GitHub.UI/FodyWeavers.xml +++ b/src/GitHub.UI/FodyWeavers.xml @@ -1,4 +1,3 @@ - + - \ No newline at end of file diff --git a/src/GitHub.UI/GitHub.UI.csproj b/src/GitHub.UI/GitHub.UI.csproj index 641c41c25..3d8ccc0cd 100644 --- a/src/GitHub.UI/GitHub.UI.csproj +++ b/src/GitHub.UI/GitHub.UI.csproj @@ -12,7 +12,7 @@ v4.5 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 55d7ee16 + 4000dd02 4 true ..\..\script\GitHubVS.ruleset @@ -42,10 +42,6 @@ - - ..\..\packages\NullGuard.Fody.1.2.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll - False - @@ -74,6 +70,7 @@ + @@ -160,9 +157,6 @@ MSBuild:Compile - - - Designer @@ -172,13 +166,6 @@ - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - @@ -92,15 +92,15 @@ - + - + @@ -114,7 +114,7 @@ - + @@ -124,7 +124,7 @@ - + @@ -137,7 +137,7 @@ - + @@ -186,5 +186,6 @@ Create repository + From 97ceed081b8110e43d97944d647ecd72491b23bc Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 26 Feb 2015 22:02:35 +0100 Subject: [PATCH 35/35] Make the test app work --- src/DesignTimeStyleHelper/App.xaml.cs | 60 +++++++++++++------ .../DesignTimeStyleHelper.csproj | 22 +++++++ src/DesignTimeStyleHelper/MainWindow.xaml | 26 +++++++- src/DesignTimeStyleHelper/MainWindow.xaml.cs | 35 ++++++++++- .../WindowController.xaml | 12 ++++ .../WindowController.xaml.cs | 35 +++++++++++ src/DesignTimeStyleHelper/packages.config | 5 ++ 7 files changed, 173 insertions(+), 22 deletions(-) create mode 100644 src/DesignTimeStyleHelper/WindowController.xaml create mode 100644 src/DesignTimeStyleHelper/WindowController.xaml.cs diff --git a/src/DesignTimeStyleHelper/App.xaml.cs b/src/DesignTimeStyleHelper/App.xaml.cs index 6d7bf71da..d200f005a 100644 --- a/src/DesignTimeStyleHelper/App.xaml.cs +++ b/src/DesignTimeStyleHelper/App.xaml.cs @@ -12,6 +12,8 @@ using Microsoft.VisualStudio.Shell; using Moq; using GitHub.Services; using GitHub.VisualStudio; +using GitHub.ViewModels; +using GitHub.Exports; namespace DesignTimeStyleHelper { @@ -20,14 +22,42 @@ namespace DesignTimeStyleHelper /// public partial class App : Application { - public static CustomServiceProvider ServiceProvider { get; private set; } + public static Holder ExportHolder { get; set; } + public static CustomServiceProvider ServiceProvider { get { return (CustomServiceProvider) ExportHolder.ServiceProvider; } } + - static App() + public App() { - ServiceProvider = new CustomServiceProvider(); + var s = new CustomServiceProvider(); + ExportHolder = new Holder(s.DefaultCompositionService); } } + + public class Holder + { + [Import] + public SVsServiceProvider ServiceProvider; + + [Import] + SComponentModel sc; + + [Import] + public IBrowser Browser; + + [Import] + public ExportFactoryProvider ExportFactoryProvider; + + [Import] + public IUIProvider UIProvider; + + public Holder(ICompositionService cc) + { + cc.SatisfyImportsOnce(this); + } + } + + [Export(typeof(SVsServiceProvider))] [Export(typeof(SComponentModel))] [PartCreationPolicy(CreationPolicy.Shared)] @@ -41,22 +71,23 @@ namespace DesignTimeStyleHelper public CustomServiceProvider() { catalog = new AggregateCatalog( - new AssemblyCatalog(typeof(GitHub.VisualStudio.Services).Assembly), // GitHub.VisualStudio - new AssemblyCatalog(typeof(GitHub.Api.ApiClient).Assembly), // GitHub.App - new AssemblyCatalog(typeof(GitHub.Api.SimpleApiClient).Assembly), // GitHub.Api - new AssemblyCatalog(typeof(Rothko.Environment).Assembly), // Rothko - new AssemblyCatalog(typeof(GitHub.Services.EnterpriseProbeTask).Assembly) // GitHub.Exports - ); + new AssemblyCatalog(typeof(CustomServiceProvider).Assembly), + new AssemblyCatalog(typeof(GitHub.VisualStudio.Services).Assembly), // GitHub.VisualStudio + new AssemblyCatalog(typeof(GitHub.Api.ApiClient).Assembly), // GitHub.App + new AssemblyCatalog(typeof(GitHub.Api.SimpleApiClient).Assembly), // GitHub.Api + new AssemblyCatalog(typeof(Rothko.Environment).Assembly), // Rothko + new AssemblyCatalog(typeof(GitHub.Services.EnterpriseProbeTask).Assembly) // GitHub.Exports + ); container = new CompositionContainer(catalog, CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); + DefaultCatalog = catalog; DefaultExportProvider = container; - DefaultCompositionService = catalog.CreateCompositionService(); - + DefaultCompositionService = DefaultCatalog.CreateCompositionService(); + var batch = new CompositionBatch(); batch.AddExportedValue(this); batch.AddExportedValue(this); batch.AddExportedValue(DefaultCompositionService); - batch.AddExportedValue(new PlaceholderGitHubSection(this)); container.Compose(batch); } @@ -69,11 +100,6 @@ namespace DesignTimeStyleHelper if (instance != null) return instance; - if (serviceType == typeof(IUIProvider)) - return new UIProvider(this); - if (serviceType == typeof(ExportFactoryProvider)) - return new ExportFactoryProvider(DefaultCompositionService); - instance = Create(serviceType); if (instance != null) diff --git a/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj b/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj index 31c9b08b6..e1ffd1db0 100644 --- a/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj +++ b/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj @@ -42,6 +42,22 @@ + + False + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + + + False + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + + + False + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + + + False + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + @@ -58,6 +74,9 @@ MSBuild:Compile + + WindowController.xaml + MSBuild:Compile Designer @@ -70,6 +89,9 @@ MainWindow.xaml Code + + MSBuild:Compile + diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml b/src/DesignTimeStyleHelper/MainWindow.xaml index 20329eddd..0af1072a0 100644 --- a/src/DesignTimeStyleHelper/MainWindow.xaml +++ b/src/DesignTimeStyleHelper/MainWindow.xaml @@ -7,7 +7,29 @@ mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> - - + + + + + + + + + + + + + + + + Clone + + Create + + + + + + diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml.cs b/src/DesignTimeStyleHelper/MainWindow.xaml.cs index 5d9fa9113..2662f7320 100644 --- a/src/DesignTimeStyleHelper/MainWindow.xaml.cs +++ b/src/DesignTimeStyleHelper/MainWindow.xaml.cs @@ -1,5 +1,10 @@ -using System.Windows; +using System; +using System.Windows; using GitHub.VisualStudio.TeamExplorerConnect; +using GitHub.VisualStudio; +using GitHub.Services; +using GitHub.UI; +using System.ComponentModel.Composition; namespace DesignTimeStyleHelper { @@ -11,10 +16,34 @@ namespace DesignTimeStyleHelper public MainWindow() { InitializeComponent(); + } - var section = (PlaceholderGitHubSection)App.ServiceProvider.GetService(typeof(PlaceholderGitHubSection)); - container.Children.Add(section.SectionContent as UIElement); + private void cloneLink_Click(object sender, RoutedEventArgs e) + { + var ui = App.ServiceProvider.GetExportedValue(); + + var factory = ui.GetService(); + var d = factory.UIControllerFactory.CreateExport(); + var creation = d.Value.SelectFlow(UIControllerFlow.Clone); + var x = new WindowController(creation); + creation.Subscribe(_ => { }, _ => x.Close()); + x.Show(); + d.Value.Start(); } + + private void createLink_Click(object sender, RoutedEventArgs e) + { + var ui = App.ServiceProvider.GetExportedValue(); + + var factory = ui.GetService(); + var d = factory.UIControllerFactory.CreateExport(); + var creation = d.Value.SelectFlow(UIControllerFlow.Create); + var x = new WindowController(creation); + creation.Subscribe(_ => { }, _ => x.Close()); + x.Show(); + d.Value.Start(); + } } + } diff --git a/src/DesignTimeStyleHelper/WindowController.xaml b/src/DesignTimeStyleHelper/WindowController.xaml new file mode 100644 index 000000000..f78d86d20 --- /dev/null +++ b/src/DesignTimeStyleHelper/WindowController.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/DesignTimeStyleHelper/WindowController.xaml.cs b/src/DesignTimeStyleHelper/WindowController.xaml.cs new file mode 100644 index 000000000..81ae1f53a --- /dev/null +++ b/src/DesignTimeStyleHelper/WindowController.xaml.cs @@ -0,0 +1,35 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace DesignTimeStyleHelper +{ + /// + /// Interaction logic for WindowController.xaml + /// + public partial class WindowController : Window + { + IDisposable disposable; + + public WindowController(IObservable controls) + { + InitializeComponent(); + + disposable = controls.Subscribe(c => Load(c), + Close + ); + } + + protected override void OnClosed(EventArgs e) + { + disposable.Dispose(); + base.OnClosed(e); + } + + public void Load(UserControl control) + { + Container.Children.Clear(); + Container.Children.Add(control); + } + } +} diff --git a/src/DesignTimeStyleHelper/packages.config b/src/DesignTimeStyleHelper/packages.config index b04c51152..009b3c7f5 100644 --- a/src/DesignTimeStyleHelper/packages.config +++ b/src/DesignTimeStyleHelper/packages.config @@ -1,4 +1,9 @@  + + + + + \ No newline at end of file