diff --git a/.editorconfig b/.editorconfig index 3f02430..34ef06c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,158 +1,163 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file root = true -# Don't use tabs for indentation. +# All files [*] indent_style = space -# (Please don't specify an indent_size here; that has too many unintended consequences.) - -# Code files -[*.{cs,csx,vb,vbx}] -indent_size = 4 -insert_final_newline = true -charset = utf-8 -# XML project files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +# Xml files +[*.xml] indent_size = 2 -# XML config files -[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] -indent_size = 2 - -# JSON files -[*.json] +# Xml project files +[*.{csproj,fsproj,vbproj,proj,slnx}] indent_size = 2 -# Powershell files -[*.ps1] +# Xml config files +[*.{props,targets,config,nuspec}] indent_size = 2 -# Shell script files -[*.sh] -end_of_line = lf +[*.json] indent_size = 2 -# Dotnet code style settings: -[*.{cs,vb}] - -# Sort using and Import directives with System.* appearing first -dotnet_sort_system_directives_first = true -dotnet_separate_import_directive_groups = false -# Avoid "this." and "Me." if not necessary -dotnet_style_qualification_for_field = false:refactoring -dotnet_style_qualification_for_property = false:refactoring -dotnet_style_qualification_for_method = false:refactoring -dotnet_style_qualification_for_event = false:refactoring - -# Use language keywords instead of framework type names for type references -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion - -# Suggest more modern language features when available -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion - -# Whitespace options -dotnet_style_allow_multiple_blank_lines_experimental = false - -# Non-private static fields are PascalCase -dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields -dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style - -dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field -dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected -dotnet_naming_symbols.non_private_static_fields.required_modifiers = static - -dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case - -# Non-private readonly fields are PascalCase -dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields -dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style - -dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected -dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly - -dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case - -# Constants are PascalCase -dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants -dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style - -dotnet_naming_symbols.constants.applicable_kinds = field, local -dotnet_naming_symbols.constants.required_modifiers = const - -dotnet_naming_style.constant_style.capitalization = pascal_case +# C# files +[*.cs] -# Static fields are camelCase and start with s_ -dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion -dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields -dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style +#### Core EditorConfig Options #### -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.required_modifiers = static +# Indentation and spacing +indent_size = 4 +tab_width = 4 -dotnet_naming_style.static_field_style.capitalization = camel_case -dotnet_naming_style.static_field_style.required_prefix = s_ +# New line preferences +insert_final_newline = false -# Instance fields are camelCase and start with _ -dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion -dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields -dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style +#### .NET Coding Conventions #### +[*.{cs,vb}] -dotnet_naming_symbols.instance_fields.applicable_kinds = field +# Organize usings +dotnet_separate_import_directive_groups = true +dotnet_sort_system_directives_first = true +file_header_template = unset -dotnet_naming_style.instance_field_style.capitalization = camel_case -dotnet_naming_style.instance_field_style.required_prefix = _ +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent -# Locals and parameters are camelCase -dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion -dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters -dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent -dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent -dotnet_naming_style.camel_case_style.capitalization = camel_case +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent -# Local functions are PascalCase -dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions -dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:warning + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +#### C# Coding Conventions #### +[*.cs] -dotnet_naming_symbols.local_functions.applicable_kinds = local_function +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_switch_expression = true:suggestion -dotnet_naming_style.local_function_style.capitalization = pascal_case +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion -# By default, name items with PascalCase -dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members -dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style +# Modifier preferences +csharp_prefer_static_anonymous_function = true:suggestion +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion -dotnet_naming_symbols.all_members.applicable_kinds = * +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent -dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent -# RS0016: Only enable if API files are present -dotnet_public_api_analyzer.require_api_files = true +#### C# Formatting Rules #### -# CSharp code style settings: -[*.cs] -# Newline settings -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true +# New line preferences csharp_new_line_before_catch = true +csharp_new_line_before_else = true csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = 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 # Indentation preferences @@ -160,38 +165,8 @@ 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 -csharp_indent_labels = flush_left - -# Whitespace options -csharp_style_allow_embedded_statements_on_same_line_experimental = false -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false -csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false -csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false - -# Prefer "var" everywhere -csharp_style_var_for_built_in_types = true:suggestion -csharp_style_var_when_type_is_apparent = true:suggestion -csharp_style_var_elsewhere = true:suggestion - -# Prefer method-like constructs to have a block body -csharp_style_expression_bodied_methods = false:none -csharp_style_expression_bodied_constructors = false:none -csharp_style_expression_bodied_operators = false:none - -# Prefer property-like constructs to have an expression-body -csharp_style_expression_bodied_properties = true:none -csharp_style_expression_bodied_indexers = true:none -csharp_style_expression_bodied_accessors = true:none - -# Suggest more modern language features when available -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_throw_expression = true:suggestion -csharp_style_conditional_delegate_call = true:suggestion -csharp_style_prefer_extended_property_pattern = true:suggestion # Space preferences csharp_space_after_cast = false @@ -201,7 +176,7 @@ 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 = do_not_ignore +csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false @@ -217,63 +192,197 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false -# Blocks are allowed -csharp_prefer_braces = true:silent +# Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true -# IDE0060: Remove unused parameter -dotnet_diagnostic.IDE0060.severity = warning +#### Naming styles #### +[*.{cs,vb}] + +# Naming rules + +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion +dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces +dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase + +dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion +dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters +dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase + +dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods +dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase -[src/{Compilers,ExpressionEvaluator,Scripting}/**Test**/*.{cs,vb}] +dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties +dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase -# IDE0060: Remove unused parameter -dotnet_diagnostic.IDE0060.severity = none +dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.events_should_be_pascalcase.symbols = events +dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase -[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}] +dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables +dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase -# IDE0011: Add braces -csharp_prefer_braces = when_multiline:warning -# NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201 -dotnet_diagnostic.IDE0011.severity = warning +dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants +dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase -# IDE0040: Add accessibility modifiers -dotnet_diagnostic.IDE0040.severity = warning +dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion +dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters +dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase -# IDE0052: Remove unread private member -dotnet_diagnostic.IDE0052.severity = warning +dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields +dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase -# IDE0059: Unnecessary assignment to a value -dotnet_diagnostic.IDE0059.severity = warning +dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion +dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields +dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase -# CA1012: Abstract types should not have public constructors -dotnet_diagnostic.CA1012.severity = warning +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase -# CA1822: Make member static -dotnet_diagnostic.CA1822.severity = warning +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase -# Prefer "var" everywhere -dotnet_diagnostic.IDE0007.severity = warning -csharp_style_var_for_built_in_types = true:warning -csharp_style_var_when_type_is_apparent = true:warning -csharp_style_var_elsewhere = true:warning +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase -# csharp_style_allow_embedded_statements_on_same_line_experimental -dotnet_diagnostic.IDE2001.severity = warning +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase -# csharp_style_allow_blank_lines_between_consecutive_braces_experimental -dotnet_diagnostic.IDE2002.severity = warning +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase -# csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental -dotnet_diagnostic.IDE2004.severity = warning +dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums +dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase -# csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental -dotnet_diagnostic.IDE2005.severity = warning +dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase -# csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental -dotnet_diagnostic.IDE2006.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase -[src/{VisualStudio}/**/*.{cs,vb}] -# CA1822: Make member static -# There is a risk of accidentally breaking an internal API that partners rely on though IVT. -dotnet_code_quality.CA1822.api_surface = private +# Symbol specifications + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interfaces.required_modifiers = + +dotnet_naming_symbols.enums.applicable_kinds = enum +dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enums.required_modifiers = + +dotnet_naming_symbols.events.applicable_kinds = event +dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.events.required_modifiers = + +dotnet_naming_symbols.methods.applicable_kinds = method +dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.methods.required_modifiers = + +dotnet_naming_symbols.properties.applicable_kinds = property +dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.properties.required_modifiers = + +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_fields.required_modifiers = + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_fields.required_modifiers = + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum +dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types_and_namespaces.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 = + +dotnet_naming_symbols.type_parameters.applicable_kinds = namespace +dotnet_naming_symbols.type_parameters.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters.required_modifiers = + +dotnet_naming_symbols.private_constant_fields.applicable_kinds = field +dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_variables.applicable_kinds = local +dotnet_naming_symbols.local_variables.applicable_accessibilities = local +dotnet_naming_symbols.local_variables.required_modifiers = + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.applicable_accessibilities = local +dotnet_naming_symbols.local_constants.required_modifiers = const + +dotnet_naming_symbols.parameters.applicable_kinds = parameter +dotnet_naming_symbols.parameters.applicable_accessibilities = * +dotnet_naming_symbols.parameters.required_modifiers = + +dotnet_naming_symbols.public_constant_fields.applicable_kinds = field +dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_constant_fields.required_modifiers = const + +dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function +dotnet_naming_symbols.local_functions.applicable_accessibilities = * +dotnet_naming_symbols.local_functions.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.capitalization = pascal_case + +dotnet_naming_style.ipascalcase.required_prefix = I +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.capitalization = pascal_case + +dotnet_naming_style.tpascalcase.required_prefix = T +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.capitalization = pascal_case + +dotnet_naming_style._camelcase.required_prefix = _ +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.capitalization = camel_case + +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.capitalization = camel_case + +dotnet_naming_style.s_camelcase.required_prefix = s_ +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.capitalization = camel_case \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad7cf42..696d1c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,12 +19,12 @@ jobs: env: NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.x + dotnet-version: 10.x cache: true cache-dependency-path: '**/packages.lock.json' - run: dotnet restore --locked-mode diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index e1fc603..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,74 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ main ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ main ] - paths: - - '**/*.cs' - schedule: - - cron: '37 14 * * 6' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'csharp' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: true - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - queries: security-and-quality - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80ec6c6..f705fcf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,19 @@ +exclude: '(?i)(?:^|[\/])external(?:[\/]|$)' + ci: skip: [ dotnet-format ] repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-json - id: check-merge-conflict - id: check-toml - id: check-yaml - - id: end-of-file-fixer - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] - id: check-xml - id: detect-private-key - id: fix-byte-order-marker @@ -22,5 +24,5 @@ repos: - id: dotnet-format name: dotnet-format language: system - entry: dotnet format --severity info --exclude 'external/' --include + entry: dotnet format --severity info --include types_or: [ "c#", "vb" ] diff --git a/Directory.Packages.props b/Directory.Packages.props index 12e9d95..3eba1b2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,26 +1,26 @@ true - NU1507 - 11.2.3 + 11.3.11 - - + + - + + - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers @@ -28,9 +28,9 @@ all runtime; build; native; contentfiles; analyzers - - + + - + - + \ No newline at end of file diff --git a/SFP.sln b/SFP.sln deleted file mode 100644 index c21eda1..0000000 --- a/SFP.sln +++ /dev/null @@ -1,57 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32104.313 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFP_UI", "SFP_UI\SFP_UI.csproj", "{55EC0EB9-96FC-4CB5-B8AB-3F4A904AC97C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFP", "SFP\SFP.csproj", "{2801B18E-31FC-4B99-AAD7-7259CD197D8D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50AF4859-D9C1-465A-97E5-66B7B944FE7D}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - Directory.Packages.props = Directory.Packages.props - SFPCommon.props = SFPCommon.props - README.md = README.md - createpublishedzip.ps1 = createpublishedzip.ps1 - .pre-commit-config.yaml = .pre-commit-config.yaml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{D50C3657-84AF-4631-9291-21EEF38F94B6}" - ProjectSection(SolutionItems) = preProject - .github\FUNDING.yml = .github\FUNDING.yml - .github\dependabot.yml = .github\dependabot.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{DED8434F-5A66-487D-A851-DC06DF32242D}" - ProjectSection(SolutionItems) = preProject - .github\workflows\publish.yml = .github\workflows\publish.yml - .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml - .github\workflows\build.yml = .github\workflows\build.yml - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {55EC0EB9-96FC-4CB5-B8AB-3F4A904AC97C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {55EC0EB9-96FC-4CB5-B8AB-3F4A904AC97C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {55EC0EB9-96FC-4CB5-B8AB-3F4A904AC97C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {55EC0EB9-96FC-4CB5-B8AB-3F4A904AC97C}.Release|Any CPU.Build.0 = Release|Any CPU - {2801B18E-31FC-4B99-AAD7-7259CD197D8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2801B18E-31FC-4B99-AAD7-7259CD197D8D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2801B18E-31FC-4B99-AAD7-7259CD197D8D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2801B18E-31FC-4B99-AAD7-7259CD197D8D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {6467C57A-09C8-44F5-88AC-95F87E14ED73} - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D50C3657-84AF-4631-9291-21EEF38F94B6} = {50AF4859-D9C1-465A-97E5-66B7B944FE7D} - {DED8434F-5A66-487D-A851-DC06DF32242D} = {D50C3657-84AF-4631-9291-21EEF38F94B6} - EndGlobalSection -EndGlobal diff --git a/SFP.sln.DotSettings b/SFP.sln.DotSettings deleted file mode 100644 index 88257cc..0000000 --- a/SFP.sln.DotSettings +++ /dev/null @@ -1,6 +0,0 @@ - - True - True - True - True - True diff --git a/SFP.slnx b/SFP.slnx new file mode 100644 index 0000000..2fb4f1a --- /dev/null +++ b/SFP.slnx @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/SFP/Models/Injection/BrowserEndpoint.cs b/SFP/Models/Injection/BrowserEndpoint.cs index afc9174..617444f 100644 --- a/SFP/Models/Injection/BrowserEndpoint.cs +++ b/SFP/Models/Injection/BrowserEndpoint.cs @@ -1,8 +1,10 @@ #region using System.Text.Json.Serialization; + using Flurl; using Flurl.Http; + using SFP.Properties; #endregion @@ -47,4 +49,4 @@ internal static async Task GetBrowserEndpointAsync() throw; } } -} +} \ No newline at end of file diff --git a/SFP/Models/Injection/Config/MillenniumConfig.cs b/SFP/Models/Injection/Config/MillenniumConfig.cs index ccf522e..38c3985 100644 --- a/SFP/Models/Injection/Config/MillenniumConfig.cs +++ b/SFP/Models/Injection/Config/MillenniumConfig.cs @@ -26,4 +26,4 @@ public class Patch public class MillenniumConfig { [JsonPropertyName("patch")] public IEnumerable Patch { get; init; } = []; -} +} \ No newline at end of file diff --git a/SFP/Models/Injection/Config/SfpConfig.cs b/SFP/Models/Injection/Config/SfpConfig.cs index 4aa9a89..f0c9a08 100644 --- a/SFP/Models/Injection/Config/SfpConfig.cs +++ b/SFP/Models/Injection/Config/SfpConfig.cs @@ -1,6 +1,5 @@ #region -using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; @@ -11,21 +10,15 @@ namespace SFP.Models.Injection.Config; public class PatchEntry : IEquatable { - [JsonIgnore] private Regex? _matchRegex; - public string MatchRegexString { get; init; } = string.Empty; public string TargetCss { get; init; } = string.Empty; public string TargetJs { get; init; } = string.Empty; - [JsonIgnore] public Regex MatchRegex => _matchRegex ??= new Regex(MatchRegexString, RegexOptions.Compiled); + [field: JsonIgnore][JsonIgnore] public Regex MatchRegex => field ??= new Regex(MatchRegexString, RegexOptions.Compiled); public bool Equals(PatchEntry? entry) { - if (entry == null) - { - return false; - } - return MatchRegexString == entry.MatchRegexString && TargetCss == entry.TargetCss && + return entry != null && MatchRegexString == entry.MatchRegexString && TargetCss == entry.TargetCss && TargetJs == entry.TargetJs; } @@ -43,75 +36,75 @@ public override int GetHashCode() public class SfpConfig { [JsonIgnore] - private static readonly IReadOnlyCollection s_defaultPatches = + private static readonly IReadOnlyCollection DefaultPatches = [ - new PatchEntry + new() { MatchRegexString = "https://.*.steampowered.com", TargetCss = "webkit.css", TargetJs = "webkit.js" }, - new PatchEntry { MatchRegexString = "https://steamcommunity.com", TargetCss = "webkit.css", TargetJs = "webkit.js" }, - new PatchEntry + new() { MatchRegexString = "https://steamcommunity.com", TargetCss = "webkit.css", TargetJs = "webkit.js" }, + new() { MatchRegexString = "^Steam$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { MatchRegexString = "^OverlayBrowser_Browser$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { MatchRegexString = "^SP Overlay:", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { - MatchRegexString = @"Supernav$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" + MatchRegexString = "Supernav$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { - MatchRegexString = @"^notificationtoasts_", + MatchRegexString = "^notificationtoasts_", TargetCss = "notifications.custom.css", TargetJs = "notifications.custom.js" }, - new PatchEntry + new() { - MatchRegexString = @"^SteamBrowser_Find$", + MatchRegexString = "^SteamBrowser_Find$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { MatchRegexString = @"^OverlayTab\d+_Find$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { MatchRegexString = "^Steam Big Picture Mode$", TargetCss = "bigpicture.custom.css", TargetJs = "bigpicture.custom.js" }, - new PatchEntry + new() { MatchRegexString = "^QuickAccess_", TargetCss = "bigpicture.custom.css", TargetJs = "bigpicture.custom.js" }, - new PatchEntry + new() { MatchRegexString = "^MainMenu_", TargetCss = "bigpicture.custom.css", TargetJs = "bigpicture.custom.js" }, // Friends List and Chat - new PatchEntry + new() { - MatchRegexString = @".friendsui-container", TargetCss = "friends.custom.css", TargetJs = "friends.custom.js" + MatchRegexString = ".friendsui-container", TargetCss = "friends.custom.css", TargetJs = "friends.custom.js" }, - new PatchEntry { MatchRegexString = "Menu$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { MatchRegexString = "Menu$", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, + new() { // Steam Dialog popups (Settings, Game Properties, etc) MatchRegexString = ".ModalDialogPopup", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" }, - new PatchEntry + new() { // Sign In Page MatchRegexString = ".FullModalOverlay", TargetCss = "libraryroot.custom.css", TargetJs = "libraryroot.custom.js" @@ -120,15 +113,14 @@ public class SfpConfig [JsonIgnore] private static SfpConfig? s_sfpConfig; - [JsonIgnore] public bool _isFromMillennium; + [JsonIgnore] public bool IsFromMillennium; public bool UseDefaultPatches { get; init; } public IEnumerable Patches { get; init; } = GetDefaultPatches(); [JsonIgnore] public static SfpConfig DefaultConfig { get; } = new(); - [ExcludeFromCodeCoverage] private static IEnumerable GetDefaultPatches() { - return s_defaultPatches; + return DefaultPatches; } public static SfpConfig GetConfig(bool overWrite = false) @@ -175,7 +167,7 @@ public static SfpConfig GetConfig(bool overWrite = false) { json = FromMillenniumConfig(millenniumConfig); s_sfpConfig = json; - s_sfpConfig._isFromMillennium = true; + s_sfpConfig.IsFromMillennium = true; Log.Logger.Info("Using config.json from Millennium skin"); } } @@ -200,10 +192,10 @@ public static SfpConfig FromMillenniumConfig(MillenniumConfig millenniumConfig) return new SfpConfig { UseDefaultPatches = false, - Patches = millenniumConfig.Patch + Patches = [.. millenniumConfig.Patch .Where(p => !string.IsNullOrWhiteSpace(p.Url) && (!string.IsNullOrWhiteSpace(p.Css) || !string.IsNullOrWhiteSpace(p.Js))) - .Select(p => new PatchEntry { MatchRegexString = p.Url, TargetCss = p.Css, TargetJs = p.Js }).ToArray() + .Select(p => new PatchEntry { MatchRegexString = p.Url, TargetCss = p.Css, TargetJs = p.Js })] }; } -} +} \ No newline at end of file diff --git a/SFP/Models/Injection/Injector.cs b/SFP/Models/Injection/Injector.cs index c96ee74..b71d645 100644 --- a/SFP/Models/Injection/Injector.cs +++ b/SFP/Models/Injection/Injector.cs @@ -2,7 +2,9 @@ using System.Text; using System.Text.RegularExpressions; + using PuppeteerSharp; + using SFP.Models.Injection.Config; using SFP.Properties; @@ -13,10 +15,10 @@ namespace SFP.Models.Injection; public static partial class Injector { private static IBrowser? s_browser; - private static bool s_isInjected; private static bool s_manualDisconnect; - private static readonly SemaphoreSlim s_semaphore = new(1, 1); - public static bool IsInjected => s_isInjected && s_browser != null; + private static readonly SemaphoreSlim Semaphore = new(1, 1); + private static bool s_webkitReloaded; + public static bool IsInjected { get => field && s_browser != null; private set; } public static event EventHandler? InjectionStateChanged; @@ -33,7 +35,7 @@ public static partial class Injector "SystemAccentColorDark3" ]; - public static string ColorsCss { get; private set; } = string.Empty; + private static string ColorsCss { get; set; } = string.Empty; public static async Task StartInjectionAsync(bool noError = false) { @@ -43,7 +45,7 @@ public static async Task StartInjectionAsync(bool noError = false) return; } - if (!await s_semaphore.WaitAsync(TimeSpan.Zero)) + if (!await Semaphore.WaitAsync(TimeSpan.Zero)) { Log.Logger.Warn("Injection already in progress, skipping injection"); return; @@ -68,17 +70,18 @@ public static async Task StartInjectionAsync(bool noError = false) BrowserWSEndpoint = browserEndpoint, DefaultViewport = null, EnqueueAsyncMessages = true, - EnqueueTransportMessages = true, + EnqueueTransportMessages = true }; Log.Logger.Info("Connecting to " + browserEndpoint); + s_webkitReloaded = false; s_browser = await Puppeteer.ConnectAsync(options); s_browser.Disconnected += OnDisconnected; Log.Logger.Info("Connected"); s_browser.TargetCreated += Browser_TargetUpdate; s_browser.TargetChanged += Browser_TargetUpdate; await InjectAsync(); - s_isInjected = true; + IsInjected = true; InjectionStateChanged?.Invoke(null, EventArgs.Empty); Log.Logger.Info("Initial injection finished"); } @@ -94,7 +97,7 @@ public static async Task StartInjectionAsync(bool noError = false) } finally { - s_semaphore.Release(); + Semaphore.Release(); } } @@ -121,15 +124,16 @@ public static void StopInjection() { Log.Logger.Info("Disconnecting from Steam instance"); } - s_isInjected = false; + IsInjected = false; s_manualDisconnect = true; s_browser?.Disconnect(); s_browser = null; + s_webkitReloaded = false; InjectionStateChanged?.Invoke(null, EventArgs.Empty); } // injection after reload occurs before content is fully loaded, needs investigation - public static async void Reload() + public static async Task Reload() { if (s_browser == null) { @@ -156,27 +160,39 @@ public static async void Reload() } } +#pragma warning disable EPC27 private static async void OnDisconnected(object? sender, EventArgs e) +#pragma warning restore EPC27 { - Log.Logger.Info("Disconnected from Steam instance"); - var manualDisconnect = s_manualDisconnect; - StopInjection(); - if (manualDisconnect) + try { - s_manualDisconnect = false; - return; - } + Log.Logger.Info("Disconnected from Steam instance"); + var manualDisconnect = s_manualDisconnect; + StopInjection(); + if (manualDisconnect) + { + s_manualDisconnect = false; + return; + } - await Task.Delay(500); - if (!Steam.IsSteamWebHelperRunning) + await Task.Delay(500); + if (!Steam.IsSteamWebHelperRunning) + { + return; + } + Log.Logger.Warn("Unexpected disconnect, trying to reconnect to Steam instance"); + await Steam.TryInject(); + } + catch (Exception ex) { - return; + Log.Logger.Error("Error in OnDisconnected event handler"); + Log.Logger.Debug(ex); } - Log.Logger.Warn("Unexpected disconnect, trying to reconnect to Steam instance"); - await Steam.TryInject(); } +#pragma warning disable EPC27 private static async void Browser_TargetUpdate(object? sender, TargetChangedArgs e) +#pragma warning restore EPC27 { try { @@ -193,6 +209,11 @@ private static async void Browser_TargetUpdate(object? sender, TargetChangedArgs Log.Logger.Warn("Puppeteer exception when trying to get page"); Log.Logger.Debug(err); } + catch (Exception err) + { + Log.Logger.Error("Unexpected error in Browser_TargetUpdate event handler"); + Log.Logger.Debug(err); + } } private static async Task ProcessPage(IPage? page) @@ -217,7 +238,7 @@ private static async Task ProcessPage(IPage? page) private static async Task ProcessFrame(IFrame frame) { var config = SfpConfig.GetConfig(); - var patches = config.Patches as PatchEntry[] ?? config.Patches.ToArray(); + var patches = config.Patches as PatchEntry[] ?? [.. config.Patches]; if (!IsFrameWebkit(frame)) { @@ -269,7 +290,7 @@ private static async Task ProcessFrame(IFrame frame) } else { - switch (config._isFromMillennium) + switch (config.IsFromMillennium) { case false when patch.MatchRegex.IsMatch(title): case true when regex == title: @@ -286,10 +307,10 @@ private static async Task ProcessFrame(IFrame frame) await SetBypassCsp(frame); var url = GetDomainRegex().Match(frame.Url).Groups[1].Value; await DumpFrame(frame, url); - if (!config._isFromMillennium) + if (!config.IsFromMillennium) { var httpPatches = patches.Where(p => p.MatchRegexString.StartsWith("http", StringComparison.CurrentCultureIgnoreCase)); - var patchEntries = httpPatches as PatchEntry[] ?? httpPatches.ToArray(); + var patchEntries = httpPatches as PatchEntry[] ?? [.. httpPatches]; var patch = patchEntries.FirstOrDefault(p => p.MatchRegex.IsMatch(frame.Url)); if (patch != null) { @@ -360,9 +381,19 @@ private static async Task SetBypassCsp(IFrame frame) } } +#pragma warning disable EPC27 private static async void Frame_Navigate(object? sender, FrameEventArgs e) +#pragma warning restore EPC27 { - await ProcessFrame(e.Frame); + try + { + await ProcessFrame(e.Frame); + } + catch (Exception ex) + { + Log.Logger.Error("Error in Frame_Navigate event handler"); + Log.Logger.Debug(ex); + } } private static async Task InjectAsync(IFrame frame, PatchEntry patch, string tabFriendlyName) @@ -404,27 +435,36 @@ private static async Task InjectResourceAsync(IFrame frame, string fileRelativeP fileRelativePath = $"{relativeSkinDir}{fileRelativePath}"; var injectString = - $@"function inject() {{ - if (document.getElementById('{frame.Id}{resourceType}') !== null) return; - const element = document.createElement('{(resourceType == "css" ? "link" : "script")}'); - element.id = '{frame.Id}{resourceType}'; - {(resourceType == "css" ? "element.rel = 'stylesheet';" : "")} - element.type = '{(resourceType == "css" ? "text/css" : "module")}'; - element.{(resourceType == "css" ? "href" : "src")} = 'https://steamloopback.host/{fileRelativePath}'; - document.head.append(element); - }} - if ((document.readyState === 'loading') && '{IsFrameWebkit(frame)}' === 'True') {{ - addEventListener('DOMContentLoaded', inject); - }} else {{ - inject(); - }} - "; + $$""" + function inject() { + if ('{{IsFrameWebkit(frame)}}' === 'True' && '{{s_webkitReloaded}}' === 'False') { + location.reload(); + } + if (document.getElementById('{{frame.Id}}{{resourceType}}') !== null) return; + const element = document.createElement('{{(resourceType == "css" ? "link" : "script")}}'); + element.id = '{{frame.Id}}{{resourceType}}'; + {{(resourceType == "css" ? "element.rel = 'stylesheet';" : "")}} + element.type = '{{(resourceType == "css" ? "text/css" : "module")}}'; + element.{{(resourceType == "css" ? "href" : "src")}} = 'https://steamloopback.host/{{fileRelativePath}}'; + document.head.append(element); + } + if ((document.readyState === 'loading') && '{{IsFrameWebkit(frame)}}' === 'True') { + addEventListener('DOMContentLoaded', inject); + } else { + inject(); + } + """; try { if (!IsFrameWebkit(frame) && resourceType == "js") { await Task.Delay(500); } + + if (!s_webkitReloaded && IsFrameWebkit(frame)) + { + s_webkitReloaded = true; + } await frame.EvaluateExpressionAsync(injectString); Log.Logger.Info($"Injected {Path.GetFileName(fileRelativePath)} into {tabFriendlyName} from patch {patchName}"); } @@ -455,7 +495,7 @@ private static async Task UpdateColorInPage(IPage page) } } - public static async void UpdateColorScheme(string? colorScheme = null) + public static async Task UpdateColorScheme(string? colorScheme = null) { if (s_browser == null || !Settings.Default.UseAppTheme && colorScheme == null) { @@ -483,7 +523,7 @@ public static void SetColorScheme(string themeVariant) public static void SetAccentColors(IEnumerable colors) { - var colorsArr = colors as string[] ?? colors.ToArray(); + var colorsArr = colors as string[] ?? [.. colors]; var colorsCss = new StringBuilder(); colorsCss.Append(":root { "); for (var i = 0; i < 7; i++) @@ -494,7 +534,7 @@ public static void SetAccentColors(IEnumerable colors) ColorsCss = colorsCss.ToString(); } - public static async void UpdateSystemAccentColors(bool useAccentColors = true) + public static async Task UpdateSystemAccentColors(bool useAccentColors = true) { if (s_browser == null || !Settings.Default.UseAppTheme && useAccentColors) { @@ -507,18 +547,20 @@ public static async void UpdateSystemAccentColors(bool useAccentColors = true) : pages.Select(async page => { var injectString = - $@"function injectAcc() {{ - var element = document.getElementById('SystemAccentColorInjection'); - if (element) {{ - element.parentNode.removeChild(element); - }} - }} - if ((document.readyState === 'loading') && '{IsFrameWebkit(page.MainFrame)}' === 'True') {{ - addEventListener('DOMContentLoaded', injectAcc); - }} else {{ - injectAcc(); - }} - "; + $$""" + function injectAcc() { + var element = document.getElementById('SystemAccentColorInjection'); + if (element) { + element.parentNode.removeChild(element); + } + } + if ((document.readyState === 'loading') && '{{IsFrameWebkit(page.MainFrame)}}' === 'True') { + addEventListener('DOMContentLoaded', injectAcc); + } else { + injectAcc(); + } + + """; await page.EvaluateExpressionAsync(injectString); }); await Task.WhenAll(processTasks); @@ -527,25 +569,27 @@ public static async void UpdateSystemAccentColors(bool useAccentColors = true) private static async Task UpdateSystemAccentColorsInPage(IPage page) { var injectString = - $@"function injectAcc() {{ - var element = document.getElementById('SystemAccentColorInjection'); - if (element) {{ - element.parentNode.removeChild(element); - }} - element = document.createElement('style'); - element.id = 'SystemAccentColorInjection'; - element.innerHTML = `{ColorsCss}`; - document.head.append(element); - }} - if ((document.readyState === 'loading') && '{IsFrameWebkit(page.MainFrame)}' === 'True') {{ - addEventListener('DOMContentLoaded', injectAcc); - }} else {{ - injectAcc(); - }} - "; + $$""" + function injectAcc() { + var element = document.getElementById('SystemAccentColorInjection'); + if (element) { + element.parentNode.removeChild(element); + } + element = document.createElement('style'); + element.id = 'SystemAccentColorInjection'; + element.innerHTML = `{{ColorsCss}}`; + document.head.append(element); + } + if ((document.readyState === 'loading') && '{{IsFrameWebkit(page.MainFrame)}}' === 'True') { + addEventListener('DOMContentLoaded', injectAcc); + } else { + injectAcc(); + } + + """; await page.EvaluateExpressionAsync(injectString); } [GeneratedRegex(@"^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/?\n]+)")] private static partial Regex GetDomainRegex(); -} +} \ No newline at end of file diff --git a/SFP/Models/Log.cs b/SFP/Models/Log.cs index 76e51d4..1777e10 100644 --- a/SFP/Models/Log.cs +++ b/SFP/Models/Log.cs @@ -9,4 +9,4 @@ namespace SFP.Models; public static class Log { public static readonly Logger Logger = LogManager.GetCurrentClassLogger(); -} +} \ No newline at end of file diff --git a/SFP/Models/Steam.cs b/SFP/Models/Steam.cs index 3bef3ad..1740100 100644 --- a/SFP/Models/Steam.cs +++ b/SFP/Models/Steam.cs @@ -3,7 +3,9 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using FileWatcherEx; + using SFP.Models.Injection; using SFP.Properties; @@ -16,19 +18,18 @@ public static class Steam private static FileSystemWatcherEx? s_watcher; private static Process? s_steamProcess; private static bool s_injectOnce; - private static readonly SemaphoreSlim s_semaphore = new(1, 1); + private static readonly SemaphoreSlim Semaphore = new(1, 1); - private static readonly int s_processAmount = OperatingSystem.IsWindows() ? 3 : OperatingSystem.IsMacOS() ? 4 : 6; - public static bool IsSteamWebHelperRunning => SteamWebHelperProcesses.Length > s_processAmount; + private static readonly int ProcessAmount = OperatingSystem.IsWindows() ? 3 : OperatingSystem.IsMacOS() ? 4 : 6; + public static bool IsSteamWebHelperRunning => SteamWebHelperProcesses.Length > ProcessAmount; public static bool IsSteamRunning => SteamProcess is not null; - private static Process[] SteamWebHelperProcesses => Process.GetProcessesByName(SteamWebHelperProcName) - .Where(p => p.ProcessName.Equals(SteamWebHelperProcName, StringComparison.OrdinalIgnoreCase)).ToArray(); + private static Process[] SteamWebHelperProcesses => [.. Process.GetProcessesByName(SteamWebHelperProcName).Where(p => p.ProcessName.Equals(SteamWebHelperProcName, StringComparison.OrdinalIgnoreCase))]; private static Process? SteamProcess => Process.GetProcessesByName(SteamProcName) .FirstOrDefault(p => p.ProcessName.Equals(SteamProcName, StringComparison.OrdinalIgnoreCase)); - private static string SteamWebHelperProcName => OperatingSystem.IsMacOS() ? "Steam Helper" : @"steamwebhelper"; + private static string SteamWebHelperProcName => OperatingSystem.IsMacOS() ? "Steam Helper" : "steamwebhelper"; private static string SteamProcName => OperatingSystem.IsMacOS() ? "steam_osx" : "steam"; @@ -54,17 +55,11 @@ public static class Steam private static string? GetSteamRootDir() { - if (OperatingSystem.IsWindows()) - { - return SteamDir; - } - - if (OperatingSystem.IsLinux()) - { - return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".steam"); - } - - return OperatingSystem.IsMacOS() + return OperatingSystem.IsWindows() + ? SteamDir + : OperatingSystem.IsLinux() + ? Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".steam") + : OperatingSystem.IsMacOS() ? Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Steam") : null; @@ -73,17 +68,11 @@ public static class Steam private static string? GetSteamDir() { var dir = Settings.Default.SteamDirectory; - if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir)) - { - return dir; - } - - if (OperatingSystem.IsWindows()) - { - return GetRegistryData(@"SOFTWARE\Valve\Steam", "SteamPath")?.ToString()?.Replace(@"/", @"\"); - } - - return OperatingSystem.IsLinux() + return !string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir) + ? dir + : OperatingSystem.IsWindows() + ? (GetRegistryData(@"SOFTWARE\Valve\Steam", "SteamPath")?.ToString()?.Replace("/", @"\")) + : OperatingSystem.IsLinux() ? Path.Join(SteamRootDir, "steam") : // OSX @@ -204,7 +193,7 @@ public static Task StartSteam(string? args = null) return Task.CompletedTask; } - public static async void RunRestartSteam() + public static async Task RunRestartSteam() { await Task.Run(() => RestartSteam()); } @@ -234,11 +223,21 @@ private static async Task RestartSteam(string? args = null) } } +#pragma warning disable EPC27 private static async void OnSteamExited(object? sender, EventArgs e) +#pragma warning restore EPC27 { - s_steamProcess?.Dispose(); - s_steamProcess = null; - await StartSteam(); + try + { + s_steamProcess?.Dispose(); + s_steamProcess = null; + await StartSteam(); + } + catch (Exception ex) + { + Log.Logger.Error("Error in OnSteamExited event handler"); + Log.Logger.Debug(ex); + } } public static void StartMonitorSteam() @@ -272,15 +271,25 @@ public static void StartMonitorSteam() } } +#pragma warning disable EPC27 private static async void OnCrashFileCreated(object? sender, FileChangedEvent e) +#pragma warning restore EPC27 { - SteamStarted?.Invoke(null, EventArgs.Empty); - if (!Settings.Default.InjectOnSteamStart && !s_injectOnce) + try { - return; + SteamStarted?.Invoke(null, EventArgs.Empty); + if (!Settings.Default.InjectOnSteamStart && !s_injectOnce) + { + return; + } + s_injectOnce = false; + await TryInject(); + } + catch (Exception ex) + { + Log.Logger.Error("Error in OnCrashFileCreated event handler"); + Log.Logger.Debug(ex); } - s_injectOnce = false; - await TryInject(); } private static void OnCrashFileDeleted(object? sender, FileChangedEvent e) @@ -293,7 +302,7 @@ public static List GetCommandLine() return Utils.GetCommandLine(SteamProcess); } - public static async void RunTryInject() + public static async Task RunTryInject() { Log.Logger.Info("Starting injection..."); await Task.Run(TryInject); @@ -301,7 +310,7 @@ public static async void RunTryInject() public static async Task TryInject() { - if (!await s_semaphore.WaitAsync(TimeSpan.Zero)) + if (!await Semaphore.WaitAsync(TimeSpan.Zero)) { Log.Logger.Warn("Injection already in progress"); return; @@ -345,7 +354,7 @@ public static async Task TryInject() } finally { - s_semaphore.Release(); + Semaphore.Release(); } } @@ -388,26 +397,26 @@ private static async Task CheckForMissingArgumentsAsync() private static void AppendArgs(ref string args) { - const string DebuggingString = @"-cef-enable-debugging"; - const string BootstrapString = @"-skipinitialbootstrap"; - const string PortString = @"-devtools-port"; + const string debuggingString = "-cef-enable-debugging"; + const string bootstrapString = "-skipinitialbootstrap"; + const string portString = "-devtools-port"; var argsList = args.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(); - if (!argsList.Contains(DebuggingString)) + if (!argsList.Contains(debuggingString)) { - argsList.Add(DebuggingString); + argsList.Add(debuggingString); } - if (OperatingSystem.IsMacOS() && !argsList.Contains(BootstrapString)) + if (OperatingSystem.IsMacOS() && !argsList.Contains(bootstrapString)) { - argsList.Add(BootstrapString); + argsList.Add(bootstrapString); } - if (!argsList.Contains(PortString)) + if (!argsList.Contains(portString)) { - argsList.Add(PortString + " " + Settings.Default.SteamCefPort); + argsList.Add(portString + " " + Settings.Default.SteamCefPort); } args = string.Join(" ", argsList); } -} +} \ No newline at end of file diff --git a/SFP/Models/Unix/Utils.cs b/SFP/Models/Unix/Utils.cs index 4600cc3..32d3b20 100644 --- a/SFP/Models/Unix/Utils.cs +++ b/SFP/Models/Unix/Utils.cs @@ -50,4 +50,4 @@ private static string RunCommand(string command) return output; } -} +} \ No newline at end of file diff --git a/SFP/Models/Utils.cs b/SFP/Models/Utils.cs index 62c1cbc..c09dd70 100644 --- a/SFP/Models/Utils.cs +++ b/SFP/Models/Utils.cs @@ -25,7 +25,9 @@ public static void OpenUrl(string url) return; } - url = $@"""{url}"""; + url = $""" + "{url}" + """; if (OperatingSystem.IsLinux()) { @@ -45,29 +47,24 @@ public static void OpenUrl(string url) public static List GetCommandLine(Process? process) { - if (process == null) + if (process != null) { - Log.Logger.Warn("Could not get command line, process does not exist."); - return []; - } - - if (OperatingSystem.IsWindows()) - { - return Windows.Utils.GetCommandLine(process); - } - - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - return Unix.Utils.GetCommandLine(process); + return OperatingSystem.IsWindows() + ? Windows.Utils.GetCommandLine(process) + : OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() + ? Unix.Utils.GetCommandLine(process) + : []; } + Log.Logger.Warn("Could not get command line, process does not exist."); return []; + } // ReSharper disable once InconsistentNaming public static string ConvertARGBtoRGBA(string argb) { - if (!argb.StartsWith("#")) + if (!argb.StartsWith('#')) { var color = Color.FromName(argb); if (color is { A: 0, R: 0, G: 0, B: 0 }) @@ -89,4 +86,4 @@ public static string ConvertARGBtoRGBA(string argb) return "#" + rgb + alpha; } -} +} \ No newline at end of file diff --git a/SFP/Models/Windows/Utils.cs b/SFP/Models/Windows/Utils.cs index d0ad2df..0e0ab69 100644 --- a/SFP/Models/Windows/Utils.cs +++ b/SFP/Models/Windows/Utils.cs @@ -2,8 +2,11 @@ using System.Diagnostics; using System.Runtime.Versioning; + using Microsoft.Win32; + using WindowsShortcutFactory; + using WmiLight; #endregion @@ -36,7 +39,8 @@ public static void SetAppRunOnLaunch(bool runOnLaunch) return; } - using WindowsShortcut shortcut = new() { Path = processPath }; + using WindowsShortcut shortcut = new(); + shortcut.Path = processPath; shortcut.Save(shortcutAddress); } @@ -62,4 +66,4 @@ public static List GetCommandLine(Process process) return value; } -} +} \ No newline at end of file diff --git a/SFP/packages.lock.json b/SFP/packages.lock.json index 433a5d2..a323590 100644 --- a/SFP/packages.lock.json +++ b/SFP/packages.lock.json @@ -1,18 +1,18 @@ { "version": 2, "dependencies": { - "net8.0": { + "net10.0": { "BepInEx.AssemblyPublicizer.MSBuild": { "type": "Direct", - "requested": "[0.4.2, )", - "resolved": "0.4.2", - "contentHash": "AwGR6zOQxhWPWmcMdmhCaawC2qYdYHttkGmlXxyvhB1EIUAtuILEle8kKSkzElnbFGZjvw6iabpFi+AYUG4IuA==" + "requested": "[0.4.3, )", + "resolved": "0.4.3", + "contentHash": "Z8Qs3JTTHZG1q0DCqY+HFJ8xp92tkEXWyQ7roeWgUvn20tjv0ZNfAdyy13XrRS1jeEoN/weJc81747TbvvwaDg==" }, "ErrorProne.NET.CoreAnalyzers": { "type": "Direct", - "requested": "[0.7.0-beta.1, )", - "resolved": "0.7.0-beta.1", - "contentHash": "c+Tcp88i2Z+nrUYqEEHroE3tyj57n6F9CB4DDRsPJatWn2RNB2mAmAVVXVg8PcE7QPlUCHD2QURJzKluDQlW5Q==" + "requested": "[0.8.2-beta.1, )", + "resolved": "0.8.2-beta.1", + "contentHash": "8zTBD0fGHSZzwUCqm7b9DykXDBWA8swQ71mB9Jli2n8MYThLrqnJL9xK3rNol21oGFn0q/JfaGYJbSheTXqeYA==" }, "ErrorProne.NET.Structs": { "type": "Direct", @@ -22,9 +22,9 @@ }, "FileWatcherEx": { "type": "Direct", - "requested": "[2.6.0, )", - "resolved": "2.6.0", - "contentHash": "zHDfVCmqguDYzcRhUmsAzUVBMm5G3nvkD5+p3/f5TjqPHcxFvDoEfSKvxZ4/Mid6fVDZJOU+7xxittk+AIE2Bw==" + "requested": "[2.7.0, )", + "resolved": "2.7.0", + "contentHash": "gw4isJWR2mr6UDSHUOGY3lR1fh4OsSRbJOwQz9Wz2V1JMQ6I8OyHB1CG3ekgrTnj+Ik4uXdMzmlPdOfXq2FApQ==" }, "Flurl.Http": { "type": "Direct", @@ -37,15 +37,15 @@ }, "MinVer": { "type": "Direct", - "requested": "[6.0.0, )", - "resolved": "6.0.0", - "contentHash": "+/SsmiySsXJlvQLCGBqaZKNVt3s/Y/HbAdwtop7Km2CnuZbaScoqkWJEBQ5Cy9ebkn6kCYKrHsXgwrFdTgcb3g==" + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "2lMTCQl5bGP4iv0JNkockPnyllC6eHLz+CoK2ICvalvHod+exXSxueu9hq+zNkU7bZBJf8wMfeRC/Edn8AGmEg==" }, "NLog": { "type": "Direct", - "requested": "[5.3.4, )", - "resolved": "5.3.4", - "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A==" + "requested": "[6.1.0, )", + "resolved": "6.1.0", + "contentHash": "K4UdxS4fuJeCM0sm+JTXWoJgOtcXX7X5kvE94bR07UB8JOB6A2+ski7q1tf2qdv+N0H3gsoWtteScJCHa3+zpw==" }, "PortableJsonSettingsProvider": { "type": "Direct", @@ -67,8 +67,7 @@ "Microsoft.Bcl.AsyncInterfaces": "1.1.0", "Microsoft.Extensions.Logging": "2.0.2", "Newtonsoft.Json": "13.0.1", - "SharpZipLib": "1.3.3", - "System.Text.Encodings.Web": "6.0.0" + "SharpZipLib": "1.3.3" } }, "WindowsShortcutFactory": { @@ -79,9 +78,9 @@ }, "WmiLight": { "type": "Direct", - "requested": "[6.9.1, )", - "resolved": "6.9.1", - "contentHash": "TDQm/8wtenGNZw4NzSdOZuo+ox32Ix6/f1mxIaYEBaTJ+h8RjRhCQeAXpM0dXCOPTYKnlPpbuDZ5VeCL/hxiDA==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" }, "Flurl": { "type": "Transitive", @@ -93,8 +92,7 @@ "resolved": "2.2.0", "contentHash": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", "dependencies": { - "Microsoft.Net.Http.Headers": "2.2.0", - "System.Text.Encodings.Web": "4.5.0" + "Microsoft.Net.Http.Headers": "2.2.0" } }, "Microsoft.Bcl.AsyncInterfaces": { @@ -134,19 +132,14 @@ "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "2.2.0", - "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", - "dependencies": { - "System.Memory": "4.5.1", - "System.Runtime.CompilerServices.Unsafe": "4.5.1" - } + "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==" }, "Microsoft.Net.Http.Headers": { "type": "Transitive", "resolved": "2.2.0", "contentHash": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", "dependencies": { - "Microsoft.Extensions.Primitives": "2.2.0", - "System.Buffers": "4.5.0" + "Microsoft.Extensions.Primitives": "2.2.0" } }, "Microsoft.Win32.SystemEvents": { @@ -164,11 +157,6 @@ "resolved": "1.3.3", "contentHash": "N8+hwhsKZm25tDJfWpBSW7EGhH/R7EMuiX+KJ4C4u+fCWVc1lJ5zg1u3S1RPPVYgTqhx/C3hxrqUpi6RwK5+Tg==" }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" - }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "7.0.0", @@ -192,16 +180,6 @@ "Microsoft.Win32.SystemEvents": "7.0.0" } }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "sDJYJpGtTgx+23Ayu5euxG5mAXWdkDb4+b0rD0Cab0M1oQS9H0HXGPriKcqpXuiJDTV7fTp/d+fMDJmnr6sNvA==" - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" - }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "7.0.0", @@ -215,14 +193,6 @@ "System.Windows.Extensions": "7.0.0" } }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -232,12 +202,12 @@ } } }, - "net8.0/linux-x64": { + "net10.0/linux-x64": { "WmiLight": { "type": "Direct", - "requested": "[6.9.1, )", - "resolved": "6.9.1", - "contentHash": "TDQm/8wtenGNZw4NzSdOZuo+ox32Ix6/f1mxIaYEBaTJ+h8RjRhCQeAXpM0dXCOPTYKnlPpbuDZ5VeCL/hxiDA==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", @@ -262,14 +232,6 @@ "resolved": "7.0.0", "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -279,12 +241,12 @@ } } }, - "net8.0/osx-x64": { + "net10.0/osx-x64": { "WmiLight": { "type": "Direct", - "requested": "[6.9.1, )", - "resolved": "6.9.1", - "contentHash": "TDQm/8wtenGNZw4NzSdOZuo+ox32Ix6/f1mxIaYEBaTJ+h8RjRhCQeAXpM0dXCOPTYKnlPpbuDZ5VeCL/hxiDA==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", @@ -309,14 +271,6 @@ "resolved": "7.0.0", "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -326,12 +280,12 @@ } } }, - "net8.0/win-x64": { + "net10.0/win-x64": { "WmiLight": { "type": "Direct", - "requested": "[6.9.1, )", - "resolved": "6.9.1", - "contentHash": "TDQm/8wtenGNZw4NzSdOZuo+ox32Ix6/f1mxIaYEBaTJ+h8RjRhCQeAXpM0dXCOPTYKnlPpbuDZ5VeCL/hxiDA==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", @@ -356,14 +310,6 @@ "resolved": "7.0.0", "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -374,4 +320,4 @@ } } } -} +} \ No newline at end of file diff --git a/SFPCommon.props b/SFPCommon.props index 644fbf1..4b147b5 100644 --- a/SFPCommon.props +++ b/SFPCommon.props @@ -1,12 +1,10 @@ - net8.0 + net10.0 win-x64;linux-x64;osx-x64 enable enable - - - false + true true embedded diff --git a/SFP_UI/App.axaml b/SFP_UI/App.axaml index 8348c8f..f977d1b 100644 --- a/SFP_UI/App.axaml +++ b/SFP_UI/App.axaml @@ -26,18 +26,18 @@ + Command="{Binding RunInjectCommand}"/> + Command="{Binding RunSteamCommand}"/> + Command="{Binding ShowSettingsCommand}"/> - + Command="{Binding ShowWindowCommand}"/> + - + \ No newline at end of file diff --git a/SFP_UI/App.axaml.cs b/SFP_UI/App.axaml.cs index f9033f5..6075f6c 100644 --- a/SFP_UI/App.axaml.cs +++ b/SFP_UI/App.axaml.cs @@ -1,16 +1,20 @@ #region -using System.Diagnostics.CodeAnalysis; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Styling; using Avalonia.Threading; + using FluentAvalonia.Styling; + +using JetBrains.Annotations; + using SFP.Models; using SFP.Models.Injection; using SFP.Properties; + using SFP_UI.Models; using SFP_UI.ViewModels; using SFP_UI.Views; @@ -31,51 +35,53 @@ public override void Initialize() AvaloniaXamlLoader.Load(this); } +#pragma warning disable EPC27 public override async void OnFrameworkInitializationCompleted() +#pragma warning restore EPC27 { - if (!Settings.Default.StartMinimized || !Settings.Default.MinimizeToTray) + try { - StartMainWindow(); - } + if (!Settings.Default.StartMinimized || !Settings.Default.MinimizeToTray) + { + StartMainWindow(); + } - base.OnFrameworkInitializationCompleted(); + base.OnFrameworkInitializationCompleted(); - if (Design.IsDesignMode) - { - return; - } + if (Design.IsDesignMode) + { + return; + } - SetIconsState(Settings.Default.ShowTrayIcon); + SetIconsState(Settings.Default.ShowTrayIcon); - Injector.SetColorScheme(ActualThemeVariant.ToString()); - Injector.SetAccentColors(GetColorValues()); - ActualThemeVariantChanged += (_, _) => - { Injector.SetColorScheme(ActualThemeVariant.ToString()); - Injector.UpdateColorScheme(); - }; - if (Current?.PlatformSettings != null) - { - Current.PlatformSettings.ColorValuesChanged += (_, _) => + Injector.SetAccentColors(GetColorValues()); + ActualThemeVariantChanged += (_, _) => _ = OnActualThemeVariantChangedAsync(); + if (Current?.PlatformSettings != null) { - Injector.SetAccentColors(GetColorValues()); - Injector.UpdateSystemAccentColors(); - }; - } - else - { - Log.Logger.Warn("PlatformSettings is null, can't update system accent colors"); - } + Current.PlatformSettings.ColorValuesChanged += (_, _) => _ = OnColorValuesChangedAsync(); + } + else + { + Log.Logger.Warn("PlatformSettings is null, can't update system accent colors"); + } - await HandleStartupTasks(); + await HandleStartupTasks(); - if (Settings.Default.CheckForUpdates) + if (Settings.Default.CheckForUpdates) + { + Dispatcher.UIThread.Post(() => _ = UpdateChecker.CheckForUpdates()); + } + } + catch (Exception ex) { - Dispatcher.UIThread.Post(() => _ = UpdateChecker.CheckForUpdates()); + Log.Logger.Error("Error in OnFrameworkInitializationCompleted event handler"); + Log.Logger.Debug(ex); } } - private static IEnumerable GetColorValues() + private static string[] GetColorValues() { if (Current!.Styles[0] is not FluentAvaloniaTheme faTheme) { @@ -129,10 +135,7 @@ public static void SetIconsState(bool state) public static void SetApplicationTheme(string themeVariantString) { var faTheme = Current?.Styles.OfType().FirstOrDefault(); - if (faTheme != null) - { - faTheme.PreferSystemTheme = themeVariantString == "System Default"; - } + faTheme?.PreferSystemTheme = themeVariantString == "System Default"; Current!.RequestedThemeVariant = themeVariantString switch { @@ -166,9 +169,41 @@ public static void QuitApplication() } } - [SuppressMessage("ReSharper", "UnusedParameter.Local")] + [UsedImplicitly] private void TrayIcon_OnClicked(object? sender, EventArgs e) { MainWindow.ShowWindow(); } -} + + private static async Task OnActualThemeVariantChangedAsync() + { + try + { + Injector.SetColorScheme(Current!.ActualThemeVariant.ToString()); + await Injector.UpdateColorScheme(); + } + catch (Exception ex) + { + Log.Logger.Error("Error updating color scheme on theme variant changed"); + Log.Logger.Debug(ex); + } + } + + private static async Task OnColorValuesChangedAsync() + { + try + { + if (Current == null) + { + return; + } + Injector.SetAccentColors(GetColorValues()); + await Injector.UpdateSystemAccentColors(); + } + catch (Exception ex) + { + Log.Logger.Error("Error updating system accent colors"); + Log.Logger.Debug(ex); + } + } +} \ No newline at end of file diff --git a/SFP_UI/FodyWeavers.xml b/SFP_UI/FodyWeavers.xml deleted file mode 100644 index e344bfb..0000000 --- a/SFP_UI/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/SFP_UI/Models/OpenFileModel.cs b/SFP_UI/Models/OpenFileModel.cs index 1daa2db..81bb6b3 100644 --- a/SFP_UI/Models/OpenFileModel.cs +++ b/SFP_UI/Models/OpenFileModel.cs @@ -1,14 +1,54 @@ -using System.Reactive; using Avalonia.Controls; -using ReactiveUI; + +using ReactiveUI.SourceGenerators; + using SFP.Models; using SFP.Models.Injection.Config; using SFP.Properties; namespace SFP_UI.Models; -public class OpenFileModel +public partial class FileCommands { + [ReactiveCommand] + private static async Task OpenFile(string relativeFilePath) => await OpenPath(relativeFilePath, false); + + [ReactiveCommand] + private static void OpenDir(string relativeDirPath) => OpenPath(relativeDirPath, true).Wait(); + + private static async Task OpenPath(string path, bool isDirectory) + { + try + { + if (isDirectory) + { + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + } + else + { + if (!File.Exists(path)) + { + await File.Create(path).DisposeAsync(); + } + } + + Utils.OpenUrl(path); + } + catch (Exception e) + { + Log.Logger.Warn("Could not open " + path); + Log.Logger.Debug(e); + } + } +} + +public abstract class OpenFileModel +{ + private static readonly FileCommands FileCommands = new(); + public static void PopulateOpenFileDropDownButton(object itemsCollection) { @@ -36,86 +76,43 @@ public static void PopulateOpenFileDropDownButton(object itemsCollection) case IList nativeMenuItems: { nativeMenuItems.Clear(); - nativeMenuItems.Add(new NativeMenuItem { Header = "steamui/skins/", Command = OpenDir, CommandParameter = Steam.SkinsDir }); - nativeMenuItems.Add(new NativeMenuItem { Header = "Active Skin: " + Settings.Default.SelectedSkin + '/', Command = OpenDir, CommandParameter = Steam.SkinDir }); + nativeMenuItems.Add(new NativeMenuItem { Header = "steamui/skins/", Command = FileCommands.OpenDirCommand, CommandParameter = Steam.SkinsDir }); + nativeMenuItems.Add(new NativeMenuItem { Header = "Active Skin: " + Settings.Default.SelectedSkin + '/', Command = FileCommands.OpenDirCommand, CommandParameter = Steam.SkinDir }); nativeMenuItems.Add(new NativeMenuItemSeparator()); foreach (var cssFile in targetCssFiles) { - nativeMenuItems.Add(new NativeMenuItem { Header = Path.GetFileName(cssFile), Command = OpenFile, CommandParameter = Path.Join(skinDir, cssFile) }); + nativeMenuItems.Add(new NativeMenuItem { Header = Path.GetFileName(cssFile), Command = FileCommands.OpenFileCommand, CommandParameter = Path.Join(skinDir, cssFile) }); } nativeMenuItems.Add(new NativeMenuItemSeparator()); foreach (var jsFile in targetJsFiles) { - nativeMenuItems.Add(new NativeMenuItem { Header = Path.GetFileName(jsFile), Command = OpenFile, CommandParameter = Path.Join(skinDir, jsFile) }); + nativeMenuItems.Add(new NativeMenuItem { Header = Path.GetFileName(jsFile), Command = FileCommands.OpenFileCommand, CommandParameter = Path.Join(skinDir, jsFile) }); } break; } case ItemCollection avaloniaItems: { avaloniaItems.Clear(); - avaloniaItems.Add(new MenuItem { Header = "steamui/skins/", Command = OpenDir, CommandParameter = Steam.SkinsDir }); - avaloniaItems.Add(new MenuItem { Header = "Active Skin: " + Settings.Default.SelectedSkin + '/', Command = OpenDir, CommandParameter = Steam.SkinDir }); + avaloniaItems.Add(new MenuItem { Header = "steamui/skins/", Command = FileCommands.OpenDirCommand, CommandParameter = Steam.SkinsDir }); + avaloniaItems.Add(new MenuItem { Header = "Active Skin: " + Settings.Default.SelectedSkin + '/', Command = FileCommands.OpenDirCommand, CommandParameter = Steam.SkinDir }); avaloniaItems.Add(new Separator()); foreach (var cssFile in targetCssFiles) { - avaloniaItems.Add(new MenuItem { Header = Path.GetFileName(cssFile), Command = OpenFile, CommandParameter = Path.Join(skinDir, cssFile) }); + avaloniaItems.Add(new MenuItem { Header = Path.GetFileName(cssFile), Command = FileCommands.OpenFileCommand, CommandParameter = Path.Join(skinDir, cssFile) }); } avaloniaItems.Add(new Separator()); foreach (var jsFile in targetJsFiles) { - avaloniaItems.Add(new MenuItem { Header = Path.GetFileName(jsFile), Command = OpenFile, CommandParameter = Path.Join(skinDir, jsFile) }); + avaloniaItems.Add(new MenuItem { Header = Path.GetFileName(jsFile), Command = FileCommands.OpenFileCommand, CommandParameter = Path.Join(skinDir, jsFile) }); } break; } } } - - private static ReactiveCommand OpenFile { get; } = ReactiveCommand.CreateFromTask(OpenFileImpl); - private static ReactiveCommand OpenDir { get; } = ReactiveCommand.Create(OpenDirImpl); - - - private static async Task OpenPath(string path, bool isDirectory) - { - try - { - if (isDirectory) - { - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - } - else - { - if (!File.Exists(path)) - { - await File.Create(path).DisposeAsync(); - } - } - - Utils.OpenUrl(path); - } - catch (Exception e) - { - Log.Logger.Warn("Could not open " + path); - Log.Logger.Debug(e); - } - } - - private static async Task OpenFileImpl(string relativeFilePath) - { - await OpenPath(relativeFilePath, false); - } - - private static void OpenDirImpl(string relativeDirPath) - { - OpenPath(relativeDirPath, true).Wait(); - } - -} +} \ No newline at end of file diff --git a/SFP_UI/Models/UpdateChecker.cs b/SFP_UI/Models/UpdateChecker.cs index 97949fd..3005a41 100644 --- a/SFP_UI/Models/UpdateChecker.cs +++ b/SFP_UI/Models/UpdateChecker.cs @@ -2,9 +2,15 @@ using System.Reflection; using System.Text.Json.Serialization; + +using JetBrains.Annotations; + using Semver; + using SFP_UI.ViewModels; + using SFP.Models; + #if RELEASE using System.Net.Http.Headers; using Flurl.Http; @@ -12,6 +18,8 @@ #if DEBUG using System.Text.Json; // ReSharper disable ConditionalAccessQualifierIsNonNullableAccordingToAPIContract +// ReSharper disable HeuristicUnreachableCode +// ReSharper disable UnusedParameter.Local #endif #endregion @@ -85,8 +93,7 @@ private static async Task GetLatestVersionAsync(bool preRelease) } } -internal struct Release +internal readonly struct Release { - // ReSharper disable once UnusedAutoPropertyAccessor.Global - [JsonPropertyName("tag_name")] public string TagName { get; set; } -} + [JsonPropertyName("tag_name")] public string TagName { get; [UsedImplicitly] init; } +} \ No newline at end of file diff --git a/SFP_UI/Pages/MainPage.axaml b/SFP_UI/Pages/MainPage.axaml index c7a48b7..bc1d86f 100644 --- a/SFP_UI/Pages/MainPage.axaml +++ b/SFP_UI/Pages/MainPage.axaml @@ -11,7 +11,7 @@ CloseButtonContent="Dismiss" PreferredPlacement="Center" Name="UpdateNotification" - ActionButtonCommand="{Binding UpdateNotificationView}" + ActionButtonCommand="{Binding UpdateNotificationViewCommand}" ActionButtonCommandParameter="https://github.com/PhantomGamers/SFP/releases" Content="{Binding UpdateNotificationContent}" IsOpen="{Binding UpdateNotificationIsOpen}" /> @@ -21,7 +21,7 @@ - + + @@ -67,7 +67,7 @@ - + diff --git a/SFP_UI/Pages/SettingsPage.axaml.cs b/SFP_UI/Pages/SettingsPage.axaml.cs index 3e51de7..ce43079 100644 --- a/SFP_UI/Pages/SettingsPage.axaml.cs +++ b/SFP_UI/Pages/SettingsPage.axaml.cs @@ -1,11 +1,14 @@ #region using System.Text; + using Avalonia.Controls; + using SFP.Models; using SFP.Models.Injection; using SFP.Models.Injection.Config; using SFP.Properties; + using SFP_UI.ViewModels; #endregion @@ -77,14 +80,24 @@ private void PopulateSteamSkinComboBox() SteamSkinComboBox.SelectionChanged += SteamSkinComboBox_SelectionChanged; } - private void SteamSkinComboBox_SelectionChanged(object? sender, SelectionChangedEventArgs e) +#pragma warning disable EPC27 + private async void SteamSkinComboBox_SelectionChanged(object? sender, SelectionChangedEventArgs e) +#pragma warning restore EPC27 { - var value = SteamSkinComboBox.SelectedValue?.ToString(); - Log.Logger.Info("Switching to skin {Skin}", value); - Settings.Default.SelectedSkin = value; - Settings.Default.Save(); - _ = Steam.GetRelativeSkinDir(force: true); - _ = SfpConfig.GetConfig(true); - Injector.Reload(); + try + { + var value = SteamSkinComboBox.SelectedValue?.ToString(); + Log.Logger.Info("Switching to skin {Skin}", value); + Settings.Default.SelectedSkin = value; + Settings.Default.Save(); + _ = Steam.GetRelativeSkinDir(force: true); + _ = SfpConfig.GetConfig(true); + await Injector.Reload(); + } + catch (Exception ex) + { + Log.Logger.Error("Error in SteamSkinComboBox_SelectionChanged event handler"); + Log.Logger.Debug(ex); + } } -} +} \ No newline at end of file diff --git a/SFP_UI/Program.cs b/SFP_UI/Program.cs index a3014c9..e07d9ad 100644 --- a/SFP_UI/Program.cs +++ b/SFP_UI/Program.cs @@ -1,20 +1,28 @@ #region using System.Runtime.InteropServices; + using Avalonia; using Avalonia.Controls; using Avalonia.Media; -using Avalonia.ReactiveUI; using Avalonia.Threading; + using Bluegrams.Application; + using FileWatcherEx; + using NLog; using NLog.Targets; + +using ReactiveUI.Avalonia; + using SFP.Models; using SFP.Properties; + using SFP_UI.Models; using SFP_UI.Targets; using SFP_UI.Views; + using SkiaSharp; #endregion @@ -23,7 +31,7 @@ namespace SFP_UI; internal static class Program { - private static readonly string s_appDataPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PhantomGamers", "SFP"); + private static readonly string AppDataPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PhantomGamers", "SFP"); private static FileStream? s_fs; private static FileSystemWatcherEx? s_fw; @@ -60,7 +68,7 @@ private static void SetupNLog() { c.ForLogger().FilterMinLevel(LogLevel.Info).WriteToConsole().WithAsync(); using var fileTarget = new FileTarget(); - fileTarget.FileName = Path.Join(s_appDataPath, "SFP.log"); + fileTarget.FileName = Path.Join(AppDataPath, "SFP.log"); fileTarget.ArchiveOldFileOnStartup = true; fileTarget.OpenFileCacheTimeout = 30; fileTarget.MaxArchiveFiles = 2; @@ -106,9 +114,19 @@ private static void WatchInstanceFile() s_fw.Start(); } +#pragma warning disable EPC27 private static async void OnInstanceFileChanged(object? sender, FileChangedEvent e) +#pragma warning restore EPC27 { - await Dispatcher.UIThread.InvokeAsync(MainWindow.ShowWindow); + try + { + await Dispatcher.UIThread.InvokeAsync(MainWindow.ShowWindow); + } + catch (Exception ex) + { + Log.Logger.Error("Error in OnInstanceFileChanged event handler"); + Log.Logger.Debug(ex); + } } private static void InitSettings() @@ -119,8 +137,8 @@ private static void InitSettings() } else { - _ = Directory.CreateDirectory(s_appDataPath); - PortableSettingsProviderBase.SettingsDirectory = s_appDataPath; + _ = Directory.CreateDirectory(AppDataPath); + PortableSettingsProviderBase.SettingsDirectory = AppDataPath; PortableJsonSettingsProvider.SettingsFileName = "settings.json"; } PortableJsonSettingsProvider.ApplyProvider(Settings.Default); @@ -149,4 +167,4 @@ private static void OnUnhandledException(object sender, UnhandledExceptionEventA LogManager.Shutdown(); CloseFileStream(); } -} +} \ No newline at end of file diff --git a/SFP_UI/SFP_UI.csproj b/SFP_UI/SFP_UI.csproj index d0704c2..e353c9c 100644 --- a/SFP_UI/SFP_UI.csproj +++ b/SFP_UI/SFP_UI.csproj @@ -24,8 +24,9 @@ - - + + + diff --git a/SFP_UI/Targets/OutputWindow.cs b/SFP_UI/Targets/OutputWindow.cs index 7d60671..4a0eb95 100644 --- a/SFP_UI/Targets/OutputWindow.cs +++ b/SFP_UI/Targets/OutputWindow.cs @@ -2,6 +2,7 @@ using NLog; using NLog.Targets; + using SFP_UI.ViewModels; #endregion @@ -15,4 +16,4 @@ protected override void Write(LogEventInfo logEvent) { MainPageViewModel.PrintLine(logEvent.Level, logEvent.FormattedMessage); } -} +} \ No newline at end of file diff --git a/SFP_UI/ViewModels/AppViewModel.cs b/SFP_UI/ViewModels/AppViewModel.cs index fb8ca9b..e68b183 100644 --- a/SFP_UI/ViewModels/AppViewModel.cs +++ b/SFP_UI/ViewModels/AppViewModel.cs @@ -1,29 +1,42 @@ -using System.Reactive; -using ReactiveUI; -using ReactiveUI.Fody.Helpers; +using ReactiveUI.SourceGenerators; + using SFP.Models; using SFP.Models.Injection; + using SFP_UI.Views; namespace SFP_UI.ViewModels; -public class AppViewModel : ViewModelBase +public partial class AppViewModel : ViewModelBase { - [Reactive] public string InjectHeader { get; set; } = "Start Injection"; - [Reactive] - public ReactiveCommand RunInject { get; set; } = - ReactiveCommand.Create(Steam.RunTryInject); + [Reactive] public partial string InjectHeader { get; set; } = "Start Injection"; - [Reactive] public string SteamHeader { get; set; } = Steam.IsSteamRunning ? "Restart Steam" : "Start Steam"; - public ReactiveCommand RunSteam { get; set; } = - ReactiveCommand.Create(Steam.RunRestartSteam); + [ReactiveCommand] + private static void RunInject() + { + if (Injector.IsInjected) + { + Injector.StopInjection(); + } + else + { + _ = Steam.RunTryInject(); + } + } - public ReactiveCommand ShowSettings { get; } = - ReactiveCommand.Create(MainWindow.ShowSettings); + [Reactive] public partial string SteamHeader { get; set; } = Steam.IsSteamRunning ? "Restart Steam" : "Start Steam"; - public ReactiveCommand ShowWindow { get; } = - ReactiveCommand.Create(MainWindow.ShowWindow); - public ReactiveCommand Quit { get; } = ReactiveCommand.Create(App.QuitApplication); + [ReactiveCommand] + private static void RunSteam() => _ = Steam.RunRestartSteam(); + + [ReactiveCommand] + private static void ShowSettings() => MainWindow.ShowSettings(); + + [ReactiveCommand] + private static void ShowWindow() => MainWindow.ShowWindow(); + + [ReactiveCommand] + private static void Quit() => App.QuitApplication(); public AppViewModel() { @@ -48,15 +61,6 @@ private void OnSteamStarted(object? o, EventArgs eventArgs) private void OnInjectionStateChanged(object? o, EventArgs eventArgs) { - if (Injector.IsInjected) - { - InjectHeader = "Stop Injection"; - RunInject = ReactiveCommand.Create(Injector.StopInjection); - } - else - { - InjectHeader = "Start Injection"; - RunInject = ReactiveCommand.Create(Steam.RunTryInject); - } + InjectHeader = Injector.IsInjected ? "Stop Injection" : "Start Injection"; } -} +} \ No newline at end of file diff --git a/SFP_UI/ViewModels/MainPageViewModel.cs b/SFP_UI/ViewModels/MainPageViewModel.cs index 346bb27..4c85039 100644 --- a/SFP_UI/ViewModels/MainPageViewModel.cs +++ b/SFP_UI/ViewModels/MainPageViewModel.cs @@ -1,11 +1,14 @@ #region -using System.Reactive; using System.Text; + using NLog; + using ReactiveUI; -using ReactiveUI.Fody.Helpers; +using ReactiveUI.SourceGenerators; + using Semver; + using SFP.Models; using SFP.Models.Injection; @@ -13,39 +16,41 @@ namespace SFP_UI.ViewModels; -public class MainPageViewModel : ViewModelBase +public partial class MainPageViewModel : ViewModelBase { public static MainPageViewModel? Instance { get; private set; } - private static readonly StringBuilder s_outputBuilder = new(); - - [Reactive] public bool UpdateNotificationIsOpen { get; set; } + private static readonly StringBuilder OutputBuilder = new(); - [Reactive] public string UpdateNotificationContent { get; set; } = string.Empty; + [Reactive] public partial bool UpdateNotificationIsOpen { get; set; } - [Reactive] public bool ButtonsEnabled { get; set; } = true; + [Reactive] public partial string UpdateNotificationContent { get; set; } = string.Empty; - [Reactive] public bool IsInjected { get; set; } = Injector.IsInjected; + [Reactive] public partial bool ButtonsEnabled { get; set; } = true; - [Reactive] public string StartSteamText { get; set; } = Steam.IsSteamRunning ? "Restart Steam" : "Start Steam"; + [Reactive] public partial bool IsInjected { get; set; } = Injector.IsInjected; - private string _output = s_outputBuilder.ToString(); + [Reactive] public partial string StartSteamText { get; set; } = Steam.IsSteamRunning ? "Restart Steam" : "Start Steam"; public string Output { - get => _output; - private set => this.RaiseAndSetIfChanged(ref _output, value); - } + get; + private set => this.RaiseAndSetIfChanged(ref field, value); + } = OutputBuilder.ToString(); + + // Commands generated by ReactiveUI.SourceGenerators via the [ReactiveCommand] attribute. + // These will produce properties named UpdateNotificationViewCommand, InjectCommand, StopInjectCommand, StartSteamCommand. - public ReactiveCommand UpdateNotificationView { get; } = - ReactiveCommand.Create(Utils.OpenUrl); + [ReactiveCommand] + private static void UpdateNotificationView(string url) => Utils.OpenUrl(url); - public ReactiveCommand Inject { get; } = - ReactiveCommand.Create(Steam.RunTryInject); + [ReactiveCommand] + private static void Inject() => _ = Steam.RunTryInject(); - public ReactiveCommand StopInject { get; } = ReactiveCommand.Create(Injector.StopInjection); + [ReactiveCommand] + private static void StopInject() => Injector.StopInjection(); - public ReactiveCommand StartSteam { get; } = - ReactiveCommand.Create(Steam.RunRestartSteam); + [ReactiveCommand] + private static void StartSteam() => _ = Steam.RunRestartSteam(); public MainPageViewModel() { @@ -81,37 +86,33 @@ public static void PrintLine(LogLevel level, string message) private static void Print(LogLevel level, string message) { TrimOutput(); - s_outputBuilder.Append($"[{DateTime.Now}][{level}] {message}"); - if (Instance == null) - { - return; - } - Instance.Output = s_outputBuilder.ToString(); + OutputBuilder.Append($"[{DateTime.Now}][{level}] {message}"); + Instance?.Output = OutputBuilder.ToString(); } private static void TrimOutput() { - const short MaxLength = short.MaxValue; - if (s_outputBuilder.Length <= MaxLength) + const short maxLength = short.MaxValue; + if (OutputBuilder.Length <= maxLength) { return; } - var output = s_outputBuilder.ToString(); + var output = OutputBuilder.ToString(); var firstNewlineIndex = output.IndexOf('\n'); if (firstNewlineIndex == -1) { return; } var firstLine = output[..firstNewlineIndex]; - var lastNewlineIndex = output.IndexOf('\n', MaxLength / 2); + var lastNewlineIndex = output.IndexOf('\n', maxLength / 2); if (lastNewlineIndex == -1) { return; } - s_outputBuilder.Clear(); - s_outputBuilder.Append(firstLine); - s_outputBuilder.Append(output.AsSpan(lastNewlineIndex)); + OutputBuilder.Clear(); + OutputBuilder.Append(firstLine); + OutputBuilder.Append(output.AsSpan(lastNewlineIndex)); } public void ShowUpdateNotification(SemVersion oldVersion, SemVersion newVersion) @@ -121,4 +122,4 @@ public void ShowUpdateNotification(SemVersion oldVersion, SemVersion newVersion) $"Your version: {oldVersion}{Environment.NewLine}Latest version: {newVersion}"; UpdateNotificationIsOpen = true; } -} +} \ No newline at end of file diff --git a/SFP_UI/ViewModels/MainViewViewModel.cs b/SFP_UI/ViewModels/MainViewViewModel.cs index 5389810..1ab2487 100644 --- a/SFP_UI/ViewModels/MainViewViewModel.cs +++ b/SFP_UI/ViewModels/MainViewViewModel.cs @@ -1,17 +1,14 @@ using Avalonia.Controls; + using FluentAvalonia.UI.Controls; + using SFP_UI.Pages; namespace SFP_UI.ViewModels; public class MainViewViewModel : ViewModelBase { - public MainViewViewModel() - { - NavigationFactory = new NavigationFactory(); - } - - public NavigationFactory NavigationFactory { get; } + public NavigationFactory NavigationFactory { get; } = new(); } public class NavigationFactory : INavigationPageFactory @@ -31,7 +28,7 @@ public NavigationFactory() } // Create a page based on an object, such as a view model - public Control? GetPageFromObject(object target) + public Control GetPageFromObject(object target) { return target switch { @@ -46,11 +43,11 @@ public NavigationFactory() private readonly Control[] _pages = [ new MainPage(), - new SettingsPage(), + new SettingsPage() ]; public static Control[] GetPages() { return Instance!._pages; } -} +} \ No newline at end of file diff --git a/SFP_UI/ViewModels/MainWindowViewModel.cs b/SFP_UI/ViewModels/MainWindowViewModel.cs index b23e1aa..befc0cf 100644 --- a/SFP_UI/ViewModels/MainWindowViewModel.cs +++ b/SFP_UI/ViewModels/MainWindowViewModel.cs @@ -1,5 +1,3 @@ namespace SFP_UI.ViewModels; -public class MainWindowViewModel : ViewModelBase -{ -} +public class MainWindowViewModel : ViewModelBase; \ No newline at end of file diff --git a/SFP_UI/ViewModels/SettingsPageViewModel.cs b/SFP_UI/ViewModels/SettingsPageViewModel.cs index 523bc37..15a5fd0 100644 --- a/SFP_UI/ViewModels/SettingsPageViewModel.cs +++ b/SFP_UI/ViewModels/SettingsPageViewModel.cs @@ -1,67 +1,69 @@ #region -using System.Reactive; using System.Reactive.Linq; + using Avalonia.Platform.Storage; + using FluentAvalonia.UI.Controls; -using ReactiveUI; -using ReactiveUI.Fody.Helpers; + +using ReactiveUI.SourceGenerators; + using SFP.Models; using SFP.Models.Injection; using SFP.Properties; + using SFP_UI.Views; -using Utils = SFP.Models.Windows.Utils; -// ReSharper disable MemberCanBePrivate.Global +using Utils = SFP.Models.Windows.Utils; #endregion namespace SFP_UI.ViewModels; -public class SettingsPageViewModel : ViewModelBase +public partial class SettingsPageViewModel : ViewModelBase { #region App - [Reactive] public bool CheckForUpdates { get; set; } + [Reactive] public partial bool CheckForUpdates { get; set; } - [Reactive] public bool ShowTrayIcon { get; set; } + [Reactive] public partial bool ShowTrayIcon { get; set; } - [Reactive] public bool MinimizeToTray { get; set; } + [Reactive] public partial bool MinimizeToTray { get; set; } - [Reactive] public bool CloseToTray { get; set; } + [Reactive] public partial bool CloseToTray { get; set; } - [Reactive] public bool StartMinimized { get; set; } + [Reactive] public partial bool StartMinimized { get; set; } - [Reactive] public bool InjectOnAppStart { get; set; } + [Reactive] public partial bool InjectOnAppStart { get; set; } - [Reactive] public bool RunSteamOnStart { get; set; } + [Reactive] public partial bool RunSteamOnStart { get; set; } - [Reactive] public bool RunOnBoot { get; set; } + [Reactive] public partial bool RunOnBoot { get; set; } public IEnumerable AppThemes { get; } = ["Dark", "Light", "System Default"]; - [Reactive] public string SelectedTheme { get; set; } = null!; + [Reactive] public partial string SelectedTheme { get; set; } = null!; - [Reactive] public int InitialInjectionDelay { get; set; } + [Reactive] public partial int InitialInjectionDelay { get; set; } #endregion #region Steam - [Reactive] public string SteamDirectory { get; set; } = null!; + [Reactive] public partial string SteamDirectory { get; set; } = null!; - [Reactive] public string SteamLaunchArgs { get; set; } = null!; + [Reactive] public partial string SteamLaunchArgs { get; set; } = null!; - [Reactive] public short SteamCefPort { get; set; } + [Reactive] public partial short SteamCefPort { get; set; } - [Reactive] public bool InjectOnSteamStart { get; set; } + [Reactive] public partial bool InjectOnSteamStart { get; set; } - [Reactive] public bool ForceSteamArgs { get; set; } + [Reactive] public partial bool ForceSteamArgs { get; set; } - [Reactive] public bool InjectCss { get; set; } + [Reactive] public partial bool InjectCss { get; set; } - [Reactive] public bool InjectJs { get; set; } + [Reactive] public partial bool InjectJs { get; set; } - [Reactive] public bool UseAppTheme { get; set; } + [Reactive] public partial bool UseAppTheme { get; set; } - [Reactive] public bool DumpPages { get; set; } + [Reactive] public partial bool DumpPages { get; set; } #endregion public bool IsWindows { get; } = OperatingSystem.IsWindows(); @@ -70,14 +72,20 @@ public SettingsPageViewModel() { InitProperties(); #region App - this.WhenAnyValue(x => x.CheckForUpdates) + this.Changed + .Where(e => e.PropertyName == nameof(CheckForUpdates)) + .Select(_ => CheckForUpdates) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.CheckForUpdates = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.ShowTrayIcon) + this.Changed + .Where(e => e.PropertyName == nameof(ShowTrayIcon)) + .Select(_ => ShowTrayIcon) + .DistinctUntilChanged() .Subscribe(value => { App.SetIconsState(value); @@ -85,42 +93,60 @@ public SettingsPageViewModel() Settings.Default.Save(); }); - this.WhenAnyValue(x => x.MinimizeToTray) + this.Changed + .Where(e => e.PropertyName == nameof(MinimizeToTray)) + .Select(_ => MinimizeToTray) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.MinimizeToTray = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.CloseToTray) + this.Changed + .Where(e => e.PropertyName == nameof(CloseToTray)) + .Select(_ => CloseToTray) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.CloseToTray = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.StartMinimized) + this.Changed + .Where(e => e.PropertyName == nameof(StartMinimized)) + .Select(_ => StartMinimized) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.StartMinimized = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.InjectOnAppStart) + this.Changed + .Where(e => e.PropertyName == nameof(InjectOnAppStart)) + .Select(_ => InjectOnAppStart) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.InjectOnAppStart = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.RunSteamOnStart) + this.Changed + .Where(e => e.PropertyName == nameof(RunSteamOnStart)) + .Select(_ => RunSteamOnStart) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.RunSteamOnStart = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.RunOnBoot) + this.Changed + .Where(e => e.PropertyName == nameof(RunOnBoot)) + .Select(_ => RunOnBoot) + .DistinctUntilChanged() .Throttle(TimeSpan.FromSeconds(1)) .Subscribe(value => { @@ -132,7 +158,10 @@ public SettingsPageViewModel() } }); - this.WhenAnyValue(x => x.SelectedTheme) + this.Changed + .Where(e => e.PropertyName == nameof(SelectedTheme)) + .Select(_ => SelectedTheme) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.AppTheme = value.ToString(); @@ -140,7 +169,10 @@ public SettingsPageViewModel() App.SetApplicationTheme(value); }); - this.WhenAnyValue(x => x.InitialInjectionDelay) + this.Changed + .Where(e => e.PropertyName == nameof(InitialInjectionDelay)) + .Select(_ => InitialInjectionDelay) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.InitialInjectionDelay = value; @@ -149,14 +181,20 @@ public SettingsPageViewModel() #endregion #region Steam - this.WhenAnyValue(x => x.SteamDirectory) + this.Changed + .Where(e => e.PropertyName == nameof(SteamDirectory)) + .Select(_ => SteamDirectory) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.SteamDirectory = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.SteamLaunchArgs) + this.Changed + .Where(e => e.PropertyName == nameof(SteamLaunchArgs)) + .Select(_ => SteamLaunchArgs) + .DistinctUntilChanged() .Throttle(TimeSpan.FromSeconds(1)) .Subscribe(value => { @@ -164,7 +202,10 @@ public SettingsPageViewModel() Settings.Default.Save(); }); - this.WhenAnyValue(x => x.SteamCefPort) + this.Changed + .Where(e => e.PropertyName == nameof(SteamCefPort)) + .Select(_ => SteamCefPort) + .DistinctUntilChanged() .Throttle(TimeSpan.FromSeconds(1)) .Subscribe(value => { @@ -172,35 +213,47 @@ public SettingsPageViewModel() Settings.Default.Save(); }); - this.WhenAnyValue(x => x.InjectOnSteamStart) + this.Changed + .Where(e => e.PropertyName == nameof(InjectOnSteamStart)) + .Select(_ => InjectOnSteamStart) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.InjectOnSteamStart = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.ForceSteamArgs) + this.Changed + .Where(e => e.PropertyName == nameof(ForceSteamArgs)) + .Select(_ => ForceSteamArgs) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.ForceSteamArgs = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.InjectCss) + this.Changed + .Where(e => e.PropertyName == nameof(InjectCss)) + .Select(_ => InjectCss) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.InjectCSS = value; Settings.Default.Save(); }); - this.WhenAnyValue(x => x.InjectJs) + this.Changed + .Where(e => e.PropertyName == nameof(InjectJs)) + .Select(_ => InjectJs) + .DistinctUntilChanged() .Subscribe(value => { if (value && !Settings.Default.InjectJSWarningAccepted) { Settings.Default.InjectJS = false; Settings.Default.Save(); - ShowWarningDialog(); + _ = ShowWarningDialog(); } else { @@ -209,44 +262,27 @@ public SettingsPageViewModel() } }); - this.WhenAnyValue(x => x.UseAppTheme) + this.Changed + .Where(e => e.PropertyName == nameof(UseAppTheme)) + .Select(_ => UseAppTheme) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.UseAppTheme = value; Settings.Default.Save(); - if (!value) - { - Injector.UpdateColorScheme("light"); - Injector.UpdateSystemAccentColors(false); - } - else - { - Injector.UpdateColorScheme(); - Injector.UpdateSystemAccentColors(); - } + _ = OnUseAppThemeChangedAsync(value); }); - this.WhenAnyValue(x => x.DumpPages) + this.Changed + .Where(e => e.PropertyName == nameof(DumpPages)) + .Select(_ => DumpPages) + .DistinctUntilChanged() .Subscribe(value => { Settings.Default.DumpPages = value; Settings.Default.Save(); }); #endregion - - BrowseSteam = ReactiveCommand.Create(BrowseSteamImpl); - ResetSteam = ReactiveCommand.Create(() => - { - Settings.Default.SteamDirectory = string.Empty; - SteamDirectory = Steam.SteamDir ?? string.Empty; - Settings.Default.Save(); - }); - ResetSettings = ReactiveCommand.Create(() => - { - Settings.Default.Reset(); - InitProperties(); - Settings.Default.Save(); - }); } private void InitProperties() @@ -277,8 +313,7 @@ private void InitProperties() #endregion } - - private async void ShowWarningDialog() + private async Task ShowWarningDialog() { var dialog = new ContentDialog { @@ -288,28 +323,49 @@ private async void ShowWarningDialog() "JavaScript can potentially contain malicious code and you should only use scripts from people you trust.\n" + "Continue?", PrimaryButtonText = "Yes", - PrimaryButtonCommand = ReactiveCommand.Create(() => - { - Settings.Default.InjectJS = true; - Settings.Default.InjectJSWarningAccepted = true; - Settings.Default.Save(); - InjectJs = true; - }), + PrimaryButtonCommand = ConfirmInjectJsCommand, SecondaryButtonText = "No", - SecondaryButtonCommand = ReactiveCommand.Create(() => - { - InjectJs = false; - Settings.Default.Save(); - }) + SecondaryButtonCommand = CancelInjectJsCommand }; await dialog.ShowAsync(); } - public ReactiveCommand ResetSteam { get; } - public ReactiveCommand ResetSettings { get; } - public ReactiveCommand BrowseSteam { get; } + [ReactiveCommand] + private async Task BrowseSteam() => await BrowseSteamImpl().ConfigureAwait(false); - private async void BrowseSteamImpl() + [ReactiveCommand] + private void ResetSteam() + { + Settings.Default.SteamDirectory = string.Empty; + SteamDirectory = Steam.SteamDir ?? string.Empty; + Settings.Default.Save(); + } + + [ReactiveCommand] + private void ResetSettings() + { + Settings.Default.Reset(); + InitProperties(); + Settings.Default.Save(); + } + + [ReactiveCommand] + private void ConfirmInjectJs() + { + Settings.Default.InjectJS = true; + Settings.Default.InjectJSWarningAccepted = true; + Settings.Default.Save(); + InjectJs = true; + } + + [ReactiveCommand] + private void CancelInjectJs() + { + InjectJs = false; + Settings.Default.Save(); + } + + private async Task BrowseSteamImpl() { if (MainWindow.Instance == null) { @@ -322,4 +378,26 @@ private async void BrowseSteamImpl() SteamDirectory = result[0].Path.LocalPath; } } -} + + private static async Task OnUseAppThemeChangedAsync(bool useAppTheme) + { + try + { + if (!useAppTheme) + { + await Injector.UpdateColorScheme("light"); + await Injector.UpdateSystemAccentColors(false); + } + else + { + await Injector.UpdateColorScheme(); + await Injector.UpdateSystemAccentColors(); + } + } + catch (Exception ex) + { + Log.Logger.Error("Error updating colors on UseAppTheme changed"); + Log.Logger.Debug(ex); + } + } +} \ No newline at end of file diff --git a/SFP_UI/ViewModels/ViewModelBase.cs b/SFP_UI/ViewModels/ViewModelBase.cs index 9a5dbf9..0a529f9 100644 --- a/SFP_UI/ViewModels/ViewModelBase.cs +++ b/SFP_UI/ViewModels/ViewModelBase.cs @@ -6,6 +6,4 @@ namespace SFP_UI.ViewModels; -public class ViewModelBase : ReactiveObject -{ -} +public class ViewModelBase : ReactiveObject; \ No newline at end of file diff --git a/SFP_UI/Views/MainView.axaml.cs b/SFP_UI/Views/MainView.axaml.cs index a3693df..98cf90b 100644 --- a/SFP_UI/Views/MainView.axaml.cs +++ b/SFP_UI/Views/MainView.axaml.cs @@ -2,8 +2,10 @@ using Avalonia; using Avalonia.Controls; + using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Navigation; + using SFP_UI.Pages; using SFP_UI.ViewModels; @@ -97,7 +99,7 @@ private IEnumerable GetNavigationViewItems() { return [ - new() + new NavigationViewItem { Content = "Home", Tag = NavigationFactory.GetPages()[0], @@ -111,7 +113,7 @@ private IEnumerable GetFooterNavigationViewItems() { return [ - new() + new NavigationViewItem { Content = "Settings", Tag = NavigationFactory.GetPages()[1], @@ -128,4 +130,4 @@ private void OnNavigationViewItemInvoked(object? sender, NavigationViewItemInvok _ = FrameView.NavigateFromObject(c); } } -} +} \ No newline at end of file diff --git a/SFP_UI/Views/MainWindow.axaml.cs b/SFP_UI/Views/MainWindow.axaml.cs index 5c56dea..25adafd 100644 --- a/SFP_UI/Views/MainWindow.axaml.cs +++ b/SFP_UI/Views/MainWindow.axaml.cs @@ -5,12 +5,15 @@ using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Styling; + using FluentAvalonia.Styling; using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Media; using FluentAvalonia.UI.Windowing; + using SFP.Models; using SFP.Properties; + using SFP_UI.Models; using SFP_UI.ViewModels; @@ -106,23 +109,33 @@ protected override void OnOpened(EventArgs e) } } +#pragma warning disable EPC27 protected override async void OnClosing(WindowClosingEventArgs e) +#pragma warning restore EPC27 { - base.OnClosing(e); - if (Instance is null) + try { - return; - } - Instance = null; - if (Settings.Default.CloseToTray) - { - return; + base.OnClosing(e); + if (Instance is null) + { + return; + } + Instance = null; + if (Settings.Default.CloseToTray) + { + return; + } + if (WindowState == WindowState.Minimized && Settings.Default is { MinimizeToTray: true }) + { + return; + } + await Task.Run(App.QuitApplication); } - if (WindowState == WindowState.Minimized && Settings.Default is { MinimizeToTray: true }) + catch (Exception ex) { - return; + Log.Logger.Error("Error in OnClosing event handler"); + Log.Logger.Debug(ex); } - await Task.Run(App.QuitApplication); } private void HandleWindowStateChanged(WindowState state) @@ -158,4 +171,4 @@ public static void ShowWindow() Instance.WindowState = WindowState.Normal; Instance.Activate(); } -} +} \ No newline at end of file diff --git a/SFP_UI/packages.lock.json b/SFP_UI/packages.lock.json index a3f16f3..395292e 100644 --- a/SFP_UI/packages.lock.json +++ b/SFP_UI/packages.lock.json @@ -1,59 +1,47 @@ { "version": 2, "dependencies": { - "net8.0": { + "net10.0": { "Avalonia": { "type": "Direct", - "requested": "[11.2.3, )", - "resolved": "11.2.3", - "contentHash": "pD6woFAUfGcyEvMmrpctntU4jv4fT8752pfx1J5iRORVX3Ob0oQi8PWo0TXVaAJZiSfH0cdKTeKx0w0DzD0/mg==", + "requested": "[11.3.11, )", + "resolved": "11.3.11", + "contentHash": "N+LjdoGLOD2Rio1JK0VlSMEB2opGZpq8/Vs5LV16eAnbjFeXdqY5uwmURFKb1rJ9McyMZQT3Kf2/DecmKqrLEw==", "dependencies": { - "Avalonia.BuildServices": "0.0.29", - "Avalonia.Remote.Protocol": "11.2.3", + "Avalonia.BuildServices": "11.3.2", + "Avalonia.Remote.Protocol": "11.3.11", "MicroCom.Runtime": "0.11.0" } }, "Avalonia.Desktop": { "type": "Direct", - "requested": "[11.2.3, )", - "resolved": "11.2.3", - "contentHash": "dX3zfgWplLqcgwQJLeC2ciqxE/GM3iw9HUNI22c8KgAAWMWl52NWCmjW228EPZG+4YbHwq8T40YARO2aQF+yqA==", + "requested": "[11.3.11, )", + "resolved": "11.3.11", + "contentHash": "m60U1jV96RquwYGg4Pzwp0ENL1HUrM3YZLMCes0gWRn8x+webT9V4Wfs58MAdTOukXPpy9t1XhEd4NON674Wmw==", "dependencies": { - "Avalonia": "11.2.3", - "Avalonia.Native": "11.2.3", - "Avalonia.Skia": "11.2.3", - "Avalonia.Win32": "11.2.3", - "Avalonia.X11": "11.2.3" + "Avalonia": "11.3.11", + "Avalonia.Native": "11.3.11", + "Avalonia.Skia": "11.3.11", + "Avalonia.Win32": "11.3.11", + "Avalonia.X11": "11.3.11" } }, "Avalonia.Diagnostics": { "type": "Direct", - "requested": "[11.2.3, )", - "resolved": "11.2.3", - "contentHash": "Qld0UvkSLfIuZD7/gS8RIO6ww3jP+xJvsMyqU8evdphPoic7h6LAeY/ppT9NtI0r1KUDT2BpFcVDqPyQH6eSiw==", + "requested": "[11.3.11, )", + "resolved": "11.3.11", + "contentHash": "gqKcPTGRZG/ocet5vOmQrIpf+gQU4mOS3mVwIisSv1GAKA/+JmNcnp5ouaKikLbdGt7Qcw2ZKl+daMws9y6/mg==", "dependencies": { - "Avalonia": "11.2.3", - "Avalonia.Controls.ColorPicker": "11.2.3", - "Avalonia.Controls.DataGrid": "11.2.3", - "Avalonia.Themes.Simple": "11.2.3" - } - }, - "Avalonia.ReactiveUI": { - "type": "Direct", - "requested": "[11.2.3, )", - "resolved": "11.2.3", - "contentHash": "MHasX3CRPkkbFN7UZC/+mRZiPvWmiIRl5SJjQYiOA+Zgw9xj6qK9mD3zNbr0Ej3yN+IRh/qDjBlTpL4TzDF88A==", - "dependencies": { - "Avalonia": "11.2.3", - "ReactiveUI": "20.1.1", - "System.Reactive": "6.0.1" + "Avalonia": "11.3.11", + "Avalonia.Controls.ColorPicker": "11.3.11", + "Avalonia.Themes.Simple": "11.3.11" } }, "ErrorProne.NET.CoreAnalyzers": { "type": "Direct", - "requested": "[0.7.0-beta.1, )", - "resolved": "0.7.0-beta.1", - "contentHash": "c+Tcp88i2Z+nrUYqEEHroE3tyj57n6F9CB4DDRsPJatWn2RNB2mAmAVVXVg8PcE7QPlUCHD2QURJzKluDQlW5Q==" + "requested": "[0.8.2-beta.1, )", + "resolved": "0.8.2-beta.1", + "contentHash": "8zTBD0fGHSZzwUCqm7b9DykXDBWA8swQ71mB9Jli2n8MYThLrqnJL9xK3rNol21oGFn0q/JfaGYJbSheTXqeYA==" }, "ErrorProne.NET.Structs": { "type": "Direct", @@ -63,43 +51,59 @@ }, "FluentAvaloniaUI": { "type": "Direct", - "requested": "[2.2.0, )", - "resolved": "2.2.0", - "contentHash": "OlDwcLmrgylcIH2G0mwgveeGeSvB/W1aaLj1Ts86QKeOdzRRQSc17F/hQ6rwA8AdJkwe0PVovIZUG45tlMp11Q==", - "dependencies": { - "Avalonia": "11.1.0", - "Avalonia.Controls.ColorPicker": "11.1.0", - "Avalonia.Controls.DataGrid": "11.1.0", - "Avalonia.Skia": "11.1.0", + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "DayZu5Mqn5nXwji1/Ya9Y4BSS638D5NrjsFsdcTUyqXkoHSv+VQFWQtg6QzkINjKqSDSnzXfFFZXIVT7k9jt5g==", + "dependencies": { + "Avalonia": "11.3.10", + "Avalonia.Controls.ColorPicker": "11.3.10", + "Avalonia.Controls.DataGrid": "11.3.10", + "Avalonia.Skia": "11.3.10", "MicroCom.Runtime": "0.11.0" } }, + "JetBrains.Annotations": { + "type": "Direct", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.11, )", - "resolved": "8.0.11", - "contentHash": "zk5lnZrYJgtuJG8L4v17Ej8rZ3PUcR2iweNV08BaO5LbYHIi2wNaVNcJoLxvqgQdnjLlKnCCfVGLDr6QHeAarQ==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw==" }, "MinVer": { "type": "Direct", - "requested": "[6.0.0, )", - "resolved": "6.0.0", - "contentHash": "+/SsmiySsXJlvQLCGBqaZKNVt3s/Y/HbAdwtop7Km2CnuZbaScoqkWJEBQ5Cy9ebkn6kCYKrHsXgwrFdTgcb3g==" + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "2lMTCQl5bGP4iv0JNkockPnyllC6eHLz+CoK2ICvalvHod+exXSxueu9hq+zNkU7bZBJf8wMfeRC/Edn8AGmEg==" }, "NLog": { "type": "Direct", - "requested": "[5.3.4, )", - "resolved": "5.3.4", - "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A==" + "requested": "[6.1.0, )", + "resolved": "6.1.0", + "contentHash": "K4UdxS4fuJeCM0sm+JTXWoJgOtcXX7X5kvE94bR07UB8JOB6A2+ski7q1tf2qdv+N0H3gsoWtteScJCHa3+zpw==" }, - "ReactiveUI.Fody": { + "ReactiveUI.Avalonia": { "type": "Direct", - "requested": "[19.5.41, )", - "resolved": "19.5.41", - "contentHash": "bh6OH1WsSHf/ryUVaPnI6LWl1+lrCvq4L/AoHpzPXCKxtR4p0ogPVONnkq0jYgSQ55x7YNCVkthBKeb6uUSbpg==", + "requested": "[11.3.8, )", + "resolved": "11.3.8", + "contentHash": "8GtFnYCyjcuTlGuUhvMMrY/sUmjuzSQbbtNQ3LYZEW7tkzXixDYnHZzqZU73W+NOnq2sBxds7gSUO52igFWVUw==", "dependencies": { - "Fody": "6.8.0", - "ReactiveUI": "19.5.41" + "Avalonia": "11.3.8", + "ReactiveUI": "22.2.1", + "Splat": "17.1.1" + } + }, + "ReactiveUI.SourceGenerators": { + "type": "Direct", + "requested": "[2.6.1, )", + "resolved": "2.6.1", + "contentHash": "QU2EQZriHX9gvUx9Y3Eb0yZYzCGJbBnbSZQMmoH90ROzLNzDsslFFqz/bX89EmcMfr+8cOK3/b80zub3X/erHw==", + "dependencies": { + "ReactiveUI.SourceGenerators.Analyzers.CodeFixes": "2.6.1" } }, "Semver": { @@ -113,63 +117,62 @@ }, "Avalonia.Angle.Windows.Natives": { "type": "Transitive", - "resolved": "2.1.22045.20230930", - "contentHash": "Bo3qOhKC1b84BIhiogndMdAzB3UrrESKK7hS769f5HWeoMw/pcd42US5KFYW2JJ4ZSTrXnP8mXwLTMzh+S+9Lg==" + "resolved": "2.1.25547.20250602", + "contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A==" }, "Avalonia.BuildServices": { "type": "Transitive", - "resolved": "0.0.29", - "contentHash": "U4eJLQdoDNHXtEba7MZUCwrBErBTxFp6sUewXBOdAhU0Kwzwaa/EKFcYm8kpcysjzKtfB4S0S9n0uxKZFz/ikw==" + "resolved": "11.3.2", + "contentHash": "qHDToxto1e3hci5YqbG9n0Ty8mlp3zBUN5wT66wKqaDVzXyQ0do3EnRILd4Ke9jpvsktaPpgE0YjEk7hornryQ==" }, "Avalonia.Controls.ColorPicker": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "JpK84GN+CkS5H0L/mDyYa4aMAJw93dukedKK1xC4g/J5NmwQ23qFPQNqZlTs+6f9nULwJPL+jiffJpSyGMt0gg==", + "resolved": "11.3.11", + "contentHash": "2i5dvZcizAtumywPRravU0/0lLsa8F+n3cTyt2ZpXAwnqnE+eeuozl9Q23YnE69BpROC5wXYrjogXhWcUPks4g==", "dependencies": { - "Avalonia": "11.2.3", - "Avalonia.Remote.Protocol": "11.2.3" + "Avalonia": "11.3.11", + "Avalonia.Remote.Protocol": "11.3.11" } }, "Avalonia.Controls.DataGrid": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "Ul6oWEoqs4eTQZIb4Hzf0+ajhgrCX9ypOj8TahKbkKoIznJkUoka3iV90Vpj/AuoT5AZsN6f+1+62SVPpeMApA==", + "resolved": "11.3.10", + "contentHash": "KSwme0XOL8594fFvoQ37s5rnSo9vCKutCpufCEmd0R7x/fRWOjK3g3su1Z3nukgbobryPVjQvZxi/E92DPPYpw==", "dependencies": { - "Avalonia": "11.2.3", - "Avalonia.Remote.Protocol": "11.2.3" + "Avalonia": "11.3.10" } }, "Avalonia.FreeDesktop": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "BOivcAE3yYFKyYg5CztnTeIFX7ZHNaFiMrQ9WO4MgKyMwbPdH6jy6Mpfu+LY5FiYpleZdmXLJXZzzPon52DUVg==", + "resolved": "11.3.11", + "contentHash": "kmmYhKVWAFAz064K4FYTqVOk/Oin6DWp5LfzVNISz3BP4YYUZdrGyglxhjbS6t8iOyzMnAoHCrNs1Pw+BRFYUQ==", "dependencies": { - "Avalonia": "11.2.3", - "Tmds.DBus.Protocol": "0.20.0" + "Avalonia": "11.3.11", + "Tmds.DBus.Protocol": "0.21.2" } }, "Avalonia.Native": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "yW9IGfa7kBuEcYP4ni7nGYNI2HjqaBg+cPJXZeiXf8RFptmluMv75hMyyq8FYIZwVcZIEcwEgff81a7b4aNTVQ==", + "resolved": "11.3.11", + "contentHash": "21XxwcOCYuf7fSYBsc4sF7CS0xNK80iDFyXyK9L4awnrjZOJjMuNSGiJ2L5+RQQkXnXaB8ECvMAWN3e/9WN3aw==", "dependencies": { - "Avalonia": "11.2.3" + "Avalonia": "11.3.11" } }, "Avalonia.Remote.Protocol": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "6V0aNtld48WmO8tAlWwlRlUmXYcOWv+1eJUSl1ETF+1blUe5yhcSmuWarPprO0hDk8Ta6wGfdfcrnVl2gITYcA==" + "resolved": "11.3.11", + "contentHash": "kooZKlJb0DkzJYFzYTgChqAr+pOBq64FyNuvjpBLBmqTTTj/f+d7XyPvdNFgPsvyY3sa4X/W79t6hQnyWmM4zw==" }, "Avalonia.Skia": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "jpzqkkmhzz6DSUy5dIw5T43MoHCdb05pmTvnsmHrbipA8mafI8RrO7tVnv1+ilFNV4516G9/kOpXjTLKjnnYrA==", + "resolved": "11.3.11", + "contentHash": "7XynAFWmUR0pEXj6dY8ezpFkeMpNTGNvz1Vf2kLKQawJiv1MKdxzbehDXG9v4A0l2oBLd5m92Dme1/EKH7oj5g==", "dependencies": { - "Avalonia": "11.2.3", - "HarfBuzzSharp": "7.3.0.3", - "HarfBuzzSharp.NativeAssets.Linux": "7.3.0.3", - "HarfBuzzSharp.NativeAssets.WebAssembly": "7.3.0.3", + "Avalonia": "11.3.11", + "HarfBuzzSharp": "8.3.1.1", + "HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1", + "HarfBuzzSharp.NativeAssets.WebAssembly": "8.3.1.1", "SkiaSharp": "2.88.9", "SkiaSharp.NativeAssets.Linux": "2.88.9", "SkiaSharp.NativeAssets.WebAssembly": "2.88.9" @@ -177,37 +180,37 @@ }, "Avalonia.Themes.Simple": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "Okio7RYUHUk1m/1n6VGrkoq0C3Y3J6RwtrTGEfXPkYMJsL6yPqstJZMYkMFQPISUr8TYUNVKv82hL1qLRw7hwA==", + "resolved": "11.3.11", + "contentHash": "vrCD6Kz4nm4dKA7j3s7nTLxdlsQ+ru7iv45FDwRfys0QhrKK4deq40RxUgfREGvFfCNMD2b9EEerieU3uFSTkw==", "dependencies": { - "Avalonia": "11.2.3" + "Avalonia": "11.3.11" } }, "Avalonia.Win32": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "VwdaOHvIowTSM2umeXOFIoUx4UydCXkXracwLQZaMlsWXCTJ+WwtlAIv0ZBCwQccAK+WELrdRXucvWWN8+sJCQ==", + "resolved": "11.3.11", + "contentHash": "hFOuFHuhOIiB1p4XtTYnIFHvVazCQwotlPW/iUPObUHQY6qoDYqzPIZIELZiLtUIs9zHA55DWvpmW/l4rDuuAQ==", "dependencies": { - "Avalonia": "11.2.3", - "Avalonia.Angle.Windows.Natives": "2.1.22045.20230930" + "Avalonia": "11.3.11", + "Avalonia.Angle.Windows.Natives": "2.1.25547.20250602" } }, "Avalonia.X11": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "0mr3zu5NEv2cTLsANyc3w51ctiLTWQia6TrlDdWCjfMx2k0VtCzgGBieByPgUl4iNWEDzgBEKek1EwJcGdJ+7g==", + "resolved": "11.3.11", + "contentHash": "ZhNHTe8n0w7+Xkxgt13WidSZudHKiDQfDDv/jEDJ4ffdh6z4oKjkPrVUer0ECgEENOR2iCLvFNlaDi5glZ66SA==", "dependencies": { - "Avalonia": "11.2.3", - "Avalonia.FreeDesktop": "11.2.3", - "Avalonia.Skia": "11.2.3" + "Avalonia": "11.3.11", + "Avalonia.FreeDesktop": "11.3.11", + "Avalonia.Skia": "11.3.11" } }, "DynamicData": { "type": "Transitive", - "resolved": "8.4.1", - "contentHash": "Mn1+fU/jqxgONEJq8KLQPGWEi7g/hUVTbjZyn4QM0sWWDAVOHPO9WjXWORSykwdfg/6S3GM15qsfz+2EvO+QAQ==", + "resolved": "9.4.1", + "contentHash": "+SoDTbuzY9j0K+sZfRbbVpGjjylkVMhUQnw6qiPhxzYLBJlwxFpmunXSaNBtTxwC2C9d/XIp0kO7VhMAVuUt7w==", "dependencies": { - "System.Reactive": "6.0.0" + "System.Reactive": "6.0.1" } }, "Flurl": { @@ -215,42 +218,34 @@ "resolved": "4.0.0", "contentHash": "rpts69yYgvJqg6PPgqShBQEZ4aNzWQqWpWppcT0oDWxDCIsBqiod4pj6LQZdhk+1OozLFagemldMRACdHF3CsA==" }, - "Fody": { - "type": "Transitive", - "resolved": "6.8.0", - "contentHash": "hfZ/f8Mezt8aTkgv9nsvFdYoQ809/AqwsJlOGOPYIfBcG2aAIG3v3ex9d8ZqQuFYyMoucjRg4eKy3VleeGodKQ==" - }, "HarfBuzzSharp": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "Hq+5+gx10coOvuRgB13KBwiWxJq1QeYuhtVLbA01ZCWaugOnolUahF44KvrQTUUHDNk/C7HB6SMaebsZeOdhgg==", + "resolved": "8.3.1.1", + "contentHash": "tLZN66oe/uiRPTZfrCU4i8ScVGwqHNh5MHrXj0yVf4l7Mz0FhTGnQ71RGySROTmdognAs0JtluHkL41pIabWuQ==", "dependencies": { - "HarfBuzzSharp.NativeAssets.Win32": "7.3.0.3", - "HarfBuzzSharp.NativeAssets.macOS": "7.3.0.3" + "HarfBuzzSharp.NativeAssets.Win32": "8.3.1.1", + "HarfBuzzSharp.NativeAssets.macOS": "8.3.1.1" } }, "HarfBuzzSharp.NativeAssets.Linux": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "hkcHeTfOyIeJuPtO/QfoqkDvV/MXebZYaA/Bn/S+nXsjH3Wt9oQ6okH2kklYO+1UUdBSJFd67bi9IrpQXI2mPw==", - "dependencies": { - "HarfBuzzSharp": "7.3.0.3" - } + "resolved": "8.3.1.1", + "contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg==" }, "HarfBuzzSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "UAwIYnkbBTzBJv1Id8FijY/i8QiIepRemSXufU8fyzwWhYJdx4+ajG8yQUie5HW/uusbVLFSr26muSlJOFDgSw==" + "resolved": "8.3.1.1", + "contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ==" }, "HarfBuzzSharp.NativeAssets.WebAssembly": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "OpheDNp9a3nC6hWNACemWkNEXJ4tWP3Gw9bykw3FbyeEmU2nUDtLIp6VgNnjHAPRMgUs1Kl7m4gJpzVYwC7CZw==" + "resolved": "8.3.1.1", + "contentHash": "loJweK2u/mH/3C2zBa0ggJlITIszOkK64HLAZB7FUT670dTg965whLFYHDQo69NmC4+d9UN0icLC9VHidXaVCA==" }, "HarfBuzzSharp.NativeAssets.Win32": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "RPxRXD16KtSs8Yxr2RK9Qs7AwyN9MlpqZIYs0AvfaJwl7RAtVhC0+u2f2SKwX0uMYYd3O98Z+OBA1sj6aWVKQA==" + "resolved": "8.3.1.1", + "contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA==" }, "MicroCom.Runtime": { "type": "Transitive", @@ -262,8 +257,7 @@ "resolved": "2.2.0", "contentHash": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", "dependencies": { - "Microsoft.Net.Http.Headers": "2.2.0", - "System.Text.Encodings.Web": "4.5.0" + "Microsoft.Net.Http.Headers": "2.2.0" } }, "Microsoft.Bcl.AsyncInterfaces": { @@ -310,8 +304,7 @@ "resolved": "2.2.0", "contentHash": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", "dependencies": { - "Microsoft.Extensions.Primitives": "2.2.0", - "System.Buffers": "4.5.0" + "Microsoft.Extensions.Primitives": "2.2.0" } }, "Microsoft.Win32.SystemEvents": { @@ -326,14 +319,19 @@ }, "ReactiveUI": { "type": "Transitive", - "resolved": "20.1.1", - "contentHash": "9hNPknWjijnaSWs6auypoXqUptPZcRpUypF+cf1zD50fgW+SEoQda502N3fVZ2eWPcaiUad+z6GaLwOWmUVHNw==", + "resolved": "22.2.1", + "contentHash": "P0ZaWZdRVoycwfcEpkC8C/b9mbEJmueTPkBA4PON9NiopGjUY2YhIP5Dm8lki/FLH9aiKt18NXsDwv/mgO/1Dw==", "dependencies": { - "DynamicData": "8.4.1", - "Splat": "15.1.1", - "System.ComponentModel.Annotations": "5.0.0" + "DynamicData": "9.4.1", + "Splat": "17.1.1", + "System.Reactive": "6.1.0" } }, + "ReactiveUI.SourceGenerators.Analyzers.CodeFixes": { + "type": "Transitive", + "resolved": "2.6.1", + "contentHash": "zawC2el6QLXwapxg1N1Kf+8+sgd4h/QW0ruirnSvk/fuMt4PJmu6LpE00bMlIU3B3LZwMJFEbkrvB7wcs7OFKw==" + }, "SharpZipLib": { "type": "Transitive", "resolved": "1.3.3", @@ -373,18 +371,39 @@ }, "Splat": { "type": "Transitive", - "resolved": "15.1.1", - "contentHash": "RHDTdF90FwVbRia2cmuIzkiVoETqnXSB2dDBBi/I35HWXqv4OKGqoMcfcd6obMvO2OmmY5PjU1M62K8LkJafAA==" + "resolved": "17.1.1", + "contentHash": "WwSoJ6aPHlXwWS2jfUtKQIgzQQDSuE/iGbqEFRcM1DKVQelf+XSfnaur6oAqzECobuJLC9nqUMeDH19gU6JDag==", + "dependencies": { + "Splat.Builder": "17.1.1", + "Splat.Logging": "17.1.1", + "System.Reactive": "6.0.2" + } }, - "System.Buffers": { + "Splat.Builder": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + "resolved": "17.1.1", + "contentHash": "xlA5ErMwa/mg//uC5Ajv4ns3HbWXE0VJiyfupdsLfC+1HPf2MIUUk9ViYxwearB9nkZ+mAmteVD0keBhg/LYsQ==", + "dependencies": { + "Splat.Core": "17.1.1", + "System.Reactive": "6.0.2" + } }, - "System.ComponentModel.Annotations": { + "Splat.Core": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg==" + "resolved": "17.1.1", + "contentHash": "sRNcO9EcnrCMr2OF4IxD8gcHtIAfCi98GwVZEK9X294RQEBIsy7sHwy8fndlxLguIHvjDJMz85v+qd226pwdNw==", + "dependencies": { + "System.Reactive": "6.0.2" + } + }, + "Splat.Logging": { + "type": "Transitive", + "resolved": "17.1.1", + "contentHash": "OSkQVj6/hYVHa0taPasaknuOLw4l8CH9oTFRNuFayDowhHtGg6+UQb2LeSJXV+EXRmUL2E/TFz2ucitnQyTXJQ==", + "dependencies": { + "Splat.Core": "17.1.1", + "System.Reactive": "6.0.2" + } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", @@ -409,20 +428,10 @@ "Microsoft.Win32.SystemEvents": "7.0.0" } }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA==" - }, "System.Reactive": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "rHaWtKDwCi9qJ3ObKo8LHPMuuwv33YbmQi7TcUK1C264V3MFnOr5Im7QgCTdLniztP3GJyeiSg5x8NqYJFqRmg==" - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + "resolved": "6.1.0", + "contentHash": "M5cCC1ZMkZr9jbSQGTHnVkb5TDN67qWCV7AP8TAHdGkvDlu0puT5NzemESNn9+HkYIDpWpocP68/i+/ame2/2w==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", @@ -437,14 +446,6 @@ "System.Windows.Extensions": "7.0.0" } }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -455,29 +456,26 @@ }, "Tmds.DBus.Protocol": { "type": "Transitive", - "resolved": "0.20.0", - "contentHash": "2gkt2kuYPhDKd8gtl34jZSJOnn4nRJfFngCDcTZT/uySbK++ua0YQx2418l9Rn1Y4dE5XNq6zG9ZsE5ltLlNNw==", - "dependencies": { - "System.IO.Pipelines": "8.0.0" - } + "resolved": "0.21.2", + "contentHash": "ScSMrUrrw8px4kK1Glh0fZv/HQUlg1078bNXNPfRPKQ3WbRzV9HpsydYEOgSoMK5LWICMf2bMwIFH0pGjxjcMA==" }, "sfp": { "type": "Project", "dependencies": { - "FileWatcherEx": "[2.6.0, )", + "FileWatcherEx": "[2.7.0, )", "Flurl.Http": "[4.0.2, )", - "NLog": "[5.3.4, )", + "NLog": "[6.1.0, )", "PortableJsonSettingsProvider": "[0.2.2, )", "PuppeteerSharp": "[8.0.0, )", "WindowsShortcutFactory": "[1.2.0, )", - "WmiLight": "[6.9.0, )" + "WmiLight": "[7.1.0, )" } }, "FileWatcherEx": { "type": "CentralTransitive", - "requested": "[2.6.0, )", - "resolved": "2.6.0", - "contentHash": "zHDfVCmqguDYzcRhUmsAzUVBMm5G3nvkD5+p3/f5TjqPHcxFvDoEfSKvxZ4/Mid6fVDZJOU+7xxittk+AIE2Bw==" + "requested": "[2.7.0, )", + "resolved": "2.7.0", + "contentHash": "gw4isJWR2mr6UDSHUOGY3lR1fh4OsSRbJOwQz9Wz2V1JMQ6I8OyHB1CG3ekgrTnj+Ik4uXdMzmlPdOfXq2FApQ==" }, "Flurl.Http": { "type": "CentralTransitive", @@ -508,8 +506,7 @@ "Microsoft.Bcl.AsyncInterfaces": "1.1.0", "Microsoft.Extensions.Logging": "2.0.2", "Newtonsoft.Json": "13.0.1", - "SharpZipLib": "1.3.3", - "System.Text.Encodings.Web": "6.0.0" + "SharpZipLib": "1.3.3" } }, "WindowsShortcutFactory": { @@ -520,42 +517,39 @@ }, "WmiLight": { "type": "CentralTransitive", - "requested": "[6.9.0, )", - "resolved": "6.9.0", - "contentHash": "XbfkP6stpmqPqP7kgPGzu8a4ahrjgqmPiiDNYQX/rH/ZX7neTaxTifxHOXqj3S1MKJLMg2VKEBcI2gFzPzKPjQ==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" } }, - "net8.0/linux-x64": { + "net10.0/linux-x64": { "Avalonia.Angle.Windows.Natives": { "type": "Transitive", - "resolved": "2.1.22045.20230930", - "contentHash": "Bo3qOhKC1b84BIhiogndMdAzB3UrrESKK7hS769f5HWeoMw/pcd42US5KFYW2JJ4ZSTrXnP8mXwLTMzh+S+9Lg==" + "resolved": "2.1.25547.20250602", + "contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A==" }, "Avalonia.Native": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "yW9IGfa7kBuEcYP4ni7nGYNI2HjqaBg+cPJXZeiXf8RFptmluMv75hMyyq8FYIZwVcZIEcwEgff81a7b4aNTVQ==", + "resolved": "11.3.11", + "contentHash": "21XxwcOCYuf7fSYBsc4sF7CS0xNK80iDFyXyK9L4awnrjZOJjMuNSGiJ2L5+RQQkXnXaB8ECvMAWN3e/9WN3aw==", "dependencies": { - "Avalonia": "11.2.3" + "Avalonia": "11.3.11" } }, "HarfBuzzSharp.NativeAssets.Linux": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "hkcHeTfOyIeJuPtO/QfoqkDvV/MXebZYaA/Bn/S+nXsjH3Wt9oQ6okH2kklYO+1UUdBSJFd67bi9IrpQXI2mPw==", - "dependencies": { - "HarfBuzzSharp": "7.3.0.3" - } + "resolved": "8.3.1.1", + "contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg==" }, "HarfBuzzSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "UAwIYnkbBTzBJv1Id8FijY/i8QiIepRemSXufU8fyzwWhYJdx4+ajG8yQUie5HW/uusbVLFSr26muSlJOFDgSw==" + "resolved": "8.3.1.1", + "contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ==" }, "HarfBuzzSharp.NativeAssets.Win32": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "RPxRXD16KtSs8Yxr2RK9Qs7AwyN9MlpqZIYs0AvfaJwl7RAtVhC0+u2f2SKwX0uMYYd3O98Z+OBA1sj6aWVKQA==" + "resolved": "8.3.1.1", + "contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", @@ -598,14 +592,6 @@ "resolved": "7.0.0", "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -616,42 +602,39 @@ }, "WmiLight": { "type": "CentralTransitive", - "requested": "[6.9.0, )", - "resolved": "6.9.0", - "contentHash": "XbfkP6stpmqPqP7kgPGzu8a4ahrjgqmPiiDNYQX/rH/ZX7neTaxTifxHOXqj3S1MKJLMg2VKEBcI2gFzPzKPjQ==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" } }, - "net8.0/osx-x64": { + "net10.0/osx-x64": { "Avalonia.Angle.Windows.Natives": { "type": "Transitive", - "resolved": "2.1.22045.20230930", - "contentHash": "Bo3qOhKC1b84BIhiogndMdAzB3UrrESKK7hS769f5HWeoMw/pcd42US5KFYW2JJ4ZSTrXnP8mXwLTMzh+S+9Lg==" + "resolved": "2.1.25547.20250602", + "contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A==" }, "Avalonia.Native": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "yW9IGfa7kBuEcYP4ni7nGYNI2HjqaBg+cPJXZeiXf8RFptmluMv75hMyyq8FYIZwVcZIEcwEgff81a7b4aNTVQ==", + "resolved": "11.3.11", + "contentHash": "21XxwcOCYuf7fSYBsc4sF7CS0xNK80iDFyXyK9L4awnrjZOJjMuNSGiJ2L5+RQQkXnXaB8ECvMAWN3e/9WN3aw==", "dependencies": { - "Avalonia": "11.2.3" + "Avalonia": "11.3.11" } }, "HarfBuzzSharp.NativeAssets.Linux": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "hkcHeTfOyIeJuPtO/QfoqkDvV/MXebZYaA/Bn/S+nXsjH3Wt9oQ6okH2kklYO+1UUdBSJFd67bi9IrpQXI2mPw==", - "dependencies": { - "HarfBuzzSharp": "7.3.0.3" - } + "resolved": "8.3.1.1", + "contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg==" }, "HarfBuzzSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "UAwIYnkbBTzBJv1Id8FijY/i8QiIepRemSXufU8fyzwWhYJdx4+ajG8yQUie5HW/uusbVLFSr26muSlJOFDgSw==" + "resolved": "8.3.1.1", + "contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ==" }, "HarfBuzzSharp.NativeAssets.Win32": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "RPxRXD16KtSs8Yxr2RK9Qs7AwyN9MlpqZIYs0AvfaJwl7RAtVhC0+u2f2SKwX0uMYYd3O98Z+OBA1sj6aWVKQA==" + "resolved": "8.3.1.1", + "contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", @@ -694,14 +677,6 @@ "resolved": "7.0.0", "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -712,42 +687,39 @@ }, "WmiLight": { "type": "CentralTransitive", - "requested": "[6.9.0, )", - "resolved": "6.9.0", - "contentHash": "XbfkP6stpmqPqP7kgPGzu8a4ahrjgqmPiiDNYQX/rH/ZX7neTaxTifxHOXqj3S1MKJLMg2VKEBcI2gFzPzKPjQ==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" } }, - "net8.0/win-x64": { + "net10.0/win-x64": { "Avalonia.Angle.Windows.Natives": { "type": "Transitive", - "resolved": "2.1.22045.20230930", - "contentHash": "Bo3qOhKC1b84BIhiogndMdAzB3UrrESKK7hS769f5HWeoMw/pcd42US5KFYW2JJ4ZSTrXnP8mXwLTMzh+S+9Lg==" + "resolved": "2.1.25547.20250602", + "contentHash": "ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A==" }, "Avalonia.Native": { "type": "Transitive", - "resolved": "11.2.3", - "contentHash": "yW9IGfa7kBuEcYP4ni7nGYNI2HjqaBg+cPJXZeiXf8RFptmluMv75hMyyq8FYIZwVcZIEcwEgff81a7b4aNTVQ==", + "resolved": "11.3.11", + "contentHash": "21XxwcOCYuf7fSYBsc4sF7CS0xNK80iDFyXyK9L4awnrjZOJjMuNSGiJ2L5+RQQkXnXaB8ECvMAWN3e/9WN3aw==", "dependencies": { - "Avalonia": "11.2.3" + "Avalonia": "11.3.11" } }, "HarfBuzzSharp.NativeAssets.Linux": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "hkcHeTfOyIeJuPtO/QfoqkDvV/MXebZYaA/Bn/S+nXsjH3Wt9oQ6okH2kklYO+1UUdBSJFd67bi9IrpQXI2mPw==", - "dependencies": { - "HarfBuzzSharp": "7.3.0.3" - } + "resolved": "8.3.1.1", + "contentHash": "3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg==" }, "HarfBuzzSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "UAwIYnkbBTzBJv1Id8FijY/i8QiIepRemSXufU8fyzwWhYJdx4+ajG8yQUie5HW/uusbVLFSr26muSlJOFDgSw==" + "resolved": "8.3.1.1", + "contentHash": "jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ==" }, "HarfBuzzSharp.NativeAssets.Win32": { "type": "Transitive", - "resolved": "7.3.0.3", - "contentHash": "RPxRXD16KtSs8Yxr2RK9Qs7AwyN9MlpqZIYs0AvfaJwl7RAtVhC0+u2f2SKwX0uMYYd3O98Z+OBA1sj6aWVKQA==" + "resolved": "8.3.1.1", + "contentHash": "UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", @@ -790,14 +762,6 @@ "resolved": "7.0.0", "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "7.0.0", @@ -808,10 +772,10 @@ }, "WmiLight": { "type": "CentralTransitive", - "requested": "[6.9.0, )", - "resolved": "6.9.0", - "contentHash": "XbfkP6stpmqPqP7kgPGzu8a4ahrjgqmPiiDNYQX/rH/ZX7neTaxTifxHOXqj3S1MKJLMg2VKEBcI2gFzPzKPjQ==" + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "kWwFsCm9Xu68hhmpI+2BXGPvMoXqNMok0d74GE1td4AEzas+J3k9dFOhtksZ4J5egVZeebXbp5zYMujzPslW6A==" } } } -} +} \ No newline at end of file