Cleaned up warnings
This commit is contained in:
parent
c2ff1e857d
commit
9fb1c69a5c
47 changed files with 203 additions and 413 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
|
@ -7,24 +9,16 @@ indent_style = space
|
||||||
indent_size = 4 # A property with the same name was updated with a value 2 in a section [{*.yaml,*.yml}]
|
indent_size = 4 # A property with the same name was updated with a value 2 in a section [{*.yaml,*.yml}]
|
||||||
|
|
||||||
[{*.yaml,*.yml}]
|
[{*.yaml,*.yml}]
|
||||||
indent_style = space
|
|
||||||
indent_size = 2 # A property with the same name was updated with a value 4 in a section [*]; with a value 4 in a section [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
indent_size = 2 # A property with the same name was updated with a value 4 in a section [*]; with a value 4 in a section [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
||||||
dotnet_diagnostic.CA1047.severity = error
|
|
||||||
|
|
||||||
[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
||||||
indent_style = space
|
|
||||||
indent_size = 4 # A property with the same name was updated with a value 2 in a section [{*.yaml,*.yml}]
|
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
dotnet_style_null_propagation = true:suggestion
|
dotnet_style_null_propagation = true:suggestion
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
|
|
||||||
dotnet_style_prefer_auto_properties = true:warning
|
|
||||||
dotnet_style_object_initializer = true:suggestion
|
dotnet_style_object_initializer = true:suggestion
|
||||||
dotnet_style_collection_initializer = true:suggestion
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
|
||||||
dotnet_style_explicit_tuple_names = true:suggestion
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
|
@ -35,13 +29,8 @@ dotnet_style_namespace_match_folder = true:suggestion
|
||||||
dotnet_style_readonly_field = true:warning
|
dotnet_style_readonly_field = true:warning
|
||||||
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
|
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
|
||||||
dotnet_style_predefined_type_for_member_access = true:warning
|
dotnet_style_predefined_type_for_member_access = true:warning
|
||||||
dotnet_style_require_accessibility_modifiers = always:error
|
|
||||||
dotnet_style_allow_multiple_blank_lines_experimental = false:silent
|
dotnet_style_allow_multiple_blank_lines_experimental = false:silent
|
||||||
dotnet_style_allow_statement_immediately_after_block_experimental = false:silent
|
dotnet_style_allow_statement_immediately_after_block_experimental = false:silent
|
||||||
dotnet_code_quality_unused_parameters = all:warning
|
|
||||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
|
|
||||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
|
|
||||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
|
|
||||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||||
dotnet_style_qualification_for_field = false:silent
|
dotnet_style_qualification_for_field = false:silent
|
||||||
dotnet_style_qualification_for_property = false:silent
|
dotnet_style_qualification_for_property = false:silent
|
||||||
|
@ -68,7 +57,7 @@ dotnet_diagnostic.CA1832.severity = warning
|
||||||
dotnet_diagnostic.CA1833.severity = warning
|
dotnet_diagnostic.CA1833.severity = warning
|
||||||
dotnet_diagnostic.CA1842.severity = warning
|
dotnet_diagnostic.CA1842.severity = warning
|
||||||
dotnet_diagnostic.CA1843.severity = warning
|
dotnet_diagnostic.CA1843.severity = warning
|
||||||
dotnet_diagnostic.CA1836.severity = none
|
dotnet_diagnostic.CA1836.severity = warning
|
||||||
dotnet_diagnostic.CA1839.severity = warning
|
dotnet_diagnostic.CA1839.severity = warning
|
||||||
dotnet_diagnostic.CA1840.severity = warning
|
dotnet_diagnostic.CA1840.severity = warning
|
||||||
dotnet_diagnostic.CA1846.severity = warning
|
dotnet_diagnostic.CA1846.severity = warning
|
||||||
|
@ -77,6 +66,8 @@ dotnet_diagnostic.CA1852.severity = suggestion
|
||||||
dotnet_diagnostic.CA2012.severity = warning
|
dotnet_diagnostic.CA2012.severity = warning
|
||||||
dotnet_diagnostic.CA2019.severity = warning
|
dotnet_diagnostic.CA2019.severity = warning
|
||||||
dotnet_diagnostic.CA2211.severity = warning
|
dotnet_diagnostic.CA2211.severity = warning
|
||||||
|
dotnet_diagnostic.CA1822.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1725.severity = suggestion
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
|
||||||
|
@ -712,14 +703,16 @@ resharper_blank_lines_inside_region = 1
|
||||||
resharper_blank_lines_inside_type = 0
|
resharper_blank_lines_inside_type = 0
|
||||||
resharper_blank_line_after_pi = true
|
resharper_blank_line_after_pi = true
|
||||||
resharper_braces_for_dowhile = required
|
resharper_braces_for_dowhile = required
|
||||||
resharper_braces_for_fixed = required
|
resharper_braces_for_fixed = required_for_multiline
|
||||||
resharper_braces_for_for = required_for_multiline
|
resharper_braces_for_for = required_for_multiline
|
||||||
resharper_braces_for_foreach = required_for_multiline
|
resharper_braces_for_foreach = required_for_multiline
|
||||||
resharper_braces_for_ifelse = required_for_multiline
|
resharper_braces_for_ifelse = required_for_multiline
|
||||||
resharper_braces_for_lock = required
|
resharper_braces_for_lock = required_for_multiline
|
||||||
resharper_braces_for_using = required
|
resharper_braces_for_using = required_for_multiline
|
||||||
resharper_braces_for_while = required_for_multiline
|
resharper_braces_for_while = required_for_multiline
|
||||||
resharper_braces_redundant = true
|
resharper_braces_redundant = true
|
||||||
|
resharper_builtin_type_apply_to_native_integer = true
|
||||||
|
resharper_csharp_empty_block_style = together_same_line
|
||||||
resharper_break_template_declaration = line_break
|
resharper_break_template_declaration = line_break
|
||||||
resharper_can_use_global_alias = false
|
resharper_can_use_global_alias = false
|
||||||
resharper_configure_await_analysis_mode = disabled
|
resharper_configure_await_analysis_mode = disabled
|
||||||
|
@ -817,7 +810,7 @@ resharper_indent_typearg_angles = inside
|
||||||
resharper_indent_typeparam_angles = inside
|
resharper_indent_typeparam_angles = inside
|
||||||
resharper_indent_type_constraints = true
|
resharper_indent_type_constraints = true
|
||||||
resharper_indent_wrapped_function_names = false
|
resharper_indent_wrapped_function_names = false
|
||||||
resharper_instance_members_qualify_declared_in = base_class
|
resharper_instance_members_qualify_declared_in =
|
||||||
resharper_int_align_assignments = false
|
resharper_int_align_assignments = false
|
||||||
resharper_int_align_binary_expressions = false
|
resharper_int_align_binary_expressions = false
|
||||||
resharper_int_align_declaration_names = false
|
resharper_int_align_declaration_names = false
|
||||||
|
@ -918,7 +911,7 @@ resharper_place_simple_switch_expression_on_single_line = false
|
||||||
resharper_place_type_attribute_on_same_line = false
|
resharper_place_type_attribute_on_same_line = false
|
||||||
resharper_place_type_constraints_on_same_line = true
|
resharper_place_type_constraints_on_same_line = true
|
||||||
resharper_prefer_explicit_discard_declaration = false
|
resharper_prefer_explicit_discard_declaration = false
|
||||||
resharper_prefer_separate_deconstructed_variables_declaration = true
|
resharper_prefer_separate_deconstructed_variables_declaration = false
|
||||||
resharper_preserve_spaces_inside_tags = pre,textarea
|
resharper_preserve_spaces_inside_tags = pre,textarea
|
||||||
resharper_qualified_using_at_nested_scope = false
|
resharper_qualified_using_at_nested_scope = false
|
||||||
resharper_quote_style = doublequoted
|
resharper_quote_style = doublequoted
|
||||||
|
@ -1069,6 +1062,7 @@ resharper_space_within_typeof_parentheses = false
|
||||||
resharper_space_within_type_argument_angles = false
|
resharper_space_within_type_argument_angles = false
|
||||||
resharper_space_within_type_parameter_angles = false
|
resharper_space_within_type_parameter_angles = false
|
||||||
resharper_space_within_type_parameter_parentheses = false
|
resharper_space_within_type_parameter_parentheses = false
|
||||||
|
resharper_csharp_allow_alias = false
|
||||||
resharper_special_else_if_treatment = true
|
resharper_special_else_if_treatment = true
|
||||||
resharper_static_members_qualify_members = none
|
resharper_static_members_qualify_members = none
|
||||||
resharper_static_members_qualify_with = declared_type
|
resharper_static_members_qualify_with = declared_type
|
||||||
|
@ -1289,7 +1283,7 @@ resharper_convert_to_using_declaration_highlighting = suggestion
|
||||||
resharper_convert_to_vb_auto_property_highlighting = suggestion
|
resharper_convert_to_vb_auto_property_highlighting = suggestion
|
||||||
resharper_convert_to_vb_auto_property_when_possible_highlighting = hint
|
resharper_convert_to_vb_auto_property_when_possible_highlighting = hint
|
||||||
resharper_convert_to_vb_auto_property_with_private_setter_highlighting = hint
|
resharper_convert_to_vb_auto_property_with_private_setter_highlighting = hint
|
||||||
resharper_convert_type_check_pattern_to_null_check_highlighting = warning
|
resharper_convert_type_check_pattern_to_null_check_highlighting = none
|
||||||
resharper_convert_type_check_to_null_check_highlighting = warning
|
resharper_convert_type_check_to_null_check_highlighting = warning
|
||||||
resharper_co_variant_array_conversion_highlighting = warning
|
resharper_co_variant_array_conversion_highlighting = warning
|
||||||
resharper_c_declaration_with_implicit_int_type_highlighting = warning
|
resharper_c_declaration_with_implicit_int_type_highlighting = warning
|
||||||
|
@ -1408,8 +1402,8 @@ resharper_meaningless_default_parameter_value_highlighting = warning
|
||||||
resharper_member_can_be_internal_highlighting = none
|
resharper_member_can_be_internal_highlighting = none
|
||||||
resharper_member_can_be_made_static_global_highlighting = hint
|
resharper_member_can_be_made_static_global_highlighting = hint
|
||||||
resharper_member_can_be_made_static_local_highlighting = hint
|
resharper_member_can_be_made_static_local_highlighting = hint
|
||||||
resharper_member_can_be_private_global_highlighting = suggestion
|
resharper_member_can_be_private_global_highlighting = hint
|
||||||
resharper_member_can_be_private_local_highlighting = suggestion
|
resharper_member_can_be_private_local_highlighting = hint
|
||||||
resharper_member_can_be_protected_global_highlighting = suggestion
|
resharper_member_can_be_protected_global_highlighting = suggestion
|
||||||
resharper_member_can_be_protected_local_highlighting = suggestion
|
resharper_member_can_be_protected_local_highlighting = suggestion
|
||||||
resharper_member_hides_interface_member_with_default_implementation_highlighting = warning
|
resharper_member_hides_interface_member_with_default_implementation_highlighting = warning
|
||||||
|
@ -1537,7 +1531,7 @@ resharper_redundant_assignment_highlighting = warning
|
||||||
resharper_redundant_attribute_parentheses_highlighting = hint
|
resharper_redundant_attribute_parentheses_highlighting = hint
|
||||||
resharper_redundant_attribute_usage_property_highlighting = suggestion
|
resharper_redundant_attribute_usage_property_highlighting = suggestion
|
||||||
resharper_redundant_base_constructor_call_highlighting = warning
|
resharper_redundant_base_constructor_call_highlighting = warning
|
||||||
resharper_redundant_base_qualifier_highlighting = warning
|
resharper_redundant_base_qualifier_highlighting = none
|
||||||
resharper_redundant_blank_lines_highlighting = none
|
resharper_redundant_blank_lines_highlighting = none
|
||||||
resharper_redundant_bool_compare_highlighting = warning
|
resharper_redundant_bool_compare_highlighting = warning
|
||||||
resharper_redundant_case_label_highlighting = warning
|
resharper_redundant_case_label_highlighting = warning
|
||||||
|
@ -1547,7 +1541,7 @@ resharper_redundant_check_before_assignment_highlighting = warning
|
||||||
resharper_redundant_collection_initializer_element_braces_highlighting = hint
|
resharper_redundant_collection_initializer_element_braces_highlighting = hint
|
||||||
resharper_redundant_configure_await_highlighting = suggestion
|
resharper_redundant_configure_await_highlighting = suggestion
|
||||||
resharper_redundant_declaration_semicolon_highlighting = hint
|
resharper_redundant_declaration_semicolon_highlighting = hint
|
||||||
resharper_redundant_default_member_initializer_highlighting = warning
|
resharper_redundant_default_member_initializer_highlighting = hint
|
||||||
resharper_redundant_delegate_creation_highlighting = warning
|
resharper_redundant_delegate_creation_highlighting = warning
|
||||||
resharper_redundant_disable_warning_comment_highlighting = warning
|
resharper_redundant_disable_warning_comment_highlighting = warning
|
||||||
resharper_redundant_discard_designation_highlighting = suggestion
|
resharper_redundant_discard_designation_highlighting = suggestion
|
||||||
|
@ -1872,4 +1866,18 @@ resharper_virtual_member_never_overridden_local_highlighting = suggestion
|
||||||
resharper_void_method_with_must_use_return_value_attribute_highlighting = warning
|
resharper_void_method_with_must_use_return_value_attribute_highlighting = warning
|
||||||
resharper_with_expression_instead_of_initializer_highlighting = suggestion
|
resharper_with_expression_instead_of_initializer_highlighting = suggestion
|
||||||
resharper_wrong_indent_size_highlighting = none
|
resharper_wrong_indent_size_highlighting = none
|
||||||
resharper_xunit_xunit_test_with_console_output_highlighting = warning
|
resharper_xunit_xunit_test_with_console_output_highlighting = warning
|
||||||
|
resharper_arrange_constructor_or_destructor_body_highlighting = hint
|
||||||
|
resharper_arrange_local_function_body_highlighting = hint
|
||||||
|
resharper_arrange_method_or_operator_body_highlighting = hint
|
||||||
|
resharper_arrange_null_checking_pattern_highlighting = suggestion
|
||||||
|
resharper_enforce_do_while_statement_braces_highlighting = warning
|
||||||
|
resharper_enforce_fixed_statement_braces_highlighting = warning
|
||||||
|
resharper_enforce_foreach_statement_braces_highlighting = suggestion
|
||||||
|
resharper_enforce_for_statement_braces_highlighting = suggestion
|
||||||
|
resharper_enforce_if_statement_braces_highlighting = suggestion
|
||||||
|
resharper_enforce_lock_statement_braces_highlighting = warning
|
||||||
|
resharper_enforce_while_statement_braces_highlighting = suggestion
|
||||||
|
resharper_remove_redundant_braces_highlighting = hint
|
||||||
|
resharper_suggest_discard_declaration_var_style_highlighting = warning
|
||||||
|
resharper_prefer_concrete_value_over_default_highlighting = hint
|
|
@ -20,7 +20,7 @@ public partial class App : Application
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IServiceProvider Services { get; } = new ServiceCollection()
|
public IServiceProvider Services { get; } = new ServiceCollection()
|
||||||
.RegisterViewsAndViewModels(Assembly.GetExecutingAssembly())
|
.RegisterViewsAndViewModels(Assembly.GetExecutingAssembly())
|
||||||
.RegisterServices()
|
.RegisterAppServices()
|
||||||
.BuildServiceProvider(true);
|
.BuildServiceProvider(true);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -6,7 +6,7 @@ using EllieHub.Features.AppWindow.Views.Windows;
|
||||||
using EllieHub.Features.BotConfig.Services.Abstractions;
|
using EllieHub.Features.BotConfig.Services.Abstractions;
|
||||||
using EllieHub.Features.BotConfig.Services.Mocks;
|
using EllieHub.Features.BotConfig.Services.Mocks;
|
||||||
using EllieHub.Features.BotConfig.ViewModels;
|
using EllieHub.Features.BotConfig.ViewModels;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Avalonia.DesignData.Controls;
|
namespace EllieHub.Avalonia.DesignData.Controls;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ using EllieHub.Features.AppConfig.Services.Abstractions;
|
||||||
using EllieHub.Features.AppConfig.Services.Mocks;
|
using EllieHub.Features.AppConfig.Services.Mocks;
|
||||||
using EllieHub.Features.AppConfig.ViewModels;
|
using EllieHub.Features.AppConfig.ViewModels;
|
||||||
using EllieHub.Features.AppWindow.Views.Windows;
|
using EllieHub.Features.AppWindow.Views.Windows;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Avalonia.DesignData.Controls;
|
namespace EllieHub.Avalonia.DesignData.Controls;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using EllieHub.Avalonia.DesignData.Common;
|
using EllieHub.Avalonia.DesignData.Common;
|
||||||
using EllieHub.Features.AppWindow.Views.Windows;
|
using EllieHub.Features.AppWindow.Views.Windows;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Avalonia.DesignData.Controls;
|
namespace EllieHub.Avalonia.DesignData.Controls;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,6 @@ public sealed class DesignFakeConsoleViewModel : FakeConsoleViewModel
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a mock <see cref="FakeConsoleViewModel"/> to be used at design-time.
|
/// Creates a mock <see cref="FakeConsoleViewModel"/> to be used at design-time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DesignFakeConsoleViewModel() : base()
|
public DesignFakeConsoleViewModel()
|
||||||
=> Watermark = "Sample watermark.";
|
=> Watermark = "Sample watermark.";
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using EllieHub.Avalonia.DesignData.Common;
|
using EllieHub.Avalonia.DesignData.Common;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Avalonia.DesignData.Controls;
|
namespace EllieHub.Avalonia.DesignData.Controls;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<FontFamily x:Key="NotoSansFont">avares://EllieHub/Assets/Fonts/NotoSans-Regular.ttf</FontFamily>
|
||||||
|
<FontFamily x:Key="NotoSansBoldFont">avares://EllieHub/Assets/Fonts/NotoSans-Bold.ttf</FontFamily>
|
||||||
|
|
||||||
<!--Preview-->
|
<!--Preview-->
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="20">
|
<Border Padding="20">
|
||||||
|
@ -13,7 +17,4 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<FontFamily x:Key="NotoSansFont">avares://EllieHub/Assets/Fonts/NotoSans-Regular.ttf</FontFamily>
|
|
||||||
<FontFamily x:Key="NotoSansBoldFont">avares://EllieHub/Assets/Fonts/NotoSans-Bold.ttf</FontFamily>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
|
@ -50,7 +50,7 @@
|
||||||
<Bitmap x:Key="UrlIcon">avares://EllieHub/Assets/Light/icon-link.png</Bitmap>
|
<Bitmap x:Key="UrlIcon">avares://EllieHub/Assets/Light/icon-link.png</Bitmap>
|
||||||
<Bitmap x:Key="SuggestionIcon">avares://EllieHub/Assets/Light/icon-suggest.png</Bitmap>
|
<Bitmap x:Key="SuggestionIcon">avares://EllieHub/Assets/Light/icon-suggest.png</Bitmap>
|
||||||
<Bitmap x:Key="DiscordIcon">avares://EllieHub/Assets/Light/icon-support.png</Bitmap>
|
<Bitmap x:Key="DiscordIcon">avares://EllieHub/Assets/Light/icon-support.png</Bitmap>
|
||||||
<WindowIcon x:Key="EllieHubIcon">avares://EllieHub/Assets/Light/ellieupdatericon.ico</WindowIcon>
|
<WindowIcon x:Key="EllieHubIcon"><![CDATA[avares://EllieHub/Assets/Light/ellieupdatericon.ico]]></WindowIcon>
|
||||||
<Bitmap x:Key="EllieHubImage">avares://EllieHub/Assets/Light/ellieupdatericon.ico</Bitmap>
|
<Bitmap x:Key="EllieHubImage">avares://EllieHub/Assets/Light/ellieupdatericon.ico</Bitmap>
|
||||||
<Bitmap x:Key="TerminalIcon">avares://EllieHub/Assets/Light/terminal.png</Bitmap>
|
<Bitmap x:Key="TerminalIcon">avares://EllieHub/Assets/Light/terminal.png</Bitmap>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
<Bitmap x:Key="UrlIcon">avares://EllieHub/Assets/Dark/icon-link.png</Bitmap>
|
<Bitmap x:Key="UrlIcon">avares://EllieHub/Assets/Dark/icon-link.png</Bitmap>
|
||||||
<Bitmap x:Key="SuggestionIcon">avares://EllieHub/Assets/Dark/icon-suggest.png</Bitmap>
|
<Bitmap x:Key="SuggestionIcon">avares://EllieHub/Assets/Dark/icon-suggest.png</Bitmap>
|
||||||
<Bitmap x:Key="DiscordIcon">avares://EllieHub/Assets/Dark/icon-support.png</Bitmap>
|
<Bitmap x:Key="DiscordIcon">avares://EllieHub/Assets/Dark/icon-support.png</Bitmap>
|
||||||
<WindowIcon x:Key="EllieHubIcon">avares://EllieHub/Assets/Dark/ellieupdatericon.ico</WindowIcon>
|
<WindowIcon x:Key="EllieHubIcon"><![CDATA[avares://EllieHub/Assets/Dark/ellieupdatericon.ico]]></WindowIcon>
|
||||||
<Bitmap x:Key="EllieHubImage">avares://EllieHub/Assets/Dark/ellieupdatericon.ico</Bitmap>
|
<Bitmap x:Key="EllieHubImage">avares://EllieHub/Assets/Dark/ellieupdatericon.ico</Bitmap>
|
||||||
<Bitmap x:Key="TerminalIcon">avares://EllieHub/Assets/Dark/terminal.png</Bitmap>
|
<Bitmap x:Key="TerminalIcon">avares://EllieHub/Assets/Dark/terminal.png</Bitmap>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace EllieHub.Common;
|
namespace EllieHub.Common;
|
||||||
|
|
||||||
|
@ -11,205 +8,30 @@ namespace EllieHub.Common;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class Utilities
|
internal static class Utilities
|
||||||
{
|
{
|
||||||
private static readonly string _programVerifier = (Environment.OSVersion.Platform is PlatformID.Win32NT) ? "where" : "which";
|
|
||||||
private static readonly string _envPathSeparator = (Environment.OSVersion.Platform is PlatformID.Win32NT) ? ";" : ":";
|
|
||||||
private static readonly EnvironmentVariableTarget _envTarget = (Environment.OSVersion.Platform is PlatformID.Win32NT)
|
|
||||||
? EnvironmentVariableTarget.User
|
|
||||||
: EnvironmentVariableTarget.Process;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads an image embeded with this application.
|
/// Loads an image embedded with this application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri">An uri that starts with "avares://"</param>
|
/// <param name="uri">An uri that starts with "avares://"</param>
|
||||||
/// <remarks>Valid uris must start with "avares://".</remarks>
|
/// <remarks>Valid uris must start with "avares://".</remarks>
|
||||||
/// <returns>The embeded image or the default bot avatar placeholder.</returns>
|
/// <returns>The embedded image or the default bot avatar placeholder.</returns>
|
||||||
/// <exception cref="FileNotFoundException">Occurs when the embeded resource does not exist.</exception>
|
/// <exception cref="FileNotFoundException">Occurs when the embedded resource does not exist.</exception>
|
||||||
public static SKBitmap LoadEmbededImage(string? uri = default)
|
public static SKBitmap LoadEmbeddedImage(string? uri = default)
|
||||||
{
|
{
|
||||||
return (string.IsNullOrWhiteSpace(uri) || !uri.StartsWith("avares://", StringComparison.Ordinal))
|
return (string.IsNullOrWhiteSpace(uri) || !uri.StartsWith("avares://", StringComparison.Ordinal))
|
||||||
? SKBitmap.Decode(AssetLoader.Open(new Uri(AppConstants.BotAvatarUri)))
|
? SKBitmap.Decode(AssetLoader.Open(new(AppConstants.BotAvatarUri)))
|
||||||
: SKBitmap.Decode(AssetLoader.Open(new Uri(uri)));
|
: SKBitmap.Decode(AssetLoader.Open(new(uri)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the image at the specified location or the bot avatar placeholder if it was not found.
|
/// Loads the image at the specified location or the bot avatar placeholder if it was not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri">The absolute path to the image file or <see langword="null"/> to get the avatar placeholder.</param>
|
/// <param name="imagePath">The absolute path to the image file or <see langword="null"/> to get the avatar placeholder.</param>
|
||||||
/// <remarks>This fallsback to <see cref="LoadEmbededImage(string?)"/> if <paramref name="uri"/> doesn't point to a valid image file.</remarks>
|
/// <remarks>This fallsback to <see cref="LoadEmbeddedImage"/> if <paramref name="imagePath"/> doesn't point to a valid image file.</remarks>
|
||||||
/// <returns>The requested image or the default bot avatar placeholder.</returns>
|
/// <returns>The requested image or the default bot avatar placeholder.</returns>
|
||||||
public static SKBitmap LoadLocalImage(string? uri = default)
|
public static SKBitmap LoadLocalImage(string? imagePath)
|
||||||
{
|
{
|
||||||
return (File.Exists(uri))
|
return (File.Exists(imagePath))
|
||||||
? SKBitmap.Decode(uri)
|
? SKBitmap.Decode(imagePath)
|
||||||
: LoadEmbededImage(uri);
|
: LoadEmbeddedImage(imagePath);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Safely casts an <see cref="object"/> to a <typeparamref name="T"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type to cast to.</typeparam>
|
|
||||||
/// <param name="obj">The object to be cast.</param>
|
|
||||||
/// <param name="castObject">The cast object, or <see langword="null"/> is casting failed.</param>
|
|
||||||
/// <returns><see langword="true"/> if the object was successfully cast, <see langword="false"/> otherwise.</returns>
|
|
||||||
public static bool TryCastTo<T>(object? obj, [MaybeNullWhen(false)] out T castObject)
|
|
||||||
{
|
|
||||||
if (obj is T result)
|
|
||||||
{
|
|
||||||
castObject = result;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
castObject = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts the specified program in the background.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="program">
|
|
||||||
/// The name of the program in the PATH environment variable,
|
|
||||||
/// or the absolute path to its executable.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="arguments">The arguments to the program.</param>
|
|
||||||
/// <returns>The process of the specified program.</returns>
|
|
||||||
/// <exception cref="ArgumentException" />
|
|
||||||
/// <exception cref="ArgumentNullException" />
|
|
||||||
/// <exception cref="Win32Exception">Occurs when <paramref name="program"/> does not exist.</exception>
|
|
||||||
/// <exception cref="InvalidOperationException">Occurs when the process fails to execute.</exception>
|
|
||||||
public static Process StartProcess(string program, string arguments = "")
|
|
||||||
{
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(program, nameof(program));
|
|
||||||
ArgumentNullException.ThrowIfNull(arguments, nameof(arguments));
|
|
||||||
|
|
||||||
return Process.Start(new ProcessStartInfo()
|
|
||||||
{
|
|
||||||
FileName = program,
|
|
||||||
Arguments = arguments,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
}) ?? throw new InvalidOperationException($"Failed spawing process for: {program} {arguments}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a program exists.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="programName">The name of the program.</param>
|
|
||||||
/// <param name="cToken">The cancellation token.</param>
|
|
||||||
/// <returns><see langword="true"/> if the program exists, <see langword="false"/> otherwise.</returns>
|
|
||||||
/// <exception cref="ArgumentException" />
|
|
||||||
/// <exception cref="ArgumentNullException" />
|
|
||||||
public static async ValueTask<bool> ProgramExistsAsync(string programName, CancellationToken cToken = default)
|
|
||||||
{
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(programName, nameof(programName));
|
|
||||||
|
|
||||||
using var process = StartProcess(_programVerifier, programName);
|
|
||||||
return !string.IsNullOrWhiteSpace(await process.StandardOutput.ReadToEndAsync(cToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Safely deletes a file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fileUri">The absolute path to the file.</param>
|
|
||||||
/// <returns><see langword="true"/> if the file was deleted, <see langword="false"/> otherwise.</returns>
|
|
||||||
/// <exception cref="ArgumentException" />
|
|
||||||
/// <exception cref="ArgumentNullException" />
|
|
||||||
/// <exception cref="IOException" />
|
|
||||||
/// <exception cref="NotSupportedException" />
|
|
||||||
/// <exception cref="PathTooLongException" />
|
|
||||||
/// <exception cref="UnauthorizedAccessException" />
|
|
||||||
public static bool TryDeleteFile(string fileUri)
|
|
||||||
{
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(fileUri, nameof(fileUri));
|
|
||||||
|
|
||||||
if (!File.Exists(fileUri))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
File.Delete(fileUri);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Safely deletes a directory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="directoryUri">The absolute path to the directory.</param>
|
|
||||||
/// <returns><see langword="true"/> if the directory was deleted, <see langword="false"/> otherwise.</returns>
|
|
||||||
/// <exception cref="ArgumentException" />
|
|
||||||
/// <exception cref="ArgumentNullException" />
|
|
||||||
/// <exception cref="IOException" />
|
|
||||||
/// <exception cref="DirectoryNotFoundException" />
|
|
||||||
/// <exception cref="PathTooLongException" />
|
|
||||||
/// <exception cref="UnauthorizedAccessException" />
|
|
||||||
public static bool TryDeleteDirectory(string directoryUri)
|
|
||||||
{
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(directoryUri, nameof(directoryUri));
|
|
||||||
|
|
||||||
if (!Directory.Exists(directoryUri))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Directory.Delete(directoryUri, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if this application can write to <paramref name="directoryUri"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="directoryUri">The absolute path to a directory.</param>
|
|
||||||
/// <returns><see langword="true"/> if writing is allowed, <see langword="false"/> otherwise.</returns>
|
|
||||||
/// <exception cref="PathTooLongException" />
|
|
||||||
/// <exception cref="DirectoryNotFoundException" />
|
|
||||||
public static bool CanWriteTo(string directoryUri)
|
|
||||||
{
|
|
||||||
var tempFileUri = Path.Combine(directoryUri, $"{Guid.NewGuid()}.tmp");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var fileStream = File.Create(tempFileUri);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
TryDeleteFile(tempFileUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a directory path to the PATH environment variable.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="directoryUri">The absolute path to a directory.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// On Windows, this needs to be called once and the dependencies will be available for the user forever. <br />
|
|
||||||
/// On Unix systems, we can only add to the PATH on a process basis, so this needs to be called at least once everytime the application is opened.
|
|
||||||
/// </remarks>
|
|
||||||
/// <returns><see langword="true"/> if <paramref name="directoryUri"/> got successfully added to the PATH envar, <see langword="false"/> otherwise.</returns>
|
|
||||||
/// <exception cref="ArgumentException" />
|
|
||||||
/// <exception cref="ArgumentNullException" />
|
|
||||||
public static bool AddPathToPATHEnvar(string directoryUri)
|
|
||||||
{
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(directoryUri, nameof(directoryUri));
|
|
||||||
|
|
||||||
if (File.Exists(directoryUri))
|
|
||||||
throw new ArgumentException("Parameter must point to a directory, not a file.", nameof(directoryUri));
|
|
||||||
|
|
||||||
var envPathValue = Environment.GetEnvironmentVariable("PATH", _envTarget) ?? string.Empty;
|
|
||||||
|
|
||||||
// If directoryPath is already in the PATH envar, don't add it again.
|
|
||||||
if (envPathValue.Contains(directoryUri, StringComparison.Ordinal))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var newPathEnvValue = envPathValue + _envPathSeparator + directoryUri;
|
|
||||||
|
|
||||||
// Add path to Windows' user envar, so it persists across reboots.
|
|
||||||
if (Environment.OSVersion.Platform is PlatformID.Win32NT)
|
|
||||||
Environment.SetEnvironmentVariable("PATH", newPathEnvValue, EnvironmentVariableTarget.User);
|
|
||||||
|
|
||||||
// Add path to the current process' envar, so the updater can see the dependencies.
|
|
||||||
Environment.SetEnvironmentVariable("PATH", newPathEnvValue, EnvironmentVariableTarget.Process);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!--Project Settings-->
|
<!--Project Settings-->
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<AnalysisLevel>latest</AnalysisLevel>
|
<AnalysisLevel>latest</AnalysisLevel>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
|
|
||||||
<!--Version-->
|
<!--Version-->
|
||||||
<VersionPrefix>1.0.3.4</VersionPrefix>
|
<VersionPrefix>1.0.4.0</VersionPrefix>
|
||||||
|
|
||||||
<!--Avalonia Settings-->
|
<!--Avalonia Settings-->
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
@ -46,21 +46,23 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.0.10" />
|
<PackageReference Include="Avalonia" Version="11.1.5" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.10" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.1.5" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.10" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.5" />
|
||||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.10" />
|
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.10" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.5" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.10" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.5" />
|
||||||
<PackageReference Include="Toastie.Events" Version="2.2.1" />
|
<PackageReference Include="Toastie.DependencyInjection" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Toastie.Events" Version="3.0.0" />
|
||||||
<PackageReference Include="MessageBox.Avalonia" Version="3.1.5.1" />
|
<PackageReference Include="MessageBox.Avalonia" Version="3.1.5.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||||
<PackageReference Include="SingleFileExtractor.Core" Version="2.2.0" />
|
<PackageReference Include="SingleFileExtractor.Core" Version="2.2.0" />
|
||||||
<PackageReference Include="SkiaImageView.Avalonia11" Version="1.5.0" />
|
<PackageReference Include="SkiaImageView.Avalonia11" Version="1.5.0" />
|
||||||
|
<PackageReference Include="Toastie.Utilities" Version="3.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -10,7 +10,6 @@ using EllieHub.Features.BotConfig.Services.Abstractions;
|
||||||
using EllieHub.Features.BotConfig.Services.Mocks;
|
using EllieHub.Features.BotConfig.Services.Mocks;
|
||||||
using EllieHub.Features.Home.Services;
|
using EllieHub.Features.Home.Services;
|
||||||
using EllieHub.Features.Home.Services.Abstractions;
|
using EllieHub.Features.Home.Services.Abstractions;
|
||||||
using EllieHub.Services;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
@ -48,7 +47,7 @@ public static class IServiceCollectionExt
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serviceCollection">This service collection.</param>
|
/// <param name="serviceCollection">This service collection.</param>
|
||||||
/// <returns>This service collection with the services added.</returns>
|
/// <returns>This service collection with the services added.</returns>
|
||||||
public static IServiceCollection RegisterServices(this IServiceCollection serviceCollection)
|
public static IServiceCollection RegisterAppServices(this IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
// Design-time
|
// Design-time
|
||||||
if (Design.IsDesignMode)
|
if (Design.IsDesignMode)
|
||||||
|
@ -66,18 +65,18 @@ public static class IServiceCollectionExt
|
||||||
|
|
||||||
// Web requests
|
// Web requests
|
||||||
serviceCollection.AddHttpClient();
|
serviceCollection.AddHttpClient();
|
||||||
serviceCollection.AddHttpClient(AppConstants.NoRedirectClient) // Client that doesn't allow automatic reditections
|
serviceCollection.AddHttpClient(AppConstants.NoRedirectClient) // Client that doesn't allow automatic redirections
|
||||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { AllowAutoRedirect = false });
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { AllowAutoRedirect = false });
|
||||||
serviceCollection.AddHttpClient(AppConstants.ToastielabClient)
|
serviceCollection.AddHttpClient(AppConstants.ToastielabClient)
|
||||||
.ConfigureHttpClient(x =>
|
.ConfigureHttpClient(x =>
|
||||||
{
|
{
|
||||||
x.DefaultRequestHeaders.Add("Accept", "application/vnd.toastielab+json");
|
x.DefaultRequestHeaders.Add("Accept", "application/vnd.toastielab+json");
|
||||||
x.DefaultRequestHeaders.Add("X-Toastielab-Api-Version", "2022-11-28");
|
x.DefaultRequestHeaders.Add("X-Toastielab-Api-Version", "2022-11-28");
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
x.DefaultRequestHeaders.UserAgent.TryParseAdd($"EllieHub v{AppStatics.AppVersion}-Debug");
|
x.DefaultRequestHeaders.UserAgent.TryParseAdd($"EllieHub v{AppStatics.AppVersion}-Debug");
|
||||||
#else
|
#else
|
||||||
x.DefaultRequestHeaders.UserAgent.TryParseAdd($"EllieHub v{AppStatics.AppVersion}");
|
x.DefaultRequestHeaders.UserAgent.TryParseAdd($"EllieHub v{AppStatics.AppVersion}");
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
|
|
||||||
// App settings
|
// App settings
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace EllieHub.Extensions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines extension methods for <see cref="IServiceProvider"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static class IServiceProviderExt
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of service object to get.</typeparam>
|
|
||||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
|
|
||||||
/// <param name="arguments"></param>
|
|
||||||
/// <remarks>Do not use abstract types in the type argument!</remarks>
|
|
||||||
/// <returns>A service object of type <typeparamref name="T"/>.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">Occurs when <paramref name="serviceProvider"/> or <paramref name="arguments"/> are <see langword="null"/>.</exception>
|
|
||||||
/// <exception cref="InvalidOperationException">Occurs when there is no concrete service of type <typeparamref name="T"/> or when the arguments are wrong.</exception>
|
|
||||||
public static T GetParameterizedService<T>(this IServiceProvider serviceProvider, params object[] arguments)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(serviceProvider, nameof(serviceProvider));
|
|
||||||
ArgumentNullException.ThrowIfNull(arguments, nameof(arguments));
|
|
||||||
|
|
||||||
var result = ActivatorUtilities.CreateInstance<T>(serviceProvider, arguments);
|
|
||||||
|
|
||||||
return (result is null)
|
|
||||||
? throw new InvalidOperationException($"There is no service of type {nameof(T)} or the arguments were incorrect.")
|
|
||||||
: result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -85,6 +85,6 @@ public static class WindowExt
|
||||||
? throw new InvalidOperationException($"Resource '{resourceName}' was not found.")
|
? throw new InvalidOperationException($"Resource '{resourceName}' was not found.")
|
||||||
: (resource is T castResource)
|
: (resource is T castResource)
|
||||||
? castResource
|
? castResource
|
||||||
: throw new InvalidCastException($"Could not convert resource of type '{resource?.GetType()?.FullName}' to '{nameof(T)}'.");
|
: throw new InvalidCastException($"Could not convert resource of type '{resource?.GetType().FullName}' to '{nameof(T)}'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
using EllieHub.Features.Shared.Services.Abstractions;
|
using EllieHub.Features.Common.Services.Abstractions;
|
||||||
|
|
||||||
namespace EllieHub.Features.AppConfig.Services.Abstractions;
|
namespace EllieHub.Features.AppConfig.Services.Abstractions;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using EllieHub.Features.Shared.Services.Abstractions;
|
using EllieHub.Features.Common.Services.Abstractions;
|
||||||
|
|
||||||
namespace EllieHub.Features.AppConfig.Services.Abstractions;
|
namespace EllieHub.Features.AppConfig.Services.Abstractions;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace EllieHub.Services;
|
namespace EllieHub.Features.AppConfig.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service that checks, downloads, installs, and updates ffmpeg on Linux.
|
/// Service that checks, downloads, installs, and updates ffmpeg on Linux.
|
||||||
|
@ -14,7 +14,7 @@ public sealed partial class FfmpegLinuxResolver : FfmpegResolver
|
||||||
{
|
{
|
||||||
private readonly Regex _ffmpegLatestVersionRegex = FfmpegLatestVersionRegexGenerator();
|
private readonly Regex _ffmpegLatestVersionRegex = FfmpegLatestVersionRegexGenerator();
|
||||||
private readonly string _tempDirectory = Path.GetTempPath();
|
private readonly string _tempDirectory = Path.GetTempPath();
|
||||||
private bool _isUpdating = false;
|
private bool _isUpdating;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -48,7 +48,7 @@ public sealed partial class FfmpegLinuxResolver : FfmpegResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string dependenciesUri, CancellationToken cToken = default)
|
public override async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string installationUri, CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
if (_isUpdating)
|
if (_isUpdating)
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
@ -64,22 +64,22 @@ public sealed partial class FfmpegLinuxResolver : FfmpegResolver
|
||||||
if (currentVersion == newVersion)
|
if (currentVersion == newVersion)
|
||||||
return (currentVersion, null);
|
return (currentVersion, null);
|
||||||
|
|
||||||
Utilities.TryDeleteFile(Path.Combine(dependenciesUri, FileName));
|
Utilities.TryDeleteFile(Path.Combine(installationUri, FileName));
|
||||||
Utilities.TryDeleteFile(Path.Combine(dependenciesUri, "ffprobe"));
|
Utilities.TryDeleteFile(Path.Combine(installationUri, "ffprobe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
Directory.CreateDirectory(dependenciesUri);
|
Directory.CreateDirectory(installationUri);
|
||||||
|
|
||||||
var architecture = (RuntimeInformation.OSArchitecture is Architecture.X64) ? "amd" : "arm";
|
var architecture = (RuntimeInformation.OSArchitecture is Architecture.X64) ? "amd" : "arm";
|
||||||
var tarFileName = $"ffmpeg-release-{architecture}64-static.tar.xz";
|
var tarFileName = $"ffmpeg-release-{architecture}64-static.tar.xz";
|
||||||
var http = _httpClientFactory.CreateClient();
|
var http = _httpClientFactory.CreateClient();
|
||||||
using var downloadStream = await http.GetStreamAsync($"https://johnvansickle.com/ffmpeg/releases/{tarFileName}", cToken);
|
await using var downloadStream = await http.GetStreamAsync($"https://johnvansickle.com/ffmpeg/releases/{tarFileName}", cToken);
|
||||||
|
|
||||||
// Save tar file to the temporary directory.
|
// Save tar file to the temporary directory.
|
||||||
var tarFilePath = Path.Combine(_tempDirectory, tarFileName);
|
var tarFilePath = Path.Combine(_tempDirectory, tarFileName);
|
||||||
var tarExtractDir = Path.Combine(_tempDirectory, $"ffmpeg-{newVersion}-{architecture}64-static");
|
var tarExtractDir = Path.Combine(_tempDirectory, $"ffmpeg-{newVersion}-{architecture}64-static");
|
||||||
using (var fileStream = new FileStream(tarFilePath, FileMode.Create))
|
await using (var fileStream = new FileStream(tarFilePath, FileMode.Create))
|
||||||
await downloadStream.CopyToAsync(fileStream, cToken);
|
await downloadStream.CopyToAsync(fileStream, cToken);
|
||||||
|
|
||||||
// Extract the tar file.
|
// Extract the tar file.
|
||||||
|
@ -87,11 +87,11 @@ public sealed partial class FfmpegLinuxResolver : FfmpegResolver
|
||||||
await extractProcess.WaitForExitAsync(cToken);
|
await extractProcess.WaitForExitAsync(cToken);
|
||||||
|
|
||||||
// Move ffmpeg to the dependencies directory.
|
// Move ffmpeg to the dependencies directory.
|
||||||
File.Move(Path.Combine(tarExtractDir, FileName), Path.Combine(dependenciesUri, FileName), true);
|
File.Move(Path.Combine(tarExtractDir, FileName), Path.Combine(installationUri, FileName), true);
|
||||||
File.Move(Path.Combine(tarExtractDir, "ffprobe"), Path.Combine(dependenciesUri, "ffprobe"), true);
|
File.Move(Path.Combine(tarExtractDir, "ffprobe"), Path.Combine(installationUri, "ffprobe"), true);
|
||||||
|
|
||||||
// Mark the files as executable.
|
// Mark the files as executable.
|
||||||
using var chmod = Utilities.StartProcess("chmod", $"+x \"{Path.Combine(dependenciesUri, FileName)}\" \"{Path.Combine(dependenciesUri, "ffprobe")}\"");
|
using var chmod = Utilities.StartProcess("chmod", $"+x \"{Path.Combine(installationUri, FileName)}\" \"{Path.Combine(installationUri, "ffprobe")}\"");
|
||||||
await chmod.WaitForExitAsync(cToken);
|
await chmod.WaitForExitAsync(cToken);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
@ -99,7 +99,7 @@ public sealed partial class FfmpegLinuxResolver : FfmpegResolver
|
||||||
Directory.Delete(tarExtractDir, true);
|
Directory.Delete(tarExtractDir, true);
|
||||||
|
|
||||||
// Update environment variable
|
// Update environment variable
|
||||||
Utilities.AddPathToPATHEnvar(dependenciesUri);
|
Utilities.AddPathToPathEnvar(installationUri);
|
||||||
|
|
||||||
_isUpdating = false;
|
_isUpdating = false;
|
||||||
return (currentVersion, newVersion);
|
return (currentVersion, newVersion);
|
||||||
|
|
|
@ -38,7 +38,7 @@ public sealed class FfmpegMacResolver : FfmpegResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string dependenciesUri, CancellationToken cToken = default)
|
public override async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string installationUri, CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
if (_isUpdating)
|
if (_isUpdating)
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
@ -57,24 +57,24 @@ public sealed class FfmpegMacResolver : FfmpegResolver
|
||||||
return (currentVersion, null);
|
return (currentVersion, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Utilities.TryDeleteFile(Path.Combine(dependenciesUri, FileName));
|
Utilities.TryDeleteFile(Path.Combine(installationUri, FileName));
|
||||||
Utilities.TryDeleteFile(Path.Combine(dependenciesUri, "ffprobe"));
|
Utilities.TryDeleteFile(Path.Combine(installationUri, "ffprobe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
Directory.CreateDirectory(dependenciesUri);
|
Directory.CreateDirectory(installationUri);
|
||||||
|
|
||||||
var http = _httpClientFactory.CreateClient();
|
var http = _httpClientFactory.CreateClient();
|
||||||
var ffmpegResponse = await http.CallApiAsync<EvermeetInfo>(_apiFfmpegInfoEndpoint, cToken);
|
var ffmpegResponse = await http.CallApiAsync<EvermeetInfo>(_apiFfmpegInfoEndpoint, cToken);
|
||||||
var ffprobeResponse = await http.CallApiAsync<EvermeetInfo>(_apiFfprobeInfoEndpoint, cToken);
|
var ffprobeResponse = await http.CallApiAsync<EvermeetInfo>(_apiFfprobeInfoEndpoint, cToken);
|
||||||
|
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
InstallDependencyAsync(ffmpegResponse, dependenciesUri, cToken),
|
InstallDependencyAsync(ffmpegResponse, installationUri, cToken),
|
||||||
InstallDependencyAsync(ffprobeResponse, dependenciesUri, cToken)
|
InstallDependencyAsync(ffprobeResponse, installationUri, cToken)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update environment variable
|
// Update environment variable
|
||||||
Utilities.AddPathToPATHEnvar(dependenciesUri);
|
Utilities.AddPathToPathEnvar(installationUri);
|
||||||
|
|
||||||
_isUpdating = false;
|
_isUpdating = false;
|
||||||
return (currentVersion, newVersion);
|
return (currentVersion, newVersion);
|
||||||
|
|
|
@ -49,7 +49,7 @@ public sealed class FfmpegWindowsResolver : FfmpegResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string dependenciesUri, CancellationToken cToken = default)
|
public override async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string installationUri, CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
if (_isUpdating)
|
if (_isUpdating)
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
@ -68,13 +68,13 @@ public sealed class FfmpegWindowsResolver : FfmpegResolver
|
||||||
return (currentVersion, null);
|
return (currentVersion, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Utilities.TryDeleteFile(Path.Combine(dependenciesUri, FileName));
|
Utilities.TryDeleteFile(Path.Combine(installationUri, FileName));
|
||||||
Utilities.TryDeleteFile(Path.Combine(dependenciesUri, "ffprobe.exe"));
|
Utilities.TryDeleteFile(Path.Combine(installationUri, "ffprobe.exe"));
|
||||||
//Utilities.TryDeleteFile(Path.Combine(dependenciesUri, "ffplay.exe"));
|
//Utilities.TryDeleteFile(Path.Combine(dependenciesUri, "ffplay.exe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
Directory.CreateDirectory(dependenciesUri);
|
Directory.CreateDirectory(installationUri);
|
||||||
|
|
||||||
var zipFileName = $"ffmpeg-{newVersion}-full_build.zip";
|
var zipFileName = $"ffmpeg-{newVersion}-full_build.zip";
|
||||||
var http = _httpClientFactory.CreateClient();
|
var http = _httpClientFactory.CreateClient();
|
||||||
|
@ -94,8 +94,8 @@ public sealed class FfmpegWindowsResolver : FfmpegResolver
|
||||||
ZipFile.ExtractToDirectory(zipFilePath, _tempDirectory);
|
ZipFile.ExtractToDirectory(zipFilePath, _tempDirectory);
|
||||||
|
|
||||||
// Move ffmpeg to the dependencies directory.
|
// Move ffmpeg to the dependencies directory.
|
||||||
File.Move(Path.Combine(zipExtractDir, "bin", FileName), Path.Combine(dependenciesUri, FileName), true);
|
File.Move(Path.Combine(zipExtractDir, "bin", FileName), Path.Combine(installationUri, FileName), true);
|
||||||
File.Move(Path.Combine(zipExtractDir, "bin", "ffprobe.exe"), Path.Combine(dependenciesUri, "ffprobe.exe"), true);
|
File.Move(Path.Combine(zipExtractDir, "bin", "ffprobe.exe"), Path.Combine(installationUri, "ffprobe.exe"), true);
|
||||||
//File.Move(Path.Combine(zipExtractDir, "bin", "ffplay.exe"), Path.Combine(dependenciesUri, "ffplay.exe"));
|
//File.Move(Path.Combine(zipExtractDir, "bin", "ffplay.exe"), Path.Combine(dependenciesUri, "ffplay.exe"));
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
@ -104,7 +104,7 @@ public sealed class FfmpegWindowsResolver : FfmpegResolver
|
||||||
}, cToken);
|
}, cToken);
|
||||||
|
|
||||||
// Update environment variable
|
// Update environment variable
|
||||||
Utilities.AddPathToPATHEnvar(dependenciesUri);
|
Utilities.AddPathToPathEnvar(installationUri);
|
||||||
|
|
||||||
_isUpdating = false;
|
_isUpdating = false;
|
||||||
return (currentVersion, newVersion);
|
return (currentVersion, newVersion);
|
||||||
|
|
|
@ -65,7 +65,7 @@ public sealed class YtdlpResolver : IYtdlpResolver
|
||||||
|
|
||||||
// Else, add the dependencies directory to the PATH envar,
|
// Else, add the dependencies directory to the PATH envar,
|
||||||
// then try again.
|
// then try again.
|
||||||
Utilities.AddPathToPATHEnvar(AppStatics.AppDepsUri);
|
Utilities.AddPathToPathEnvar(AppStatics.AppDepsUri);
|
||||||
return await GetCurrentVersionAsync(cToken);
|
return await GetCurrentVersionAsync(cToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ public sealed class YtdlpResolver : IYtdlpResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string dependenciesUri, CancellationToken cToken = default)
|
public async ValueTask<(string? OldVersion, string? NewVersion)> InstallOrUpdateAsync(string installationUri, CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
if (_isUpdating)
|
if (_isUpdating)
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
@ -124,16 +124,16 @@ public sealed class YtdlpResolver : IYtdlpResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
Directory.CreateDirectory(dependenciesUri);
|
Directory.CreateDirectory(installationUri);
|
||||||
|
|
||||||
var finalFilePath = Path.Combine(dependenciesUri, FileName);
|
var finalFilePath = Path.Combine(installationUri, FileName);
|
||||||
var http = _httpClientFactory.CreateClient();
|
var http = _httpClientFactory.CreateClient();
|
||||||
using var downloadStream = await http.GetStreamAsync($"https://github.com/yt-dlp/yt-dlp/releases/download/{newVersion}/{_downloadedFileName}", cToken);
|
using var downloadStream = await http.GetStreamAsync($"https://github.com/yt-dlp/yt-dlp/releases/download/{newVersion}/{_downloadedFileName}", cToken);
|
||||||
using (var fileStream = new FileStream(finalFilePath, FileMode.Create))
|
using (var fileStream = new FileStream(finalFilePath, FileMode.Create))
|
||||||
await downloadStream.CopyToAsync(fileStream, cToken);
|
await downloadStream.CopyToAsync(fileStream, cToken);
|
||||||
|
|
||||||
// Update environment variable
|
// Update environment variable
|
||||||
Utilities.AddPathToPATHEnvar(dependenciesUri);
|
Utilities.AddPathToPathEnvar(installationUri);
|
||||||
|
|
||||||
// On Linux and MacOS, we need to mark the file as executable.
|
// On Linux and MacOS, we need to mark the file as executable.
|
||||||
if (Environment.OSVersion.Platform is PlatformID.Unix)
|
if (Environment.OSVersion.Platform is PlatformID.Unix)
|
||||||
|
|
|
@ -8,8 +8,8 @@ using EllieHub.Features.AppConfig.Views.Controls;
|
||||||
using EllieHub.Features.AppConfig.Views.Windows;
|
using EllieHub.Features.AppConfig.Views.Windows;
|
||||||
using EllieHub.Features.AppWindow.ViewModels;
|
using EllieHub.Features.AppWindow.ViewModels;
|
||||||
using EllieHub.Features.AppWindow.Views.Windows;
|
using EllieHub.Features.AppWindow.Views.Windows;
|
||||||
using EllieHub.Features.Shared.Services.Abstractions;
|
using EllieHub.Features.Common.Services.Abstractions;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:EllieHub.Features.AppConfig.ViewModels"
|
xmlns:vm="using:EllieHub.Features.AppConfig.ViewModels"
|
||||||
xmlns:views="using:EllieHub.Features.Shared.Views.Controls"
|
xmlns:views="using:EllieHub.Features.Common.Views.Controls"
|
||||||
xmlns:const="using:EllieHub.Common"
|
xmlns:const="using:EllieHub.Common"
|
||||||
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
|
@ -23,7 +23,6 @@
|
||||||
Fill="{DynamicResource HeavyBackground}" />
|
Fill="{DynamicResource HeavyBackground}" />
|
||||||
|
|
||||||
<Rectangle Grid.Row="1"
|
<Rectangle Grid.Row="1"
|
||||||
Grid.RowSpan="2"
|
|
||||||
Fill="{DynamicResource MediumBackground}"/>
|
Fill="{DynamicResource MediumBackground}"/>
|
||||||
|
|
||||||
<!--Settings Title-->
|
<!--Settings Title-->
|
||||||
|
@ -87,9 +86,10 @@
|
||||||
|
|
||||||
<!--Backup Directory Bar-->
|
<!--Backup Directory Bar-->
|
||||||
<TextBlock Grid.Row="2"
|
<TextBlock Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
Margin="0 8 0 0"
|
Margin="0 8 0 0"
|
||||||
Text="Backup Directory:"
|
Text="Backup Directory:"
|
||||||
ToolTip.Tip="Defines the directory the updater is going to store the backup of the bot instances."/>
|
ToolTip.Tip="Defines the directory the updater is going to store the backups of the bot instances."/>
|
||||||
|
|
||||||
<TextBox Grid.Row="3"
|
<TextBox Grid.Row="3"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
|
@ -113,6 +113,7 @@
|
||||||
|
|
||||||
<!--Logs directory Bar-->
|
<!--Logs directory Bar-->
|
||||||
<TextBlock Grid.Row="4"
|
<TextBlock Grid.Row="4"
|
||||||
|
Grid.Column="0"
|
||||||
Margin="0 8 0 0"
|
Margin="0 8 0 0"
|
||||||
Text="Logs Directory:"
|
Text="Logs Directory:"
|
||||||
ToolTip.Tip="Defines the directory the updater is going to store the logs of the bot instances."/>
|
ToolTip.Tip="Defines the directory the updater is going to store the logs of the bot instances."/>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:EllieHub.Features.AppConfig.ViewModels"
|
xmlns:vm="using:EllieHub.Features.AppConfig.ViewModels"
|
||||||
xmlns:const="using:EllieHub.Common"
|
|
||||||
xmlns:dd="using:EllieHub.Avalonia.DesignData.Windows"
|
xmlns:dd="using:EllieHub.Avalonia.DesignData.Windows"
|
||||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="305"
|
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="305"
|
||||||
Width="400" Height="305"
|
Width="400" Height="305"
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
xmlns:siv="https://github.com/kekyo/SkiaImageView"
|
xmlns:siv="https://github.com/kekyo/SkiaImageView"
|
||||||
xmlns:vm="using:EllieHub.Features.AppWindow.ViewModels"
|
xmlns:vm="using:EllieHub.Features.AppWindow.ViewModels"
|
||||||
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
||||||
xmlns:const="using:EllieHub.Common"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="70" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="70" d:DesignHeight="450"
|
||||||
x:Class="EllieHub.Features.AppWindow.Views.Controls.LateralBarView"
|
x:Class="EllieHub.Features.AppWindow.Views.Controls.LateralBarView"
|
||||||
x:DataType="vm:LateralBarViewModel">
|
x:DataType="vm:LateralBarViewModel">
|
||||||
|
|
|
@ -20,8 +20,6 @@ namespace EllieHub.Features.AppWindow.Views.Controls;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class LateralBarView : ReactiveUserControl<LateralBarViewModel>
|
public partial class LateralBarView : ReactiveUserControl<LateralBarViewModel>
|
||||||
{
|
{
|
||||||
private static readonly Cursor _pointingHandCursor = new(StandardCursorType.Hand);
|
|
||||||
private static readonly Cursor _arrow = new(StandardCursorType.Arrow);
|
|
||||||
private readonly ReadOnlyAppSettings _appConfig;
|
private readonly ReadOnlyAppSettings _appConfig;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
|
using Toastie.DependencyInjection.Extensions;
|
||||||
|
using Toastie.Utilities;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using EllieHub.Avalonia.DesignData.Common;
|
using EllieHub.Avalonia.DesignData.Common;
|
||||||
using EllieHub.Enums;
|
using EllieHub.Enums;
|
||||||
|
@ -15,7 +17,7 @@ using EllieHub.Features.BotConfig.ViewModels;
|
||||||
using EllieHub.Features.Home.Services.Abstractions;
|
using EllieHub.Features.Home.Services.Abstractions;
|
||||||
using EllieHub.Features.Home.ViewModels;
|
using EllieHub.Features.Home.ViewModels;
|
||||||
using EllieHub.Features.Home.Views.Windows;
|
using EllieHub.Features.Home.Views.Windows;
|
||||||
using EllieHub.Services;
|
using EllieHub.Features.BotConfig.Services;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
@ -117,7 +119,7 @@ public partial class AppView : ReactiveWindow<AppViewModel>
|
||||||
{
|
{
|
||||||
// Ensure that bots on Unix system have access to the dependencies.
|
// Ensure that bots on Unix system have access to the dependencies.
|
||||||
if (Environment.OSVersion.Platform is PlatformID.Unix)
|
if (Environment.OSVersion.Platform is PlatformID.Unix)
|
||||||
Utilities.AddPathToPATHEnvar(AppStatics.AppDepsUri);
|
ToastieUtilities.AddPathToPATHEnvar(AppStatics.AppDepsUri);
|
||||||
|
|
||||||
// Set the window size from the last session
|
// Set the window size from the last session
|
||||||
base.Height = _appConfigManager.AppConfig.WindowSize.Height;
|
base.Height = _appConfigManager.AppConfig.WindowSize.Height;
|
||||||
|
@ -156,11 +158,11 @@ public partial class AppView : ReactiveWindow<AppViewModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override async void OnClosed(EventArgs eventArgs)
|
protected override void OnClosed(EventArgs eventArgs)
|
||||||
{
|
{
|
||||||
// When the updater is closed, kill all bots and write their logs.
|
// When the updater is closed, kill all bots and write their logs.
|
||||||
_botOrchestrator.StopAll();
|
_botOrchestrator.StopAllBots();
|
||||||
await _logWriter.FlushAllAsync(true);
|
_logWriter.FlushAllAsync(true).Wait();
|
||||||
|
|
||||||
base.OnClosed(eventArgs);
|
base.OnClosed(eventArgs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,18 +34,18 @@ public interface IBotOrchestrator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="botId">The bot's Id.</param>
|
/// <param name="botId">The bot's Id.</param>
|
||||||
/// <returns><see langword="true"/> if the bot successfully started, <see langword="false"/> otherwise.</returns>
|
/// <returns><see langword="true"/> if the bot successfully started, <see langword="false"/> otherwise.</returns>
|
||||||
bool Start(Guid botId);
|
bool StartBot(Guid botId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops the bot with the specified <paramref name="botId"/>.
|
/// Stops the bot with the specified <paramref name="botId"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="botId">The bot's Id.</param>
|
/// <param name="botId">The bot's Id.</param>
|
||||||
/// <returns><see langword="true"/> if the bot successfully stopped, <see langword="false"/> otherwise.</returns>
|
/// <returns><see langword="true"/> if the bot successfully stopped, <see langword="false"/> otherwise.</returns>
|
||||||
bool Stop(Guid botId);
|
bool StopBot(Guid botId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops all bot instances.
|
/// Stops all bot instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><see langword="true"/> if at least one bot instance was stopped, <see langword="false"/> otherwise.</returns>
|
/// <returns><see langword="true"/> if at least one bot instance was stopped, <see langword="false"/> otherwise.</returns>
|
||||||
bool StopAll();
|
bool StopAllBots();
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
using EllieHub.Features.Shared.Services.Abstractions;
|
using EllieHub.Features.Common.Services.Abstractions;
|
||||||
|
|
||||||
namespace EllieHub.Features.BotConfig.Services.Abstractions;
|
namespace EllieHub.Features.BotConfig.Services.Abstractions;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
=> _runningBots.ContainsKey(botId);
|
=> _runningBots.ContainsKey(botId);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Start(Guid botId)
|
public bool StartBot(Guid botId)
|
||||||
{
|
{
|
||||||
if (_runningBots.ContainsKey(botId)
|
if (_runningBots.ContainsKey(botId)
|
||||||
|| !_appConfig.BotEntries.TryGetValue(botId, out var botEntry)
|
|| !_appConfig.BotEntries.TryGetValue(botId, out var botEntry)
|
||||||
|
@ -66,7 +66,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Stop(Guid botId)
|
public bool StopBot(Guid botId)
|
||||||
{
|
{
|
||||||
if (!_runningBots.TryGetValue(botId, out var botProcess))
|
if (!_runningBots.TryGetValue(botId, out var botProcess))
|
||||||
return false;
|
return false;
|
||||||
|
@ -76,7 +76,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool StopAll()
|
public bool StopAllBots()
|
||||||
{
|
{
|
||||||
var amount = _runningBots.Count;
|
var amount = _runningBots.Count;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Toastie.Utilities;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using EllieHub.Features.AppConfig.Services.Abstractions;
|
using EllieHub.Features.AppConfig.Services.Abstractions;
|
||||||
using EllieHub.Features.BotConfig.Models.Api.Toastielab;
|
using EllieHub.Features.BotConfig.Models.Api.Toastielab;
|
||||||
|
@ -10,7 +11,7 @@ using System.Runtime.InteropServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace EllieHub.Services;
|
namespace EllieHub.Features.BotConfig.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service that checks, downloads, installs, and updates a EllieBot instance.
|
/// Service that checks, downloads, installs, and updates a EllieBot instance.
|
||||||
|
@ -23,7 +24,7 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
private const string _toastielabReleasesRepoUrl = "https://toastielab.dev/EllieBotDevs/elliebot/releases/latest";
|
private const string _toastielabReleasesRepoUrl = "https://toastielab.dev/EllieBotDevs/elliebot/releases/latest";
|
||||||
private static readonly HashSet<Guid> _updateIdOngoing = [];
|
private static readonly HashSet<Guid> _updateIdOngoing = [];
|
||||||
private static readonly string _tempDirectory = Path.GetTempPath();
|
private static readonly string _tempDirectory = Path.GetTempPath();
|
||||||
private static readonly Regex _unzipedDirRegex = GenerateUnzipedDirRegex();
|
private static readonly Regex _unzippedDirRegex = GenerateUnzipedDirRegex();
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
private readonly IAppConfigManager _appConfigManager;
|
private readonly IAppConfigManager _appConfigManager;
|
||||||
|
@ -94,7 +95,7 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
var now = DateTimeOffset.Now;
|
var now = DateTimeOffset.Now;
|
||||||
var date = new DateOnly(now.Year, now.Month, now.Day).ToShortDateString().Replace('/', '-');
|
var date = new DateOnly(now.Year, now.Month, now.Day).ToShortDateString().Replace('/', '-');
|
||||||
var backupZipName = $"{botInstance.Name}_v{botInstance.Version}_{date}-{now.ToUnixTimeMilliseconds()}.zip";
|
var backupZipName = $"{botInstance.Name}_v{botInstance.Version}_{date}-{now.ToUnixTimeMilliseconds()}.zip";
|
||||||
var destinationUri = Path.Combine(_appConfigManager.AppConfig.BotsBackupDirectoryUri, backupZipName);
|
var destinationUri = Path.Join(_appConfigManager.AppConfig.BotsBackupDirectoryUri, backupZipName);
|
||||||
|
|
||||||
// ZipFile does not provide asynchronous implementations, so we have to schedule its
|
// ZipFile does not provide asynchronous implementations, so we have to schedule its
|
||||||
// execution to be run in the thread-pool due to how long it takes to finish execution.
|
// execution to be run in the thread-pool due to how long it takes to finish execution.
|
||||||
|
@ -107,7 +108,7 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
public async ValueTask<string?> GetCurrentVersionAsync(CancellationToken cToken = default)
|
public async ValueTask<string?> GetCurrentVersionAsync(CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
var botEntry = _appConfigManager.AppConfig.BotEntries[Id];
|
var botEntry = _appConfigManager.AppConfig.BotEntries[Id];
|
||||||
var executableUri = Path.Combine(botEntry.InstanceDirectoryUri, FileName);
|
var executableUri = Path.Join(botEntry.InstanceDirectoryUri, FileName);
|
||||||
|
|
||||||
if (!File.Exists(executableUri))
|
if (!File.Exists(executableUri))
|
||||||
{
|
{
|
||||||
|
@ -161,8 +162,8 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
|
|
||||||
var http = _httpClientFactory.CreateClient();
|
var http = _httpClientFactory.CreateClient();
|
||||||
var downloadFileName = GetDownloadFileName(latestVersion);
|
var downloadFileName = GetDownloadFileName(latestVersion);
|
||||||
var botTempLocation = Path.Combine(_tempDirectory, "elliebot-" + _unzipedDirRegex.Match(downloadFileName).Groups[1].Value);
|
var botTempLocation = Path.Join(_tempDirectory, "elliebot-" + _unzippedDirRegex.Match(downloadFileName).Groups[1].Value);
|
||||||
var zipTempLocation = Path.Combine(_tempDirectory, downloadFileName);
|
var zipTempLocation = Path.Join(_tempDirectory, downloadFileName);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -185,10 +186,10 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
await _appConfigManager.UpdateBotEntryAsync(Id, x => x with { Version = latestVersion }, cToken);
|
await _appConfigManager.UpdateBotEntryAsync(Id, x => x with { Version = latestVersion }, cToken);
|
||||||
|
|
||||||
// Create creds.yml
|
// Create creds.yml
|
||||||
var credsUri = Path.Combine(installationUri, "creds.yml");
|
var credsUri = Path.Join(installationUri, "creds.yml");
|
||||||
|
|
||||||
if (!File.Exists(credsUri))
|
if (!File.Exists(credsUri))
|
||||||
File.Copy(Path.Combine(installationUri, "creds_example.yml"), credsUri);
|
File.Copy(Path.Join(installationUri, "creds_example.yml"), credsUri);
|
||||||
|
|
||||||
return (currentVersion, latestVersion);
|
return (currentVersion, latestVersion);
|
||||||
}
|
}
|
||||||
|
@ -197,8 +198,8 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
_updateIdOngoing.Remove(Id);
|
_updateIdOngoing.Remove(Id);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Utilities.TryDeleteFile(zipTempLocation);
|
ToastieUtilities.TryDeleteFile(zipTempLocation);
|
||||||
Utilities.TryDeleteDirectory(botTempLocation);
|
ToastieUtilities.TryDeleteDirectory(botTempLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,12 +215,10 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
// Extract the tar ball
|
// Extract the tar ball
|
||||||
await TarFile.ExtractToDirectoryAsync(downloadStream, _tempDirectory, true, cToken);
|
await TarFile.ExtractToDirectoryAsync(downloadStream, _tempDirectory, true, cToken);
|
||||||
|
|
||||||
// Move the bot root directory with "mv" to circumvent this issue on Unix systems: https://github.com/dotnet/runtime/issues/31149
|
ToastieUtilities.TryMoveDirectory(botTempLocation, installationUri);
|
||||||
using var moveProcess = Utilities.StartProcess("mv", $"\"{botTempLocation}\" \"{installationUri}\"");
|
|
||||||
await moveProcess.WaitForExitAsync(cToken);
|
|
||||||
|
|
||||||
// Set executable permission
|
// Set executable permission
|
||||||
using var chmod = Utilities.StartProcess("chmod", $"+x \"{Path.Combine(installationUri, FileName)}\"");
|
using var chmod = ToastieUtilities.StartProcess("chmod", ["+x", Path.Join(installationUri, FileName)]);
|
||||||
await chmod.WaitForExitAsync(cToken);
|
await chmod.WaitForExitAsync(cToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,10 +230,10 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
/// <param name="zipTempLocation">The absolute path to the zip file the bot is initially on.</param>
|
/// <param name="zipTempLocation">The absolute path to the zip file the bot is initially on.</param>
|
||||||
/// <param name="botTempLocation">The absolute path to the temporary directory the bot is extracted to.</param>
|
/// <param name="botTempLocation">The absolute path to the temporary directory the bot is extracted to.</param>
|
||||||
/// <param name="cToken">The cancellation token.</param>
|
/// <param name="cToken">The cancellation token.</param>
|
||||||
private async static ValueTask InstallToWindowsAsync(Stream downloadStream, string installationUri, string zipTempLocation, string botTempLocation, CancellationToken cToken = default)
|
private static async ValueTask InstallToWindowsAsync(Stream downloadStream, string installationUri, string zipTempLocation, string botTempLocation, CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
// Save the zip file
|
// Save the zip file
|
||||||
using (var fileStream = new FileStream(zipTempLocation, FileMode.Create))
|
await using (var fileStream = new FileStream(zipTempLocation, FileMode.Create))
|
||||||
await downloadStream.CopyToAsync(fileStream, cToken);
|
await downloadStream.CopyToAsync(fileStream, cToken);
|
||||||
|
|
||||||
// Extract the zip file
|
// Extract the zip file
|
||||||
|
@ -250,7 +249,7 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
/// <param name="installationUri">The absolute path to the directory the bot got installed to.</param>
|
/// <param name="installationUri">The absolute path to the directory the bot got installed to.</param>
|
||||||
/// <param name="backupFileUri">The absolute path to the backup zip file.</param>
|
/// <param name="backupFileUri">The absolute path to the backup zip file.</param>
|
||||||
/// <param name="cToken">The cancellation token.</param>
|
/// <param name="cToken">The cancellation token.</param>
|
||||||
private async static ValueTask ReaplyBotSettingsAsync(string installationUri, string backupFileUri, CancellationToken cToken = default)
|
private static async ValueTask ReaplyBotSettingsAsync(string installationUri, string backupFileUri, CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
using var zipFile = ZipFile.OpenRead(backupFileUri);
|
using var zipFile = ZipFile.OpenRead(backupFileUri);
|
||||||
var zippedFiles = zipFile.Entries
|
var zippedFiles = zipFile.Entries
|
||||||
|
@ -265,7 +264,7 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
.Prepend(Directory.GetParent(installationUri)?.FullName ?? string.Empty)
|
.Prepend(Directory.GetParent(installationUri)?.FullName ?? string.Empty)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
await RestoreFileAsync(zippedFile, Path.Combine(fileDestinationPath), cToken);
|
await RestoreFileAsync(zippedFile, Path.Join(fileDestinationPath), cToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,10 +274,10 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
/// <param name="zippedFile">The file to be extracted.</param>
|
/// <param name="zippedFile">The file to be extracted.</param>
|
||||||
/// <param name="destinationPath">The final location of the extracted file.</param>
|
/// <param name="destinationPath">The final location of the extracted file.</param>
|
||||||
/// <param name="cToken">The cancellation token.</param>
|
/// <param name="cToken">The cancellation token.</param>
|
||||||
private async static ValueTask RestoreFileAsync(ZipArchiveEntry zippedFile, string destinationPath, CancellationToken cToken = default)
|
private static async ValueTask RestoreFileAsync(ZipArchiveEntry zippedFile, string destinationPath, CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
using var zipStream = zippedFile.Open();
|
await using var zipStream = zippedFile.Open();
|
||||||
using var fileStream = new FileStream(destinationPath, FileMode.Create);
|
await using var fileStream = new FileStream(destinationPath, FileMode.Create);
|
||||||
|
|
||||||
await zipStream.CopyToAsync(fileStream, cToken);
|
await zipStream.CopyToAsync(fileStream, cToken);
|
||||||
}
|
}
|
||||||
|
@ -414,7 +413,7 @@ public sealed partial class EllieResolver : IBotResolver
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (isSingleFile)
|
if (isSingleFile)
|
||||||
Utilities.TryDeleteDirectory(directoryUri);
|
ToastieUtilities.TryDeleteDirectory(directoryUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Toastie.Utilities;
|
||||||
using MsBox.Avalonia.Enums;
|
using MsBox.Avalonia.Enums;
|
||||||
using EllieHub.Enums;
|
using EllieHub.Enums;
|
||||||
using EllieHub.Features.Abstractions;
|
using EllieHub.Features.Abstractions;
|
||||||
using EllieHub.Features.AppConfig.Services.Abstractions;
|
using EllieHub.Features.AppConfig.Services.Abstractions;
|
||||||
using EllieHub.Features.AppWindow.ViewModels;
|
using EllieHub.Features.AppWindow.ViewModels;
|
||||||
using EllieHub.Features.AppWindow.Views.Controls;
|
|
||||||
using EllieHub.Features.AppWindow.Views.Windows;
|
using EllieHub.Features.AppWindow.Views.Windows;
|
||||||
using EllieHub.Features.BotConfig.Models;
|
using EllieHub.Features.BotConfig.Models;
|
||||||
using EllieHub.Features.BotConfig.Services.Abstractions;
|
using EllieHub.Features.BotConfig.Services.Abstractions;
|
||||||
using EllieHub.Features.BotConfig.Views.Controls;
|
using EllieHub.Features.BotConfig.Views.Controls;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
@ -216,14 +216,12 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
Directory.Delete(BotDirectoryUriBar.CurrentUri);
|
Directory.Delete(BotDirectoryUriBar.CurrentUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Environment.OSVersion.Platform is not PlatformID.Unix)
|
if (!ToastieUtilities.TryMoveDirectory(oldUri, BotDirectoryUriBar.CurrentUri))
|
||||||
Directory.Move(oldUri, BotDirectoryUriBar.CurrentUri);
|
throw new InvalidOperationException(
|
||||||
else
|
$"Could not move \"{oldUri}\" to \"{BotDirectoryUriBar.CurrentUri}\"." +
|
||||||
{
|
Environment.NewLine + Environment.NewLine +
|
||||||
// Move the bot root directory with "mv" to circumvent this issue on Unix systems: https://github.com/dotnet/runtime/issues/31149
|
"Make sure you have permission to write to the target directory."
|
||||||
using var moveProcess = Utilities.StartProcess("mv", $"\"{oldUri}\" \"{BotDirectoryUriBar.CurrentUri}\"");
|
);
|
||||||
await moveProcess.WaitForExitAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
BotDirectoryUriBar.RecheckCurrentUri();
|
BotDirectoryUriBar.RecheckCurrentUri();
|
||||||
}
|
}
|
||||||
|
@ -330,7 +328,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
ButtonDefinitions = ButtonEnum.OkCancel,
|
ButtonDefinitions = ButtonEnum.OkCancel,
|
||||||
ContentTitle = "Are you sure?",
|
ContentTitle = "Are you sure?",
|
||||||
ContentMessage = $"Are you sure you want to delete {ActualBotName}?{Environment.NewLine}This action cannot be reversed.",
|
ContentMessage = $"Are you sure you want to delete {ActualBotName}?{Environment.NewLine}This action cannot be reversed.",
|
||||||
MaxWidth = int.Parse(WindowConstants.DefaultWindowWidth) / 2.0,
|
MaxWidth = WindowConstants.DefaultWindowWidth / 2.0,
|
||||||
SizeToContent = SizeToContent.WidthAndHeight,
|
SizeToContent = SizeToContent.WidthAndHeight,
|
||||||
ShowInCenter = true,
|
ShowInCenter = true,
|
||||||
WindowIcon = _mainWindow.GetResource<WindowIcon>(AppResources.EllieHubIcon),
|
WindowIcon = _mainWindow.GetResource<WindowIcon>(AppResources.EllieHubIcon),
|
||||||
|
@ -343,7 +341,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
EnableButtons(true, false);
|
EnableButtons(true, false);
|
||||||
|
|
||||||
// Stop the bot instance
|
// Stop the bot instance
|
||||||
_botOrchestrator.Stop(Resolver.Id);
|
_botOrchestrator.StopBot(Resolver.Id);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
FakeConsole.Content = string.Empty;
|
FakeConsole.Content = string.Empty;
|
||||||
|
@ -410,7 +408,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartBot()
|
public void StartBot()
|
||||||
{
|
{
|
||||||
IsBotRunning = _botOrchestrator.Start(Id);
|
IsBotRunning = _botOrchestrator.StartBot(Id);
|
||||||
|
|
||||||
if (IsBotRunning)
|
if (IsBotRunning)
|
||||||
EnableButtons(true, false);
|
EnableButtons(true, false);
|
||||||
|
@ -420,7 +418,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
/// Stops the bot instance associated with this view-model.
|
/// Stops the bot instance associated with this view-model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopBot()
|
public void StopBot()
|
||||||
=> _botOrchestrator.Stop(Id);
|
=> _botOrchestrator.StopBot(Id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the bot update bar.
|
/// Loads the bot update bar.
|
||||||
|
@ -508,7 +506,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
BotAvatar?.Dispose();
|
BotAvatar.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
using EllieHub.Features.Abstractions;
|
using EllieHub.Features.Abstractions;
|
||||||
using EllieHub.Features.AppConfig.Views.Controls;
|
using EllieHub.Features.BotConfig.Views.Controls;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace EllieHub.Features.BotConfig.ViewModels;
|
namespace EllieHub.Features.BotConfig.ViewModels;
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
Fill="{DynamicResource HeavyBackground}"/>
|
Fill="{DynamicResource HeavyBackground}"/>
|
||||||
|
|
||||||
<Rectangle Grid.Row="1"
|
<Rectangle Grid.Row="1"
|
||||||
Grid.RowSpan="3"
|
Grid.RowSpan="2"
|
||||||
Fill="{DynamicResource MediumBackground}"/>
|
Fill="{DynamicResource MediumBackground}"/>
|
||||||
|
|
||||||
<!--Bot Settings Title-->
|
<!--Bot Settings Title-->
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
xmlns:vm="using:EllieHub.Features.BotConfig.ViewModels"
|
xmlns:vm="using:EllieHub.Features.BotConfig.ViewModels"
|
||||||
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="250"
|
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="250"
|
||||||
x:Class="EllieHub.Features.AppConfig.Views.Controls.FakeConsole"
|
x:Class="EllieHub.Features.BotConfig.Views.Controls.FakeConsole"
|
||||||
x:DataType="vm:FakeConsoleViewModel">
|
x:DataType="vm:FakeConsoleViewModel">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<dd:DesignFakeConsoleViewModel/>
|
<dd:DesignFakeConsoleViewModel/>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using EllieHub.Features.BotConfig.ViewModels;
|
using EllieHub.Features.BotConfig.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Features.AppConfig.Views.Controls;
|
namespace EllieHub.Features.BotConfig.Views.Controls;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a control that mimics the appearance of a terminal emulator.
|
/// Represents a control that mimics the appearance of a terminal emulator.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Features.Shared.Models;
|
namespace EllieHub.Features.Common.Models;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the event arguments for when a valid uri is set to a <see cref="UriInputBarViewModel"/>.
|
/// Defines the event arguments for when a valid uri is set to a <see cref="UriInputBarViewModel"/>.
|
|
@ -1,4 +1,4 @@
|
||||||
namespace EllieHub.Features.Shared.Services.Abstractions;
|
namespace EllieHub.Features.Common.Services.Abstractions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a service that checks, downloads, installs, and updates a dependency.
|
/// Represents a service that checks, downloads, installs, and updates a dependency.
|
|
@ -2,11 +2,11 @@ using Avalonia.Media.Immutable;
|
||||||
using EllieHub.Enums;
|
using EllieHub.Enums;
|
||||||
using EllieHub.Features.Abstractions;
|
using EllieHub.Features.Abstractions;
|
||||||
using EllieHub.Features.AppWindow.Views.Windows;
|
using EllieHub.Features.AppWindow.Views.Windows;
|
||||||
using EllieHub.Features.Shared.Views.Controls;
|
using EllieHub.Features.Common.Views.Controls;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace EllieHub.Features.Shared.ViewModels;
|
namespace EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the view-model for a button that installs a dependency for Ellie.
|
/// Defines the view-model for a button that installs a dependency for Ellie.
|
|
@ -1,11 +1,11 @@
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using EllieHub.Features.Abstractions;
|
using EllieHub.Features.Abstractions;
|
||||||
using EllieHub.Features.Shared.Models;
|
using EllieHub.Features.Common.Models;
|
||||||
using EllieHub.Features.Shared.Views.Controls;
|
using EllieHub.Features.Common.Views.Controls;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace EllieHub.Features.Shared.ViewModels;
|
namespace EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a text box for inputting the absolute path of a directory.
|
/// Represents a text box for inputting the absolute path of a directory.
|
|
@ -2,10 +2,10 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:EllieHub.Features.Shared.ViewModels"
|
xmlns:vm="using:EllieHub.Features.Common.ViewModels"
|
||||||
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="50"
|
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="50"
|
||||||
x:Class="EllieHub.Features.Shared.Views.Controls.DependencyButton"
|
x:Class="EllieHub.Features.Common.Views.Controls.DependencyButton"
|
||||||
x:DataType="vm:DependencyButtonViewModel">
|
x:DataType="vm:DependencyButtonViewModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
|
@ -1,7 +1,7 @@
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Features.Shared.Views.Controls;
|
namespace EllieHub.Features.Common.Views.Controls;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a button that installs a dependency for Ellie.
|
/// Represents a button that installs a dependency for Ellie.
|
|
@ -2,10 +2,10 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:EllieHub.Features.Shared.ViewModels"
|
xmlns:vm="using:EllieHub.Features.Common.ViewModels"
|
||||||
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
xmlns:dd="using:EllieHub.Avalonia.DesignData.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="33"
|
mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="33"
|
||||||
x:Class="EllieHub.Features.Shared.Views.Controls.UriInputBar"
|
x:Class="EllieHub.Features.Common.Views.Controls.UriInputBar"
|
||||||
x:DataType="vm:UriInputBarViewModel">
|
x:DataType="vm:UriInputBarViewModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
|
@ -1,7 +1,7 @@
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using EllieHub.Features.Shared.ViewModels;
|
using EllieHub.Features.Common.ViewModels;
|
||||||
|
|
||||||
namespace EllieHub.Features.Shared.Views.Controls;
|
namespace EllieHub.Features.Common.Views.Controls;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a text box that receives the absolute path to a directory.
|
/// Represents a text box that receives the absolute path to a directory.
|
|
@ -1,4 +1,4 @@
|
||||||
using EllieHub.Features.Shared.Services.Abstractions;
|
using EllieHub.Features.Common.Services.Abstractions;
|
||||||
|
|
||||||
namespace EllieHub.Features.Home.Services.Abstractions;
|
namespace EllieHub.Features.Home.Services.Abstractions;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Toastie.Utilities;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using EllieHub.Features.Home.Models.Api.Toastielab;
|
using EllieHub.Features.Home.Models.Api.Toastielab;
|
||||||
using EllieHub.Features.Home.Services.Abstractions;
|
using EllieHub.Features.Home.Services.Abstractions;
|
||||||
|
@ -51,7 +52,7 @@ public sealed class AppResolver : IAppResolver
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void LaunchNewVersion()
|
public void LaunchNewVersion()
|
||||||
=> Utilities.StartProcess(BinaryUri);
|
=> ToastieUtilities.StartProcess(BinaryUri);
|
||||||
|
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// <see langword="true"/> if the updater can be updated,
|
/// <see langword="true"/> if the updater can be updated,
|
||||||
|
@ -61,7 +62,7 @@ public sealed class AppResolver : IAppResolver
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool?> CanUpdateAsync(CancellationToken cToken = default)
|
public async ValueTask<bool?> CanUpdateAsync(CancellationToken cToken = default)
|
||||||
{
|
{
|
||||||
if (!Utilities.CanWriteTo(AppContext.BaseDirectory))
|
if (!ToastieUtilities.HasWritePermissionAt(AppContext.BaseDirectory))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var currentVersion = await GetCurrentVersionAsync(cToken);
|
var currentVersion = await GetCurrentVersionAsync(cToken);
|
||||||
|
@ -88,7 +89,7 @@ public sealed class AppResolver : IAppResolver
|
||||||
var result = false;
|
var result = false;
|
||||||
|
|
||||||
foreach (var file in Directory.GetFiles(AppContext.BaseDirectory).Where(x => x.EndsWith(OldFileSuffix, StringComparison.Ordinal)))
|
foreach (var file in Directory.GetFiles(AppContext.BaseDirectory).Where(x => x.EndsWith(OldFileSuffix, StringComparison.Ordinal)))
|
||||||
result |= Utilities.TryDeleteFile(file);
|
result |= ToastieUtilities.TryDeleteFile(file);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -144,21 +145,13 @@ public sealed class AppResolver : IAppResolver
|
||||||
if (File.Exists(destinationUri))
|
if (File.Exists(destinationUri))
|
||||||
File.Move(destinationUri, destinationUri + OldFileSuffix, true);
|
File.Move(destinationUri, destinationUri + OldFileSuffix, true);
|
||||||
|
|
||||||
// Move the new file to the application's directory.
|
ToastieUtilities.TryMoveFile(newFileUri, destinationUri, true);
|
||||||
if (Environment.OSVersion.Platform is not PlatformID.Unix)
|
|
||||||
File.Move(newFileUri, destinationUri, true);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Circumvent this issue on Unix systems: https://github.com/dotnet/runtime/issues/31149
|
|
||||||
using var moveProcess = Utilities.StartProcess("mv", $"\"{newFileUri}\" \"{destinationUri}\"");
|
|
||||||
await moveProcess.WaitForExitAsync(cToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the new binary file as executable.
|
// Mark the new binary file as executable.
|
||||||
if (Environment.OSVersion.Platform is PlatformID.Unix)
|
if (Environment.OSVersion.Platform is PlatformID.Unix)
|
||||||
{
|
{
|
||||||
using var chmod = Utilities.StartProcess("chmod", $"+x \"{BinaryUri}\"");
|
using var chmod = ToastieUtilities.StartProcess("chmod", $"+x \"{BinaryUri}\"");
|
||||||
await chmod.WaitForExitAsync(cToken);
|
await chmod.WaitForExitAsync(cToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,8 +160,8 @@ public sealed class AppResolver : IAppResolver
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Utilities.TryDeleteFile(zipTempLocation);
|
ToastieUtilities.TryDeleteFile(zipTempLocation);
|
||||||
Utilities.TryDeleteDirectory(appTempLocation);
|
ToastieUtilities.TryDeleteDirectory(appTempLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,15 +28,15 @@
|
||||||
Grid.ColumnSpan="5"
|
Grid.ColumnSpan="5"
|
||||||
Fill="{DynamicResource HeavyBackground}"/>
|
Fill="{DynamicResource HeavyBackground}"/>
|
||||||
|
|
||||||
<Rectangle Grid.Row="5"
|
<Rectangle Grid.Row="4"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Fill="#008DDA"/>
|
Fill="#008DDA"/>
|
||||||
|
|
||||||
<Rectangle Grid.Row="5"
|
<Rectangle Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Fill="#F86752"/>
|
Fill="#F86752"/>
|
||||||
|
|
||||||
<Rectangle Grid.Row="5"
|
<Rectangle Grid.Row="4"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Fill="#0e3f88"/>
|
Fill="#0e3f88"/>
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
VerticalAlignment="Top"/>
|
VerticalAlignment="Top"/>
|
||||||
|
|
||||||
<!--Bottom Buttons-->
|
<!--Bottom Buttons-->
|
||||||
<Button Grid.Row="5"
|
<Button Grid.Row="4"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Classes="transparent"
|
Classes="transparent"
|
||||||
Content="Buy me a Ko-fi"
|
Content="Buy me a Ko-fi"
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
CommandParameter="https://ko-fi.com/toastie_t0ast"
|
CommandParameter="https://ko-fi.com/toastie_t0ast"
|
||||||
Command="{Binding OpenUrl}"/>
|
Command="{Binding OpenUrl}"/>
|
||||||
|
|
||||||
<Image Grid.Row="5"
|
<Image Grid.Row="4"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Classes="icon"
|
Classes="icon"
|
||||||
MaxHeight="30"
|
MaxHeight="30"
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
Margin="0 0 3 0"
|
Margin="0 0 3 0"
|
||||||
Source="{DynamicResource KofiIcon}"/>
|
Source="{DynamicResource KofiIcon}"/>
|
||||||
|
|
||||||
<Button Grid.Row="5"
|
<Button Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Classes="transparent"
|
Classes="transparent"
|
||||||
Content="Support EllieBot"
|
Content="Support EllieBot"
|
||||||
|
@ -178,7 +178,7 @@
|
||||||
CommandParameter="https://patreon.com/toastiet0ast"
|
CommandParameter="https://patreon.com/toastiet0ast"
|
||||||
Command="{Binding OpenUrl}"/>
|
Command="{Binding OpenUrl}"/>
|
||||||
|
|
||||||
<Image Grid.Row="5"
|
<Image Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Classes="icon"
|
Classes="icon"
|
||||||
MaxHeight="30"
|
MaxHeight="30"
|
||||||
|
@ -187,7 +187,7 @@
|
||||||
Margin="0 0 3 0"
|
Margin="0 0 3 0"
|
||||||
Source="{DynamicResource PatreonIcon}"/>
|
Source="{DynamicResource PatreonIcon}"/>
|
||||||
|
|
||||||
<TextBlock Grid.Row="5"
|
<TextBlock Grid.Row="4"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Text="Thank You ♡"
|
Text="Thank You ♡"
|
||||||
|
|
|
@ -21,7 +21,7 @@ public sealed class ViewLocator : IDataTemplate
|
||||||
{
|
{
|
||||||
return (data is ViewModelBase viewModel && viewModel.GetType().BaseType?.GenericTypeArguments[0] is Type controlType)
|
return (data is ViewModelBase viewModel && viewModel.GetType().BaseType?.GenericTypeArguments[0] is Type controlType)
|
||||||
? (Application.Current as App)?.Services.GetService(controlType) as Control
|
? (Application.Current as App)?.Services.GetService(controlType) as Control
|
||||||
?? new TextBlock { Text = $"View-model of type \"{data?.GetType().FullName ?? "null"}\" is not registered in the IoC container.", TextWrapping = TextWrapping.WrapWithOverflow }
|
?? new TextBlock { Text = $"View-model of type \"{data.GetType().FullName ?? "null"}\" is not registered in the IoC container.", TextWrapping = TextWrapping.WrapWithOverflow }
|
||||||
: new TextBlock { Text = $"Component of type \"{data?.GetType().FullName ?? "null"}\" is not a valid view-model.", TextWrapping = TextWrapping.WrapWithOverflow };
|
: new TextBlock { Text = $"Component of type \"{data?.GetType().FullName ?? "null"}\" is not a valid view-model.", TextWrapping = TextWrapping.WrapWithOverflow };
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue