Skip to content

Commit bfe20a9

Browse files
committed
bitbucket: add Avalonia-based UI helper for BB
Add a new UI helper for Bitbucket using Avalonia.
1 parent 1047184 commit bfe20a9

File tree

14 files changed

+604
-0
lines changed

14 files changed

+604
-0
lines changed

Git-Credential-Manager.sln

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "linux", "linux", "{8F9D7E67
7575
EndProject
7676
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.UI", "src\shared\GitHub.UI\GitHub.UI.csproj", "{B5F00B46-FE93-45F2-B283-52B74B3E13B9}"
7777
EndProject
78+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atlassian.Bitbucket.UI", "src\shared\Atlassian.Bitbucket.UI\Atlassian.Bitbucket.UI.csproj", "{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}"
79+
EndProject
7880
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Git.CredentialManager.UI", "src\shared\Microsoft.Git.CredentialManager.UI\Microsoft.Git.CredentialManager.UI.csproj", "{001846B0-462B-4A27-90CD-2435D4C0F680}"
7981
EndProject
8082
Global
@@ -335,6 +337,22 @@ Global
335337
{B5F00B46-FE93-45F2-B283-52B74B3E13B9}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU
336338
{B5F00B46-FE93-45F2-B283-52B74B3E13B9}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU
337339
{B5F00B46-FE93-45F2-B283-52B74B3E13B9}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU
340+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
341+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
342+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU
343+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.MacDebug|Any CPU.Build.0 = Debug|Any CPU
344+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU
345+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.MacRelease|Any CPU.Build.0 = Release|Any CPU
346+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
347+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.Release|Any CPU.Build.0 = Release|Any CPU
348+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU
349+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU
350+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU
351+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU
352+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU
353+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU
354+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU
355+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU
338356
{001846B0-462B-4A27-90CD-2435D4C0F680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
339357
{001846B0-462B-4A27-90CD-2435D4C0F680}.Debug|Any CPU.Build.0 = Debug|Any CPU
340358
{001846B0-462B-4A27-90CD-2435D4C0F680}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -378,6 +396,7 @@ Global
378396
{8F9D7E67-7DD7-4E32-9134-423281AF00E9} = {A7FC1234-95E3-4496-B5F7-4306F41E6A0E}
379397
{AD2A935F-3720-4802-8119-6A9B35B254DF} = {8F9D7E67-7DD7-4E32-9134-423281AF00E9}
380398
{B5F00B46-FE93-45F2-B283-52B74B3E13B9} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
399+
{EB1AA840-6FFF-4464-A9B2-0AEA36F615EA} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
381400
{001846B0-462B-4A27-90CD-2435D4C0F680} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
382401
EndGlobalSection
383402
GlobalSection(ExtensibilityGlobals) = postSolution
13.1 KB
Loading
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<TargetFramework>net5.0</TargetFramework>
6+
<RuntimeIdentifiers>osx-x64;linux-x64</RuntimeIdentifiers>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\Atlassian.Bitbucket\Atlassian.Bitbucket.csproj" />
11+
<ProjectReference Include="..\Microsoft.Git.CredentialManager.UI\Microsoft.Git.CredentialManager.UI.csproj" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<Compile Update="Controls\TesterWindow.axaml.cs">
16+
<DependentUpon>TesterWindow.axaml</DependentUpon>
17+
<SubType>Code</SubType>
18+
</Compile>
19+
<AvaloniaResource Include="Assets\**" />
20+
</ItemGroup>
21+
22+
</Project>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.CommandLine;
4+
using System.CommandLine.Invocation;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Atlassian.Bitbucket.UI.ViewModels;
8+
using Atlassian.Bitbucket.UI.Views;
9+
using Microsoft.Git.CredentialManager;
10+
using Microsoft.Git.CredentialManager.UI;
11+
12+
namespace Atlassian.Bitbucket.UI.Commands
13+
{
14+
internal class CredentialsCommand : HelperCommand
15+
{
16+
public CredentialsCommand(CommandContext context)
17+
: base(context, "userpass", "Show authentication prompt.")
18+
{
19+
AddOption(
20+
new Option<string>("--username", "Username or email.")
21+
);
22+
23+
Handler = CommandHandler.Create<string>(ExecuteAsync);
24+
}
25+
26+
private async Task<int> ExecuteAsync(string userName)
27+
{
28+
var viewModel = new CredentialsViewModel(Context.Environment)
29+
{
30+
UserName = userName
31+
};
32+
33+
await AvaloniaUi.ShowViewAsync<CredentialsView>(viewModel, GetParentHandle(), CancellationToken.None);
34+
35+
if (!viewModel.WindowResult)
36+
{
37+
throw new Exception("User cancelled dialog.");
38+
}
39+
40+
WriteResult(new Dictionary<string, string>
41+
{
42+
["username"] = viewModel.UserName,
43+
["password"] = viewModel.Password,
44+
});
45+
46+
return 0;
47+
}
48+
}
49+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.CommandLine;
4+
using System.CommandLine.Invocation;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Atlassian.Bitbucket.UI.ViewModels;
8+
using Atlassian.Bitbucket.UI.Views;
9+
using Microsoft.Git.CredentialManager;
10+
using Microsoft.Git.CredentialManager.UI;
11+
12+
namespace Atlassian.Bitbucket.UI.Commands
13+
{
14+
internal class OAuthCommand : HelperCommand
15+
{
16+
public OAuthCommand(CommandContext context)
17+
: base(context, "oauth", "Show OAuth required prompt.")
18+
{
19+
Handler = CommandHandler.Create(ExecuteAsync);
20+
}
21+
22+
private async Task<int> ExecuteAsync()
23+
{
24+
var viewModel = new OAuthViewModel(Context.Environment);
25+
await AvaloniaUi.ShowViewAsync<OAuthView>(viewModel, GetParentHandle(), CancellationToken.None);
26+
27+
if (!viewModel.WindowResult)
28+
{
29+
throw new Exception("User cancelled dialog.");
30+
}
31+
32+
WriteResult(new Dictionary<string, string>
33+
{
34+
["continue"] = "true"
35+
});
36+
37+
return 0;
38+
}
39+
}
40+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Window xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
6+
x:Class="Atlassian.Bitbucket.UI.Controls.TesterWindow"
7+
Title="Bitbucket Authentication Dialog Tester"
8+
Height="240" Width="420" CanResize="False">
9+
<DockPanel>
10+
<Button Content="Show Credentials Dialog" Padding="10" Click="ShowCredentials" />
11+
<Button Content="Show OAuth Dialog" Padding="10" Click="ShowOAuth" />
12+
</DockPanel>
13+
</Window>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using Atlassian.Bitbucket.UI.ViewModels;
2+
using Atlassian.Bitbucket.UI.Views;
3+
using Avalonia;
4+
using Avalonia.Controls;
5+
using Avalonia.Interactivity;
6+
using Avalonia.Markup.Xaml;
7+
using Microsoft.Git.CredentialManager;
8+
using Microsoft.Git.CredentialManager.Interop.Linux;
9+
using Microsoft.Git.CredentialManager.Interop.MacOS;
10+
using Microsoft.Git.CredentialManager.Interop.Posix;
11+
using Microsoft.Git.CredentialManager.Interop.Windows;
12+
using Microsoft.Git.CredentialManager.UI.Controls;
13+
14+
namespace Atlassian.Bitbucket.UI.Controls
15+
{
16+
public class TesterWindow : Window
17+
{
18+
private readonly IEnvironment _environment;
19+
20+
public TesterWindow()
21+
{
22+
InitializeComponent();
23+
#if DEBUG
24+
this.AttachDevTools();
25+
#endif
26+
27+
if (PlatformUtils.IsWindows())
28+
{
29+
_environment = new WindowsEnvironment(new WindowsFileSystem());
30+
}
31+
else
32+
{
33+
IFileSystem fs;
34+
if (PlatformUtils.IsMacOS())
35+
{
36+
fs = new MacOSFileSystem();
37+
}
38+
else
39+
{
40+
fs = new LinuxFileSystem();
41+
}
42+
43+
_environment = new PosixEnvironment(fs);
44+
}
45+
}
46+
47+
private void InitializeComponent()
48+
{
49+
AvaloniaXamlLoader.Load(this);
50+
}
51+
52+
private void ShowCredentials(object sender, RoutedEventArgs e)
53+
{
54+
var vm = new CredentialsViewModel(_environment);
55+
var view = new CredentialsView();
56+
var window = new DialogWindow(view) {DataContext = vm};
57+
window.ShowDialog(this);
58+
}
59+
60+
private void ShowOAuth(object sender, RoutedEventArgs e)
61+
{
62+
var vm = new OAuthViewModel(_environment);
63+
var view = new OAuthView();
64+
var window = new DialogWindow(view) {DataContext = vm};
65+
window.ShowDialog(this);
66+
}
67+
}
68+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.CommandLine;
3+
using System.Threading;
4+
using Atlassian.Bitbucket.UI.Commands;
5+
using Atlassian.Bitbucket.UI.Controls;
6+
using Avalonia;
7+
using Microsoft.Git.CredentialManager;
8+
using Microsoft.Git.CredentialManager.UI;
9+
10+
namespace Atlassian.Bitbucket.UI
11+
{
12+
public static class Program
13+
{
14+
public static void Main(string[] args)
15+
{
16+
// If we have no arguments then just start the app with the test window.
17+
if (args.Length == 0)
18+
{
19+
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
20+
return;
21+
}
22+
23+
// Create the dispatcher on the main thread. This is required
24+
// for some platform UI services such as macOS that mandates
25+
// all controls are created/accessed on the initial thread
26+
// created by the process (the process entry thread).
27+
Dispatcher.Initialize();
28+
29+
// Run AppMain in a new thread and keep the main thread free
30+
// to process the dispatcher's job queue.
31+
var appMain = new Thread(AppMain) {Name = nameof(AppMain)};
32+
appMain.Start(args);
33+
34+
// Process the dispatcher job queue (aka: message pump, run-loop, etc...)
35+
// We must ensure to run this on the same thread that it was created on
36+
// (the main thread) so we cannot use any async/await calls between
37+
// Dispatcher.Create and Run.
38+
Dispatcher.MainThread.Run();
39+
40+
// Execution should never reach here as AppMain terminates the process on completion.
41+
throw new InvalidOperationException("Main dispatcher job queue shutdown unexpectedly");
42+
}
43+
44+
private static void AppMain(object o)
45+
{
46+
string[] args = (string[]) o;
47+
48+
string appPath = ApplicationBase.GetEntryApplicationPath();
49+
using (var context = new CommandContext(appPath))
50+
using (var app = new HelperApplication(context))
51+
{
52+
app.RegisterCommand(new CredentialsCommand(context));
53+
app.RegisterCommand(new OAuthCommand(context));
54+
55+
// Run!
56+
int exitCode = app.RunAsync(args)
57+
.ConfigureAwait(false)
58+
.GetAwaiter()
59+
.GetResult();
60+
61+
Environment.Exit(exitCode);
62+
}
63+
}
64+
65+
public static AppBuilder BuildAvaloniaApp()
66+
=> AppBuilder.Configure(() => new AvaloniaApp(() => new TesterWindow()))
67+
.UsePlatformDetect()
68+
.LogToTrace();
69+
}
70+
}

0 commit comments

Comments
 (0)