diff --git a/HoubyStudio.LazyAdmin.DesktopApp/.editorconfig b/HoubyStudio.LazyAdmin.DesktopApp/.editorconfig new file mode 100644 index 0000000..eece740 --- /dev/null +++ b/HoubyStudio.LazyAdmin.DesktopApp/.editorconfig @@ -0,0 +1,217 @@ +# Pokud chcete nastavení souboru .editorconfig zdědit z vyšších adresářů, odeberte řádek níže. +root = true + +# Soubory C# +[*.cs] + +#### Základní možnosti pro EditorConfig #### + +# Odsazení a mezery +indent_size = 4 +indent_style = space +tab_width = 4 + +# Předvolby nových řádků +end_of_line = crlf +insert_final_newline = false + +#### Konvence kódování v .NET #### + +# Uspořádat direktivy using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# Předvolby pro this. a Me. +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = true +dotnet_style_qualification_for_method = true +dotnet_style_qualification_for_property = false + +# Předvolby klíčových slov jazyka a typů BCL +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Předvolby závorek +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Předvolby modifikátorů +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Předvolby na úrovni výrazů +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Předvolby polí +dotnet_style_readonly_field = true + +# Předvolby parametrů +dotnet_code_quality_unused_parameters = all + +# Předvolby potlačování +dotnet_remove_unnecessary_suppression_exclusions = none + +# Předvolby nových řádků +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### Konvence kódování v C# #### + +# Předvolby pro var +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Členy s výrazem v těle +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Předvolby porovnávání vzorů +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Předvolby kontrol hodnoty null +csharp_style_conditional_delegate_call = true + +# Předvolby modifikátorů +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# Předvolby bloků kódu +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true + +# Předvolby na úrovni výrazů +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_pattern_local_over_anonymous_function = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_range_operator = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# Předvolby direktivy using +csharp_using_directive_placement =inside_namespace:silent + +# Předvolby nových řádků +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### Pravidla formátování jazyka C# #### + +# Předvolby nových řádků +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Předvolby odsazení +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Předvolby mezer +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Předvolby zalamování +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Styly pojmenování #### + +# Pravidla pojmenování + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Specifikace symbolů + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Styly pojmenování + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp.sln b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp.sln index 63b074f..7d38584 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp.sln +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp.sln @@ -5,6 +5,11 @@ VisualStudioVersion = 16.0.31605.320 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HoubyStudio.LazyAdmin.DesktopApp", "HoubyStudio.LazyAdmin.DesktopApp\HoubyStudio.LazyAdmin.DesktopApp.csproj", "{8C101530-8338-41D7-A8BD-88393C91F37C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FEA29883-858E-440D-9573-0413E6377B6E}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml index 60a1e2e..fbbc667 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml @@ -1,7 +1,6 @@  diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml.cs b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml.cs index 3ba6c56..373d2d2 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml.cs +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/App.xaml.cs @@ -63,7 +63,7 @@ private async void Application_Exit(object sender, ExitEventArgs e) { using (this.host) { - var timeoutCts = new CancellationTokenSource(1000); + CancellationTokenSource timeoutCts = new CancellationTokenSource(1000); await this.host.RunAsync(timeoutCts.Token); } diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/LazyAdminWebView.cs b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/LazyAdminWebView.cs index a39fb76..f0dbee4 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/LazyAdminWebView.cs +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/LazyAdminWebView.cs @@ -58,9 +58,9 @@ public static void ShowMessage(string message) /// /// Change Runspace status. /// - public static void PostRunspaceStatus(Guid Uid, string Status, string Result) + public static void PostRunspaceStatus(Guid uid, string status, string result) { - CSharpRunspaceStatusMessage jsonMessage = new(Uid, Status, Result); + CSharpRunspaceStatusMessage jsonMessage = new(uid, status, result); string jsonString = JsonConvert.SerializeObject(jsonMessage); _webView.CoreWebView2.PostWebMessageAsJson(jsonString); @@ -82,10 +82,10 @@ public static void PostRunspaceStatus(Guid uid, string status) /// /// Posts WebMessage in the JSON form to the WebView2 control. /// - /// JSON sent to the WebView2 control. - public static void PostWebMessageAsJSON(string Message) + /// JSON sent to the WebView2 control. + public static void PostWebMessageAsJSON(string message) { - PowerShellData jsonMessage = new(Message); + PowerShellData jsonMessage = new(message); string jsonString = JsonConvert.SerializeObject(jsonMessage); _webView.CoreWebView2.PostWebMessageAsJson(jsonString); diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/MainWindow.xaml.cs b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/MainWindow.xaml.cs index 6797f04..a6bfa94 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/MainWindow.xaml.cs +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/MainWindow.xaml.cs @@ -41,7 +41,7 @@ public MainWindow(IWebViewService webViewService) // LazyAdminWebView.WebView = this.webView; // SetWebView(this.webView); this.webViewService = webViewService; - this.webViewService.EnsureCoreWebView2Async(this.webView); + this.webViewService.InitializeWebView2Async(this.webView); //GetLazyAdminPwsh().MockPowerShell = this.PowerShell; } @@ -112,7 +112,7 @@ private async void Execute_Click(object sender, RoutedEventArgs e) // TODO: Are we able to load service using scope and service provider? // using var scope = _services.CreateScope(); // var webViewService = scope.ServiceProvider.GetRequiredService(); - _ = await this.webViewService.ShowMessageAsync("PowerShell.Text", this.webView); + _ = await this.webViewService.ShowMessageAsync(this.PowerShell.Text, this.webView); } } } diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/IWebViewCommunicationProvider.cs b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/IWebViewCommunicationProvider.cs index d66e24e..1d84999 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/IWebViewCommunicationProvider.cs +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/IWebViewCommunicationProvider.cs @@ -29,6 +29,6 @@ public interface IWebViewCommunicationProvider /// /// WebView2 control. /// A representing the asynchronous operation. - public Task EnsureCoreWebView2Async(WebView2 webView); + public Task InitializeWebView2Async(WebView2 webView); } } diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/WebViewCommunicationProvider.cs b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/WebViewCommunicationProvider.cs index 1fb9446..7d9325d 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/WebViewCommunicationProvider.cs +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Providers/WebViewCommunicationProvider.cs @@ -5,11 +5,11 @@ namespace HoubyStudio.LazyAdmin.DesktopApp.WebView.Providers { using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; + using System.IO; using System.Threading.Tasks; + using System.Windows; using Microsoft.Extensions.Logging; + using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; /// @@ -17,6 +17,20 @@ namespace HoubyStudio.LazyAdmin.DesktopApp.WebView.Providers /// public class WebViewCommunicationProvider : IWebViewCommunicationProvider { + /// + /// Resolves to the full path of the user data folder for WebView2 component. + /// + /// Usually the returned folder path will conform to this format: + /// C:\Users\{username}\AppData\Local\LazyAdmin. + private static readonly string CacheFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LazyAdmin"); + + /// + /// Resolves to the index.html file, which gets rendered by WebView2 component. + /// + /// Usually the returned file path will conform to this format: + /// C:\Users\{username}\AppData\Local\LazyAdmin\EBWebView\WebResources\index.html. + private static readonly Uri IndexFilePath = new(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LazyAdmin/EBWebView/WebResources/index.html")); + private readonly ILogger logger; /// @@ -48,19 +62,83 @@ public virtual async Task ShowMessageAsync(string message, WebView2 webV } /// - public virtual async Task EnsureCoreWebView2Async(WebView2 webView) + public virtual async Task InitializeWebView2Async(WebView2 webView) { + // Register event handlers to WebView2 component + try + { + webView.CoreWebView2InitializationCompleted += this.CheckWebViewInitializationStatus; + } + catch (Exception exception) + { + this.logger.LogError(exception, "Failed to register all required event handlers for WebView 2 component. Application cannot continue."); + Application.Current.Dispatcher.Invoke(() => + { + MessageBoxResult result = MessageBox.Show( + "We have encountered an error, which makes Lazy Admin unusable. Read logs for more details.", + "Lazy Admin: Terminating error occurred!", + MessageBoxButton.OK, + MessageBoxImage.Error); + }); + Application.Current.Shutdown(); + throw; + } + bool result = false; await Task.Run(() => { - // TODO: Ensure WebView is present with all the settings. - _ = System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => + _ = System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(async () => { - result = webView.EnsureCoreWebView2Async().IsCompleted; + CoreWebView2Environment coreWebView2Environment; + + // Create custom WebView2 environment setting custom user data folder path + try + { + coreWebView2Environment = await CoreWebView2Environment.CreateAsync(null, CacheFolderPath); + } + catch (Exception exception) + { + this.logger.LogError(exception, "Failed to create WebView2 environment configuration."); + throw; + } + + // Initialize WebView2 with custom environment settings + try + { + result = webView.EnsureCoreWebView2Async(coreWebView2Environment).IsCompleted; + } + catch (Exception exception) + { + this.logger.LogError(exception, "Failed to initialize WebView2 component with user data folder set to: {CacheFolderPath}.", CacheFolderPath); + throw; + } + + // Set WebView2 component's source to Lazy Admin's UI index.html file + try + { + webView.Source = new UriBuilder(IndexFilePath).Uri; + } + catch (Exception exception) + { + this.logger.LogError(exception, "Failed to load Lazy Admin's index.html file from: {IndexFilePath}.", IndexFilePath); + throw; + } })); }); return result; } + + private void CheckWebViewInitializationStatus(object sender, CoreWebView2InitializationCompletedEventArgs args) + { + if (args.IsSuccess) + { + this.logger.LogInformation("Successfully initialized Lazy Admin's WebView2 comonent."); + } + else + { + this.logger.LogError(args.InitializationException, "Failed to initialize Lazy Admin's WebView2 component."); + } + } } } diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/IWebViewService.cs b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/IWebViewService.cs index 8b6db3a..7d42292 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/IWebViewService.cs +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/IWebViewService.cs @@ -16,6 +16,6 @@ public interface IWebViewService public Task ShowMessageAsync(string message, WebView2 webView); /// - public Task EnsureCoreWebView2Async(WebView2 webView); + public Task InitializeWebView2Async(WebView2 webView); } } diff --git a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/WebViewService.cs b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/WebViewService.cs index 4e2e3e7..f7c385d 100644 --- a/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/WebViewService.cs +++ b/HoubyStudio.LazyAdmin.DesktopApp/HoubyStudio.LazyAdmin.DesktopApp/WebView/Services/WebViewService.cs @@ -41,9 +41,9 @@ public virtual async Task ShowMessageAsync(string message, WebView2 webV } /// - public virtual async Task EnsureCoreWebView2Async(WebView2 webView) + public virtual async Task InitializeWebView2Async(WebView2 webView) { - return await this.communicationProvider.EnsureCoreWebView2Async(webView); + return await this.communicationProvider.InitializeWebView2Async(webView); } } }