Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit641f1bc

Browse files
feat: Sign in Window views & view models (#20)
Adds views for the Sign In dialog window, and a backing view model.Co-authored-by: Dean Sheather <dean@deansheather.com>
1 parent88a4a97 commit641f1bc

11 files changed

+470
-7
lines changed

‎App/App.xaml.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ namespace Coder.Desktop.App;
1212
publicpartialclassApp:Application
1313
{
1414
privatereadonlyIServiceProvider_services;
15-
privateTrayWindow?_trayWindow;
1615
privatereadonlybool_handleClosedEvents=true;
1716

1817
publicApp()
@@ -21,6 +20,11 @@ public App()
2120
services.AddSingleton<ICredentialManager,CredentialManager>();
2221
services.AddSingleton<IRpcController,RpcController>();
2322

23+
// SignInWindow views and view models
24+
services.AddTransient<SignInViewModel>();
25+
services.AddTransient<SignInWindow>();
26+
27+
// TrayWindow views and view models
2428
services.AddTransient<TrayWindowDisconnectedViewModel>();
2529
services.AddTransient<TrayWindowDisconnectedPage>();
2630
services.AddTransient<TrayWindowLoginRequiredViewModel>();
@@ -42,14 +46,14 @@ public App()
4246

4347
protectedoverridevoidOnLaunched(LaunchActivatedEventArgsargs)
4448
{
45-
_trayWindow=_services.GetRequiredService<TrayWindow>();
46-
_trayWindow.Closed+=(sender,args)=>
49+
vartrayWindow=_services.GetRequiredService<TrayWindow>();
50+
trayWindow.Closed+=(sender,args)=>
4751
{
4852
// TODO: wire up HandleClosedEvents properly
4953
if(_handleClosedEvents)
5054
{
5155
args.Handled=true;
52-
_trayWindow.AppWindow.Hide();
56+
trayWindow.AppWindow.Hide();
5357
}
5458
};
5559
}

‎App/DisplayScale.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
usingSystem;
2+
usingSystem.Runtime.InteropServices;
3+
usingMicrosoft.UI.Xaml;
4+
usingWinRT.Interop;
5+
6+
namespaceCoder.Desktop.App;
7+
8+
/// <summary>
9+
/// A static utility class to house methods related to the visual scale of the display monitor.
10+
/// </summary>
11+
publicstaticclassDisplayScale
12+
{
13+
publicstaticdoubleWindowScale(Windowwin)
14+
{
15+
varhwnd=WindowNative.GetWindowHandle(win);
16+
vardpi=NativeApi.GetDpiForWindow(hwnd);
17+
if(dpi==0)return1;// assume scale of 1
18+
returndpi/96.0;// 96 DPI == 1
19+
}
20+
21+
publicclassNativeApi
22+
{
23+
[DllImport("user32.dll")]
24+
publicstaticexternintGetDpiForWindow(IntPtrhwnd);
25+
}
26+
}

‎App/Services/CredentialManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public async Task SetCredentials(string coderUrl, string apiToken, CancellationT
6767
try
6868
{
6969
varsdkClient=newCoderApiClient(uri);
70+
sdkClient.SetSessionToken(apiToken);
7071
// TODO: we should probably perform a version check here too,
7172
// rather than letting the service do it on Start
7273
_=awaitsdkClient.GetBuildInfo(ct);

‎App/ViewModels/SignInViewModel.cs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
usingSystem;
2+
usingSystem.Threading;
3+
usingSystem.Threading.Tasks;
4+
usingCoder.Desktop.App.Services;
5+
usingCoder.Desktop.App.Views;
6+
usingCommunityToolkit.Mvvm.ComponentModel;
7+
usingCommunityToolkit.Mvvm.Input;
8+
usingMicrosoft.UI.Xaml;
9+
10+
namespaceCoder.Desktop.App.ViewModels;
11+
12+
/// <summary>
13+
/// The View Model backing the sign in window and all its associated pages.
14+
/// </summary>
15+
publicpartialclassSignInViewModel:ObservableObject
16+
{
17+
privatereadonlyICredentialManager_credentialManager;
18+
19+
[ObservableProperty]
20+
[NotifyPropertyChangedFor(nameof(CoderUrlError))]
21+
[NotifyPropertyChangedFor(nameof(GenTokenUrl))]
22+
publicpartialstringCoderUrl{get;set;}=string.Empty;
23+
24+
[ObservableProperty]
25+
[NotifyPropertyChangedFor(nameof(CoderUrlError))]
26+
publicpartialboolCoderUrlTouched{get;set;}=false;
27+
28+
[ObservableProperty]
29+
[NotifyPropertyChangedFor(nameof(ApiTokenError))]
30+
publicpartialstringApiToken{get;set;}=string.Empty;
31+
32+
[ObservableProperty]
33+
[NotifyPropertyChangedFor(nameof(ApiTokenError))]
34+
publicpartialboolApiTokenTouched{get;set;}=false;
35+
36+
[ObservableProperty]
37+
publicpartialstring?SignInError{get;set;}=null;
38+
39+
[ObservableProperty]
40+
publicpartialboolSignInLoading{get;set;}=false;
41+
42+
publicstring?CoderUrlError=>CoderUrlTouched?_coderUrlError:null;
43+
44+
privatestring?_coderUrlError
45+
{
46+
get
47+
{
48+
if(!Uri.TryCreate(CoderUrl,UriKind.Absolute,outvaruri))
49+
return"Invalid URL";
50+
if(uri.Schemeis not"http" and not"https")
51+
return"Must be a HTTP or HTTPS URL";
52+
if(uri.PathAndQuery!="/")
53+
return"Must be a root URL with no path or query";
54+
returnnull;
55+
}
56+
}
57+
58+
publicstring?ApiTokenError=>ApiTokenTouched?_apiTokenError:null;
59+
60+
privatestring?_apiTokenError=>string.IsNullOrWhiteSpace(ApiToken)?"Invalid token":null;
61+
62+
publicUriGenTokenUrl
63+
{
64+
get
65+
{
66+
// In case somehow the URL is invalid, just default to coder.com.
67+
// The HyperlinkButton will crash the entire app if the URL is
68+
// invalid.
69+
try
70+
{
71+
varbaseUri=newUri(CoderUrl.Trim());
72+
varcliAuthUri=newUri(baseUri,"/cli-auth");
73+
returncliAuthUri;
74+
}
75+
catch
76+
{
77+
returnnewUri("https://coder.com");
78+
}
79+
}
80+
}
81+
82+
publicSignInViewModel(ICredentialManagercredentialManager)
83+
{
84+
_credentialManager=credentialManager;
85+
}
86+
87+
publicvoidCoderUrl_FocusLost(objectsender,RoutedEventArgse)
88+
{
89+
CoderUrlTouched=true;
90+
}
91+
92+
publicvoidApiToken_FocusLost(objectsender,RoutedEventArgse)
93+
{
94+
ApiTokenTouched=true;
95+
}
96+
97+
[RelayCommand]
98+
publicvoidUrlPage_Next(SignInWindowsignInWindow)
99+
{
100+
CoderUrlTouched=true;
101+
if(_coderUrlError!=null)return;
102+
signInWindow.NavigateToTokenPage();
103+
}
104+
105+
[RelayCommand]
106+
publicvoidTokenPage_Back(SignInWindowsignInWindow)
107+
{
108+
ApiToken="";
109+
signInWindow.NavigateToUrlPage();
110+
}
111+
112+
[RelayCommand]
113+
publicasyncTaskTokenPage_SignIn(SignInWindowsignInWindow)
114+
{
115+
CoderUrlTouched=true;
116+
ApiTokenTouched=true;
117+
if(_coderUrlError!=null||_apiTokenError!=null)return;
118+
119+
try
120+
{
121+
SignInLoading=true;
122+
SignInError=null;
123+
124+
varcts=newCancellationTokenSource(TimeSpan.FromSeconds(15));
125+
await_credentialManager.SetCredentials(CoderUrl.Trim(),ApiToken.Trim(),cts.Token);
126+
127+
signInWindow.Close();
128+
}
129+
catch(Exceptione)
130+
{
131+
SignInError=$"Failed to sign in:{e}";
132+
}
133+
finally
134+
{
135+
SignInLoading=false;
136+
}
137+
}
138+
}
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
1-
usingCommunityToolkit.Mvvm.ComponentModel;
1+
usingSystem;
2+
usingCoder.Desktop.App.Views;
23
usingCommunityToolkit.Mvvm.Input;
4+
usingMicrosoft.Extensions.DependencyInjection;
35

46
namespaceCoder.Desktop.App.ViewModels;
57

6-
publicpartialclassTrayWindowLoginRequiredViewModel:ObservableObject
8+
publicpartialclassTrayWindowLoginRequiredViewModel
79
{
10+
privatereadonlyIServiceProvider_services;
11+
12+
privateSignInWindow?_signInWindow;
13+
14+
publicTrayWindowLoginRequiredViewModel(IServiceProviderservices)
15+
{
16+
_services=services;
17+
}
18+
819
[RelayCommand]
920
publicvoidLogin()
1021
{
11-
// TODO: open the login window
22+
// This is safe against concurrent access since it all happens in the
23+
// UI thread.
24+
if(_signInWindow!=null)
25+
{
26+
_signInWindow.Activate();
27+
return;
28+
}
29+
30+
_signInWindow=_services.GetRequiredService<SignInWindow>();
31+
_signInWindow.Closed+=(_,_)=>_signInWindow=null;
32+
_signInWindow.Activate();
1233
}
1334
}

‎App/Views/Pages/SignInTokenPage.xaml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<Page
4+
x:Class="Coder.Desktop.App.Views.Pages.SignInTokenPage"
5+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
6+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
7+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
9+
mc:Ignorable="d"
10+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
11+
12+
<StackPanel
13+
Orientation="Vertical"
14+
HorizontalAlignment="Stretch"
15+
VerticalAlignment="Top"
16+
Padding="20"
17+
Spacing="10">
18+
19+
<TextBlock
20+
Text="Coder Desktop"
21+
FontSize="24"
22+
VerticalAlignment="Center"
23+
HorizontalAlignment="Center" />
24+
25+
<Grid>
26+
<Grid.ColumnDefinitions>
27+
<ColumnDefinitionWidth="Auto" />
28+
<ColumnDefinitionWidth="*" />
29+
</Grid.ColumnDefinitions>
30+
<Grid.RowDefinitions>
31+
<RowDefinitionHeight="1*" />
32+
<RowDefinitionHeight="1*" />
33+
<RowDefinitionHeight="1*" />
34+
<RowDefinitionHeight="1*" />
35+
<RowDefinitionHeight="1*" />
36+
</Grid.RowDefinitions>
37+
38+
<TextBlock
39+
Grid.Column="0"
40+
Grid.Row="0"
41+
Text="Server URL"
42+
HorizontalAlignment="Right"
43+
Padding="10" />
44+
45+
<TextBlock
46+
Grid.Column="1"
47+
Grid.Row="0"
48+
HorizontalAlignment="Stretch"
49+
VerticalAlignment="Center"
50+
Padding="10"
51+
Text="{x:Bind ViewModel.CoderUrl, Mode=OneWay}" />
52+
53+
<TextBlock
54+
Grid.Column="0"
55+
Grid.Row="2"
56+
Text="Session Token"
57+
HorizontalAlignment="Right"
58+
Padding="10" />
59+
60+
<TextBox
61+
Grid.Column="1"
62+
Grid.Row="2"
63+
HorizontalAlignment="Stretch"
64+
PlaceholderText="Paste your token here"
65+
LostFocus="{x:Bind ViewModel.ApiToken_FocusLost, Mode=OneWay}"
66+
Text="{x:Bind ViewModel.ApiToken, Mode=TwoWay}" />
67+
68+
<TextBlock
69+
Grid.Column="1"
70+
Grid.Row="3"
71+
Text="{x:Bind ViewModel.ApiTokenError, Mode=OneWay}"
72+
Foreground="Red" />
73+
74+
<HyperlinkButton
75+
Grid.Column="1"
76+
Grid.Row="4"
77+
Content="Generate a token via the Web UI"
78+
NavigateUri="{x:Bind ViewModel.GenTokenUrl, Mode=OneWay}" />
79+
</Grid>
80+
81+
<StackPanel
82+
Orientation="Horizontal"
83+
HorizontalAlignment="Center"
84+
Spacing="10">
85+
86+
<Button
87+
Content="Back"HorizontalAlignment="Right"
88+
Command="{x:Bind ViewModel.TokenPage_BackCommand, Mode=OneWay}"
89+
CommandParameter="{x:Bind SignInWindow, Mode=OneWay}" />
90+
91+
<Button
92+
Content="Sign In"
93+
HorizontalAlignment="Left"
94+
Style="{StaticResource AccentButtonStyle}"
95+
Command="{x:Bind ViewModel.TokenPage_SignInCommand, Mode=OneWay}"
96+
CommandParameter="{x:Bind SignInWindow, Mode=OneWay}" />
97+
</StackPanel>
98+
99+
<TextBlock
100+
Text="{x:Bind ViewModel.SignInError, Mode=OneWay}"
101+
HorizontalAlignment="Center"
102+
Foreground="Red" />
103+
</StackPanel>
104+
</Page>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
usingCoder.Desktop.App.ViewModels;
2+
usingMicrosoft.UI.Xaml.Controls;
3+
4+
namespaceCoder.Desktop.App.Views.Pages;
5+
6+
/// <summary>
7+
/// A sign in page to accept the user's Coder token.
8+
/// </summary>
9+
publicsealedpartialclassSignInTokenPage:Page
10+
{
11+
publicreadonlySignInViewModelViewModel;
12+
publicreadonlySignInWindowSignInWindow;
13+
14+
publicSignInTokenPage(SignInWindowparent,SignInViewModelviewModel)
15+
{
16+
InitializeComponent();
17+
ViewModel=viewModel;
18+
SignInWindow=parent;
19+
}
20+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp