Skip to content

Commit b141c83

Browse files
committed
ui: add Avalonia-based UI for generic cred prompt
Add an Avalonia-based UI prompt for the generic/basic credential prompt.
1 parent 1be714c commit b141c83

File tree

9 files changed

+310
-1
lines changed

9 files changed

+310
-1
lines changed

Git-Credential-Manager.sln

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitLab.UI.Avalonia", "src\s
6767
EndProject
6868
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitLab.UI.Windows", "src\windows\GitLab.UI.Windows\GitLab.UI.Windows.csproj", "{83EAC1F9-8E1F-41FC-8FC9-2C452452D64E}"
6969
EndProject
70+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Git-Credential-Manager.UI.Avalonia", "src\shared\Git-Credential-Manager.UI.Avalonia\Git-Credential-Manager.UI.Avalonia.csproj", "{35659127-8859-4DB9-8DD6-A08C1952632E}"
71+
EndProject
7072
Global
7173
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7274
Debug|Any CPU = Debug|Any CPU
@@ -469,6 +471,22 @@ Global
469471
{83EAC1F9-8E1F-41FC-8FC9-2C452452D64E}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU
470472
{83EAC1F9-8E1F-41FC-8FC9-2C452452D64E}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU
471473
{83EAC1F9-8E1F-41FC-8FC9-2C452452D64E}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU
474+
{35659127-8859-4DB9-8DD6-A08C1952632E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
475+
{35659127-8859-4DB9-8DD6-A08C1952632E}.Debug|Any CPU.Build.0 = Debug|Any CPU
476+
{35659127-8859-4DB9-8DD6-A08C1952632E}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU
477+
{35659127-8859-4DB9-8DD6-A08C1952632E}.MacDebug|Any CPU.Build.0 = Debug|Any CPU
478+
{35659127-8859-4DB9-8DD6-A08C1952632E}.Release|Any CPU.ActiveCfg = Release|Any CPU
479+
{35659127-8859-4DB9-8DD6-A08C1952632E}.Release|Any CPU.Build.0 = Release|Any CPU
480+
{35659127-8859-4DB9-8DD6-A08C1952632E}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU
481+
{35659127-8859-4DB9-8DD6-A08C1952632E}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU
482+
{35659127-8859-4DB9-8DD6-A08C1952632E}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU
483+
{35659127-8859-4DB9-8DD6-A08C1952632E}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU
484+
{35659127-8859-4DB9-8DD6-A08C1952632E}.LinuxRelease|Any CPU.ActiveCfg = Release|Any CPU
485+
{35659127-8859-4DB9-8DD6-A08C1952632E}.LinuxRelease|Any CPU.Build.0 = Release|Any CPU
486+
{35659127-8859-4DB9-8DD6-A08C1952632E}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU
487+
{35659127-8859-4DB9-8DD6-A08C1952632E}.MacRelease|Any CPU.Build.0 = Release|Any CPU
488+
{35659127-8859-4DB9-8DD6-A08C1952632E}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU
489+
{35659127-8859-4DB9-8DD6-A08C1952632E}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU
472490
EndGlobalSection
473491
GlobalSection(SolutionProperties) = preSolution
474492
HideSolutionNode = FALSE
@@ -505,6 +523,7 @@ Global
505523
{9AFD88E2-7E2C-46DA-9D38-4342086426D3} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
506524
{47186A50-8889-4FC7-8A05-F9FCE7F8F4AE} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
507525
{83EAC1F9-8E1F-41FC-8FC9-2C452452D64E} = {66722747-1B61-40E4-A89B-1AC8E6D62EA9}
526+
{35659127-8859-4DB9-8DD6-A08C1952632E} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
508527
EndGlobalSection
509528
GlobalSection(ExtensibilityGlobals) = postSolution
510529
SolutionGuid = {0EF9FC65-E6BA-45D4-A455-262A9EA4366B}

src/shared/Core.UI.Avalonia/Controls/DialogWindow.axaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
ExtendClientAreaChromeHints="{Binding ShowCustomChrome, Converter={x:Static converters:WindowClientAreaConverters.BoolToChromeHints}}"
1111
Title="{Binding Title}"
1212
SizeToContent="Height" CanResize="False"
13-
Width="420" MaxHeight="520" MinHeight="320"
13+
Width="420" MaxHeight="520" MinHeight="280"
1414
WindowState="Normal" WindowStartupLocation="CenterScreen"
1515
ShowInTaskbar="True" ShowActivated="True"
1616
PointerPressed="Window_PointerPressed"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using GitCredentialManager.UI.ViewModels;
4+
using GitCredentialManager.UI.Views;
5+
6+
namespace GitCredentialManager.UI.Commands
7+
{
8+
public class CredentialsCommandImpl : CredentialsCommand
9+
{
10+
public CredentialsCommandImpl(ICommandContext context) : base(context) { }
11+
12+
protected override Task ShowAsync(CredentialsViewModel viewModel, CancellationToken ct)
13+
{
14+
return AvaloniaUi.ShowViewAsync<CredentialsView>(viewModel, GetParentHandle(), ct);
15+
}
16+
}
17+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<Window xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
x:Class="GitCredentialManager.UI.Controls.TesterWindow"
4+
CanResize="False" Width="550" SizeToContent="Height"
5+
Title="Git Credential Manager Dialog Tester">
6+
<TabControl Margin="5,10">
7+
<TabControl.Styles>
8+
<Style Selector="TabItem">
9+
<Setter Property="MinHeight" Value="30" />
10+
<Setter Property="FontSize" Value="12"/>
11+
</Style>
12+
<Style Selector="TabItem > StackPanel">
13+
<Setter Property="Margin" Value="0,10,0,0"/>
14+
</Style>
15+
<Style Selector="TabItem > StackPanel > Grid > Label">
16+
<Setter Property="VerticalAlignment" Value="Center" />
17+
<Setter Property="Margin" Value="0,0,10,0"/>
18+
</Style>
19+
<Style Selector="TabItem > StackPanel > Button">
20+
<Setter Property="Margin" Value="0,10,0,0" />
21+
<Setter Property="Padding" Value="14,10" />
22+
<Setter Property="HorizontalAlignment" Value="Right" />
23+
</Style>
24+
</TabControl.Styles>
25+
<TabItem Header="Basic">
26+
<StackPanel>
27+
<Grid RowDefinitions="Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*">
28+
<Label Grid.Row="0" Grid.Column="0"
29+
Content="Window Title" />
30+
<TextBox Grid.Row="0" Grid.Column="1"
31+
x:Name="title" Text="Git Credential Manager" />
32+
<Label Grid.Row="1" Grid.Column="0"
33+
Content="Description" />
34+
<TextBox Grid.Row="1" Grid.Column="1"
35+
x:Name="description" Text="Enter credentials for 'https://example.com'" />
36+
<Label Grid.Row="2" Grid.Column="0"
37+
Content="Username" />
38+
<TextBox Grid.Row="2" Grid.Column="1"
39+
x:Name="username" />
40+
<Label Grid.Row="3" Grid.Column="0"
41+
Content="Show Logo" />
42+
<CheckBox Grid.Row="3" Grid.Column="1"
43+
x:Name="showLogo" IsChecked="True" />
44+
</Grid>
45+
<Button Classes="accent" Content="Show" Click="ShowBasic" />
46+
</StackPanel>
47+
</TabItem>
48+
</TabControl>
49+
</Window>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using Avalonia;
3+
using Avalonia.Controls;
4+
using Avalonia.Interactivity;
5+
using Avalonia.Markup.Xaml;
6+
using GitCredentialManager.UI.ViewModels;
7+
using GitCredentialManager.UI.Views;
8+
9+
namespace GitCredentialManager.UI.Controls
10+
{
11+
public class TesterWindow : Window
12+
{
13+
public TesterWindow()
14+
{
15+
InitializeComponent();
16+
#if DEBUG
17+
this.AttachDevTools();
18+
#endif
19+
}
20+
21+
private void InitializeComponent()
22+
{
23+
AvaloniaXamlLoader.Load(this);
24+
}
25+
26+
private void ShowBasic(object sender, RoutedEventArgs e)
27+
{
28+
var vm = new CredentialsViewModel
29+
{
30+
Title = this.FindControl<TextBox>("title").Text,
31+
Description = this.FindControl<TextBox>("description").Text,
32+
UserName = this.FindControl<TextBox>("username").Text,
33+
ShowProductHeader = this.FindControl<CheckBox>("showLogo").IsChecked ?? false
34+
};
35+
var view = new CredentialsView();
36+
var window = new DialogWindow(view) {DataContext = vm};
37+
window.ShowDialog(this);
38+
}
39+
}
40+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<RuntimeIdentifiers>osx-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
7+
<AssemblyName>git-credential-manager-ui</AssemblyName>
8+
<RootNamespace>GitCredentialManager.UI</RootNamespace>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\Core.UI.Avalonia\Core.UI.Avalonia.csproj" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<Compile Update="Views\CredentialsView.axaml.cs">
17+
<DependentUpon>CredentialsView.axaml</DependentUpon>
18+
<SubType>Code</SubType>
19+
</Compile>
20+
<Compile Update="Controls\TesterWindow.axaml.cs">
21+
<DependentUpon>TesterWindow.axaml</DependentUpon>
22+
<SubType>Code</SubType>
23+
</Compile>
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Threading;
3+
using Avalonia;
4+
using GitCredentialManager.UI.Commands;
5+
using GitCredentialManager.UI.Controls;
6+
7+
namespace GitCredentialManager.UI
8+
{
9+
public static class Program
10+
{
11+
public static void Main(string[] args)
12+
{
13+
// If we have no arguments then just start the app with the test window.
14+
if (args.Length == 0)
15+
{
16+
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
17+
return;
18+
}
19+
20+
// Create the dispatcher on the main thread. This is required
21+
// for some platform UI services such as macOS that mandates
22+
// all controls are created/accessed on the initial thread
23+
// created by the process (the process entry thread).
24+
Dispatcher.Initialize();
25+
26+
// Run AppMain in a new thread and keep the main thread free
27+
// to process the dispatcher's job queue.
28+
var appMain = new Thread(AppMain) {Name = nameof(AppMain)};
29+
appMain.Start(args);
30+
31+
// Process the dispatcher job queue (aka: message pump, run-loop, etc...)
32+
// We must ensure to run this on the same thread that it was created on
33+
// (the main thread) so we cannot use any async/await calls between
34+
// Dispatcher.Create and Run.
35+
Dispatcher.MainThread.Run();
36+
37+
// Execution should never reach here as AppMain terminates the process on completion.
38+
throw new InvalidOperationException("Main dispatcher job queue shutdown unexpectedly");
39+
}
40+
41+
private static void AppMain(object o)
42+
{
43+
string[] args = (string[]) o;
44+
45+
string appPath = ApplicationBase.GetEntryApplicationPath();
46+
using (var context = new CommandContext(appPath))
47+
using (var app = new HelperApplication(context))
48+
{
49+
app.RegisterCommand(new CredentialsCommandImpl(context));
50+
51+
int exitCode = app.RunAsync(args)
52+
.ConfigureAwait(false)
53+
.GetAwaiter()
54+
.GetResult();
55+
56+
Environment.Exit(exitCode);
57+
}
58+
}
59+
60+
public static AppBuilder BuildAvaloniaApp()
61+
=> AppBuilder.Configure(() => new AvaloniaApp(() => new TesterWindow()))
62+
.UsePlatformDetect()
63+
.LogToTrace();
64+
}
65+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<UserControl 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+
xmlns:vm="clr-namespace:GitCredentialManager.UI.ViewModels;assembly=gcmcoreui"
6+
xmlns:converters="clr-namespace:GitCredentialManager.UI.Converters;assembly=gcmcoreuiavn"
7+
mc:Ignorable="d" d:DesignWidth="420"
8+
x:Class="GitCredentialManager.UI.Views.CredentialsView">
9+
<Design.DataContext>
10+
<vm:CredentialsViewModel/>
11+
</Design.DataContext>
12+
<DockPanel>
13+
<StackPanel DockPanel.Dock="Top" Margin="10,0,10,10">
14+
<StackPanel Margin="0"
15+
Orientation="Horizontal"
16+
HorizontalAlignment="Center"
17+
IsVisible="{Binding ShowProductHeader}">
18+
<Image Margin="0,0,10,0"
19+
VerticalAlignment="Center"
20+
Source="{StaticResource GcmLogo}"
21+
Width="32" Height="32" />
22+
<TextBlock Text="Git Credential Manager"
23+
VerticalAlignment="Center"
24+
FontSize="18"
25+
FontWeight="Light"/>
26+
</StackPanel>
27+
28+
<TextBlock Text="{Binding Description}"
29+
FontSize="14"
30+
HorizontalAlignment="Center"
31+
TextWrapping="Wrap"
32+
Margin="0,15,0,15"/>
33+
</StackPanel>
34+
35+
<StackPanel Margin="20,0">
36+
<TextBox x:Name="userNameTextBox"
37+
Watermark="Username" Margin="0,0,0,10"
38+
Text="{Binding UserName}"/>
39+
<TextBox x:Name="passwordTextBox"
40+
Watermark="Password" Margin="0,0,0,20"
41+
PasswordChar=""
42+
Text="{Binding Password}"/>
43+
<Button Content="Continue"
44+
IsDefault="True"
45+
Command="{Binding SignInCommand}"
46+
HorizontalAlignment="Center"
47+
Classes="accent"/>
48+
</StackPanel>
49+
</DockPanel>
50+
</UserControl>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Markup.Xaml;
3+
using GitCredentialManager.UI.Controls;
4+
using GitCredentialManager.UI.ViewModels;
5+
6+
namespace GitCredentialManager.UI.Views
7+
{
8+
public class CredentialsView : UserControl, IFocusable
9+
{
10+
private TextBox _userNameTextBox;
11+
private TextBox _passwordTextBox;
12+
13+
public CredentialsView()
14+
{
15+
InitializeComponent();
16+
}
17+
18+
private void InitializeComponent()
19+
{
20+
AvaloniaXamlLoader.Load(this);
21+
22+
_userNameTextBox = this.FindControl<TextBox>("userNameTextBox");
23+
_passwordTextBox = this.FindControl<TextBox>("passwordTextBox");
24+
}
25+
26+
public void SetFocus()
27+
{
28+
if (!(DataContext is CredentialsViewModel vm))
29+
{
30+
return;
31+
}
32+
33+
if (string.IsNullOrWhiteSpace(vm.UserName))
34+
{
35+
_userNameTextBox.Focus();
36+
}
37+
else
38+
{
39+
_passwordTextBox.Focus();
40+
}
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)