diff --git a/GitHubVS.sln b/GitHubVS.sln index 08422b209..d7859d09f 100644 --- a/GitHubVS.sln +++ b/GitHubVS.sln @@ -45,6 +45,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{7B6C5F8D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8A7DA2E7-262B-4581-807A-1C45CE79CDFD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports", "src\GitHub.Exports\GitHub.Exports.csproj", "{9AEA02DB-02B5-409C-B0CA-115D05331A6B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +85,10 @@ Global {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|Any CPU.ActiveCfg = Release|Any CPU {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|Any CPU.Build.0 = Release|Any CPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/GitHub.App/Authentication/AuthenticationResult.cs b/src/GitHub.App/Authentication/AuthenticationResult.cs index f1c6f9072..7a73a121d 100644 --- a/src/GitHub.App/Authentication/AuthenticationResult.cs +++ b/src/GitHub.App/Authentication/AuthenticationResult.cs @@ -1,11 +1,7 @@ -namespace GitHub.Authentication +using GitHub.Exports; + +namespace GitHub.Authentication { - public enum AuthenticationResult - { - CredentialFailure, - VerificationFailure, - Success - } public static class AuthenticationResultExtensions { diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 15f422661..b1fe4c40e 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -163,6 +163,10 @@ + + {9aea02db-02b5-409c-b0ca-115d05331a6b} + GitHub.Exports + {6559e128-8b40-49a5-85a8-05565ed0c7e3} GitHub.Extensions.Reactive diff --git a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs b/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs index 33901496c..e9c16c8ca 100644 --- a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs +++ b/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs @@ -7,6 +7,7 @@ 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 index ac19312f4..a918e8efd 100644 --- a/src/GitHub.App/Models/IRepositoryHost.cs +++ b/src/GitHub.App/Models/IRepositoryHost.cs @@ -3,6 +3,7 @@ using System.Reactive; using GitHub.Authentication; using GitHub.Helpers; using ReactiveUI; +using GitHub.Exports; namespace GitHub.Models { diff --git a/src/GitHub.App/Models/IRepositoryHosts.cs b/src/GitHub.App/Models/IRepositoryHosts.cs index a7c6f1c59..d974886ff 100644 --- a/src/GitHub.App/Models/IRepositoryHosts.cs +++ b/src/GitHub.App/Models/IRepositoryHosts.cs @@ -1,6 +1,7 @@ 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 46faa8f3e..5199adefb 100644 --- a/src/GitHub.App/Models/LocalRepositoriesHost.cs +++ b/src/GitHub.App/Models/LocalRepositoriesHost.cs @@ -3,6 +3,7 @@ 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 f534d615d..f90c5606d 100644 --- a/src/GitHub.App/Models/RepositoryHost.cs +++ b/src/GitHub.App/Models/RepositoryHost.cs @@ -15,6 +15,7 @@ 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 6bf866f12..9197ad8b4 100644 --- a/src/GitHub.App/Models/RepositoryHosts.cs +++ b/src/GitHub.App/Models/RepositoryHosts.cs @@ -7,6 +7,7 @@ using Akavache; using GitHub.Authentication; using GitHub.Extensions.Reactive; using ReactiveUI; +using GitHub.Exports; namespace GitHub.Models { diff --git a/src/GitHub.App/ViewModels/LoginControlViewModel.cs b/src/GitHub.App/ViewModels/LoginControlViewModel.cs index a4727655e..81a5d2f76 100644 --- a/src/GitHub.App/ViewModels/LoginControlViewModel.cs +++ b/src/GitHub.App/ViewModels/LoginControlViewModel.cs @@ -12,32 +12,142 @@ using GitHub.Info; using GitHub.Models; using GitHub.Services; using GitHub.Validation; +using GitHub.Exports; using NullGuard; using ReactiveUI; +using System.Windows.Input; namespace GitHub.ViewModels { [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] - [Export(typeof(LoginControlViewModel))] + [Export(typeof(ILoginDialog))] [PartCreationPolicy(CreationPolicy.NonShared)] - public class LoginControlViewModel : ReactiveValidatableObject, IDisposable + public class LoginControlViewModel : ReactiveValidatableObject, ILoginDialog, IDisposable { - string enterpriseUrl; - readonly ObservableAsPropertyHelper isLoggingInToEnterprise; - readonly ObservableAsPropertyHelper isLoginInProgress; - readonly Subject authenticationResults; - readonly ObservableAsPropertyHelper loginButtonText; - bool loginFailed; - string loginFailedText; - readonly ObservableAsPropertyHelper loginMode; - readonly ObservableAsPropertyHelper loginTarget; - readonly ObservableAsPropertyHelper visualState; - string password; - string usernameOrEmail; - Uri enterpriseHostBaseUrl; readonly Lazy lazyEnterpriseProbe; const string notEnterpriseServerError = "Not an Enterprise server. Please enter an Enterprise URL"; + + public ReactiveCommand LoginCommand { get; private set; } + 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 ReactiveCommand ForgotPasswordCommand { get; private set; } + public ReactiveCommand ShowDotComLoginCommand { get; set; } + public ReactiveCommand ShowEnterpriseLoginCommand { get; set; } + public ReactiveCommand SignupCommand { get; private set; } + public ReactiveCommand LearnMoreCommand { get; private set; } + + string enterpriseUrl; + [ValidateIf("IsLoggingInToEnterprise")] + [Required(ErrorMessage = "Please enter an Enterprise URL")] + [AllowNull] + public string EnterpriseUrl + { + get { return enterpriseUrl; } + set { this.RaiseAndSetIfChanged(ref enterpriseUrl, value); } + } + + readonly ObservableAsPropertyHelper isLoggingInToEnterprise; + public bool IsLoggingInToEnterprise + { + get { return isLoggingInToEnterprise.Value; } + } + + readonly ObservableAsPropertyHelper isLoginInProgress; + public bool IsLoginInProgress + { + get { return isLoginInProgress.Value; } + } + + readonly ObservableAsPropertyHelper loginButtonText; + public string LoginButtonText + { + get { return loginButtonText.Value; } + } + + bool loginFailed; + public bool LoginFailed + { + get { return loginFailed; } + set { this.RaiseAndSetIfChanged(ref loginFailed, value); } + } + + string loginFailedText; + public string LoginFailedText + { + get { return loginFailedText; } + private set { this.RaiseAndSetIfChanged(ref loginFailedText, value); } + } + + readonly ObservableAsPropertyHelper loginMode; + public LoginMode LoginMode + { + get { return loginMode.Value; } + } + + public string LoginPrefix { get; set; } + + readonly ObservableAsPropertyHelper loginTarget; + public LoginTarget LoginTarget + { + get { return loginTarget.Value; } + } + + readonly ObservableAsPropertyHelper visualState; + public VisualState VisualState + { + get { return visualState.Value; } + } + + string password; + [AllowNull] + public string Password + { + [return: AllowNull] + get + { return password; } + set { this.RaiseAndSetIfChanged(ref password, value); } + } + readonly ObservableAsPropertyHelper forgotPasswordUrl; + public Uri ForgotPasswordUrl + { + get { return forgotPasswordUrl.Value; } + } + + Uri enterpriseHostBaseUrl; + Uri EnterpriseHostBaseUrl + { + get { return enterpriseHostBaseUrl; } + set { this.RaiseAndSetIfChanged(ref enterpriseHostBaseUrl, value); } + } + + // HACKETY HACK! + // Because #Bind() doesn't yet set up validation, we must use XAML bindings for username and password. + // But, because our SecurePasswordBox manipulates base.Text, it doesn't work with XAML binding. + // (It binds the password mask, not the password.) + // So, this property is a "black hole" to point the XAML binding to so validation works. + // And the actual password is bound to #Password via #Bind(). Ugly? Yep. + [Required(ErrorMessage = "Please enter your password")] + public string PasswordNoOp { get; set; } + + protected IRepositoryHosts RepositoryHosts { get; private set; } + + string usernameOrEmail; + [Required(ErrorMessage = "Please enter your username or email address")] + [AllowNull] + public string UsernameOrEmail + { + [return: AllowNull] + get + { return usernameOrEmail; } + set { this.RaiseAndSetIfChanged(ref usernameOrEmail, value); } + } + + readonly Subject authenticationResults; + public IObservable AuthenticationResults { get { return authenticationResults; } } [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "It's Rx baby")] [ImportingConstructor] @@ -141,113 +251,6 @@ namespace GitHub.ViewModels ForgotPasswordCommand.Subscribe(_ => browser.OpenUrl(ForgotPasswordUrl)); } - public ReactiveCommand CancelCommand { get; private set; } - - [ValidateIf("IsLoggingInToEnterprise")] - [Required(ErrorMessage = "Please enter an Enterprise URL")] - [AllowNull] - public string EnterpriseUrl - { - get { return enterpriseUrl; } - set { this.RaiseAndSetIfChanged(ref enterpriseUrl, value); } - } - - public ReactiveCommand ForgotPasswordCommand { get; private set; } - - public bool IsLoggingInToEnterprise - { - get { return isLoggingInToEnterprise.Value; } - } - - public bool IsLoginInProgress - { - get { return isLoginInProgress.Value; } - } - - public IObservable AuthenticationResults { get { return authenticationResults; } } - - public string LoginButtonText - { - get { return loginButtonText.Value; } - } - - public ReactiveCommand LoginCommand { get; private set; } - - public bool LoginFailed - { - get { return loginFailed; } - set { this.RaiseAndSetIfChanged(ref loginFailed, value); } - } - - public string LoginFailedText - { - get { return loginFailedText; } - private set { this.RaiseAndSetIfChanged(ref loginFailedText, value); } - } - - public LoginMode LoginMode - { - get { return loginMode.Value; } - } - - public string LoginPrefix { get; set; } - - public LoginTarget LoginTarget - { - get { return loginTarget.Value; } - } - - public VisualState VisualState - { - get { return visualState.Value; } - } - - [AllowNull] - public string Password - { - [return: AllowNull] - get { return password; } - set { this.RaiseAndSetIfChanged(ref password, value); } - } - - public Uri ForgotPasswordUrl - { - get { return forgotPasswordUrl.Value; } - } - - Uri EnterpriseHostBaseUrl - { - get { return enterpriseHostBaseUrl; } - set { this.RaiseAndSetIfChanged(ref enterpriseHostBaseUrl, value); } - } - - // HACKETY HACK! - // Because #Bind() doesn't yet set up validation, we must use XAML bindings for username and password. - // But, because our SecurePasswordBox manipulates base.Text, it doesn't work with XAML binding. - // (It binds the password mask, not the password.) - // So, this property is a "black hole" to point the XAML binding to so validation works. - // And the actual password is bound to #Password via #Bind(). Ugly? Yep. - [Required(ErrorMessage = "Please enter your password")] - public string PasswordNoOp { get; set; } - - protected IRepositoryHosts RepositoryHosts { get; private set; } - - public ReactiveCommand ShowDotComLoginCommand { get; set; } - - public ReactiveCommand ShowEnterpriseLoginCommand { get; set; } - - public ReactiveCommand SignupCommand { get; private set; } - - public ReactiveCommand LearnMoreCommand { get; private set; } - - [Required(ErrorMessage = "Please enter your username or email address")] - [AllowNull] - public string UsernameOrEmail - { - [return: AllowNull] - get { return usernameOrEmail; } - set { this.RaiseAndSetIfChanged(ref usernameOrEmail, value); } - } public void Dispose() { diff --git a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs index 37e461c6c..996991bcd 100644 --- a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs +++ b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs @@ -8,12 +8,13 @@ using GitHub.Validation; using NullGuard; using Octokit; using ReactiveUI; +using GitHub.Exports; namespace GitHub.ViewModels { - [Export(typeof(TwoFactorDialogViewModel))] + [Export(typeof(ITwoFactorDialog))] [PartCreationPolicy(CreationPolicy.Shared)] - public class TwoFactorDialogViewModel : ReactiveValidatableObject + public class TwoFactorDialogViewModel : ReactiveValidatableObject, ITwoFactorDialog { bool isAuthenticationCodeSent; string authenticationCode; diff --git a/src/GitHub.Exports/AuthenticationResult.cs b/src/GitHub.Exports/AuthenticationResult.cs new file mode 100644 index 000000000..9457cb679 --- /dev/null +++ b/src/GitHub.Exports/AuthenticationResult.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.Exports +{ + public enum AuthenticationResult + { + CredentialFailure, + VerificationFailure, + Success + } +} diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj new file mode 100644 index 000000000..de3138e21 --- /dev/null +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -0,0 +1,57 @@ + + + + + Debug + AnyCPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B} + Library + Properties + GitHub.Exports + GitHub.Exports + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.Exports/ICloneDialog.cs b/src/GitHub.Exports/ICloneDialog.cs new file mode 100644 index 000000000..b7b786d18 --- /dev/null +++ b/src/GitHub.Exports/ICloneDialog.cs @@ -0,0 +1,12 @@ +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/ILoginDialog.cs b/src/GitHub.Exports/ILoginDialog.cs new file mode 100644 index 000000000..796dae55e --- /dev/null +++ b/src/GitHub.Exports/ILoginDialog.cs @@ -0,0 +1,15 @@ +using System; +using System.Windows.Input; + +namespace GitHub.Exports +{ + public interface ILoginDialog + { + string UsernameOrEmail { get; set; } + string Password { get; set; } + ICommand LoginCmd { get; } + ICommand CancelCmd { get; } + IObservable CancelEvt { get; } + IObservable AuthenticationResults { get; } + } +} diff --git a/src/GitHub.Exports/ITwoFactorDialog.cs b/src/GitHub.Exports/ITwoFactorDialog.cs new file mode 100644 index 000000000..ed486d39f --- /dev/null +++ b/src/GitHub.Exports/ITwoFactorDialog.cs @@ -0,0 +1,12 @@ +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/Properties/AssemblyInfo.cs b/src/GitHub.Exports/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..408e5a4da --- /dev/null +++ b/src/GitHub.Exports/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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("GitHub.Exports")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.Exports")] +[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)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9aea02db-02b5-409c-b0ca-115d05331a6b")] + +// 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/GitHub.VisualStudio/Base/PackageBase.cs b/src/GitHub.VisualStudio/Base/PackageBase.cs new file mode 100644 index 000000000..4e7118bd4 --- /dev/null +++ b/src/GitHub.VisualStudio/Base/PackageBase.cs @@ -0,0 +1,75 @@ +using GitHub.VisualStudio.Services; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.VisualStudio +{ + public abstract class PackageBase : Package + { + IServiceProvider serviceProvider; + protected IServiceProvider ServiceProvider + { + get { return serviceProvider; } + set + { + serviceProvider = value; + } + } + + public PackageBase() + { + ServiceProvider = this; + } + + public PackageBase(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + protected void AddTopLevelMenuItem( + uint packageCommandId, + EventHandler eventHandler) + { + var menuCommandService = GetService(typeof(IMenuCommandService)) as IMenuCommandService; + var menuCommandId = new CommandID(GuidList.guidGitHubCmdSet, (int)packageCommandId); + var menuItem = new MenuCommand(eventHandler, menuCommandId); + menuCommandService.AddCommand(menuItem); + } + + public T GetService() + { + Debug.Assert(this.serviceProvider != null, "GetService called before service provider is set"); + if (serviceProvider == null) + return default(T); + return (T)serviceProvider.GetService(typeof(T)); + } + + public Ret GetService() where Ret : class + { + return GetService() as Ret; + } + + public T GetExportedValue() + { + var componentModel = (IComponentModel)GetService(); + if (componentModel == null) + return default(T); + var exportProvider = componentModel.DefaultExportProvider; + return exportProvider.GetExportedValue(); + } + + protected void EnsureUIProvider() + { + var ui = GetExportedValue(); + ui.EnsureProvider(GetService().DefaultExportProvider); + } + + } +} diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs new file mode 100644 index 000000000..12136ea40 --- /dev/null +++ b/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs @@ -0,0 +1,95 @@ +using Microsoft.TeamFoundation.Client; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.VisualStudio.Base +{ + public abstract class TeamExplorerBase : IDisposable, INotifyPropertyChanged + { + bool subscribed = false; + + IServiceProvider serviceProvider; + protected IServiceProvider ServiceProvider + { + get { return serviceProvider; } + set + { + if (serviceProvider != null) + UnsubscribeContextChanges(); + serviceProvider = value; + if (serviceProvider != null) + SubscribeContextChanges(); + } + } + + protected ITeamFoundationContext CurrentContext + { + get + { + var manager = GetService(); + if (manager != null) + return manager.CurrentContext; + return null; + } + } + + void SubscribeContextChanges() + { + Debug.Assert(serviceProvider != null, "ServiceProvider must be set before subscribing to context changes"); + if (serviceProvider == null || subscribed) + return; + + var manager = GetService(); + if (manager != null) + { + manager.ContextChanged += ContextChanged; + subscribed = true; + } + } + + void UnsubscribeContextChanges() + { + var manager = GetService(); + if (manager != null) + { + manager.ContextChanged -= ContextChanged; + subscribed = false; + } + } + + public event PropertyChangedEventHandler PropertyChanged; + protected void RaisePropertyChanged(string propertyName) + { + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + + public virtual void Dispose() + { + UnsubscribeContextChanges(); + } + + public T GetService() + { + Debug.Assert(this.serviceProvider != null, "GetService called before service provider is set"); + if (serviceProvider == null) + return default(T); + return (T)serviceProvider.GetService(typeof(T)); + } + + public Ret GetService() where Ret : class + { + return GetService() as Ret; + } + + + protected virtual void ContextChanged(object sender, ContextChangedEventArgs e) + { + } + } +} diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index c83939362..cf565b1f4 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -33,9 +33,10 @@ DEBUG;TRACE prompt 4 - true + false ..\..\script\GitHubVS.ruleset - true + false + false pdbonly @@ -52,6 +53,30 @@ ..\..\packages\EditorUtils2013.1.4.1.1\lib\net40\EditorUtils2013.dll + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Client.dll + + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Common.dll + + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Controls.dll + + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Client.dll + + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Common.dll + + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Controls.dll + @@ -161,7 +186,10 @@ Properties\SolutionInfo.cs + + + True True @@ -171,6 +199,7 @@ + LoginControl.xaml @@ -245,6 +274,12 @@ {1a1da411-8d1f-4578-80a6-04576bea2dc5} GitHub.App + + {9aea02db-02b5-409c-b0ca-115d05331a6b} + GitHub.Exports + BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + {6559e128-8b40-49a5-85a8-05565ed0c7e3} GitHub.Extensions.Reactive diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 7f6b4611c..dd4f9d1a2 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -20,6 +20,8 @@ using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; using ReactiveUI; using Splat; +using GitHub.Exports; +using GitHub.VisualStudio.Services; namespace GitHub.VisualStudio { @@ -43,33 +45,15 @@ namespace GitHub.VisualStudio [ProvideMenuResource("Menus.ctmenu", 1)] [Guid(GuidList.guidGitHubPkgString)] [ProvideBindingPath] - public class GitHubPackage : Package + public class GitHubPackage : PackageBase { - readonly IServiceProvider serviceProvider; - - // Set of assemblies we need to load early. - static readonly IEnumerable earlyLoadAssemblies = new[] { - "Rothko.dll", - "GitHub.App.dll", - "GitHub.UI.Reactive.dll", - "GitHub.UI.dll" - }; - - /// - /// Default constructor of the package. - /// Inside this method you can place any initialization code that does not require - /// any Visual Studio service because at this point the package object is created but - /// not sited yet inside Visual Studio environment. The place to do all the other - /// initialization is the Initialize method. - /// public GitHubPackage() { - serviceProvider = this; } public GitHubPackage(IServiceProvider serviceProvider) + : base(serviceProvider) { - this.serviceProvider = serviceProvider; } /// @@ -82,42 +66,16 @@ namespace GitHub.VisualStudio Debug.WriteLine("Entering Initialize() of: {0}", ToString()); base.Initialize(); - ModeDetector.OverrideModeDetector(new AppModeDetector()); - RxApp.MainThreadScheduler = new DispatcherScheduler(Application.Current.Dispatcher); - - var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - Debug.Assert(dir != null, "The Assembly location can't be null"); - foreach (var v in earlyLoadAssemblies) - { - Assembly.LoadFile(Path.Combine(dir, v)); - } - // Set the Export Provider - var mefServiceProvider = GetExportedValue() as MefServiceProvider; - Debug.Assert(mefServiceProvider != null, "Service Provider can't be imported"); - var componentModel = (IComponentModel)(serviceProvider.GetService(typeof(SComponentModel))); - mefServiceProvider.ExportProvider = componentModel.DefaultExportProvider; + // Add our command handlers for menu (commands must exist in the .vsct file) - var mcs = serviceProvider.GetService(typeof(IMenuCommandService)) as IMenuCommandService; - if (mcs != null) - { - // Login Command Menu Item - AddTopLevelMenuItem(mcs, PkgCmdIDList.loginCommand, OnLoginCommand); + // Login Command Menu Item - // Create Issue Command Menu Item - AddTopLevelMenuItem(mcs, PkgCmdIDList.createIssueCommand, OnCreateIssueCommand); - } - } + AddTopLevelMenuItem(PkgCmdIDList.loginCommand, OnLoginCommand); - static void AddTopLevelMenuItem( - IMenuCommandService menuCommandService, - uint packageCommandId, - EventHandler eventHandler) - { - var menuCommandId = new CommandID(GuidList.guidGitHubCmdSet, (int)packageCommandId); - var menuItem = new MenuCommand(eventHandler, menuCommandId); - menuCommandService.AddCommand(menuItem); + // Create Issue Command Menu Item + AddTopLevelMenuItem(PkgCmdIDList.createIssueCommand, OnCreateIssueCommand); } /// @@ -125,7 +83,7 @@ namespace GitHub.VisualStudio /// See the Initialize method to see how the menu item is associated to this function using /// the OleMenuCommandService service and the MenuCommand class. /// - static void OnCreateIssueCommand(object sender, EventArgs e) + void OnCreateIssueCommand(object sender, EventArgs e) { var createIssueDialog = new CreateIssueDialog(); createIssueDialog.ShowModal(); @@ -133,41 +91,38 @@ namespace GitHub.VisualStudio void OnLoginCommand(object sender, EventArgs e) { - var loginControlViewModel = GetExportedValue(); + EnsureUIProvider(); + /* + var mefServiceProvider = GetExportedValue() as MefServiceProvider; + Debug.Assert(mefServiceProvider != null, "Service Provider can't be imported"); + var componentModel = GetService() as IComponentModel; + if (componentModel != null) + mefServiceProvider.ExportProvider = componentModel.DefaultExportProvider; + */ + + //var r = GetExportedValue(); + var factory = GetExportedValue().LoginViewModelFactory; + var disposable = factory.CreateExport(); + var loginControlViewModel = disposable.Value; var loginIssueDialog = new LoginCommandDialog(loginControlViewModel); + loginIssueDialog.Closed += (o,ev) => disposable.Dispose(); + loginControlViewModel.CancelEvt.Subscribe(x => loginIssueDialog.Close()); + loginIssueDialog.Show(); + loginControlViewModel.AuthenticationResults.Subscribe(result => { if (result == AuthenticationResult.Success) - loginIssueDialog.Hide(); + { + loginIssueDialog.Close(); + } }); + } - T GetExportedValue() - { - var componentModel = (IComponentModel)(serviceProvider.GetService(typeof(SComponentModel))); - var exportProvider = componentModel.DefaultExportProvider; - return exportProvider.GetExportedValue(); - } } + - [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; - - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - "Could not locate any instances of contract {0}.", contract)); - } - } + } diff --git a/src/GitHub.VisualStudio/Helpers/ExportFactoryProvider.cs b/src/GitHub.VisualStudio/Helpers/ExportFactoryProvider.cs new file mode 100644 index 000000000..999f21a02 --- /dev/null +++ b/src/GitHub.VisualStudio/Helpers/ExportFactoryProvider.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GitHub.Exports +{ + [Export] + public class ExportFactoryProvider + { + + [ImportingConstructor] + public ExportFactoryProvider(ICompositionService cc) + { + cc.SatisfyImportsOnce(this); + } + + [Import(AllowRecomposition =true)] + public ExportFactory LoginViewModelFactory { get; set; } + /* + [Import(AllowRecomposition = true)] + public ExportFactory TwoFactorViewModelFactory { get; set; } + */ + } +} diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs new file mode 100644 index 000000000..0eae7837b --- /dev/null +++ b/src/GitHub.VisualStudio/Services/UIProvider.cs @@ -0,0 +1,55 @@ +using GitHub.Infrastructure; +using ReactiveUI; +using Splat; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.Globalization; +using System.Linq; +using System.Reactive.Concurrency; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace GitHub.VisualStudio.Services +{ + [Export] + [PartCreationPolicy(CreationPolicy.Shared)] + public class UIProvider + { + [Import(typeof(IServiceProvider))] + public MefServiceProvider ServiceProvider { get; set; } + + public UIProvider() + { + 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; + + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Could not locate any instances of contract {0}.", contract)); + } + } +} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs index c685c43c6..c6acca248 100644 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs @@ -2,45 +2,46 @@ using GitHub.ViewModels; using NullGuard; using ReactiveUI; +using GitHub.Exports; namespace GitHub.VisualStudio.UI.Views.Controls { /// /// Interaction logic for LoginControl.xaml /// - public partial class LoginControl : IViewFor + public partial class LoginControl : IViewFor { public LoginControl() { InitializeComponent(); - DataContextChanged += (s, e) => ViewModel = (LoginControlViewModel)e.NewValue; + DataContextChanged += (s, e) => ViewModel = (ILoginDialog)e.NewValue; this.WhenActivated(d => { d(this.Bind(ViewModel, vm => vm.UsernameOrEmail, v => v.usernameOrEmailTextBox.Text)); d(this.Bind(ViewModel, vm => vm.Password, v => v.passwordTextBox.Text)); - d(this.BindCommand(ViewModel, vm => vm.LoginCommand, v => v.loginButton)); - d(this.BindCommand(ViewModel, vm => vm.CancelCommand, v => v.cancelButton)); + d(this.BindCommand(ViewModel, vm => vm.LoginCmd, v => v.loginButton)); + d(this.BindCommand(ViewModel, vm => vm.CancelCmd, v => v.cancelButton)); }); VisualStateManager.GoToState(this, "DotCom", true); } public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( - "ViewModel", typeof(LoginControlViewModel), typeof(LoginControl), new PropertyMetadata(null)); + "ViewModel", typeof(ILoginDialog), typeof(LoginControl), new PropertyMetadata(null)); object IViewFor.ViewModel { get { return ViewModel; } - set { ViewModel = (LoginControlViewModel)value; } + set { ViewModel = (ILoginDialog)value; } } - public LoginControlViewModel ViewModel + public ILoginDialog ViewModel { [return: AllowNull] - get { return (LoginControlViewModel)GetValue(ViewModelProperty); } + get { return (ILoginDialog)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } } diff --git a/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs b/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs index a76bd4937..8ea4596ea 100644 --- a/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs +++ b/src/GitHub.VisualStudio/UI/Views/LoginCommandDialog.xaml.cs @@ -1,4 +1,4 @@ -using GitHub.ViewModels; +using GitHub.Exports; using Microsoft.VisualStudio.PlatformUI; namespace GitHub.VisualStudio.UI.Views @@ -8,7 +8,7 @@ namespace GitHub.VisualStudio.UI.Views /// public partial class LoginCommandDialog : DialogWindow { - public LoginCommandDialog(LoginControlViewModel loginControlViewModel) + public LoginCommandDialog(ILoginDialog loginControlViewModel) { InitializeComponent(); diff --git a/src/GitHub.VisualStudio/source.extension.vsixmanifest b/src/GitHub.VisualStudio/source.extension.vsixmanifest index 537c70ef5..360bc22e2 100644 --- a/src/GitHub.VisualStudio/source.extension.vsixmanifest +++ b/src/GitHub.VisualStudio/source.extension.vsixmanifest @@ -20,5 +20,6 @@ +