1
- using Flow . Launcher . Core . Resource ;
1
+ using Accessibility ;
2
+ using Flow . Launcher . Core . Resource ;
3
+ using Flow . Launcher . Infrastructure ;
2
4
using System ;
3
5
using System . Collections . Generic ;
4
6
using System . Diagnostics ;
8
10
using System . Text . Json ;
9
11
using System . Threading ;
10
12
using System . Threading . Tasks ;
11
- using System . Windows . Forms ;
12
13
using Flow . Launcher . Infrastructure . Logger ;
14
+ using Flow . Launcher . Infrastructure . UserSettings ;
13
15
using Flow . Launcher . Plugin ;
14
16
using ICSharpCode . SharpZipLib . Zip ;
15
17
using JetBrains . Annotations ;
16
18
using Microsoft . IO ;
19
+ using System . Text . Json . Serialization ;
20
+ using System . Windows ;
21
+ using System . Windows . Controls ;
22
+ using YamlDotNet . Serialization ;
23
+ using YamlDotNet . Serialization . NamingConventions ;
24
+ using CheckBox = System . Windows . Controls . CheckBox ;
25
+ using Control = System . Windows . Controls . Control ;
26
+ using Label = System . Windows . Controls . Label ;
27
+ using Orientation = System . Windows . Controls . Orientation ;
28
+ using TextBox = System . Windows . Controls . TextBox ;
29
+ using UserControl = System . Windows . Controls . UserControl ;
30
+ using System . Windows . Data ;
17
31
18
32
namespace Flow . Launcher . Core . Plugin
19
33
{
20
34
/// <summary>
21
35
/// Represent the plugin that using JsonPRC
22
36
/// every JsonRPC plugin should has its own plugin instance
23
37
/// </summary>
24
- internal abstract class JsonRPCPlugin : IAsyncPlugin , IContextMenu
38
+ internal abstract class JsonRPCPlugin : IAsyncPlugin , IContextMenu , ISettingProvider , ISavable
25
39
{
26
40
protected PluginInitContext context ;
27
41
public const string JsonRPC = "JsonRPC" ;
@@ -35,6 +49,9 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu
35
49
36
50
private static readonly RecyclableMemoryStreamManager BufferManager = new ( ) ;
37
51
52
+ private string SettingConfigurationPath => Path . Combine ( context . CurrentPluginMetadata . PluginDirectory , "SettingsTemplate.yaml" ) ;
53
+ private string SettingPath => Path . Combine ( DataLocation . PluginSettingsDirectory , context . CurrentPluginMetadata . Name , "Settings.json" ) ;
54
+
38
55
public List < Result > LoadContextMenus ( Result selectedResult )
39
56
{
40
57
var request = new JsonRPCRequestModel
@@ -59,6 +76,14 @@ public List<Result> LoadContextMenus(Result selectedResult)
59
76
}
60
77
} ;
61
78
79
+ private static readonly JsonSerializerOptions settingSerializeOption = new ( )
80
+ {
81
+ WriteIndented = true
82
+ } ;
83
+ private Dictionary < string , object > Settings { get ; set ; }
84
+
85
+ private Dictionary < string , FrameworkElement > _settingControls = new ( ) ;
86
+
62
87
private async Task < List < Result > > DeserializedResultAsync ( Stream output )
63
88
{
64
89
if ( output == Stream . Null ) return null ;
@@ -92,6 +117,8 @@ private List<Result> ParseResults(JsonRPCQueryResponseModel queryResponseModel)
92
117
{
93
118
result . Action = c =>
94
119
{
120
+ UpdateSettings ( result . SettingsChange ) ;
121
+
95
122
if ( result . JsonRPCAction == null ) return false ;
96
123
97
124
if ( string . IsNullOrEmpty ( result . JsonRPCAction . Method ) )
@@ -131,6 +158,8 @@ private List<Result> ParseResults(JsonRPCQueryResponseModel queryResponseModel)
131
158
132
159
results . AddRange ( queryResponseModel . Result ) ;
133
160
161
+ UpdateSettings ( queryResponseModel . SettingsChange ) ;
162
+
134
163
return results ;
135
164
}
136
165
@@ -283,19 +312,222 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
283
312
var request = new JsonRPCRequestModel
284
313
{
285
314
Method = "query" ,
286
- Parameters = new [ ]
315
+ Parameters = new object [ ]
287
316
{
288
317
query . Search
289
- }
318
+ } ,
319
+ Settings = Settings
290
320
} ;
291
321
var output = await RequestAsync ( request , token ) ;
292
322
return await DeserializedResultAsync ( output ) ;
293
323
}
294
324
295
- public virtual Task InitAsync ( PluginInitContext context )
325
+ public async Task InitSettingAsync ( )
326
+ {
327
+ if ( ! File . Exists ( SettingConfigurationPath ) )
328
+ return ;
329
+
330
+ if ( File . Exists ( SettingPath ) )
331
+ {
332
+ await using var fileStream = File . OpenRead ( SettingPath ) ;
333
+ Settings = await JsonSerializer . DeserializeAsync < Dictionary < string , object > > ( fileStream , options ) ;
334
+ }
335
+
336
+ var deserializer = new DeserializerBuilder ( ) . WithNamingConvention ( CamelCaseNamingConvention . Instance ) . Build ( ) ;
337
+ _settingsTemplate = deserializer . Deserialize < JsonRpcConfigurationModel > ( await File . ReadAllTextAsync ( SettingConfigurationPath ) ) ;
338
+
339
+ Settings ??= new Dictionary < string , object > ( ) ;
340
+
341
+ foreach ( var ( type , attribute ) in _settingsTemplate . Body )
342
+ {
343
+ if ( type == "textBlock" )
344
+ continue ;
345
+ if ( ! Settings . ContainsKey ( attribute . Name ) )
346
+ {
347
+ Settings [ attribute . Name ] = attribute . DefaultValue ;
348
+ }
349
+ }
350
+ }
351
+
352
+ public virtual async Task InitAsync ( PluginInitContext context )
296
353
{
297
354
this . context = context ;
298
- return Task . CompletedTask ;
355
+ await InitSettingAsync ( ) ;
356
+ }
357
+ private static readonly Thickness settingControlMargin = new ( 10 ) ;
358
+ private JsonRpcConfigurationModel _settingsTemplate ;
359
+ public Control CreateSettingPanel ( )
360
+ {
361
+ if ( Settings == null )
362
+ return new ( ) ;
363
+ var settingWindow = new UserControl ( ) ;
364
+ var mainPanel = new StackPanel
365
+ {
366
+ Margin = settingControlMargin ,
367
+ Orientation = Orientation . Vertical
368
+ } ;
369
+ settingWindow . Content = mainPanel ;
370
+
371
+ foreach ( var ( type , attribute ) in _settingsTemplate . Body )
372
+ {
373
+ var panel = new StackPanel
374
+ {
375
+ Orientation = Orientation . Horizontal ,
376
+ Margin = settingControlMargin
377
+ } ;
378
+ var name = new Label ( )
379
+ {
380
+ Content = attribute . Label ,
381
+ Margin = settingControlMargin
382
+ } ;
383
+
384
+ FrameworkElement contentControl ;
385
+
386
+ switch ( type )
387
+ {
388
+ case "textBlock" :
389
+ {
390
+ contentControl = new TextBlock
391
+ {
392
+ Text = attribute . Description . Replace ( "\\ r\\ n" , "\r \n " ) ,
393
+ Margin = settingControlMargin ,
394
+ MaxWidth = 400 ,
395
+ TextWrapping = TextWrapping . WrapWithOverflow
396
+ } ;
397
+ break ;
398
+ }
399
+ case "input" :
400
+ {
401
+ var textBox = new TextBox ( )
402
+ {
403
+ Width = 300 ,
404
+ Text = Settings [ attribute . Name ] as string ?? string . Empty ,
405
+ Margin = settingControlMargin ,
406
+ ToolTip = attribute . Description
407
+ } ;
408
+ textBox . TextChanged += ( _ , _ ) =>
409
+ {
410
+ Settings [ attribute . Name ] = textBox . Text ;
411
+ } ;
412
+ contentControl = textBox ;
413
+ break ;
414
+ }
415
+ case "textarea" :
416
+ {
417
+ var textBox = new TextBox ( )
418
+ {
419
+ Width = 300 ,
420
+ Height = 120 ,
421
+ Margin = settingControlMargin ,
422
+ TextWrapping = TextWrapping . WrapWithOverflow ,
423
+ AcceptsReturn = true ,
424
+ Text = Settings [ attribute . Name ] as string ?? string . Empty ,
425
+ ToolTip = attribute . Description
426
+ } ;
427
+ textBox . TextChanged += ( sender , _ ) =>
428
+ {
429
+ Settings [ attribute . Name ] = ( ( TextBox ) sender ) . Text ;
430
+ } ;
431
+ contentControl = textBox ;
432
+ break ;
433
+ }
434
+ case "passwordBox" :
435
+ {
436
+ var passwordBox = new PasswordBox ( )
437
+ {
438
+ Width = 300 ,
439
+ Margin = settingControlMargin ,
440
+ Password = Settings [ attribute . Name ] as string ?? string . Empty ,
441
+ PasswordChar = attribute . passwordChar == default ? '*' : attribute . passwordChar ,
442
+ ToolTip = attribute . Description
443
+ } ;
444
+ passwordBox . PasswordChanged += ( sender , _ ) =>
445
+ {
446
+ Settings [ attribute . Name ] = ( ( PasswordBox ) sender ) . Password ;
447
+ } ;
448
+ contentControl = passwordBox ;
449
+ break ;
450
+ }
451
+ case "dropdown" :
452
+ {
453
+ var comboBox = new ComboBox ( )
454
+ {
455
+ ItemsSource = attribute . Options ,
456
+ SelectedItem = Settings [ attribute . Name ] ,
457
+ Margin = settingControlMargin ,
458
+ ToolTip = attribute . Description
459
+ } ;
460
+ comboBox . SelectionChanged += ( sender , _ ) =>
461
+ {
462
+ Settings [ attribute . Name ] = ( string ) ( ( ComboBox ) sender ) . SelectedItem ;
463
+ } ;
464
+ contentControl = comboBox ;
465
+ break ;
466
+ }
467
+ case "checkbox" :
468
+ var checkBox = new CheckBox
469
+ {
470
+ IsChecked = Settings [ attribute . Name ] is bool isChecked ? isChecked : bool . Parse ( attribute . DefaultValue ) ,
471
+ Margin = settingControlMargin ,
472
+ ToolTip = attribute . Description
473
+ } ;
474
+ checkBox . Click += ( sender , _ ) =>
475
+ {
476
+ Settings [ attribute . Name ] = ( ( CheckBox ) sender ) . IsChecked ;
477
+ } ;
478
+ contentControl = checkBox ;
479
+ break ;
480
+ default :
481
+ continue ;
482
+ }
483
+ if ( type != "textBlock" )
484
+ _settingControls [ attribute . Name ] = contentControl ;
485
+ panel . Children . Add ( name ) ;
486
+ panel . Children . Add ( contentControl ) ;
487
+ mainPanel . Children . Add ( panel ) ;
488
+ }
489
+ return settingWindow ;
490
+ }
491
+ public void Save ( )
492
+ {
493
+ if ( Settings != null )
494
+ {
495
+ Helper . ValidateDirectory ( Path . Combine ( DataLocation . PluginSettingsDirectory , context . CurrentPluginMetadata . Name ) ) ;
496
+ File . WriteAllText ( SettingPath , JsonSerializer . Serialize ( Settings , settingSerializeOption ) ) ;
497
+ }
498
+ }
499
+
500
+ public void UpdateSettings ( Dictionary < string , object > settings )
501
+ {
502
+ if ( settings == null || settings . Count == 0 )
503
+ return ;
504
+
505
+ foreach ( var ( key , value ) in settings )
506
+ {
507
+ if ( Settings . ContainsKey ( key ) )
508
+ {
509
+ Settings [ key ] = value ;
510
+ }
511
+ if ( _settingControls . ContainsKey ( key ) )
512
+ {
513
+
514
+ switch ( _settingControls [ key ] )
515
+ {
516
+ case TextBox textBox :
517
+ textBox . Dispatcher . Invoke ( ( ) => textBox . Text = value as string ) ;
518
+ break ;
519
+ case PasswordBox passwordBox :
520
+ passwordBox . Dispatcher . Invoke ( ( ) => passwordBox . Password = value as string ) ;
521
+ break ;
522
+ case ComboBox comboBox :
523
+ comboBox . Dispatcher . Invoke ( ( ) => comboBox . SelectedItem = value ) ;
524
+ break ;
525
+ case CheckBox checkBox :
526
+ checkBox . Dispatcher . Invoke ( ( ) => checkBox . IsChecked = value is bool isChecked ? isChecked : bool . Parse ( value as string ) ) ;
527
+ break ;
528
+ }
529
+ }
530
+ }
299
531
}
300
532
}
301
533
}
0 commit comments