зеркало из https://github.com/github/VisualStudio.git
Add clone and create dialogs...
... and everything that the UI depends on (translations, styles, controls, user errors, etc, etc)
This commit is contained in:
Родитель
7d864f16df
Коммит
fd0868a7c3
12
GitHubVS.sln
12
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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GitHub.Exports;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.Services;
|
||||
using Octokit;
|
||||
using System;
|
||||
|
|
|
@ -12,10 +12,10 @@ namespace GitHub.Authentication
|
|||
public class TwoFactorChallengeHandler : ITwoFactorChallengeHandler
|
||||
{
|
||||
//readonly IServiceProvider serviceProvider;
|
||||
readonly Lazy<ITwoFactorDialog> lazyTwoFactorDialog;
|
||||
readonly Lazy<ITwoFactorViewModel> lazyTwoFactorDialog;
|
||||
|
||||
[ImportingConstructor]
|
||||
public TwoFactorChallengeHandler(Lazy<ITwoFactorDialog> twoFactorDialog)
|
||||
public TwoFactorChallengeHandler(Lazy<ITwoFactorViewModel> twoFactorDialog)
|
||||
{
|
||||
//this.serviceProvider = serviceProvider;
|
||||
this.lazyTwoFactorDialog = twoFactorDialog;
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace GitHub.VisualStudio.UI
|
|||
machine.Configure(UIState.Auth)
|
||||
.OnEntry(() =>
|
||||
{
|
||||
var twofa = uiProvider.GetService<ITwoFactorDialog>();
|
||||
var twofa = uiProvider.GetService<ITwoFactorViewModel>();
|
||||
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<IViewFor<ILoginDialog>>();
|
||||
var view = uiProvider.GetService<IViewFor<ILoginViewModel>>();
|
||||
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<ITwoFactorDialog>();
|
||||
var view = uiProvider.GetService<IViewFor<ITwoFactorDialog>>();
|
||||
var d = uiProvider.GetService<ITwoFactorViewModel>();
|
||||
var view = uiProvider.GetService<IViewFor<ITwoFactorViewModel>>();
|
||||
view.ViewModel = d;
|
||||
transition.OnNext(view);
|
||||
})
|
||||
|
@ -93,9 +93,9 @@ namespace GitHub.VisualStudio.UI
|
|||
machine.Configure(UIState.Clone)
|
||||
.OnEntry(() =>
|
||||
{
|
||||
var d = uiProvider.GetService<ICloneRepoDialog>();
|
||||
var d = uiProvider.GetService<ICloneRepoViewModel>();
|
||||
|
||||
var view = uiProvider.GetService<IViewFor<ICloneRepoDialog>>();
|
||||
var view = uiProvider.GetService<IViewFor<ICloneRepoViewModel>>();
|
||||
view.ViewModel = d;
|
||||
transition.OnNext(view);
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers>
|
||||
<NullGuard />
|
||||
<NullGuard ExcludeRegex="^GitHub.SampleData.*$" />
|
||||
</Weavers>
|
|
@ -11,6 +11,7 @@
|
|||
<AssemblyName>GitHub.App</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<NuGetPackageImportStamp>5547d2f4</NuGetPackageImportStamp>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
|
@ -41,7 +42,9 @@
|
|||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\packages\akavache.sqlite3.4.1.0\lib\Portable-Net45+Win8+WP8+Wpa81\Akavache.Sqlite3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
|
@ -74,6 +77,9 @@
|
|||
<Reference Include="SQLitePCL.raw">
|
||||
<HintPath>..\..\packages\SQLitePCL.raw_basic.0.7.1\lib\net45\SQLitePCL.raw.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Stateless">
|
||||
<HintPath>..\..\packages\Stateless.2.5.11.0\lib\portable-net40+sl50+win+wp80\Stateless.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
|
@ -107,14 +113,11 @@
|
|||
<Link>Properties\SolutionInfo.cs</Link>
|
||||
</Compile>
|
||||
<None Include="AkavacheSqliteLinkerOverride.cs" />
|
||||
<Compile Include="Authentication\IAccount.cs" />
|
||||
<Compile Include="Authentication\ITwoFactorChallengeHandler.cs" />
|
||||
<Compile Include="Authentication\TwoFactorChallengeHandler.cs" />
|
||||
<Compile Include="Authentication\TwoFactorRequiredUserError.cs" />
|
||||
<Compile Include="Caches\HostCache.cs" />
|
||||
<Compile Include="Caches\HostCacheFactory.cs" />
|
||||
<Compile Include="Caches\IBlobCacheFactory.cs" />
|
||||
<Compile Include="Caches\IHostCache.cs" />
|
||||
<Compile Include="Caches\IHostCacheFactory.cs" />
|
||||
<Compile Include="Caches\ILoginCache.cs" />
|
||||
<Compile Include="Caches\ISharedCache.cs" />
|
||||
|
@ -131,33 +134,48 @@
|
|||
<Compile Include="Models\LocalRepositoriesHost.cs" />
|
||||
<Compile Include="Models\RepositoryHost.cs" />
|
||||
<Compile Include="Models\RepositoryHosts.cs" />
|
||||
<Compile Include="Models\IRepositoryHost.cs" />
|
||||
<Compile Include="Models\IRepositoryHosts.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SampleData\SampleViewModels.cs" />
|
||||
<Compile Include="Services\AccountFactory.cs" />
|
||||
<Compile Include="Services\ApiClient.cs" />
|
||||
<Compile Include="Services\ApiClientFactory.cs" />
|
||||
<None Include="Services\Browser.cs" />
|
||||
<Compile Include="Services\EnterpriseProbe.cs" />
|
||||
<Compile Include="Services\ErrorMap.cs" />
|
||||
<Compile Include="Services\ErrorMessage.cs" />
|
||||
<Compile Include="Services\ErrorMessageTranslator.cs" />
|
||||
<Compile Include="Services\GitHubCredentialStore.cs" />
|
||||
<Compile Include="Services\IAccountFactory.cs" />
|
||||
<Compile Include="Services\IApiClient.cs" />
|
||||
<Compile Include="Services\IApiClientFactory.cs" />
|
||||
<Compile Include="Services\IEnterpriseProbe.cs" />
|
||||
<Compile Include="Services\IRepositoryHostFactory.cs" />
|
||||
<Compile Include="Services\RepositoryHostFactory.cs" />
|
||||
<Compile Include="Services\StandardUserErrors.cs" />
|
||||
<Compile Include="Controllers\UIController.cs" />
|
||||
<Compile Include="Services\Translation.cs" />
|
||||
<Compile Include="UserErrors\PublishRepositoryUserError.cs" />
|
||||
<Compile Include="UserErrors\PrivateRepositoryOnFreeAccountUserError.cs" />
|
||||
<Compile Include="UserErrors\PrivateRepositoryQuotaExceededUserError.cs" />
|
||||
<Compile Include="ViewModels\CreateRepoViewModel.cs" />
|
||||
<Compile Include="ViewModels\CloneRepoViewModel.cs" />
|
||||
<Compile Include="ViewModels\LoginControlViewModel.cs" />
|
||||
<Compile Include="ViewModels\TwoFactorDialogViewModel.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="FodyWeavers.xml" />
|
||||
<Resource Include="Images\default_org_avatar.png" />
|
||||
<Resource Include="Images\default_user_avatar.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj">
|
||||
<Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project>
|
||||
<Name>GitHub.Exports.Reactive</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj">
|
||||
<Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project>
|
||||
<Name>GitHub.Exports</Name>
|
||||
|
@ -179,7 +197,6 @@
|
|||
<Name>Rothko</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.7 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.9 KiB |
|
@ -0,0 +1,194 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reactive;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GitHub.Helpers;
|
||||
using GitHub.Models;
|
||||
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
|
||||
{
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class CreateRepoViewModelDesigner : ReactiveObject, ICreateRepoViewModel
|
||||
{
|
||||
public CreateRepoViewModelDesigner()
|
||||
{
|
||||
RepositoryName = "Hello-World";
|
||||
Description = "A description";
|
||||
KeepPrivate = true;
|
||||
Accounts = new ReactiveList<IAccount> { 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<string> RepositoryNameValidator { get; private set; }
|
||||
public string Description { get; set; }
|
||||
public ReactivePropertyValidator<IAccount> 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<Unit> CreateRepository { get; private set; }
|
||||
public bool IsPublishing { get; private set; }
|
||||
public ReactiveCommand<Object> UpgradeAccountPlan { get; private set; }
|
||||
public ReactiveCommand<Object> Reset { get; private set; }
|
||||
public ReactiveList<IAccount> 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<BitmapSource> GetAvatar(Account apiAccount)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IObservable<Unit> InvalidateAvatar(Account apiAccount)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IObservable<BitmapSource> 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<string>();
|
||||
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<string> AdditionalClones { get; private set; }
|
||||
|
||||
public bool IsPrivate { get; set; }
|
||||
}
|
||||
}
|
|
@ -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<Translation> translations, IEnumerable<IRecoveryCommand> recoveryCommands)
|
||||
{
|
||||
this.defaultMessage = defaultMessage;
|
||||
this.translations = translations;
|
||||
RecoveryCommands = recoveryCommands;
|
||||
}
|
||||
|
||||
readonly ErrorMessage defaultMessage;
|
||||
readonly IEnumerable<Translation> translations;
|
||||
public IEnumerable<IRecoveryCommand> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<bool> headingHasPlaceholders;
|
||||
readonly Lazy<bool> messageHasPlaceholders;
|
||||
|
||||
public ErrorMessage(string heading, string message)
|
||||
{
|
||||
Heading = heading;
|
||||
headingHasPlaceholders = new Lazy<bool>(() => placeholderRegex.IsMatch(heading));
|
||||
Message = message;
|
||||
messageHasPlaceholders = new Lazy<bool>(() => 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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ErrorType, ErrorMap> userErrorMappings;
|
||||
|
||||
public ErrorMessageTranslator(IDictionary<ErrorType, ErrorMap> 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<IRecoveryCommand> recoveryCommands)
|
||||
{
|
||||
ErrorMessage = errorMessage;
|
||||
CauseOrResolution = causeOrResolution;
|
||||
RecoveryCommands = recoveryCommands;
|
||||
}
|
||||
|
||||
public string ErrorMessage { get; private set; }
|
||||
public string CauseOrResolution { get; private set; }
|
||||
public IEnumerable<IRecoveryCommand> RecoveryCommands { get; private set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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<RecoveryOptionResult> ShowUserErrorMessage(
|
||||
this Exception ex, ErrorType errorType, params object[] messageArgs)
|
||||
internal static readonly Lazy<ErrorMessageTranslator> Translator = new Lazy<ErrorMessageTranslator>(() => new ErrorMessageTranslator(new Dictionary<ErrorType, ErrorMap>
|
||||
{
|
||||
// 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<AuthorizationException>("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<LoginAttemptsExceededException>("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<DirectoryNotFoundException>("Directory not found", "The directory does not exist"),
|
||||
new Translation<UnauthorizedAccessException>("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<HttpRequestException>("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<RecoveryOptionResult> ShowUserErrorMessage(this Exception exception, ErrorType errorType, params object[] messageArgs)
|
||||
{
|
||||
return exception.DisplayErrorMessage(errorType, messageArgs, null);
|
||||
}
|
||||
|
||||
public static IObservable<RecoveryOptionResult> ShowUserErrorMessage(ErrorType errorType, params object[] messageArgs)
|
||||
{
|
||||
return DisplayErrorMessage(null, errorType, messageArgs, null);
|
||||
}
|
||||
|
||||
public static IObservable<RecoveryOptionResult> ShowUserThatRepoAlreadyExists(string repositoryName, string fullPath)
|
||||
{
|
||||
return DisplayErrorMessage(ErrorType.RepoExistsOnDisk, new object[] { repositoryName, fullPath }, new[] { OpenPathInExplorer(fullPath), Cancel });
|
||||
}
|
||||
|
||||
public static IObservable<RecoveryOptionResult> ShowCloneError(this Exception exception,
|
||||
ErrorType errorType,
|
||||
string displayName,
|
||||
string repositoryLocalWorkingDirectory)
|
||||
{
|
||||
return exception.DisplayErrorMessage(errorType, new object[] { displayName }, null);
|
||||
}
|
||||
|
||||
public static IObservable<RecoveryOptionResult> 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<RecoveryOptionResult> DisplayErrorMessage(ErrorType errorType, object[] messageArgs, IEnumerable<IRecoveryCommand> recoveryOptions)
|
||||
{
|
||||
return DisplayErrorMessage(null, errorType, messageArgs, recoveryOptions);
|
||||
}
|
||||
|
||||
static IObservable<RecoveryOptionResult> DisplayErrorMessage(this Exception exception, ErrorType errorType, object[] messageArgs, IEnumerable<IRecoveryCommand> 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<IBrowser>().OpenUrl(url);
|
||||
return RecoveryOptionResult.CancelOperation;
|
||||
})
|
||||
{
|
||||
IsDefault = true,
|
||||
};
|
||||
}
|
||||
|
||||
static IObservable<RecoveryOptionResult> 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<IRecoveryCommand> recoveryCommands)
|
||||
{
|
||||
return new ErrorMap(defaultMessage, null, recoveryCommands);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Exception, ErrorMessage> translator;
|
||||
|
||||
public Translation(string original, string heading, string message)
|
||||
{
|
||||
Original = original;
|
||||
defaultMessage = new ErrorMessage(heading, message);
|
||||
}
|
||||
|
||||
public Translation(string original, Func<Exception, ErrorMessage> translator)
|
||||
{
|
||||
Original = original;
|
||||
this.translator = translator;
|
||||
}
|
||||
|
||||
public Translation(string original, string heading, string messageFormatString, Func<Exception, string> 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<Translation, string> 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<Translation, string>(this, null);
|
||||
|
||||
var exceptionMessageLines = exceptionMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (exceptionMessageLines.Any(l => l == Original)) return new Tuple<Translation, string>(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<ApiError>(exceptionMessage)
|
||||
: null;
|
||||
if (githubError != null
|
||||
&& (githubError.Message == Original
|
||||
|| (githubError.Errors != null && githubError.Errors.Any(e => e.Message == Original))))
|
||||
{
|
||||
return new Tuple<Translation, string>(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<Translation, string>(this, regexMatchingLine) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public class Translation<TException> : 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<Translation, string> Match(Exception exception)
|
||||
{
|
||||
if (exception is TException) return new Tuple<Translation, string>(null, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RecoveryOptionResult> 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RecoveryOptionResult> 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<RecoveryOptionResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<object> CancelCommand { get; private set; }
|
||||
public ICommand CancelCmd { get { return CancelCommand; } }
|
||||
public IObservable<object> Cancelling { get { return CancelCommand; } }
|
||||
|
||||
public ReactiveCommand<object> OkCommand { get; private set; }
|
||||
public ICommand OkCmd { get { return OkCommand; } }
|
||||
|
||||
[ImportingConstructor]
|
||||
public CloneRepoViewModel(IRepositoryHosts hosts)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<object> CancelCommand { get; private set; }
|
||||
public ICommand CancelCmd { get { return CancelCommand; } }
|
||||
|
||||
public ReactiveCommand<object> 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<string> RepositoryNameValidator { get; private set; }
|
||||
public string Description { get; set; }
|
||||
public ReactivePropertyValidator<IAccount> 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<Unit> CreateRepository { get; private set; }
|
||||
public bool IsPublishing { get; private set; }
|
||||
public ReactiveCommand<Object> UpgradeAccountPlan { get; private set; }
|
||||
public ReactiveCommand<Object> Reset { get; private set; }
|
||||
public ReactiveList<IAccount> Accounts { get; private set; }
|
||||
public IAccount SelectedAccount { get; private set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -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<IEnterpriseProbe> 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<object> CancelCommand { get; private set; }
|
||||
public ICommand CancelCmd { get { return CancelCommand; } }
|
||||
public IObservable<object> CancelEvt { get { return CancelCommand; } }
|
||||
public IObservable<object> Cancelling { get { return CancelCommand; } }
|
||||
|
||||
public ReactiveCommand<object> ForgotPasswordCommand { get; private set; }
|
||||
public ReactiveCommand<object> ShowDotComLoginCommand { get; set; }
|
||||
|
|
|
@ -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<RecoveryOptionResult> ShowHelpCommand { get; private set; }
|
||||
public ReactiveCommand<RecoveryOptionResult> 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<RecoveryOptionResult> Show(TwoFactorRequiredUserError error)
|
||||
{
|
||||
TwoFactorType = error.TwoFactorType;
|
||||
|
|
|
@ -19,4 +19,5 @@
|
|||
<package id="Rx-XAML" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Splat" version="1.6.0" targetFramework="net45" />
|
||||
<package id="SQLitePCL.raw_basic" version="0.7.1" targetFramework="net45" />
|
||||
<package id="Stateless" version="2.5.11.0" targetFramework="net45" />
|
||||
</packages>
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using GitHub.Exports;
|
||||
|
||||
namespace GitHub.Authentication
|
||||
namespace GitHub.Authentication
|
||||
{
|
||||
|
||||
public static class AuthenticationResultExtensions
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<Reference Include="Octokit">
|
||||
<HintPath>..\..\packages\Octokit.0.6.2\lib\net45\Octokit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Core" />
|
||||
|
@ -51,22 +52,30 @@
|
|||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Authentication\AuthenticationResultExtensions.cs" />
|
||||
<Compile Include="Helpers\ImageHelper.cs" />
|
||||
<Compile Include="Models\IProgram.cs" />
|
||||
<Compile Include="Api\ISimpleApiClient.cs" />
|
||||
<Compile Include="Api\ISimpleApiClientFactory.cs" />
|
||||
<Compile Include="Authentication\AuthenticationResult.cs" />
|
||||
<Compile Include="Models\IRepositoryModel.cs" />
|
||||
<Compile Include="Primitives\StringEquivalent.cs" />
|
||||
<Compile Include="Primitives\UriString.cs" />
|
||||
<Compile Include="Services\EnterpriseProbeTask.cs" />
|
||||
<Compile Include="Services\ExportFactoryProvider.cs" />
|
||||
<Compile Include="Services\IBrowser.cs" />
|
||||
<Compile Include="Services\IEnterpriseProbeTask.cs" />
|
||||
<Compile Include="Services\IUIProvider.cs" />
|
||||
<Compile Include="Services\IWikiProbe.cs" />
|
||||
<Compile Include="Services\WikiProbe.cs" />
|
||||
<Compile Include="UI\ICloneDialog.cs" />
|
||||
<Compile Include="UI\ILoginDialog.cs" />
|
||||
<Compile Include="UI\ITwoFactorDialog.cs" />
|
||||
<Compile Include="UI\ICloneRepoViewModel.cs" />
|
||||
<Compile Include="UI\ILoginViewModel.cs" />
|
||||
<Compile Include="UI\ITwoFactorViewModel.cs" />
|
||||
<Compile Include="Primitives\HostAddress.cs" />
|
||||
<Compile Include="UI\IUIController.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GlobalSuppressions.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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<T> : ISerializable, IXmlSerializable where T : StringEquivalent<T>
|
||||
{
|
||||
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<T> a, string b)
|
||||
{
|
||||
return a.Combine(b);
|
||||
}
|
||||
|
||||
public static bool operator ==(StringEquivalent<T> a, StringEquivalent<T> 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<T> a, StringEquivalent<T> 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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a URI given to us as a string and is implicitly
|
||||
/// convertible to and from string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly", Justification = "GetObjectData is implemented in the base class")]
|
||||
[Serializable]
|
||||
public class UriString : StringEquivalent<UriString>, IEquatable<UriString>
|
||||
{
|
||||
//static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
|
||||
static readonly Regex sshRegex = new Regex(@"^.+@(?<host>(\[.*?\]|[a-z0-9-.]+?))(:(?<owner>.*?))?(/(?<repo>.*)(\.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<string, string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the URL is HTTP or HTTPS
|
||||
/// </summary>
|
||||
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<UriString>.Equals(UriString other)
|
||||
{
|
||||
return other != null && ToString().Equals(other.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ILoginDialog> LoginViewModelFactory { get; set; }
|
||||
public ExportFactory<ILoginViewModel> LoginViewModelFactory { get; set; }
|
||||
|
||||
[Import(AllowRecomposition = true)]
|
||||
public ExportFactory<IUIController> UIControllerFactory { get; set; }
|
||||
|
||||
/*
|
||||
[Import(AllowRecomposition = true)]
|
||||
public ExportFactory<ITwoFactorDialog> TwoFactorViewModelFactory { get; set; }
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace GitHub.UI
|
||||
{
|
||||
public interface ICloneRepoViewModel
|
||||
{
|
||||
ICommand OkCmd { get; }
|
||||
ICommand CancelCmd { get; }
|
||||
IObservable<object> Cancelling { get; }
|
||||
}
|
||||
}
|
|
@ -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<object> CancelEvt { get; }
|
||||
IObservable<object> Cancelling { get; }
|
||||
IObservable<AuthenticationResult> AuthenticationResults { get; }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -37,6 +37,12 @@
|
|||
<HintPath>..\..\packages\NullGuard.Fody.1.2.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ReactiveUI">
|
||||
<HintPath>..\..\packages\reactiveui-core.6.3.1\lib\Net45\ReactiveUI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Splat">
|
||||
<HintPath>..\..\packages\Splat.1.6.0\lib\Net45\Splat.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Reactive.Core">
|
||||
|
@ -50,6 +56,13 @@
|
|||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Reactive.PlatformServices">
|
||||
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Reactive.Windows.Threading">
|
||||
<HintPath>..\..\packages\Rx-XAML.2.2.5\lib\net45\System.Reactive.Windows.Threading.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\script\SolutionInfo.cs">
|
||||
|
@ -57,6 +70,7 @@
|
|||
</Compile>
|
||||
<Compile Include="ObservableExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RecoveryCommandWithIcon.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
|
|
@ -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<object, RecoveryOptionResult> handler = null) : base(commandName, handler)
|
||||
{
|
||||
Icon = icon;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,12 @@
|
|||
<packages>
|
||||
<package id="Fody" version="1.26.4" targetFramework="net45" developmentDependency="true" />
|
||||
<package id="NullGuard.Fody" version="1.2.0.0" targetFramework="net45" developmentDependency="true" />
|
||||
<package id="reactiveui-core" version="6.3.1" targetFramework="net45" />
|
||||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Rx-Main" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Rx-XAML" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Splat" version="1.6.0" targetFramework="net45" />
|
||||
</packages>
|
|
@ -33,6 +33,8 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="NullGuard">
|
||||
<HintPath>..\..\packages\NullGuard.Fody.1.2.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
@ -42,6 +44,7 @@
|
|||
<HintPath>..\..\packages\Splat.1.4.2.1\lib\Net45\Splat.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
|
|
|
@ -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<TUserError>(IObservable<bool> clearWhen) where TUserError : UserError
|
||||
{
|
||||
if (IsVisible)
|
||||
{
|
||||
return UserError.RegisterHandler<TUserError>(userError =>
|
||||
{
|
||||
UserError = userError;
|
||||
return clearWhen
|
||||
.Skip(1)
|
||||
.Do(_ => UserError = null)
|
||||
.Select(x => RecoveryOptionResult.CancelOperation);
|
||||
});
|
||||
}
|
||||
return Disposable.Empty;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:GitHub.UI"
|
||||
xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"
|
||||
xmlns:rxui="clr-namespace:ReactiveUI;assembly=ReactiveUI">
|
||||
<Style TargetType="{x:Type local:UserErrorMessages}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type local:UserErrorMessages}">
|
||||
<Grid x:Name="grid" Visibility="Collapsed" Opacity="0">
|
||||
|
||||
<Grid.LayoutTransform>
|
||||
<ScaleTransform x:Name="ScaleTransform" ScaleX="1" ScaleY="0"></ScaleTransform>
|
||||
</Grid.LayoutTransform>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Viewbox Grid.Column="0" VerticalAlignment="Top"
|
||||
Margin="{TemplateBinding IconMargin}"
|
||||
HorizontalAlignment ="Right" Width="16" Height="16">
|
||||
<ui:OcticonPath x:Name="icon" Icon="{TemplateBinding Icon}" Height="1024" Fill="{TemplateBinding Fill}"/>
|
||||
</Viewbox>
|
||||
<ContentControl Grid.Column="1" Content="{Binding}">
|
||||
<ContentControl.Resources>
|
||||
|
||||
<DataTemplate DataType="{x:Type rxui:UserError}">
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Path=ErrorMessage}"
|
||||
FontWeight="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserErrorMessages}},Path=ErrorMessageFontWeight}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserErrorMessages}},Path=MessageMargin}"/>
|
||||
|
||||
<TextBlock x:Name="descriptionTextBlock" Text="{Binding Path=ErrorCauseOrResolution}"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<Trigger SourceName="descriptionTextBlock" Property="Text" Value="">
|
||||
<Setter TargetName="descriptionTextBlock" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</DataTemplate.Triggers>
|
||||
|
||||
</DataTemplate>
|
||||
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Resources>
|
||||
|
||||
<Storyboard x:Key="Show" Storyboard.TargetName="grid">
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.LayoutTransform).(ScaleTransform.ScaleY)">
|
||||
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<Storyboard x:Key="Hide" Storyboard.TargetName="grid">
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.LayoutTransform).(ScaleTransform.ScaleY)">
|
||||
<SplineDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
|
||||
<SplineDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<SplineDoubleKeyFrame KeyTime="0" Value="1"/>
|
||||
<SplineDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</ControlTemplate.Resources>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
|
||||
<Trigger Property="IsShowingMessage" Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard x:Name="Show_BeginStoryboard" Storyboard="{StaticResource Show}"/>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<RemoveStoryboard BeginStoryboardName="Show_BeginStoryboard"/>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsShowingMessage" Value="False">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard x:Name="Hide_BeginStoryboard" Storyboard="{StaticResource Hide}"/>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<RemoveStoryboard BeginStoryboardName="Hide_BeginStoryboard"/>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="e">The selection changed arguments</param>
|
||||
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<object> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Weavers>
|
||||
<NullGuard />
|
||||
<NullGuard ExcludeRegex="^GitHub.UI.*$"/>
|
||||
</Weavers>
|
|
@ -11,6 +11,7 @@
|
|||
<AssemblyName>GitHub.UI.Reactive</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<NuGetPackageImportStamp>9a81e774</NuGetPackageImportStamp>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
|
@ -88,8 +89,9 @@
|
|||
<Compile Include="..\..\script\SolutionInfo.cs">
|
||||
<Link>Properties\SolutionInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Assets\Controls\Validation\ReactivePropertyValidator.cs" />
|
||||
<Compile Include="Assets\Controls\Validation\UserErrorMessages.cs" />
|
||||
<Compile Include="Assets\Controls\Validation\ValidationMessage.cs" />
|
||||
<Compile Include="Controls\FilteredComboBox.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Validation\ReactiveValidatableObject.cs" />
|
||||
<Compile Include="Validation\ValidateIfAttribute.cs" />
|
||||
|
@ -99,15 +101,25 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Assets\Controls\Validation\UserErrorMessages.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Assets\Controls\Validation\ValidationMessage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="SharedDictionary.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj">
|
||||
<Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project>
|
||||
<Name>GitHub.Exports.Reactive</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GitHub.Extensions.Reactive\GitHub.Extensions.Reactive.csproj">
|
||||
<Project>{6559E128-8B40-49A5-85A8-05565ED0C7E3}</Project>
|
||||
<Name>GitHub.Extensions.Reactive</Name>
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
mc:Ignorable="d">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Controls/GitHubLinkButton.xaml" />
|
||||
<ResourceDictionary Source="Controls/ScrollViewerWithShadow.xaml" />
|
||||
<ResourceDictionary Source="Controls/LightListBox.xaml" />
|
||||
<ResourceDictionary Source="../Controls/Octicons/OcticonImage.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
|
@ -13,333 +16,13 @@
|
|||
Styles for standard windows controls
|
||||
-->
|
||||
|
||||
<!-- TextBox -->
|
||||
<Style x:Key="GitHubTextBox" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource GitHubTextBoxBorderBrush}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource GitHubTextBoxForegroundBrush}" />
|
||||
<Setter Property="FontFamily" Value="{StaticResource GitHubFontFamilyNormal}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="3,3,3,0" />
|
||||
<Setter Property="Margin" Value="12,0,12,0" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="ToolTipService.ShowDuration" Value="30000" />
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True" />
|
||||
<Setter Property="ContextMenu" Value="{DynamicResource DefaultContextMenu}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TextBox}">
|
||||
<Grid>
|
||||
<Border x:Name="Bd" CornerRadius="2" ClipToBounds="True" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
||||
<Border CornerRadius="0" BorderBrush="Black" BorderThickness="0,1,0,0" Margin="2,-2,2,0" Opacity="0.4">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Direction="270" ShadowDepth="0" BlurRadius="4" Opacity="1" />
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Grid Margin="3,1,0,0">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalAlignment="Top" Margin="0"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource GitHubTextBoxMouseOverBorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocusWithin" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GitHubAccentBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="RoundedPromptTextBox" TargetType="{x:Type ui:PromptTextBox}" BasedOn="{StaticResource GitHubTextBox}">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#D1D1D1"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource GHTextBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="AllowDrop" Value="true"/>
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ui:PromptTextBox}">
|
||||
<Grid>
|
||||
<Border x:Name="Bd" CornerRadius="2" ClipToBounds="True" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
||||
<Border CornerRadius="0" BorderBrush="Black" BorderThickness="0,1,0,0" Margin="2,-2,2,0" Opacity="0.4">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Direction="270" ShadowDepth="0" BlurRadius="4" Opacity="1" />
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Grid Margin="1,0,0,0">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalAlignment="Top" Margin="0"/>
|
||||
<Label x:Name="PromptLabel" HorizontalAlignment="Left"
|
||||
Foreground="{DynamicResource GHTextBrush}"
|
||||
FontSize="{TemplateBinding FontSize}" Margin="2,0,0,0" Padding="{TemplateBinding Padding}" Opacity="0"
|
||||
Target="{Binding ElementName=Bd}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Focusable="False" IsHitTestVisible="False"
|
||||
VerticalAlignment="Top">
|
||||
<TextBlock Text="{TemplateBinding PromptText}" TextTrimming="CharacterEllipsis" />
|
||||
</Label>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource GitHubTextBoxMouseOverBorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocusWithin" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GitHubAccentBrush}" />
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}" Value="0">
|
||||
<Setter Property="Opacity" TargetName="PromptLabel" Value="0.7" />
|
||||
<Setter Property="Foreground" Value="Transparent" />
|
||||
</DataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
|
||||
</ControlTemplate>
|
||||
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="GitHubPromptRichText" TargetType="{x:Type ui:PromptRichTextBox}">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#D1D1D1"/>
|
||||
<Setter Property="Foreground" Value="#333333"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="3,1,3,0" />
|
||||
<Setter Property="Margin" Value="12,0,12,0" />
|
||||
<Setter Property="MinWidth" Value="10" />
|
||||
<Setter Property="AllowDrop" Value="False" />
|
||||
<Setter Property="AcceptsReturn" Value="False" />
|
||||
<Setter Property="AcceptsTab" Value="False" />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="False" />
|
||||
<Setter Property="ToolTipService.ShowDuration" Value="30000" />
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True" />
|
||||
<Setter Property="ContextMenu" Value="{DynamicResource DefaultContextMenu}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
|
||||
<ControlTemplate TargetType="{x:Type ui:PromptRichTextBox}">
|
||||
<Grid>
|
||||
<Border x:Name="Bd" CornerRadius="2" ClipToBounds="True" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
||||
<Border CornerRadius="0" BorderBrush="Black" BorderThickness="0,1,0,0" Margin="2,-2,2,0" Opacity="0.4">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Direction="270" ShadowDepth="0" BlurRadius="4" Opacity="1" />
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Grid Margin="2,2,0,0">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalAlignment="Top" Margin="0"/>
|
||||
<Label x:Name="PromptLabel"
|
||||
FontSize="{TemplateBinding FontSize}" Margin="2,0,0,0" Padding="{TemplateBinding Padding}" Opacity="0"
|
||||
Content="{TemplateBinding PromptText}" Target="{Binding ElementName=Bd}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Focusable="False" IsHitTestVisible="False"
|
||||
VerticalAlignment="Top" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource GitHubTextBoxMouseOverBorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocusWithin" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GitHubAccentBrush}" />
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
|
||||
<Setter Property="Opacity" TargetName="PromptLabel" Value="0.7" />
|
||||
<Setter Property="Foreground" Value="Transparent" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}" Value="0">
|
||||
<Setter Property="Opacity" TargetName="PromptLabel" Value="0.7" />
|
||||
<Setter Property="Foreground" Value="Transparent" />
|
||||
</DataTrigger>
|
||||
<Trigger Property="IsOverCharacterLimit" Value="True">
|
||||
<Setter Property="ToolTip"
|
||||
Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=CharacterLimitToolTipWarning}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
|
||||
</ControlTemplate>
|
||||
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource GitHubTextBox}" />
|
||||
<Style TargetType="{x:Type ui:PromptTextBox}" BasedOn="{StaticResource RoundedPromptTextBox}" />
|
||||
<Style TargetType="{x:Type ui:SecurePasswordBox}" BasedOn="{StaticResource RoundedPromptTextBox}" />
|
||||
<!-- End TextBox -->
|
||||
|
||||
<!-- PasswordBox -->
|
||||
<Style x:Key="GitHubPasswordBox" TargetType="{x:Type PasswordBox}">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource GitHubTextBoxBorderBrush}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource GitHubTextBoxForegroundBrush}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource GitHubFontFamilyNormal}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="3,2,3,3" />
|
||||
<Setter Property="Margin" Value="12,0,12,0" />
|
||||
<Setter Property="Width" Value="Auto" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type PasswordBox}">
|
||||
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource GitHubTextBoxMouseOverBorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocusWithin" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GitHubAccentBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource GitHubPasswordBox}" />
|
||||
<!-- End PasswordBox -->
|
||||
|
||||
<!-- Label -->
|
||||
<Style x:Key="GitHubLabel" TargetType="{x:Type Label}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource GHTextBrush}" />
|
||||
</Style>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource GitHubLabel}" />
|
||||
<!-- End Label -->
|
||||
|
||||
<!-- Textblock -->
|
||||
<Style x:Key="GitHubTextBlock" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource GHTextBrush}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource GitHubFontFamilyNormal}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="ToolTipService.ShowDuration" Value="30000" />
|
||||
</Style>
|
||||
<Style x:Key="GitHubH1TextBlock" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource GitHubTextBlock}">
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="Margin" Value="24,10,0,6" />
|
||||
<Setter Property="TextOptions.TextFormattingMode" Value="Ideal" />
|
||||
</Style>
|
||||
<Style x:Key="GitHubDescriptionTextBlock" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource GitHubTextBlock}">
|
||||
<Setter Property="Margin" Value="24,0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
<!-- End Textblock -->
|
||||
|
||||
<!-- ToolTip -->
|
||||
<Style x:Key="GitHubToolTip" TargetType="{x:Type ToolTip}">
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="FontSize" Value="10pt" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="HasDropShadow" Value="False" />
|
||||
<Setter Property="Padding" Value="5,1,5,3" />
|
||||
<Setter Property="Background" Value="#f9f9f9" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#20000000" />
|
||||
<Setter Property="Foreground" Value="#494949" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToolTip">
|
||||
<Grid>
|
||||
<Rectangle Margin="2,2,0,0" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"
|
||||
Fill="{TemplateBinding Background}" Opacity="0" StrokeThickness="0" />
|
||||
<Border Opacity="0.95" Margin="4,2,4,5" SnapsToDevicePixels="True"
|
||||
Background="{TemplateBinding Background}" BorderBrush="#cacaca" BorderThickness="1" Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}">
|
||||
<ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" Direction="270" BlurRadius="4" Opacity="0.2" />
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToolTip}" BasedOn="{StaticResource GitHubToolTip}" />
|
||||
<!-- End ToolTip -->
|
||||
|
||||
<ContextMenu x:Key="DefaultContextMenu">
|
||||
<ContextMenu x:Key="DefaultContextMenu">
|
||||
<MenuItem Header="Cut" Command="ApplicationCommands.Cut"/>
|
||||
<MenuItem Header="Copy" Command="ApplicationCommands.Copy"/>
|
||||
<MenuItem Header="Paste" Command="ApplicationCommands.Paste"/>
|
||||
</ContextMenu>
|
||||
<!--End ContextMenu-->
|
||||
|
||||
<LinearGradientBrush x:Key="TextBoxBorder" EndPoint="0,20" MappingMode="Absolute" StartPoint="0,0">
|
||||
<GradientStop Color="#ABADB3" Offset="0.05" />
|
||||
<GradientStop Color="#E2E3EA" Offset="0.07" />
|
||||
<GradientStop Color="#E3E9EF" Offset="1" />
|
||||
</LinearGradientBrush>
|
||||
<Style x:Key="{x:Type TextBoxBase}" BasedOn="{x:Null}" TargetType="{x:Type TextBoxBase}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource GHTextBrush}" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Padding" Value="1" />
|
||||
<Setter Property="AllowDrop" Value="true" />
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TextBoxBase}">
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Type Hyperlink}" TargetType="{x:Type Hyperlink}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource GHBlueLinkButtonTextBrush}" />
|
||||
<Setter Property="TextDecorations" Value="None" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter Property="Foreground" Value="{DynamicResource GHBlueLinkButtonHoverBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="Foreground" Value="{DynamicResource GHBlueLinkButtonDisabledTextBrush}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="ValidationAdorner">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="GitHubBlueLinkButton" TargetType="{x:Type ButtonBase}">
|
||||
<Setter Property="Foreground" Value="{StaticResource GHBlueLinkButtonTextBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource GHBlueLinkButtonTextBrush}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource GitHubFontFamilyNormal}" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="FocusVisualStyle" Value="{StaticResource NegativeMarginFocusVisual}" />
|
||||
<Setter Property="ToolTipService.ShowDuration" Value="30000" />
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ButtonBase}">
|
||||
<Grid Background="Transparent" Margin="{TemplateBinding Margin}">
|
||||
<ContentPresenter
|
||||
x:Name="Text"
|
||||
Grid.Column="0"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
RecognizesAccessKey="True" />
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="{StaticResource GHBlueLinkButtonHoverBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Foreground" Value="{StaticResource GHBlueLinkButtonPressedBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
|
@ -0,0 +1,104 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ScrollViewerWithShadow.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style x:Key="LightListItemContainerStyle" TargetType="{x:Type ListBoxItem}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
|
||||
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment,
|
||||
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
|
||||
|
||||
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment,
|
||||
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
|
||||
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="#222222"/>
|
||||
<Setter Property="BorderBrush" Value="#b7b7b7"/>
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ListBoxItem}">
|
||||
<Border x:Name="Bd" BorderThickness="0" Background="{TemplateBinding Background}">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsSelected" Value="False"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Foreground" Value="#0a0a0a"/>
|
||||
<Setter Property="BorderBrush" Value="#909090"/>
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="Selector.IsSelectionActive" Value="False"/>
|
||||
<Condition Property="IsSelected" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="#E7E7E7"/>
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="Selector.IsSelectionActive" Value="True"/>
|
||||
<Condition Property="IsSelected" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="#3998d6"/>
|
||||
<Setter Property="Foreground" Value="#ffffff"/>
|
||||
<Setter Property="BorderBrush" Value="#ffffff"/>
|
||||
</MultiTrigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.3"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LightListBox" TargetType="{x:Type ListBox}">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource LightListItemContainerStyle}" />
|
||||
<Setter Property="TextSearch.TextPath" Value="Name" />
|
||||
|
||||
<!-- Sane default -->
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
|
||||
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel" />
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="True" />
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ListBox}">
|
||||
<ScrollViewer Style="{StaticResource ScrollViewerWithShadow}">
|
||||
<ItemsPresenter />
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ItemTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid Height="30">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="30,0,6,0"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
|
@ -0,0 +1,75 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:GitHub.UI">
|
||||
|
||||
<Style x:Key="ScrollViewerWithShadow" TargetType="{x:Type ScrollViewer}">
|
||||
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ScrollViewer}">
|
||||
<Grid x:Name="Grid" Background="{TemplateBinding Background}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Rectangle x:Name="Corner" HorizontalAlignment="Right" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
|
||||
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
|
||||
<ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" HorizontalAlignment="Right" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/>
|
||||
<ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
|
||||
|
||||
<ui:HorizontalShadowDivider
|
||||
x:Name="shadow"
|
||||
Grid.Row="0"
|
||||
Visibility="{TemplateBinding ContentVerticalOffset, Converter={ui:VerticalOffsetToVisibilityConverter}}"
|
||||
Grid.ColumnSpan="1" />
|
||||
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<!-- shoutout to De Morgan
|
||||
- when the mouse leave AND keyboard focus leaves, hide the scrollviewer
|
||||
- otherwise show the scrollviewer -->
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="False" />
|
||||
<Condition Property="IsKeyboardFocusWithin" Value="False" />
|
||||
</MultiTrigger.Conditions>
|
||||
<MultiTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="PART_VerticalScrollBar"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="0" To="1" Duration="0:0:0.3" EasingFunction="{StaticResource GitHubDefaultEaseInOut}" />
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="PART_HorizontalScrollBar"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="0" To="1" Duration="0:0:0.3" EasingFunction="{StaticResource GitHubDefaultEaseInOut}" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</MultiTrigger.ExitActions>
|
||||
<MultiTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="PART_VerticalScrollBar"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="1" To="0" Duration="0:0:1" EasingFunction="{StaticResource GitHubDefaultEaseInOut}" />
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="PART_HorizontalScrollBar"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="1" To="0" Duration="0:0:1" EasingFunction="{StaticResource GitHubDefaultEaseInOut}" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</MultiTrigger.EnterActions>
|
||||
</MultiTrigger>
|
||||
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
|
@ -47,6 +47,7 @@
|
|||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="RoundedPromptTextBox" TargetType="{x:Type ui:PromptTextBox}" BasedOn="{StaticResource GitHubTextBox}">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#D1D1D1"/>
|
||||
|
@ -104,6 +105,7 @@
|
|||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GitHubPromptRichText" TargetType="{x:Type ui:PromptRichTextBox}">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#D1D1D1"/>
|
||||
|
@ -183,6 +185,54 @@
|
|||
<Style TargetType="{x:Type ui:PromptTextBox}" BasedOn="{StaticResource RoundedPromptTextBox}" />
|
||||
<Style TargetType="{x:Type ui:SecurePasswordBox}" BasedOn="{StaticResource RoundedPromptTextBox}" />
|
||||
|
||||
<Style x:Key="AppendingPathTextBoxStyle" TargetType="{x:Type ui:AppendingPathTextBox}" BasedOn="{StaticResource GitHubTextBox}">
|
||||
<Setter Property="Padding" Value="3,3,0,0" />
|
||||
<Setter Property="Foreground" Value="{StaticResource GHTextBrush}" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
|
||||
<ControlTemplate TargetType="{x:Type ui:AppendingPathTextBox}">
|
||||
|
||||
<Grid>
|
||||
<Border x:Name="Bd" CornerRadius="2" ClipToBounds="True" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
||||
<Border CornerRadius="0" BorderBrush="Black" BorderThickness="0,1,0,0" Margin="2,-2,2,0" Opacity="0.4">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Direction="270" ShadowDepth="0" BlurRadius="4" Opacity="1" />
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Grid Margin="1,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Padding="3,3,0,0" />
|
||||
<TextBlock Visibility="{TemplateBinding PathSeparatorVisibility}" Text="\" Grid.Column="1" Padding="0,3,0,3" Margin="-3,0,0,0"/>
|
||||
<TextBlock x:Name="childFolderTextBlock" Visibility="{TemplateBinding ChildFolderVisibility}" Text="{TemplateBinding Property=ChildFolderName}" Grid.Column="2" Padding="0,3,0,3" Margin="-2,0,0,0" Opacity="0.7" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource GitHubTextBoxMouseOverBorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocusWithin" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GitHubAccentBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="PathSeparatorVisibility" Value="Visible">
|
||||
<Setter Property="Margin" Value="-1,0,0,0" TargetName="childFolderTextBlock" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- PasswordBox -->
|
||||
<Style x:Key="GitHubPasswordBox" TargetType="{x:Type PasswordBox}">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource GitHubTextBoxBorderBrush}" />
|
||||
|
@ -322,37 +372,6 @@
|
|||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="ValidationAdorner">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border BorderThickness="1" BorderBrush="{DynamicResource GitHubRed}" />
|
||||
<AdornedElementPlaceholder />
|
||||
<Grid Grid.Column="1" Margin="6,0,-3,0">
|
||||
<Image Width="10" Height="20" RenderTransformOrigin="0.5,0.5">
|
||||
<Image.RenderTransform>
|
||||
<TransformGroup>
|
||||
<RotateTransform Angle="180" />
|
||||
</TransformGroup>
|
||||
</Image.RenderTransform>
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Brush="{DynamicResource GitHubRed}" Geometry="M897.41,613.924l-10.33,10.331v-20.661L897.41,613.924z" />
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2" Background="{DynamicResource GitHubRed}">
|
||||
<TextBlock Foreground="White" FontWeight="Bold" FontSize="12" Style="{StaticResource GitHubTextBlock}" Margin="8,4,12,0" Text="{Binding Path=/ErrorContent}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="LockIconPath" TargetType="{x:Type Path}">
|
||||
<Setter Property="Data" Value="F1M2,9C2,9 2,16 2,16 2,16 9,16 9,16 9,16 9,15.891 9,15.699L9,15C9,15 3,15 3,15 3,15 3,14 3,14 3,14 9,14 9,14L9,13C9,13 3,13 3,13 3,13 3,12 3,12 3,12 9,12 9,12L9,11C9,11 3,11 3,11 3,11 3,10 3,10 3,10 9,10 9,10 9,9.438 9,9 9,9 9,9 2,9 2,9z M7,3C5.895,3 5,3.895 5,5 5,6.105 5,8 5,8 5,8 9,8 9,8 9,8 9,5.707 9,5 9,3.895 8.105,3 7,3z M7,1C9.209,1 11,2.791 11,5 11,5.298 11,5.822 11,6.441L11,8 11.57,8C11.844,8 12,8 12,8 12.552,8 13,8.448 13,9 13,9 13,16 13,16 13,16.552 12.552,17 12,17 12,17 2,17 2,17 1.448,17 1,16.552 1,16 1,16 1,9 1,9 1,8.448 1.448,8 2,8 2,8 2.156,8 2.43,8L3,8 3,6.869C3,6.203 3,5.552 3,5 3,2.791 4.791,1 7,1z" />
|
||||
<Setter Property="Fill" Value="#CACACA" />
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace GitHub.UI
|
||||
{
|
||||
public class AppendingPathTextBox : TextBox
|
||||
{
|
||||
public static readonly DependencyProperty ParentFolderPathProperty =
|
||||
DependencyProperty.Register("ParentFolderPath", typeof(string), typeof(AppendingPathTextBox), new FrameworkPropertyMetadata(null, OnUpdateParentFolder));
|
||||
|
||||
public string ParentFolderPath
|
||||
{
|
||||
get { return (string)GetValue(ParentFolderPathProperty); }
|
||||
set { SetValue(ParentFolderPathProperty, value); }
|
||||
}
|
||||
|
||||
public string ChildFolderName
|
||||
{
|
||||
get { return (string)GetValue(ChildFolderNameProperty); }
|
||||
set { SetValue(ChildFolderNameProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ChildFolderNameProperty =
|
||||
DependencyProperty.Register("ChildFolderName", typeof(string), typeof(AppendingPathTextBox), new PropertyMetadata(null, UpdateVisibilities));
|
||||
|
||||
public Visibility PathSeparatorVisibility
|
||||
{
|
||||
get { return (Visibility)GetValue(PathSeparatorVisibilityProperty); }
|
||||
set { SetValue(PathSeparatorVisibilityProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PathSeparatorVisibilityProperty =
|
||||
DependencyProperty.Register("PathSeparatorVisibility", typeof(Visibility), typeof(AppendingPathTextBox), new PropertyMetadata(Visibility.Visible));
|
||||
|
||||
public Visibility ChildFolderVisibility
|
||||
{
|
||||
get { return (Visibility)GetValue(ChildFolderVisibilityProperty); }
|
||||
set { SetValue(ChildFolderVisibilityProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ChildFolderVisibilityProperty =
|
||||
DependencyProperty.Register("ChildFolderVisibility", typeof(Visibility), typeof(AppendingPathTextBox), new PropertyMetadata(Visibility.Visible));
|
||||
|
||||
protected override void OnTextChanged(TextChangedEventArgs e)
|
||||
{
|
||||
ParentFolderPath = Text;
|
||||
base.OnTextChanged(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseDoubleClick(e);
|
||||
|
||||
if (!e.Handled)
|
||||
{
|
||||
this.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
static void OnUpdateParentFolder(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var control = (AppendingPathTextBox)d;
|
||||
control.Text = control.ParentFolderPath;
|
||||
UpdatePathSeparatorVisibility(control);
|
||||
UpdateChildFolderVisibilityVisibility(control);
|
||||
}
|
||||
|
||||
static void UpdateVisibilities(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var control = (AppendingPathTextBox)d;
|
||||
UpdatePathSeparatorVisibility(control);
|
||||
UpdateChildFolderVisibilityVisibility(control);
|
||||
}
|
||||
|
||||
static void UpdateChildFolderVisibilityVisibility(AppendingPathTextBox control)
|
||||
{
|
||||
control.ChildFolderVisibility = string.IsNullOrEmpty(control.ParentFolderPath) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
static void UpdatePathSeparatorVisibility(AppendingPathTextBox control)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(control.ParentFolderPath))
|
||||
{
|
||||
control.PathSeparatorVisibility = control.ParentFolderPath.EndsWith("\\", StringComparison.Ordinal)
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
control.PathSeparatorVisibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
namespace GitHub.UI
|
||||
{
|
||||
public class GitHubComboBox : ComboBox
|
||||
{
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
var popUp = GetTemplateChild("PART_Popup") as Popup;
|
||||
if (popUp != null)
|
||||
{
|
||||
popUp.CustomPopupPlacementCallback = PlacePopup;
|
||||
}
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
public CustomPopupPlacement[] PlacePopup(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[] { new CustomPopupPlacement(new Point(0, targetSize.Height), PopupPrimaryAxis.Vertical) };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace GitHub.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used to workaround some of the terrible issues that
|
||||
/// embedding an image inside a InlineCollection occurs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - as changing the Visibility of the image triggers a reflow,
|
||||
/// -> we trash the Source to achieve an equivalent effect
|
||||
/// - the position of the image can reduce as you adjust the textbox size
|
||||
/// -> only store the largest value as that's the correct one
|
||||
/// </remarks>
|
||||
public class EmojiImage : Image
|
||||
{
|
||||
BitmapSource source;
|
||||
|
||||
public void SetSource(BitmapSource bitmapImage)
|
||||
{
|
||||
source = bitmapImage;
|
||||
Show();
|
||||
Position = double.MinValue;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
Source = source;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
Source = null;
|
||||
}
|
||||
|
||||
public void UpdateVisibility(double offset, double actualWidth, bool isLast)
|
||||
{
|
||||
var end = offset + Width;
|
||||
|
||||
if (!isLast)
|
||||
{
|
||||
// IF EMOJI IS NOT AT END, ADD 18 BECAUSE LOL FLOWDOCUMENTS
|
||||
end += 18;
|
||||
}
|
||||
|
||||
Position = Math.Max(Position, end);
|
||||
|
||||
if (Position >= actualWidth)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
Show();
|
||||
}
|
||||
}
|
||||
|
||||
public double Position { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace GitHub.UI
|
||||
{
|
||||
public class FilterTextBox : TextBox
|
||||
{
|
||||
public static readonly DependencyProperty PromptTextProperty =
|
||||
DependencyProperty.Register("PromptText", typeof(string), typeof(FilterTextBox), new UIPropertyMetadata("Filter"));
|
||||
|
||||
[Localizability(LocalizationCategory.Text)]
|
||||
[DefaultValue("Filter")]
|
||||
public string PromptText
|
||||
{
|
||||
get { return (string)GetValue(PromptTextProperty); }
|
||||
set { SetValue(PromptTextProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand ClearCommand { get; private set; }
|
||||
|
||||
public FilterTextBox()
|
||||
{
|
||||
// http://stackoverflow.com/a/661224/2114
|
||||
AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyIgnoreMouseButton), true);
|
||||
AddHandler(GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText), true);
|
||||
AddHandler(MouseDoubleClickEvent, new RoutedEventHandler(SelectAllText), true);
|
||||
AddHandler(Button.ClickEvent, new RoutedEventHandler(ClearButtonClick), true);
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape && !String.IsNullOrEmpty(Text))
|
||||
{
|
||||
Clear();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
base.OnPreviewKeyDown(e);
|
||||
}
|
||||
|
||||
void ClearButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Clear();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/661224/2114
|
||||
static void SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var textBox = FindTextBoxInAncestors(e.OriginalSource as UIElement);
|
||||
|
||||
if (textBox != null && !textBox.IsKeyboardFocusWithin)
|
||||
{
|
||||
// If the text box is not yet focussed, give it the focus and
|
||||
// stop further processing of this click event.
|
||||
textBox.Focus();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
static TextBox FindTextBoxInAncestors(DependencyObject current)
|
||||
{
|
||||
TextBox tb;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
tb = current as TextBox;
|
||||
if (tb != null)
|
||||
return tb;
|
||||
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void SelectAllText(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var textBox = e.OriginalSource as TextBox;
|
||||
if (textBox != null)
|
||||
textBox.SelectAll();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<UserControl x:Class="GitHub.UI.HorizontalShadowDivider"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="2" d:DesignWidth="300" Height="2"
|
||||
IsHitTestVisible="False"
|
||||
VerticalAlignment="Top">
|
||||
<Grid IsHitTestVisible="False">
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Fill="{StaticResource HorizontalDividerBorderBrush}"
|
||||
VerticalAlignment="Top"
|
||||
IsHitTestVisible="False" />
|
||||
<Rectangle
|
||||
x:Name="shadow"
|
||||
Height="4"
|
||||
StrokeThickness="0"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,-2,0,0"
|
||||
Opacity="0.25"
|
||||
IsHitTestVisible="False"
|
||||
Fill="{StaticResource HorizontalDividerShadowBrush}">
|
||||
<Rectangle.Clip>
|
||||
<RectangleGeometry Rect="0,2,10000,2" />
|
||||
</Rectangle.Clip>
|
||||
</Rectangle>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,15 @@
|
|||
using GitHub.UI.Helpers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace GitHub.UI
|
||||
{
|
||||
public partial class HorizontalShadowDivider : UserControl
|
||||
{
|
||||
public HorizontalShadowDivider()
|
||||
{
|
||||
Resources.MergedDictionaries.Add(SharedDictionaryManager.Load("GitHub.UI"));
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace GitHub.UI
|
||||
{
|
||||
public class TrimmedTextBlock : TextBlock
|
||||
{
|
||||
public static readonly DependencyProperty IsTextTrimmedProperty =
|
||||
DependencyProperty.Register("IsTextTrimmed", typeof(bool), typeof(TrimmedTextBlock), new UIPropertyMetadata(false));
|
||||
|
||||
public bool IsTextTrimmed
|
||||
{
|
||||
get { return (bool)GetValue(IsTextTrimmedProperty); }
|
||||
set { SetValue(IsTextTrimmedProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether or not the text in the TextBlock is currently being
|
||||
/// trimmed due to width or height constraints.
|
||||
/// </summary>
|
||||
/// <remarks>Does not work properly when TextWrapping is set to WrapWithOverflow.</remarks>
|
||||
/// <returns><c>true</c> if the text is currently being trimmed; otherwise <c>false</c></returns>
|
||||
bool CalculateIsTextTrimmed()
|
||||
{
|
||||
if (!IsArrangeValid)
|
||||
{
|
||||
return IsTextTrimmed;
|
||||
}
|
||||
|
||||
string text = Text;
|
||||
if (string.IsNullOrEmpty(Text) && Inlines.Count > 0)
|
||||
{
|
||||
text = string.Join("", Inlines.OfType<Run>().Select(x => x.Text));
|
||||
}
|
||||
|
||||
var typeface = new Typeface(
|
||||
FontFamily,
|
||||
FontStyle,
|
||||
FontWeight,
|
||||
FontStretch);
|
||||
|
||||
// FormattedText is used to measure the whole width of the text held up by TextBlock container
|
||||
var formattedText = new FormattedText(
|
||||
text,
|
||||
Thread.CurrentThread.CurrentCulture,
|
||||
FlowDirection,
|
||||
typeface,
|
||||
FontSize,
|
||||
Foreground);
|
||||
|
||||
formattedText.MaxTextWidth = ActualWidth;
|
||||
|
||||
// When the maximum text width of the FormattedText instance is set to the actual
|
||||
// width of the textBlock, if the textBlock is being trimmed to fit then the formatted
|
||||
// text will report a larger height than the textBlock. Should work whether the
|
||||
// textBlock is single or multi-line.
|
||||
return (Math.Floor(formattedText.Height) > Math.Floor(ActualHeight));
|
||||
}
|
||||
|
||||
protected override void OnInitialized(EventArgs e)
|
||||
{
|
||||
base.OnInitialized(e);
|
||||
SizeChanged += TrimmedTextBlockSizeChanged;
|
||||
}
|
||||
|
||||
void TrimmedTextBlockSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
IsTextTrimmed = TextTrimming.None != TextTrimming && CalculateIsTextTrimmed();
|
||||
|
||||
foreach (var container in Inlines.OfType<InlineUIContainer>().ToArray())
|
||||
{
|
||||
var image = container.Child as EmojiImage;
|
||||
if (image == null) continue;
|
||||
|
||||
var startF = container.ContentStart.GetCharacterRect(LogicalDirection.Forward);
|
||||
|
||||
var isLast = Inlines.LastInline == container;
|
||||
image.UpdateVisibility(startF.Right, ActualWidth, isLast);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
<AssemblyName>GitHub.UI</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<NuGetPackageImportStamp>55d7ee16</NuGetPackageImportStamp>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
|
@ -61,10 +62,18 @@
|
|||
<Compile Include="..\..\script\SolutionInfo.cs">
|
||||
<Link>Properties\SolutionInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Controls\AppendingPathTextBox.cs" />
|
||||
<Compile Include="Controls\ComboBoxes\GitHubComboBox.cs" />
|
||||
<Compile Include="Controls\EmojiImage.cs" />
|
||||
<Compile Include="Controls\FilterTextBox.cs" />
|
||||
<Compile Include="Controls\HorizontalShadowDivider.xaml.cs">
|
||||
<DependentUpon>HorizontalShadowDivider.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\Octicons\Octicon.cs" />
|
||||
<Compile Include="Controls\Octicons\OcticonImage.cs" />
|
||||
<Compile Include="Controls\Octicons\OcticonPath.cs" />
|
||||
<Compile Include="Controls\Octicons\OcticonPaths.Designer.cs" />
|
||||
<Compile Include="Controls\TrimmedTextBlock.cs" />
|
||||
<Compile Include="Helpers\AccessKeysManagerScoping.cs" />
|
||||
<Compile Include="Controls\Buttons\OcticonButton.cs" />
|
||||
<Compile Include="Controls\Buttons\OcticonCircleButton.cs" />
|
||||
|
@ -95,9 +104,19 @@
|
|||
<Compile Include="Converters\VerticalOffsetToVisibilityConverter.cs" />
|
||||
<Compile Include="Helpers\BindingProxy.cs" />
|
||||
<Compile Include="Helpers\GitHubBrushes.cs" />
|
||||
<Compile Include="Helpers\SharedDictionaryManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="Assets\Controls\GitHubLinkButton.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Assets\Controls\LightListBox.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Assets\Controls\ScrollViewerWithShadow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Assets\TextBlocks.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
@ -122,6 +141,9 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Controls\HorizontalShadowDivider.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Controls\Octicons\OcticonImage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
@ -134,6 +156,9 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="SharedDictionary.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
[assembly: AssemblyTitle("GitHub.UI")]
|
||||
[assembly: AssemblyDescription("GitHub flavored WPF styles and controls")]
|
||||
[assembly: Guid("f3cec21e-6a86-43ae-97a6-a274fa31efbe")]
|
||||
|
||||
[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)
|
||||
)]
|
|
@ -57,11 +57,5 @@ namespace GitHub.VisualStudio
|
|||
var exportProvider = componentModel.DefaultExportProvider;
|
||||
return exportProvider.GetExportedValue<T>();
|
||||
}
|
||||
|
||||
protected void EnsureUIProvider()
|
||||
{
|
||||
var ui = GetExportedValue<UIProvider>();
|
||||
ui.EnsureProvider(GetService<SComponentModel, IComponentModel>().DefaultExportProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
<RunCodeAnalysis>false</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\..\script\GitHubVS.ruleset</CodeAnalysisRuleSet>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode>
|
||||
|
@ -56,6 +56,12 @@
|
|||
<HintPath>..\..\packages\EditorUtils2013.1.4.1.1\lib\net40\EditorUtils2013.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="LibGit2Sharp, Version=0.17.30619.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\LibGit2Sharp.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
@ -71,6 +77,7 @@
|
|||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.CoreUtility, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.Text.Data, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.Text.Logic, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.VisualStudio.Text.UI, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
|
@ -87,7 +94,6 @@
|
|||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TextManager.Interop" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.12.0" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0" />
|
||||
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0" />
|
||||
|
@ -140,24 +146,6 @@
|
|||
<Reference Include="System.Xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="EnvDTE100">
|
||||
<Guid>{26AD1324-4B7C-44BC-84F8-B86AED45729F}</Guid>
|
||||
<VersionMajor>10</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>primary</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="EnvDTE90">
|
||||
<Guid>{2CE2370E-D744-4936-A090-3FFFE667B0E1}</Guid>
|
||||
<VersionMajor>9</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>primary</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="Microsoft.VisualStudio.CommandBars">
|
||||
<Guid>{1CBA492E-7263-47BB-87FE-639000619B15}</Guid>
|
||||
<VersionMajor>8</VersionMajor>
|
||||
|
@ -189,7 +177,8 @@
|
|||
<Compile Include="Base\TeamExplorerSectionBase.cs" />
|
||||
<Compile Include="Helpers\Browser.cs" />
|
||||
<Compile Include="Helpers\Colors.cs" />
|
||||
<Compile Include="Helpers\ExportFactoryProvider.cs" />
|
||||
<Compile Include="Helpers\Constants.cs" />
|
||||
<Compile Include="Services\Logger.cs" />
|
||||
<Compile Include="Services\Program.cs" />
|
||||
<Compile Include="Services\Services.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
|
@ -206,6 +195,7 @@
|
|||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="PkgCmdID.cs" />
|
||||
<Compile Include="Services\UIProvider.cs" />
|
||||
<Compile Include="TeamExplorerConnect\PlaceholderGitHubSection.cs" />
|
||||
<Compile Include="TeamExplorerHome\GitHubHomeSection.cs" />
|
||||
<Compile Include="TeamExplorerHome\GraphsNavigationItem.cs" />
|
||||
<Compile Include="TeamExplorerHome\PullRequestsNavigationItem.cs" />
|
||||
|
@ -213,20 +203,26 @@
|
|||
<Compile Include="TeamExplorerHome\WikiNavigationItem.cs" />
|
||||
<Compile Include="TeamExplorerHome\PulseNavigationItem.cs" />
|
||||
<Compile Include="UI\DrawingExtensions.cs" />
|
||||
<Compile Include="UI\Views\Controls\CloneRepoControl.xaml.cs">
|
||||
<DependentUpon>CloneRepoControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Views\Controls\CreateRepoControl.xaml.cs">
|
||||
<DependentUpon>CreateRepoControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Views\Controls\LoginControl.xaml.cs">
|
||||
<DependentUpon>LoginControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Views\CreateIssueDialog.xaml.cs">
|
||||
<DependentUpon>CreateIssueDialog.xaml</DependentUpon>
|
||||
<Compile Include="UI\Views\GitHubConnectContent.xaml.cs">
|
||||
<DependentUpon>GitHubConnectContent.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Views\GitHubHomeContent.xaml.cs">
|
||||
<DependentUpon>GitHubHomeContent.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Views\LoginCommandDialog.xaml.cs">
|
||||
<DependentUpon>LoginCommandDialog.xaml</DependentUpon>
|
||||
<Compile Include="UI\Views\Controls\TwoFactorControl.xaml.cs">
|
||||
<DependentUpon>TwoFactorControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Views\TwoFactorView.xaml.cs">
|
||||
<DependentUpon>TwoFactorView.xaml</DependentUpon>
|
||||
<Compile Include="UI\WindowController.xaml.cs">
|
||||
<DependentUpon>WindowController.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -276,24 +272,37 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="SharedDictionary.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Views\Controls\CloneRepoControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Views\Controls\CreateRepoControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Views\Controls\LoginControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Views\CreateIssueDialog.xaml">
|
||||
<Page Include="UI\Views\GitHubConnectContent.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="UI\Views\GitHubHomeContent.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Views\LoginCommandDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Page Include="UI\Views\Controls\TwoFactorControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>
|
||||
</SubType>
|
||||
</Page>
|
||||
<Page Include="UI\WindowController.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Views\TwoFactorView.xaml">
|
||||
<Page Include="..\..\script\Resources.xaml" Condition="'$(DesignTime)'=='true' OR ('$(SolutionPath)'!='' AND Exists('$(SolutionPath)') AND '$(BuildingInsideVisualStudio)'!='true' AND '$(BuildingInsideExpressionBlend)'!='true')">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<ContainsDesignTimeResources>true</ContainsDesignTimeResources>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -305,6 +314,10 @@
|
|||
<Project>{1a1da411-8d1f-4578-80a6-04576bea2dc5}</Project>
|
||||
<Name>GitHub.App</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj">
|
||||
<Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project>
|
||||
<Name>GitHub.Exports.Reactive</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj">
|
||||
<Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project>
|
||||
<Name>GitHub.Exports</Name>
|
||||
|
|
|
@ -58,25 +58,18 @@
|
|||
<ButtonText>&Login</ButtonText>
|
||||
</Strings>
|
||||
</Button>
|
||||
<Button guid="guidGitHubCmdSet" id="createIssueCommand" priority="0x0101" type="Button">
|
||||
<Button guid="guidGitHubCmdSet" id="createRepoCommand" priority="0x0101" type="Button">
|
||||
<Parent guid="guidGitHubCmdSet" id="GitHubMenuGroup" />
|
||||
<Icon guid="guidImages" id="bmpPic1" />
|
||||
<Strings>
|
||||
<ButtonText>&Create Issue</ButtonText>
|
||||
<ButtonText>&Create repo</ButtonText>
|
||||
</Strings>
|
||||
</Button>
|
||||
<Button guid="guidGitHubCmdSet" id="goToIssueCommand" priority="0x0102" type="Button">
|
||||
<Button guid="guidGitHubCmdSet" id="cloneRepoCommand" priority="0x0102" type="Button">
|
||||
<Parent guid="guidGitHubCmdSet" id="GitHubMenuGroup" />
|
||||
<Icon guid="guidImages" id="bmpPic1" />
|
||||
<Strings>
|
||||
<ButtonText>&Go to issue</ButtonText>
|
||||
</Strings>
|
||||
</Button>
|
||||
<Button guid="guidGitHubCmdSet" id="viewIssuesCommand" priority="0x0103" type="Button">
|
||||
<Parent guid="guidGitHubCmdSet" id="GitHubMenuGroup" />
|
||||
<Icon guid="guidImages" id="bmpPic1" />
|
||||
<Strings>
|
||||
<ButtonText>&View Issues</ButtonText>
|
||||
<ButtonText>&Clone repo</ButtonText>
|
||||
</Strings>
|
||||
</Button>
|
||||
</Buttons>
|
||||
|
@ -94,9 +87,8 @@
|
|||
|
||||
<KeyBindings>
|
||||
<KeyBinding guid="guidGitHubCmdSet" id="loginCommand" editor="guidVSStd97" key1="U" key2="L" mod2="CONTROL" mod1="CONTROL"/>
|
||||
<KeyBinding guid="guidGitHubCmdSet" id="createIssueCommand" editor="guidVSStd97" key1="U" key2="C" mod2="CONTROL" mod1="CONTROL"/>
|
||||
<KeyBinding guid="guidGitHubCmdSet" id="goToIssueCommand" editor="guidVSStd97" key1="U" key2="G" mod2="CONTROL" mod1="CONTROL"/>
|
||||
<KeyBinding guid="guidGitHubCmdSet" id="viewIssuesCommand" editor="guidVSStd97" key1="U" key2="V" mod2="CONTROL" mod1="CONTROL"/>
|
||||
<KeyBinding guid="guidGitHubCmdSet" id="createRepoCommand" editor="guidVSStd97" key1="U" key2="C" mod2="CONTROL" mod1="CONTROL"/>
|
||||
<KeyBinding guid="guidGitHubCmdSet" id="cloneRepoCommand" editor="guidVSStd97" key1="U" key2="G" mod2="CONTROL" mod1="CONTROL"/>
|
||||
</KeyBindings>
|
||||
|
||||
<Symbols>
|
||||
|
@ -106,10 +98,9 @@
|
|||
<!-- This is the guid used to group the menu commands together -->
|
||||
<GuidSymbol name="guidGitHubCmdSet" value="{c4c91892-8881-4588-a5d9-b41e8f540f5a}">
|
||||
<IDSymbol name="GitHubMenuGroup" value="0x1020" />
|
||||
<IDSymbol name="loginCommand" value="0x0110" />
|
||||
<IDSymbol name="createIssueCommand" value="0x0100" />
|
||||
<IDSymbol name="goToIssueCommand" value="0x0101" />
|
||||
<IDSymbol name="viewIssuesCommand" value="0x0102" />
|
||||
<IDSymbol name="loginCommand" value="0x110" />
|
||||
<IDSymbol name="createRepoCommand" value="0x111" />
|
||||
<IDSymbol name="cloneRepoCommand" value="0x112" />
|
||||
<IDSymbol name="GitHubMenu" value="0x1021"/>
|
||||
</GuidSymbol>
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ using System.Runtime.InteropServices;
|
|||
using GitHub.VisualStudio.UI.Views;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using GitHub.Exports;
|
||||
using GitHub.Services;
|
||||
using GitHub.Authentication;
|
||||
|
||||
namespace GitHub.VisualStudio
|
||||
{
|
||||
|
@ -43,69 +45,46 @@ namespace GitHub.VisualStudio
|
|||
/// Initialization of the package; this method is called right after the package is sited, so this is the place
|
||||
/// where you can put all the initialization code that rely on services provided by VisualStudio.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFile")]
|
||||
protected override void Initialize()
|
||||
{
|
||||
Debug.WriteLine("Entering Initialize() of: {0}", ToString());
|
||||
base.Initialize();
|
||||
|
||||
// Set the Export Provider
|
||||
|
||||
|
||||
// Add our command handlers for menu (commands must exist in the .vsct file)
|
||||
// Login Command Menu Item
|
||||
|
||||
// for testing purposes only
|
||||
AddTopLevelMenuItem(PkgCmdIDList.loginCommand, OnLoginCommand);
|
||||
|
||||
// Create Issue Command Menu Item
|
||||
AddTopLevelMenuItem(PkgCmdIDList.createIssueCommand, OnCreateIssueCommand);
|
||||
AddTopLevelMenuItem(PkgCmdIDList.createRepoCommand, OnCreateRepo);
|
||||
AddTopLevelMenuItem(PkgCmdIDList.cloneRepoCommand, OnCloneRepo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is the callback used to execute a command when the a menu item is clicked.
|
||||
/// See the Initialize method to see how the menu item is associated to this function using
|
||||
/// the OleMenuCommandService service and the MenuCommand class.
|
||||
/// </summary>
|
||||
void OnCreateIssueCommand(object sender, EventArgs e)
|
||||
void ShowDialog(GitHub.UI.UIControllerFlow flow)
|
||||
{
|
||||
var createIssueDialog = new CreateIssueDialog();
|
||||
createIssueDialog.ShowModal();
|
||||
var ui = GetExportedValue<IUIProvider>();
|
||||
var disposable = ui.GetService<ExportFactoryProvider>().UIControllerFactory.CreateExport();
|
||||
var watcher = disposable.Value.SelectFlow(flow);
|
||||
var window = new WindowController(watcher);
|
||||
watcher.Subscribe(_ => { }, _ => {
|
||||
window.Close();
|
||||
disposable.Dispose();
|
||||
});
|
||||
//window.Owner = System.Windows.Application.Current.MainWindow;
|
||||
//window.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
|
||||
window.ShowModal();
|
||||
}
|
||||
|
||||
void OnCreateRepo(object sender, EventArgs e)
|
||||
{
|
||||
ShowDialog(GitHub.UI.UIControllerFlow.Create);
|
||||
}
|
||||
|
||||
void OnCloneRepo(object sender, EventArgs e)
|
||||
{
|
||||
ShowDialog(GitHub.UI.UIControllerFlow.Clone);
|
||||
}
|
||||
|
||||
void OnLoginCommand(object sender, EventArgs e)
|
||||
{
|
||||
EnsureUIProvider();
|
||||
/*
|
||||
var mefServiceProvider = GetExportedValue<IServiceProvider>() as MefServiceProvider;
|
||||
Debug.Assert(mefServiceProvider != null, "Service Provider can't be imported");
|
||||
var componentModel = GetService<SComponentModel>() as IComponentModel;
|
||||
if (componentModel != null)
|
||||
mefServiceProvider.ExportProvider = componentModel.DefaultExportProvider;
|
||||
*/
|
||||
|
||||
//var r = GetExportedValue<ILoginDialog>();
|
||||
var factory = GetExportedValue<ExportFactoryProvider>().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.Close();
|
||||
}
|
||||
});
|
||||
|
||||
ShowDialog(GitHub.UI.UIControllerFlow.Authentication);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
namespace GitHub
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string NoAngleBracketsErrorMessage = "Failed to parse signature - Neither `name` nor `email` should contain angle brackets chars.";
|
||||
public const int MaxRepositoryNameLength = 100;
|
||||
public const int MaxDirectoryLength = 200; // Windows allows 248, but we need to allow room for subdirectories.
|
||||
public const int MaxFilePathLength = 260;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace GitHub.VisualStudio
|
|||
static class PkgCmdIDList
|
||||
{
|
||||
public const uint loginCommand = 0x110;
|
||||
public const uint createIssueCommand = 0x100;
|
||||
public const uint createRepoCommand = 0x111;
|
||||
public const uint cloneRepoCommand = 0x112;
|
||||
};
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
using GitHub.Api;
|
||||
using EnvDTE;
|
||||
using EnvDTE80;
|
||||
using GitHub.Api;
|
||||
using GitHub.Services;
|
||||
using LibGit2Sharp;
|
||||
using Microsoft.VisualStudio;
|
||||
using Microsoft.VisualStudio.ComponentModelHost;
|
||||
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;
|
||||
|
@ -14,26 +21,74 @@ namespace GitHub.VisualStudio
|
|||
{
|
||||
public static class Services
|
||||
{
|
||||
public static IComponentModel ComponentModel
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(SComponentModel)) as IComponentModel; }
|
||||
}
|
||||
|
||||
public static IVsWebBrowsingService WebBrowsingService
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(SVsWebBrowsingService)) as IVsWebBrowsingService; }
|
||||
}
|
||||
|
||||
public static IVsTextManager TextManager
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(SVsTextManager)) as IVsTextManager; }
|
||||
}
|
||||
|
||||
public static IVsOutputWindow OutputWindow
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow; }
|
||||
}
|
||||
|
||||
public static IVsSolution Solution
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; }
|
||||
}
|
||||
|
||||
public static IVsSolution GetSolution(this IServiceProvider provider)
|
||||
{
|
||||
return provider.GetService(typeof(SVsSolution)) as IVsSolution;
|
||||
}
|
||||
|
||||
public static IVsWebBrowsingService WebBrowsing
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(SVsWebBrowsingService)) as IVsWebBrowsingService; }
|
||||
}
|
||||
|
||||
public static IVsUIShell Shell
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(SVsUIShell)) as IVsUIShell; }
|
||||
}
|
||||
|
||||
public static DTE Dte
|
||||
{
|
||||
get { return Package.GetGlobalService(typeof(DTE)) as DTE; }
|
||||
}
|
||||
|
||||
public static DTE2 Dte2
|
||||
{
|
||||
get { return Dte as DTE2; }
|
||||
}
|
||||
|
||||
public static IVsSolution GetSolution(this IServiceProvider provider)
|
||||
{
|
||||
return provider.GetService(typeof(SVsSolution)) as IVsSolution;
|
||||
}
|
||||
|
||||
public static IVsWebBrowsingService GetWebBrowsing(this IServiceProvider provider)
|
||||
{
|
||||
return provider.GetService(typeof(SVsWebBrowsingService)) as IVsWebBrowsingService;
|
||||
}
|
||||
|
||||
[return: AllowNull]
|
||||
public static T GetExportedValue<T>(this IServiceProvider serviceProvider)
|
||||
{
|
||||
var ui = serviceProvider as IUIProvider;
|
||||
if (ui != null)
|
||||
return ui.GetService<T>();
|
||||
else
|
||||
{
|
||||
var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel));
|
||||
return componentModel.DefaultExportProvider.GetExportedValue<T>();
|
||||
}
|
||||
}
|
||||
|
||||
[return: AllowNull]
|
||||
public static Uri GetRepoUrlFromSolution(IVsSolution solution)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<UserControl x:Class="GitHub.VisualStudio.UI.Views.Controls.CloneRepoControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI"
|
||||
xmlns:SampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300" Background="White">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application,,,/GitHub.UI;component/SharedDictionary.xaml" />
|
||||
<ResourceDictionary Source="pack://application,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type ui:TrimmedTextBlock}" BasedOn="{StaticResource GitHubTextBlock}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsTextTrimmed" Value="True">
|
||||
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="70" />
|
||||
</Grid.RowDefinitions>
|
||||
<ui:FilterTextBox
|
||||
x:Name="filterText"
|
||||
PromptText="Search repositories"
|
||||
Margin="10" />
|
||||
|
||||
<Grid x:Name="repositoriesListPane" Grid.Row="1">
|
||||
<ListBox x:Name="repositoryList" HorizontalContentAlignment="Stretch" Style="{StaticResource LightListBox}">
|
||||
<ListBox.ItemsSource>
|
||||
<x:Array Type="SampleData:RepositoryModelDesigner">
|
||||
<SampleData:RepositoryModelDesigner Name="github" IsHosted="True" IsPrivate="True" />
|
||||
<SampleData:RepositoryModelDesigner Name="windows" IsHosted="True" IsPrivate="True" />
|
||||
<SampleData:RepositoryModelDesigner Name="ReactiveUI" IsHosted="True" />
|
||||
<SampleData:RepositoryModelDesigner Name="this-is-a-stupidly-long-name-for-a-repository-its-actually-kind-of-insane-how-long-can-it-even-be-wo" IsHosted="True" IsPrivate="True" />
|
||||
</x:Array>
|
||||
</ListBox.ItemsSource>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
||||
<Grid Height="30">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:OcticonImage
|
||||
x:Name="iconPath"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="6,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
Icon="{Binding Icon}"
|
||||
Foreground="{StaticResource GHLinkButtonIconBrush}" />
|
||||
|
||||
<ui:TrimmedTextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True" />
|
||||
<Condition Binding="{Binding Path=(Selector.IsSelectionActive), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Foreground" Value="White" TargetName="iconPath" />
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate> </ListBox>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for CloneRepoControl.xaml
|
||||
/// </summary>
|
||||
[Export(typeof(IViewFor<ICloneRepoViewModel>))]
|
||||
public partial class CloneRepoControl : IViewFor<ICloneRepoViewModel>
|
||||
{
|
||||
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); }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
<UserControl x:Class="GitHub.VisualStudio.UI.Views.Controls.CreateRepoControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="432" d:DesignWidth="354"
|
||||
Background="White">
|
||||
|
||||
<d:DesignProperties.DataContext>
|
||||
<Binding>
|
||||
<Binding.Source>
|
||||
<sampleData:CreateRepoViewModelDesigner />
|
||||
</Binding.Source>
|
||||
</Binding>
|
||||
</d:DesignProperties.DataContext>
|
||||
|
||||
<StackPanel>
|
||||
<ui:OcticonImage Icon="logo_github" Width="64" Height="64"/>
|
||||
<ui:HorizontalShadowDivider Width="120"/>
|
||||
|
||||
<Grid
|
||||
Margin="0,12,0,0"
|
||||
FocusManager.IsFocusScope="True"
|
||||
x:Name="loginStackPanel"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
>
|
||||
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Margin" Value="0,3,0,3" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Label}">
|
||||
<Setter Property="Foreground" Value="{StaticResource GHTextBrush}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="Margin" Value="0,0,10,0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ui:PromptTextBox}" BasedOn="{StaticResource RoundedPromptTextBox}">
|
||||
<Setter Property="Margin" Value="0,5" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ui:AppendingPathTextBox}" BasedOn="{StaticResource GitHubTextBox}">
|
||||
<Setter Property="Margin" Value="0,5" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource GitHubBlueLinkButton}">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type uirx:UserErrorMessages}" BasedOn="{StaticResource {x:Type uirx:UserErrorMessages}}">
|
||||
<Setter Property="IconMargin" Value="-1,2,7,0" />
|
||||
<Setter Property="ErrorMessageFontWeight" Value="Normal" />
|
||||
<Setter Property="Icon" Value="alert" />
|
||||
<Setter Property="Fill" Value="#f39c12" />
|
||||
<Setter Property="Margin" Value="0,5,0,0"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="0" Target="{Binding ElementName=nameText}">Name</Label>
|
||||
<ui:PromptTextBox x:Name="nameText" Grid.Column="1" Grid.Row="0" MaxLength="{x:Static GitHub:Constants.MaxRepositoryNameLength}"/>
|
||||
|
||||
<StackPanel Grid.Column="1" Grid.Row="1">
|
||||
<uirx:ValidationMessage x:Name="nameValidationMessage" ValidatesControl="{Binding ElementName=nameText}"/>
|
||||
<uirx:ValidationMessage x:Name="safeRepositoryNameWarning" ValidatesControl="{Binding ElementName=nameText}" Icon="alert" Fill="#f39c12" Padding="2,0" />
|
||||
</StackPanel>
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="2" Target="{Binding ElementName=description}">Description</Label>
|
||||
<ui:PromptTextBox x:Name="description" Grid.Column="1" Grid.Row="2"/>
|
||||
|
||||
<!-- Custom margin for text box labels, text inside textboxes are offset -->
|
||||
<Label Grid.Column="0" Grid.Row="3" Target="{Binding ElementName=localPathText}">Local path</Label>
|
||||
<Grid Grid.Column="1" Grid.Row="3">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:AppendingPathTextBox x:Name="localPathText" Grid.Column="0" Grid.Row="0" />
|
||||
<Button x:Name="browsePathButton" Grid.Column="1" Grid.Row="0" Margin="3,0,0,0">Browse</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
<uirx:ValidationMessage x:Name="pathValidationMessage" Grid.Column="1" Grid.Row="4" ValidatesControl="{Binding ElementName=localPathText}"/>
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="5" Target="{Binding ElementName=ignoreTemplateList}">Git ignore</Label>
|
||||
<uirx:FilteredComboBox x:Name="ignoreTemplateList" Grid.Column="1" Grid.Row="5" />
|
||||
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="6" Target="{Binding ElementName=licenseList}">License</Label>
|
||||
<uirx:FilteredComboBox x:Name="licenseList" Grid.Column="1" Grid.Row="6" />
|
||||
|
||||
|
||||
<uirx:UserErrorMessages x:Name="userErrorMessages" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="7" />
|
||||
|
||||
<ui:HorizontalShadowDivider Width="120" Grid.Row="8" Grid.ColumnSpan="2" Margin="0,12,0,12"/>
|
||||
|
||||
<ui:GitHubComboBox x:Name="accountsComboBox" Grid.Column="1" Grid.Row="9">
|
||||
<ComboBox.ItemsSource>
|
||||
<x:Array Type="sampleData:AccountDesigner">
|
||||
<sampleData:AccountDesigner Login="Brendan" />
|
||||
<sampleData:AccountDesigner Login="Drew"/>
|
||||
<sampleData:AccountDesigner Login="Paul"/>
|
||||
</x:Array>
|
||||
</ComboBox.ItemsSource>
|
||||
</ui:GitHubComboBox>
|
||||
<uirx:ValidationMessage x:Name="accountValidationMessage" Grid.Column="1" Grid.Row="10" ValidatesControl="{Binding ElementName=nameText}"/>
|
||||
|
||||
|
||||
<CheckBox x:Name="makePrivate" Grid.Column="1" Grid.Row="11" Content="Private Repository" Padding="10,0,0,0"/>
|
||||
<Grid Grid.Column="1" Grid.Row="12">
|
||||
<Label x:Name="upgradeToMicroPlanWarning" HorizontalAlignment="Left" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
|
||||
<TextBlock>Private repositories require a</TextBlock>
|
||||
<ui:OcticonLinkButton
|
||||
x:Name="upgradeToMicroLink"
|
||||
FontSize="12"
|
||||
Icon="link_external"
|
||||
Content="micro plan"
|
||||
ToolTip="upgrade"
|
||||
Margin="5,0"/>
|
||||
<TextBlock>for $7/month</TextBlock>
|
||||
</StackPanel>
|
||||
</Label>
|
||||
<Label x:Name="upgradePlanWarning" HorizontalAlignment="Left" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
|
||||
<TextBlock>You're using
|
||||
<Run x:Name="ownedPrivateReposText"/>/<Run x:Name="privateReposInPlanText"/>
|
||||
private repositories.
|
||||
</TextBlock>
|
||||
<ui:OcticonLinkButton
|
||||
x:Name="upgradeAccountLink"
|
||||
FontSize="12"
|
||||
Icon="link_external"
|
||||
Content="Manage plans"
|
||||
ToolTip="manage plans"
|
||||
Margin="5,0"/>
|
||||
</StackPanel>
|
||||
</Label>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="createButtonPane" Height="70" Background="#FCFCFC">
|
||||
|
||||
<Rectangle Height="1" VerticalAlignment="Top" Fill="{StaticResource GHBoxDividerBrush}" />
|
||||
|
||||
<ui:OcticonCircleButton
|
||||
IsDefault="True"
|
||||
x:Name="createRepositoryButton"
|
||||
HorizontalAlignment="Center"
|
||||
Icon="check">
|
||||
<TextBlock>Create repository</TextBlock>
|
||||
</ui:OcticonCircleButton>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for CloneRepoControl.xaml
|
||||
/// </summary>
|
||||
[Export(typeof(IViewFor<ICreateRepoViewModel>))]
|
||||
public partial class CreateRepoControl : IViewFor<ICreateRepoViewModel>
|
||||
{
|
||||
public CreateRepoControl()
|
||||
{
|
||||
SharedDictionaryManager.Load("GitHub.UI");
|
||||
SharedDictionaryManager.Load("GitHub.UI.Reactive");
|
||||
Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
IObservable<bool> 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<PublishRepositoryUserError>(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); }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Assets/Styles.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Assets/Controls.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Assets/Buttons.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Assets/TextBlocks.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/GitHub.UI.Reactive;component/Assets/Controls.xaml" />
|
||||
<ResourceDictionary Source="pack://application,,,/GitHub.UI;component/SharedDictionary.xaml" />
|
||||
<ResourceDictionary Source="pack://application,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LoginControl.xaml
|
||||
/// </summary>
|
||||
public partial class LoginControl : IViewFor<ILoginDialog>
|
||||
[Export(typeof(IViewFor<ILoginViewModel>))]
|
||||
public partial class LoginControl : IViewFor<ILoginViewModel>
|
||||
{
|
||||
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); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<UserControl x:Class="GitHub.VisualStudio.UI.Views.Controls.TwoFactorControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"
|
||||
xmlns:pfui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.12.0"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="1.5*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Fill="{DynamicResource GitHubModalBackgroundFill}" IsHitTestVisible="False" />
|
||||
<Rectangle Grid.Row="1" Fill="{DynamicResource GitHubModalBackgroundFill}" />
|
||||
<Border Grid.Row="2" Background="{DynamicResource GitHubLightModalViewBackground}">
|
||||
<Grid Grid.Row="2" HorizontalAlignment="Center" MaxWidth="650" MinWidth="450">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:OcticonPath
|
||||
Stretch="Uniform"
|
||||
Fill="#90000000"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,71,0,0"
|
||||
Height="64"
|
||||
Icon="device_mobile"/>
|
||||
<StackPanel Grid.Column="1" HorizontalAlignment="Left" Margin="24,50,24,0">
|
||||
<WrapPanel Orientation="Horizontal" Margin="0,12,0,0" >
|
||||
<TextBlock
|
||||
Text="Two-factor authentication"
|
||||
Padding="0"
|
||||
Margin="0,0,12,0"
|
||||
Style="{DynamicResource GitHubH1TextBlock}"
|
||||
Foreground="{DynamicResource GitHubLightModalViewTextBrush}"/>
|
||||
<TextBlock
|
||||
Name="authenticationSentLabel"
|
||||
Text="authentication code sent!"
|
||||
Padding="0"
|
||||
Margin="0"
|
||||
Style="{DynamicResource GitHubH1TextBlock}"
|
||||
Foreground="{DynamicResource GitHubAccentBrush}" />
|
||||
</WrapPanel>
|
||||
<TextBlock
|
||||
Name="description"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,0,0,6"
|
||||
Text="Open the two-factor authentication app on your device to view your authentication code."
|
||||
Style="{DynamicResource GitHubDescriptionTextBlock}"
|
||||
Foreground="{DynamicResource GitHubLightModalViewTextBrush}"/>
|
||||
<ui:OcticonLinkButton
|
||||
x:Name="helpButton"
|
||||
Grid.Row="2"
|
||||
Margin="0,0,0,6"
|
||||
Icon="link_external"
|
||||
Foreground="{DynamicResource GitHubLightModalViewTextBrush}"
|
||||
ToolTip="Learn more about two-factor authentication on GitHub"
|
||||
Content="Read more" />
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="3">
|
||||
<ui:PromptTextBox
|
||||
Name="authenticationCode"
|
||||
MaxLength="6"
|
||||
TabIndex="1"
|
||||
PromptText="Authentication code"
|
||||
Margin="0"
|
||||
Style="{DynamicResource RoundedPromptTextBox}"
|
||||
VerticalAlignment="Stretch"
|
||||
d:LayoutOverrides="Height"
|
||||
Text="{Binding AuthenticationCode, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
|
||||
Validation.ErrorTemplate="{DynamicResource ValidationAdorner}"
|
||||
MinWidth="164"
|
||||
MaxWidth="164" />
|
||||
<ui:OcticonLinkButton
|
||||
x:Name="resendCodeButton"
|
||||
ToolTip="Send the code to your registered SMS Device again"
|
||||
FontSize="12"
|
||||
Icon="sync"
|
||||
Margin="4,0,0,0"
|
||||
Content="_Resend" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="4"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
Margin="0,24,24,60">
|
||||
<ui:OcticonCircleButton
|
||||
x:Name="okButton"
|
||||
TabIndex="2"
|
||||
Icon="check"
|
||||
IsDefault="True"
|
||||
Content="Log in"
|
||||
Margin="0" />
|
||||
<ui:OcticonCircleButton
|
||||
x:Name="cancelButton"
|
||||
IsCancel="True"
|
||||
Margin="12,0,0,0"
|
||||
Icon="x"
|
||||
Content="Cancel" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Rectangle Grid.Row="3" Fill="{DynamicResource GitHubModalBackgroundFill}" />
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for PasswordView.xaml
|
||||
/// </summary>
|
||||
[Export(typeof(IViewFor<ITwoFactorViewModel>))]
|
||||
public partial class TwoFactorControl : IViewFor<ITwoFactorViewModel>
|
||||
{
|
||||
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<KeyEventArgs>()
|
||||
.Where(x => ViewModel.IsShowing && x.Key == Key.Escape && !x.Handled)
|
||||
.Subscribe(async key =>
|
||||
{
|
||||
key.Handled = true;
|
||||
await ((ReactiveCommand<object>)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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<pfui:DialogWindow x:Class="GitHub.VisualStudio.UI.Views.WindowController"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views"
|
||||
xmlns:ctl="clr-namespace:GitHub.VisualStudio.UI.Views.Controls"
|
||||
xmlns:pfui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.12.0"
|
||||
mc:Ignorable="d"
|
||||
Title="WindowHolder" Height="655" Width="519" HasMinimizeButton="False" HasMaximizeButton="False">
|
||||
<Grid x:Name="Container">
|
||||
<ctl:CreateRepoControl />
|
||||
</Grid>
|
||||
</pfui:DialogWindow>
|
|
@ -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<object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче