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
+
+ {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 @@
+