1
1
using System ;
2
2
using System . Linq ;
3
3
using Windows . System ;
4
+ using Coder . Desktop . App . Models ;
5
+ using Coder . Desktop . App . Services ;
4
6
using Coder . Desktop . App . Utils ;
5
7
using Coder . Desktop . Vpn . Proto ;
6
8
using CommunityToolkit . Mvvm . ComponentModel ;
7
9
using CommunityToolkit . Mvvm . Input ;
8
10
using Microsoft . Extensions . Logging ;
9
11
using Microsoft . UI . Xaml ;
12
+ using Microsoft . UI . Xaml . Controls ;
13
+ using Microsoft . UI . Xaml . Controls . Primitives ;
10
14
using Microsoft . UI . Xaml . Media ;
11
15
using Microsoft . UI . Xaml . Media . Imaging ;
12
16
13
17
namespace Coder . Desktop . App . ViewModels ;
14
18
15
19
public interface IAgentAppViewModelFactory
16
20
{
17
- public AgentAppViewModel Create ( Uuid id , string name , Uri appUri , Uri ? iconUrl ) ;
21
+ public AgentAppViewModel Create ( Uuid id , string name , string appUri , Uri ? iconUrl ) ;
18
22
}
19
23
20
- public class AgentAppViewModelFactory : IAgentAppViewModelFactory
24
+ public class AgentAppViewModelFactory ( ILogger < AgentAppViewModel > childLogger , ICredentialManager credentialManager )
25
+ : IAgentAppViewModelFactory
21
26
{
22
- private readonly ILogger < AgentAppViewModel > _childLogger ;
23
-
24
- public AgentAppViewModelFactory ( ILogger < AgentAppViewModel > childLogger )
25
- {
26
- _childLogger = childLogger ;
27
- }
28
-
29
- public AgentAppViewModel Create ( Uuid id , string name , Uri appUri , Uri ? iconUrl )
27
+ public AgentAppViewModel Create ( Uuid id , string name , string appUri , Uri ? iconUrl )
30
28
{
31
- return new AgentAppViewModel ( _childLogger )
29
+ return new AgentAppViewModel ( childLogger , credentialManager )
32
30
{
33
31
Id = id ,
34
32
Name = name ,
@@ -40,61 +38,40 @@ public AgentAppViewModel Create(Uuid id, string name, Uri appUri, Uri? iconUrl)
40
38
41
39
public partial class AgentAppViewModel : ObservableObject , IModelUpdateable < AgentAppViewModel >
42
40
{
41
+ private const string SessionTokenUriVar = "$SESSION_TOKEN" ;
42
+
43
43
private readonly ILogger < AgentAppViewModel > _logger ;
44
+ private readonly ICredentialManager _credentialManager ;
44
45
45
46
public required Uuid Id { get ; init ; }
46
47
47
48
[ ObservableProperty ] public required partial string Name { get ; set ; }
48
49
49
50
[ ObservableProperty ]
50
51
[ NotifyPropertyChangedFor ( nameof ( Details ) ) ]
51
- public required partial Uri AppUri { get ; set ; }
52
+ public required partial string AppUri { get ; set ; }
52
53
53
- [ ObservableProperty ]
54
- [ NotifyPropertyChangedFor ( nameof ( ImageSource ) ) ]
55
- public partial Uri ? IconUrl { get ; set ; }
54
+ [ ObservableProperty ] public partial Uri ? IconUrl { get ; set ; }
55
+
56
+ [ ObservableProperty ] public partial ImageSource IconImageSource { get ; set ; }
56
57
57
58
[ ObservableProperty ] public partial bool UseFallbackIcon { get ; set ; } = true ;
58
59
59
60
public string Details =>
60
61
( string . IsNullOrWhiteSpace ( Name ) ? "(no name)" : Name ) + ":\n \n " + AppUri ;
61
62
62
- public ImageSource ImageSource
63
- {
64
- get
65
- {
66
- if ( IconUrl is null || ( IconUrl . Scheme != "http" && IconUrl . Scheme != "https" ) )
67
- {
68
- UseFallbackIcon = true ;
69
- return new BitmapImage ( ) ;
70
- }
71
-
72
- // Determine what image source to use based on extension, use a
73
- // BitmapImage as last resort.
74
- var ext = IconUrl . AbsolutePath . Split ( '/' ) . LastOrDefault ( ) ? . Split ( '.' ) . LastOrDefault ( ) ;
75
- // TODO: this is definitely a hack, URLs shouldn't need to end in .svg
76
- if ( ext is "svg" )
77
- {
78
- // TODO: Some SVGs like `/icon/cursor.svg` contain PNG data and
79
- // don't render at all.
80
- var svg = new SvgImageSource ( IconUrl ) ;
81
- svg . Opened += ( _ , _ ) => _logger . LogDebug ( "app icon opened (svg): {uri}" , IconUrl ) ;
82
- svg . OpenFailed += ( _ , args ) =>
83
- _logger . LogDebug ( "app icon failed to open (svg): {uri}: {Status}" , IconUrl , args . Status ) ;
84
- return svg ;
85
- }
86
-
87
- var bitmap = new BitmapImage ( IconUrl ) ;
88
- bitmap . ImageOpened += ( _ , _ ) => _logger . LogDebug ( "app icon opened (bitmap): {uri}" , IconUrl ) ;
89
- bitmap . ImageFailed += ( _ , args ) =>
90
- _logger . LogDebug ( "app icon failed to open (bitmap): {uri}: {ErrorMessage}" , IconUrl , args . ErrorMessage ) ;
91
- return bitmap ;
92
- }
93
- }
94
-
95
- public AgentAppViewModel ( ILogger < AgentAppViewModel > logger )
63
+ public AgentAppViewModel ( ILogger < AgentAppViewModel > logger , ICredentialManager credentialManager )
96
64
{
97
65
_logger = logger ;
66
+ _credentialManager = credentialManager ;
67
+
68
+ // Apply the icon URL to the icon image source when it is updated.
69
+ IconImageSource = UpdateIcon ( ) ;
70
+ PropertyChanged += ( _ , args ) =>
71
+ {
72
+ if ( args . PropertyName == nameof ( IconUrl ) )
73
+ IconImageSource = UpdateIcon ( ) ;
74
+ } ;
98
75
}
99
76
100
77
public bool TryApplyChanges ( AgentAppViewModel obj )
@@ -116,6 +93,36 @@ public bool TryApplyChanges(AgentAppViewModel obj)
116
93
return true ;
117
94
}
118
95
96
+ private ImageSource UpdateIcon ( )
97
+ {
98
+ if ( IconUrl is null || ( IconUrl . Scheme != "http" && IconUrl . Scheme != "https" ) )
99
+ {
100
+ UseFallbackIcon = true ;
101
+ return new BitmapImage ( ) ;
102
+ }
103
+
104
+ // Determine what image source to use based on extension, use a
105
+ // BitmapImage as last resort.
106
+ var ext = IconUrl . AbsolutePath . Split ( '/' ) . LastOrDefault ( ) ? . Split ( '.' ) . LastOrDefault ( ) ;
107
+ // TODO: this is definitely a hack, URLs shouldn't need to end in .svg
108
+ if ( ext is "svg" )
109
+ {
110
+ // TODO: Some SVGs like `/icon/cursor.svg` contain PNG data and
111
+ // don't render at all.
112
+ var svg = new SvgImageSource ( IconUrl ) ;
113
+ svg . Opened += ( _ , _ ) => _logger . LogDebug ( "app icon opened (svg): {uri}" , IconUrl ) ;
114
+ svg . OpenFailed += ( _ , args ) =>
115
+ _logger . LogDebug ( "app icon failed to open (svg): {uri}: {Status}" , IconUrl , args . Status ) ;
116
+ return svg ;
117
+ }
118
+
119
+ var bitmap = new BitmapImage ( IconUrl ) ;
120
+ bitmap . ImageOpened += ( _ , _ ) => _logger . LogDebug ( "app icon opened (bitmap): {uri}" , IconUrl ) ;
121
+ bitmap . ImageFailed += ( _ , args ) =>
122
+ _logger . LogDebug ( "app icon failed to open (bitmap): {uri}: {ErrorMessage}" , IconUrl , args . ErrorMessage ) ;
123
+ return bitmap ;
124
+ }
125
+
119
126
public void OnImageOpened ( object ? sender , RoutedEventArgs e )
120
127
{
121
128
UseFallbackIcon = false ;
@@ -127,15 +134,45 @@ public void OnImageFailed(object? sender, RoutedEventArgs e)
127
134
}
128
135
129
136
[ RelayCommand ]
130
- private void OpenApp ( )
137
+ private void OpenApp ( object parameter )
131
138
{
132
139
try
133
140
{
134
- _ = Launcher . LaunchUriAsync ( AppUri ) ;
141
+ var uriString = AppUri ;
142
+ var cred = _credentialManager . GetCachedCredentials ( ) ;
143
+ if ( cred . State is CredentialState . Valid && cred . ApiToken is not null )
144
+ uriString = uriString . Replace ( SessionTokenUriVar , cred . ApiToken ) ;
145
+ uriString += SessionTokenUriVar ;
146
+ if ( uriString . Contains ( SessionTokenUriVar ) )
147
+ throw new Exception ( $ "URI contains { SessionTokenUriVar } variable but could not be replaced") ;
148
+
149
+ var uri = new Uri ( uriString ) ;
150
+ _ = Launcher . LaunchUriAsync ( uri ) ;
135
151
}
136
- catch
152
+ catch ( Exception e )
137
153
{
138
- // TODO: log/notify
154
+ _logger . LogWarning ( e , "could not parse or launch app" ) ;
155
+
156
+ if ( parameter is not FrameworkElement frameworkElement ) return ;
157
+ var flyout = new Flyout
158
+ {
159
+ Content = new TextBlock
160
+ {
161
+ Text = $ "Could not open app: { e . Message } ",
162
+ Margin = new Thickness ( 4 ) ,
163
+ TextWrapping = TextWrapping . Wrap ,
164
+ } ,
165
+ FlyoutPresenterStyle = new Style ( typeof ( FlyoutPresenter ) )
166
+ {
167
+ Setters =
168
+ {
169
+ new Setter ( ScrollViewer . HorizontalScrollModeProperty , ScrollMode . Disabled ) ,
170
+ new Setter ( ScrollViewer . HorizontalScrollBarVisibilityProperty , ScrollBarVisibility . Disabled ) ,
171
+ } ,
172
+ } ,
173
+ } ;
174
+ FlyoutBase . SetAttachedFlyout ( frameworkElement , flyout ) ;
175
+ FlyoutBase . ShowAttachedFlyout ( frameworkElement ) ;
139
176
}
140
177
}
141
178
}
0 commit comments