diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5181ac8e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,263 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 2 +indent_style = space +tab_width = 2 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Code Actions #### + +# Type members +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Symbol search +dotnet_search_reference_assemblies = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = file_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_simple_property_accessors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.severity = suggestion +dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.style = begins_with__ + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.begins_with__.required_prefix = _ +dotnet_naming_style.begins_with__.required_suffix = +dotnet_naming_style.begins_with__.word_separator = +dotnet_naming_style.begins_with__.capitalization = camel_case + +[*.g.cs] +generated_code = true diff --git a/AGENTS.md b/AGENTS.md index 64f7dfd9..1a2d3503 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,4 +4,9 @@ This solution-level file points to project-specific agent guidance. - AresScript guidance: `ARES/AresScript/AGENTS.md` - Namespace style guidance: prefer namespace imports (`using` / `@using`) and concise type names across all files. Avoid fully qualified type names unless needed to resolve unavoidable namespace/type conflicts. -- Do not add extra empty lines at the end of a file after modifying it \ No newline at end of file +- Do not add extra empty lines at the end of a file after modifying it +- These commands will need network access and inside your sandbox you always have to run those commands +with `with_escalated_permissions: true` on the `shell` tool call and include a one-sentence justification +(e.g., "Need network access for npm install/build"). +Ensure to include the `with_escalated_permissions` for all builds, restores, migrations, installs, tests, etc +where network access is required otherwise the command will hang. \ No newline at end of file diff --git a/UI/AppSettings.cs b/UI/AppSettings.cs deleted file mode 100644 index 7e671c20..00000000 --- a/UI/AppSettings.cs +++ /dev/null @@ -1,8 +0,0 @@ -public struct AppSettings -{ - public AppSettings() - { - } - - public string DatabaseProvider { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/UI/Backend/ViewModels/DeviceStateLogging/ICombinedDeviceGetter.cs b/UI/Application/DeviceStateLogging/ICombinedDeviceGetter.cs similarity index 72% rename from UI/Backend/ViewModels/DeviceStateLogging/ICombinedDeviceGetter.cs rename to UI/Application/DeviceStateLogging/ICombinedDeviceGetter.cs index 63314aec..1f57bec6 100644 --- a/UI/Backend/ViewModels/DeviceStateLogging/ICombinedDeviceGetter.cs +++ b/UI/Application/DeviceStateLogging/ICombinedDeviceGetter.cs @@ -1,6 +1,6 @@ using Ares.Messages.DeviceStates; -namespace UI.Backend.ViewModels.DeviceStateLogging; +namespace UI.Application.DeviceStateLogging; public interface ICombinedDeviceGetter { diff --git a/UI/Application/Devices/AresDeviceDescription.cs b/UI/Application/Devices/AresDeviceDescription.cs new file mode 100644 index 00000000..a98fd93e --- /dev/null +++ b/UI/Application/Devices/AresDeviceDescription.cs @@ -0,0 +1,3 @@ +namespace UI.Application.Devices; + +public record AresDeviceDescription(string Id, string Name); diff --git a/UI/Backend/Devices/ConnectionStatus.cs b/UI/Application/Devices/ConnectionStatus.cs similarity index 87% rename from UI/Backend/Devices/ConnectionStatus.cs rename to UI/Application/Devices/ConnectionStatus.cs index 6e47b682..ca910984 100644 --- a/UI/Backend/Devices/ConnectionStatus.cs +++ b/UI/Application/Devices/ConnectionStatus.cs @@ -1,12 +1,12 @@ -namespace UI.Backend.Devices; - -public enum ConnectionStatus -{ - Undefined, - Disconnected, - // we've connected to the service and are receiving updates - // but the service does not have connection to the device itself - ConnectedToService, - // the UI is talking to service and service is talking to the device - ConnectedToDevice, -} +namespace UI.Application.Devices; + +public enum ConnectionStatus +{ + Undefined, + Disconnected, + // we've connected to the service and are receiving updates + // but the service does not have connection to the device itself + ConnectedToService, + // the UI is talking to service and service is talking to the device + ConnectedToDevice, +} diff --git a/UI/Backend/Devices/DeviceDeletedMessage.cs b/UI/Application/Devices/DeviceDeletedMessage.cs similarity index 59% rename from UI/Backend/Devices/DeviceDeletedMessage.cs rename to UI/Application/Devices/DeviceDeletedMessage.cs index de2f8d1b..070d66c7 100644 --- a/UI/Backend/Devices/DeviceDeletedMessage.cs +++ b/UI/Application/Devices/DeviceDeletedMessage.cs @@ -1,3 +1,3 @@ -namespace UI.Backend.Devices; +namespace UI.Application.Devices; public record DeviceDeletedMessage(string DeviceId); diff --git a/UI/Backend/ViewModels/DeviceUnitControlViewModel.cs b/UI/Application/Devices/DeviceUnitControlViewModel.cs similarity index 91% rename from UI/Backend/ViewModels/DeviceUnitControlViewModel.cs rename to UI/Application/Devices/DeviceUnitControlViewModel.cs index a8a67d8b..57001e3e 100644 --- a/UI/Backend/ViewModels/DeviceUnitControlViewModel.cs +++ b/UI/Application/Devices/DeviceUnitControlViewModel.cs @@ -1,6 +1,6 @@ using ReactiveUI; -namespace UI.Backend.ViewModels; +namespace UI.Application.Devices; public abstract class DeviceUnitControlViewModel : ReactiveObject { diff --git a/UI/Backend/Devices/IAresDeviceAdapter.cs b/UI/Application/Devices/IAresDeviceAdapter.cs similarity index 90% rename from UI/Backend/Devices/IAresDeviceAdapter.cs rename to UI/Application/Devices/IAresDeviceAdapter.cs index 7211200d..36d8e3c9 100644 --- a/UI/Backend/Devices/IAresDeviceAdapter.cs +++ b/UI/Application/Devices/IAresDeviceAdapter.cs @@ -1,19 +1,19 @@ -using Ares.Datamodel; -using Ares.Datamodel.Device; - -namespace UI.Backend.Devices; - -public interface IAresDeviceAdapter -{ - string Id { get; } - string Name { get; } - string Description { get; } - string Type { get; } - string Version { get; } - Task Activate(); - Task UpdateConnectionStatus(); - AresStruct? State { get; } - IObservable StateStream { get; } - DeviceOperationalStatus OperationalStatus { get; } - IObservable ConnectionStatusStream { get; } -} +using Ares.Datamodel; +using Ares.Datamodel.Device; + +namespace UI.Application.Devices; + +public interface IAresDeviceAdapter +{ + string Id { get; } + string Name { get; } + string Description { get; } + string Type { get; } + string Version { get; } + Task Activate(); + Task UpdateConnectionStatus(); + AresStruct? State { get; } + IObservable StateStream { get; } + DeviceOperationalStatus OperationalStatus { get; } + IObservable ConnectionStatusStream { get; } +} diff --git a/UI/Backend/Repos/DeviceControlViewModelRepo.cs b/UI/Application/Devices/Repos/DeviceControlViewModelRepo.cs similarity index 92% rename from UI/Backend/Repos/DeviceControlViewModelRepo.cs rename to UI/Application/Devices/Repos/DeviceControlViewModelRepo.cs index bf4eec63..fe9f105c 100644 --- a/UI/Backend/Repos/DeviceControlViewModelRepo.cs +++ b/UI/Application/Devices/Repos/DeviceControlViewModelRepo.cs @@ -1,9 +1,7 @@ -using CommunityToolkit.Mvvm.Messaging; +using CommunityToolkit.Mvvm.Messaging; using DynamicData; -using UI.Backend.Devices; -using UI.Backend.ViewModels; -namespace UI.Backend.Repos +namespace UI.Application.Devices.Repos { public class DeviceControlViewModelRepo : IDeviceControlViewModelRepo { diff --git a/UI/Application/Devices/Repos/IDeviceAdapterRepository.cs b/UI/Application/Devices/Repos/IDeviceAdapterRepository.cs new file mode 100644 index 00000000..141d76d2 --- /dev/null +++ b/UI/Application/Devices/Repos/IDeviceAdapterRepository.cs @@ -0,0 +1,7 @@ +using DynamicData; + +namespace UI.Application.Devices.Repos; + +public interface IDeviceAdapterRepository : ISourceCache, IDisposable +{ +} \ No newline at end of file diff --git a/UI/Backend/Repos/IDeviceControlViewModelRepo.cs b/UI/Application/Devices/Repos/IDeviceControlViewModelRepo.cs similarity index 61% rename from UI/Backend/Repos/IDeviceControlViewModelRepo.cs rename to UI/Application/Devices/Repos/IDeviceControlViewModelRepo.cs index fbe93429..a28575a1 100644 --- a/UI/Backend/Repos/IDeviceControlViewModelRepo.cs +++ b/UI/Application/Devices/Repos/IDeviceControlViewModelRepo.cs @@ -1,7 +1,6 @@ -using DynamicData; -using UI.Backend.ViewModels; +using DynamicData; -namespace UI.Backend.Repos +namespace UI.Application.Devices.Repos { public interface IDeviceControlViewModelRepo : ISourceList { diff --git a/UI/Services/Dialog/IUiDialogService.cs b/UI/Application/Dialog/IUiDialogService.cs similarity index 80% rename from UI/Services/Dialog/IUiDialogService.cs rename to UI/Application/Dialog/IUiDialogService.cs index 00837c1f..20df4d2c 100644 --- a/UI/Services/Dialog/IUiDialogService.cs +++ b/UI/Application/Dialog/IUiDialogService.cs @@ -1,6 +1,7 @@ -namespace UI.Services.Dialog; +namespace UI.Application.Dialog; public interface IUiDialogService { Task ConfirmAsync(string message, string? title = null, UiConfirmOptions? options = null); } + diff --git a/UI/Services/Dialog/UiConfirmOptions.cs b/UI/Application/Dialog/UiConfirmOptions.cs similarity index 81% rename from UI/Services/Dialog/UiConfirmOptions.cs rename to UI/Application/Dialog/UiConfirmOptions.cs index 7abe55dd..6bd3446e 100644 --- a/UI/Services/Dialog/UiConfirmOptions.cs +++ b/UI/Application/Dialog/UiConfirmOptions.cs @@ -1,7 +1,8 @@ -namespace UI.Services.Dialog; +namespace UI.Application.Dialog; public sealed class UiConfirmOptions { public string OkButtonText { get; set; } = "Ok"; public string CancelButtonText { get; set; } = "Cancel"; } + diff --git a/UI/Services/ILocalService.cs b/UI/Application/Hosting/ILocalService.cs similarity index 61% rename from UI/Services/ILocalService.cs rename to UI/Application/Hosting/ILocalService.cs index 6809ee4e..a798853b 100644 --- a/UI/Services/ILocalService.cs +++ b/UI/Application/Hosting/ILocalService.cs @@ -1,6 +1,6 @@ -namespace UI.Services; - -internal interface ILocalService -{ - Task Start(); -} +namespace UI.Application.Hosting; + +internal interface ILocalService +{ + Task Start(); +} diff --git a/UI/Application/Notifications/INotificationReceivingService.cs b/UI/Application/Notifications/INotificationReceivingService.cs new file mode 100644 index 00000000..c2647390 --- /dev/null +++ b/UI/Application/Notifications/INotificationReceivingService.cs @@ -0,0 +1,13 @@ +using Ares.Services; + +namespace UI.Application.Notifications; + +public interface INotificationReceivingService +{ + void StartNotificationStream(); + // Might not be a bad idea to switch from directly accessing a datamodel AresNotification + // to some sort of app-specific implementation so we're not depending on datamodel in our + // Application layer + void PushNotification(AresNotification notification); +} + diff --git a/UI/Backend/Notifications/INotificationRepository.cs b/UI/Application/Notifications/INotificationRepository.cs similarity index 56% rename from UI/Backend/Notifications/INotificationRepository.cs rename to UI/Application/Notifications/INotificationRepository.cs index d67b08c1..29bb2f0d 100644 --- a/UI/Backend/Notifications/INotificationRepository.cs +++ b/UI/Application/Notifications/INotificationRepository.cs @@ -1,7 +1,8 @@ -using Ares.Services; +using Ares.Services; -namespace UI.Backend.Notifications; +namespace UI.Application.Notifications; public interface INotificationRepository : ICollection { } + diff --git a/UI/Services/Notification/IUiNotificationService.cs b/UI/Application/Notifications/IUiNotificationService.cs similarity index 68% rename from UI/Services/Notification/IUiNotificationService.cs rename to UI/Application/Notifications/IUiNotificationService.cs index 2555104b..688977ea 100644 --- a/UI/Services/Notification/IUiNotificationService.cs +++ b/UI/Application/Notifications/IUiNotificationService.cs @@ -1,6 +1,7 @@ -namespace UI.Services.Notification; +namespace UI.Application.Notifications; public interface IUiNotificationService { void Notify(UiNotificationMessage message); } + diff --git a/UI/Services/Notification/UiNotificationMessage.cs b/UI/Application/Notifications/UiNotificationMessage.cs similarity index 89% rename from UI/Services/Notification/UiNotificationMessage.cs rename to UI/Application/Notifications/UiNotificationMessage.cs index 83076b2a..b54c589b 100644 --- a/UI/Services/Notification/UiNotificationMessage.cs +++ b/UI/Application/Notifications/UiNotificationMessage.cs @@ -1,4 +1,4 @@ -namespace UI.Services.Notification; +namespace UI.Application.Notifications; public sealed record UiNotificationMessage { @@ -8,3 +8,4 @@ public sealed record UiNotificationMessage public int DurationMs { get; set; } = 5000; public bool CloseOnClick { get; set; } = true; } + diff --git a/UI/Services/Notification/UiNotificationServiceExtensions.cs b/UI/Application/Notifications/UiNotificationServiceExtensions.cs similarity index 96% rename from UI/Services/Notification/UiNotificationServiceExtensions.cs rename to UI/Application/Notifications/UiNotificationServiceExtensions.cs index 2d681250..952f8156 100644 --- a/UI/Services/Notification/UiNotificationServiceExtensions.cs +++ b/UI/Application/Notifications/UiNotificationServiceExtensions.cs @@ -1,4 +1,4 @@ -namespace UI.Services.Notification; +namespace UI.Application.Notifications; public static class UiNotificationServiceExtensions { @@ -42,3 +42,4 @@ public static void Info(this IUiNotificationService notificationService, string }); } } + diff --git a/UI/Services/Notification/UiNotificationSeverity.cs b/UI/Application/Notifications/UiNotificationSeverity.cs similarity index 65% rename from UI/Services/Notification/UiNotificationSeverity.cs rename to UI/Application/Notifications/UiNotificationSeverity.cs index 01d97443..ee3a4d6d 100644 --- a/UI/Services/Notification/UiNotificationSeverity.cs +++ b/UI/Application/Notifications/UiNotificationSeverity.cs @@ -1,4 +1,4 @@ -namespace UI.Services.Notification; +namespace UI.Application.Notifications; public enum UiNotificationSeverity { @@ -7,3 +7,4 @@ public enum UiNotificationSeverity Warning, Error } + diff --git a/UI/Application/Scripting/IMonacoCompletionProvider.cs b/UI/Application/Scripting/IMonacoCompletionProvider.cs new file mode 100644 index 00000000..c69ae0db --- /dev/null +++ b/UI/Application/Scripting/IMonacoCompletionProvider.cs @@ -0,0 +1,9 @@ +using MonacoCompletionItem = BlazorMonaco.Languages.CompletionItem; + +namespace UI.Application.Scripting; + +public interface IMonacoCompletionProvider +{ + Task GetCompletionItems(string script, int line, int column); +} + diff --git a/UI/Application/Scripting/IMonacoDiagnosticsProvider.cs b/UI/Application/Scripting/IMonacoDiagnosticsProvider.cs new file mode 100644 index 00000000..11b327c3 --- /dev/null +++ b/UI/Application/Scripting/IMonacoDiagnosticsProvider.cs @@ -0,0 +1,16 @@ +namespace UI.Application.Scripting; + +public interface IMonacoDiagnosticsProvider +{ + Task GetDiagnostics(string script); +} + +public record MonacoDiagnostic( + int StartLineNumber, + int StartColumn, + int EndLineNumber, + int EndColumn, + string Message, + int Severity, + string? Code); + diff --git a/UI/Application/Scripting/IMonacoHoverProvider.cs b/UI/Application/Scripting/IMonacoHoverProvider.cs new file mode 100644 index 00000000..33479a6a --- /dev/null +++ b/UI/Application/Scripting/IMonacoHoverProvider.cs @@ -0,0 +1,7 @@ +namespace UI.Application.Scripting; + +public interface IMonacoHoverProvider +{ + Task GetHoverText(string script, int line, int column, string identifier); +} + diff --git a/UI/Application/Scripting/IMonacoSemanticTokensProvider.cs b/UI/Application/Scripting/IMonacoSemanticTokensProvider.cs new file mode 100644 index 00000000..0d11fa36 --- /dev/null +++ b/UI/Application/Scripting/IMonacoSemanticTokensProvider.cs @@ -0,0 +1,9 @@ +namespace UI.Application.Scripting; + +public interface IMonacoSemanticTokensProvider +{ + SemanticToken[] GetSemanticTokens(string script); +} + +public record SemanticToken(int Line, int StartColumn, int Length, string Type); + diff --git a/UI/Application/Settings/AppSettings.cs b/UI/Application/Settings/AppSettings.cs new file mode 100644 index 00000000..3fd85917 --- /dev/null +++ b/UI/Application/Settings/AppSettings.cs @@ -0,0 +1,10 @@ +namespace UI.Application.Settings; + +public struct AppSettings +{ + public AppSettings() + { + } + + public string DatabaseProvider { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/UI/Settings/CertificateSettings.cs b/UI/Application/Settings/CertificateSettings.cs similarity index 75% rename from UI/Settings/CertificateSettings.cs rename to UI/Application/Settings/CertificateSettings.cs index 21b3578e..dbf6d913 100644 --- a/UI/Settings/CertificateSettings.cs +++ b/UI/Application/Settings/CertificateSettings.cs @@ -1,7 +1,7 @@ -namespace UI.Settings; - -internal record CertificateSettings -{ - public string? Path { get; set; } - public string? Password { get; set; } -} +namespace UI.Application.Settings; + +internal record CertificateSettings +{ + public string? Path { get; set; } + public string? Password { get; set; } +} diff --git a/UI/Settings/RemoteServiceSettings.cs b/UI/Application/Settings/RemoteServiceSettings.cs similarity index 76% rename from UI/Settings/RemoteServiceSettings.cs rename to UI/Application/Settings/RemoteServiceSettings.cs index 0e4e9291..414c732d 100644 --- a/UI/Settings/RemoteServiceSettings.cs +++ b/UI/Application/Settings/RemoteServiceSettings.cs @@ -1,7 +1,7 @@ -namespace UI.Settings; - -public class RemoteServiceSettings -{ - public string? ServerHost { get; set; } - public int? ServerPort { get; set; } -} +namespace UI.Application.Settings; + +public class RemoteServiceSettings +{ + public string? ServerHost { get; set; } + public int? ServerPort { get; set; } +} diff --git a/UI/Backend/Notifications/NotificationRepository.cs b/UI/Backend/Notifications/NotificationRepository.cs deleted file mode 100644 index 9b330b8a..00000000 --- a/UI/Backend/Notifications/NotificationRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Ares.Services; - -namespace UI.Backend.Notifications; - -public class NotificationRepository : List, INotificationRepository -{ -} diff --git a/UI/Backend/ViewModels/AresDeviceDescription.cs b/UI/Backend/ViewModels/AresDeviceDescription.cs deleted file mode 100644 index 00e564e1..00000000 --- a/UI/Backend/ViewModels/AresDeviceDescription.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace UI.Backend.ViewModels; - -public record AresDeviceDescription(string Id, string Name); \ No newline at end of file diff --git a/UI/Backend/ViewModels/Automation/CustomStepBuilderViewModel.cs b/UI/Backend/ViewModels/Automation/CustomStepBuilderViewModel.cs deleted file mode 100644 index 52ea6e50..00000000 --- a/UI/Backend/ViewModels/Automation/CustomStepBuilderViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -using ReactiveUI; - -namespace UI.Backend.ViewModels.Automation; - -public class CustomStepBuilderViewModel : ReactiveObject -{ -} \ No newline at end of file diff --git a/UI/Backend/ViewModels/IndexViewModel.cs b/UI/Backend/ViewModels/IndexViewModel.cs deleted file mode 100644 index 6140b865..00000000 --- a/UI/Backend/ViewModels/IndexViewModel.cs +++ /dev/null @@ -1,161 +0,0 @@ -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels; - -public partial class IndexViewModel : ReactiveObject -{ - public IndexViewModel() - { - var currentMonth = DateTime.Parse("2019-12-01"); - Revenue2019 = - [ - new() - { - Date = DateTime.Parse("2019-01-01"), - Revenue = 234000 - }, - new() - { - Date = DateTime.Parse("2019-02-01"), - Revenue = 269000 - }, - new() - { - Date = DateTime.Parse("2019-03-01"), - Revenue = 233000 - }, - new() - { - Date = DateTime.Parse("2019-04-01"), - Revenue = 244000 - }, - new() - { - Date = DateTime.Parse("2019-05-01"), - Revenue = 214000 - }, - new() - { - Date = DateTime.Parse("2019-06-01"), - Revenue = 253000 - }, - new() - { - Date = DateTime.Parse("2019-07-01"), - Revenue = 274000 - }, - new() - { - Date = DateTime.Parse("2019-08-01"), - Revenue = 284000 - }, - new() - { - Date = DateTime.Parse("2019-09-01"), - Revenue = 273000 - }, - new() - { - Date = DateTime.Parse("2019-10-01"), - Revenue = 282000 - }, - new() - { - Date = DateTime.Parse("2019-11-01"), - Revenue = 289000 - }, - new() - { - Date = DateTime.Parse("2019-12-01"), - Revenue = 294000 - } - ]; - - Revenue2020 = - [ - new() - { - Date = DateTime.Parse("2019-01-01"), - Revenue = 334000 - }, - new() - { - Date = DateTime.Parse("2019-02-01"), - Revenue = 369000 - }, - new() - { - Date = DateTime.Parse("2019-03-01"), - Revenue = 333000 - }, - new() - { - Date = DateTime.Parse("2019-04-01"), - Revenue = 344000 - }, - new() - { - Date = DateTime.Parse("2019-05-01"), - Revenue = 314000 - }, - new() - { - Date = DateTime.Parse("2019-06-01"), - Revenue = 353000 - }, - new() - { - Date = DateTime.Parse("2019-07-01"), - Revenue = 374000 - }, - new() - { - Date = DateTime.Parse("2019-08-01"), - Revenue = 384000 - }, - new() - { - Date = DateTime.Parse("2019-09-01"), - Revenue = 373000 - }, - new() - { - Date = DateTime.Parse("2019-10-01"), - Revenue = 382000 - }, - new() - { - Date = DateTime.Parse("2019-11-01"), - Revenue = 389000 - }, - new() - { - Date = DateTime.Parse("2019-12-01"), - Revenue = 394000 - } - ]; - - // Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(_ => - // { - // var currArray = Revenue2019.ToList(); - // currentMonth = currentMonth.AddMonths(1); - // var di = new DataItem - // { - // Date = currentMonth, - // Revenue = new Random().NextDouble() * 400000 - // }; - // currArray.Add(di); - // Revenue2019 = currArray; - // }); - } - - [Reactive] public partial List Revenue2019 { get; set; } - - [Reactive] public partial List Revenue2020 { get; set; } -} -public class DataItem -{ - public DateTime Date { get; set; } - public double Revenue { get; set; } -} diff --git a/UI/Backend/ViewModels/ProjectViewModel.cs b/UI/Backend/ViewModels/ProjectViewModel.cs deleted file mode 100644 index 6db01efc..00000000 --- a/UI/Backend/ViewModels/ProjectViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -using ReactiveUI; - -namespace UI.Backend.ViewModels; - -public class ProjectViewModel : ReactiveObject -{ -} \ No newline at end of file diff --git a/UI/Backend/ViewModels/QuasiManualViewModel.cs b/UI/Backend/ViewModels/QuasiManualViewModel.cs deleted file mode 100644 index 85247681..00000000 --- a/UI/Backend/ViewModels/QuasiManualViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -using ReactiveUI; - -namespace UI.Backend.ViewModels; - -public class QuasiManualViewModel : ReactiveObject -{ -} \ No newline at end of file diff --git a/UI/Backend/ViewModels/SettingsViewModel.cs b/UI/Backend/ViewModels/SettingsViewModel.cs deleted file mode 100644 index 92a811e4..00000000 --- a/UI/Backend/ViewModels/SettingsViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -using ReactiveUI; - -namespace UI.Backend.ViewModels; - -public class SettingsViewModel : ReactiveObject -{ -} \ No newline at end of file diff --git a/UI/Pages/Shared/AresFooter.razor b/UI/Components/AresFooter.razor similarity index 94% rename from UI/Pages/Shared/AresFooter.razor rename to UI/Components/AresFooter.razor index 2758d4e8..c4a71f43 100644 --- a/UI/Pages/Shared/AresFooter.razor +++ b/UI/Components/AresFooter.razor @@ -1,8 +1,8 @@ -
- @ChildContent -
- -@code { - [Parameter] - public RenderFragment? ChildContent { get; set; } -} +
+ @ChildContent +
+ +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } +} diff --git a/UI/Pages/Shared/AresValueDesigner.razor b/UI/Components/AresValueDesigner.razor similarity index 100% rename from UI/Pages/Shared/AresValueDesigner.razor rename to UI/Components/AresValueDesigner.razor diff --git a/UI/Pages/Shared/AresValueDisplay.razor b/UI/Components/AresValueDisplay.razor similarity index 100% rename from UI/Pages/Shared/AresValueDisplay.razor rename to UI/Components/AresValueDisplay.razor diff --git a/UI/Pages/Shared/DashboardWidget.razor b/UI/Components/DashboardWidget.razor similarity index 95% rename from UI/Pages/Shared/DashboardWidget.razor rename to UI/Components/DashboardWidget.razor index e48f8f6e..14516e66 100644 --- a/UI/Pages/Shared/DashboardWidget.razor +++ b/UI/Components/DashboardWidget.razor @@ -1,7 +1,7 @@ -
- @ChildContent -
-@code { - [Parameter] - public RenderFragment? ChildContent { get; set; } -} +
+ @ChildContent +
+@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } +} diff --git a/UI/Pages/Shared/DashboardWidget.razor.css b/UI/Components/DashboardWidget.razor.css similarity index 96% rename from UI/Pages/Shared/DashboardWidget.razor.css rename to UI/Components/DashboardWidget.razor.css index 580ceafb..0e237bba 100644 --- a/UI/Pages/Shared/DashboardWidget.razor.css +++ b/UI/Components/DashboardWidget.razor.css @@ -1,7 +1,7 @@ -.ares-dashboard-widget { - background: rgba(224, 255, 255, 0.1); - padding: 10px; - max-height: fit-content; - border-radius: 5px; - box-shadow: 5px 5px 5px rgba(10, 10, 10, 0.7); +.ares-dashboard-widget { + background: rgba(224, 255, 255, 0.1); + padding: 10px; + max-height: fit-content; + border-radius: 5px; + box-shadow: 5px 5px 5px rgba(10, 10, 10, 0.7); } \ No newline at end of file diff --git a/UI/Pages/Shared/CampaignEdit/CommandViewer.razor b/UI/Components/Experiments/CommandViewer.razor similarity index 96% rename from UI/Pages/Shared/CampaignEdit/CommandViewer.razor rename to UI/Components/Experiments/CommandViewer.razor index 6e33c0db..8a910575 100644 --- a/UI/Pages/Shared/CampaignEdit/CommandViewer.razor +++ b/UI/Components/Experiments/CommandViewer.razor @@ -1,97 +1,97 @@ -@using Ares.Datamodel.Templates -@using Ares.Services.Device - -@inject AresDevices.AresDevicesClient DevicesClient - -@if (CommandTemplate is not null) -{ - if (CommandTemplate.Metadata?.Name is not null) - { -
-
- @DeviceName -
-
- @CommandTemplate.Metadata.Name -
- @if (CommandTemplate.Parameters.Any()) - { - - } - @foreach (var argument in CommandTemplate.Parameters) - { - @if (argument.PlanningMetadata is not null) - { -
- @TryUnpackDisplayData(argument) -
- } - - else if(argument.VariableType > 0) - { -
- @argument.VariableType.ToString() -
- } - else - { - if (argument.Value is null) - { -
- Error! Parameter Deleted! -
- } - - else - { -
- @TryUnpackDisplayData(argument) -
- } - } - } -
- } - else - { - Undefined Command :^( - } -} -@code { - [Parameter] - public CommandTemplate? CommandTemplate { get; set; } - - private string DeviceName { get; set; } = ""; - - public object TryUnpackDisplayData(Parameter argument) - { - if(argument.PlanningMetadata is not null) - { - return $"{argument.Metadata.Name}: {argument.PlanningMetadata.Name}"; - } - var aresValue = argument.Value; - if(aresValue.HasNumberValue) - return $"{argument.Metadata.Name}: {aresValue.NumberValue} {argument.Metadata.Unit}"; - - else if(aresValue.HasStringValue) - return $"{argument.Metadata.Name}: {aresValue.StringValue} {argument.Metadata.Unit}"; - - else - return $"{argument.Metadata.Name}: Custom Data"; - } - - private async Task GetDeviceName(string deviceId) - { - var info = await DevicesClient.GetDeviceInfoAsync(new DeviceInfoRequest { DeviceId = deviceId }); - - return info.Name; - } - - protected override async Task OnInitializedAsync() - { - if(CommandTemplate is null) - return; - - DeviceName = await GetDeviceName(CommandTemplate.Metadata.DeviceId); - } -} +@using Ares.Datamodel.Templates +@using Ares.Services.Device + +@inject AresDevices.AresDevicesClient DevicesClient + +@if (CommandTemplate is not null) +{ + if (CommandTemplate.Metadata?.Name is not null) + { +
+
+ @DeviceName +
+
+ @CommandTemplate.Metadata.Name +
+ @if (CommandTemplate.Parameters.Any()) + { + + } + @foreach (var argument in CommandTemplate.Parameters) + { + @if (argument.PlanningMetadata is not null) + { +
+ @TryUnpackDisplayData(argument) +
+ } + + else if(argument.VariableType > 0) + { +
+ @argument.VariableType.ToString() +
+ } + else + { + if (argument.Value is null) + { +
+ Error! Parameter Deleted! +
+ } + + else + { +
+ @TryUnpackDisplayData(argument) +
+ } + } + } +
+ } + else + { + Undefined Command :^( + } +} +@code { + [Parameter] + public CommandTemplate? CommandTemplate { get; set; } + + private string DeviceName { get; set; } = ""; + + public object TryUnpackDisplayData(Parameter argument) + { + if(argument.PlanningMetadata is not null) + { + return $"{argument.Metadata.Name}: {argument.PlanningMetadata.Name}"; + } + var aresValue = argument.Value; + if(aresValue.HasNumberValue) + return $"{argument.Metadata.Name}: {aresValue.NumberValue} {argument.Metadata.Unit}"; + + else if(aresValue.HasStringValue) + return $"{argument.Metadata.Name}: {aresValue.StringValue} {argument.Metadata.Unit}"; + + else + return $"{argument.Metadata.Name}: Custom Data"; + } + + private async Task GetDeviceName(string deviceId) + { + var info = await DevicesClient.GetDeviceInfoAsync(new DeviceInfoRequest { DeviceId = deviceId }); + + return info.Name; + } + + protected override async Task OnInitializedAsync() + { + if(CommandTemplate is null) + return; + + DeviceName = await GetDeviceName(CommandTemplate.Metadata.DeviceId); + } +} diff --git a/UI/Pages/Resources/ExperimentOutputIcon.razor b/UI/Components/Experiments/ExperimentOutputIcon.razor similarity index 100% rename from UI/Pages/Resources/ExperimentOutputIcon.razor rename to UI/Components/Experiments/ExperimentOutputIcon.razor diff --git a/UI/Pages/Shared/CampaignEdit/ExperimentViewer.razor b/UI/Components/Experiments/ExperimentViewer.razor similarity index 100% rename from UI/Pages/Shared/CampaignEdit/ExperimentViewer.razor rename to UI/Components/Experiments/ExperimentViewer.razor diff --git a/UI/Pages/Shared/CampaignEdit/StepViewer.razor b/UI/Components/Experiments/StepViewer.razor similarity index 97% rename from UI/Pages/Shared/CampaignEdit/StepViewer.razor rename to UI/Components/Experiments/StepViewer.razor index bb2befb4..8652eca7 100644 --- a/UI/Pages/Shared/CampaignEdit/StepViewer.razor +++ b/UI/Components/Experiments/StepViewer.razor @@ -14,9 +14,9 @@ @CommandTemplates.Count
- @if(HasExperimentOutputs) - { - + @if(HasExperimentOutputs) + { + }
diff --git a/UI/Backend/Helpers/DisplayFormatHelper.cs b/UI/Components/Formatting/DisplayFormatHelper.cs similarity index 73% rename from UI/Backend/Helpers/DisplayFormatHelper.cs rename to UI/Components/Formatting/DisplayFormatHelper.cs index 93224141..f83eed48 100644 --- a/UI/Backend/Helpers/DisplayFormatHelper.cs +++ b/UI/Components/Formatting/DisplayFormatHelper.cs @@ -1,6 +1,6 @@ -using Google.Protobuf.WellKnownTypes; +using Google.Protobuf.WellKnownTypes; -namespace UI.Backend.Helpers; +namespace UI.Components.Formatting; public static class DisplayFormatHelper { diff --git a/UI/Backend/Helpers/UnitCategoryHelper.cs b/UI/Components/Formatting/UnitCategoryHelper.cs similarity index 91% rename from UI/Backend/Helpers/UnitCategoryHelper.cs rename to UI/Components/Formatting/UnitCategoryHelper.cs index 547df327..4d61bed7 100644 --- a/UI/Backend/Helpers/UnitCategoryHelper.cs +++ b/UI/Components/Formatting/UnitCategoryHelper.cs @@ -1,43 +1,43 @@ -using UnitsNet; - -namespace UI.Backend.Helpers; - -public class UnitCategoryHelper -{ - private readonly Dictionary _cachedLookups = []; - - public UnitCategoryHelper() - { - UnitCategories = Quantity.Infos.Select(i => i.Name).ToArray(); - Task.Run(BuildLookup); - } - - public string[] UnitCategories { get; } - - private void BuildLookup() - { - foreach(var quantityInfo in Quantity.Infos) - foreach(var unit in quantityInfo.UnitInfos) - // var humanUnit = unit.Name.Humanize(); - _cachedLookups.TryAdd(unit.Name, quantityInfo); - } - - public static string[] GetTypes(string unitCategory) - { - var selection = Quantity.Infos.FirstOrDefault(i => i.Name.Equals(unitCategory)); - if(selection == null) - return Array.Empty(); - - var units = selection.UnitInfos; - return units.Select(i => i.Name).ToArray(); - } - - public string GetCategoryForUnit(string unit) - { - if(unit == "None") - return unit; - - else - return _cachedLookups.ContainsKey(unit) ? _cachedLookups[unit].Name : string.Empty; - } -} +using UnitsNet; + +namespace UI.Components.Formatting; + +public class UnitCategoryHelper +{ + private readonly Dictionary _cachedLookups = []; + + public UnitCategoryHelper() + { + UnitCategories = Quantity.Infos.Select(i => i.Name).ToArray(); + Task.Run(BuildLookup); + } + + public string[] UnitCategories { get; } + + private void BuildLookup() + { + foreach(var quantityInfo in Quantity.Infos) + foreach(var unit in quantityInfo.UnitInfos) + // var humanUnit = unit.Name.Humanize(); + _cachedLookups.TryAdd(unit.Name, quantityInfo); + } + + public static string[] GetTypes(string unitCategory) + { + var selection = Quantity.Infos.FirstOrDefault(i => i.Name.Equals(unitCategory)); + if(selection == null) + return Array.Empty(); + + var units = selection.UnitInfos; + return units.Select(i => i.Name).ToArray(); + } + + public string GetCategoryForUnit(string unit) + { + if(unit == "None") + return unit; + + else + return _cachedLookups.ContainsKey(unit) ? _cachedLookups[unit].Name : string.Empty; + } +} diff --git a/UI/Pages/Shared/ErrorLayout.razor b/UI/Components/Layouts/ErrorLayout.razor similarity index 95% rename from UI/Pages/Shared/ErrorLayout.razor rename to UI/Components/Layouts/ErrorLayout.razor index abdc7df1..e4764d16 100644 --- a/UI/Pages/Shared/ErrorLayout.razor +++ b/UI/Components/Layouts/ErrorLayout.razor @@ -1,18 +1,18 @@ -@inherits LayoutComponentBase - - - - -ARES - -
- - - - - -
- @Body - Go Home -
-
+@inherits LayoutComponentBase + + + + +ARES + +
+ + + + + +
+ @Body + Go Home +
+
diff --git a/UI/Pages/Shared/ErrorLayout.razor.css b/UI/Components/Layouts/ErrorLayout.razor.css similarity index 94% rename from UI/Pages/Shared/ErrorLayout.razor.css rename to UI/Components/Layouts/ErrorLayout.razor.css index 699f17cd..551e4b27 100644 --- a/UI/Pages/Shared/ErrorLayout.razor.css +++ b/UI/Components/Layouts/ErrorLayout.razor.css @@ -1,70 +1,70 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - } - - .top-row a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { - justify-content: space-between; - } - - .top-row a, .top-row .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + } + + .top-row a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row:not(.auth) { + display: none; + } + + .top-row.auth { + justify-content: space-between; + } + + .top-row a, .top-row .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} diff --git a/UI/Pages/Shared/MainLayout.razor b/UI/Components/Layouts/MainLayout.razor similarity index 96% rename from UI/Pages/Shared/MainLayout.razor rename to UI/Components/Layouts/MainLayout.razor index 303743b2..0c5f0ded 100644 --- a/UI/Pages/Shared/MainLayout.razor +++ b/UI/Components/Layouts/MainLayout.razor @@ -1,146 +1,147 @@ -@using Ares.Services -@using Google.Protobuf.WellKnownTypes -@using UI.Services.Dialog -@using UI.Services.Notification -@inherits LayoutComponentBase - -@inject IUiDialogService dialogService -@inject AresSafetyService.AresSafetyServiceClient safetyServiceClient -@inject INotificationReceivingService notificationService - - - - -ARES - -
- - - - - -
- @* - *@ -
-
- - - - - - - - - - - - - @* Coming Soon *@ - - @* *@ - - - - - - - - - - -
- -
- - - - - - - @* Coming Soon *@ - @* *@ - - -
-
- -
-
- @Body -
- -
- @*
- -
- -
-
-
*@ -
-
- -@code { - public bool SidebarExpanded { get; set; } = true; - public bool BodyExpanded { get; set; } - - private void SidebarExpandClick() - { - SidebarExpanded = !SidebarExpanded; - BodyExpanded = !BodyExpanded; - } - - async Task ShowEmergencyStopDialog() - { - var confirmDialogOptions = new UiConfirmOptions { OkButtonText = "Yes", CancelButtonText = "No" }; - var result = await dialogService.ConfirmAsync("Are you sure you want to trigger an emergency stop?", "Emergency Stop Confirmation", confirmDialogOptions); - - if(result) - { - var emergencyStopResponse = await safetyServiceClient.RequestEmergencyStopAsync(new EmergencyStopRequest()); - AresNotification notification; - - if(emergencyStopResponse.Status == EmergencyStopStatus.Success) - { - notification = new AresNotification - { - Loiter = true, - Message = emergencyStopResponse.StatusMessage, - Title = "Successfully Processed Emergency Shutdown Request", - NotificationSeverity = Severity.Success, - Timestamp = DateTime.UtcNow.ToTimestamp() - }; - } - - else - { - notification = new AresNotification - { - Loiter = true, - Message = emergencyStopResponse.StatusMessage, - Title = "Failed to Process Emergency Shutdown Request!", - NotificationSeverity = Severity.Danger, - Timestamp = DateTime.UtcNow.ToTimestamp() - }; - } - - notificationService.PushNotification(notification); - } - - } - -} \ No newline at end of file +@using Ares.Services +@using Google.Protobuf.WellKnownTypes +@using UI.Application.Dialog +@using UI.Application.Notifications + +@inherits LayoutComponentBase + +@inject IUiDialogService dialogService +@inject AresSafetyService.AresSafetyServiceClient safetyServiceClient +@inject INotificationReceivingService notificationService + + + + +ARES + +
+ + + + + +
+ @* + *@ +
+
+ + + + + + + + + + + + + @* Coming Soon *@ + + @* *@ + + + + + + + + + + +
+ +
+ + + + + + + @* Coming Soon *@ + @* *@ + + +
+
+ +
+
+ @Body +
+ +
+ @*
+ +
+ +
+
+
*@ +
+
+ +@code { + public bool SidebarExpanded { get; set; } = true; + public bool BodyExpanded { get; set; } + + private void SidebarExpandClick() + { + SidebarExpanded = !SidebarExpanded; + BodyExpanded = !BodyExpanded; + } + + async Task ShowEmergencyStopDialog() + { + var confirmDialogOptions = new UiConfirmOptions { OkButtonText = "Yes", CancelButtonText = "No" }; + var result = await dialogService.ConfirmAsync("Are you sure you want to trigger an emergency stop?", "Emergency Stop Confirmation", confirmDialogOptions); + + if(result) + { + var emergencyStopResponse = await safetyServiceClient.RequestEmergencyStopAsync(new EmergencyStopRequest()); + AresNotification notification; + + if(emergencyStopResponse.Status == EmergencyStopStatus.Success) + { + notification = new AresNotification + { + Loiter = true, + Message = emergencyStopResponse.StatusMessage, + Title = "Successfully Processed Emergency Shutdown Request", + NotificationSeverity = Severity.Success, + Timestamp = DateTime.UtcNow.ToTimestamp() + }; + } + + else + { + notification = new AresNotification + { + Loiter = true, + Message = emergencyStopResponse.StatusMessage, + Title = "Failed to Process Emergency Shutdown Request!", + NotificationSeverity = Severity.Danger, + Timestamp = DateTime.UtcNow.ToTimestamp() + }; + } + + notificationService.PushNotification(notification); + } + + } + +} diff --git a/UI/Pages/Shared/MainLayout.razor.css b/UI/Components/Layouts/MainLayout.razor.css similarity index 93% rename from UI/Pages/Shared/MainLayout.razor.css rename to UI/Components/Layouts/MainLayout.razor.css index 6ab56faa..79824cc7 100644 --- a/UI/Pages/Shared/MainLayout.razor.css +++ b/UI/Components/Layouts/MainLayout.razor.css @@ -1,42 +1,42 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.hamburger-button { - position: absolute; - z-index: 10; - padding-left: 0.9rem; -} - -.main-layout-grid { - display: grid; - grid-template: - "sidebar content" 1fr - "sidebar footer" auto - /auto 1fr; -} - -.ares-sidebar { - grid-area: sidebar; -} - -.ares-footer { - grid-area: footer; -} - -.ares-content { - grid-area: content; - overflow: auto; - display: flex; - flex-direction: column; +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.hamburger-button { + position: absolute; + z-index: 10; + padding-left: 0.9rem; +} + +.main-layout-grid { + display: grid; + grid-template: + "sidebar content" 1fr + "sidebar footer" auto + /auto 1fr; +} + +.ares-sidebar { + grid-area: sidebar; +} + +.ares-footer { + grid-area: footer; +} + +.ares-content { + grid-area: content; + overflow: auto; + display: flex; + flex-direction: column; } \ No newline at end of file diff --git a/UI/Pages/Shared/Settings/SettingsLayout.razor b/UI/Components/Layouts/SettingsLayout.razor similarity index 96% rename from UI/Pages/Shared/Settings/SettingsLayout.razor rename to UI/Components/Layouts/SettingsLayout.razor index 1172213a..a981f1a2 100644 --- a/UI/Pages/Shared/Settings/SettingsLayout.razor +++ b/UI/Components/Layouts/SettingsLayout.razor @@ -1,24 +1,24 @@ -@inherits LayoutComponentBase -@layout MainLayout - - -
- @Body -
- -@code { - -} +@inherits LayoutComponentBase +@layout MainLayout + + +
+ @Body +
+ +@code { + +} diff --git a/UI/Pages/Shared/Settings/SettingsLayout.razor.css b/UI/Components/Layouts/SettingsLayout.razor.css similarity index 96% rename from UI/Pages/Shared/Settings/SettingsLayout.razor.css rename to UI/Components/Layouts/SettingsLayout.razor.css index 165ecf57..ec4b9294 100644 --- a/UI/Pages/Shared/Settings/SettingsLayout.razor.css +++ b/UI/Components/Layouts/SettingsLayout.razor.css @@ -1,4 +1,4 @@ -.settings-container { - display: grid; - grid-template-rows: auto max-content; +.settings-container { + display: grid; + grid-template-rows: auto max-content; } \ No newline at end of file diff --git a/UI/Pages/Shared/LoadingSpinner.razor b/UI/Components/LoadingSpinner.razor similarity index 94% rename from UI/Pages/Shared/LoadingSpinner.razor rename to UI/Components/LoadingSpinner.razor index 220641f4..dd32243e 100644 --- a/UI/Pages/Shared/LoadingSpinner.razor +++ b/UI/Components/LoadingSpinner.razor @@ -1,21 +1,21 @@ -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -@code { - - [Parameter] - public SpinnerSize SpinnerSize { get; set; } = SpinnerSize.Large; - +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +@code { + + [Parameter] + public SpinnerSize SpinnerSize { get; set; } = SpinnerSize.Large; + } \ No newline at end of file diff --git a/UI/Pages/Shared/LoadingSpinner.razor.css b/UI/Components/LoadingSpinner.razor.css similarity index 94% rename from UI/Pages/Shared/LoadingSpinner.razor.css rename to UI/Components/LoadingSpinner.razor.css index baeb577c..f7f8c21d 100644 --- a/UI/Pages/Shared/LoadingSpinner.razor.css +++ b/UI/Components/LoadingSpinner.razor.css @@ -1,119 +1,119 @@ -.lds-spinner { - color: grey; - display: inline-block; - position: relative; -} - -.lds-spinner.lds-spinner-sm { - width: 18px; - height: 18px; -} - -.lds-spinner.lds-spinner-lg { - width: 80px; - height: 80px; -} - -.lds-spinner.lds-spinner-sm div { - transform-origin: 10px 10px; -} - -.lds-spinner.lds-spinner-lg div { - transform-origin: 40px 40px; -} - -.lds-spinner div { - animation: lds-spinner 1.2s linear infinite; -} - -.lds-spinner div:after { - content: " "; - display: block; - position: absolute; - border-radius: 20%; - background: #fff; -} - -.lds-spinner.lds-spinner-sm div:after { - top: 1px; - left: 9px; - width: 1px; - height: 5px; -} - - .lds-spinner.lds-spinner-lg div:after { - top: 3px; - left: 37px; - width: 6px; - height: 18px; - } - -.lds-spinner div:nth-child(1) { - transform: rotate(0deg); - animation-delay: -1.1s; -} - -.lds-spinner div:nth-child(2) { - transform: rotate(30deg); - animation-delay: -1s; -} - -.lds-spinner div:nth-child(3) { - transform: rotate(60deg); - animation-delay: -0.9s; -} - -.lds-spinner div:nth-child(4) { - transform: rotate(90deg); - animation-delay: -0.8s; -} - -.lds-spinner div:nth-child(5) { - transform: rotate(120deg); - animation-delay: -0.7s; -} - -.lds-spinner div:nth-child(6) { - transform: rotate(150deg); - animation-delay: -0.6s; -} - -.lds-spinner div:nth-child(7) { - transform: rotate(180deg); - animation-delay: -0.5s; -} - -.lds-spinner div:nth-child(8) { - transform: rotate(210deg); - animation-delay: -0.4s; -} - -.lds-spinner div:nth-child(9) { - transform: rotate(240deg); - animation-delay: -0.3s; -} - -.lds-spinner div:nth-child(10) { - transform: rotate(270deg); - animation-delay: -0.2s; -} - -.lds-spinner div:nth-child(11) { - transform: rotate(300deg); - animation-delay: -0.1s; -} - -.lds-spinner div:nth-child(12) { - transform: rotate(330deg); - animation-delay: 0s; -} - -@keyframes lds-spinner { - 0% { - opacity: 1; - } - - 100% { - opacity: 0; - } +.lds-spinner { + color: grey; + display: inline-block; + position: relative; +} + +.lds-spinner.lds-spinner-sm { + width: 18px; + height: 18px; +} + +.lds-spinner.lds-spinner-lg { + width: 80px; + height: 80px; +} + +.lds-spinner.lds-spinner-sm div { + transform-origin: 10px 10px; +} + +.lds-spinner.lds-spinner-lg div { + transform-origin: 40px 40px; +} + +.lds-spinner div { + animation: lds-spinner 1.2s linear infinite; +} + +.lds-spinner div:after { + content: " "; + display: block; + position: absolute; + border-radius: 20%; + background: #fff; +} + +.lds-spinner.lds-spinner-sm div:after { + top: 1px; + left: 9px; + width: 1px; + height: 5px; +} + + .lds-spinner.lds-spinner-lg div:after { + top: 3px; + left: 37px; + width: 6px; + height: 18px; + } + +.lds-spinner div:nth-child(1) { + transform: rotate(0deg); + animation-delay: -1.1s; +} + +.lds-spinner div:nth-child(2) { + transform: rotate(30deg); + animation-delay: -1s; +} + +.lds-spinner div:nth-child(3) { + transform: rotate(60deg); + animation-delay: -0.9s; +} + +.lds-spinner div:nth-child(4) { + transform: rotate(90deg); + animation-delay: -0.8s; +} + +.lds-spinner div:nth-child(5) { + transform: rotate(120deg); + animation-delay: -0.7s; +} + +.lds-spinner div:nth-child(6) { + transform: rotate(150deg); + animation-delay: -0.6s; +} + +.lds-spinner div:nth-child(7) { + transform: rotate(180deg); + animation-delay: -0.5s; +} + +.lds-spinner div:nth-child(8) { + transform: rotate(210deg); + animation-delay: -0.4s; +} + +.lds-spinner div:nth-child(9) { + transform: rotate(240deg); + animation-delay: -0.3s; +} + +.lds-spinner div:nth-child(10) { + transform: rotate(270deg); + animation-delay: -0.2s; +} + +.lds-spinner div:nth-child(11) { + transform: rotate(300deg); + animation-delay: -0.1s; +} + +.lds-spinner div:nth-child(12) { + transform: rotate(330deg); + animation-delay: 0s; +} + +@keyframes lds-spinner { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } } \ No newline at end of file diff --git a/UI/Pages/Shared/AresNavFooterItem.razor b/UI/Components/Nav/AresNavFooterItem.razor similarity index 95% rename from UI/Pages/Shared/AresNavFooterItem.razor rename to UI/Components/Nav/AresNavFooterItem.razor index ec484832..b1517885 100644 --- a/UI/Pages/Shared/AresNavFooterItem.razor +++ b/UI/Components/Nav/AresNavFooterItem.razor @@ -1,14 +1,14 @@ - - - - -@code { - [Parameter] - public RenderFragment? ChildContent { get; set; } - - [Parameter] - public string? Icon { get; set; } - - [Parameter] - public string? Path { get; set; } -} + + + + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public string? Icon { get; set; } + + [Parameter] + public string? Path { get; set; } +} diff --git a/UI/Pages/Shared/AresNavItem.razor b/UI/Components/Nav/AresNavItem.razor similarity index 99% rename from UI/Pages/Shared/AresNavItem.razor rename to UI/Components/Nav/AresNavItem.razor index 32883fd8..fabdc0b8 100644 --- a/UI/Pages/Shared/AresNavItem.razor +++ b/UI/Components/Nav/AresNavItem.razor @@ -30,7 +30,7 @@ } -@code { +@code { [Parameter] public string? Path { get; set; } diff --git a/UI/Pages/Shared/AresNavMenu.razor b/UI/Components/Nav/AresNavMenu.razor similarity index 96% rename from UI/Pages/Shared/AresNavMenu.razor rename to UI/Components/Nav/AresNavMenu.razor index 862cc6a9..05ac838c 100644 --- a/UI/Pages/Shared/AresNavMenu.razor +++ b/UI/Components/Nav/AresNavMenu.razor @@ -1,35 +1,35 @@ -
- - Ares Logo - -
- -
-
- @FooterContent -
-
- -
-
- -
-
- -@code { - [Parameter] - public RenderFragment? MainNavContent { get; set; } - - [Parameter] - public RenderFragment? FooterContent { get; set; } - - [Parameter] - public RenderFragment? CollapsedNavContent { get; set; } - - [Parameter] - public bool ExpandedMainNav { get; set; } -} +
+ + Ares Logo + +
+ +
+
+ @FooterContent +
+
+ +
+
+ +
+
+ +@code { + [Parameter] + public RenderFragment? MainNavContent { get; set; } + + [Parameter] + public RenderFragment? FooterContent { get; set; } + + [Parameter] + public RenderFragment? CollapsedNavContent { get; set; } + + [Parameter] + public bool ExpandedMainNav { get; set; } +} diff --git a/UI/Pages/Shared/ScriptEditor.razor b/UI/Components/ScriptEditor.razor similarity index 86% rename from UI/Pages/Shared/ScriptEditor.razor rename to UI/Components/ScriptEditor.razor index b0b3fc78..71f14983 100644 --- a/UI/Pages/Shared/ScriptEditor.razor +++ b/UI/Components/ScriptEditor.razor @@ -1,4 +1,4 @@ -@using UI.JsInterops +@using UI.Application.Scripting @inject IJSRuntime JSRuntime @implements IAsyncDisposable @@ -13,22 +13,22 @@ private IJSObjectReference? _semanticModule; private IJSObjectReference? _hoverModule; - private DotNetObjectReference? _completionReference; - private DotNetObjectReference? _diagnosticsReference; - private DotNetObjectReference? _semanticReference; - private DotNetObjectReference? _hoverReference; + private DotNetObjectReference? _completionReference; + private DotNetObjectReference? _diagnosticsReference; + private DotNetObjectReference? _semanticReference; + private DotNetObjectReference? _hoverReference; [Parameter] - public MonacoCompletionProvider? CompletionProvider { get; set; } + public IMonacoCompletionProvider? CompletionProvider { get; set; } [Parameter] - public MonacoDiagnosticsProvider? DiagnosticsProvider { get; set; } + public IMonacoDiagnosticsProvider? DiagnosticsProvider { get; set; } [Parameter] - public MonacoSemanticTokensProvider? SemanticTokensProvider { get; set; } + public IMonacoSemanticTokensProvider? SemanticTokensProvider { get; set; } [Parameter] - public MonacoHoverProvider? HoverProvider { get; set; } + public IMonacoHoverProvider? HoverProvider { get; set; } public Task GetScript() => _editor.GetValue(); @@ -138,3 +138,4 @@ }; } } + diff --git a/UI/Pages/Shared/ScriptEditor.razor.css b/UI/Components/ScriptEditor.razor.css similarity index 100% rename from UI/Pages/Shared/ScriptEditor.razor.css rename to UI/Components/ScriptEditor.razor.css diff --git a/UI/Pages/Shared/SpinnerSize.cs b/UI/Components/SpinnerSize.cs similarity index 55% rename from UI/Pages/Shared/SpinnerSize.cs rename to UI/Components/SpinnerSize.cs index c9de554c..01c3c7f5 100644 --- a/UI/Pages/Shared/SpinnerSize.cs +++ b/UI/Components/SpinnerSize.cs @@ -1,7 +1,7 @@ -namespace UI.Pages.Shared; - -public enum SpinnerSize -{ - Small, - Large -} +namespace UI.Components; + +public enum SpinnerSize +{ + Small, + Large +} diff --git a/UI/Pages/Shared/SyringeLoader.razor b/UI/Components/SyringeLoader.razor similarity index 100% rename from UI/Pages/Shared/SyringeLoader.razor rename to UI/Components/SyringeLoader.razor diff --git a/UI/Pages/Shared/SyringeLoader.razor.css b/UI/Components/SyringeLoader.razor.css similarity index 100% rename from UI/Pages/Shared/SyringeLoader.razor.css rename to UI/Components/SyringeLoader.razor.css diff --git a/UI/Backend/Extensions/StatusExtensions.cs b/UI/Domain/Execution/StatusExtensions.cs similarity index 97% rename from UI/Backend/Extensions/StatusExtensions.cs rename to UI/Domain/Execution/StatusExtensions.cs index c9ced341..0711228b 100644 --- a/UI/Backend/Extensions/StatusExtensions.cs +++ b/UI/Domain/Execution/StatusExtensions.cs @@ -1,6 +1,6 @@ -using Ares.Datamodel; +using Ares.Datamodel; -namespace UI.Backend.Extensions; +namespace UI.Domain.Execution; public static class StatusExtensions { diff --git a/UI/Backend/Extensions/ExperimentTemplateExtensions.cs b/UI/Domain/Experiments/ExperimentTemplateExtensions.cs similarity index 92% rename from UI/Backend/Extensions/ExperimentTemplateExtensions.cs rename to UI/Domain/Experiments/ExperimentTemplateExtensions.cs index 08196bf8..ff8c9c05 100644 --- a/UI/Backend/Extensions/ExperimentTemplateExtensions.cs +++ b/UI/Domain/Experiments/ExperimentTemplateExtensions.cs @@ -1,6 +1,6 @@ -using Ares.Datamodel.Templates; +using Ares.Datamodel.Templates; -namespace UI.Backend.Extensions; +namespace UI.Domain.Experiments; internal static class ExperimentTemplateExtensions { diff --git a/UI/Pages/Shared/Settings/Analysis/AnalyzerConfigEditView.razor b/UI/Features/Analyzing/Settings/AnalyzerConfigEditView.razor similarity index 84% rename from UI/Pages/Shared/Settings/Analysis/AnalyzerConfigEditView.razor rename to UI/Features/Analyzing/Settings/AnalyzerConfigEditView.razor index ebf73156..ab025887 100644 --- a/UI/Pages/Shared/Settings/Analysis/AnalyzerConfigEditView.razor +++ b/UI/Features/Analyzing/Settings/AnalyzerConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Backend/ViewModels/Settings/Analysis/AnalyzerConfigEditViewModel.cs b/UI/Features/Analyzing/Settings/AnalyzerConfigEditViewModel.cs similarity index 92% rename from UI/Backend/ViewModels/Settings/Analysis/AnalyzerConfigEditViewModel.cs rename to UI/Features/Analyzing/Settings/AnalyzerConfigEditViewModel.cs index 52af8d6b..67a33d32 100644 --- a/UI/Backend/ViewModels/Settings/Analysis/AnalyzerConfigEditViewModel.cs +++ b/UI/Features/Analyzing/Settings/AnalyzerConfigEditViewModel.cs @@ -1,8 +1,8 @@ -using Ares.Datamodel.Analyzing; +using Ares.Datamodel.Analyzing; using Ares.Services; using ReactiveUI; -namespace UI.Backend.ViewModels.Settings.Analysis; +namespace UI.Features.Analyzing.Settings; public class AnalyzerConfigEditViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsEditorView.razor b/UI/Features/Analyzing/Settings/AnalyzerSettingsEditorView.razor similarity index 84% rename from UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsEditorView.razor rename to UI/Features/Analyzing/Settings/AnalyzerSettingsEditorView.razor index 9ca50956..76d6f618 100644 --- a/UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsEditorView.razor +++ b/UI/Features/Analyzing/Settings/AnalyzerSettingsEditorView.razor @@ -1,5 +1,5 @@ -@using Ares.Datamodel -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using Ares.Datamodel +@inherits ReactiveUI.Blazor.ReactiveComponentBase diff --git a/UI/Backend/ViewModels/Settings/Analysis/AnalyzerSettingsEditorViewModel.cs b/UI/Features/Analyzing/Settings/AnalyzerSettingsEditorViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Settings/Analysis/AnalyzerSettingsEditorViewModel.cs rename to UI/Features/Analyzing/Settings/AnalyzerSettingsEditorViewModel.cs index 427ba9a8..3e5680ca 100644 --- a/UI/Backend/ViewModels/Settings/Analysis/AnalyzerSettingsEditorViewModel.cs +++ b/UI/Features/Analyzing/Settings/AnalyzerSettingsEditorViewModel.cs @@ -1,10 +1,10 @@ -using Ares.Datamodel; +using Ares.Datamodel; using Ares.Datamodel.Analyzing; using Ares.Services; using Grpc.Core; using ReactiveUI; -namespace UI.Backend.ViewModels.Settings.Analysis; +namespace UI.Features.Analyzing.Settings; public class AnalyzerSettingsEditorViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsList.razor b/UI/Features/Analyzing/Settings/AnalyzerSettingsList.razor similarity index 93% rename from UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsList.razor rename to UI/Features/Analyzing/Settings/AnalyzerSettingsList.razor index 69ec813c..d232fbfe 100644 --- a/UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsList.razor +++ b/UI/Features/Analyzing/Settings/AnalyzerSettingsList.razor @@ -1,8 +1,7 @@ -@page "/settings/analysis" +@page "/settings/analysis" @using Ares.Services @using Google.Protobuf.WellKnownTypes -@using UI.Backend.ViewModels.Settings.Analysis -@using UI.Pages.Shared.Settings.Analysis.Layout +@using UI.Features.Analyzing.Settings.Layout @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @layout AnalyzerSettingsLayout diff --git a/UI/Backend/ViewModels/Settings/Analysis/AnalyzerSettingsListViewModel.cs b/UI/Features/Analyzing/Settings/AnalyzerSettingsListViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Settings/Analysis/AnalyzerSettingsListViewModel.cs rename to UI/Features/Analyzing/Settings/AnalyzerSettingsListViewModel.cs index 2cce139d..4655e1e4 100644 --- a/UI/Backend/ViewModels/Settings/Analysis/AnalyzerSettingsListViewModel.cs +++ b/UI/Features/Analyzing/Settings/AnalyzerSettingsListViewModel.cs @@ -1,11 +1,11 @@ -using Ares.Datamodel.Analyzing; +using Ares.Datamodel.Analyzing; using Ares.Services; using Google.Protobuf.WellKnownTypes; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Services.Notification; +using UI.Application.Notifications; -namespace UI.Backend.ViewModels.Settings.Analysis; +namespace UI.Features.Analyzing.Settings; public partial class AnalyzerSettingsListViewModel : ReactiveObject { @@ -62,3 +62,5 @@ private async Task OnAnalyzerRemoved() [Reactive] public partial IEnumerable? SettingsViewModels { get; private set; } } + + diff --git a/UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsView.razor b/UI/Features/Analyzing/Settings/AnalyzerSettingsView.razor similarity index 92% rename from UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsView.razor rename to UI/Features/Analyzing/Settings/AnalyzerSettingsView.razor index b80f4260..609ca0dc 100644 --- a/UI/Pages/Shared/Settings/Analysis/AnalyzerSettingsView.razor +++ b/UI/Features/Analyzing/Settings/AnalyzerSettingsView.razor @@ -1,7 +1,7 @@ -@using Ares.Services +@using Ares.Services @using Google.Protobuf.WellKnownTypes -@using UI.Pages.Shared.Settings.Analysis.Layout -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Features.Analyzing.Settings.Layout +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService - -Project - - - - -@code { - void ShowContextMenuWithItems(MouseEventArgs args) - { - ContextMenuService.Open(args, - new List { - new(){ Text = "Context menu item 1", Value = 1 }, - new(){ Text = "Context menu item 2", Value = 2 }, - new(){ Text = "Context menu item 3", Value = 3 }, - }, OnMenuItemClick); - } - - void OnMenuItemClick(MenuItemEventArgs args) - { - ContextMenuService.Close(); - } +@page "/profile" +@inject ContextMenuService ContextMenuService +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase + +Project + + + + +@code { + void ShowContextMenuWithItems(MouseEventArgs args) + { + ContextMenuService.Open(args, + new List { + new(){ Text = "Context menu item 1", Value = 1 }, + new(){ Text = "Context menu item 2", Value = 2 }, + new(){ Text = "Context menu item 3", Value = 3 }, + }, OnMenuItemClick); + } + + void OnMenuItemClick(MenuItemEventArgs args) + { + ContextMenuService.Close(); + } } \ No newline at end of file diff --git a/UI/Backend/ViewModels/ProfileViewModel.cs b/UI/Features/Auth/ProfileViewModel.cs similarity index 65% rename from UI/Backend/ViewModels/ProfileViewModel.cs rename to UI/Features/Auth/ProfileViewModel.cs index 27a7cc1f..cfd322c2 100644 --- a/UI/Backend/ViewModels/ProfileViewModel.cs +++ b/UI/Features/Auth/ProfileViewModel.cs @@ -1,7 +1,7 @@ -using ReactiveUI; - -namespace UI.Backend.ViewModels; - -public class ProfileViewModel : ReactiveObject -{ +using ReactiveUI; + +namespace UI.Features.Auth; + +public class ProfileViewModel : ReactiveObject +{ } \ No newline at end of file diff --git a/UI/Pages/Auth/Register.razor b/UI/Features/Auth/Register.razor similarity index 100% rename from UI/Pages/Auth/Register.razor rename to UI/Features/Auth/Register.razor diff --git a/UI/Services/CampaignEdit/CampaignEditContext.cs b/UI/Features/CampaignEdit/CampaignEditContext.cs similarity index 85% rename from UI/Services/CampaignEdit/CampaignEditContext.cs rename to UI/Features/CampaignEdit/CampaignEditContext.cs index ee92a511..37416b59 100644 --- a/UI/Services/CampaignEdit/CampaignEditContext.cs +++ b/UI/Features/CampaignEdit/CampaignEditContext.cs @@ -1,14 +1,14 @@ -using Ares.Datamodel.Templates; - -namespace UI.Services.CampaignEdit; - -/// -/// Kind of a hacky way to give certain designers a way to reference any needed info from the campaign itself. -/// Ex.: Campaign template has plannable parameters, but the command parameter designer does not have a way to get to those -/// unless they are passed through all the way from the campaign template to those designers or if we use something -/// like this context in scoped context (one scope can edit one campaign at a time anyways). -/// -public class CampaignEditContext -{ - public CampaignTemplate? CurrentlyEditingCampaign { get; set; } -} +using Ares.Datamodel.Templates; + +namespace UI.Features.CampaignEdit; + +/// +/// Kind of a hacky way to give certain designers a way to reference any necessary info from the campaign itself. +/// Ex.: Campaign template has plannable parameters, but the command parameter designer does not have a way to get to those +/// unless they are passed through all the way from the campaign template to those designers or if we use something +/// like this context in scoped context (one scope can edit one campaign at a time anyways). +/// +public class CampaignEditContext +{ + public CampaignTemplate? CurrentlyEditingCampaign { get; set; } +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/AnalyzerInputDesignerVmFactory.cs b/UI/Features/CampaignEdit/Factories/AnalyzerInputDesignerVmFactory.cs similarity index 80% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/AnalyzerInputDesignerVmFactory.cs rename to UI/Features/CampaignEdit/Factories/AnalyzerInputDesignerVmFactory.cs index ea5b104d..af5df381 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/AnalyzerInputDesignerVmFactory.cs +++ b/UI/Features/CampaignEdit/Factories/AnalyzerInputDesignerVmFactory.cs @@ -1,7 +1,9 @@ using Ares.Datamodel.Templates; using Ares.Services; +using AnalyzerDesignerViewModel=UI.Features.CampaignEdit.ViewModels.AnalyzerDesignerViewModel; +using CommandDesignerViewModel=UI.Features.CampaignEdit.ViewModels.CommandDesignerViewModel; -namespace UI.Backend.ViewModels.Automation.CampaignEdit.Factories; +namespace UI.Features.CampaignEdit.Factories; public class AnalyzerInputDesignerVmFactory { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CloseoutDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/CloseoutDesignerFactory.cs similarity index 89% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CloseoutDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/CloseoutDesignerFactory.cs index 160eb94d..556f1888 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CloseoutDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/CloseoutDesignerFactory.cs @@ -1,9 +1,9 @@ using Ares.Datamodel.Templates; using Ares.Services; using Radzen; -using UI.Backend.ViewModels.Factories; +using CloseoutDesignerViewModel=UI.Features.CampaignEdit.ViewModels.CloseoutDesignerViewModel; -namespace UI.Backend.ViewModels.Automation.CampaignEdit.Factories; +namespace UI.Features.CampaignEdit.Factories; public class CloseoutDesignerFactory { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CommandDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/CommandDesignerFactory.cs similarity index 88% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CommandDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/CommandDesignerFactory.cs index 6e9d9bff..13e45e5f 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CommandDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/CommandDesignerFactory.cs @@ -1,28 +1,28 @@ -using Ares.Datamodel.Templates; -using Ares.Services.Device; -using UI.Backend.ViewModels.Automation.CampaignEdit; - -namespace UI.Backend.ViewModels.Factories; - -public class CommandDesignerFactory -{ - private readonly CommandParameterDesignerFactory _commandParameterDesignerFactory; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly MetadataPickerFactory _metadataPickerFactory; - - public CommandDesignerFactory( - MetadataPickerFactory metadataPickerFactory, - CommandParameterDesignerFactory commandParameterDesignerFactory, - AresDevices.AresDevicesClient devicesClient) - { - _metadataPickerFactory = metadataPickerFactory; - _commandParameterDesignerFactory = commandParameterDesignerFactory; - _devicesClient = devicesClient; - } - - public CommandDesignerViewModel Create() - => new CommandDesignerViewModel(_commandParameterDesignerFactory, _metadataPickerFactory, _devicesClient); - - public CommandDesignerViewModel Create(CommandTemplate existingTemplate) - => new CommandDesignerViewModel(existingTemplate, _commandParameterDesignerFactory, _metadataPickerFactory, _devicesClient); -} +using Ares.Datamodel.Templates; +using Ares.Services.Device; +using CommandDesignerViewModel=UI.Features.CampaignEdit.ViewModels.CommandDesignerViewModel; + +namespace UI.Features.CampaignEdit.Factories; + +public class CommandDesignerFactory +{ + private readonly CommandParameterDesignerFactory _commandParameterDesignerFactory; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly MetadataPickerFactory _metadataPickerFactory; + + public CommandDesignerFactory( + MetadataPickerFactory metadataPickerFactory, + CommandParameterDesignerFactory commandParameterDesignerFactory, + AresDevices.AresDevicesClient devicesClient) + { + _metadataPickerFactory = metadataPickerFactory; + _commandParameterDesignerFactory = commandParameterDesignerFactory; + _devicesClient = devicesClient; + } + + public CommandDesignerViewModel Create() + => new CommandDesignerViewModel(_commandParameterDesignerFactory, _metadataPickerFactory, _devicesClient); + + public CommandDesignerViewModel Create(CommandTemplate existingTemplate) + => new CommandDesignerViewModel(existingTemplate, _commandParameterDesignerFactory, _metadataPickerFactory, _devicesClient); +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CommandParameterDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/CommandParameterDesignerFactory.cs similarity index 79% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CommandParameterDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/CommandParameterDesignerFactory.cs index 2e1b1f0f..60e4c505 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/CommandParameterDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/CommandParameterDesignerFactory.cs @@ -1,22 +1,21 @@ -using Ares.Datamodel.Templates; -using UI.Backend.Helpers; -using UI.Backend.ViewModels.Automation.CampaignEdit; -using UI.Services.CampaignEdit; - -namespace UI.Backend.ViewModels.Factories; - -public class CommandParameterDesignerFactory -{ - private readonly CampaignEditContext _campaignEditContext; - private readonly UnitCategoryHelper _unitCategoryHelper; - - public CommandParameterDesignerFactory(CampaignEditContext campaignEditContext, UnitCategoryHelper unitCategoryHelper) - { - _campaignEditContext = campaignEditContext; - _unitCategoryHelper = unitCategoryHelper; - } - - public CommandParameterDesignerViewModel Create(ParameterMetadata existingParameterMetadata) => new(existingParameterMetadata, _unitCategoryHelper, _campaignEditContext.CurrentlyEditingCampaign?.PlannableParameters); - - public CommandParameterDesignerViewModel Create(Parameter existingParameter) => new(existingParameter, _unitCategoryHelper, _campaignEditContext.CurrentlyEditingCampaign?.PlannableParameters); -} +using Ares.Datamodel.Templates; +using UI.Features.CampaignEdit.ViewModels; +using UI.Components.Formatting; + +namespace UI.Features.CampaignEdit.Factories; + +public class CommandParameterDesignerFactory +{ + private readonly CampaignEditContext _campaignEditContext; + private readonly UnitCategoryHelper _unitCategoryHelper; + + public CommandParameterDesignerFactory(CampaignEditContext campaignEditContext, UnitCategoryHelper unitCategoryHelper) + { + _campaignEditContext = campaignEditContext; + _unitCategoryHelper = unitCategoryHelper; + } + + public CommandParameterDesignerViewModel Create(ParameterMetadata existingParameterMetadata) => new(existingParameterMetadata, _unitCategoryHelper, _campaignEditContext.CurrentlyEditingCampaign?.PlannableParameters); + + public CommandParameterDesignerViewModel Create(Parameter existingParameter) => new(existingParameter, _unitCategoryHelper, _campaignEditContext.CurrentlyEditingCampaign?.PlannableParameters); +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/ExperimentDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/ExperimentDesignerFactory.cs similarity index 89% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/ExperimentDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/ExperimentDesignerFactory.cs index 97160b43..c9efba6c 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/ExperimentDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/ExperimentDesignerFactory.cs @@ -1,35 +1,35 @@ -using Ares.Datamodel.Templates; -using Ares.Services; -using Radzen; -using UI.Backend.ViewModels.Automation.CampaignEdit; - -namespace UI.Backend.ViewModels.Factories; - -public class ExperimentDesignerFactory -{ - private readonly AresAutomation.AresAutomationClient _automationClient; - private readonly StepDesignerFactory _stepDesignerFactory; - private readonly AresValidation.AresValidationClient _validationClient; - private readonly NotificationService _notificationService; - - public ExperimentDesignerFactory(StepDesignerFactory stepDesignerFactory, - AresAutomation.AresAutomationClient automationClient, - AresValidation.AresValidationClient validationClient, - NotificationService notificationService) - { - _stepDesignerFactory = stepDesignerFactory; - _automationClient = automationClient; - _validationClient = validationClient; - _notificationService = notificationService; - } - - public ExperimentDesignerViewModel Create() => new(_stepDesignerFactory, _automationClient, _validationClient, _notificationService); - - public ExperimentDesignerViewModel Create(ExperimentTemplate? existingTemplate) - { - if(existingTemplate is null) - return Create(); - - return new ExperimentDesignerViewModel(existingTemplate, _stepDesignerFactory, _automationClient, _validationClient, _notificationService); - } -} +using Ares.Datamodel.Templates; +using Ares.Services; +using Radzen; +using ExperimentDesignerViewModel=UI.Features.CampaignEdit.ViewModels.ExperimentDesignerViewModel; + +namespace UI.Features.CampaignEdit.Factories; + +public class ExperimentDesignerFactory +{ + private readonly AresAutomation.AresAutomationClient _automationClient; + private readonly StepDesignerFactory _stepDesignerFactory; + private readonly AresValidation.AresValidationClient _validationClient; + private readonly NotificationService _notificationService; + + public ExperimentDesignerFactory(StepDesignerFactory stepDesignerFactory, + AresAutomation.AresAutomationClient automationClient, + AresValidation.AresValidationClient validationClient, + NotificationService notificationService) + { + _stepDesignerFactory = stepDesignerFactory; + _automationClient = automationClient; + _validationClient = validationClient; + _notificationService = notificationService; + } + + public ExperimentDesignerViewModel Create() => new(_stepDesignerFactory, _automationClient, _validationClient, _notificationService); + + public ExperimentDesignerViewModel Create(ExperimentTemplate? existingTemplate) + { + if(existingTemplate is null) + return Create(); + + return new ExperimentDesignerViewModel(existingTemplate, _stepDesignerFactory, _automationClient, _validationClient, _notificationService); + } +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/MetadataPickerFactory.cs b/UI/Features/CampaignEdit/Factories/MetadataPickerFactory.cs similarity index 81% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/MetadataPickerFactory.cs rename to UI/Features/CampaignEdit/Factories/MetadataPickerFactory.cs index 86c7f17d..47817982 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/MetadataPickerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/MetadataPickerFactory.cs @@ -1,26 +1,26 @@ -using Ares.Datamodel.Templates; -using Ares.Services.Device; -using UI.Backend.ViewModels.Automation.CampaignEdit; - -namespace UI.Backend.ViewModels.Factories; - -public class MetadataPickerFactory -{ - private readonly AresDevices.AresDevicesClient _devicesClient; - - public MetadataPickerFactory(AresDevices.AresDevicesClient devicesClient) - { - _devicesClient = devicesClient; - } - - public MetadataPickerViewModel Create() - => new MetadataPickerViewModel(_devicesClient); - - public MetadataPickerViewModel Create(CommandMetadata? existingMetadata) - { - if (existingMetadata is null) - return Create(); - - return new MetadataPickerViewModel(existingMetadata, _devicesClient); - } -} +using Ares.Datamodel.Templates; +using Ares.Services.Device; +using MetadataPickerViewModel=UI.Features.CampaignEdit.ViewModels.MetadataPickerViewModel; + +namespace UI.Features.CampaignEdit.Factories; + +public class MetadataPickerFactory +{ + private readonly AresDevices.AresDevicesClient _devicesClient; + + public MetadataPickerFactory(AresDevices.AresDevicesClient devicesClient) + { + _devicesClient = devicesClient; + } + + public MetadataPickerViewModel Create() + => new MetadataPickerViewModel(_devicesClient); + + public MetadataPickerViewModel Create(CommandMetadata? existingMetadata) + { + if (existingMetadata is null) + return Create(); + + return new MetadataPickerViewModel(existingMetadata, _devicesClient); + } +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/ParameterEditorFactory.cs b/UI/Features/CampaignEdit/Factories/ParameterEditorFactory.cs similarity index 72% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/ParameterEditorFactory.cs rename to UI/Features/CampaignEdit/Factories/ParameterEditorFactory.cs index c97a8b00..9e55a8e1 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/ParameterEditorFactory.cs +++ b/UI/Features/CampaignEdit/Factories/ParameterEditorFactory.cs @@ -1,19 +1,19 @@ -using Ares.Datamodel.Templates; -using UI.Backend.Helpers; -using UI.Backend.ViewModels.Automation.CampaignEdit; - -namespace UI.Backend.ViewModels.Factories; - -public class ParameterEditorFactory -{ - private readonly UnitCategoryHelper _unitCategoryHelper; - - public ParameterEditorFactory(UnitCategoryHelper unitCategoryHelper) - { - _unitCategoryHelper = unitCategoryHelper; - } - - public ParameterEditorViewModel Create(IEnumerable availableOutputs) => new(_unitCategoryHelper, availableOutputs); - - public ParameterEditorViewModel Create(ParameterMetadata existingParameterMetadata, IEnumerable availableOutputs) => new(existingParameterMetadata, availableOutputs, _unitCategoryHelper); -} +using Ares.Datamodel.Templates; +using UI.Components.Formatting; +using ParameterEditorViewModel = UI.Features.CampaignEdit.ViewModels.ParameterEditorViewModel; + +namespace UI.Features.CampaignEdit.Factories; + +public class ParameterEditorFactory +{ + private readonly UnitCategoryHelper _unitCategoryHelper; + + public ParameterEditorFactory(UnitCategoryHelper unitCategoryHelper) + { + _unitCategoryHelper = unitCategoryHelper; + } + + public ParameterEditorViewModel Create(IEnumerable availableOutputs) => new(_unitCategoryHelper, availableOutputs); + + public ParameterEditorViewModel Create(ParameterMetadata existingParameterMetadata, IEnumerable availableOutputs) => new(existingParameterMetadata, availableOutputs, _unitCategoryHelper); +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/PlannableParameterDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/PlannableParameterDesignerFactory.cs similarity index 82% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/PlannableParameterDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/PlannableParameterDesignerFactory.cs index eb963bd6..a94907ad 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/PlannableParameterDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/PlannableParameterDesignerFactory.cs @@ -1,17 +1,17 @@ -using Ares.Datamodel.Templates; -using UI.Backend.ViewModels.Automation.CampaignEdit; - -namespace UI.Backend.ViewModels.Factories; - -public class PlannableParameterDesignerFactory -{ - private readonly ParameterEditorFactory _parameterEditorFactory; - - public PlannableParameterDesignerFactory(ParameterEditorFactory parameterEditorFactory) - { - _parameterEditorFactory = parameterEditorFactory; - } - - public PlannableParameterDesignerViewModel Create(IEnumerable existingParameterMeta, ExperimentTemplate? experimentTemplate) - => new(existingParameterMeta, experimentTemplate, _parameterEditorFactory); -} +using Ares.Datamodel.Templates; +using UI.Features.CampaignEdit.ViewModels; + +namespace UI.Features.CampaignEdit.Factories; + +public class PlannableParameterDesignerFactory +{ + private readonly ParameterEditorFactory _parameterEditorFactory; + + public PlannableParameterDesignerFactory(ParameterEditorFactory parameterEditorFactory) + { + _parameterEditorFactory = parameterEditorFactory; + } + + public PlannableParameterDesignerViewModel Create(IEnumerable existingParameterMeta, ExperimentTemplate? experimentTemplate) + => new(existingParameterMeta, experimentTemplate, _parameterEditorFactory); +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/PlanningDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/PlanningDesignerFactory.cs similarity index 79% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/PlanningDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/PlanningDesignerFactory.cs index f2a2735c..8050fcb3 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/PlanningDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/PlanningDesignerFactory.cs @@ -1,26 +1,28 @@ -using Ares.Datamodel.Templates; -using Ares.Services; -using Google.Protobuf.WellKnownTypes; -using UI.Backend.ViewModels.Automation.CampaignEdit; -using UI.Services.Notification; - - -namespace UI.Backend.ViewModels.Factories; - -public class PlanningDesignerFactory -{ - private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _client; - private readonly INotificationReceivingService _notificationService; - - public PlanningDesignerFactory(AresPlannerManagementService.AresPlannerManagementServiceClient client, INotificationReceivingService notificationService) - { - _client = client; - _notificationService = notificationService; - } - - public async Task Create(CampaignTemplate template) - { - var planners = await _client.GetAllPlannersAsync(new Empty()); - return new PlanningViewModel(template, planners.Planners, _client, _notificationService); - } -} +using Ares.Datamodel.Templates; +using Ares.Services; +using Google.Protobuf.WellKnownTypes; +using UI.Features.CampaignEdit.ViewModels; +using UI.Application.Notifications; + + +namespace UI.Features.CampaignEdit.Factories; + +public class PlanningDesignerFactory +{ + private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _client; + private readonly INotificationReceivingService _notificationService; + + public PlanningDesignerFactory(AresPlannerManagementService.AresPlannerManagementServiceClient client, INotificationReceivingService notificationService) + { + _client = client; + _notificationService = notificationService; + } + + public async Task Create(CampaignTemplate template) + { + var planners = await _client.GetAllPlannersAsync(new Empty()); + return new PlanningViewModel(template, planners.Planners, _client, _notificationService); + } +} + + diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/StartupDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/StartupDesignerFactory.cs similarity index 89% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/StartupDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/StartupDesignerFactory.cs index ab240ea3..f705d064 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/StartupDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/StartupDesignerFactory.cs @@ -1,9 +1,9 @@ using Ares.Datamodel.Templates; using Ares.Services; using Radzen; -using UI.Backend.ViewModels.Factories; +using StartupDesignerViewModel=UI.Features.CampaignEdit.ViewModels.StartupDesignerViewModel; -namespace UI.Backend.ViewModels.Automation.CampaignEdit.Factories; +namespace UI.Features.CampaignEdit.Factories; public class StartupDesignerFactory { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/StepDesignerFactory.cs b/UI/Features/CampaignEdit/Factories/StepDesignerFactory.cs similarity index 81% rename from UI/Backend/ViewModels/Automation/CampaignEdit/Factories/StepDesignerFactory.cs rename to UI/Features/CampaignEdit/Factories/StepDesignerFactory.cs index da7015fd..3ff0e793 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/Factories/StepDesignerFactory.cs +++ b/UI/Features/CampaignEdit/Factories/StepDesignerFactory.cs @@ -1,20 +1,20 @@ -using Ares.Datamodel.Templates; -using UI.Backend.ViewModels.Automation.CampaignEdit; - -namespace UI.Backend.ViewModels.Factories; - -public class StepDesignerFactory -{ - private readonly CommandDesignerFactory _commandDesignerFactory; - - public StepDesignerFactory(CommandDesignerFactory commandDesignerFactory) - { - _commandDesignerFactory = commandDesignerFactory; - } - - public StepDesignerViewModel Create() - => new StepDesignerViewModel(_commandDesignerFactory); - - public StepDesignerViewModel Create(StepTemplate existingTemplate) - => new StepDesignerViewModel(existingTemplate, _commandDesignerFactory); -} +using Ares.Datamodel.Templates; +using UI.Features.CampaignEdit.ViewModels; + +namespace UI.Features.CampaignEdit.Factories; + +public class StepDesignerFactory +{ + private readonly CommandDesignerFactory _commandDesignerFactory; + + public StepDesignerFactory(CommandDesignerFactory commandDesignerFactory) + { + _commandDesignerFactory = commandDesignerFactory; + } + + public StepDesignerViewModel Create() + => new StepDesignerViewModel(_commandDesignerFactory); + + public StepDesignerViewModel Create(StepTemplate existingTemplate) + => new StepDesignerViewModel(existingTemplate, _commandDesignerFactory); +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/AnalyzerDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/AnalyzerDesignerViewModel.cs similarity index 98% rename from UI/Backend/ViewModels/Automation/CampaignEdit/AnalyzerDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/AnalyzerDesignerViewModel.cs index c433cca1..301915bd 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/AnalyzerDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/AnalyzerDesignerViewModel.cs @@ -7,7 +7,7 @@ using ReactiveUI; using ReactiveUI.SourceGenerators; -namespace UI.Backend.ViewModels.Automation.CampaignEdit; +namespace UI.Features.CampaignEdit.ViewModels; public partial class AnalyzerDesignerViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignCloseoutViewModel.cs b/UI/Features/CampaignEdit/ViewModels/CampaignCloseoutViewModel.cs similarity index 63% rename from UI/Backend/ViewModels/Automation/CampaignEdit/CampaignCloseoutViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/CampaignCloseoutViewModel.cs index b4fd1729..a7347117 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignCloseoutViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/CampaignCloseoutViewModel.cs @@ -1,6 +1,6 @@ using ReactiveUI; -namespace UI.Backend.ViewModels.Automation.CampaignEdit +namespace UI.Features.CampaignEdit.ViewModels { public class CampaignCloseoutViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/CampaignDesignerViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Automation/CampaignEdit/CampaignDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/CampaignDesignerViewModel.cs index 7e4ae828..1a340661 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/CampaignDesignerViewModel.cs @@ -1,166 +1,164 @@ -using Ares.Datamodel.Templates; -using Ares.Services; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using UI.Backend.ViewModels.Automation.CampaignEdit.Factories; -using UI.Backend.ViewModels.Factories; -using UI.Services.CampaignEdit; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public partial class CampaignDesignerViewModel : ReactiveObject -{ - private readonly AresAutomation.AresAutomationClient _automationClient; - private readonly CampaignEditContext _editContext; - private readonly ExperimentDesignerFactory _experimentDesignerFactory; - private readonly StartupDesignerFactory _startupDesignerFactory; - private readonly CloseoutDesignerFactory _closeoutDesignerFactory; - private readonly PlannableParameterDesignerFactory _plannableParameterDesignerFactory; - private readonly PlanningDesignerFactory _planningDesignerFactory; - private CampaignTemplate _campaignTemplate = null!; - readonly AnalyzerInputDesignerVmFactory _analyzerInputDesignerFactory; - - public CampaignDesignerViewModel( - AresAutomation.AresAutomationClient automationClient, - ExperimentDesignerFactory experimentDesignerFactory, - StartupDesignerFactory startupDesignerFactory, - CloseoutDesignerFactory closeoutDesignerFactory, - PlanningDesignerFactory planningDesignerFactory, - AnalyzerInputDesignerVmFactory analyzingDesignerFactory, - PlannableParameterDesignerFactory plannableParameterDesignerFactory, - CampaignEditContext editContext, - IConfiguration configuration) - { - _analyzerInputDesignerFactory = analyzingDesignerFactory; - _automationClient = automationClient; - _experimentDesignerFactory = experimentDesignerFactory; - _startupDesignerFactory = startupDesignerFactory; - _closeoutDesignerFactory = closeoutDesignerFactory; - _planningDesignerFactory = planningDesignerFactory; - _plannableParameterDesignerFactory = plannableParameterDesignerFactory; - _editContext = editContext; - IsCreatingCampaign = false; - IsNotCreatingCampaign = true; - Placeholder = "New Campaign 1"; - - CampaignTemplate = new CampaignTemplate - { - Name = Placeholder, - UniqueId = Guid.NewGuid().ToString(), - ExperimentTemplate = new ExperimentTemplate() { UniqueId = Guid.NewGuid().ToString(), Name = "New Experiment" } - }; - } - - [Reactive] public partial bool IsCreatingCampaign { get; set; } - - [Reactive] public partial bool IsNotCreatingCampaign { get; set; } - - [Reactive] public partial string Placeholder { get; set; } - - public PlannableParameterDesignerViewModel? PlannableParameterDesigner { get; private set; } - - public ExperimentDesignerViewModel? ExperimentDesigner { get; private set; } - - public StartupDesignerViewModel? StartupDesigner { get; private set; } - - public CloseoutDesignerViewModel? CloseoutDesigner { get; private set; } - - public PlanningViewModel? PlanningDesigner { get; private set; } - - public AnalyzerDesignerViewModel? AnalyzerDesignerViewModel { get; private set; } - - public string CampaignName { get; set; } = "Unnamed Campaign"; - - public CampaignTemplate CampaignTemplate - { - private get => _campaignTemplate; - - set - { - _campaignTemplate = value; - _editContext.CurrentlyEditingCampaign = value; - _ = Init(value); - } - } - - [Reactive] public partial bool CreationIsErrorFree { get; set; } - - [Reactive] public partial string? CreationErrorText { get; set; } - - private async Task Init(CampaignTemplate campaignTemplate) - { - CampaignName = campaignTemplate.Name; - PlannableParameterDesigner = _plannableParameterDesignerFactory.Create(campaignTemplate.PlannableParameters, campaignTemplate.ExperimentTemplate); - ExperimentDesigner = _experimentDesignerFactory.Create(campaignTemplate.ExperimentTemplate); - StartupDesigner = _startupDesignerFactory.Create(campaignTemplate.StartupTemplate); - CloseoutDesigner = _closeoutDesignerFactory.Create(campaignTemplate.CloseoutTemplate); - PlanningDesigner = await _planningDesignerFactory.Create(campaignTemplate); - var commandDesigners = ExperimentDesigner?.StepDesigners?.SelectMany(sd => sd.CommandDesigners) ?? []; - var startupDesigners = StartupDesigner?.StartupStepDesigners?.SelectMany(ssd => ssd.CommandDesigners) ?? []; - - if(CampaignTemplate.ExperimentTemplate is not null) - { - AnalyzerDesignerViewModel = _analyzerInputDesignerFactory.Create(campaignTemplate.ExperimentTemplate, commandDesigners, startupDesigners); - } - } - - public CampaignTemplate Save() - { - CampaignTemplate.Name = CampaignName; - CampaignTemplate.PlannableParameters.Clear(); - CampaignTemplate.PlannableParameters.AddRange(PlannableParameterDesigner?.Save() ?? Array.Empty()); - CampaignTemplate.ExperimentTemplate = null; - - if(StartupDesigner is not null) - CampaignTemplate.StartupTemplate = StartupDesigner.Save(); - - if(ExperimentDesigner is not null) - CampaignTemplate.ExperimentTemplate = ExperimentDesigner.Save(); - - if(CloseoutDesigner is not null) - CampaignTemplate.CloseoutTemplate = CloseoutDesigner.Save(); - - //Try Save Instead? - PlannableParameterDesigner?.Save(); - PlanningDesigner?.Save(); - AnalyzerDesignerViewModel?.Save(); - return CampaignTemplate; - } - - public async Task SelectCampaignById(Guid campaignId) - { - var request = new CampaignRequest - { - UniqueId = campaignId.ToString(), - }; - - CampaignTemplate = await _automationClient.GetSingleCampaignAsync(request); - } - - public async Task Update() - { - if(string.IsNullOrEmpty(CampaignName)) - CampaignName = Placeholder; - - var isUpdating = await _automationClient.CampaignExistsAsync(new CampaignRequest { UniqueId = CampaignTemplate.UniqueId }); - - var nameChanged = CampaignTemplate.Name != CampaignName; - if(nameChanged) - { - var campaignExists = await _automationClient.CampaignExistsAsync(new CampaignRequest { CampaignName = CampaignName }); - if(campaignExists.Value) - { - CreationErrorText = "Campaign Name " + CampaignName + " Already Exists!"; - CreationIsErrorFree = false; - return; - } - } - - var template = Save(); - if(isUpdating.Value) - await _automationClient.UpdateCampaignAsync(new AddOrUpdateCampaignRequest() { Template = template }); - - else - await _automationClient.AddCampaignAsync(new AddOrUpdateCampaignRequest() { Template = template }); - } -} +using Ares.Datamodel.Templates; +using Ares.Services; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using UI.Features.CampaignEdit.Factories; + +namespace UI.Features.CampaignEdit.ViewModels; + +public partial class CampaignDesignerViewModel : ReactiveObject +{ + private readonly AresAutomation.AresAutomationClient _automationClient; + private readonly CampaignEditContext _editContext; + private readonly ExperimentDesignerFactory _experimentDesignerFactory; + private readonly StartupDesignerFactory _startupDesignerFactory; + private readonly CloseoutDesignerFactory _closeoutDesignerFactory; + private readonly PlannableParameterDesignerFactory _plannableParameterDesignerFactory; + private readonly PlanningDesignerFactory _planningDesignerFactory; + private CampaignTemplate _campaignTemplate = null!; + readonly AnalyzerInputDesignerVmFactory _analyzerInputDesignerFactory; + + public CampaignDesignerViewModel( + AresAutomation.AresAutomationClient automationClient, + ExperimentDesignerFactory experimentDesignerFactory, + StartupDesignerFactory startupDesignerFactory, + CloseoutDesignerFactory closeoutDesignerFactory, + PlanningDesignerFactory planningDesignerFactory, + AnalyzerInputDesignerVmFactory analyzingDesignerFactory, + PlannableParameterDesignerFactory plannableParameterDesignerFactory, + CampaignEditContext editContext, + IConfiguration configuration) + { + _analyzerInputDesignerFactory = analyzingDesignerFactory; + _automationClient = automationClient; + _experimentDesignerFactory = experimentDesignerFactory; + _startupDesignerFactory = startupDesignerFactory; + _closeoutDesignerFactory = closeoutDesignerFactory; + _planningDesignerFactory = planningDesignerFactory; + _plannableParameterDesignerFactory = plannableParameterDesignerFactory; + _editContext = editContext; + IsCreatingCampaign = false; + IsNotCreatingCampaign = true; + Placeholder = "New Campaign 1"; + + CampaignTemplate = new CampaignTemplate + { + Name = Placeholder, + UniqueId = Guid.NewGuid().ToString(), + ExperimentTemplate = new ExperimentTemplate() { UniqueId = Guid.NewGuid().ToString(), Name = "New Experiment" } + }; + } + + [Reactive] public partial bool IsCreatingCampaign { get; set; } + + [Reactive] public partial bool IsNotCreatingCampaign { get; set; } + + [Reactive] public partial string Placeholder { get; set; } + + public PlannableParameterDesignerViewModel? PlannableParameterDesigner { get; private set; } + + public ExperimentDesignerViewModel? ExperimentDesigner { get; private set; } + + public StartupDesignerViewModel? StartupDesigner { get; private set; } + + public CloseoutDesignerViewModel? CloseoutDesigner { get; private set; } + + public PlanningViewModel? PlanningDesigner { get; private set; } + + public AnalyzerDesignerViewModel? AnalyzerDesignerViewModel { get; private set; } + + public string CampaignName { get; set; } = "Unnamed Campaign"; + + public CampaignTemplate CampaignTemplate + { + private get => _campaignTemplate; + + set + { + _campaignTemplate = value; + _editContext.CurrentlyEditingCampaign = value; + _ = Init(value); + } + } + + [Reactive] public partial bool CreationIsErrorFree { get; set; } + + [Reactive] public partial string? CreationErrorText { get; set; } + + private async Task Init(CampaignTemplate campaignTemplate) + { + CampaignName = campaignTemplate.Name; + PlannableParameterDesigner = _plannableParameterDesignerFactory.Create(campaignTemplate.PlannableParameters, campaignTemplate.ExperimentTemplate); + ExperimentDesigner = _experimentDesignerFactory.Create(campaignTemplate.ExperimentTemplate); + StartupDesigner = _startupDesignerFactory.Create(campaignTemplate.StartupTemplate); + CloseoutDesigner = _closeoutDesignerFactory.Create(campaignTemplate.CloseoutTemplate); + PlanningDesigner = await _planningDesignerFactory.Create(campaignTemplate); + var commandDesigners = ExperimentDesigner?.StepDesigners?.SelectMany(sd => sd.CommandDesigners) ?? []; + var startupDesigners = StartupDesigner?.StartupStepDesigners?.SelectMany(ssd => ssd.CommandDesigners) ?? []; + + if(CampaignTemplate.ExperimentTemplate is not null) + { + AnalyzerDesignerViewModel = _analyzerInputDesignerFactory.Create(campaignTemplate.ExperimentTemplate, commandDesigners, startupDesigners); + } + } + + public CampaignTemplate Save() + { + CampaignTemplate.Name = CampaignName; + CampaignTemplate.PlannableParameters.Clear(); + CampaignTemplate.PlannableParameters.AddRange(PlannableParameterDesigner?.Save() ?? Array.Empty()); + CampaignTemplate.ExperimentTemplate = null; + + if(StartupDesigner is not null) + CampaignTemplate.StartupTemplate = StartupDesigner.Save(); + + if(ExperimentDesigner is not null) + CampaignTemplate.ExperimentTemplate = ExperimentDesigner.Save(); + + if(CloseoutDesigner is not null) + CampaignTemplate.CloseoutTemplate = CloseoutDesigner.Save(); + + //Try Save Instead? + PlannableParameterDesigner?.Save(); + PlanningDesigner?.Save(); + AnalyzerDesignerViewModel?.Save(); + return CampaignTemplate; + } + + public async Task SelectCampaignById(Guid campaignId) + { + var request = new CampaignRequest + { + UniqueId = campaignId.ToString(), + }; + + CampaignTemplate = await _automationClient.GetSingleCampaignAsync(request); + } + + public async Task Update() + { + if(string.IsNullOrEmpty(CampaignName)) + CampaignName = Placeholder; + + var isUpdating = await _automationClient.CampaignExistsAsync(new CampaignRequest { UniqueId = CampaignTemplate.UniqueId }); + + var nameChanged = CampaignTemplate.Name != CampaignName; + if(nameChanged) + { + var campaignExists = await _automationClient.CampaignExistsAsync(new CampaignRequest { CampaignName = CampaignName }); + if(campaignExists.Value) + { + CreationErrorText = "Campaign Name " + CampaignName + " Already Exists!"; + CreationIsErrorFree = false; + return; + } + } + + var template = Save(); + if(isUpdating.Value) + await _automationClient.UpdateCampaignAsync(new AddOrUpdateCampaignRequest() { Template = template }); + + else + await _automationClient.AddCampaignAsync(new AddOrUpdateCampaignRequest() { Template = template }); + } +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignListViewModel.cs b/UI/Features/CampaignEdit/ViewModels/CampaignListViewModel.cs similarity index 92% rename from UI/Backend/ViewModels/Automation/CampaignEdit/CampaignListViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/CampaignListViewModel.cs index 61e8d2b9..0abadf7c 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignListViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/CampaignListViewModel.cs @@ -1,36 +1,36 @@ -using DynamicData; -using ReactiveUI; -using System.Collections.ObjectModel; -using Ares.Datamodel.Templates; -using Ares.Services; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public class CampaignListViewModel : ReactiveObject -{ - private readonly AresAutomation.AresAutomationClient _automationClient; - public readonly ObservableCollection Templates = []; - - public CampaignListViewModel(AresAutomation.AresAutomationClient automationClient) - { - _automationClient = automationClient; - } - - public async Task GetFullCampaignTemplate(string campaignId) - { - return await _automationClient.GetSingleCampaignAsync(new CampaignRequest { UniqueId = campaignId }); - } - - public async Task RefreshCampaigns() - { - var campaigns = await _automationClient.GetAllCampaignsAsync(new GetAllCampaignsRequest()); - Templates.Clear(); - Templates.AddRange(campaigns.Campaigns); - } - - public async Task DeleteCampaign(Guid campaignId) - { - await _automationClient.RemoveCampaignAsync(new CampaignRequest { UniqueId = campaignId.ToString() }); - await RefreshCampaigns(); - } -} +using DynamicData; +using ReactiveUI; +using System.Collections.ObjectModel; +using Ares.Datamodel.Templates; +using Ares.Services; + +namespace UI.Features.CampaignEdit.ViewModels; + +public class CampaignListViewModel : ReactiveObject +{ + private readonly AresAutomation.AresAutomationClient _automationClient; + public readonly ObservableCollection Templates = []; + + public CampaignListViewModel(AresAutomation.AresAutomationClient automationClient) + { + _automationClient = automationClient; + } + + public async Task GetFullCampaignTemplate(string campaignId) + { + return await _automationClient.GetSingleCampaignAsync(new CampaignRequest { UniqueId = campaignId }); + } + + public async Task RefreshCampaigns() + { + var campaigns = await _automationClient.GetAllCampaignsAsync(new GetAllCampaignsRequest()); + Templates.Clear(); + Templates.AddRange(campaigns.Campaigns); + } + + public async Task DeleteCampaign(Guid campaignId) + { + await _automationClient.RemoveCampaignAsync(new CampaignRequest { UniqueId = campaignId.ToString() }); + await RefreshCampaigns(); + } +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignStartupViewModel.cs b/UI/Features/CampaignEdit/ViewModels/CampaignStartupViewModel.cs similarity index 63% rename from UI/Backend/ViewModels/Automation/CampaignEdit/CampaignStartupViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/CampaignStartupViewModel.cs index 40dc21cb..de857ad6 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/CampaignStartupViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/CampaignStartupViewModel.cs @@ -1,6 +1,6 @@ using ReactiveUI; -namespace UI.Backend.ViewModels.Automation.CampaignEdit +namespace UI.Features.CampaignEdit.ViewModels { public class CampaignStartupViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/CloseoutDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/CloseoutDesignerViewModel.cs similarity index 97% rename from UI/Backend/ViewModels/Automation/CampaignEdit/CloseoutDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/CloseoutDesignerViewModel.cs index 6bfb100f..7cb259b8 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/CloseoutDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/CloseoutDesignerViewModel.cs @@ -3,9 +3,9 @@ using Radzen; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Backend.ViewModels.Factories; +using UI.Features.CampaignEdit.Factories; -namespace UI.Backend.ViewModels.Automation.CampaignEdit; +namespace UI.Features.CampaignEdit.ViewModels; public partial class CloseoutDesignerViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/CommandDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/CommandDesignerViewModel.cs similarity index 98% rename from UI/Backend/ViewModels/Automation/CampaignEdit/CommandDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/CommandDesignerViewModel.cs index b5bd100e..a11e5c87 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/CommandDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/CommandDesignerViewModel.cs @@ -3,9 +3,9 @@ using Ares.Services.Device; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Backend.ViewModels.Factories; +using UI.Features.CampaignEdit.Factories; -namespace UI.Backend.ViewModels.Automation.CampaignEdit; +namespace UI.Features.CampaignEdit.ViewModels; public partial class CommandDesignerViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/CommandParameterDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/CommandParameterDesignerViewModel.cs similarity index 97% rename from UI/Backend/ViewModels/Automation/CampaignEdit/CommandParameterDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/CommandParameterDesignerViewModel.cs index 0616b9c2..653114c4 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/CommandParameterDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/CommandParameterDesignerViewModel.cs @@ -1,9 +1,9 @@ -using Ares.Datamodel; +using Ares.Datamodel; using Ares.Datamodel.Templates; using ReactiveUI; -using UI.Backend.Helpers; +using UI.Components.Formatting; -namespace UI.Backend.ViewModels.Automation.CampaignEdit; +namespace UI.Features.CampaignEdit.ViewModels; public class CommandParameterDesignerViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/ExperimentDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/ExperimentDesignerViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Automation/CampaignEdit/ExperimentDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/ExperimentDesignerViewModel.cs index 44077efc..29df4418 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/ExperimentDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/ExperimentDesignerViewModel.cs @@ -1,138 +1,138 @@ -using Ares.Datamodel.Templates; -using Ares.Services; -using Radzen; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using UI.Backend.ViewModels.Factories; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public partial class ExperimentDesignerViewModel : ReactiveObject -{ - private readonly StepDesignerFactory _stepDesignerFactory; - private readonly AresValidation.AresValidationClient _validationClient; - private ExperimentTemplate _experimentTemplate = null!; - readonly AresAutomation.AresAutomationClient _automationClient; - private readonly NotificationService _notificationService; - - public ExperimentDesignerViewModel(StepDesignerFactory stepDesignerFactory, - AresAutomation.AresAutomationClient automationClient, - AresValidation.AresValidationClient validationClient, - NotificationService notificationService) - { - _automationClient = automationClient; - _stepDesignerFactory = stepDesignerFactory; - _validationClient = validationClient; - _notificationService = notificationService; - Name = "Unnamed Template"; - ExperimentTemplate = new ExperimentTemplate - { - UniqueId = Guid.NewGuid().ToString(), - Name = "Experiment Template" - }; - } - - public ExperimentDesignerViewModel(ExperimentTemplate existingTemplate, - StepDesignerFactory stepDesignerFactory, - AresAutomation.AresAutomationClient automationClient, - AresValidation.AresValidationClient validationClient, - NotificationService notificationService) : this(stepDesignerFactory, automationClient, validationClient, notificationService) - { - ExperimentTemplate = existingTemplate; - } - - private void Init(ExperimentTemplate existingTemplate) - { - Name = existingTemplate.Name; - StepDesigners = existingTemplate.StepTemplates.Select(template => _stepDesignerFactory.Create(template)).OrderBy(model => model.Index).ToList(); - if(existingTemplate.StepTemplates.Select(step => step.CommandTemplates.Select(cmd => cmd.UserOutputKeyMap)).Any()) - { - var commandDesigners = StepDesigners.SelectMany(model => model.CommandDesigners).Where(model => model.CommandTemplate.UserOutputKeyMap.Any()); - foreach(var designer in commandDesigners) - { - designer.OutputProvider = true; - } - - ExperimentOutputProviderCommand = commandDesigners.Select(designer => designer.CommandTemplate); - } - } - - public ExperimentTemplate Save() - { - if(StepDesigners is null) - { - _notificationService.Notify(NotificationSeverity.Error, "A Step Designer was null! No data saved."); - return ExperimentTemplate; - } - - ExperimentTemplate.Name = Name; - ExperimentTemplate.StepTemplates.Clear(); - ExperimentTemplate.StepTemplates.AddRange(StepDesigners.Select(designer => designer.Save())); - return ExperimentTemplate; - } - - public StepDesignerViewModel AddStep() - { - var stepDesigner = _stepDesignerFactory.Create(); - stepDesigner.Index = StepDesigners.Count; - StepDesigners.Add(stepDesigner); - return stepDesigner; - } - - public void RemoveStep(StepDesignerViewModel vm) - { - if(StepDesigners is not null) - { - StepDesigners.Remove(vm); - ReindexSteps(); - } - } - - public void MoveStepDesignerUp(StepDesignerViewModel vm) - { - if(StepDesigners is null || vm.Index == 0) - return; - - StepDesigners.RemoveAt(vm.Index); - StepDesigners.Insert(vm.Index - 1, vm); - ReindexSteps(); - } - - public void MoveStepDesignerDown(StepDesignerViewModel vm) - { - if(StepDesigners is null || vm.Index == StepDesigners.Count - 1) - return; - - StepDesigners.RemoveAt(vm.Index); - StepDesigners.Insert(vm.Index + 1, vm); - ReindexSteps(); - } - - private void ReindexSteps() - { - if(StepDesigners is not null) - { - var idx = 0; - foreach(var stepDesigner in StepDesigners) - stepDesigner.Index = idx++; - } - } - - public ExperimentTemplate ExperimentTemplate - { - private get => _experimentTemplate; - - set - { - _experimentTemplate = value; - Init(value); - } - } - - [Reactive] - public partial string Name { get; set; } - - public IList StepDesigners { get; private set; } = []; - - public IEnumerable? ExperimentOutputProviderCommand { get; set; } -} +using Ares.Datamodel.Templates; +using Ares.Services; +using Radzen; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using UI.Features.CampaignEdit.Factories; + +namespace UI.Features.CampaignEdit.ViewModels; + +public partial class ExperimentDesignerViewModel : ReactiveObject +{ + private readonly StepDesignerFactory _stepDesignerFactory; + private readonly AresValidation.AresValidationClient _validationClient; + private ExperimentTemplate _experimentTemplate = null!; + readonly AresAutomation.AresAutomationClient _automationClient; + private readonly NotificationService _notificationService; + + public ExperimentDesignerViewModel(StepDesignerFactory stepDesignerFactory, + AresAutomation.AresAutomationClient automationClient, + AresValidation.AresValidationClient validationClient, + NotificationService notificationService) + { + _automationClient = automationClient; + _stepDesignerFactory = stepDesignerFactory; + _validationClient = validationClient; + _notificationService = notificationService; + Name = "Unnamed Template"; + ExperimentTemplate = new ExperimentTemplate + { + UniqueId = Guid.NewGuid().ToString(), + Name = "Experiment Template" + }; + } + + public ExperimentDesignerViewModel(ExperimentTemplate existingTemplate, + StepDesignerFactory stepDesignerFactory, + AresAutomation.AresAutomationClient automationClient, + AresValidation.AresValidationClient validationClient, + NotificationService notificationService) : this(stepDesignerFactory, automationClient, validationClient, notificationService) + { + ExperimentTemplate = existingTemplate; + } + + private void Init(ExperimentTemplate existingTemplate) + { + Name = existingTemplate.Name; + StepDesigners = existingTemplate.StepTemplates.Select(template => _stepDesignerFactory.Create(template)).OrderBy(model => model.Index).ToList(); + if(existingTemplate.StepTemplates.Select(step => step.CommandTemplates.Select(cmd => cmd.UserOutputKeyMap)).Any()) + { + var commandDesigners = StepDesigners.SelectMany(model => model.CommandDesigners).Where(model => model.CommandTemplate.UserOutputKeyMap.Any()); + foreach(var designer in commandDesigners) + { + designer.OutputProvider = true; + } + + ExperimentOutputProviderCommand = commandDesigners.Select(designer => designer.CommandTemplate); + } + } + + public ExperimentTemplate Save() + { + if(StepDesigners is null) + { + _notificationService.Notify(NotificationSeverity.Error, "A Step Designer was null! No data saved."); + return ExperimentTemplate; + } + + ExperimentTemplate.Name = Name; + ExperimentTemplate.StepTemplates.Clear(); + ExperimentTemplate.StepTemplates.AddRange(StepDesigners.Select(designer => designer.Save())); + return ExperimentTemplate; + } + + public StepDesignerViewModel AddStep() + { + var stepDesigner = _stepDesignerFactory.Create(); + stepDesigner.Index = StepDesigners.Count; + StepDesigners.Add(stepDesigner); + return stepDesigner; + } + + public void RemoveStep(StepDesignerViewModel vm) + { + if(StepDesigners is not null) + { + StepDesigners.Remove(vm); + ReindexSteps(); + } + } + + public void MoveStepDesignerUp(StepDesignerViewModel vm) + { + if(StepDesigners is null || vm.Index == 0) + return; + + StepDesigners.RemoveAt(vm.Index); + StepDesigners.Insert(vm.Index - 1, vm); + ReindexSteps(); + } + + public void MoveStepDesignerDown(StepDesignerViewModel vm) + { + if(StepDesigners is null || vm.Index == StepDesigners.Count - 1) + return; + + StepDesigners.RemoveAt(vm.Index); + StepDesigners.Insert(vm.Index + 1, vm); + ReindexSteps(); + } + + private void ReindexSteps() + { + if(StepDesigners is not null) + { + var idx = 0; + foreach(var stepDesigner in StepDesigners) + stepDesigner.Index = idx++; + } + } + + public ExperimentTemplate ExperimentTemplate + { + private get => _experimentTemplate; + + set + { + _experimentTemplate = value; + Init(value); + } + } + + [Reactive] + public partial string Name { get; set; } + + public IList StepDesigners { get; private set; } = []; + + public IEnumerable? ExperimentOutputProviderCommand { get; set; } +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/MetadataPickerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/MetadataPickerViewModel.cs similarity index 97% rename from UI/Backend/ViewModels/Automation/CampaignEdit/MetadataPickerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/MetadataPickerViewModel.cs index 83f13da8..c63aefd8 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/MetadataPickerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/MetadataPickerViewModel.cs @@ -5,7 +5,7 @@ using ReactiveUI; using ReactiveUI.SourceGenerators; -namespace UI.Backend.ViewModels.Automation.CampaignEdit; +namespace UI.Features.CampaignEdit.ViewModels; public partial class MetadataPickerViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/ParameterEditorViewModel.cs b/UI/Features/CampaignEdit/ViewModels/ParameterEditorViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Automation/CampaignEdit/ParameterEditorViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/ParameterEditorViewModel.cs index eb44862d..58f219de 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/ParameterEditorViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/ParameterEditorViewModel.cs @@ -1,248 +1,248 @@ -using Ares.Datamodel; -using Ares.Datamodel.Extensions; -using Ares.Datamodel.Factories; -using Ares.Datamodel.Templates; -using Humanizer; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using UI.Backend.Helpers; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public partial class ParameterEditorViewModel : ReactiveObject -{ - private readonly UnitCategoryHelper _unitHelper; - private string? _category; - private double _maximum; - private double _minimum; - private string? _name; - private ParameterMetadata _parameterMetadata = null!; - private string? _unit; - private AresValue? _initialValue; - private bool _hasInitialValue; - private AresDataType _dataType; - - public ParameterEditorViewModel(UnitCategoryHelper unitHelper, IEnumerable availableOutputs) - { - _unitHelper = unitHelper; - - ParameterMetadata = new ParameterMetadata - { - UniqueId = Guid.NewGuid().ToString(), - Name = "Param", - Schema = AresSchemaBuilder.Entry(AresDataType.UnspecifiedType).Build(), - Constraints = - { - new Limits() - } - }; - - AvailableOutputs = availableOutputs.ToArray(); - CategoryOptions = []; - UnitOptions = []; - } - - public ParameterEditorViewModel(ParameterMetadata existingMetadata, IEnumerable availableOutputs, UnitCategoryHelper unitHelper) - { - CategoryOptions = []; - UnitOptions = []; - _unitHelper = unitHelper; - ParameterMetadata = existingMetadata; - AvailableOutputs = availableOutputs.ToArray(); - HasInitialValue = existingMetadata.InitialValue != null; - InitialValue = existingMetadata.InitialValue; - } - - public ParameterMetadata ParameterMetadata - { - get => _parameterMetadata; - - set - { - _parameterMetadata = value; - Init(value); - } - } - - public AresDataType DataType - { - get => _dataType; - - set - { - _dataType = value; - - if(HasInitialValue) - InitialValue = AresValueHelper.CreateDefault(value); - } - } - - public string? Category - { - get => _category; - - set - { - var changed = _category != value; - this.RaiseAndSetIfChanged(ref _category, value); - if(value is null || !changed) - return; - - UnitOptions = UnitCategoryHelper.GetTypes(value.Dehumanize()).Select(s => s.Humanize()).ToList(); - - if(value == "None" || UnitOptions.Count == 0) - UnitOptions.Add("None"); - - Unit = UnitOptions.First(); - } - } - - [Reactive] - public partial List CategoryOptions { get; private set; } - - [Reactive] - public partial List UnitOptions { get; private set; } - - public string? Unit - { - get => _unit; - - set - { - this.RaiseAndSetIfChanged(ref _unit, value); - if(value == null) - return; - - Category = _unitHelper.GetCategoryForUnit(_unit.Dehumanize()).Humanize(); - } - } - - public string? Name - { - get => _name; - - set => this.RaiseAndSetIfChanged(ref _name, value); - } - - public double Minimum - { - get => _minimum; - - set => this.RaiseAndSetIfChanged(ref _minimum, value); - } - - public double Maximum - { - get => _maximum; - - set => this.RaiseAndSetIfChanged(ref _maximum, value); - } - - private void Init(ParameterMetadata meta) - { - LockInParams(meta); - Name = meta.Name; - DataType = meta.Schema.Type; - foreach(var metaConstraint in meta.Constraints) - { - Minimum = metaConstraint.Minimum; - Maximum = metaConstraint.Maximum; - } - - SelectedAchievedOutput = string.IsNullOrEmpty(meta.OutputName) ? null : meta.OutputName; - HasAchievedValue = SelectedAchievedOutput is not null; - } - - public ParameterMetadata Save() - { - if(ParameterMetadata.Constraints.Count == 0) - ParameterMetadata.Constraints.Add(new Limits()); - - ParameterMetadata.Schema = AresSchemaBuilder.Entry(DataType).Build(); - ParameterMetadata.Name = Name; - ParameterMetadata.InitialValue ??= InitialValue; - - if(DataType == AresDataType.Number) - { - ParameterMetadata.Constraints[0].Maximum = (float)Maximum; - ParameterMetadata.Constraints[0].Minimum = (float)Minimum; - ParameterMetadata.Unit = Unit.Dehumanize(); - } - - // TODO Should technically set to null but protobuf complains. Maybe investigate further? - ParameterMetadata.OutputName = HasAchievedValue ? SelectedAchievedOutput : ""; - - return ParameterMetadata; - } - - private bool IsValid(AresValue value) - { - switch(ParameterMetadata.Schema.Type) - { - case AresDataType.Number: - if(!value.HasNumberValue) - return false; - - if(ParameterMetadata.Constraints.Count == 0) - return true; - - return ParameterMetadata.Constraints.Any(limits => value.NumberValue >= limits.Minimum && value.NumberValue <= limits.Maximum); - - case AresDataType.String: - return value.HasStringValue; - - default: - return true; - } - } - - public bool HasAchievedValue { get; set; } - - public bool HasInitialValue - { - get => _hasInitialValue; - - set - { - _hasInitialValue = value; - if(value) - InitialValue = AresValueHelper.CreateDefault(DataType); - - else - InitialValue = null; - } - } - - public string? SelectedAchievedOutput { get; set; } - - public string[]? AvailableOutputs { get; set; } - - private void LockInParams(ParameterMetadata meta) - { - var categories = _unitHelper.UnitCategories.Select(s => s.Humanize()).ToList(); - categories.Add("None"); - CategoryOptions = categories; - var unit = meta.Unit; - var cat = _unitHelper.GetCategoryForUnit(unit); - UnitOptions = UnitCategoryHelper.GetTypes(cat).Select(s => s.Humanize()).ToList(); - Category = cat.Humanize(); - Unit = unit.Humanize(); - } - - public AresValue? InitialValue - { - get => _initialValue; - - set - { - this.RaiseAndSetIfChanged(ref _initialValue, value); - if(value is null) - return; - - IsInitialValueValid = IsValid(value); - } - } - - public bool IsInitialValueValid { get; set; } - -} +using Ares.Datamodel; +using Ares.Datamodel.Extensions; +using Ares.Datamodel.Factories; +using Ares.Datamodel.Templates; +using Humanizer; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using UI.Components.Formatting; + +namespace UI.Features.CampaignEdit.ViewModels; + +public partial class ParameterEditorViewModel : ReactiveObject +{ + private readonly UnitCategoryHelper _unitHelper; + private string? _category; + private double _maximum; + private double _minimum; + private string? _name; + private ParameterMetadata _parameterMetadata = null!; + private string? _unit; + private AresValue? _initialValue; + private bool _hasInitialValue; + private AresDataType _dataType; + + public ParameterEditorViewModel(UnitCategoryHelper unitHelper, IEnumerable availableOutputs) + { + _unitHelper = unitHelper; + + ParameterMetadata = new ParameterMetadata + { + UniqueId = Guid.NewGuid().ToString(), + Name = "Param", + Schema = AresSchemaBuilder.Entry(AresDataType.UnspecifiedType).Build(), + Constraints = + { + new Limits() + } + }; + + AvailableOutputs = availableOutputs.ToArray(); + CategoryOptions = []; + UnitOptions = []; + } + + public ParameterEditorViewModel(ParameterMetadata existingMetadata, IEnumerable availableOutputs, UnitCategoryHelper unitHelper) + { + CategoryOptions = []; + UnitOptions = []; + _unitHelper = unitHelper; + ParameterMetadata = existingMetadata; + AvailableOutputs = availableOutputs.ToArray(); + HasInitialValue = existingMetadata.InitialValue != null; + InitialValue = existingMetadata.InitialValue; + } + + public ParameterMetadata ParameterMetadata + { + get => _parameterMetadata; + + set + { + _parameterMetadata = value; + Init(value); + } + } + + public AresDataType DataType + { + get => _dataType; + + set + { + _dataType = value; + + if(HasInitialValue) + InitialValue = AresValueHelper.CreateDefault(value); + } + } + + public string? Category + { + get => _category; + + set + { + var changed = _category != value; + this.RaiseAndSetIfChanged(ref _category, value); + if(value is null || !changed) + return; + + UnitOptions = UnitCategoryHelper.GetTypes(value.Dehumanize()).Select(s => s.Humanize()).ToList(); + + if(value == "None" || UnitOptions.Count == 0) + UnitOptions.Add("None"); + + Unit = UnitOptions.First(); + } + } + + [Reactive] + public partial List CategoryOptions { get; private set; } + + [Reactive] + public partial List UnitOptions { get; private set; } + + public string? Unit + { + get => _unit; + + set + { + this.RaiseAndSetIfChanged(ref _unit, value); + if(value == null) + return; + + Category = _unitHelper.GetCategoryForUnit(_unit.Dehumanize()).Humanize(); + } + } + + public string? Name + { + get => _name; + + set => this.RaiseAndSetIfChanged(ref _name, value); + } + + public double Minimum + { + get => _minimum; + + set => this.RaiseAndSetIfChanged(ref _minimum, value); + } + + public double Maximum + { + get => _maximum; + + set => this.RaiseAndSetIfChanged(ref _maximum, value); + } + + private void Init(ParameterMetadata meta) + { + LockInParams(meta); + Name = meta.Name; + DataType = meta.Schema.Type; + foreach(var metaConstraint in meta.Constraints) + { + Minimum = metaConstraint.Minimum; + Maximum = metaConstraint.Maximum; + } + + SelectedAchievedOutput = string.IsNullOrEmpty(meta.OutputName) ? null : meta.OutputName; + HasAchievedValue = SelectedAchievedOutput is not null; + } + + public ParameterMetadata Save() + { + if(ParameterMetadata.Constraints.Count == 0) + ParameterMetadata.Constraints.Add(new Limits()); + + ParameterMetadata.Schema = AresSchemaBuilder.Entry(DataType).Build(); + ParameterMetadata.Name = Name; + ParameterMetadata.InitialValue ??= InitialValue; + + if(DataType == AresDataType.Number) + { + ParameterMetadata.Constraints[0].Maximum = (float)Maximum; + ParameterMetadata.Constraints[0].Minimum = (float)Minimum; + ParameterMetadata.Unit = Unit.Dehumanize(); + } + + // TODO Should technically set to null but protobuf complains. Maybe investigate further? + ParameterMetadata.OutputName = HasAchievedValue ? SelectedAchievedOutput : ""; + + return ParameterMetadata; + } + + private bool IsValid(AresValue value) + { + switch(ParameterMetadata.Schema.Type) + { + case AresDataType.Number: + if(!value.HasNumberValue) + return false; + + if(ParameterMetadata.Constraints.Count == 0) + return true; + + return ParameterMetadata.Constraints.Any(limits => value.NumberValue >= limits.Minimum && value.NumberValue <= limits.Maximum); + + case AresDataType.String: + return value.HasStringValue; + + default: + return true; + } + } + + public bool HasAchievedValue { get; set; } + + public bool HasInitialValue + { + get => _hasInitialValue; + + set + { + _hasInitialValue = value; + if(value) + InitialValue = AresValueHelper.CreateDefault(DataType); + + else + InitialValue = null; + } + } + + public string? SelectedAchievedOutput { get; set; } + + public string[]? AvailableOutputs { get; set; } + + private void LockInParams(ParameterMetadata meta) + { + var categories = _unitHelper.UnitCategories.Select(s => s.Humanize()).ToList(); + categories.Add("None"); + CategoryOptions = categories; + var unit = meta.Unit; + var cat = _unitHelper.GetCategoryForUnit(unit); + UnitOptions = UnitCategoryHelper.GetTypes(cat).Select(s => s.Humanize()).ToList(); + Category = cat.Humanize(); + Unit = unit.Humanize(); + } + + public AresValue? InitialValue + { + get => _initialValue; + + set + { + this.RaiseAndSetIfChanged(ref _initialValue, value); + if(value is null) + return; + + IsInitialValueValid = IsValid(value); + } + } + + public bool IsInitialValueValid { get; set; } + +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/PlannableParameterDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/PlannableParameterDesignerViewModel.cs similarity index 88% rename from UI/Backend/ViewModels/Automation/CampaignEdit/PlannableParameterDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/PlannableParameterDesignerViewModel.cs index 34c4e94a..931fc881 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/PlannableParameterDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/PlannableParameterDesignerViewModel.cs @@ -1,57 +1,57 @@ -using System.Collections.ObjectModel; -using Ares.Datamodel.Templates; -using DynamicData; -using ReactiveUI; -using UI.Backend.Extensions; -using UI.Backend.ViewModels.Factories; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public class PlannableParameterDesignerViewModel : ReactiveObject -{ - private readonly ParameterEditorFactory _editorFactory; - private IEnumerable _parameterMetadata = []; - private ExperimentTemplate? _experimentTemplate; - - public PlannableParameterDesignerViewModel(IEnumerable existingMetadata, ExperimentTemplate? experimentTemplate, ParameterEditorFactory editorFactory) - { - _editorFactory = editorFactory; - _experimentTemplate = experimentTemplate; - ParameterMetadata = existingMetadata; - } - - public ObservableCollection ParameterEditors { get; } = []; - - public IEnumerable ParameterMetadata - { - private get => _parameterMetadata; - - set - { - _parameterMetadata = value; - Init(value); - } - } - - public IEnumerable Save() - { - return ParameterEditors.Select(model => model.Save()); - } - - private void Init(IEnumerable paramMetadata) - { - var outputs = _experimentTemplate?.GetAllOutputCommands().SelectMany(cmd => cmd.UserOutputKeyMap.Select(m => m.Value)).ToArray() ?? []; - ParameterEditors.AddRange(paramMetadata.Select(metadata => _editorFactory.Create(metadata, outputs))); - } - - public void Create() - { - var outputs = _experimentTemplate?.GetAllOutputCommands().SelectMany(cmd => cmd.UserOutputKeyMap.Select(m => m.Value)).ToArray() ?? []; - ParameterEditors.Add(_editorFactory.Create(outputs)); - } - - public void Remove(ParameterEditorViewModel vm) - { - ParameterEditors.Remove(vm); - } -} +using System.Collections.ObjectModel; +using Ares.Datamodel.Templates; +using DynamicData; +using ReactiveUI; +using UI.Features.CampaignEdit.Factories; +using UI.Domain.Experiments; + +namespace UI.Features.CampaignEdit.ViewModels; + +public class PlannableParameterDesignerViewModel : ReactiveObject +{ + private readonly ParameterEditorFactory _editorFactory; + private IEnumerable _parameterMetadata = []; + private ExperimentTemplate? _experimentTemplate; + + public PlannableParameterDesignerViewModel(IEnumerable existingMetadata, ExperimentTemplate? experimentTemplate, ParameterEditorFactory editorFactory) + { + _editorFactory = editorFactory; + _experimentTemplate = experimentTemplate; + ParameterMetadata = existingMetadata; + } + + public ObservableCollection ParameterEditors { get; } = []; + + public IEnumerable ParameterMetadata + { + private get => _parameterMetadata; + + set + { + _parameterMetadata = value; + Init(value); + } + } + + public IEnumerable Save() + { + return ParameterEditors.Select(model => model.Save()); + } + + private void Init(IEnumerable paramMetadata) + { + var outputs = _experimentTemplate?.GetAllOutputCommands().SelectMany(cmd => cmd.UserOutputKeyMap.Select(m => m.Value)).ToArray() ?? []; + ParameterEditors.AddRange(paramMetadata.Select(metadata => _editorFactory.Create(metadata, outputs))); + } + + public void Create() + { + var outputs = _experimentTemplate?.GetAllOutputCommands().SelectMany(cmd => cmd.UserOutputKeyMap.Select(m => m.Value)).ToArray() ?? []; + ParameterEditors.Add(_editorFactory.Create(outputs)); + } + + public void Remove(ParameterEditorViewModel vm) + { + ParameterEditors.Remove(vm); + } +} diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/PlannerAllocationEditorViewModel.cs b/UI/Features/CampaignEdit/ViewModels/PlannerAllocationEditorViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Automation/CampaignEdit/PlannerAllocationEditorViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/PlannerAllocationEditorViewModel.cs index 252ac871..1a0a43f2 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/PlannerAllocationEditorViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/PlannerAllocationEditorViewModel.cs @@ -1,107 +1,109 @@ -using Ares.Datamodel.Planning; -using Ares.Datamodel.Templates; -using Ares.Services; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using UI.Services.Notification; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public partial class PlannerAllocationEditorViewModel : ReactiveObject -{ - private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _plannerClient; - private readonly INotificationReceivingService _notificationService; - private PlannerServiceInfo? _selectedAdapter; - - public PlannerAllocationEditorViewModel(ParameterMetadata metadata, - PlannerServiceInfo? plannerInfo, - IEnumerable plannerAdapters, - AresPlannerManagementService.AresPlannerManagementServiceClient plannerClient, - INotificationReceivingService notificationService) - { - var plannerArray = plannerAdapters.ToArray(); - - ParameterMetadata = metadata; - PlannerServices = plannerArray; - _plannerClient = plannerClient; - _notificationService = notificationService; - - if(plannerInfo is not null) - { - SelectedService = plannerInfo; - SelectedPlannerOption = plannerInfo.Capabilities.AvailablePlanners.FirstOrDefault(p => p.PlannerName == metadata.PlannerName); - } - - PlannerOptions = Enumerable.Empty(); - } - - public PlannerAllocation? Save() - { - if(SelectedService is null) - return null; - - var allocation = new PlannerAllocation - { - Parameter = ParameterMetadata, - Planner = SelectedService, - UniqueId = Guid.NewGuid().ToString() - }; - - allocation.Parameter.PlannerName = SelectedPlannerOption?.PlannerName ?? SelectedService.Name; - allocation.Parameter.PlannerDescription = SelectedPlannerOption?.Description ?? SelectedService.Description; - - return allocation; - } - - public async Task UpdatePlannerOptions() - { - if(SelectedService is null) - return; - - var updatedInfo = await _plannerClient.GetInfoAsync(new PlannerInfoRequest { PlannerId = SelectedService.UniqueId }); - - if(updatedInfo.Info.Name != string.Empty) - { - PlannerOptions = updatedInfo.Info.Capabilities.AvailablePlanners; - - //Not all adapters will have multiple options, if not auto assign the value - if(PlannerOptions.Count() == 1) - SelectedPlannerOption = SelectedService.Capabilities.AvailablePlanners.First(); - } - - else - { - var notification = new AresNotification(); - notification.Title = "Assigned Planner Unavailable!"; - notification.Message = $"This template uses a planner that ARES no longer has a connection with. The template won't be usable until this is resolved."; - notification.NotificationSeverity = Severity.Warning; - notification.Loiter = true; - _notificationService.PushNotification(notification); - } - } - - public PlannerServiceInfo? SelectedService - { - get => _selectedAdapter; - - set - { - if(value is null || _selectedAdapter == value) - return; - - if(_selectedAdapter is not null) - SelectedPlannerOption = _selectedAdapter.Capabilities.AvailablePlanners.FirstOrDefault(); - - _selectedAdapter = value; - _ = UpdatePlannerOptions(); - } - } - - [Reactive] - public partial ParameterMetadata ParameterMetadata { get; set; } - public IEnumerable PlannerServices { get; } - [Reactive] - public partial IEnumerable PlannerOptions { get; set; } - [Reactive] - public partial Planner? SelectedPlannerOption { get; set; } -} +using Ares.Datamodel.Planning; +using Ares.Datamodel.Templates; +using Ares.Services; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using UI.Application.Notifications; + +namespace UI.Features.CampaignEdit.ViewModels; + +public partial class PlannerAllocationEditorViewModel : ReactiveObject +{ + private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _plannerClient; + private readonly INotificationReceivingService _notificationService; + private PlannerServiceInfo? _selectedAdapter; + + public PlannerAllocationEditorViewModel(ParameterMetadata metadata, + PlannerServiceInfo? plannerInfo, + IEnumerable plannerAdapters, + AresPlannerManagementService.AresPlannerManagementServiceClient plannerClient, + INotificationReceivingService notificationService) + { + var plannerArray = plannerAdapters.ToArray(); + + ParameterMetadata = metadata; + PlannerServices = plannerArray; + _plannerClient = plannerClient; + _notificationService = notificationService; + + if(plannerInfo is not null) + { + SelectedService = plannerInfo; + SelectedPlannerOption = plannerInfo.Capabilities.AvailablePlanners.FirstOrDefault(p => p.PlannerName == metadata.PlannerName); + } + + PlannerOptions = Enumerable.Empty(); + } + + public PlannerAllocation? Save() + { + if(SelectedService is null) + return null; + + var allocation = new PlannerAllocation + { + Parameter = ParameterMetadata, + Planner = SelectedService, + UniqueId = Guid.NewGuid().ToString() + }; + + allocation.Parameter.PlannerName = SelectedPlannerOption?.PlannerName ?? SelectedService.Name; + allocation.Parameter.PlannerDescription = SelectedPlannerOption?.Description ?? SelectedService.Description; + + return allocation; + } + + public async Task UpdatePlannerOptions() + { + if(SelectedService is null) + return; + + var updatedInfo = await _plannerClient.GetInfoAsync(new PlannerInfoRequest { PlannerId = SelectedService.UniqueId }); + + if(updatedInfo.Info.Name != string.Empty) + { + PlannerOptions = updatedInfo.Info.Capabilities.AvailablePlanners; + + //Not all adapters will have multiple options, if not auto assign the value + if(PlannerOptions.Count() == 1) + SelectedPlannerOption = SelectedService.Capabilities.AvailablePlanners.First(); + } + + else + { + var notification = new AresNotification(); + notification.Title = "Assigned Planner Unavailable!"; + notification.Message = $"This template uses a planner that ARES no longer has a connection with. The template won't be usable until this is resolved."; + notification.NotificationSeverity = Severity.Warning; + notification.Loiter = true; + _notificationService.PushNotification(notification); + } + } + + public PlannerServiceInfo? SelectedService + { + get => _selectedAdapter; + + set + { + if(value is null || _selectedAdapter == value) + return; + + if(_selectedAdapter is not null) + SelectedPlannerOption = _selectedAdapter.Capabilities.AvailablePlanners.FirstOrDefault(); + + _selectedAdapter = value; + _ = UpdatePlannerOptions(); + } + } + + [Reactive] + public partial ParameterMetadata ParameterMetadata { get; set; } + public IEnumerable PlannerServices { get; } + [Reactive] + public partial IEnumerable PlannerOptions { get; set; } + [Reactive] + public partial Planner? SelectedPlannerOption { get; set; } +} + + diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/PlanningViewModel.cs b/UI/Features/CampaignEdit/ViewModels/PlanningViewModel.cs similarity index 92% rename from UI/Backend/ViewModels/Automation/CampaignEdit/PlanningViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/PlanningViewModel.cs index a17fa4d0..0d3ad756 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/PlanningViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/PlanningViewModel.cs @@ -1,47 +1,49 @@ -using ReactiveUI; -using System.Collections.ObjectModel; -using Ares.Datamodel.Templates; -using Ares.Services; -using Ares.Datamodel.Planning; -using UI.Services.Notification; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public class PlanningViewModel : ReactiveObject -{ - private readonly CampaignTemplate _template; - private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _client; - private readonly INotificationReceivingService _notificationService; - - public PlanningViewModel(CampaignTemplate template, - IEnumerable plannerAdapters, - AresPlannerManagementService.AresPlannerManagementServiceClient client, - INotificationReceivingService notificationService) - { - _template = template; - _client = client; - _notificationService = notificationService; - PlannerAdapters = new ReadOnlyCollection(plannerAdapters.ToList()); - PlannerAllocationEditors = template.PlannableParameters.Select(metadata => new PlannerAllocationEditorViewModel(metadata, template.PlannerAllocations.FirstOrDefault(allocation => allocation.Parameter.Equals(metadata))?.Planner, PlannerAdapters, client, notificationService)).ToArray(); - } - - public IEnumerable PlannerAllocationEditors { get; private set; } - - public IEnumerable PlannerAdapters { get; } - - public void Save() - { - //We're not updating everything here maybe? Or maybe I'll just update the ARES core stuff. - _template.PlannerAllocations.Clear(); - - _template.PlannerAllocations.AddRange(PlannerAllocationEditors - .Select(editor => editor.Save()) - .Where(allocation => allocation is not null) - .Where(allocation => _template.PlannableParameters.Any(meta => meta.UniqueId == allocation!.Parameter.UniqueId))); - - PlannerAllocationEditors = _template.PlannableParameters - .Select(metadata => new PlannerAllocationEditorViewModel(metadata, _template.PlannerAllocations - .FirstOrDefault(allocation => allocation.Parameter.Equals(metadata))?.Planner, PlannerAdapters, _client, _notificationService)) - .ToArray(); - } -} +using ReactiveUI; +using System.Collections.ObjectModel; +using Ares.Datamodel.Templates; +using Ares.Services; +using Ares.Datamodel.Planning; +using UI.Application.Notifications; + +namespace UI.Features.CampaignEdit.ViewModels; + +public class PlanningViewModel : ReactiveObject +{ + private readonly CampaignTemplate _template; + private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _client; + private readonly INotificationReceivingService _notificationService; + + public PlanningViewModel(CampaignTemplate template, + IEnumerable plannerAdapters, + AresPlannerManagementService.AresPlannerManagementServiceClient client, + INotificationReceivingService notificationService) + { + _template = template; + _client = client; + _notificationService = notificationService; + PlannerAdapters = new ReadOnlyCollection(plannerAdapters.ToList()); + PlannerAllocationEditors = template.PlannableParameters.Select(metadata => new PlannerAllocationEditorViewModel(metadata, template.PlannerAllocations.FirstOrDefault(allocation => allocation.Parameter.Equals(metadata))?.Planner, PlannerAdapters, client, notificationService)).ToArray(); + } + + public IEnumerable PlannerAllocationEditors { get; private set; } + + public IEnumerable PlannerAdapters { get; } + + public void Save() + { + //We're not updating everything here maybe? Or maybe I'll just update the ARES core stuff. + _template.PlannerAllocations.Clear(); + + _template.PlannerAllocations.AddRange(PlannerAllocationEditors + .Select(editor => editor.Save()) + .Where(allocation => allocation is not null) + .Where(allocation => _template.PlannableParameters.Any(meta => meta.UniqueId == allocation!.Parameter.UniqueId))); + + PlannerAllocationEditors = _template.PlannableParameters + .Select(metadata => new PlannerAllocationEditorViewModel(metadata, _template.PlannerAllocations + .FirstOrDefault(allocation => allocation.Parameter.Equals(metadata))?.Planner, PlannerAdapters, _client, _notificationService)) + .ToArray(); + } +} + + diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/StartupDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/StartupDesignerViewModel.cs similarity index 97% rename from UI/Backend/ViewModels/Automation/CampaignEdit/StartupDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/StartupDesignerViewModel.cs index dafb2772..4e1a14e9 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/StartupDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/StartupDesignerViewModel.cs @@ -3,9 +3,9 @@ using Radzen; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Backend.ViewModels.Factories; +using UI.Features.CampaignEdit.Factories; -namespace UI.Backend.ViewModels.Automation.CampaignEdit; +namespace UI.Features.CampaignEdit.ViewModels; public partial class StartupDesignerViewModel : ReactiveObject { diff --git a/UI/Backend/ViewModels/Automation/CampaignEdit/StepDesignerViewModel.cs b/UI/Features/CampaignEdit/ViewModels/StepDesignerViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Automation/CampaignEdit/StepDesignerViewModel.cs rename to UI/Features/CampaignEdit/ViewModels/StepDesignerViewModel.cs index 91674fbe..b67e8d1b 100644 --- a/UI/Backend/ViewModels/Automation/CampaignEdit/StepDesignerViewModel.cs +++ b/UI/Features/CampaignEdit/ViewModels/StepDesignerViewModel.cs @@ -1,123 +1,123 @@ -using Ares.Datamodel.Templates; -using ReactiveUI; -using UI.Backend.ViewModels.Factories; - -namespace UI.Backend.ViewModels.Automation.CampaignEdit; - -public class StepDesignerViewModel : ReactiveObject -{ - private readonly CommandDesignerFactory _commandDesignerFactory; - private StepTemplate _stepTemplate = null!; - - public StepDesignerViewModel(CommandDesignerFactory commandDesignerFactory) - { - _commandDesignerFactory = commandDesignerFactory; - StepTemplate = new StepTemplate - { - UniqueId = Guid.NewGuid().ToString(), - Name = "New Step" - }; - } - - public StepDesignerViewModel(StepTemplate existingStepTemplate, CommandDesignerFactory commandDesignerFactory) - { - _commandDesignerFactory = commandDesignerFactory; - StepTemplate = existingStepTemplate; - } - - public string Name - { - get => StepTemplate.Name; - - set - { - StepTemplate.Name = value; - this.RaisePropertyChanged(); - } - } - - public bool Parallel - { - get => StepTemplate.IsParallel; - - set - { - StepTemplate.IsParallel = value; - this.RaisePropertyChanged(); - } - } - - public int Index { get; set; } - - public StepTemplate StepTemplate - { - get => _stepTemplate; - - set - { - _stepTemplate = value; - Init(value); - } - } - - public IList CommandDesigners { get; private set; } = Array.Empty(); - - private void Init(StepTemplate existingTemplate) - { - Name = existingTemplate.Name; - Parallel = existingTemplate.IsParallel; - Index = Convert.ToInt32(existingTemplate.Index); - CommandDesigners = existingTemplate.CommandTemplates.Select(template => _commandDesignerFactory.Create(template)).OrderBy(model => model.Index).ToList(); - } - - public StepTemplate Save() - { - StepTemplate.Name = Name; - StepTemplate.IsParallel = Parallel; - StepTemplate.Index = Index; - StepTemplate.CommandTemplates.Clear(); - StepTemplate.CommandTemplates.AddRange(CommandDesigners.Select(designer => designer.Save())); - return StepTemplate; - } - - public CommandDesignerViewModel AddCommandDesigner() - { - var newDesigner = _commandDesignerFactory.Create(); - newDesigner.Index = CommandDesigners.Count; - CommandDesigners.Add(newDesigner); - return newDesigner; - } - - public void RemoveCommandDesigner(CommandDesignerViewModel vm) - { - CommandDesigners.Remove(vm); - ReindexCommands(); - } - - public void MoveCommandDesignerUp(CommandDesignerViewModel vm) - { - if(vm.Index == 0) - return; - - CommandDesigners.RemoveAt(vm.Index); - CommandDesigners.Insert(vm.Index - 1, vm); - ReindexCommands(); - } - - public void MoveCommandDesignerDown(CommandDesignerViewModel vm) - { - if(vm.Index == CommandDesigners.Count - 1) - return; - - CommandDesigners.RemoveAt(vm.Index); - CommandDesigners.Insert(vm.Index + 1, vm); - ReindexCommands(); - } - - private void ReindexCommands() - { - var idx = 0; - foreach(var commandDesigner in CommandDesigners) - commandDesigner.Index = idx++; - } -} +using Ares.Datamodel.Templates; +using ReactiveUI; +using UI.Features.CampaignEdit.Factories; + +namespace UI.Features.CampaignEdit.ViewModels; + +public class StepDesignerViewModel : ReactiveObject +{ + private readonly CommandDesignerFactory _commandDesignerFactory; + private StepTemplate _stepTemplate = null!; + + public StepDesignerViewModel(CommandDesignerFactory commandDesignerFactory) + { + _commandDesignerFactory = commandDesignerFactory; + StepTemplate = new StepTemplate + { + UniqueId = Guid.NewGuid().ToString(), + Name = "New Step" + }; + } + + public StepDesignerViewModel(StepTemplate existingStepTemplate, CommandDesignerFactory commandDesignerFactory) + { + _commandDesignerFactory = commandDesignerFactory; + StepTemplate = existingStepTemplate; + } + + public string Name + { + get => StepTemplate.Name; + + set + { + StepTemplate.Name = value; + this.RaisePropertyChanged(); + } + } + + public bool Parallel + { + get => StepTemplate.IsParallel; + + set + { + StepTemplate.IsParallel = value; + this.RaisePropertyChanged(); + } + } + + public int Index { get; set; } + + public StepTemplate StepTemplate + { + get => _stepTemplate; + + set + { + _stepTemplate = value; + Init(value); + } + } + + public IList CommandDesigners { get; private set; } = Array.Empty(); + + private void Init(StepTemplate existingTemplate) + { + Name = existingTemplate.Name; + Parallel = existingTemplate.IsParallel; + Index = Convert.ToInt32(existingTemplate.Index); + CommandDesigners = existingTemplate.CommandTemplates.Select(template => _commandDesignerFactory.Create(template)).OrderBy(model => model.Index).ToList(); + } + + public StepTemplate Save() + { + StepTemplate.Name = Name; + StepTemplate.IsParallel = Parallel; + StepTemplate.Index = Index; + StepTemplate.CommandTemplates.Clear(); + StepTemplate.CommandTemplates.AddRange(CommandDesigners.Select(designer => designer.Save())); + return StepTemplate; + } + + public CommandDesignerViewModel AddCommandDesigner() + { + var newDesigner = _commandDesignerFactory.Create(); + newDesigner.Index = CommandDesigners.Count; + CommandDesigners.Add(newDesigner); + return newDesigner; + } + + public void RemoveCommandDesigner(CommandDesignerViewModel vm) + { + CommandDesigners.Remove(vm); + ReindexCommands(); + } + + public void MoveCommandDesignerUp(CommandDesignerViewModel vm) + { + if(vm.Index == 0) + return; + + CommandDesigners.RemoveAt(vm.Index); + CommandDesigners.Insert(vm.Index - 1, vm); + ReindexCommands(); + } + + public void MoveCommandDesignerDown(CommandDesignerViewModel vm) + { + if(vm.Index == CommandDesigners.Count - 1) + return; + + CommandDesigners.RemoveAt(vm.Index); + CommandDesigners.Insert(vm.Index + 1, vm); + ReindexCommands(); + } + + private void ReindexCommands() + { + var idx = 0; + foreach(var commandDesigner in CommandDesigners) + commandDesigner.Index = idx++; + } +} diff --git a/UI/Pages/Automation/AnalyzerInputDesigner.razor b/UI/Features/CampaignEdit/Views/AnalyzerInputDesigner.razor similarity index 92% rename from UI/Pages/Automation/AnalyzerInputDesigner.razor rename to UI/Features/CampaignEdit/Views/AnalyzerInputDesigner.razor index 14cc7500..1400352a 100644 --- a/UI/Pages/Automation/AnalyzerInputDesigner.razor +++ b/UI/Features/CampaignEdit/Views/AnalyzerInputDesigner.razor @@ -1,6 +1,6 @@ -@using UI.Backend.ViewModels.Automation.CampaignEdit +@using UI.Features.CampaignEdit.ViewModels -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase @if(DesignerLoading) { diff --git a/UI/Pages/Automation/CampaignDesigner.razor b/UI/Features/CampaignEdit/Views/CampaignDesigner.razor similarity index 94% rename from UI/Pages/Automation/CampaignDesigner.razor rename to UI/Features/CampaignEdit/Views/CampaignDesigner.razor index 66e749b7..2c5a227d 100644 --- a/UI/Pages/Automation/CampaignDesigner.razor +++ b/UI/Features/CampaignEdit/Views/CampaignDesigner.razor @@ -1,184 +1,184 @@ -@page "/automation/campaigndesigner" -@page "/automation/campaigndesigner/{CampaignId:guid}" -@using UI.Backend.ViewModels.Automation.CampaignEdit; -@using UI.Pages.Shared.CampaignEdit -@inject IUiNotificationService NotificationService -@inject DialogService DialogService -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase - -Campaign Designer - -
-
-

@ViewModel!.CampaignName

- -
-
- - - - - - - - - - - - -
-
- @if (SelectedSubOption1) - { -
- -
- } - @if (SelectedSubOption2) - { - - } - @if (SelectedSubOption3) - { - - } - @if (SelectedSubOption4) - { - - } - @if (SelectedSubOption5) - { - - } - @if (SelectedSubOption6) - { - - } -
-
- - -
-
- - -@code { - - [Parameter] - public Guid? CampaignId { get; set; } - - protected override async Task OnInitializedAsync() - { - if (CampaignId.HasValue) - await ViewModel!.SelectCampaignById(CampaignId.Value); - } - - public bool SelectedSubOption1 { get; set; } = true; - public bool SelectedSubOption2 { get; set; } - public bool SelectedSubOption3 { get; set; } - public bool SelectedSubOption4 { get; set; } - public bool SelectedSubOption5 { get; set; } - public bool SelectedSubOption6 { get; set; } - - - - public void SelectParam() - { - SelectedSubOption1 = true; - SelectedSubOption2 = false; - SelectedSubOption3 = false; - SelectedSubOption4 = false; - SelectedSubOption5 = false; - SelectedSubOption6 = false; - } - - public void SelectExp() - { - SelectedSubOption2 = true; - SelectedSubOption1 = false; - SelectedSubOption3 = false; - SelectedSubOption4 = false; - SelectedSubOption5 = false; - SelectedSubOption6 = false; - } - - public void SelectPlan() - { - SelectedSubOption2 = false; - SelectedSubOption1 = false; - SelectedSubOption3 = true; - SelectedSubOption4 = false; - SelectedSubOption5 = false; - SelectedSubOption6 = false; - } - - public void SelectStartup() - { - SelectedSubOption1 = false; - SelectedSubOption2 = false; - SelectedSubOption3 = false; - SelectedSubOption4 = true; - SelectedSubOption5 = false; - SelectedSubOption6 = false; - } - - public void SelectCloseout() - { - SelectedSubOption1 = false; - SelectedSubOption2 = false; - SelectedSubOption3 = false; - SelectedSubOption4 = false; - SelectedSubOption5 = true; - SelectedSubOption6 = false; - } - - public void SelectAnalyzer() - { - SelectedSubOption1 = false; - SelectedSubOption2 = false; - SelectedSubOption3 = false; - SelectedSubOption4 = false; - SelectedSubOption5 = false; - SelectedSubOption6 = true; - } - - private async Task SaveClick() - { - try - { - await ViewModel!.Update(); - NotificationService.Success("Campaign has been saved"); - } - catch (Exception e) - { - NotificationService.Error($"Failed to save campaign {e.Message}"); - } - } - - private async void TitleClicked(MouseEventArgs obj) - { - string newName = ViewModel!.CampaignName; - var result = await DialogService.OpenAsync("Campaign Name Edit", ds => - @
-
-
- Campaign Name -
- -
-
- - -
-
- ); - - if (result ?? false) - { - ViewModel!.CampaignName = newName; - } - } - -} +@page "/automation/campaigndesigner" +@page "/automation/campaigndesigner/{CampaignId:guid}" +@using UI.Application.Notifications +@inject IUiNotificationService NotificationService +@inject DialogService DialogService +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase + +Campaign Designer + +
+
+

@ViewModel!.CampaignName

+ +
+
+ + + + + + + + + + + + +
+
+ @if (SelectedSubOption1) + { +
+ +
+ } + @if (SelectedSubOption2) + { + + } + @if (SelectedSubOption3) + { + + } + @if (SelectedSubOption4) + { + + } + @if (SelectedSubOption5) + { + + } + @if (SelectedSubOption6) + { + + } +
+
+ + +
+
+ + +@code { + + [Parameter] + public Guid? CampaignId { get; set; } + + protected override async Task OnInitializedAsync() + { + if (CampaignId.HasValue) + await ViewModel!.SelectCampaignById(CampaignId.Value); + } + + public bool SelectedSubOption1 { get; set; } = true; + public bool SelectedSubOption2 { get; set; } + public bool SelectedSubOption3 { get; set; } + public bool SelectedSubOption4 { get; set; } + public bool SelectedSubOption5 { get; set; } + public bool SelectedSubOption6 { get; set; } + + + + public void SelectParam() + { + SelectedSubOption1 = true; + SelectedSubOption2 = false; + SelectedSubOption3 = false; + SelectedSubOption4 = false; + SelectedSubOption5 = false; + SelectedSubOption6 = false; + } + + public void SelectExp() + { + SelectedSubOption2 = true; + SelectedSubOption1 = false; + SelectedSubOption3 = false; + SelectedSubOption4 = false; + SelectedSubOption5 = false; + SelectedSubOption6 = false; + } + + public void SelectPlan() + { + SelectedSubOption2 = false; + SelectedSubOption1 = false; + SelectedSubOption3 = true; + SelectedSubOption4 = false; + SelectedSubOption5 = false; + SelectedSubOption6 = false; + } + + public void SelectStartup() + { + SelectedSubOption1 = false; + SelectedSubOption2 = false; + SelectedSubOption3 = false; + SelectedSubOption4 = true; + SelectedSubOption5 = false; + SelectedSubOption6 = false; + } + + public void SelectCloseout() + { + SelectedSubOption1 = false; + SelectedSubOption2 = false; + SelectedSubOption3 = false; + SelectedSubOption4 = false; + SelectedSubOption5 = true; + SelectedSubOption6 = false; + } + + public void SelectAnalyzer() + { + SelectedSubOption1 = false; + SelectedSubOption2 = false; + SelectedSubOption3 = false; + SelectedSubOption4 = false; + SelectedSubOption5 = false; + SelectedSubOption6 = true; + } + + private async Task SaveClick() + { + try + { + await ViewModel!.Update(); + NotificationService.Success("Campaign has been saved"); + } + catch (Exception e) + { + NotificationService.Error($"Failed to save campaign {e.Message}"); + } + } + + private async void TitleClicked(MouseEventArgs obj) + { + string newName = ViewModel!.CampaignName; + var result = await DialogService.OpenAsync("Campaign Name Edit", ds => + @
+
+
+ Campaign Name +
+ +
+
+ + +
+
+ ); + + if (result ?? false) + { + ViewModel!.CampaignName = newName; + } + } + +} + diff --git a/UI/Pages/Automation/CampaignList.razor b/UI/Features/CampaignEdit/Views/CampaignList.razor similarity index 95% rename from UI/Pages/Automation/CampaignList.razor rename to UI/Features/CampaignEdit/Views/CampaignList.razor index aa2bd91a..06c89fb4 100644 --- a/UI/Pages/Automation/CampaignList.razor +++ b/UI/Features/CampaignEdit/Views/CampaignList.razor @@ -1,107 +1,107 @@ -@page "/automation" -@page "/automation/campaignlist" -@using Ares.Services -@using Newtonsoft.Json -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inject DialogService DialogService -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase -@inject IJSRuntime JS - - -
-
-

Campaigns

-
- - - - -
-
- - - - - - - - - @foreach (var template in ViewModel!.Templates) - { - - - - - - } - -
Campaign NameCampaign ID
- @template.CampaignName - @template.UniqueId -
- - - - - -
-
-
- -@code { - protected override async Task OnInitializedAsync() - { - await ViewModel!.RefreshCampaigns(); - } - - private async void DeleteClick(CampaignTemplateSummary template) - { - var result = await DialogService.OpenAsync($"Delete campaign {template.CampaignName}?", ds => - @
-
- - -
-
- ); - if (result is null || !result) - return; - - await ViewModel!.DeleteCampaign(Guid.Parse(template.UniqueId)); - StateHasChanged(); - } - - private async void Refresh() - { - await ViewModel!.RefreshCampaigns(); - StateHasChanged(); - } - - private async void ExportTemplate(CampaignTemplateSummary templateSummary) - { - //Generate a copy of the template, create a new unique ID and change the name to avoid collisions if the user adds it to their templates folder - var template = await ViewModel!.GetFullCampaignTemplate(templateSummary.UniqueId); - - if(template is not null) - { - var templateCopy = template.Clone(); - templateCopy.UniqueId = Guid.NewGuid().ToString(); - templateCopy.Name = $"{template.Name}-Copy"; - var fileName = templateCopy.UniqueId; - var jsonData = JsonConvert.SerializeObject(templateCopy, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); - await JS.InvokeVoidAsync("downloadJsonFile", fileName, jsonData); - } - - else - { - //Notify of failure..? - } - } - -} +@page "/automation" +@page "/automation/campaignlist" +@using Ares.Services +@using Newtonsoft.Json +@using UI.Features.CampaignEdit.ViewModels +@inject DialogService DialogService +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase +@inject IJSRuntime JS + + +
+
+

Campaigns

+
+ + + + +
+
+ + + + + + + + + @foreach (var template in ViewModel!.Templates) + { + + + + + + } + +
Campaign NameCampaign ID
+ @template.CampaignName + @template.UniqueId +
+ + + + + +
+
+
+ +@code { + protected override async Task OnInitializedAsync() + { + await ViewModel!.RefreshCampaigns(); + } + + private async void DeleteClick(CampaignTemplateSummary template) + { + var result = await DialogService.OpenAsync($"Delete campaign {template.CampaignName}?", ds => + @
+
+ + +
+
+ ); + if (result is null || !result) + return; + + await ViewModel!.DeleteCampaign(Guid.Parse(template.UniqueId)); + StateHasChanged(); + } + + private async void Refresh() + { + await ViewModel!.RefreshCampaigns(); + StateHasChanged(); + } + + private async void ExportTemplate(CampaignTemplateSummary templateSummary) + { + //Generate a copy of the template, create a new unique ID and change the name to avoid collisions if the user adds it to their templates folder + var template = await ViewModel!.GetFullCampaignTemplate(templateSummary.UniqueId); + + if(template is not null) + { + var templateCopy = template.Clone(); + templateCopy.UniqueId = Guid.NewGuid().ToString(); + templateCopy.Name = $"{template.Name}-Copy"; + var fileName = templateCopy.UniqueId; + var jsonData = JsonConvert.SerializeObject(templateCopy, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); + await JS.InvokeVoidAsync("downloadJsonFile", fileName, jsonData); + } + + else + { + //Notify of failure..? + } + } + +} diff --git a/UI/Pages/Shared/CampaignEdit/CloseoutScriptDesigner.razor b/UI/Features/CampaignEdit/Views/CloseoutScriptDesigner.razor similarity index 93% rename from UI/Pages/Shared/CampaignEdit/CloseoutScriptDesigner.razor rename to UI/Features/CampaignEdit/Views/CloseoutScriptDesigner.razor index 084b8c9f..fe60f9bd 100644 --- a/UI/Pages/Shared/CampaignEdit/CloseoutScriptDesigner.razor +++ b/UI/Features/CampaignEdit/Views/CloseoutScriptDesigner.razor @@ -1,7 +1,7 @@ @inject DialogService DialogService @using Ares.Datamodel.Templates -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Features.CampaignEdit.ViewModels +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Pages/Shared/CampaignEdit/CommandDesigner.razor b/UI/Features/CampaignEdit/Views/CommandDesigner.razor similarity index 90% rename from UI/Pages/Shared/CampaignEdit/CommandDesigner.razor rename to UI/Features/CampaignEdit/Views/CommandDesigner.razor index 1a2eff63..2cadc174 100644 --- a/UI/Pages/Shared/CampaignEdit/CommandDesigner.razor +++ b/UI/Features/CampaignEdit/Views/CommandDesigner.razor @@ -1,6 +1,6 @@ @using Ares.Datamodel.Templates -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Features.CampaignEdit.ViewModels +@inherits ReactiveUI.Blazor.ReactiveComponentBase diff --git a/UI/Pages/Shared/CampaignEdit/CommandDesignerQuickView.razor b/UI/Features/CampaignEdit/Views/CommandDesignerQuickView.razor similarity index 94% rename from UI/Pages/Shared/CampaignEdit/CommandDesignerQuickView.razor rename to UI/Features/CampaignEdit/Views/CommandDesignerQuickView.razor index ce1d5593..f3f64a8e 100644 --- a/UI/Pages/Shared/CampaignEdit/CommandDesignerQuickView.razor +++ b/UI/Features/CampaignEdit/Views/CommandDesignerQuickView.razor @@ -1,104 +1,105 @@ -@using Ares.Datamodel.Templates -@using UI.Backend.ViewModels.Automation.CampaignEdit -@using UI.Pages.Resources - -@inject IUiNotificationService notificationService; - -@if (CommandDesignerViewModel is not null) -{ - if (CommandDesignerViewModel.TemplateCommandName is not null) - { -
- @if((CommandDesignerViewModel.MetadataDeviceName ?? CommandDesignerViewModel.TemplateDeviceName) is null) - { -
- UNKNOWN_DEVICE -
- } - else - { -
- @(CommandDesignerViewModel.MetadataDeviceName ?? CommandDesignerViewModel.TemplateDeviceName) -
- } -
- @CommandDesignerViewModel.TemplateCommandName -
- @if (CommandDesignerViewModel.Arguments.Any()) - { - - } -
- @foreach (var argument in CommandDesignerViewModel.Arguments) - { - @if (argument.PlanningMetadata is not null) - { -
- @ConvertToDisplayData(argument) -
- } - - else if(argument.VariableType > 0) - { -
- @argument.VariableType.ToString() -
- } - - else - { - if(argument.Value is null) - { - NotifyOfParameterError(argument); -
- Error! Parameter Deleted! -
- } - - else - { -
- @ConvertToDisplayData(argument) -
- } - } - } -
-
- -
- } - else - { - Undefined Command - } -} -@code { - [Parameter] - public CommandDesignerViewModel? CommandDesignerViewModel { get; set; } - - public void NotifyOfParameterError(Parameter errorParam) - { - var message = $"A command utilizing the parameter {errorParam.Metadata.Name} needs attention, as this parameter has been deleted"; - notificationService.Error(message, "Experiment Parameter Deleted"); - } - - public object ConvertToDisplayData(Parameter argument) - { - if(argument.PlanningMetadata is not null) - { - return $"{argument.Metadata.Name}: {argument.PlanningMetadata.Name}"; - } - var aresValue = argument.Value; - if(aresValue.HasNumberValue) - return $"{argument.Metadata.Name}: {aresValue.NumberValue} {argument.Metadata.Unit}"; - - else if(aresValue.HasStringValue) - return $"{argument.Metadata.Name}: {aresValue.StringValue} {argument.Metadata.Unit}"; - - else - return $"{argument.Metadata.Name}: Custom Data"; - } -} +@using Ares.Datamodel.Templates +@using UI.Application.Notifications +@using CommandDesignerViewModel=UI.Features.CampaignEdit.ViewModels.CommandDesignerViewModel + +@inject IUiNotificationService notificationService; + +@if (CommandDesignerViewModel is not null) +{ + if (CommandDesignerViewModel.TemplateCommandName is not null) + { +
+ @if((CommandDesignerViewModel.MetadataDeviceName ?? CommandDesignerViewModel.TemplateDeviceName) is null) + { +
+ UNKNOWN_DEVICE +
+ } + else + { +
+ @(CommandDesignerViewModel.MetadataDeviceName ?? CommandDesignerViewModel.TemplateDeviceName) +
+ } +
+ @CommandDesignerViewModel.TemplateCommandName +
+ @if (CommandDesignerViewModel.Arguments.Any()) + { + + } +
+ @foreach (var argument in CommandDesignerViewModel.Arguments) + { + @if (argument.PlanningMetadata is not null) + { +
+ @ConvertToDisplayData(argument) +
+ } + + else if(argument.VariableType > 0) + { +
+ @argument.VariableType.ToString() +
+ } + + else + { + if(argument.Value is null) + { + NotifyOfParameterError(argument); +
+ Error! Parameter Deleted! +
+ } + + else + { +
+ @ConvertToDisplayData(argument) +
+ } + } + } +
+
+ +
+ } + else + { + Undefined Command + } +} +@code { + [Parameter] + public CommandDesignerViewModel? CommandDesignerViewModel { get; set; } + + public void NotifyOfParameterError(Parameter errorParam) + { + var message = $"A command utilizing the parameter {errorParam.Metadata.Name} needs attention, as this parameter has been deleted"; + notificationService.Error(message, "Experiment Parameter Deleted"); + } + + public object ConvertToDisplayData(Parameter argument) + { + if(argument.PlanningMetadata is not null) + { + return $"{argument.Metadata.Name}: {argument.PlanningMetadata.Name}"; + } + var aresValue = argument.Value; + if(aresValue.HasNumberValue) + return $"{argument.Metadata.Name}: {aresValue.NumberValue} {argument.Metadata.Unit}"; + + else if(aresValue.HasStringValue) + return $"{argument.Metadata.Name}: {aresValue.StringValue} {argument.Metadata.Unit}"; + + else + return $"{argument.Metadata.Name}: Custom Data"; + } +} + diff --git a/UI/Pages/Shared/CampaignEdit/CommandParameterDesigner.razor b/UI/Features/CampaignEdit/Views/CommandParameterDesigner.razor similarity index 95% rename from UI/Pages/Shared/CampaignEdit/CommandParameterDesigner.razor rename to UI/Features/CampaignEdit/Views/CommandParameterDesigner.razor index f40fdfb1..ce2a8420 100644 --- a/UI/Pages/Shared/CampaignEdit/CommandParameterDesigner.razor +++ b/UI/Features/CampaignEdit/Views/CommandParameterDesigner.razor @@ -1,83 +1,83 @@ -@using Ares.Datamodel -@using Ares.Datamodel.Templates -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -
-
- @if (ViewModel!.IsPlanned || ViewModel!.IsEnvironmentBased) - { - if(ViewModel!.IsPlanned) - { - - - - - } - - else - { - - } - } - - else - { - - } -
- - @if (ViewModel!.IsEnvironmentBased && ViewModel!.SelectedVariableType == VariableType.PreviousExperimentPath) - { -
- - -
- } - - @if(!DisablePlanning) - { -
- - -
- } - -
- - -
- -
- -@code { - [Parameter] - public bool DisablePlanning { get; set; } - - private Task ValueEditCallback(AresValue value) - { - //Not sure what scenario this would be to be honest - if(ViewModel!.Value is null) - return Task.CompletedTask; - - ViewModel!.Value = value; - return Task.CompletedTask; - } +@using Ares.Datamodel +@using Ares.Datamodel.Templates +@using UI.Features.CampaignEdit.ViewModels +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+
+ @if (ViewModel!.IsPlanned || ViewModel!.IsEnvironmentBased) + { + if(ViewModel!.IsPlanned) + { + + + + + } + + else + { + + } + } + + else + { + + } +
+ + @if (ViewModel!.IsEnvironmentBased && ViewModel!.SelectedVariableType == VariableType.PreviousExperimentPath) + { +
+ + +
+ } + + @if(!DisablePlanning) + { +
+ + +
+ } + +
+ + +
+ +
+ +@code { + [Parameter] + public bool DisablePlanning { get; set; } + + private Task ValueEditCallback(AresValue value) + { + //Not sure what scenario this would be to be honest + if(ViewModel!.Value is null) + return Task.CompletedTask; + + ViewModel!.Value = value; + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/UI/Pages/Shared/CampaignEdit/ExperimentDesigner.razor b/UI/Features/CampaignEdit/Views/ExperimentDesigner.razor similarity index 91% rename from UI/Pages/Shared/CampaignEdit/ExperimentDesigner.razor rename to UI/Features/CampaignEdit/Views/ExperimentDesigner.razor index 22fcd4c7..ad09359b 100644 --- a/UI/Pages/Shared/CampaignEdit/ExperimentDesigner.razor +++ b/UI/Features/CampaignEdit/Views/ExperimentDesigner.razor @@ -1,74 +1,74 @@ -@inject DialogService DialogService -@using Ares.Datamodel.Templates -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - -
-
-
- @foreach (var stepDesigner in ViewModel!.StepDesigners) - { -
-
- - -
-
- -
-
- } -
- -
-
- - -@code { - [Parameter] - public ExperimentTemplate? ExperimentTemplate { get; set; } - - private void RemoveStepCallback(StepDesignerViewModel vm) - { - ViewModel!.RemoveStep(vm); - StateHasChanged(); - } - - private async void AddStepClick() - { - var stepDesigner = ViewModel!.AddStep(); - string name = stepDesigner.Name; - bool isParallel = stepDesigner.Parallel; - var result = await DialogService.OpenAsync("Step Create", ds => - @
- -
- - -
-
- ); - if (result ?? false) - { - stepDesigner.Name = name; - stepDesigner.Parallel = isParallel; - } - else - { - ViewModel!.RemoveStep(stepDesigner); - StateHasChanged(); - } - } - -} -@code { - -} +@inject DialogService DialogService +@using Ares.Datamodel.Templates +@using UI.Features.CampaignEdit.ViewModels +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + +
+
+
+ @foreach (var stepDesigner in ViewModel!.StepDesigners) + { +
+
+ + +
+
+ +
+
+ } +
+ +
+
+ + +@code { + [Parameter] + public ExperimentTemplate? ExperimentTemplate { get; set; } + + private void RemoveStepCallback(StepDesignerViewModel vm) + { + ViewModel!.RemoveStep(vm); + StateHasChanged(); + } + + private async void AddStepClick() + { + var stepDesigner = ViewModel!.AddStep(); + string name = stepDesigner.Name; + bool isParallel = stepDesigner.Parallel; + var result = await DialogService.OpenAsync("Step Create", ds => + @
+ +
+ + +
+
+ ); + if (result ?? false) + { + stepDesigner.Name = name; + stepDesigner.Parallel = isParallel; + } + else + { + ViewModel!.RemoveStep(stepDesigner); + StateHasChanged(); + } + } + +} +@code { + +} diff --git a/UI/Pages/Shared/CampaignEdit/MetadataPicker.razor b/UI/Features/CampaignEdit/Views/MetadataPicker.razor similarity index 96% rename from UI/Pages/Shared/CampaignEdit/MetadataPicker.razor rename to UI/Features/CampaignEdit/Views/MetadataPicker.razor index 570ec8c7..b69e234a 100644 --- a/UI/Pages/Shared/CampaignEdit/MetadataPicker.razor +++ b/UI/Features/CampaignEdit/Views/MetadataPicker.razor @@ -1,6 +1,5 @@ @using Ares.Datamodel.Templates -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Pages/Shared/CampaignEdit/PlannableParameterDesigner.razor b/UI/Features/CampaignEdit/Views/PlannableParameterDesigner.razor similarity index 82% rename from UI/Pages/Shared/CampaignEdit/PlannableParameterDesigner.razor rename to UI/Features/CampaignEdit/Views/PlannableParameterDesigner.razor index b895394f..0ee09dd5 100644 --- a/UI/Pages/Shared/CampaignEdit/PlannableParameterDesigner.razor +++ b/UI/Features/CampaignEdit/Views/PlannableParameterDesigner.razor @@ -1,22 +1,23 @@ -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -
- @foreach (var parameterEditorVm in ViewModel!.ParameterEditors) - { - - } -
- -
-
- -@code { - private void NewParameterClick() => ViewModel!.Create(); - - private void DeleteParameterCallback(ParameterEditorViewModel paramEditor) - { - ViewModel!.Remove(paramEditor); - StateHasChanged(); - } +@using UI.Features.CampaignEdit.ViewModels +@using ParameterEditorViewModel=UI.Features.CampaignEdit.ViewModels.ParameterEditorViewModel +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+ @foreach (var parameterEditorVm in ViewModel!.ParameterEditors) + { + + } +
+ +
+
+ +@code { + private void NewParameterClick() => ViewModel!.Create(); + + private void DeleteParameterCallback(ParameterEditorViewModel paramEditor) + { + ViewModel!.Remove(paramEditor); + StateHasChanged(); + } } \ No newline at end of file diff --git a/UI/Pages/Shared/CampaignEdit/PlannableParameterEditor.razor b/UI/Features/CampaignEdit/Views/PlannableParameterEditor.razor similarity index 94% rename from UI/Pages/Shared/CampaignEdit/PlannableParameterEditor.razor rename to UI/Features/CampaignEdit/Views/PlannableParameterEditor.razor index b97eff36..8863c4d2 100644 --- a/UI/Pages/Shared/CampaignEdit/PlannableParameterEditor.razor +++ b/UI/Features/CampaignEdit/Views/PlannableParameterEditor.razor @@ -1,113 +1,113 @@ -@using Ares.Datamodel -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - -
-
-
- - - -
- -
- -
- - - -
- - @if(ViewModel!.DataType == AresDataType.Number) - { -
-
- - - -
- -
- - - -
-
- -
-
- - - -
- -
- - - -
-
- } - - @if(ViewModel!.HasInitialValue) - { - - } - - -
- - - - - - - - - - - - @if (ViewModel!.AvailableOutputs is not null && ViewModel!.HasAchievedValue) - { -
- - - -
- } -
- -@code { - - private IEnumerable? FilteredTypes; - - [Parameter] - public Action DeleteParameterCallback { get; set; } = delegate { }; - - private void DeleteParameterClick() - { - DeleteParameterCallback(ViewModel!); - } - - private Task ValueEditCallback(AresValue value) - { - //Not sure what scenario this would be to be honest - if(value is null) - return Task.CompletedTask; - - if(ViewModel!.InitialValue is null) - ViewModel!.InitialValue = new AresValue(); - - ViewModel!.InitialValue = value; - return Task.CompletedTask; - } - - protected override void OnInitialized() - { - FilteredTypes = Enum.GetValues(typeof(AresDataType)).Cast().Where(s => s != AresDataType.Null && s != AresDataType.UnspecifiedType); - } +@using Ares.Datamodel +@using ParameterEditorViewModel=UI.Features.CampaignEdit.ViewModels.ParameterEditorViewModel +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + +
+
+
+ + + +
+ +
+ +
+ + + +
+ + @if(ViewModel!.DataType == AresDataType.Number) + { +
+
+ + + +
+ +
+ + + +
+
+ +
+
+ + + +
+ +
+ + + +
+
+ } + + @if(ViewModel!.HasInitialValue) + { + + } + + +
+ + + + + + + + + + + + @if (ViewModel!.AvailableOutputs is not null && ViewModel!.HasAchievedValue) + { +
+ + + +
+ } +
+ +@code { + + private IEnumerable? FilteredTypes; + + [Parameter] + public Action DeleteParameterCallback { get; set; } = delegate { }; + + private void DeleteParameterClick() + { + DeleteParameterCallback(ViewModel!); + } + + private Task ValueEditCallback(AresValue value) + { + //Not sure what scenario this would be to be honest + if(value is null) + return Task.CompletedTask; + + if(ViewModel!.InitialValue is null) + ViewModel!.InitialValue = new AresValue(); + + ViewModel!.InitialValue = value; + return Task.CompletedTask; + } + + protected override void OnInitialized() + { + FilteredTypes = Enum.GetValues(typeof(AresDataType)).Cast().Where(s => s != AresDataType.Null && s != AresDataType.UnspecifiedType); + } } \ No newline at end of file diff --git a/UI/Pages/Shared/CampaignEdit/PlannerAllocationEditor.razor b/UI/Features/CampaignEdit/Views/PlannerAllocationEditor.razor similarity index 92% rename from UI/Pages/Shared/CampaignEdit/PlannerAllocationEditor.razor rename to UI/Features/CampaignEdit/Views/PlannerAllocationEditor.razor index cb2f88fc..2bc806a0 100644 --- a/UI/Pages/Shared/CampaignEdit/PlannerAllocationEditor.razor +++ b/UI/Features/CampaignEdit/Views/PlannerAllocationEditor.razor @@ -1,56 +1,56 @@ -@using Ares.Datamodel.Planning; -@inject DialogService DialogService -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -
- - - - @ViewModel!.ParameterMetadata.Name - - - - - - - - - - @if(ViewModel!.PlannerOptions.Count() > 1) - { - - - - - - @context.PlannerName - - - - } - - - - Planner Description: - @(ViewModel!.SelectedPlannerOption?.Description ?? ViewModel!.SelectedService?.Description ?? string.Empty) - - - -
- -@code { - private async Task ShowPlannerOptionTip() - { - await DialogService.OpenAsync("Planner Selection", ds => - @
- In ARES, some planner services report the capability of using multiple types of planners. - In this scenario, a seperate dropdown allows you to choose what planning method to use from the services advertised options. -
- , new DialogOptions() { CloseDialogOnOverlayClick = true }); - } - -} +@using Ares.Datamodel.Planning; +@inject DialogService DialogService +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+ + + + @ViewModel!.ParameterMetadata.Name + + + + + + + + + + @if(ViewModel!.PlannerOptions.Count() > 1) + { + + + + + + @context.PlannerName + + + + } + + + + Planner Description: + @(ViewModel!.SelectedPlannerOption?.Description ?? ViewModel!.SelectedService?.Description ?? string.Empty) + + + +
+ +@code { + private async Task ShowPlannerOptionTip() + { + await DialogService.OpenAsync("Planner Selection", ds => + @
+ In ARES, some planner services report the capability of using multiple types of planners. + In this scenario, a seperate dropdown allows you to choose what planning method to use from the services advertised options. +
+ , new DialogOptions() { CloseDialogOnOverlayClick = true }); + } + +} diff --git a/UI/Pages/Automation/Planning.razor b/UI/Features/CampaignEdit/Views/Planning.razor similarity index 50% rename from UI/Pages/Automation/Planning.razor rename to UI/Features/CampaignEdit/Views/Planning.razor index a3616b80..46213ad3 100644 --- a/UI/Pages/Automation/Planning.razor +++ b/UI/Features/CampaignEdit/Views/Planning.razor @@ -1,16 +1,16 @@ -@using UI.Pages.Shared.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - -
-

Planning

- @foreach (var editor in ViewModel!.PlannerAllocationEditors) - { - - } -
- - -@code { - +@using UI.Features.CampaignEdit.ViewModels +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + +
+

Planning

+ @foreach (var editor in ViewModel!.PlannerAllocationEditors) + { + + } +
+ + +@code { + } \ No newline at end of file diff --git a/UI/Pages/Shared/CampaignEdit/StartupScriptDesigner.razor b/UI/Features/CampaignEdit/Views/StartupScriptDesigner.razor similarity index 93% rename from UI/Pages/Shared/CampaignEdit/StartupScriptDesigner.razor rename to UI/Features/CampaignEdit/Views/StartupScriptDesigner.razor index f21c3de9..071bd42a 100644 --- a/UI/Pages/Shared/CampaignEdit/StartupScriptDesigner.razor +++ b/UI/Features/CampaignEdit/Views/StartupScriptDesigner.razor @@ -1,7 +1,7 @@ @inject DialogService DialogService @using Ares.Datamodel.Templates -@using UI.Backend.ViewModels.Automation.CampaignEdit -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Features.CampaignEdit.ViewModels +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Pages/Shared/CampaignEdit/StepDesigner.razor b/UI/Features/CampaignEdit/Views/StepDesigner.razor similarity index 97% rename from UI/Pages/Shared/CampaignEdit/StepDesigner.razor rename to UI/Features/CampaignEdit/Views/StepDesigner.razor index bea52dac..3c10fb23 100644 --- a/UI/Pages/Shared/CampaignEdit/StepDesigner.razor +++ b/UI/Features/CampaignEdit/Views/StepDesigner.razor @@ -1,5 +1,6 @@ @inject DialogService DialogService -@using UI.Backend.ViewModels.Automation.CampaignEdit +@using UI.Features.CampaignEdit.ViewModels +@using CommandDesignerViewModel=UI.Features.CampaignEdit.ViewModels.CommandDesignerViewModel @inherits ReactiveUI.Blazor.ReactiveComponentBase
@@ -12,7 +13,7 @@ @ViewModel!.Name @ViewModel!.CommandDesigners.Count - -@code { - private async Task OpenExporter() - { - await DialogService.OpenAsync("State Export", dialog => - @ - ); - } -} +@page "/device_states" +@using Ares.Services +@inject DialogService DialogService +@inject AresAutomation.AresAutomationClient AutomationClient + + +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase + + + +@code { + private async Task OpenExporter() + { + await DialogService.OpenAsync("State Export", dialog => + @ + ); + } +} diff --git a/UI/Backend/ViewModels/DeviceStatesViewModel.cs b/UI/Features/DeviceStateExport/DeviceStatesViewModel.cs similarity index 92% rename from UI/Backend/ViewModels/DeviceStatesViewModel.cs rename to UI/Features/DeviceStateExport/DeviceStatesViewModel.cs index 61a4fe14..bb5a864d 100644 --- a/UI/Backend/ViewModels/DeviceStatesViewModel.cs +++ b/UI/Features/DeviceStateExport/DeviceStatesViewModel.cs @@ -1,29 +1,29 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels; - -public partial class DeviceStatesViewModel : ReactiveObject -{ - readonly AresDevices.AresDevicesClient _devicesClient; - - public DeviceStatesViewModel(AresDevices.AresDevicesClient devicesClient) - { - _devicesClient = devicesClient; - _devicesClient - .ListAresDevicesAsync(new Empty()).ResponseAsync - .ContinueWith(task => AvailableDevices = task.Result.AresDevices); - } - public string? SelectedDeviceName { get; set; } - - public void ChooseDevice(string deviceName) - { - - } - - [Reactive] - public partial IEnumerable? AvailableDevices { get; private set; } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace UI.Features.DeviceStateExport; + +public partial class DeviceStatesViewModel : ReactiveObject +{ + readonly AresDevices.AresDevicesClient _devicesClient; + + public DeviceStatesViewModel(AresDevices.AresDevicesClient devicesClient) + { + _devicesClient = devicesClient; + _devicesClient + .ListAresDevicesAsync(new Empty()).ResponseAsync + .ContinueWith(task => AvailableDevices = task.Result.AresDevices); + } + public string? SelectedDeviceName { get; set; } + + public void ChooseDevice(string deviceName) + { + + } + + [Reactive] + public partial IEnumerable? AvailableDevices { get; private set; } +} diff --git a/UI/Pages/Shared/DeviceStateLogging/DeviceStateFilterView.razor b/UI/Features/DeviceStateLogging/DeviceStateFilterView.razor similarity index 95% rename from UI/Pages/Shared/DeviceStateLogging/DeviceStateFilterView.razor rename to UI/Features/DeviceStateLogging/DeviceStateFilterView.razor index 4add61bc..e0f1e627 100644 --- a/UI/Pages/Shared/DeviceStateLogging/DeviceStateFilterView.razor +++ b/UI/Features/DeviceStateLogging/DeviceStateFilterView.razor @@ -1,54 +1,53 @@ -@using Ares.Services -@using UI.Backend.ViewModels.DeviceStateLogging; - -@inject AresAutomation.AresAutomationClient AutomationClient - -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -
- @if (ViewModel?.AvailableDevices is not null) - { -
- - - -
- } - else - { - Loading Devices... - } - - - - - - - - - - - - - - - - - - @if (ViewModel?.Campaigns is not null) - { - - } - else - { - Loading Campaigns... - } -
-@code { - private void IntervalChanged(double val) - { - ViewModel!.Interval = TimeSpan.FromMilliseconds(val); - } -} +@using Ares.Services + +@inject AresAutomation.AresAutomationClient AutomationClient + +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+ @if (ViewModel?.AvailableDevices is not null) + { +
+ + + +
+ } + else + { + Loading Devices... + } + + + + + + + + + + + + + + + + + + @if (ViewModel?.Campaigns is not null) + { + + } + else + { + Loading Campaigns... + } +
+@code { + private void IntervalChanged(double val) + { + ViewModel!.Interval = TimeSpan.FromMilliseconds(val); + } +} diff --git a/UI/Pages/Shared/DeviceStateLogging/DeviceStateFilterView.razor.css b/UI/Features/DeviceStateLogging/DeviceStateFilterView.razor.css similarity index 94% rename from UI/Pages/Shared/DeviceStateLogging/DeviceStateFilterView.razor.css rename to UI/Features/DeviceStateLogging/DeviceStateFilterView.razor.css index 980a8184..1083f3e7 100644 --- a/UI/Pages/Shared/DeviceStateLogging/DeviceStateFilterView.razor.css +++ b/UI/Features/DeviceStateLogging/DeviceStateFilterView.razor.css @@ -1,11 +1,11 @@ -.filter-grid { - display: grid; - align-items: center; - grid-row-gap: 10px; - grid-gap: 10px; - grid-template-columns: auto auto 1fr; -} - -.device-picker { - grid-column: 1 / span 3; +.filter-grid { + display: grid; + align-items: center; + grid-row-gap: 10px; + grid-gap: 10px; + grid-template-columns: auto auto 1fr; +} + +.device-picker { + grid-column: 1 / span 3; } \ No newline at end of file diff --git a/UI/Backend/ViewModels/DeviceStateLogging/DeviceStateFilterViewModel.cs b/UI/Features/DeviceStateLogging/DeviceStateFilterViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/DeviceStateLogging/DeviceStateFilterViewModel.cs rename to UI/Features/DeviceStateLogging/DeviceStateFilterViewModel.cs index d005a414..eae21081 100644 --- a/UI/Backend/ViewModels/DeviceStateLogging/DeviceStateFilterViewModel.cs +++ b/UI/Features/DeviceStateLogging/DeviceStateFilterViewModel.cs @@ -1,85 +1,86 @@ -using Ares.Datamodel; -using Ares.Datamodel.Device; -using Ares.Messages.DeviceStates; -using Ares.Services; -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels.DeviceStateLogging; - -public partial class DeviceStateFilterViewModel : ReactiveObject -{ - readonly AresAutomation.AresAutomationClient _automationClient; - - public DeviceStateFilterViewModel( - AresAutomation.AresAutomationClient automationClient, - ICombinedDeviceGetter deviceGetter) - { - _automationClient = automationClient; - _automationClient.GetAvailableCampaignExecutionSummariesAsync(new Empty()).ResponseAsync - .ContinueWith(task => UpdateCampaigns(task.Result)); - deviceGetter.GetAvailableDevices() - .ContinueWith(task => AvailableDevices = task.Result); - - var currentTime = DateTime.Now; - // probably don't need millisecond precision - var truncatedTime = new DateTime(currentTime.Ticks - (currentTime.Ticks % TimeSpan.TicksPerMinute)); - StartTime = truncatedTime - TimeSpan.FromHours(1); - EndTime = truncatedTime; - } - - private void UpdateCampaigns(AvailableCampaignExecutionSummariesResponse response) - { - Campaigns = response.AvailableCampaignSummaries.Select(result => new CampaignExecutionSummaryMetadata { SummaryId = result.SummaryId, CampaignName = $"result.CampaignName-{result.CompletionTime}", CompletionTime = result.CompletionTime }); - } - - [Reactive] - public partial IEnumerable? AvailableDevices { get; private set; } - - public IEnumerable? SelectedDevices { get; set; } - - [Reactive] - public partial IEnumerable? Campaigns { get; private set; } - - public async Task UpdateExperiments(string? campaignResultId) - { - Experiments = null; - var campaignResult = await _automationClient.GetCampaignSummaryAsync( - new CampaignExecutionSummaryRequest { SummaryId = campaignResultId }); - Experiments = campaignResult.ExperimentSummaries; - } - - [Reactive] - public partial IEnumerable? Experiments { get; private set; } - - public string? SelectedExperimentId { get; set; } - - public DateTime StartTime { get; set; } - - public DateTime EndTime { get; set; } - - public TimeSpan Interval { get; set; } - - public bool UseStartTime { get; set; } - public bool UseEndTime { get; set; } - public bool UseExperiment { get; set; } - - public DeviceStateRequestFilter GetStateRequestFilter() - { - var request = new DeviceStateRequestFilter - { - Start = UseStartTime ? StartTime.ToUniversalTime().ToTimestamp() : default, - End = UseEndTime ? EndTime.ToUniversalTime().ToTimestamp() : default, - CompletedExperimentId = UseExperiment ? SelectedExperimentId : string.Empty, - Interval = Interval.ToDuration() - }; - - if(SelectedDevices is not null) - request.DeviceIds.AddRange(SelectedDevices.Select(d => d.DeviceId)); - - return request; - } -} - - +using Ares.Datamodel; +using Ares.Datamodel.Device; +using Ares.Messages.DeviceStates; +using Ares.Services; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using UI.Application.DeviceStateLogging; + +namespace UI.Features.DeviceStateLogging; + +public partial class DeviceStateFilterViewModel : ReactiveObject +{ + readonly AresAutomation.AresAutomationClient _automationClient; + + public DeviceStateFilterViewModel( + AresAutomation.AresAutomationClient automationClient, + ICombinedDeviceGetter deviceGetter) + { + _automationClient = automationClient; + _automationClient.GetAvailableCampaignExecutionSummariesAsync(new Empty()).ResponseAsync + .ContinueWith(task => UpdateCampaigns(task.Result)); + deviceGetter.GetAvailableDevices() + .ContinueWith(task => AvailableDevices = task.Result); + + var currentTime = DateTime.Now; + // probably don't need millisecond precision + var truncatedTime = new DateTime(currentTime.Ticks - (currentTime.Ticks % TimeSpan.TicksPerMinute)); + StartTime = truncatedTime - TimeSpan.FromHours(1); + EndTime = truncatedTime; + } + + private void UpdateCampaigns(AvailableCampaignExecutionSummariesResponse response) + { + Campaigns = response.AvailableCampaignSummaries.Select(result => new CampaignExecutionSummaryMetadata { SummaryId = result.SummaryId, CampaignName = $"result.CampaignName-{result.CompletionTime}", CompletionTime = result.CompletionTime }); + } + + [Reactive] + public partial IEnumerable? AvailableDevices { get; private set; } + + public IEnumerable? SelectedDevices { get; set; } + + [Reactive] + public partial IEnumerable? Campaigns { get; private set; } + + public async Task UpdateExperiments(string? campaignResultId) + { + Experiments = null; + var campaignResult = await _automationClient.GetCampaignSummaryAsync( + new CampaignExecutionSummaryRequest { SummaryId = campaignResultId }); + Experiments = campaignResult.ExperimentSummaries; + } + + [Reactive] + public partial IEnumerable? Experiments { get; private set; } + + public string? SelectedExperimentId { get; set; } + + public DateTime StartTime { get; set; } + + public DateTime EndTime { get; set; } + + public TimeSpan Interval { get; set; } + + public bool UseStartTime { get; set; } + public bool UseEndTime { get; set; } + public bool UseExperiment { get; set; } + + public DeviceStateRequestFilter GetStateRequestFilter() + { + var request = new DeviceStateRequestFilter + { + Start = UseStartTime ? StartTime.ToUniversalTime().ToTimestamp() : default, + End = UseEndTime ? EndTime.ToUniversalTime().ToTimestamp() : default, + CompletedExperimentId = UseExperiment ? SelectedExperimentId : string.Empty, + Interval = Interval.ToDuration() + }; + + if(SelectedDevices is not null) + request.DeviceIds.AddRange(SelectedDevices.Select(d => d.DeviceId)); + + return request; + } +} + + diff --git a/UI/Backend/ViewModels/DeviceStateLogging/DeviceStateFilterViewModelFactory.cs b/UI/Features/DeviceStateLogging/DeviceStateFilterViewModelFactory.cs similarity index 86% rename from UI/Backend/ViewModels/DeviceStateLogging/DeviceStateFilterViewModelFactory.cs rename to UI/Features/DeviceStateLogging/DeviceStateFilterViewModelFactory.cs index 080afd9c..13c3bfd6 100644 --- a/UI/Backend/ViewModels/DeviceStateLogging/DeviceStateFilterViewModelFactory.cs +++ b/UI/Features/DeviceStateLogging/DeviceStateFilterViewModelFactory.cs @@ -1,17 +1,18 @@ -using Ares.Services; - -namespace UI.Backend.ViewModels.DeviceStateLogging; - -public class DeviceStateFilterViewModelFactory -{ - private readonly AresAutomation.AresAutomationClient _automationClient; - readonly ICombinedDeviceGetter _deviceGetter; - - public DeviceStateFilterViewModelFactory(AresAutomation.AresAutomationClient automationClient, ICombinedDeviceGetter deviceGetter) - { - _deviceGetter = deviceGetter; - _automationClient = automationClient; - } - - public DeviceStateFilterViewModel Create() => new DeviceStateFilterViewModel(_automationClient, _deviceGetter); -} +using Ares.Services; +using UI.Application.DeviceStateLogging; + +namespace UI.Features.DeviceStateLogging; + +public class DeviceStateFilterViewModelFactory +{ + private readonly AresAutomation.AresAutomationClient _automationClient; + readonly ICombinedDeviceGetter _deviceGetter; + + public DeviceStateFilterViewModelFactory(AresAutomation.AresAutomationClient automationClient, ICombinedDeviceGetter deviceGetter) + { + _deviceGetter = deviceGetter; + _automationClient = automationClient; + } + + public DeviceStateFilterViewModel Create() => new DeviceStateFilterViewModel(_automationClient, _deviceGetter); +} diff --git a/UI/Pages/Shared/Misc/ManualExecutionWidget.razor b/UI/Features/DeviceStateLogging/ManualDeviceLoggerWidget.razor similarity index 89% rename from UI/Pages/Shared/Misc/ManualExecutionWidget.razor rename to UI/Features/DeviceStateLogging/ManualDeviceLoggerWidget.razor index 59f0b356..3cb52564 100644 --- a/UI/Pages/Shared/Misc/ManualExecutionWidget.razor +++ b/UI/Features/DeviceStateLogging/ManualDeviceLoggerWidget.razor @@ -1,84 +1,83 @@ -@using ReactiveUI.Blazor; -@using System.Diagnostics; -@using Ares.Services -@using UI.Backend.ViewModels.Misc; -@using UI.Backend.Extensions; - -@inherits ReactiveInjectableComponentBase - - -
- @if (ViewModel!.IsCollecting) - { - - } - else - { - - } -
-
- @(_stopwatch.Elapsed.Minutes.ToString("00")):@(_stopwatch.Elapsed.Seconds.ToString("00")):@(_stopwatch.Elapsed.Milliseconds.ToString("000")) -
- @if (ViewModel!.HasData) - { -
- Start - @ViewModel!.CollectionStarted - End - @ViewModel!.CollectionFinished -
- } - -
-
-
-@inject IJSRuntime JS -@code { - private string _error = string.Empty; - - private Stopwatch _stopwatch = new Stopwatch(); - - private async Task StartCollecting() - { - await ViewModel!.StartDataCollection(); - await StartStopwatch(); - } - - private void StopCollecting() - { - ViewModel!.StopDataCollection(); - _stopwatch.Stop(); - } - - private async Task StartStopwatch() - { - _stopwatch.Restart(); - while (ViewModel!.IsCollecting) - { - StateHasChanged(); - await Task.Delay(100); - } - } - - private async Task Export() - { - _error = string.Empty; - var fileData = await ViewModel!.GetExportData(); - if (fileData is null) - { - _error = "Unable to get a state stream. Maybe provider wasn't selected."; - StateHasChanged(); - return; - } - - var fileName = $"{DateTime.Now}"; - fileName = Path.ChangeExtension(fileName, ExportType.Combined.ToFileExtension()); - - using var stream = new MemoryStream(fileData); - using var streamRef = new DotNetStreamReference(stream: stream); - - await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef); - StateHasChanged(); - } -} +@using System.Diagnostics; +@using Ares.Services +@using ReactiveUI.Blazor; +@using UI.Features.Shared + +@inherits ReactiveInjectableComponentBase + + +
+ @if (ViewModel!.IsCollecting) + { + + } + else + { + + } +
+
+ @(_stopwatch.Elapsed.Minutes.ToString("00")):@(_stopwatch.Elapsed.Seconds.ToString("00")):@(_stopwatch.Elapsed.Milliseconds.ToString("000")) +
+ @if (ViewModel!.HasData) + { +
+ Start + @ViewModel!.CollectionStarted + End + @ViewModel!.CollectionFinished +
+ } + +
+
+
+@inject IJSRuntime JS +@code { + private string _error = string.Empty; + + private Stopwatch _stopwatch = new Stopwatch(); + + private async Task StartCollecting() + { + await ViewModel!.StartDataCollection(); + await StartStopwatch(); + } + + private void StopCollecting() + { + ViewModel!.StopDataCollection(); + _stopwatch.Stop(); + } + + private async Task StartStopwatch() + { + _stopwatch.Restart(); + while (ViewModel!.IsCollecting) + { + StateHasChanged(); + await Task.Delay(100); + } + } + + private async Task Export() + { + _error = string.Empty; + var fileData = await ViewModel!.GetExportData(); + if (fileData is null) + { + _error = "Unable to get a state stream. Maybe provider wasn't selected."; + StateHasChanged(); + return; + } + + var fileName = $"{DateTime.Now}"; + fileName = Path.ChangeExtension(fileName, ExportType.Combined.ToFileExtension()); + + using var stream = new MemoryStream(fileData); + using var streamRef = new DotNetStreamReference(stream: stream); + + await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef); + StateHasChanged(); + } +} diff --git a/UI/Pages/Shared/Misc/ManualExecutionWidget.razor.css b/UI/Features/DeviceStateLogging/ManualDeviceLoggerWidget.razor.css similarity index 92% rename from UI/Pages/Shared/Misc/ManualExecutionWidget.razor.css rename to UI/Features/DeviceStateLogging/ManualDeviceLoggerWidget.razor.css index e12510a0..cf6a25aa 100644 --- a/UI/Pages/Shared/Misc/ManualExecutionWidget.razor.css +++ b/UI/Features/DeviceStateLogging/ManualDeviceLoggerWidget.razor.css @@ -1,18 +1,18 @@ -.stopwatch { - font-size: 1.5em; -} - -.collection-date { - font-size: 0.7em; -} - -.date-label { - font-size: 0.5em; - opacity: 0.8; - align-self: center; -} - -.date-grid { - display: grid; - grid-template-columns: 1fr auto; +.stopwatch { + font-size: 1.5em; +} + +.collection-date { + font-size: 0.7em; +} + +.date-label { + font-size: 0.5em; + opacity: 0.8; + align-self: center; +} + +.date-grid { + display: grid; + grid-template-columns: 1fr auto; } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Misc/ManualExecutionWidgetViewModel.cs b/UI/Features/DeviceStateLogging/ManualDeviceLoggerWidgetViewModel.cs similarity index 87% rename from UI/Backend/ViewModels/Misc/ManualExecutionWidgetViewModel.cs rename to UI/Features/DeviceStateLogging/ManualDeviceLoggerWidgetViewModel.cs index dbb0c8b2..db75896d 100644 --- a/UI/Backend/ViewModels/Misc/ManualExecutionWidgetViewModel.cs +++ b/UI/Features/DeviceStateLogging/ManualDeviceLoggerWidgetViewModel.cs @@ -6,15 +6,15 @@ using ReactiveUI; using DeviceStateRequest = Ares.Services.DeviceStateRequest; -namespace UI.Backend.ViewModels.Misc; +namespace UI.Features.DeviceStateLogging; -public class ManualExecutionWidgetViewModel : ReactiveObject +public class ManualDeviceLoggerWidgetViewModel : ReactiveObject { private readonly DeviceStateExportService.DeviceStateExportServiceClient _stateExportClient; readonly AresDevices.AresDevicesClient _devicesClient; private IEnumerable _activeDevices = Array.Empty(); - public ManualExecutionWidgetViewModel(DeviceStateExportService.DeviceStateExportServiceClient stateExportClient, AresDevices.AresDevicesClient devicesClient) + public ManualDeviceLoggerWidgetViewModel(DeviceStateExportService.DeviceStateExportServiceClient stateExportClient, AresDevices.AresDevicesClient devicesClient) { _stateExportClient = stateExportClient; _devicesClient = devicesClient; diff --git a/UI/Pages/Shared/Settings/Logging/LoggingSettingsEdit.razor b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsEdit.razor similarity index 98% rename from UI/Pages/Shared/Settings/Logging/LoggingSettingsEdit.razor rename to UI/Features/DeviceStateLogging/Settings/LoggingSettingsEdit.razor index 8d7ce672..e4f4ccef 100644 --- a/UI/Pages/Shared/Settings/Logging/LoggingSettingsEdit.razor +++ b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsEdit.razor @@ -1,7 +1,6 @@ @using System.Globalization @using System.Linq @using ReactiveUI.Blazor -@using UI.Backend.ViewModels.Settings.Logging @using Ares.Datamodel.Device @inherits ReactiveComponentBase diff --git a/UI/Pages/Shared/Settings/Logging/LoggingSettingsList.razor b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Logging/LoggingSettingsList.razor rename to UI/Features/DeviceStateLogging/Settings/LoggingSettingsList.razor index 0f20df2b..302c583f 100644 --- a/UI/Pages/Shared/Settings/Logging/LoggingSettingsList.razor +++ b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsList.razor @@ -1,6 +1,5 @@ @page "/settings/logging" @using ReactiveUI.Blazor -@using UI.Backend.ViewModels.Settings.Logging @layout SettingsLayout @inherits ReactiveInjectableComponentBase diff --git a/UI/Backend/ViewModels/Settings/Logging/LoggingSettingsListViewModel.cs b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsListViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Logging/LoggingSettingsListViewModel.cs rename to UI/Features/DeviceStateLogging/Settings/LoggingSettingsListViewModel.cs index e6fba9ce..695d3d59 100644 --- a/UI/Backend/ViewModels/Settings/Logging/LoggingSettingsListViewModel.cs +++ b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsListViewModel.cs @@ -4,10 +4,10 @@ using Ares.Services.Device; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Backend.ViewModels.DeviceStateLogging; -using UI.Services.Notification; +using UI.Application.Notifications; +using UI.Application.DeviceStateLogging; -namespace UI.Backend.ViewModels.Settings.Logging; +namespace UI.Features.DeviceStateLogging.Settings; public partial class LoggingSettingsListViewModel : ReactiveObject { @@ -62,4 +62,5 @@ public async Task Save() _notificationService.PushNotification(notif); } } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Logging/LoggingSettingsViewModel.cs b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsViewModel.cs similarity index 98% rename from UI/Backend/ViewModels/Settings/Logging/LoggingSettingsViewModel.cs rename to UI/Features/DeviceStateLogging/Settings/LoggingSettingsViewModel.cs index 615fb4f3..1d360ea8 100644 --- a/UI/Backend/ViewModels/Settings/Logging/LoggingSettingsViewModel.cs +++ b/UI/Features/DeviceStateLogging/Settings/LoggingSettingsViewModel.cs @@ -4,7 +4,7 @@ using ReactiveUI; using ReactiveUI.SourceGenerators; -namespace UI.Backend.ViewModels.Settings.Logging; +namespace UI.Features.DeviceStateLogging.Settings; public partial class LoggingSettingsViewModel : ReactiveObject { diff --git a/UI/Backend/Factories/CM3CamDeviceControlViewModelFactory.cs b/UI/Features/Devices/CM3Camera/CM3CamDeviceControlViewModelFactory.cs similarity index 87% rename from UI/Backend/Factories/CM3CamDeviceControlViewModelFactory.cs rename to UI/Features/Devices/CM3Camera/CM3CamDeviceControlViewModelFactory.cs index 1f2027ba..43bc6cac 100644 --- a/UI/Backend/Factories/CM3CamDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/CM3Camera/CM3CamDeviceControlViewModelFactory.cs @@ -1,12 +1,12 @@ -using Ares.Services.Device; +using Ares.Services.Device; using DynamicData; using FlirCM3.Services; using Google.Protobuf.WellKnownTypes; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Devices.CM3Camera; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.CM3Camera; public class CM3CamDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { @@ -31,3 +31,4 @@ protected override async Task> GetAvailableDe return descriptions; } } + diff --git a/UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraConfigEditView.razor b/UI/Features/Devices/CM3Camera/CM3CameraConfigEditView.razor similarity index 95% rename from UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraConfigEditView.razor rename to UI/Features/Devices/CM3Camera/CM3CameraConfigEditView.razor index 45a7ab31..451ed3ef 100644 --- a/UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraConfigEditView.razor +++ b/UI/Features/Devices/CM3Camera/CM3CameraConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase diff --git a/UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraConfigEditView.razor.css b/UI/Features/Devices/CM3Camera/CM3CameraConfigEditView.razor.css similarity index 100% rename from UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraConfigEditView.razor.css rename to UI/Features/Devices/CM3Camera/CM3CameraConfigEditView.razor.css diff --git a/UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraConfigEditViewModel.cs b/UI/Features/Devices/CM3Camera/CM3CameraConfigEditViewModel.cs similarity index 98% rename from UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraConfigEditViewModel.cs rename to UI/Features/Devices/CM3Camera/CM3CameraConfigEditViewModel.cs index 0cef4fa9..8d32b6bc 100644 --- a/UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraConfigEditViewModel.cs +++ b/UI/Features/Devices/CM3Camera/CM3CameraConfigEditViewModel.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations; using Ares.Services.Device; -namespace UI.Backend.ViewModels.Settings.Device.CM3Camera +namespace UI.Features.Devices.CM3Camera { public class FlirCM3ConfigEditViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Devices/CM3Camera/CM3CameraControlWidgetView.razor b/UI/Features/Devices/CM3Camera/CM3CameraControlWidgetView.razor similarity index 94% rename from UI/Pages/Shared/Devices/CM3Camera/CM3CameraControlWidgetView.razor rename to UI/Features/Devices/CM3Camera/CM3CameraControlWidgetView.razor index 8e8d7c2d..329865f3 100644 --- a/UI/Pages/Shared/Devices/CM3Camera/CM3CameraControlWidgetView.razor +++ b/UI/Features/Devices/CM3Camera/CM3CameraControlWidgetView.razor @@ -1,5 +1,4 @@ -@using UI.Backend.ViewModels.Devices.CM3Camera -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject TooltipService tooltipService @inject IJSRuntime JS diff --git a/UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraSettingsList.razor b/UI/Features/Devices/CM3Camera/CM3CameraSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraSettingsList.razor rename to UI/Features/Devices/CM3Camera/CM3CameraSettingsList.razor index 32f21100..989cf7ea 100644 --- a/UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraSettingsList.razor +++ b/UI/Features/Devices/CM3Camera/CM3CameraSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/cm3camera" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.CM3Camera +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -65,4 +65,5 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraSettingsListViewModel.cs b/UI/Features/Devices/CM3Camera/CM3CameraSettingsListViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraSettingsListViewModel.cs rename to UI/Features/Devices/CM3Camera/CM3CameraSettingsListViewModel.cs index 8bc6ce60..6dad2247 100644 --- a/UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraSettingsListViewModel.cs +++ b/UI/Features/Devices/CM3Camera/CM3CameraSettingsListViewModel.cs @@ -7,7 +7,7 @@ using ReactiveUI; using ReactiveUI.SourceGenerators; -namespace UI.Backend.ViewModels.Settings.Device.CM3Camera +namespace UI.Features.Devices.CM3Camera { public partial class CM3CameraSettingsListViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraSettingsView.razor b/UI/Features/Devices/CM3Camera/CM3CameraSettingsView.razor similarity index 89% rename from UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraSettingsView.razor rename to UI/Features/Devices/CM3Camera/CM3CameraSettingsView.razor index bbafab3c..9d147880 100644 --- a/UI/Pages/Shared/Settings/Device/CM3Camera/CM3CameraSettingsView.razor +++ b/UI/Features/Devices/CM3Camera/CM3CameraSettingsView.razor @@ -1,4 +1,5 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService @inject IUiNotificationService NotificationService @@ -35,3 +36,5 @@ } } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraSettingsViewModel.cs b/UI/Features/Devices/CM3Camera/CM3CameraSettingsViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraSettingsViewModel.cs rename to UI/Features/Devices/CM3Camera/CM3CameraSettingsViewModel.cs index 93c2d8dc..0fb7a3de 100644 --- a/UI/Backend/ViewModels/Settings/Device/CM3Camera/CM3CameraSettingsViewModel.cs +++ b/UI/Features/Devices/CM3Camera/CM3CameraSettingsViewModel.cs @@ -5,9 +5,10 @@ using FlirCM3.Services; using Grpc.Core; using ReactiveUI; -using UI.Backend.Devices; +using UI.Application.Devices; +using UI.Features.Devices.Shared; -namespace UI.Backend.ViewModels.Settings.Device.CM3Camera; +namespace UI.Features.Devices.CM3Camera; public class CM3CameraSettingsViewModel : ReactiveObject { @@ -47,10 +48,10 @@ public Task GetDeviceOperationalStatus() public async Task Save() { var cameraConfig = EditViewModel.Save(); - var updateRequest = new UpdateCameraRequest - { - CameraId = cameraConfig.Id, - Config = cameraConfig + var updateRequest = new UpdateCameraRequest + { + CameraId = cameraConfig.Id, + Config = cameraConfig }; await _cameraClient.UpdateCM3CameraAsync(updateRequest); diff --git a/UI/Backend/ViewModels/Devices/CM3Camera/CM3CameraUnitControlViewModel.cs b/UI/Features/Devices/CM3Camera/CM3CameraUnitControlViewModel.cs similarity index 90% rename from UI/Backend/ViewModels/Devices/CM3Camera/CM3CameraUnitControlViewModel.cs rename to UI/Features/Devices/CM3Camera/CM3CameraUnitControlViewModel.cs index ff018de0..2d8080cd 100644 --- a/UI/Backend/ViewModels/Devices/CM3Camera/CM3CameraUnitControlViewModel.cs +++ b/UI/Features/Devices/CM3Camera/CM3CameraUnitControlViewModel.cs @@ -1,8 +1,8 @@ -using FlirCM3.Services; +using FlirCM3.Services; using ReactiveUI.SourceGenerators; -using UI.Pages.Shared.Devices.CM3Camera; +using UI.Application.Devices; -namespace UI.Backend.ViewModels.Devices.CM3Camera; +namespace UI.Features.Devices.CM3Camera; public partial class CM3CameraUnitControlViewModel : DeviceUnitControlViewModel { @@ -38,3 +38,4 @@ public async Task CaptureImage() [Reactive] public partial byte[]? DisplayData { get; set; } } + diff --git a/UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpConfigEditView.razor b/UI/Features/Devices/ChemyxPump/ChemyxPumpConfigEditView.razor similarity index 92% rename from UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpConfigEditView.razor rename to UI/Features/Devices/ChemyxPump/ChemyxPumpConfigEditView.razor index ee41efc3..66bfbae9 100644 --- a/UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpConfigEditView.razor +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpConfigEditViewModel.cs b/UI/Features/Devices/ChemyxPump/ChemyxPumpConfigEditViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpConfigEditViewModel.cs rename to UI/Features/Devices/ChemyxPump/ChemyxPumpConfigEditViewModel.cs index 5ffe6c93..9f6c0d24 100644 --- a/UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpConfigEditViewModel.cs +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpConfigEditViewModel.cs @@ -2,12 +2,11 @@ using ChemyxPumpPlugin.Config; using ChemyxPumpPlugin.Services; using Google.Protobuf.WellKnownTypes; -using HerkulexDRS.Config; using ReactiveUI; using ReactiveUI.SourceGenerators; using System.ComponentModel.DataAnnotations; -namespace UI.Backend.ViewModels.Settings.Device.ChemyxPump; +namespace UI.Features.Devices.ChemyxPump; public partial class ChemyxPumpConfigEditViewModel : ReactiveObject { diff --git a/UI/Backend/Factories/ChemyxPumpControlViewModelFactory.cs b/UI/Features/Devices/ChemyxPump/ChemyxPumpControlViewModelFactory.cs similarity index 87% rename from UI/Backend/Factories/ChemyxPumpControlViewModelFactory.cs rename to UI/Features/Devices/ChemyxPump/ChemyxPumpControlViewModelFactory.cs index b79773be..f826bb3b 100644 --- a/UI/Backend/Factories/ChemyxPumpControlViewModelFactory.cs +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpControlViewModelFactory.cs @@ -1,12 +1,12 @@ -using Ares.Services.Device; +using Ares.Services.Device; using ChemyxPumpPlugin.Services; using DynamicData; using Google.Protobuf.WellKnownTypes; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Devices.ChemyxPump; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.ChemyxPump; public class ChemyxPumpControlViewModelFactory : DeviceConnectorViewModelFactory { @@ -31,3 +31,4 @@ protected override async Task> GetAvailableDe return pumps; } } + diff --git a/UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpSettingsList.razor b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpSettingsList.razor rename to UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsList.razor index 85abc5f3..ab2db6b5 100644 --- a/UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpSettingsList.razor +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/chemyx" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.ChemyxPump +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,4 +62,6 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + + diff --git a/UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpSettingsListViewModel.cs b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsListViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpSettingsListViewModel.cs rename to UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsListViewModel.cs index 4b1f9d36..773d91cc 100644 --- a/UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpSettingsListViewModel.cs +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsListViewModel.cs @@ -6,7 +6,7 @@ using ReactiveUI; using ReactiveUI.SourceGenerators; -namespace UI.Backend.ViewModels.Settings.Device.ChemyxPump; +namespace UI.Features.Devices.ChemyxPump; public partial class ChemyxPumpSettingsListViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpSettingsView.razor b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsView.razor similarity index 90% rename from UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpSettingsView.razor rename to UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsView.razor index 0bc6eb58..9f62bb3c 100644 --- a/UI/Pages/Shared/Settings/Device/ChemyxPump/ChemyxPumpSettingsView.razor +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsView.razor @@ -1,4 +1,5 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService @inject IUiNotificationService NotificationService @@ -39,3 +40,5 @@ } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpSettingsViewModel.cs b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpSettingsViewModel.cs rename to UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsViewModel.cs index c391a812..22ced0d8 100644 --- a/UI/Backend/ViewModels/Settings/Device/ChemyxPump/ChemyxPumpSettingsViewModel.cs +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpSettingsViewModel.cs @@ -5,9 +5,10 @@ using CommunityToolkit.Mvvm.Messaging; using Grpc.Core; using ReactiveUI; -using UI.Backend.Devices; +using UI.Application.Devices; +using UI.Features.Devices.Shared; -namespace UI.Backend.ViewModels.Settings.Device.ChemyxPump; +namespace UI.Features.Devices.ChemyxPump; public class ChemyxPumpSettingsViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Devices/ChemyxPump/ChemyxPumpUnitControl.razor b/UI/Features/Devices/ChemyxPump/ChemyxPumpUnitControl.razor similarity index 66% rename from UI/Pages/Shared/Devices/ChemyxPump/ChemyxPumpUnitControl.razor rename to UI/Features/Devices/ChemyxPump/ChemyxPumpUnitControl.razor index 984b185a..10312d26 100644 --- a/UI/Pages/Shared/Devices/ChemyxPump/ChemyxPumpUnitControl.razor +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpUnitControl.razor @@ -1,6 +1,4 @@ -@using ChemyxPumpPlugin.Services -@using UI.Backend.ViewModels.Devices.ChemyxPump -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject TooltipService tooltipService diff --git a/UI/Backend/ViewModels/Devices/ChemyxPump/ChemyxPumpUnitControlViewModel.cs b/UI/Features/Devices/ChemyxPump/ChemyxPumpUnitControlViewModel.cs similarity index 84% rename from UI/Backend/ViewModels/Devices/ChemyxPump/ChemyxPumpUnitControlViewModel.cs rename to UI/Features/Devices/ChemyxPump/ChemyxPumpUnitControlViewModel.cs index 1bd0936a..826e8ee3 100644 --- a/UI/Backend/ViewModels/Devices/ChemyxPump/ChemyxPumpUnitControlViewModel.cs +++ b/UI/Features/Devices/ChemyxPump/ChemyxPumpUnitControlViewModel.cs @@ -1,7 +1,7 @@ -using ChemyxPumpPlugin.Services; -using UI.Pages.Shared.Devices.ChemyxPump; +using ChemyxPumpPlugin.Services; +using UI.Application.Devices; -namespace UI.Backend.ViewModels.Devices.ChemyxPump; +namespace UI.Features.Devices.ChemyxPump; public class ChemyxPumpUnitControlViewModel : DeviceUnitControlViewModel { @@ -20,3 +20,4 @@ public ChemyxPumpUnitControlViewModel(string deviceId, string deviceName, Chemyx public IndividualPumpViewModel[] PumpViewModels { get; } } + diff --git a/UI/Pages/Shared/Devices/ChemyxPump/IndividualPump.razor b/UI/Features/Devices/ChemyxPump/IndividualPump.razor similarity index 97% rename from UI/Pages/Shared/Devices/ChemyxPump/IndividualPump.razor rename to UI/Features/Devices/ChemyxPump/IndividualPump.razor index 79a27fa3..86224a6a 100644 --- a/UI/Pages/Shared/Devices/ChemyxPump/IndividualPump.razor +++ b/UI/Features/Devices/ChemyxPump/IndividualPump.razor @@ -1,8 +1,4 @@ -@using UI.Backend.ViewModels.Devices.ChemyxPump -@using ChemyxPumpPlugin.Services -@using System.Reactive.Linq -@using Radzen -@using Radzen.Blazor +@using ChemyxPumpPlugin.Services @inherits ReactiveUI.Blazor.ReactiveComponentBase @implements IAsyncDisposable diff --git a/UI/Backend/ViewModels/Devices/ChemyxPump/IndividualPumpViewModel.cs b/UI/Features/Devices/ChemyxPump/IndividualPumpViewModel.cs similarity index 99% rename from UI/Backend/ViewModels/Devices/ChemyxPump/IndividualPumpViewModel.cs rename to UI/Features/Devices/ChemyxPump/IndividualPumpViewModel.cs index 4fd34f80..39b2ac5b 100644 --- a/UI/Backend/ViewModels/Devices/ChemyxPump/IndividualPumpViewModel.cs +++ b/UI/Features/Devices/ChemyxPump/IndividualPumpViewModel.cs @@ -4,7 +4,7 @@ using ReactiveUI; using ReactiveUI.SourceGenerators; -namespace UI.Backend.ViewModels.Devices.ChemyxPump; +namespace UI.Features.Devices.ChemyxPump; public partial class IndividualPumpViewModel : ReactiveObject, IAsyncDisposable { diff --git a/UI/Pages/Shared/Devices/ChemyxPump/Syringe.razor b/UI/Features/Devices/ChemyxPump/Syringe.razor similarity index 98% rename from UI/Pages/Shared/Devices/ChemyxPump/Syringe.razor rename to UI/Features/Devices/ChemyxPump/Syringe.razor index bf0ad2f2..5a2cc5eb 100644 --- a/UI/Pages/Shared/Devices/ChemyxPump/Syringe.razor +++ b/UI/Features/Devices/ChemyxPump/Syringe.razor @@ -1,5 +1,4 @@ -@using Microsoft.AspNetCore.Components -@using System.Globalization +@using System.Globalization @* Syringe.razor Parameters: diff --git a/UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerConfigEditView.razor b/UI/Features/Devices/LaserChiller/LaserChillerConfigEditView.razor similarity index 90% rename from UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerConfigEditView.razor rename to UI/Features/Devices/LaserChiller/LaserChillerConfigEditView.razor index 2dffbf5f..6d6d3372 100644 --- a/UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerConfigEditView.razor +++ b/UI/Features/Devices/LaserChiller/LaserChillerConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerConfigEditViewModel.cs b/UI/Features/Devices/LaserChiller/LaserChillerConfigEditViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerConfigEditViewModel.cs rename to UI/Features/Devices/LaserChiller/LaserChillerConfigEditViewModel.cs index 3eecd33a..c689a2cd 100644 --- a/UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerConfigEditViewModel.cs +++ b/UI/Features/Devices/LaserChiller/LaserChillerConfigEditViewModel.cs @@ -1,12 +1,12 @@ -using Chiller.Config; +using Ares.Services.Device; +using Chiller.Config; using Chiller.Services; using Google.Protobuf.WellKnownTypes; using ReactiveUI; using ReactiveUI.SourceGenerators; using System.ComponentModel.DataAnnotations; -using Ares.Services.Device; -namespace UI.Backend.ViewModels.Settings.Device.LaserChiller; +namespace UI.Features.Devices.LaserChiller; public partial class LaserChillerConfigEditViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Devices/LaserChiller/LaserChillerControlWidgetView.razor b/UI/Features/Devices/LaserChiller/LaserChillerControlWidgetView.razor similarity index 92% rename from UI/Pages/Shared/Devices/LaserChiller/LaserChillerControlWidgetView.razor rename to UI/Features/Devices/LaserChiller/LaserChillerControlWidgetView.razor index 486b8033..88f9dab2 100644 --- a/UI/Pages/Shared/Devices/LaserChiller/LaserChillerControlWidgetView.razor +++ b/UI/Features/Devices/LaserChiller/LaserChillerControlWidgetView.razor @@ -1,4 +1,3 @@ -@using UI.Backend.ViewModels.Devices.LaserChiller @inherits ReactiveUI.Blazor.ReactiveComponentBase @inject TooltipService tooltipService diff --git a/UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerSettingsList.razor b/UI/Features/Devices/LaserChiller/LaserChillerSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerSettingsList.razor rename to UI/Features/Devices/LaserChiller/LaserChillerSettingsList.razor index 5a6b6938..e2be075e 100644 --- a/UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerSettingsList.razor +++ b/UI/Features/Devices/LaserChiller/LaserChillerSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/laserchiller" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.LaserChiller +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,3 +62,5 @@ else private bool SavingInProgress { get; set; } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerSettingsListViewModel.cs b/UI/Features/Devices/LaserChiller/LaserChillerSettingsListViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerSettingsListViewModel.cs rename to UI/Features/Devices/LaserChiller/LaserChillerSettingsListViewModel.cs index c6e3272d..495d21df 100644 --- a/UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerSettingsListViewModel.cs +++ b/UI/Features/Devices/LaserChiller/LaserChillerSettingsListViewModel.cs @@ -7,7 +7,7 @@ using ReactiveUI; using ReactiveUI.SourceGenerators; -namespace UI.Backend.ViewModels.Settings.Device.LaserChiller; +namespace UI.Features.Devices.LaserChiller; public partial class LaserChillerSettingsListViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerSettingsView.razor b/UI/Features/Devices/LaserChiller/LaserChillerSettingsView.razor similarity index 90% rename from UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerSettingsView.razor rename to UI/Features/Devices/LaserChiller/LaserChillerSettingsView.razor index 2ab234fa..9dc1763a 100644 --- a/UI/Pages/Shared/Settings/Device/LaserChiller/LaserChillerSettingsView.razor +++ b/UI/Features/Devices/LaserChiller/LaserChillerSettingsView.razor @@ -1,4 +1,5 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService @inject IUiNotificationService NotificationService @@ -39,3 +40,5 @@ } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerSettingsViewModel.cs b/UI/Features/Devices/LaserChiller/LaserChillerSettingsViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerSettingsViewModel.cs rename to UI/Features/Devices/LaserChiller/LaserChillerSettingsViewModel.cs index bbddc2d5..96d780bf 100644 --- a/UI/Backend/ViewModels/Settings/Device/LaserChiller/LaserChillerSettingsViewModel.cs +++ b/UI/Features/Devices/LaserChiller/LaserChillerSettingsViewModel.cs @@ -5,9 +5,10 @@ using CommunityToolkit.Mvvm.Messaging; using Grpc.Core; using ReactiveUI; -using UI.Backend.Devices; +using UI.Application.Devices; +using UI.Features.Devices.Shared; -namespace UI.Backend.ViewModels.Settings.Device.LaserChiller; +namespace UI.Features.Devices.LaserChiller; public class LaserChillerSettingsViewModel : ReactiveObject { @@ -47,10 +48,10 @@ public Task GetDeviceOperationalStatus() public async Task Save() { var laserConfig = EditViewModel.Save(); - var updateRequest = new UpdateChillerRequest - { - ChillerId = _deviceConfig.UniqueId, - Config = laserConfig + var updateRequest = new UpdateChillerRequest + { + ChillerId = _deviceConfig.UniqueId, + Config = laserConfig }; await _chillerClient.UpdateChillerAsync(updateRequest); diff --git a/UI/Backend/ViewModels/Devices/LaserChiller/LaserChillerUnitControlViewModel.cs b/UI/Features/Devices/LaserChiller/LaserChillerUnitControlViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Devices/LaserChiller/LaserChillerUnitControlViewModel.cs rename to UI/Features/Devices/LaserChiller/LaserChillerUnitControlViewModel.cs index 17897b08..87da30c4 100644 --- a/UI/Backend/ViewModels/Devices/LaserChiller/LaserChillerUnitControlViewModel.cs +++ b/UI/Features/Devices/LaserChiller/LaserChillerUnitControlViewModel.cs @@ -1,7 +1,8 @@ -using Chiller.Services; +using Chiller.Services; using ReactiveUI.SourceGenerators; +using UI.Application.Devices; -namespace UI.Backend.ViewModels.Devices.LaserChiller; +namespace UI.Features.Devices.LaserChiller; public partial class LaserChillerUnitControlViewModel : DeviceUnitControlViewModel { @@ -42,3 +43,4 @@ public async Task SetChillerToRunningMode() public partial double CurrentManifoldTemperature { get; set; } public double DesiredTemperature { get; set; } } + diff --git a/UI/Backend/ViewModels/Settings/Device/CharacterRangeAttribute.cs b/UI/Features/Devices/Mfc/CharacterRangeAttribute.cs similarity index 92% rename from UI/Backend/ViewModels/Settings/Device/CharacterRangeAttribute.cs rename to UI/Features/Devices/Mfc/CharacterRangeAttribute.cs index 5a232ade..739439e4 100644 --- a/UI/Backend/ViewModels/Settings/Device/CharacterRangeAttribute.cs +++ b/UI/Features/Devices/Mfc/CharacterRangeAttribute.cs @@ -1,44 +1,44 @@ -using System.ComponentModel.DataAnnotations; - -namespace UI.Backend.ViewModels.Settings.Device; - -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] -public class CharacterRangeAttribute : ValidationAttribute -{ - private readonly char _max; - private readonly char _min; - - public CharacterRangeAttribute(char min, char max) - { - _min = min; - _max = max; - } - - protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) - { - switch (value) - { - case string str when string.IsNullOrEmpty(str): - return ValidationResult.Success; - case string str when str.Length != 1: - return new ValidationResult("Input must be a single character."); - case string str: - { - var character = str.First(); - return CheckChar(character); - } - case char chr: - return CheckChar(chr); - } - - return new ValidationResult("Bork"); - } - - private ValidationResult? CheckChar(char character) - { - if (character >= _min && character <= _max) - return ValidationResult.Success; - - return new ValidationResult($"The character must be between {_min} and {_max}"); - } -} +using System.ComponentModel.DataAnnotations; + +namespace UI.Features.Devices.Mfc; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] +public class CharacterRangeAttribute : ValidationAttribute +{ + private readonly char _max; + private readonly char _min; + + public CharacterRangeAttribute(char min, char max) + { + _min = min; + _max = max; + } + + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) + { + switch (value) + { + case string str when string.IsNullOrEmpty(str): + return ValidationResult.Success; + case string str when str.Length != 1: + return new ValidationResult("Input must be a single character."); + case string str: + { + var character = str.First(); + return CheckChar(character); + } + case char chr: + return CheckChar(chr); + } + + return new ValidationResult("Bork"); + } + + private ValidationResult? CheckChar(char character) + { + if (character >= _min && character <= _max) + return ValidationResult.Success; + + return new ValidationResult($"The character must be between {_min} and {_max}"); + } +} diff --git a/UI/Backend/Factories/MFCDeviceControlViewModelFactory.cs b/UI/Features/Devices/Mfc/MFCDeviceControlViewModelFactory.cs similarity index 85% rename from UI/Backend/Factories/MFCDeviceControlViewModelFactory.cs rename to UI/Features/Devices/Mfc/MFCDeviceControlViewModelFactory.cs index b5d59c50..aabd27b8 100644 --- a/UI/Backend/Factories/MFCDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/Mfc/MFCDeviceControlViewModelFactory.cs @@ -1,13 +1,12 @@ -using Ares.Alicat.Mfc.Messaging; +using Ares.Alicat.Mfc.Messaging; using Ares.Services.Device; using DynamicData; using Google.Protobuf.WellKnownTypes; -using System.Reactive.Linq; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Devices.Mfc; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.Mfc; public class MFCDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { @@ -33,3 +32,4 @@ protected override async Task> GetAvailableDe return mfcs; } } + diff --git a/UI/Pages/Shared/Settings/Device/Mfc/MfcConfigEditView.razor b/UI/Features/Devices/Mfc/MfcConfigEditView.razor similarity index 95% rename from UI/Pages/Shared/Settings/Device/Mfc/MfcConfigEditView.razor rename to UI/Features/Devices/Mfc/MfcConfigEditView.razor index 2dc5c42b..9c556065 100644 --- a/UI/Pages/Shared/Settings/Device/Mfc/MfcConfigEditView.razor +++ b/UI/Features/Devices/Mfc/MfcConfigEditView.razor @@ -1,92 +1,91 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@using UI.Backend.ViewModels.Settings.Device.Mfc; -@using Ares.Alicat.Mfc.Config; - - -
- - - @if (ViewModel!.AvailablePorts is not null) - { - @foreach (var port in ViewModel!.AvailablePorts) - { - - } - } - - - - - - @if (IdsLoading) - { -
- -
- } - -
- - Simulated -
-
- - Has Valve -
-
- - - -
-@code { - - [Parameter] - public Action OnValidSubmit { get; set; } = delegate { - }; - - private bool IdsLoading { get; set; } - - private async Task SimulatedChanged(bool val) - { - await ReloadIds(); - } - - private async Task PortChanged(ChangeEventArgs obj) - { - if (obj.Value is not string stringValue) - throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); - ViewModel!.Port = stringValue; - await ReloadIds(); - } - - private async Task ReloadIds() - { - IdsLoading = true; - StateHasChanged(); - await ViewModel!.UpdateAvailableIds(); - IdsLoading = false; - StateHasChanged(); - } -} +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using Ares.Alicat.Mfc.Config; + + +
+ + + @if (ViewModel!.AvailablePorts is not null) + { + @foreach (var port in ViewModel!.AvailablePorts) + { + + } + } + + + + + + @if (IdsLoading) + { +
+ +
+ } + +
+ + Simulated +
+
+ + Has Valve +
+
+ + + +
+@code { + + [Parameter] + public Action OnValidSubmit { get; set; } = delegate { + }; + + private bool IdsLoading { get; set; } + + private async Task SimulatedChanged(bool val) + { + await ReloadIds(); + } + + private async Task PortChanged(ChangeEventArgs obj) + { + if (obj.Value is not string stringValue) + throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); + ViewModel!.Port = stringValue; + await ReloadIds(); + } + + private async Task ReloadIds() + { + IdsLoading = true; + StateHasChanged(); + await ViewModel!.UpdateAvailableIds(); + IdsLoading = false; + StateHasChanged(); + } +} diff --git a/UI/Pages/Shared/Settings/Device/Mfc/MfcConfigEditView.razor.css b/UI/Features/Devices/Mfc/MfcConfigEditView.razor.css similarity index 92% rename from UI/Pages/Shared/Settings/Device/Mfc/MfcConfigEditView.razor.css rename to UI/Features/Devices/Mfc/MfcConfigEditView.razor.css index 9b4dfd1f..8de1fc05 100644 --- a/UI/Pages/Shared/Settings/Device/Mfc/MfcConfigEditView.razor.css +++ b/UI/Features/Devices/Mfc/MfcConfigEditView.razor.css @@ -1,41 +1,41 @@ -.mfc-property-grid { - display: grid; - grid-template: - "name ." 1fr - "port ." 1fr - "simulated ." 1fr - "id spinner" 1fr - "type ." 1fr - "valve ." 1fr - / auto 40px -} - -.mfc-name { - grid-area: name; -} - -.mfc-port { - grid-area: port; -} - -.mfc-id { - grid-area: id; -} - -.mfc-type { - grid-area: type; -} - -.mfc-simulated { - grid-area: simulated; -} - -.mfc-valve { - grid-area: valve; -} - -.mfc-spinner { - grid-area: spinner; - align-self: center; - justify-self: center; +.mfc-property-grid { + display: grid; + grid-template: + "name ." 1fr + "port ." 1fr + "simulated ." 1fr + "id spinner" 1fr + "type ." 1fr + "valve ." 1fr + / auto 40px +} + +.mfc-name { + grid-area: name; +} + +.mfc-port { + grid-area: port; +} + +.mfc-id { + grid-area: id; +} + +.mfc-type { + grid-area: type; +} + +.mfc-simulated { + grid-area: simulated; +} + +.mfc-valve { + grid-area: valve; +} + +.mfc-spinner { + grid-area: spinner; + align-self: center; + justify-self: center; } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Settings/Device/Mfc/MfcConfigEditViewModel.cs b/UI/Features/Devices/Mfc/MfcConfigEditViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/Mfc/MfcConfigEditViewModel.cs rename to UI/Features/Devices/Mfc/MfcConfigEditViewModel.cs index bb18d12a..af6902bd 100644 --- a/UI/Backend/ViewModels/Settings/Device/Mfc/MfcConfigEditViewModel.cs +++ b/UI/Features/Devices/Mfc/MfcConfigEditViewModel.cs @@ -1,136 +1,136 @@ -using System.ComponentModel.DataAnnotations; -using Ares.Alicat.Mfc.Config; -using Ares.Alicat.Mfc.Messaging; -using Ares.Services.Device; -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using Enum = System.Enum; - -namespace UI.Backend.ViewModels.Settings.Device.Mfc; - -public partial class MfcConfigEditViewModel : ReactiveObject -{ - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly MfcRpc.MfcRpcClient _mfcClient; - private readonly MfcConfig _mfcConfig; - private readonly ILogger _logger; - private string? _name; - - public MfcConfigEditViewModel(MfcRpc.MfcRpcClient mfcClient, - AresDevices.AresDevicesClient devicesClient, - MfcConfig mfcConfig, - ILogger logger - ) - { - _mfcClient = mfcClient; - _devicesClient = devicesClient; - _mfcConfig = mfcConfig; - _logger = logger; - _ = UpdateAvailableSerialPorts(); - _name = _mfcConfig.Name; - Id = _mfcConfig.Id; - Port = _mfcConfig.PortName; - Simulated = _mfcConfig.Simulated; - HasValve = _mfcConfig.HasValve; - SelectedMfcType = _mfcConfig.MfcType == MfcType.None ? MfcType.Normal : _mfcConfig.MfcType; - SetpointSource = SetpointSource.UnknownSource; - } - - public MfcConfigEditViewModel(MfcRpc.MfcRpcClient mfcClient, AresDevices.AresDevicesClient devicesClient, ILogger logger) - { - _mfcClient = mfcClient; - _devicesClient = devicesClient; - _logger = logger; - _mfcConfig = new MfcConfig(); - _ = UpdateAvailableSerialPorts(); - NewConfig = true; - SetpointSource = SetpointSource.UnknownSource; - } - - [Required] - public string? Name - { - get => _name; - - set - { - if(!NewConfig) - return; - - _name = value; - } - } - - [Required] - public string? Port { get; set; } - - public bool NewConfig { get; } - - [Required] - [CharacterRange('A', 'Z', ErrorMessage = "Id must be any capital letter from A to Z")] - [StringLength(1, ErrorMessage = "Id must be one character")] - public string? Id { get; set; } - - public bool HasValve { get; set; } = true; - - public MfcType[] AvailableMfcTypes { get; } = System.Enum.GetValues(); - - public MfcType SelectedMfcType { get; set; } = MfcType.Normal; - - [Reactive] - public partial SetpointSource SetpointSource { get; private set; } - - public SetpointSource[] AvailableSetpointSources { get; } = - Enum.GetValues().Except([SetpointSource.UnknownSource]).ToArray(); - - public bool Simulated { get; set; } - - [Reactive] - public partial IEnumerable? AvailableIds { get; private set; } - - [Reactive] - public partial IEnumerable? AvailablePorts { get; private set; } - - public bool Modified => _mfcConfig.Id != Id || _mfcConfig.Name != Name || _mfcConfig.PortName != Port || _mfcConfig.Simulated != Simulated || _mfcConfig.HasValve != HasValve || _mfcConfig.MfcType != SelectedMfcType; - - public async Task UpdateSetpointSource(SetpointSource source) - { - if (SetpointSource == source) - return; - - try - { - await _mfcClient.SetSetpointSourceAsync(new SetSetpointSourceRequest { Id = _mfcConfig.Id, Source = source }); - var newSource = await _mfcClient.GetSetpointSourceAsync(new DeviceRequest { DeviceId = _mfcConfig.Id }); - SetpointSource = newSource.Source; - } - catch (Exception e) - { - SetpointSource = SetpointSource.UnknownSource; - _logger.LogError(e, "Failed to update setpoint source for alicat {}", _mfcConfig.Name); - } - } - - public async Task UpdateAvailableSerialPorts() - { - AvailablePorts = null; - Port = null; - var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); - AvailablePorts = ports.SerialPorts; - } - - public async Task UpdateAvailableIds() - { - AvailableIds = null; - Id = null; - if(Port is null) - return; - - var ids = await _mfcClient.GetAvailableIdsAsync(new GetAvailableIdsRequest { PortName = Port, Simulated = Simulated }); - AvailableIds = ids.Ids.Select(s => s.First()); - } - - public MfcConfig Save() - => Modified ? new MfcConfig { Id = Id, Name = Name, PortName = Port, Simulated = Simulated, HasValve = HasValve, MfcType = SelectedMfcType } : _mfcConfig; -} +using Ares.Alicat.Mfc.Config; +using Ares.Alicat.Mfc.Messaging; +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using System.ComponentModel.DataAnnotations; +using Enum = System.Enum; + +namespace UI.Features.Devices.Mfc; + +public partial class MfcConfigEditViewModel : ReactiveObject +{ + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly MfcRpc.MfcRpcClient _mfcClient; + private readonly MfcConfig _mfcConfig; + private readonly ILogger _logger; + private string? _name; + + public MfcConfigEditViewModel(MfcRpc.MfcRpcClient mfcClient, + AresDevices.AresDevicesClient devicesClient, + MfcConfig mfcConfig, + ILogger logger + ) + { + _mfcClient = mfcClient; + _devicesClient = devicesClient; + _mfcConfig = mfcConfig; + _logger = logger; + _ = UpdateAvailableSerialPorts(); + _name = _mfcConfig.Name; + Id = _mfcConfig.Id; + Port = _mfcConfig.PortName; + Simulated = _mfcConfig.Simulated; + HasValve = _mfcConfig.HasValve; + SelectedMfcType = _mfcConfig.MfcType == MfcType.None ? MfcType.Normal : _mfcConfig.MfcType; + SetpointSource = SetpointSource.UnknownSource; + } + + public MfcConfigEditViewModel(MfcRpc.MfcRpcClient mfcClient, AresDevices.AresDevicesClient devicesClient, ILogger logger) + { + _mfcClient = mfcClient; + _devicesClient = devicesClient; + _logger = logger; + _mfcConfig = new MfcConfig(); + _ = UpdateAvailableSerialPorts(); + NewConfig = true; + SetpointSource = SetpointSource.UnknownSource; + } + + [Required] + public string? Name + { + get => _name; + + set + { + if(!NewConfig) + return; + + _name = value; + } + } + + [Required] + public string? Port { get; set; } + + public bool NewConfig { get; } + + [Required] + [CharacterRange('A', 'Z', ErrorMessage = "Id must be any capital letter from A to Z")] + [StringLength(1, ErrorMessage = "Id must be one character")] + public string? Id { get; set; } + + public bool HasValve { get; set; } = true; + + public MfcType[] AvailableMfcTypes { get; } = System.Enum.GetValues(); + + public MfcType SelectedMfcType { get; set; } = MfcType.Normal; + + [Reactive] + public partial SetpointSource SetpointSource { get; private set; } + + public SetpointSource[] AvailableSetpointSources { get; } = + Enum.GetValues().Except([SetpointSource.UnknownSource]).ToArray(); + + public bool Simulated { get; set; } + + [Reactive] + public partial IEnumerable? AvailableIds { get; private set; } + + [Reactive] + public partial IEnumerable? AvailablePorts { get; private set; } + + public bool Modified => _mfcConfig.Id != Id || _mfcConfig.Name != Name || _mfcConfig.PortName != Port || _mfcConfig.Simulated != Simulated || _mfcConfig.HasValve != HasValve || _mfcConfig.MfcType != SelectedMfcType; + + public async Task UpdateSetpointSource(SetpointSource source) + { + if (SetpointSource == source) + return; + + try + { + await _mfcClient.SetSetpointSourceAsync(new SetSetpointSourceRequest { Id = _mfcConfig.Id, Source = source }); + var newSource = await _mfcClient.GetSetpointSourceAsync(new DeviceRequest { DeviceId = _mfcConfig.Id }); + SetpointSource = newSource.Source; + } + catch (Exception e) + { + SetpointSource = SetpointSource.UnknownSource; + _logger.LogError(e, "Failed to update setpoint source for alicat {}", _mfcConfig.Name); + } + } + + public async Task UpdateAvailableSerialPorts() + { + AvailablePorts = null; + Port = null; + var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); + AvailablePorts = ports.SerialPorts; + } + + public async Task UpdateAvailableIds() + { + AvailableIds = null; + Id = null; + if(Port is null) + return; + + var ids = await _mfcClient.GetAvailableIdsAsync(new GetAvailableIdsRequest { PortName = Port, Simulated = Simulated }); + AvailableIds = ids.Ids.Select(s => s.First()); + } + + public MfcConfig Save() + => Modified ? new MfcConfig { Id = Id, Name = Name, PortName = Port, Simulated = Simulated, HasValve = HasValve, MfcType = SelectedMfcType } : _mfcConfig; +} diff --git a/UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsList.razor b/UI/Features/Devices/Mfc/MfcSettingsList.razor similarity index 97% rename from UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsList.razor rename to UI/Features/Devices/Mfc/MfcSettingsList.razor index 60da5027..877dfdc6 100644 --- a/UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsList.razor +++ b/UI/Features/Devices/Mfc/MfcSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/alicat" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.Mfc +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,4 +62,6 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + + diff --git a/UI/Backend/ViewModels/Settings/Device/Mfc/MfcSettingsListViewModel.cs b/UI/Features/Devices/Mfc/MfcSettingsListViewModel.cs similarity index 92% rename from UI/Backend/ViewModels/Settings/Device/Mfc/MfcSettingsListViewModel.cs rename to UI/Features/Devices/Mfc/MfcSettingsListViewModel.cs index b2b29ad5..9ee909c3 100644 --- a/UI/Backend/ViewModels/Settings/Device/Mfc/MfcSettingsListViewModel.cs +++ b/UI/Features/Devices/Mfc/MfcSettingsListViewModel.cs @@ -1,61 +1,60 @@ -using AlicatMFC; -using Ares.Alicat.Mfc.Config; -using Ares.Alicat.Mfc.Messaging; -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using Microsoft.Extensions.Logging; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels.Settings.Device.Mfc; - -public partial class MfcSettingsListViewModel : ReactiveObject -{ - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly MfcRpc.MfcRpcClient _mfcClient; - private readonly IMessenger _messenger; - private readonly ILoggerFactory _loggerFactory; - - public MfcSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, MfcRpc.MfcRpcClient mfcClient, IMessenger messenger, ILoggerFactory loggerFactory) - { - _devicesClient = devicesClient; - _mfcClient = mfcClient; - _messenger = messenger; - _loggerFactory = loggerFactory; - _ = UpdateConfigs(); - } - - [Reactive] - public partial IEnumerable? SettingsViewModels { get; private set; } - - private void UpdateViewModels(IEnumerable deviceConfigs) - { - var viewModels = deviceConfigs.Select(config => new MfcSettingsViewModel(config, _mfcClient, _devicesClient, _loggerFactory, _messenger, OnConfigRemoved)).ToArray(); - SettingsViewModels = viewModels; - } - - public MfcConfigEditViewModel GetNewConfigEditViewModel() - => new(_mfcClient, _devicesClient, _loggerFactory.CreateLogger()); - - private async Task UpdateConfigs() - { - SettingsViewModels = null; - var configs = await _devicesClient - .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IMassFlowController).FullName }); - - UpdateViewModels(configs.Configs); - } - - public async Task OnConfigRemoved() - { - SettingsViewModels = null; - await UpdateConfigs(); - } - - public async Task AddNewConfig(MfcConfig config) - { - await _mfcClient.AddMfcAsync(config); - await UpdateConfigs(); - } -} +using AlicatMFC; +using Ares.Alicat.Mfc.Config; +using Ares.Alicat.Mfc.Messaging; +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace UI.Features.Devices.Mfc; + +public partial class MfcSettingsListViewModel : ReactiveObject +{ + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly MfcRpc.MfcRpcClient _mfcClient; + private readonly IMessenger _messenger; + private readonly ILoggerFactory _loggerFactory; + + public MfcSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, MfcRpc.MfcRpcClient mfcClient, IMessenger messenger, ILoggerFactory loggerFactory) + { + _devicesClient = devicesClient; + _mfcClient = mfcClient; + _messenger = messenger; + _loggerFactory = loggerFactory; + _ = UpdateConfigs(); + } + + [Reactive] + public partial IEnumerable? SettingsViewModels { get; private set; } + + private void UpdateViewModels(IEnumerable deviceConfigs) + { + var viewModels = deviceConfigs.Select(config => new MfcSettingsViewModel(config, _mfcClient, _devicesClient, _loggerFactory, _messenger, OnConfigRemoved)).ToArray(); + SettingsViewModels = viewModels; + } + + public MfcConfigEditViewModel GetNewConfigEditViewModel() + => new(_mfcClient, _devicesClient, _loggerFactory.CreateLogger()); + + private async Task UpdateConfigs() + { + SettingsViewModels = null; + var configs = await _devicesClient + .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IMassFlowController).FullName }); + + UpdateViewModels(configs.Configs); + } + + public async Task OnConfigRemoved() + { + SettingsViewModels = null; + await UpdateConfigs(); + } + + public async Task AddNewConfig(MfcConfig config) + { + await _mfcClient.AddMfcAsync(config); + await UpdateConfigs(); + } +} diff --git a/UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsView.razor b/UI/Features/Devices/Mfc/MfcSettingsView.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsView.razor rename to UI/Features/Devices/Mfc/MfcSettingsView.razor index eee5dde3..fc1ab407 100644 --- a/UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsView.razor +++ b/UI/Features/Devices/Mfc/MfcSettingsView.razor @@ -1,171 +1,172 @@ -@using Ares.Alicat.Mfc.Config -@using UI.Backend.ViewModels.Settings.Device.Mfc -@using Ares.Alicat.Mfc.Messaging -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - - -
-
- Port - - @ViewModel!.MfcConfig.PortName -
- - @if (ViewModel!.DeviceActive) - { -
- @if (_changing) - { -
-
- -
-
- } -
- Current Gas - @ViewModel!.CurrentGas - @if (ViewModel!.AvailableGases is not null) - { - - - } - - Current Id - @ViewModel!.CurrentId - @if (ViewModel!.AvailableIds is not null) - { - - - } - @if (ViewModel!.MfcConfig.MfcType == MfcType.Basis2) - { - Setpoint Source - @ViewModel!.SelectedSetpointSource - - @if (ViewModel!.SetpointSourceUpdating) - { - - - - } - } -
-
- } - else if(_initActive) - { - - } -
-
- -@code { - private bool _initActive; - private bool _changing; - - private string Name => ViewModel!.MfcConfig.Name + (ViewModel!.MfcConfig.Simulated ? " -- (Simulated)" : ""); - - protected override async Task OnInitializedAsync() - { - _initActive = true; - StateHasChanged(); - await ViewModel!.Init(); - _initActive = false; - StateHasChanged(); - } - - private async Task EditCallback() - { - var viewModel = ViewModel!.EditViewModel; - var result = await DialogService.OpenAsync("Edit MFC", ds => - @); - - if (result is true && viewModel.Modified) - { - await ViewModel!.Save(); - NotificationService.Success("Config has been saved"); - } - } - - private async Task RemoveCallback() - { - var result = await DialogService.Confirm($"Delete {ViewModel!.MfcConfig.Name}?"); - if (result is true) - { - await ViewModel!.Remove(); - } - } - - private async Task ChangeIdClicked() - { - _changing = true; - await ViewModel!.ChangeId(); - await ViewModel!.Init(); - _changing = false; - StateHasChanged(); - } - - private async Task ChangeGasClicked() - { - _changing = true; - try - { - await ViewModel!.ChangeGas(); - } - catch(TimeoutException) - { - NotificationService.Error("Gas change command timed out."); - } - await ViewModel!.Init(); - _changing = false; - StateHasChanged(); - } - - private string SelectedSetpointSourceValue - { - get => ViewModel?.SelectedSetpointSource.ToString() ?? SetpointSource.UnknownSource.ToString(); - set - { - if (ViewModel is null) - return; - - if (!Enum.TryParse(value, out var parsed)) - return; - - if (ViewModel.SetpointSourceUpdating || ViewModel.SelectedSetpointSource == parsed) - return; - - _ = InvokeAsync(() => ChangeSetpointSourceAsync(parsed)); - } - } - - private async Task ChangeSetpointSourceAsync(SetpointSource source) - { - await ViewModel!.UpdateSetpointSource(source); - StateHasChanged(); - } -} \ No newline at end of file +@using Ares.Alicat.Mfc.Config +@using Ares.Alicat.Mfc.Messaging +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + + +
+
+ Port - + @ViewModel!.MfcConfig.PortName +
+ + @if (ViewModel!.DeviceActive) + { +
+ @if (_changing) + { +
+
+ +
+
+ } +
+ Current Gas + @ViewModel!.CurrentGas + @if (ViewModel!.AvailableGases is not null) + { + + + } + + Current Id + @ViewModel!.CurrentId + @if (ViewModel!.AvailableIds is not null) + { + + + } + @if (ViewModel!.MfcConfig.MfcType == MfcType.Basis2) + { + Setpoint Source + @ViewModel!.SelectedSetpointSource + + @if (ViewModel!.SetpointSourceUpdating) + { + + + + } + } +
+
+ } + else if(_initActive) + { + + } +
+
+ +@code { + private bool _initActive; + private bool _changing; + + private string Name => ViewModel!.MfcConfig.Name + (ViewModel!.MfcConfig.Simulated ? " -- (Simulated)" : ""); + + protected override async Task OnInitializedAsync() + { + _initActive = true; + StateHasChanged(); + await ViewModel!.Init(); + _initActive = false; + StateHasChanged(); + } + + private async Task EditCallback() + { + var viewModel = ViewModel!.EditViewModel; + var result = await DialogService.OpenAsync("Edit MFC", ds => + @); + + if (result is true && viewModel.Modified) + { + await ViewModel!.Save(); + NotificationService.Success("Config has been saved"); + } + } + + private async Task RemoveCallback() + { + var result = await DialogService.Confirm($"Delete {ViewModel!.MfcConfig.Name}?"); + if (result is true) + { + await ViewModel!.Remove(); + } + } + + private async Task ChangeIdClicked() + { + _changing = true; + await ViewModel!.ChangeId(); + await ViewModel!.Init(); + _changing = false; + StateHasChanged(); + } + + private async Task ChangeGasClicked() + { + _changing = true; + try + { + await ViewModel!.ChangeGas(); + } + catch(TimeoutException) + { + NotificationService.Error("Gas change command timed out."); + } + await ViewModel!.Init(); + _changing = false; + StateHasChanged(); + } + + private string SelectedSetpointSourceValue + { + get => ViewModel?.SelectedSetpointSource.ToString() ?? SetpointSource.UnknownSource.ToString(); + set + { + if (ViewModel is null) + return; + + if (!Enum.TryParse(value, out var parsed)) + return; + + if (ViewModel.SetpointSourceUpdating || ViewModel.SelectedSetpointSource == parsed) + return; + + _ = InvokeAsync(() => ChangeSetpointSourceAsync(parsed)); + } + } + + private async Task ChangeSetpointSourceAsync(SetpointSource source) + { + await ViewModel!.UpdateSetpointSource(source); + StateHasChanged(); + } +} + diff --git a/UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsView.razor.css b/UI/Features/Devices/Mfc/MfcSettingsView.razor.css similarity index 94% rename from UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsView.razor.css rename to UI/Features/Devices/Mfc/MfcSettingsView.razor.css index fe1beb40..fc38112c 100644 --- a/UI/Pages/Shared/Settings/Device/Mfc/MfcSettingsView.razor.css +++ b/UI/Features/Devices/Mfc/MfcSettingsView.razor.css @@ -1,21 +1,21 @@ -.mfc-settings-grid { - display: grid; - grid-template-columns: 1fr; - position: relative; -} - -.mfc-settings-col1 { - display: grid; - align-items: center; - grid-template-columns: 1fr 1fr auto auto; - grid-column-gap: 5px; - max-width: 450px; -} - -.disabled-overlay { - width: 100%; - height: 100%; - z-index: 10; - position: absolute; - background: rgba(0, 0, 0, 0.25); +.mfc-settings-grid { + display: grid; + grid-template-columns: 1fr; + position: relative; +} + +.mfc-settings-col1 { + display: grid; + align-items: center; + grid-template-columns: 1fr 1fr auto auto; + grid-column-gap: 5px; + max-width: 450px; +} + +.disabled-overlay { + width: 100%; + height: 100%; + z-index: 10; + position: absolute; + background: rgba(0, 0, 0, 0.25); } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Settings/Device/Mfc/MfcSettingsViewModel.cs b/UI/Features/Devices/Mfc/MfcSettingsViewModel.cs similarity index 98% rename from UI/Backend/ViewModels/Settings/Device/Mfc/MfcSettingsViewModel.cs rename to UI/Features/Devices/Mfc/MfcSettingsViewModel.cs index cbf2376c..ece98232 100644 --- a/UI/Backend/ViewModels/Settings/Device/Mfc/MfcSettingsViewModel.cs +++ b/UI/Features/Devices/Mfc/MfcSettingsViewModel.cs @@ -7,9 +7,10 @@ using Grpc.Core; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Backend.Devices; +using UI.Application.Devices; +using UI.Features.Devices.Shared; -namespace UI.Backend.ViewModels.Settings.Device.Mfc; +namespace UI.Features.Devices.Mfc; public partial class MfcSettingsViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Devices/Mfc/MfcUnitControl.razor b/UI/Features/Devices/Mfc/MfcUnitControl.razor similarity index 95% rename from UI/Pages/Shared/Devices/Mfc/MfcUnitControl.razor rename to UI/Features/Devices/Mfc/MfcUnitControl.razor index 2632bbb2..f682b2f5 100644 --- a/UI/Pages/Shared/Devices/Mfc/MfcUnitControl.razor +++ b/UI/Features/Devices/Mfc/MfcUnitControl.razor @@ -1,143 +1,141 @@ -@using Ares.Alicat.Mfc.Messaging -@using UI.Backend.Devices -@using UI.Backend.ViewModels.Devices.Mfc - -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -
-
- [@ViewModel!.AssumedId] -
-
- @ViewModel!.MfcName -
- @if (ViewModel!.HasValidData) - { -
- @(ViewModel!.SelectedGas) -
-
- @if (ViewModel!.AbsolutePressure is not null) - { -
Absolute Pressure
-
@($"{ViewModel!.AbsolutePressure}")
- - } - - @if (ViewModel!.Temperature is not null) - { -
Temperature
-
@($"{ViewModel!.Temperature}")
- } - - @if (ViewModel!.VolumetricFlow is not null) - { -
Volumetric Flow
-
@($"{ViewModel!.VolumetricFlow}")
- } - - @if (ViewModel!.MassFlow is not null) - { -
Mass Flow
-
@($"{ViewModel!.MassFlow}")
- - } - - @if (ViewModel!.Setpoint is not null && ViewModel!.HasValve) - { -
Setpoint
-
@($"{ViewModel!.Setpoint}")
-
New Setpoint
- - - } - - @if(ViewModel!.ValveDrive is not null) - { -
Valve Drive
-
@($"{ViewModel!.ValveDrive}")
- } -
- @if (ViewModel!.Setpoint is not null && ViewModel!.HasValve) - { -
- Valve Hold Control -
- - -
-
- } -
- @foreach (var stat in ViewModel!.StatusCodes.Items) - { -
@stat.ToString().ToUpper()
- } -
- } - else - { - Loading MFC - - } -
- -@code { - private bool _waitingOnHoldCurrent; - private bool _waitingOnHoldClose; - private bool _cancellingHold; - - private bool ValveInteracting => _cancellingHold || _waitingOnHoldClose || _waitingOnHoldCurrent; - private bool ValveHolding => ViewModel!.StatusCodes.Items.Contains(Status.Hld); - - private async Task CancelHoldClicked() - { - _cancellingHold = true; - await ViewModel!.CancelValveHold(); - _cancellingHold = false; - StateHasChanged(); - } - - private async Task HoldCurrentClicked() - { - _waitingOnHoldCurrent = true; - await ViewModel!.HoldValvesAtCurrentPosition(); - _waitingOnHoldCurrent = false; - StateHasChanged(); - } - - private async Task HoldCloseClicked() - { - _waitingOnHoldClose = true; - await ViewModel!.HoldValvesClose(); - _waitingOnHoldClose = false; - StateHasChanged(); - } +@using Ares.Alicat.Mfc.Messaging + +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+
+ [@ViewModel!.AssumedId] +
+
+ @ViewModel!.MfcName +
+ @if (ViewModel!.HasValidData) + { +
+ @(ViewModel!.SelectedGas) +
+
+ @if (ViewModel!.AbsolutePressure is not null) + { +
Absolute Pressure
+
@($"{ViewModel!.AbsolutePressure}")
+ + } + + @if (ViewModel!.Temperature is not null) + { +
Temperature
+
@($"{ViewModel!.Temperature}")
+ } + + @if (ViewModel!.VolumetricFlow is not null) + { +
Volumetric Flow
+
@($"{ViewModel!.VolumetricFlow}")
+ } + + @if (ViewModel!.MassFlow is not null) + { +
Mass Flow
+
@($"{ViewModel!.MassFlow}")
+ + } + + @if (ViewModel!.Setpoint is not null && ViewModel!.HasValve) + { +
Setpoint
+
@($"{ViewModel!.Setpoint}")
+
New Setpoint
+ + + } + + @if(ViewModel!.ValveDrive is not null) + { +
Valve Drive
+
@($"{ViewModel!.ValveDrive}")
+ } +
+ @if (ViewModel!.Setpoint is not null && ViewModel!.HasValve) + { +
+ Valve Hold Control +
+ + +
+
+ } +
+ @foreach (var stat in ViewModel!.StatusCodes.Items) + { +
@stat.ToString().ToUpper()
+ } +
+ } + else + { + Loading MFC + + } +
+ +@code { + private bool _waitingOnHoldCurrent; + private bool _waitingOnHoldClose; + private bool _cancellingHold; + + private bool ValveInteracting => _cancellingHold || _waitingOnHoldClose || _waitingOnHoldCurrent; + private bool ValveHolding => ViewModel!.StatusCodes.Items.Contains(Status.Hld); + + private async Task CancelHoldClicked() + { + _cancellingHold = true; + await ViewModel!.CancelValveHold(); + _cancellingHold = false; + StateHasChanged(); + } + + private async Task HoldCurrentClicked() + { + _waitingOnHoldCurrent = true; + await ViewModel!.HoldValvesAtCurrentPosition(); + _waitingOnHoldCurrent = false; + StateHasChanged(); + } + + private async Task HoldCloseClicked() + { + _waitingOnHoldClose = true; + await ViewModel!.HoldValvesClose(); + _waitingOnHoldClose = false; + StateHasChanged(); + } } \ No newline at end of file diff --git a/UI/Pages/Shared/Devices/Mfc/MfcUnitControl.razor.css b/UI/Features/Devices/Mfc/MfcUnitControl.razor.css similarity index 93% rename from UI/Pages/Shared/Devices/Mfc/MfcUnitControl.razor.css rename to UI/Features/Devices/Mfc/MfcUnitControl.razor.css index 91fbd190..1338c244 100644 --- a/UI/Pages/Shared/Devices/Mfc/MfcUnitControl.razor.css +++ b/UI/Features/Devices/Mfc/MfcUnitControl.razor.css @@ -1,114 +1,114 @@ -.mfc-container { - display: grid; - grid-template: "header" - "state" - "valve-control" - "status-codes" 1fr - / auto; -} - -.mfc-gas { - grid-area: header; - font-size: 1.5em; - font-weight: bolder; -} - -.mfc-title { - grid-area: header; - align-self: start; - justify-self: center; - font-weight: bold; -} - -.mfc-state { - grid-area: state; - padding: 5px; - display: grid; - grid-template-columns: repeat(3, auto); - grid-gap: 5px; - align-items: center; -} - -.mfc-temperature { - grid-row: 1; -} - -.mfc-absolute-pressure { - grid-row: 2; -} - -.mfc-volumetric-flow { - grid-row: 3; -} - -.mfc-mass-flow { - grid-row: 4; -} - -.mfc-id { - grid-area: header; - justify-self: end; - font-size: .85em; -} - -.mfc-setpoint { - grid-row: 6; -} - -.mfc-setpoint-control { - grid-row: 7; -} - -.mfc-valve-drive { - grid-row: 5; -} - -.mfc-setpoint-input { - max-width: 100px; - justify-self: stretch; -} - -.mfc-valve-hold { - grid-area: valve-hold; -} - -.field-descriptor { - font-size: 0.8em; - opacity: 0.8; -} - -.valve-control { - grid-area: valve-control; - border: 1px solid rgba(224, 255, 255, 0.2); - padding: 10px; - border-radius: 10px; - box-shadow: 5px 5px 5px rgba(10, 10, 10, 0.3); -} - -.valve-legend { - font-size: 0.8em; -} - -.status-codes { - grid-area: status-codes; - align-items: center; -} - -.valve-control-grid { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-template-rows: 36px; - grid-column-gap: 5px; -} - -.button-spinner { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.status-display { - border: 2px solid orange; - font-weight: bold; +.mfc-container { + display: grid; + grid-template: "header" + "state" + "valve-control" + "status-codes" 1fr + / auto; +} + +.mfc-gas { + grid-area: header; + font-size: 1.5em; + font-weight: bolder; +} + +.mfc-title { + grid-area: header; + align-self: start; + justify-self: center; + font-weight: bold; +} + +.mfc-state { + grid-area: state; + padding: 5px; + display: grid; + grid-template-columns: repeat(3, auto); + grid-gap: 5px; + align-items: center; +} + +.mfc-temperature { + grid-row: 1; +} + +.mfc-absolute-pressure { + grid-row: 2; +} + +.mfc-volumetric-flow { + grid-row: 3; +} + +.mfc-mass-flow { + grid-row: 4; +} + +.mfc-id { + grid-area: header; + justify-self: end; + font-size: .85em; +} + +.mfc-setpoint { + grid-row: 6; +} + +.mfc-setpoint-control { + grid-row: 7; +} + +.mfc-valve-drive { + grid-row: 5; +} + +.mfc-setpoint-input { + max-width: 100px; + justify-self: stretch; +} + +.mfc-valve-hold { + grid-area: valve-hold; +} + +.field-descriptor { + font-size: 0.8em; + opacity: 0.8; +} + +.valve-control { + grid-area: valve-control; + border: 1px solid rgba(224, 255, 255, 0.2); + padding: 10px; + border-radius: 10px; + box-shadow: 5px 5px 5px rgba(10, 10, 10, 0.3); +} + +.valve-legend { + font-size: 0.8em; +} + +.status-codes { + grid-area: status-codes; + align-items: center; +} + +.valve-control-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 36px; + grid-column-gap: 5px; +} + +.button-spinner { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.status-display { + border: 2px solid orange; + font-weight: bold; } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Devices/Mfc/MfcUnitControlViewModel.cs b/UI/Features/Devices/Mfc/MfcUnitControlViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Devices/Mfc/MfcUnitControlViewModel.cs rename to UI/Features/Devices/Mfc/MfcUnitControlViewModel.cs index 5b622e33..69c0db05 100644 --- a/UI/Backend/ViewModels/Devices/Mfc/MfcUnitControlViewModel.cs +++ b/UI/Features/Devices/Mfc/MfcUnitControlViewModel.cs @@ -1,208 +1,209 @@ -using Ares.Alicat.Mfc.Messaging; -using DynamicData; -using ReactiveUI.SourceGenerators; -using UI.Pages.Shared.Devices.Mfc; -using UnitsNet; -using UnitsNet.Units; - -namespace UI.Backend.ViewModels.Devices.Mfc; - -public partial class MfcUnitControlViewModel : DeviceUnitControlViewModel, IAsyncDisposable -{ - private readonly DeviceRequest _deviceRequest; - private readonly MfcRpc.MfcRpcClient _mfcClient; - private readonly CancellationTokenSource _stateStreamCts = new(); - private Task _stateListener = Task.CompletedTask; - - public MfcUnitControlViewModel(string mfcId, string mfcName, MfcRpc.MfcRpcClient mfcClient) : base(mfcId, mfcName) - { - MfcName = mfcName; - _deviceRequest = new DeviceRequest { DeviceId = DeviceId }; - _mfcClient = mfcClient; - ViewType = typeof(MfcUnitControl); - Initialize(); - - DefaultWidth = 19; - } - - public string MfcName { get; } - - [Reactive] - public partial int TargetGas { get; set; } - - [Reactive] - public partial double? TargetSetpoint { get; set; } - - [Reactive] - public partial bool CapturingLiveData { get; private set; } - - [Reactive] - public partial IEnumerable? AvailableGases { get; set; } - - [Reactive] - public partial string? SelectedGas { get; set; } - - [Reactive] - public partial char? AssumedId { get; set; } - - [Reactive] - public partial bool HasValve { get; private set; } - - [Reactive] - public partial Temperature? Temperature { get; set; } - - [Reactive] - public partial Pressure? AbsolutePressure { get; set; } - - [Reactive] - public partial VolumeFlow? VolumetricFlow { get; set; } - - [Reactive] - public partial StandardVolumeFlow? MassFlow { get; set; } - - [Reactive] - public partial StandardVolumeFlow? Setpoint { get; set; } - - [Reactive] - public partial double? ValveDrive { get; set; } - - [Reactive] - public partial bool HasValidData { get; private set; } - - public ISourceList StatusCodes { get; } = new SourceList(); - - public async ValueTask DisposeAsync() - { - _stateStreamCts.Cancel(); - await _stateListener; - _stateStreamCts.Dispose(); - - GC.SuppressFinalize(this); - } - - private void Initialize() - { - ListenForStates(); - } - - public void ListenForStates() - { - try - { - _stateListener = Task.Run(async () => - { - Thread.CurrentThread.Name = $"Mass Flow Controller {DeviceName} State Listener Thread"; - var state = await _mfcClient.GetStateAsync(_deviceRequest); - UpdateState(state); - CapturingLiveData = true; - try - { - while(!_stateStreamCts.Token.IsCancellationRequested) - { - var stateResponse = await _mfcClient.GetStateUpdateAsync(_deviceRequest, null, null, _stateStreamCts.Token); - UpdateState(stateResponse); - await Task.Delay(100); - } - } - catch(Exception) - { - Console.WriteLine($"~~~~~~~ Exception Getting State, Thread will die probably? ~~~~~~~"); - } - - CapturingLiveData = false; - }, - _stateStreamCts.Token); - } - catch(OperationCanceledException) - { - } - } - - private void UpdateState(StateResponse state) - { - AssumedId = state.AssumedId?.FirstOrDefault(); - AvailableGases = state.AvailableGasInfos; - if(state.Data is null) - { - HasValidData = false; - return; - } - HasValidData = true; - - if(state.Data.Temperature is not null) - { - var foundTempUnit = UnitsNet.Temperature.TryParseUnit(state.Data.Temperature.Unit, out var tempUnit); - Temperature = UnitsNet.Temperature.From(state.Data.Temperature.Value, foundTempUnit ? tempUnit : TemperatureUnit.DegreeCelsius); - } - - if(state.Data.AbsolutePressure is not null) - { - var foundAbsolutePressureUnit = Pressure.TryParseUnit(state.Data.AbsolutePressure.Unit, out var pressureUnit); - AbsolutePressure = Pressure.From(state.Data.AbsolutePressure.Value, foundAbsolutePressureUnit ? pressureUnit : PressureUnit.PoundForcePerSquareInch); - } - - if(state.Data.VolumetricFlow is not null) - { - var foundVolumetricFlowUnit = VolumeFlow.TryParseUnit(state.Data.VolumetricFlow.Unit, out var volumeFlowUnit); - VolumetricFlow = VolumeFlow.From(state.Data.VolumetricFlow.Value, foundVolumetricFlowUnit ? volumeFlowUnit : VolumeFlowUnit.CubicCentimeterPerMinute); - } - - if(state.Data.MassFlow is not null) - { - var foundMassFlowUnit = StandardVolumeFlow.TryParseUnit(state.Data.MassFlow.Unit, out var massFlowUnit); - MassFlow = StandardVolumeFlow.From(state.Data.MassFlow.Value, foundMassFlowUnit ? massFlowUnit : StandardVolumeFlowUnit.StandardLiterPerMinute); - } - - if(state.Data.Setpoint is not null) - { - var foundSetPointUnit = StandardVolumeFlow.TryParseUnit(state.Data.Setpoint.Unit, out var setpointUnit); - Setpoint = StandardVolumeFlow.From(state.Data.Setpoint.Value, foundSetPointUnit ? setpointUnit : StandardVolumeFlowUnit.StandardLiterPerMinute); - } - - if(state.Data.HasValveDrive) - { - ValveDrive = state.Data.ValveDrive; - } - - HasValve = state.HasValve; - - ParseStatusCodes(state.Data.StatusCodes); - SelectedGas = state.Data.Gas; - } - - private void ParseStatusCodes(IEnumerable statusCodes) - { - var statusCodesArr = statusCodes.ToArray(); - var removedCodes = StatusCodes.Items.Except(statusCodesArr); - var addedCodes = statusCodesArr.Except(StatusCodes.Items); - StatusCodes.RemoveMany(removedCodes); - StatusCodes.AddRange(addedCodes); - } - - public void SetSetpoint() - { - if(!TargetSetpoint.HasValue) - return; - var setSetpointReq = new SetSetpointRequest { DeviceRequest = _deviceRequest, Setpoint = TargetSetpoint.Value }; - _mfcClient.SetSetpoint(setSetpointReq); - } - - public Task HoldValvesAtCurrentPosition() - => _mfcClient.HoldValvesAtCurrentPositionAsync(_deviceRequest).ResponseAsync; - - public Task CancelValveHold() - => _mfcClient.CancelValveHoldAsync(_deviceRequest).ResponseAsync; - - public Task HoldValvesClose() - => _mfcClient.HoldValvesClosedAsync(_deviceRequest).ResponseAsync; - - public void TareFLow() - { - _mfcClient.TareFlow(_deviceRequest); - } - - public void TareAbsolutePressureWithBarometer() - { - _mfcClient.TareAbsolutePressureWithBarometer(_deviceRequest); - } -} +using Ares.Alicat.Mfc.Messaging; +using DynamicData; +using ReactiveUI.SourceGenerators; +using UI.Application.Devices; +using UnitsNet; +using UnitsNet.Units; + +namespace UI.Features.Devices.Mfc; + +public partial class MfcUnitControlViewModel : DeviceUnitControlViewModel, IAsyncDisposable +{ + private readonly DeviceRequest _deviceRequest; + private readonly MfcRpc.MfcRpcClient _mfcClient; + private readonly CancellationTokenSource _stateStreamCts = new(); + private Task _stateListener = Task.CompletedTask; + + public MfcUnitControlViewModel(string mfcId, string mfcName, MfcRpc.MfcRpcClient mfcClient) : base(mfcId, mfcName) + { + MfcName = mfcName; + _deviceRequest = new DeviceRequest { DeviceId = DeviceId }; + _mfcClient = mfcClient; + ViewType = typeof(MfcUnitControl); + Initialize(); + + DefaultWidth = 19; + } + + public string MfcName { get; } + + [Reactive] + public partial int TargetGas { get; set; } + + [Reactive] + public partial double? TargetSetpoint { get; set; } + + [Reactive] + public partial bool CapturingLiveData { get; private set; } + + [Reactive] + public partial IEnumerable? AvailableGases { get; set; } + + [Reactive] + public partial string? SelectedGas { get; set; } + + [Reactive] + public partial char? AssumedId { get; set; } + + [Reactive] + public partial bool HasValve { get; private set; } + + [Reactive] + public partial Temperature? Temperature { get; set; } + + [Reactive] + public partial Pressure? AbsolutePressure { get; set; } + + [Reactive] + public partial VolumeFlow? VolumetricFlow { get; set; } + + [Reactive] + public partial StandardVolumeFlow? MassFlow { get; set; } + + [Reactive] + public partial StandardVolumeFlow? Setpoint { get; set; } + + [Reactive] + public partial double? ValveDrive { get; set; } + + [Reactive] + public partial bool HasValidData { get; private set; } + + public ISourceList StatusCodes { get; } = new SourceList(); + + public async ValueTask DisposeAsync() + { + _stateStreamCts.Cancel(); + await _stateListener; + _stateStreamCts.Dispose(); + + GC.SuppressFinalize(this); + } + + private void Initialize() + { + ListenForStates(); + } + + public void ListenForStates() + { + try + { + _stateListener = Task.Run(async () => + { + Thread.CurrentThread.Name = $"Mass Flow Controller {DeviceName} State Listener Thread"; + var state = await _mfcClient.GetStateAsync(_deviceRequest); + UpdateState(state); + CapturingLiveData = true; + try + { + while(!_stateStreamCts.Token.IsCancellationRequested) + { + var stateResponse = await _mfcClient.GetStateUpdateAsync(_deviceRequest, null, null, _stateStreamCts.Token); + UpdateState(stateResponse); + await Task.Delay(100); + } + } + catch(Exception) + { + Console.WriteLine($"~~~~~~~ Exception Getting State, Thread will die probably? ~~~~~~~"); + } + + CapturingLiveData = false; + }, + _stateStreamCts.Token); + } + catch(OperationCanceledException) + { + } + } + + private void UpdateState(StateResponse state) + { + AssumedId = state.AssumedId?.FirstOrDefault(); + AvailableGases = state.AvailableGasInfos; + if(state.Data is null) + { + HasValidData = false; + return; + } + HasValidData = true; + + if(state.Data.Temperature is not null) + { + var foundTempUnit = UnitsNet.Temperature.TryParseUnit(state.Data.Temperature.Unit, out var tempUnit); + Temperature = UnitsNet.Temperature.From(state.Data.Temperature.Value, foundTempUnit ? tempUnit : TemperatureUnit.DegreeCelsius); + } + + if(state.Data.AbsolutePressure is not null) + { + var foundAbsolutePressureUnit = Pressure.TryParseUnit(state.Data.AbsolutePressure.Unit, out var pressureUnit); + AbsolutePressure = Pressure.From(state.Data.AbsolutePressure.Value, foundAbsolutePressureUnit ? pressureUnit : PressureUnit.PoundForcePerSquareInch); + } + + if(state.Data.VolumetricFlow is not null) + { + var foundVolumetricFlowUnit = VolumeFlow.TryParseUnit(state.Data.VolumetricFlow.Unit, out var volumeFlowUnit); + VolumetricFlow = VolumeFlow.From(state.Data.VolumetricFlow.Value, foundVolumetricFlowUnit ? volumeFlowUnit : VolumeFlowUnit.CubicCentimeterPerMinute); + } + + if(state.Data.MassFlow is not null) + { + var foundMassFlowUnit = StandardVolumeFlow.TryParseUnit(state.Data.MassFlow.Unit, out var massFlowUnit); + MassFlow = StandardVolumeFlow.From(state.Data.MassFlow.Value, foundMassFlowUnit ? massFlowUnit : StandardVolumeFlowUnit.StandardLiterPerMinute); + } + + if(state.Data.Setpoint is not null) + { + var foundSetPointUnit = StandardVolumeFlow.TryParseUnit(state.Data.Setpoint.Unit, out var setpointUnit); + Setpoint = StandardVolumeFlow.From(state.Data.Setpoint.Value, foundSetPointUnit ? setpointUnit : StandardVolumeFlowUnit.StandardLiterPerMinute); + } + + if(state.Data.HasValveDrive) + { + ValveDrive = state.Data.ValveDrive; + } + + HasValve = state.HasValve; + + ParseStatusCodes(state.Data.StatusCodes); + SelectedGas = state.Data.Gas; + } + + private void ParseStatusCodes(IEnumerable statusCodes) + { + var statusCodesArr = statusCodes.ToArray(); + var removedCodes = StatusCodes.Items.Except(statusCodesArr); + var addedCodes = statusCodesArr.Except(StatusCodes.Items); + StatusCodes.RemoveMany(removedCodes); + StatusCodes.AddRange(addedCodes); + } + + public void SetSetpoint() + { + if(!TargetSetpoint.HasValue) + return; + var setSetpointReq = new SetSetpointRequest { DeviceRequest = _deviceRequest, Setpoint = TargetSetpoint.Value }; + _mfcClient.SetSetpoint(setSetpointReq); + } + + public Task HoldValvesAtCurrentPosition() + => _mfcClient.HoldValvesAtCurrentPositionAsync(_deviceRequest).ResponseAsync; + + public Task CancelValveHold() + => _mfcClient.CancelValveHoldAsync(_deviceRequest).ResponseAsync; + + public Task HoldValvesClose() + => _mfcClient.HoldValvesClosedAsync(_deviceRequest).ResponseAsync; + + public void TareFLow() + { + _mfcClient.TareFlow(_deviceRequest); + } + + public void TareAbsolutePressureWithBarometer() + { + _mfcClient.TareAbsolutePressureWithBarometer(_deviceRequest); + } +} + diff --git a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceConfigEditView.razor b/UI/Features/Devices/Remote/RemoteDeviceConfigEditView.razor similarity index 95% rename from UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceConfigEditView.razor rename to UI/Features/Devices/Remote/RemoteDeviceConfigEditView.razor index 5f4d135c..fbc2e018 100644 --- a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceConfigEditView.razor +++ b/UI/Features/Devices/Remote/RemoteDeviceConfigEditView.razor @@ -1,15 +1,14 @@ -@using UI.Backend.ViewModels.Settings.Device.Remote; - + @inherits ReactiveUI.Blazor.ReactiveComponentBase
- + - + @@ -26,22 +25,22 @@ -@code { - - private bool ValidateAddress(string address) - { - if (string.IsNullOrEmpty(address)) - { - return false; - } - - var test = Uri.TryCreate(address, UriKind.Absolute, out var _); - if (!test) - { - return false; - } - - return true; +@code { + + private bool ValidateAddress(string address) + { + if (string.IsNullOrEmpty(address)) + { + return false; + } + + var test = Uri.TryCreate(address, UriKind.Absolute, out var _); + if (!test) + { + return false; + } + + return true; } [Parameter] diff --git a/UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceConfigEditViewModel.cs b/UI/Features/Devices/Remote/RemoteDeviceConfigEditViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceConfigEditViewModel.cs rename to UI/Features/Devices/Remote/RemoteDeviceConfigEditViewModel.cs index c84ef03b..035370f3 100644 --- a/UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceConfigEditViewModel.cs +++ b/UI/Features/Devices/Remote/RemoteDeviceConfigEditViewModel.cs @@ -2,66 +2,66 @@ using Ares.Datamodel.Device; using ReactiveUI; -namespace UI.Backend.ViewModels.Settings.Device.Remote; +namespace UI.Features.Devices.Remote; public class RemoteDeviceConfigEditViewModel : ReactiveObject -{ - private readonly RemoteDeviceConfig _originalConfig; - private readonly ObservableAsPropertyHelper _modified; - - private string _name; - private string _address; - - // Constructor for a new configuration - public RemoteDeviceConfigEditViewModel() : this(new RemoteDeviceConfig(), isNew: true) - { - } - - // Constructor for editing an existing configuration - public RemoteDeviceConfigEditViewModel(RemoteDeviceConfig remoteDeviceConfig) : this(remoteDeviceConfig, isNew: false) - { - } - - // Private master constructor to reduce duplication - private RemoteDeviceConfigEditViewModel(RemoteDeviceConfig config, bool isNew) - { - _originalConfig = config ?? throw new ArgumentNullException(nameof(config)); - NewConfig = isNew; - - // Set initial values from the model - _name = _originalConfig.Name; - _address = _originalConfig.Url; - - // A reactive property that tracks if the view model has been modified. - _modified = this.WhenAnyValue( - x => x.Name, - x => x.Address) - .Select(_ => Name != _originalConfig.Name || BuildUrl() != (_originalConfig.Url ?? string.Empty)) - .ToProperty(this, x => x.Modified, initialValue: false); - } - - public string Name - { - get => _name; - set => this.RaiseAndSetIfChanged(ref _name, value); - } - - public string Address - { - get => _address; - set => this.RaiseAndSetIfChanged(ref _address, value); - } - - public bool Modified => _modified.Value; - - public bool NewConfig { get; } - - public RemoteDeviceConfig Save() - => Modified ? new RemoteDeviceConfig { Name = Name, Url = BuildUrl() } : _originalConfig; - - private string BuildUrl() - { - var success = Uri.TryCreate(Address, UriKind.Absolute, out var result); - return result?.ToString().TrimEnd('/') ?? ""; +{ + private readonly RemoteDeviceConfig _originalConfig; + private readonly ObservableAsPropertyHelper _modified; + + private string _name; + private string _address; + + // Constructor for a new configuration + public RemoteDeviceConfigEditViewModel() : this(new RemoteDeviceConfig(), isNew: true) + { + } + + // Constructor for editing an existing configuration + public RemoteDeviceConfigEditViewModel(RemoteDeviceConfig remoteDeviceConfig) : this(remoteDeviceConfig, isNew: false) + { + } + + // Private master constructor to reduce duplication + private RemoteDeviceConfigEditViewModel(RemoteDeviceConfig config, bool isNew) + { + _originalConfig = config ?? throw new ArgumentNullException(nameof(config)); + NewConfig = isNew; + + // Set initial values from the model + _name = _originalConfig.Name; + _address = _originalConfig.Url; + + // A reactive property that tracks if the view model has been modified. + _modified = this.WhenAnyValue( + x => x.Name, + x => x.Address) + .Select(_ => Name != _originalConfig.Name || BuildUrl() != (_originalConfig.Url ?? string.Empty)) + .ToProperty(this, x => x.Modified, initialValue: false); + } + + public string Name + { + get => _name; + set => this.RaiseAndSetIfChanged(ref _name, value); + } + + public string Address + { + get => _address; + set => this.RaiseAndSetIfChanged(ref _address, value); + } + + public bool Modified => _modified.Value; + + public bool NewConfig { get; } + + public RemoteDeviceConfig Save() + => Modified ? new RemoteDeviceConfig { Name = Name, Url = BuildUrl() } : _originalConfig; + + private string BuildUrl() + { + var success = Uri.TryCreate(Address, UriKind.Absolute, out var result); + return result?.ToString().TrimEnd('/') ?? ""; } } diff --git a/UI/Backend/Factories/RemoteDeviceControlViewModelFactory.cs b/UI/Features/Devices/Remote/RemoteDeviceControlViewModelFactory.cs similarity index 81% rename from UI/Backend/Factories/RemoteDeviceControlViewModelFactory.cs rename to UI/Features/Devices/Remote/RemoteDeviceControlViewModelFactory.cs index 375b25ce..6ab5fcc5 100644 --- a/UI/Backend/Factories/RemoteDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/Remote/RemoteDeviceControlViewModelFactory.cs @@ -1,21 +1,20 @@ -using Ares.Services.Device; +using Ares.Services.Device; using DynamicData; using Google.Protobuf.WellKnownTypes; -using UI.Backend.Devices; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Devices.Remote; +using UI.Infrastructure.Devices; +using UI.Application.Devices.Repos; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.Remote; public class RemoteDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { - private readonly DeviceAdapterRepository _deviceAdapterRepo; + private readonly IDeviceAdapterRepository _deviceAdapterRepo; private readonly IDeviceControlViewModelRepo _deviceControlViewModelRepo; private readonly AresDevices.AresDevicesClient _devicesClient; public RemoteDeviceControlViewModelFactory(AresDevices.AresDevicesClient devicesClient, - DeviceAdapterRepository deviceAdapterRepo, + IDeviceAdapterRepository deviceAdapterRepo, IDeviceControlViewModelRepo deviceControlViewModelRepo) : base(devicesClient, deviceControlViewModelRepo) { _deviceAdapterRepo = deviceAdapterRepo; @@ -39,3 +38,4 @@ protected override async Task> GetAvailableDe return remotedDevices; } } + diff --git a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsEditorView.razor b/UI/Features/Devices/Remote/RemoteDeviceSettingsEditorView.razor similarity index 87% rename from UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsEditorView.razor rename to UI/Features/Devices/Remote/RemoteDeviceSettingsEditorView.razor index 3a86d87e..c0ca2bdd 100644 --- a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsEditorView.razor +++ b/UI/Features/Devices/Remote/RemoteDeviceSettingsEditorView.razor @@ -1,6 +1,6 @@ @using Ares.Datamodel; @using System.Reactive.Threading.Tasks -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase diff --git a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsList.razor b/UI/Features/Devices/Remote/RemoteDeviceSettingsList.razor similarity index 97% rename from UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsList.razor rename to UI/Features/Devices/Remote/RemoteDeviceSettingsList.razor index 900b607d..8ca66825 100644 --- a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsList.razor +++ b/UI/Features/Devices/Remote/RemoteDeviceSettingsList.razor @@ -2,7 +2,6 @@ @layout DeviceSettingsLayout @using Ares.Services @using Google.Protobuf.WellKnownTypes -@using UI.Backend.ViewModels.Settings.Device.Remote @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService diff --git a/UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceSettingsListViewModel.cs b/UI/Features/Devices/Remote/RemoteDeviceSettingsListViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceSettingsListViewModel.cs rename to UI/Features/Devices/Remote/RemoteDeviceSettingsListViewModel.cs index ab35c1b4..9a95a02e 100644 --- a/UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceSettingsListViewModel.cs +++ b/UI/Features/Devices/Remote/RemoteDeviceSettingsListViewModel.cs @@ -1,4 +1,3 @@ -using System.Collections.ObjectModel; using Ares.Datamodel.Device; using Ares.Services; using Ares.Services.Device; @@ -6,9 +5,10 @@ using Google.Protobuf.WellKnownTypes; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Services.Notification; +using System.Collections.ObjectModel; +using UI.Application.Notifications; -namespace UI.Backend.ViewModels.Settings.Device.Remote; +namespace UI.Features.Devices.Remote; public partial class RemoteDeviceSettingsListViewModel : ReactiveObject { @@ -91,3 +91,5 @@ private async Task OnDeviceRemoved() public ObservableCollection SettingsViewModels { get; } } + + diff --git a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsView.razor b/UI/Features/Devices/Remote/RemoteDeviceSettingsView.razor similarity index 97% rename from UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsView.razor rename to UI/Features/Devices/Remote/RemoteDeviceSettingsView.razor index 95dbdcfc..9445d6de 100644 --- a/UI/Pages/Shared/Settings/Device/Remote/RemoteDeviceSettingsView.razor +++ b/UI/Features/Devices/Remote/RemoteDeviceSettingsView.razor @@ -1,6 +1,5 @@ @using System.Reactive.Linq @using System.Reactive.Threading.Tasks -@using UI.Backend.ViewModels.Settings.Device.Remote @inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService diff --git a/UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceSettingsViewModel.cs b/UI/Features/Devices/Remote/RemoteDeviceSettingsViewModel.cs similarity index 97% rename from UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceSettingsViewModel.cs rename to UI/Features/Devices/Remote/RemoteDeviceSettingsViewModel.cs index 156c26a8..9888fe2d 100644 --- a/UI/Backend/ViewModels/Settings/Device/Remote/RemoteDeviceSettingsViewModel.cs +++ b/UI/Features/Devices/Remote/RemoteDeviceSettingsViewModel.cs @@ -1,5 +1,3 @@ -using System.Reactive; -using System.Reactive.Linq; using Ares.Datamodel; using Ares.Datamodel.Device; using Ares.Services; @@ -8,10 +6,13 @@ using Grpc.Core; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Backend.Devices; -using UI.Services.Notification; +using System.Reactive; +using System.Reactive.Linq; +using UI.Features.Devices.Shared; +using UI.Application.Notifications; +using UI.Application.Devices; -namespace UI.Backend.ViewModels.Settings.Device.Remote; +namespace UI.Features.Devices.Remote; public partial class RemoteDeviceSettingsViewModel : ReactiveObject { @@ -217,3 +218,5 @@ private void HandleError(Exception ex) private void PushNotification(AresNotification notification) => _notificationService.PushNotification(notification); } + + diff --git a/UI/Pages/Shared/Devices/Remote/RemoteDeviceUnitView.razor b/UI/Features/Devices/Remote/RemoteDeviceUnitView.razor similarity index 96% rename from UI/Pages/Shared/Devices/Remote/RemoteDeviceUnitView.razor rename to UI/Features/Devices/Remote/RemoteDeviceUnitView.razor index d5ca37fd..5e594550 100644 --- a/UI/Pages/Shared/Devices/Remote/RemoteDeviceUnitView.razor +++ b/UI/Features/Devices/Remote/RemoteDeviceUnitView.razor @@ -1,7 +1,6 @@ -@using UI.Backend.Devices -@using UI.Backend.ViewModels.Devices.Remote -@inject TooltipService TooltipService +@using UI.Application.Devices @inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject TooltipService TooltipService @@ -51,4 +50,4 @@ { TooltipService.Open(elementReference, text, new TooltipOptions { Position = TooltipPosition.Top }); } -} \ No newline at end of file +} diff --git a/UI/Pages/Shared/Devices/Remote/RemoteDeviceUnitView.razor.css b/UI/Features/Devices/Remote/RemoteDeviceUnitView.razor.css similarity index 100% rename from UI/Pages/Shared/Devices/Remote/RemoteDeviceUnitView.razor.css rename to UI/Features/Devices/Remote/RemoteDeviceUnitView.razor.css diff --git a/UI/Backend/ViewModels/Devices/Remote/RemoteDeviceUnitViewModel.cs b/UI/Features/Devices/Remote/RemoteDeviceUnitViewModel.cs similarity index 89% rename from UI/Backend/ViewModels/Devices/Remote/RemoteDeviceUnitViewModel.cs rename to UI/Features/Devices/Remote/RemoteDeviceUnitViewModel.cs index be65caa2..f4fbb89e 100644 --- a/UI/Backend/ViewModels/Devices/Remote/RemoteDeviceUnitViewModel.cs +++ b/UI/Features/Devices/Remote/RemoteDeviceUnitViewModel.cs @@ -1,9 +1,8 @@ -using Ares.Datamodel; +using Ares.Datamodel; using ReactiveUI.SourceGenerators; -using UI.Backend.Devices; -using UI.Pages.Shared.Devices.Remote; +using UI.Application.Devices; -namespace UI.Backend.ViewModels.Devices.Remote; +namespace UI.Features.Devices.Remote; public partial class RemoteDeviceUnitViewModel : DeviceUnitControlViewModel, IAsyncDisposable { @@ -41,3 +40,4 @@ public ValueTask DisposeAsync() return ValueTask.CompletedTask; } } + diff --git a/UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceConfigEditView.razor b/UI/Features/Devices/RestDevice/RestDeviceConfigEditView.razor similarity index 86% rename from UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceConfigEditView.razor rename to UI/Features/Devices/RestDevice/RestDeviceConfigEditView.razor index 71acb2b4..ad435b8f 100644 --- a/UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceConfigEditView.razor +++ b/UI/Features/Devices/RestDevice/RestDeviceConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase diff --git a/UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceConfigEditViewModel.cs b/UI/Features/Devices/RestDevice/RestDeviceConfigEditViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceConfigEditViewModel.cs rename to UI/Features/Devices/RestDevice/RestDeviceConfigEditViewModel.cs index de40349e..1fd40693 100644 --- a/UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceConfigEditViewModel.cs +++ b/UI/Features/Devices/RestDevice/RestDeviceConfigEditViewModel.cs @@ -4,7 +4,7 @@ using RestDevice.Config; using RestDevice.Services; -namespace UI.Backend.ViewModels.Settings.Device.RestDevice; +namespace UI.Features.Devices.RestDevice; public class RestDeviceConfigEditViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceSettingsList.razor b/UI/Features/Devices/RestDevice/RestDeviceSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceSettingsList.razor rename to UI/Features/Devices/RestDevice/RestDeviceSettingsList.razor index 2c47e196..d2a60438 100644 --- a/UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceSettingsList.razor +++ b/UI/Features/Devices/RestDevice/RestDeviceSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/restdevice" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.RestDevice +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -63,3 +63,5 @@ else private bool SavingInProgress { get; set; } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceSettingsListViewModel.cs b/UI/Features/Devices/RestDevice/RestDeviceSettingsListViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceSettingsListViewModel.cs rename to UI/Features/Devices/RestDevice/RestDeviceSettingsListViewModel.cs index 87032c54..943cc69c 100644 --- a/UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceSettingsListViewModel.cs +++ b/UI/Features/Devices/RestDevice/RestDeviceSettingsListViewModel.cs @@ -6,7 +6,7 @@ using RestDevice.Config; using RestDevice.Services; -namespace UI.Backend.ViewModels.Settings.Device.RestDevice; +namespace UI.Features.Devices.RestDevice; public partial class RestDeviceSettingsListViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceSettingsView.razor b/UI/Features/Devices/RestDevice/RestDeviceSettingsView.razor similarity index 90% rename from UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceSettingsView.razor rename to UI/Features/Devices/RestDevice/RestDeviceSettingsView.razor index d92f7f66..e8e8f497 100644 --- a/UI/Pages/Shared/Settings/Device/RestDevice/RestDeviceSettingsView.razor +++ b/UI/Features/Devices/RestDevice/RestDeviceSettingsView.razor @@ -1,4 +1,5 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService @inject IUiNotificationService NotificationService @@ -38,3 +39,5 @@ } } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceSettingsViewModel.cs b/UI/Features/Devices/RestDevice/RestDeviceSettingsViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceSettingsViewModel.cs rename to UI/Features/Devices/RestDevice/RestDeviceSettingsViewModel.cs index baf7f257..d9889885 100644 --- a/UI/Backend/ViewModels/Settings/Device/RestDevice/RestDeviceSettingsViewModel.cs +++ b/UI/Features/Devices/RestDevice/RestDeviceSettingsViewModel.cs @@ -5,7 +5,7 @@ using RestDevice.Config; using RestDevice.Services; -namespace UI.Backend.ViewModels.Settings.Device.RestDevice; +namespace UI.Features.Devices.RestDevice; public class RestDeviceSettingsViewModel : ReactiveObject { @@ -42,10 +42,10 @@ public async Task GetDeviceOperationalStatus() public async Task Save() { var servoConfig = EditViewModel.Save(); - var updateRequest = new RestDeviceUpdateRequest - { - Id = _deviceConfig.UniqueId, - Config = servoConfig + var updateRequest = new RestDeviceUpdateRequest + { + Id = _deviceConfig.UniqueId, + Config = servoConfig }; await _client.UpdateRestDeviceAsync(updateRequest); diff --git a/UI/Pages/Shared/Devices/RestDevice/RestDeviceUnitControl.razor b/UI/Features/Devices/RestDevice/RestDeviceUnitControl.razor similarity index 90% rename from UI/Pages/Shared/Devices/RestDevice/RestDeviceUnitControl.razor rename to UI/Features/Devices/RestDevice/RestDeviceUnitControl.razor index 66f8a06c..74cb2999 100644 --- a/UI/Pages/Shared/Devices/RestDevice/RestDeviceUnitControl.razor +++ b/UI/Features/Devices/RestDevice/RestDeviceUnitControl.razor @@ -1,5 +1,4 @@ -@using UI.Backend.ViewModels.Devices.RestDevice -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject TooltipService tooltipService diff --git a/UI/Backend/ViewModels/Devices/RestDevice/RestDeviceUnitControlViewModel.cs b/UI/Features/Devices/RestDevice/RestDeviceUnitControlViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Devices/RestDevice/RestDeviceUnitControlViewModel.cs rename to UI/Features/Devices/RestDevice/RestDeviceUnitControlViewModel.cs index 471a479f..8df0838d 100644 --- a/UI/Backend/ViewModels/Devices/RestDevice/RestDeviceUnitControlViewModel.cs +++ b/UI/Features/Devices/RestDevice/RestDeviceUnitControlViewModel.cs @@ -1,6 +1,7 @@ -using RestDevice.Services; +using RestDevice.Services; +using UI.Application.Devices; -namespace UI.Backend.ViewModels.Devices.RestDevice; +namespace UI.Features.Devices.RestDevice; public class RestDeviceUnitControlViewModel : DeviceUnitControlViewModel { @@ -57,3 +58,4 @@ public async Task HandleDeviceCommand(string commandName) public Dictionary MethodParameters { get; set; } = []; } + diff --git a/UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceConfigEditView.razor b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceConfigEditView.razor similarity index 90% rename from UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceConfigEditView.razor rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceConfigEditView.razor index 99a9b526..83d52a12 100644 --- a/UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceConfigEditView.razor +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceConfigEditViewModel.cs b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceConfigEditViewModel.cs similarity index 97% rename from UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceConfigEditViewModel.cs rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceConfigEditViewModel.cs index cfecc14a..891a00ef 100644 --- a/UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceConfigEditViewModel.cs +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceConfigEditViewModel.cs @@ -34,13 +34,13 @@ public SerialRestDeviceConfigEditViewModel(RestSerialDeviceRpc.RestSerialDeviceR using Ares.Services.Device; using Google.Protobuf.WellKnownTypes; -using RestSerialDevice.Config; -using RestSerialDevice.Services; using Microsoft.Build.Framework; using ReactiveUI; using ReactiveUI.SourceGenerators; +using RestSerialDevice.Config; +using RestSerialDevice.Services; -namespace UI.Backend.ViewModels.Settings.Device.SerialRestDevice; +namespace UI.Features.Devices.SerialRestDevice; public partial class SerialRestDeviceConfigEditViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsList.razor b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsList.razor rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsList.razor index 9c65498b..7d052673 100644 --- a/UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsList.razor +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/serialrest" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.SerialRestDevice +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,4 +62,5 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsListViewModel.cs b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsListViewModel.cs similarity index 98% rename from UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsListViewModel.cs rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsListViewModel.cs index 6ed480ae..386ed649 100644 --- a/UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsListViewModel.cs +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsListViewModel.cs @@ -69,7 +69,7 @@ public async Task AddNewConfig(RestSerialConfig config) using RestSerialDevice.Config; using RestSerialDevice.Services; -namespace UI.Backend.ViewModels.Settings.Device.SerialRestDevice; +namespace UI.Features.Devices.SerialRestDevice; public partial class SerialRestDeviceSettingsListViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsView.razor b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsView.razor similarity index 89% rename from UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsView.razor rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsView.razor index d9858da2..c0e0c7d1 100644 --- a/UI/Pages/Shared/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsView.razor +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsView.razor @@ -1,4 +1,5 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService @inject IUiNotificationService NotificationService @@ -39,3 +40,5 @@ } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsViewModel.cs b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsViewModel.cs similarity index 88% rename from UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsViewModel.cs rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsViewModel.cs index e5e1e601..5ed29a0a 100644 --- a/UI/Backend/ViewModels/Settings/Device/SerialRestDevice/SerialRestDeviceSettingsViewModel.cs +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceSettingsViewModel.cs @@ -1,78 +1,70 @@ -/*namespace UI.Backend.ViewModels.Settings.Device.SerialRestDevice; - -public class SerialRestDeviceSettingsViewModel -{ - -}*/ - -using Ares.Datamodel.Device; +using Ares.Datamodel.Device; using Ares.Services.Device; using Grpc.Core; using ReactiveUI; using RestSerialDevice.Config; using RestSerialDevice.Services; - -namespace UI.Backend.ViewModels.Settings.Device.SerialRestDevice; +namespace UI.Features.Devices.SerialRestDevice; public class SerialRestDeviceSettingsViewModel : ReactiveObject -{ - private readonly RestSerialDeviceRpc.RestSerialDeviceRpcClient _client; - private readonly DeviceConfig _deviceConfig; - private readonly AresDevices.AresDevicesClient _devicesClient; - - public SerialRestDeviceSettingsViewModel(DeviceConfig deviceConfig, - RestSerialDeviceRpc.RestSerialDeviceRpcClient restClient, - AresDevices.AresDevicesClient devicesClient, - Func onRemoveCallback) - { - _deviceConfig = deviceConfig; - _client = restClient; - _devicesClient = devicesClient; - Config = deviceConfig.ConfigData.Unpack(); - OnRemoveCallback = onRemoveCallback; - EditViewModel = new SerialRestDeviceConfigEditViewModel(_client, _devicesClient, Config); - } - - public async Task GetDeviceOperationalStatus() - { - try - { - return await _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }); - } - - catch(RpcException) - { - return new DeviceOperationalStatus() { OperationalState = OperationalState.Error, Message = $"Unable to find a registered Rest Device with a name {Config.Name}" }; - } - } - - public async Task Save() - { - var config = EditViewModel.Save(); - var updateRequest = new GenericSerialRestDeviceUpdateRequest - { - Id = _deviceConfig.UniqueId, - Config = config, - }; - - await _client.UpdateGenericSerialDeviceAsync(updateRequest); - } - - public Task Activate() - => _devicesClient.ActivateAsync(new DeviceActivateRequest - { - DeviceId = _deviceConfig.UniqueId - }).ResponseAsync; - - public async Task Remove() - { - await _client.RemoveGenericSerialDeviceAsync(new DeviceRequest() { DeviceId = _deviceConfig.UniqueId }); - await OnRemoveCallback(); - } - - public RestSerialConfig Config { get; set; } - public Func OnRemoveCallback { get; set; } +{ + private readonly RestSerialDeviceRpc.RestSerialDeviceRpcClient _client; + private readonly DeviceConfig _deviceConfig; + private readonly AresDevices.AresDevicesClient _devicesClient; + + public SerialRestDeviceSettingsViewModel(DeviceConfig deviceConfig, + RestSerialDeviceRpc.RestSerialDeviceRpcClient restClient, + AresDevices.AresDevicesClient devicesClient, + Func onRemoveCallback) + { + _deviceConfig = deviceConfig; + _client = restClient; + _devicesClient = devicesClient; + Config = deviceConfig.ConfigData.Unpack(); + OnRemoveCallback = onRemoveCallback; + EditViewModel = new SerialRestDeviceConfigEditViewModel(_client, _devicesClient, Config); + } + + public async Task GetDeviceOperationalStatus() + { + try + { + return await _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }); + } + + catch(RpcException) + { + return new DeviceOperationalStatus() { OperationalState = OperationalState.Error, Message = $"Unable to find a registered Rest Device with a name {Config.Name}" }; + } + } + + public async Task Save() + { + var config = EditViewModel.Save(); + var updateRequest = new GenericSerialRestDeviceUpdateRequest + { + Id = _deviceConfig.UniqueId, + Config = config, + }; + + await _client.UpdateGenericSerialDeviceAsync(updateRequest); + } + + public Task Activate() + => _devicesClient.ActivateAsync(new DeviceActivateRequest + { + DeviceId = _deviceConfig.UniqueId + }).ResponseAsync; + + public async Task Remove() + { + await _client.RemoveGenericSerialDeviceAsync(new DeviceRequest() { DeviceId = _deviceConfig.UniqueId }); + await OnRemoveCallback(); + } + + public RestSerialConfig Config { get; set; } + public Func OnRemoveCallback { get; set; } public SerialRestDeviceConfigEditViewModel EditViewModel { get; set; } } diff --git a/UI/Pages/Shared/Devices/SerialRestDevice/SerialRestDeviceUnitControl.razor b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceUnitControl.razor similarity index 93% rename from UI/Pages/Shared/Devices/SerialRestDevice/SerialRestDeviceUnitControl.razor rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceUnitControl.razor index 734b8264..a4d8bc83 100644 --- a/UI/Pages/Shared/Devices/SerialRestDevice/SerialRestDeviceUnitControl.razor +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceUnitControl.razor @@ -1,5 +1,4 @@ -@using UI.Backend.ViewModels.Devices.SerialRestDevice; -@using System.Collections.Generic +@using System.Collections.Generic @inherits ReactiveUI.Blazor.ReactiveComponentBase @inject TooltipService tooltipService diff --git a/UI/Backend/ViewModels/Devices/SerialRestDevice/SerialRestDeviceUnitControlViewModel.cs b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceUnitControlViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Devices/SerialRestDevice/SerialRestDeviceUnitControlViewModel.cs rename to UI/Features/Devices/SerialRestDevice/SerialRestDeviceUnitControlViewModel.cs index 8525ab2a..3d9ac320 100644 --- a/UI/Backend/ViewModels/Devices/SerialRestDevice/SerialRestDeviceUnitControlViewModel.cs +++ b/UI/Features/Devices/SerialRestDevice/SerialRestDeviceUnitControlViewModel.cs @@ -1,6 +1,7 @@ -using RestSerialDevice.Services; +using RestSerialDevice.Services; +using UI.Application.Devices; -namespace UI.Backend.ViewModels.Devices.SerialRestDevice; +namespace UI.Features.Devices.SerialRestDevice; public class SerialRestDeviceUnitControlViewModel : DeviceUnitControlViewModel, IAsyncDisposable { @@ -69,3 +70,4 @@ public async ValueTask DisposeAsync() } + diff --git a/UI/Pages/Shared/Settings/Device/Servo/ServoConfigEditView.razor b/UI/Features/Devices/Servo/ServoConfigEditView.razor similarity index 89% rename from UI/Pages/Shared/Settings/Device/Servo/ServoConfigEditView.razor rename to UI/Features/Devices/Servo/ServoConfigEditView.razor index 579ae977..114bdbfc 100644 --- a/UI/Pages/Shared/Settings/Device/Servo/ServoConfigEditView.razor +++ b/UI/Features/Devices/Servo/ServoConfigEditView.razor @@ -1,39 +1,39 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - -
- - - - @if (ViewModel!.AvailablePorts is not null) - { - @foreach (var port in ViewModel!.AvailablePorts) - { - - } - } - -
- - Simulated -
-
- - - -
-@code { - - [Parameter] - public Action OnValidSubmit { get; set; } = delegate - { - }; - - private void PortChanged(ChangeEventArgs obj) - { - if (obj.Value is not string stringValue) - throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); - - ViewModel!.Port = stringValue; - } +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + +
+ + + + @if (ViewModel!.AvailablePorts is not null) + { + @foreach (var port in ViewModel!.AvailablePorts) + { + + } + } + +
+ + Simulated +
+
+ + + +
+@code { + + [Parameter] + public Action OnValidSubmit { get; set; } = delegate + { + }; + + private void PortChanged(ChangeEventArgs obj) + { + if (obj.Value is not string stringValue) + throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); + + ViewModel!.Port = stringValue; + } } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Settings/Device/Servo/ServoConfigEditViewModel.cs b/UI/Features/Devices/Servo/ServoConfigEditViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/Servo/ServoConfigEditViewModel.cs rename to UI/Features/Devices/Servo/ServoConfigEditViewModel.cs index 00cf41ad..f6a0d996 100644 --- a/UI/Backend/ViewModels/Settings/Device/Servo/ServoConfigEditViewModel.cs +++ b/UI/Features/Devices/Servo/ServoConfigEditViewModel.cs @@ -1,74 +1,74 @@ -using Ares.Services.Device; -using Google.Protobuf.WellKnownTypes; -using HerkulexDRS.Config; -using HerkulexDRS.Services; -using Microsoft.Build.Framework; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels.Settings.Device.Servo; - -public partial class ServoConfigEditViewModel : ReactiveObject -{ - private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _client; - private readonly ServoConfig _config; - private readonly AresDevices.AresDevicesClient _devicesClient; - private string? _name; - - public ServoConfigEditViewModel(HerkulexDRSRpc.HerkulexDRSRpcClient client, AresDevices.AresDevicesClient devicesClient) - { - _client = client; - _devicesClient = devicesClient; - _config = new ServoConfig(); - NewConfig = true; - _ = UpdateAvailableSerialPorts(); - } - - public ServoConfigEditViewModel(HerkulexDRSRpc.HerkulexDRSRpcClient client, AresDevices.AresDevicesClient devicesClient, ServoConfig config) - { - _client = client; - _devicesClient = devicesClient; - _config = config; - _ = UpdateAvailableSerialPorts(); - _name = config.Name; - Port = config.PortName; - Simulated = config.Simulated; - } - - [Required] - public string? Name - { - get => _name; - - set - { - if (!NewConfig) - return; - - _name = value; - } - } - - [Required] - public string? Port { get; set; } - - public bool NewConfig { get; set; } - - public bool Simulated { get; set; } - - [Reactive] - public partial IEnumerable? AvailablePorts { get; private set; } - - public bool Modified => _config.Name != Name || _config.PortName != Port || _config.Simulated != Simulated; - - public async Task UpdateAvailableSerialPorts() - { - AvailablePorts = null; - Port = null; - var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); - AvailablePorts = ports.SerialPorts; - } - - public ServoConfig Save() - => Modified ? new ServoConfig { Name = Name, PortName = Port, Simulated = Simulated } : _config; -} +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using HerkulexDRS.Config; +using HerkulexDRS.Services; +using Microsoft.Build.Framework; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace UI.Features.Devices.Servo; + +public partial class ServoConfigEditViewModel : ReactiveObject +{ + private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _client; + private readonly ServoConfig _config; + private readonly AresDevices.AresDevicesClient _devicesClient; + private string? _name; + + public ServoConfigEditViewModel(HerkulexDRSRpc.HerkulexDRSRpcClient client, AresDevices.AresDevicesClient devicesClient) + { + _client = client; + _devicesClient = devicesClient; + _config = new ServoConfig(); + NewConfig = true; + _ = UpdateAvailableSerialPorts(); + } + + public ServoConfigEditViewModel(HerkulexDRSRpc.HerkulexDRSRpcClient client, AresDevices.AresDevicesClient devicesClient, ServoConfig config) + { + _client = client; + _devicesClient = devicesClient; + _config = config; + _ = UpdateAvailableSerialPorts(); + _name = config.Name; + Port = config.PortName; + Simulated = config.Simulated; + } + + [Required] + public string? Name + { + get => _name; + + set + { + if (!NewConfig) + return; + + _name = value; + } + } + + [Required] + public string? Port { get; set; } + + public bool NewConfig { get; set; } + + public bool Simulated { get; set; } + + [Reactive] + public partial IEnumerable? AvailablePorts { get; private set; } + + public bool Modified => _config.Name != Name || _config.PortName != Port || _config.Simulated != Simulated; + + public async Task UpdateAvailableSerialPorts() + { + AvailablePorts = null; + Port = null; + var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); + AvailablePorts = ports.SerialPorts; + } + + public ServoConfig Save() + => Modified ? new ServoConfig { Name = Name, PortName = Port, Simulated = Simulated } : _config; +} diff --git a/UI/Pages/Shared/Devices/Servo/ServoControlWidgetView.razor b/UI/Features/Devices/Servo/ServoControlWidgetView.razor similarity index 90% rename from UI/Pages/Shared/Devices/Servo/ServoControlWidgetView.razor rename to UI/Features/Devices/Servo/ServoControlWidgetView.razor index 2c6ac4f4..4bf0367d 100644 --- a/UI/Pages/Shared/Devices/Servo/ServoControlWidgetView.razor +++ b/UI/Features/Devices/Servo/ServoControlWidgetView.razor @@ -1,5 +1,4 @@ -@using UI.Backend.ViewModels.Devices.HerkulexDRS -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject TooltipService tooltipService diff --git a/UI/Pages/Shared/Devices/Servo/ServoControlWidgetView.razor.css b/UI/Features/Devices/Servo/ServoControlWidgetView.razor.css similarity index 94% rename from UI/Pages/Shared/Devices/Servo/ServoControlWidgetView.razor.css rename to UI/Features/Devices/Servo/ServoControlWidgetView.razor.css index 995a7609..fc0649a9 100644 --- a/UI/Pages/Shared/Devices/Servo/ServoControlWidgetView.razor.css +++ b/UI/Features/Devices/Servo/ServoControlWidgetView.razor.css @@ -1,3 +1,3 @@ -.servobutton { - margin: 5px; +.servobutton { + margin: 5px; } \ No newline at end of file diff --git a/UI/Backend/Factories/ServoDeviceControlViewModelFactory.cs b/UI/Features/Devices/Servo/ServoDeviceControlViewModelFactory.cs similarity index 87% rename from UI/Backend/Factories/ServoDeviceControlViewModelFactory.cs rename to UI/Features/Devices/Servo/ServoDeviceControlViewModelFactory.cs index 5d3272da..ed33c51e 100644 --- a/UI/Backend/Factories/ServoDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/Servo/ServoDeviceControlViewModelFactory.cs @@ -1,12 +1,12 @@ -using Ares.Services.Device; +using Ares.Services.Device; using DynamicData; using Google.Protobuf.WellKnownTypes; using HerkulexDRS.Services; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Devices.HerkulexDRS; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.Servo; public class ServoDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { @@ -31,3 +31,4 @@ protected override async Task> GetAvailableDe return servos; } } + diff --git a/UI/Pages/Shared/Settings/Device/Servo/ServoSettingsList.razor b/UI/Features/Devices/Servo/ServoSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/Servo/ServoSettingsList.razor rename to UI/Features/Devices/Servo/ServoSettingsList.razor index 06d6e57b..6d8e8688 100644 --- a/UI/Pages/Shared/Settings/Device/Servo/ServoSettingsList.razor +++ b/UI/Features/Devices/Servo/ServoSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/servo" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.Servo +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,4 +62,5 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Device/Servo/ServoSettingsListViewModel.cs b/UI/Features/Devices/Servo/ServoSettingsListViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/Servo/ServoSettingsListViewModel.cs rename to UI/Features/Devices/Servo/ServoSettingsListViewModel.cs index d2023fd9..d3ba549b 100644 --- a/UI/Backend/ViewModels/Settings/Device/Servo/ServoSettingsListViewModel.cs +++ b/UI/Features/Devices/Servo/ServoSettingsListViewModel.cs @@ -1,57 +1,57 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using HerkulexDRS; -using HerkulexDRS.Config; -using HerkulexDRS.Services; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels.Settings.Device.Servo; - -public partial class ServoSettingsListViewModel : ReactiveObject -{ - private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _servoClient; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public ServoSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, HerkulexDRSRpc.HerkulexDRSRpcClient servoClient, IMessenger messenger) - { - _servoClient = servoClient; - _devicesClient = devicesClient; - _messenger = messenger; - UpdateConfigs(); - } - - [Reactive] - public partial IEnumerable? SettingsViewModels { get; private set; } - - private void UpdateViewModels(IEnumerable deviceConfigs) - { - var viewModels = deviceConfigs.Select(config => new ServoSettingsViewModel(config, _servoClient, _devicesClient, _messenger, OnConfigRemoved)); - SettingsViewModels = viewModels; - } - - public ServoConfigEditViewModel GetNewConfigEditViewModel() - => new(_servoClient, _devicesClient); - - private Task UpdateConfigs() - { - SettingsViewModels = null; - return _devicesClient - .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IServo).FullName }) - .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); - } - - private async Task OnConfigRemoved() - { - SettingsViewModels = null; - await UpdateConfigs(); - } - - public async Task AddNewConfig(ServoConfig config) - { - await _servoClient.AddServoAsync(config); - await UpdateConfigs(); - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using HerkulexDRS; +using HerkulexDRS.Config; +using HerkulexDRS.Services; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace UI.Features.Devices.Servo; + +public partial class ServoSettingsListViewModel : ReactiveObject +{ + private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _servoClient; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public ServoSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, HerkulexDRSRpc.HerkulexDRSRpcClient servoClient, IMessenger messenger) + { + _servoClient = servoClient; + _devicesClient = devicesClient; + _messenger = messenger; + UpdateConfigs(); + } + + [Reactive] + public partial IEnumerable? SettingsViewModels { get; private set; } + + private void UpdateViewModels(IEnumerable deviceConfigs) + { + var viewModels = deviceConfigs.Select(config => new ServoSettingsViewModel(config, _servoClient, _devicesClient, _messenger, OnConfigRemoved)); + SettingsViewModels = viewModels; + } + + public ServoConfigEditViewModel GetNewConfigEditViewModel() + => new(_servoClient, _devicesClient); + + private Task UpdateConfigs() + { + SettingsViewModels = null; + return _devicesClient + .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IServo).FullName }) + .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); + } + + private async Task OnConfigRemoved() + { + SettingsViewModels = null; + await UpdateConfigs(); + } + + public async Task AddNewConfig(ServoConfig config) + { + await _servoClient.AddServoAsync(config); + await UpdateConfigs(); + } +} diff --git a/UI/Pages/Shared/Settings/Device/Servo/ServoSettingsView.razor b/UI/Features/Devices/Servo/ServoSettingsView.razor similarity index 91% rename from UI/Pages/Shared/Settings/Device/Servo/ServoSettingsView.razor rename to UI/Features/Devices/Servo/ServoSettingsView.razor index 0df10688..bb8bc080 100644 --- a/UI/Pages/Shared/Settings/Device/Servo/ServoSettingsView.razor +++ b/UI/Features/Devices/Servo/ServoSettingsView.razor @@ -1,4 +1,5 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService @inject IUiNotificationService NotificationService @@ -39,4 +40,5 @@ await ViewModel!.Remove(); } } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Device/Servo/ServoSettingsViewModel.cs b/UI/Features/Devices/Servo/ServoSettingsViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/Servo/ServoSettingsViewModel.cs rename to UI/Features/Devices/Servo/ServoSettingsViewModel.cs index 28b1c70f..8fa0f8f8 100644 --- a/UI/Backend/ViewModels/Settings/Device/Servo/ServoSettingsViewModel.cs +++ b/UI/Features/Devices/Servo/ServoSettingsViewModel.cs @@ -1,70 +1,71 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using Grpc.Core; -using HerkulexDRS.Config; -using HerkulexDRS.Services; -using ReactiveUI; -using UI.Backend.Devices; - -namespace UI.Backend.ViewModels.Settings.Device.Servo; - -public class ServoSettingsViewModel : ReactiveObject -{ - private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _servoClient; - private readonly DeviceConfig _deviceConfig; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public ServoSettingsViewModel(DeviceConfig deviceConfig, - HerkulexDRSRpc.HerkulexDRSRpcClient servoClient, - AresDevices.AresDevicesClient devicesClient, - IMessenger messenger, - Func onRemoveCallback) - { - _deviceConfig = deviceConfig; - _servoClient = servoClient; - ServoConfig = deviceConfig.ConfigData.Unpack(); - _devicesClient = devicesClient; - _messenger = messenger; - OnRemoveCallback = onRemoveCallback; - EditViewModel = new ServoConfigEditViewModel(_servoClient, _devicesClient, ServoConfig); - } - - public ServoConfig ServoConfig { get; } - public Func OnRemoveCallback { get; } - public ServoConfigEditViewModel EditViewModel { get; } - - public Task GetDeviceOperationalStatus() - { - try - { - return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - } - - catch(RpcException) - { - return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered Servo with a name {ServoConfig.Name}" }); - } - } - - public async Task Save() - { - var servoConfig = EditViewModel.Save(); - await _servoClient.UpdateServoAsync(servoConfig); - } - - public Task Activate() - => _devicesClient.ActivateAsync(new DeviceActivateRequest - { - DeviceId = _deviceConfig.UniqueId - }).ResponseAsync; - - public async Task Remove() - { - await _servoClient.RemoveServoAsync(new HerkulexRequest { HerkulexId = _deviceConfig.UniqueId }); - _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); - await OnRemoveCallback(); - } -} - +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using Grpc.Core; +using HerkulexDRS.Config; +using HerkulexDRS.Services; +using ReactiveUI; +using UI.Application.Devices; +using UI.Features.Devices.Shared; + +namespace UI.Features.Devices.Servo; + +public class ServoSettingsViewModel : ReactiveObject +{ + private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _servoClient; + private readonly DeviceConfig _deviceConfig; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public ServoSettingsViewModel(DeviceConfig deviceConfig, + HerkulexDRSRpc.HerkulexDRSRpcClient servoClient, + AresDevices.AresDevicesClient devicesClient, + IMessenger messenger, + Func onRemoveCallback) + { + _deviceConfig = deviceConfig; + _servoClient = servoClient; + ServoConfig = deviceConfig.ConfigData.Unpack(); + _devicesClient = devicesClient; + _messenger = messenger; + OnRemoveCallback = onRemoveCallback; + EditViewModel = new ServoConfigEditViewModel(_servoClient, _devicesClient, ServoConfig); + } + + public ServoConfig ServoConfig { get; } + public Func OnRemoveCallback { get; } + public ServoConfigEditViewModel EditViewModel { get; } + + public Task GetDeviceOperationalStatus() + { + try + { + return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + } + + catch(RpcException) + { + return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered Servo with a name {ServoConfig.Name}" }); + } + } + + public async Task Save() + { + var servoConfig = EditViewModel.Save(); + await _servoClient.UpdateServoAsync(servoConfig); + } + + public Task Activate() + => _devicesClient.ActivateAsync(new DeviceActivateRequest + { + DeviceId = _deviceConfig.UniqueId + }).ResponseAsync; + + public async Task Remove() + { + await _servoClient.RemoveServoAsync(new HerkulexRequest { HerkulexId = _deviceConfig.UniqueId }); + _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); + await OnRemoveCallback(); + } +} + diff --git a/UI/Backend/ViewModels/Devices/HerkulexDRS/ServoUnitControlViewModel.cs b/UI/Features/Devices/Servo/ServoUnitControlViewModel.cs similarity index 81% rename from UI/Backend/ViewModels/Devices/HerkulexDRS/ServoUnitControlViewModel.cs rename to UI/Features/Devices/Servo/ServoUnitControlViewModel.cs index e60471f4..11472c0b 100644 --- a/UI/Backend/ViewModels/Devices/HerkulexDRS/ServoUnitControlViewModel.cs +++ b/UI/Features/Devices/Servo/ServoUnitControlViewModel.cs @@ -1,31 +1,32 @@ -using HerkulexDRS.Services; -using UI.Pages.Shared.Devices.Servo; - -namespace UI.Backend.ViewModels.Devices.HerkulexDRS; - -public class ServoUnitControlViewModel : DeviceUnitControlViewModel -{ - private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _client; - - public ServoUnitControlViewModel(string deviceId, string deviceName, HerkulexDRSRpc.HerkulexDRSRpcClient client) : base(deviceId, deviceName) - { - _client = client; - ViewType = typeof(ServoControlWidgetView); - DefaultWidth = 20; - } - - public void PistonUp() - { - _client.PistonUp(new DeviceRequest { DeviceId = DeviceId }); - } - - public void PistonDown() - { - _client.PistonDown(new DeviceRequest { DeviceId = DeviceId }); - } - - public void ServoReset() - { - _client.ResetServo(new DeviceRequest { DeviceId = DeviceId }); - } -} +using HerkulexDRS.Services; +using UI.Application.Devices; + +namespace UI.Features.Devices.Servo; + +public class ServoUnitControlViewModel : DeviceUnitControlViewModel +{ + private readonly HerkulexDRSRpc.HerkulexDRSRpcClient _client; + + public ServoUnitControlViewModel(string deviceId, string deviceName, HerkulexDRSRpc.HerkulexDRSRpcClient client) : base(deviceId, deviceName) + { + _client = client; + ViewType = typeof(ServoControlWidgetView); + DefaultWidth = 20; + } + + public void PistonUp() + { + _client.PistonUp(new DeviceRequest { DeviceId = DeviceId }); + } + + public void PistonDown() + { + _client.PistonDown(new DeviceRequest { DeviceId = DeviceId }); + } + + public void ServoReset() + { + _client.ResetServo(new DeviceRequest { DeviceId = DeviceId }); + } +} + diff --git a/UI/Pages/Shared/Settings/Device/DeviceInfoContainer.razor b/UI/Features/Devices/Shared/DeviceInfoContainer.razor similarity index 98% rename from UI/Pages/Shared/Settings/Device/DeviceInfoContainer.razor rename to UI/Features/Devices/Shared/DeviceInfoContainer.razor index 3cbd8d24..1c4d9520 100644 --- a/UI/Pages/Shared/Settings/Device/DeviceInfoContainer.razor +++ b/UI/Features/Devices/Shared/DeviceInfoContainer.razor @@ -1,8 +1,5 @@ @using Ares.Datamodel @using Ares.Datamodel.Device -@using Radzen -@using Radzen.Blazor -@using System.Collections.Generic diff --git a/UI/Pages/Shared/Settings/Device/DeviceSettingsContainer.razor b/UI/Features/Devices/Shared/Settings/DeviceSettingsContainer.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/DeviceSettingsContainer.razor rename to UI/Features/Devices/Shared/Settings/DeviceSettingsContainer.razor index 4822dab2..c2d8c8bf 100644 --- a/UI/Pages/Shared/Settings/Device/DeviceSettingsContainer.razor +++ b/UI/Features/Devices/Shared/Settings/DeviceSettingsContainer.razor @@ -1,131 +1,131 @@ -@using Ares.Datamodel.Device - -
-
@Name
- @if (DeviceOperationalStatusGetter is not null) - { -
- - @if (_deviceOperationalStatus is not null) - { -
-
- @_deviceOperationalStatus.OperationalState.ToString() -
- @if(_deviceOperationalStatus.OperationalState == OperationalState.Active) - { -
- @_deviceOperationalStatus.Message -
- } - else if(_deviceOperationalStatus.OperationalState == OperationalState.Inactive) - { -
- Couldn't connect to @Name. Is it running? -
- } - else - { -
- Error when connecting to device! Check notification history. -
- } - -
- } - else - { - - } -
- } -
- - - - - -
-
-
- @ChildContent -
-
- -@code { - [Parameter] - public RenderFragment? ChildContent { get; set; } - - [Parameter] - public Func? RemoveCallback { get; set; } - - [Parameter] - public Func? SettingsCallback { get; set; } - - [Parameter] - public Func? EditCallback { get; set; } - - [Parameter] - public Func? ActivateCallback { get; set; } - - [Parameter] - public Func>? DeviceOperationalStatusGetter { get; set; } - - [Parameter] - public Func? ShowDeviceInfoCallback { get; set; } - - [Parameter] - public string Name { get; set; } = "No name?"; - - private DeviceOperationalStatus? _deviceOperationalStatus; - - protected override async Task OnInitializedAsync() - { - if (DeviceOperationalStatusGetter is not null) - { - _deviceOperationalStatus = await DeviceOperationalStatusGetter(); - StateHasChanged(); - } - } - - private Task EditClicked() - { - return EditCallback is not null ? EditCallback() : Task.CompletedTask; - } - - private Task SettingsClicked() - { - return SettingsCallback is not null ? SettingsCallback() : Task.CompletedTask; - } - - private Task RemoveClicked() - { - return RemoveCallback is not null ? RemoveCallback() : Task.CompletedTask; - } - - private Task InfoClicked() - { - return ShowDeviceInfoCallback is not null ? ShowDeviceInfoCallback() : Task.CompletedTask; - } - - private async Task UpdateDeviceOperationalStatus() - { - if (DeviceOperationalStatusGetter is null) - return; - - _deviceOperationalStatus = null; - _deviceOperationalStatus = await DeviceOperationalStatusGetter(); - } - - private async Task ActivateClicked() - { - if (ActivateCallback is null) - return; - - await ActivateCallback(); - await UpdateDeviceOperationalStatus(); - } - -} +@using Ares.Datamodel.Device + +
+
@Name
+ @if (DeviceOperationalStatusGetter is not null) + { +
+ + @if (_deviceOperationalStatus is not null) + { +
+
+ @_deviceOperationalStatus.OperationalState.ToString() +
+ @if(_deviceOperationalStatus.OperationalState == OperationalState.Active) + { +
+ @_deviceOperationalStatus.Message +
+ } + else if(_deviceOperationalStatus.OperationalState == OperationalState.Inactive) + { +
+ Couldn't connect to @Name. Is it running? +
+ } + else + { +
+ Error when connecting to device! Check notification history. +
+ } + +
+ } + else + { + + } +
+ } +
+ + + + + +
+
+
+ @ChildContent +
+
+ +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public Func? RemoveCallback { get; set; } + + [Parameter] + public Func? SettingsCallback { get; set; } + + [Parameter] + public Func? EditCallback { get; set; } + + [Parameter] + public Func? ActivateCallback { get; set; } + + [Parameter] + public Func>? DeviceOperationalStatusGetter { get; set; } + + [Parameter] + public Func? ShowDeviceInfoCallback { get; set; } + + [Parameter] + public string Name { get; set; } = "No name?"; + + private DeviceOperationalStatus? _deviceOperationalStatus; + + protected override async Task OnInitializedAsync() + { + if (DeviceOperationalStatusGetter is not null) + { + _deviceOperationalStatus = await DeviceOperationalStatusGetter(); + StateHasChanged(); + } + } + + private Task EditClicked() + { + return EditCallback is not null ? EditCallback() : Task.CompletedTask; + } + + private Task SettingsClicked() + { + return SettingsCallback is not null ? SettingsCallback() : Task.CompletedTask; + } + + private Task RemoveClicked() + { + return RemoveCallback is not null ? RemoveCallback() : Task.CompletedTask; + } + + private Task InfoClicked() + { + return ShowDeviceInfoCallback is not null ? ShowDeviceInfoCallback() : Task.CompletedTask; + } + + private async Task UpdateDeviceOperationalStatus() + { + if (DeviceOperationalStatusGetter is null) + return; + + _deviceOperationalStatus = null; + _deviceOperationalStatus = await DeviceOperationalStatusGetter(); + } + + private async Task ActivateClicked() + { + if (ActivateCallback is null) + return; + + await ActivateCallback(); + await UpdateDeviceOperationalStatus(); + } + +} diff --git a/UI/Pages/Shared/Settings/Device/DeviceSettingsContainer.razor.css b/UI/Features/Devices/Shared/Settings/DeviceSettingsContainer.razor.css similarity index 94% rename from UI/Pages/Shared/Settings/Device/DeviceSettingsContainer.razor.css rename to UI/Features/Devices/Shared/Settings/DeviceSettingsContainer.razor.css index 1fa41b42..ee9b07da 100644 --- a/UI/Pages/Shared/Settings/Device/DeviceSettingsContainer.razor.css +++ b/UI/Features/Devices/Shared/Settings/DeviceSettingsContainer.razor.css @@ -1,52 +1,52 @@ -.device-settings-grid { - display: grid; - background: rgba(224, 255, 255, 0.1); - padding: 10px; - /*border: 1px solid rgba(224, 255, 255, 0.3);*/ - border-radius: 5px; - grid-template: - "name status actions" auto - "separator separator separator" auto - "content content content" auto - / 1fr 1fr auto; - box-shadow: 5px 5px 5px rgba(10, 10, 10, 0.7); -} - -.device-settings-content { - grid-area: content; -} - -.device-status { - grid-area: status; - font-size: 0.75em; -} - -.device-status .device-status-active { - color: green; - } - -.device-status .device-status-inactive { - color: grey; - } - -.device-status .device-status-error { - color: darkred; - } - -.device-settings-name { - grid-area: name; -} - -.device-settings-actions { - grid-area: actions; - justify-self: right; -} - -.device-settings-separator { - grid-area: separator; - margin: 5px; -} - -.device-action-button { - margin: 0 2px; -} +.device-settings-grid { + display: grid; + background: rgba(224, 255, 255, 0.1); + padding: 10px; + /*border: 1px solid rgba(224, 255, 255, 0.3);*/ + border-radius: 5px; + grid-template: + "name status actions" auto + "separator separator separator" auto + "content content content" auto + / 1fr 1fr auto; + box-shadow: 5px 5px 5px rgba(10, 10, 10, 0.7); +} + +.device-settings-content { + grid-area: content; +} + +.device-status { + grid-area: status; + font-size: 0.75em; +} + +.device-status .device-status-active { + color: green; + } + +.device-status .device-status-inactive { + color: grey; + } + +.device-status .device-status-error { + color: darkred; + } + +.device-settings-name { + grid-area: name; +} + +.device-settings-actions { + grid-area: actions; + justify-self: right; +} + +.device-settings-separator { + grid-area: separator; + margin: 5px; +} + +.device-action-button { + margin: 0 2px; +} diff --git a/UI/Pages/Shared/Settings/Device/DeviceSettingsLayout.razor b/UI/Features/Devices/Shared/Settings/DeviceSettingsLayout.razor similarity index 98% rename from UI/Pages/Shared/Settings/Device/DeviceSettingsLayout.razor rename to UI/Features/Devices/Shared/Settings/DeviceSettingsLayout.razor index 84f18e55..827a237e 100644 --- a/UI/Pages/Shared/Settings/Device/DeviceSettingsLayout.razor +++ b/UI/Features/Devices/Shared/Settings/DeviceSettingsLayout.razor @@ -1,33 +1,33 @@ -@inherits LayoutComponentBase -@layout SettingsLayout - -
-
- -
-
-
-
-
- @Body -
- -@code { - -} +@inherits LayoutComponentBase +@layout SettingsLayout + +
+
+ +
+
+
+
+
+ @Body +
+ +@code { + +} diff --git a/UI/Pages/Shared/Settings/Device/EmptyDevicePage.razor b/UI/Features/Devices/Shared/Settings/EmptyDeviceSettingsPage.razor similarity index 93% rename from UI/Pages/Shared/Settings/Device/EmptyDevicePage.razor rename to UI/Features/Devices/Shared/Settings/EmptyDeviceSettingsPage.razor index 4f9914f0..b05969ec 100644 --- a/UI/Pages/Shared/Settings/Device/EmptyDevicePage.razor +++ b/UI/Features/Devices/Shared/Settings/EmptyDeviceSettingsPage.razor @@ -1,5 +1,5 @@ -@page "/settings/device" -@layout DeviceSettingsLayout - -@code { -} +@page "/settings/device" +@layout DeviceSettingsLayout + +@code { +} diff --git a/UI/Pages/Shared/Devices/StepperController/BoolVisualizer.razor b/UI/Features/Devices/StepperController/BoolVisualizer.razor similarity index 94% rename from UI/Pages/Shared/Devices/StepperController/BoolVisualizer.razor rename to UI/Features/Devices/StepperController/BoolVisualizer.razor index dea7bf23..5e33cce1 100644 --- a/UI/Pages/Shared/Devices/StepperController/BoolVisualizer.razor +++ b/UI/Features/Devices/StepperController/BoolVisualizer.razor @@ -1,19 +1,19 @@ - -
- @if (Bool) - { - - } - else - { - - } - @Label -
-@code { - [Parameter] - public bool Bool { get; set; } - - [Parameter] - public string Label { get; set; } = string.Empty; -} + +
+ @if (Bool) + { + + } + else + { + + } + @Label +
+@code { + [Parameter] + public bool Bool { get; set; } + + [Parameter] + public string Label { get; set; } = string.Empty; +} diff --git a/UI/Pages/Shared/Devices/StepperController/SimpleDataDisplay.razor b/UI/Features/Devices/StepperController/SimpleDataDisplay.razor similarity index 96% rename from UI/Pages/Shared/Devices/StepperController/SimpleDataDisplay.razor rename to UI/Features/Devices/StepperController/SimpleDataDisplay.razor index fdd506ac..c74388fc 100644 --- a/UI/Pages/Shared/Devices/StepperController/SimpleDataDisplay.razor +++ b/UI/Features/Devices/StepperController/SimpleDataDisplay.razor @@ -1,12 +1,12 @@ - - @Label - - -@code { - [Parameter] - public string Label { get; set; } = string.Empty; - - [Parameter] - public object Data { get; set; } = string.Empty; - -} + + @Label + + +@code { + [Parameter] + public string Label { get; set; } = string.Empty; + + [Parameter] + public object Data { get; set; } = string.Empty; + +} diff --git a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerConfigEditView.razor b/UI/Features/Devices/StepperController/StepperControllerConfigEditView.razor similarity index 95% rename from UI/Pages/Shared/Settings/Device/StepperController/StepperControllerConfigEditView.razor rename to UI/Features/Devices/StepperController/StepperControllerConfigEditView.razor index 2973b74d..b29f6490 100644 --- a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerConfigEditView.razor +++ b/UI/Features/Devices/StepperController/StepperControllerConfigEditView.razor @@ -1,88 +1,87 @@ -@using TicStepperController.Messaging; -@using UI.Backend.ViewModels.Settings.Device.StepperController - -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @if(ViewModel!.DynamicStepCalculation) - { - - - - - - - - - - - - - - - - } - - - - - - - - - - - - - - - - - - - - - -@code { - [Parameter] - public Action OnValidSubmit { get; set; } = delegate - { - }; -} +@using TicStepperController.Messaging; + +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if(ViewModel!.DynamicStepCalculation) + { + + + + + + + + + + + + + + + + } + + + + + + + + + + + + + + + + + + + + + +@code { + [Parameter] + public Action OnValidSubmit { get; set; } = delegate + { + }; +} diff --git a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerConfigEditView.razor.css b/UI/Features/Devices/StepperController/StepperControllerConfigEditView.razor.css similarity index 92% rename from UI/Pages/Shared/Settings/Device/StepperController/StepperControllerConfigEditView.razor.css rename to UI/Features/Devices/StepperController/StepperControllerConfigEditView.razor.css index 8f89307d..7c44cfee 100644 --- a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerConfigEditView.razor.css +++ b/UI/Features/Devices/StepperController/StepperControllerConfigEditView.razor.css @@ -1,54 +1,54 @@ -.stepper-config-grid { - display: grid; - grid-template: - "name ." 1fr - "port ." 1fr - "simulated ." 1fr - "properties ." auto - / auto 40px -} - -.stepper-property-grid { - grid-area: properties; - grid-template-columns: 1fr 1fr; -} - -.stepper-max-acceleration { - grid-column: 0; -} - -.stepper-max-deceleration { - grid-column: 1; -} - -.stepper-starting-speed { - grid-column: 0; -} - -.stepper-max-speed { - grid-column: 1; -} - -.stepper-custom-step-size { - grid-column: 0; -} - -.stepper-step-mode { - grid-column: 1; -} - -.stepper-name { - grid-area: name; -} - -.stepper-port { - grid-area: port; -} - -.stepper-id { - grid-area: id; -} - -.stepper-simulated { - grid-area: simulated; +.stepper-config-grid { + display: grid; + grid-template: + "name ." 1fr + "port ." 1fr + "simulated ." 1fr + "properties ." auto + / auto 40px +} + +.stepper-property-grid { + grid-area: properties; + grid-template-columns: 1fr 1fr; +} + +.stepper-max-acceleration { + grid-column: 0; +} + +.stepper-max-deceleration { + grid-column: 1; +} + +.stepper-starting-speed { + grid-column: 0; +} + +.stepper-max-speed { + grid-column: 1; +} + +.stepper-custom-step-size { + grid-column: 0; +} + +.stepper-step-mode { + grid-column: 1; +} + +.stepper-name { + grid-area: name; +} + +.stepper-port { + grid-area: port; +} + +.stepper-id { + grid-area: id; +} + +.stepper-simulated { + grid-area: simulated; } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerConfigEditViewModel.cs b/UI/Features/Devices/StepperController/StepperControllerConfigEditViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerConfigEditViewModel.cs rename to UI/Features/Devices/StepperController/StepperControllerConfigEditViewModel.cs index 44ab97cd..44a04c1e 100644 --- a/UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerConfigEditViewModel.cs +++ b/UI/Features/Devices/StepperController/StepperControllerConfigEditViewModel.cs @@ -1,174 +1,174 @@ -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using System.ComponentModel.DataAnnotations; -using Ares.Services.Device; -using TicStepperController.Config; -using TicStepperController.Messaging; - -namespace UI.Backend.ViewModels.Settings.Device.StepperController; - -public partial class StepperControllerConfigEditViewModel : ReactiveObject -{ - private readonly StepperControllerRpc.StepperControllerRpcClient _stepperClient; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly StepperControllerConfig _config; - private string _name = string.Empty; - - public StepperControllerConfigEditViewModel(StepperControllerRpc.StepperControllerRpcClient stepperClient, AresDevices.AresDevicesClient devicesClient) - { - _stepperClient = stepperClient; - _devicesClient = devicesClient; - _config = new StepperControllerConfig(); - NewConfig = true; - _ = UpdateAvailableSerialPorts(); - StepAngle = 1.8; - CustomStepSize = 1; - } - - public StepperControllerConfigEditViewModel(StepperControllerRpc.StepperControllerRpcClient stepperClient, AresDevices.AresDevicesClient devicesClient, - StepperControllerConfig config) - { - _stepperClient = stepperClient; - _devicesClient = devicesClient; - _config = config; - _ = UpdateAvailableSerialPorts(); - StepAngle = 1.8; - CustomStepSize = 1; - LoadConfig(config); - } - - public bool NewConfig { get; private set; } - - [Required] - public string Name - { - get => _name; - set - { - if(!NewConfig) - { - return; - } - - this.RaiseAndSetIfChanged(ref _name, value); - } - } - - public void LoadConfig(StepperControllerConfig config) - { - Port = config.PortName; - _name = config.Name; - MaxAcceleration = config.MaxAcceleration; - MaxDeceleration = config.MaxDeceleration; - StartingSpeed = config.StartingSpeed; - CustomStepSize = config.CustomStepSize; - MaxSpeed = config.MaxSpeed; - CurrentLimit = config.CurrentLimit; - StepMode = config.StepMode; - Simulated = config.Simulated; - DynamicStepCalculation = config.DynamicStepCalculation; - InitialSpoolRadius = config.SpoolRadius; - FilterPaperThickness = config.FilterPaperThickness; - LinearStepSize = config.IdealLinearStepSize; - StepAngle = config.StepAngle; - } - - [Reactive] - public partial IEnumerable? AvailablePorts { get; private set; } - - public async Task UpdateAvailableSerialPorts() - { - AvailablePorts = null; - Port = null; - var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); - AvailablePorts = ports.SerialPorts; - } - - [Reactive] - [Required] - public partial string? Port { get; set; } - - [Reactive] - [Range(100, 2_147_483_647, ErrorMessage = "Acceleration must be a value from 100 to 2,147,483,647 microsteps per 100 s²")] - public partial uint? MaxAcceleration { get; set; } - - [Reactive] - [Range(100, 2_147_483_647, ErrorMessage = "Deceleration must be a value from 100 to 2,147,483,647 microsteps per 100 s²")] - public partial uint? MaxDeceleration { get; set; } - - [Reactive] - [Range(0, 500_000_000, ErrorMessage = "Starting speed must be a value from 0 to 500,000,000 microsteps per 10,000 s")] - public partial uint? StartingSpeed { get; set; } - - [Reactive] - [Range(1, uint.MaxValue, ErrorMessage = "Custom step size must be greater than 0")] - public partial uint? CustomStepSize { get; set; } - - [Reactive] - [Range(100, 500_000_000, ErrorMessage = "Max speed must be a value from 0 to 500,000,000 microsteps per 10,000 s")] - public partial uint? MaxSpeed { get; set; } - - [Reactive] - [Range(0, 64, ErrorMessage = "Current Limit must be between 0 and 64")] - public partial uint? CurrentLimit { get; set; } - - [Reactive] - public partial StepMode StepMode { get; set; } - - [Reactive] - public partial bool Simulated { get; set; } - - [Reactive] - public partial bool DynamicStepCalculation { get; set; } //Determines whether a spool is set to dynamically calculate its steps - - [Reactive] - public partial double? InitialSpoolRadius { get; set; } - - [Reactive] - public partial double? FilterPaperThickness { get; set; } - - [Reactive] - public partial double? LinearStepSize { get; set; } - - [Reactive] - public partial double? StepAngle { get; set; } - - public bool Modified => _config.Name != Name - || _config.PortName != Port - || _config.MaxAcceleration != MaxAcceleration - || _config.MaxDeceleration != MaxDeceleration - || _config.StartingSpeed != StartingSpeed - || _config.CustomStepSize != CustomStepSize - || _config.MaxSpeed != MaxSpeed - || _config.CurrentLimit != CurrentLimit - || _config.StepMode != StepMode - || _config.Simulated != Simulated - || _config.DynamicStepCalculation != DynamicStepCalculation - || _config.SpoolRadius != InitialSpoolRadius - || _config.FilterPaperThickness != FilterPaperThickness - || _config.IdealLinearStepSize != LinearStepSize - || _config.StepAngle != StepAngle; - - public StepperControllerConfig Save() - { - return Modified ? new StepperControllerConfig - { - Name = Name, - Simulated = Simulated, - StepMode = StepMode, - CustomStepSize = CustomStepSize, - MaxAcceleration = MaxAcceleration, - MaxDeceleration = MaxDeceleration, - CurrentLimit = CurrentLimit, - StartingSpeed = StartingSpeed, - MaxSpeed = MaxSpeed, - IdealLinearStepSize = LinearStepSize, - SpoolRadius = InitialSpoolRadius, - FilterPaperThickness = FilterPaperThickness, - StepAngle = StepAngle, - DynamicStepCalculation = DynamicStepCalculation, - PortName = Port - } : _config; - } -} +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using System.ComponentModel.DataAnnotations; +using TicStepperController.Config; +using TicStepperController.Messaging; + +namespace UI.Features.Devices.StepperController; + +public partial class StepperControllerConfigEditViewModel : ReactiveObject +{ + private readonly StepperControllerRpc.StepperControllerRpcClient _stepperClient; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly StepperControllerConfig _config; + private string _name = string.Empty; + + public StepperControllerConfigEditViewModel(StepperControllerRpc.StepperControllerRpcClient stepperClient, AresDevices.AresDevicesClient devicesClient) + { + _stepperClient = stepperClient; + _devicesClient = devicesClient; + _config = new StepperControllerConfig(); + NewConfig = true; + _ = UpdateAvailableSerialPorts(); + StepAngle = 1.8; + CustomStepSize = 1; + } + + public StepperControllerConfigEditViewModel(StepperControllerRpc.StepperControllerRpcClient stepperClient, AresDevices.AresDevicesClient devicesClient, + StepperControllerConfig config) + { + _stepperClient = stepperClient; + _devicesClient = devicesClient; + _config = config; + _ = UpdateAvailableSerialPorts(); + StepAngle = 1.8; + CustomStepSize = 1; + LoadConfig(config); + } + + public bool NewConfig { get; private set; } + + [Required] + public string Name + { + get => _name; + set + { + if(!NewConfig) + { + return; + } + + this.RaiseAndSetIfChanged(ref _name, value); + } + } + + public void LoadConfig(StepperControllerConfig config) + { + Port = config.PortName; + _name = config.Name; + MaxAcceleration = config.MaxAcceleration; + MaxDeceleration = config.MaxDeceleration; + StartingSpeed = config.StartingSpeed; + CustomStepSize = config.CustomStepSize; + MaxSpeed = config.MaxSpeed; + CurrentLimit = config.CurrentLimit; + StepMode = config.StepMode; + Simulated = config.Simulated; + DynamicStepCalculation = config.DynamicStepCalculation; + InitialSpoolRadius = config.SpoolRadius; + FilterPaperThickness = config.FilterPaperThickness; + LinearStepSize = config.IdealLinearStepSize; + StepAngle = config.StepAngle; + } + + [Reactive] + public partial IEnumerable? AvailablePorts { get; private set; } + + public async Task UpdateAvailableSerialPorts() + { + AvailablePorts = null; + Port = null; + var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); + AvailablePorts = ports.SerialPorts; + } + + [Reactive] + [Required] + public partial string? Port { get; set; } + + [Reactive] + [Range(100, 2_147_483_647, ErrorMessage = "Acceleration must be a value from 100 to 2,147,483,647 microsteps per 100 s²")] + public partial uint? MaxAcceleration { get; set; } + + [Reactive] + [Range(100, 2_147_483_647, ErrorMessage = "Deceleration must be a value from 100 to 2,147,483,647 microsteps per 100 s²")] + public partial uint? MaxDeceleration { get; set; } + + [Reactive] + [Range(0, 500_000_000, ErrorMessage = "Starting speed must be a value from 0 to 500,000,000 microsteps per 10,000 s")] + public partial uint? StartingSpeed { get; set; } + + [Reactive] + [Range(1, uint.MaxValue, ErrorMessage = "Custom step size must be greater than 0")] + public partial uint? CustomStepSize { get; set; } + + [Reactive] + [Range(100, 500_000_000, ErrorMessage = "Max speed must be a value from 0 to 500,000,000 microsteps per 10,000 s")] + public partial uint? MaxSpeed { get; set; } + + [Reactive] + [Range(0, 64, ErrorMessage = "Current Limit must be between 0 and 64")] + public partial uint? CurrentLimit { get; set; } + + [Reactive] + public partial StepMode StepMode { get; set; } + + [Reactive] + public partial bool Simulated { get; set; } + + [Reactive] + public partial bool DynamicStepCalculation { get; set; } //Determines whether a spool is set to dynamically calculate its steps + + [Reactive] + public partial double? InitialSpoolRadius { get; set; } + + [Reactive] + public partial double? FilterPaperThickness { get; set; } + + [Reactive] + public partial double? LinearStepSize { get; set; } + + [Reactive] + public partial double? StepAngle { get; set; } + + public bool Modified => _config.Name != Name + || _config.PortName != Port + || _config.MaxAcceleration != MaxAcceleration + || _config.MaxDeceleration != MaxDeceleration + || _config.StartingSpeed != StartingSpeed + || _config.CustomStepSize != CustomStepSize + || _config.MaxSpeed != MaxSpeed + || _config.CurrentLimit != CurrentLimit + || _config.StepMode != StepMode + || _config.Simulated != Simulated + || _config.DynamicStepCalculation != DynamicStepCalculation + || _config.SpoolRadius != InitialSpoolRadius + || _config.FilterPaperThickness != FilterPaperThickness + || _config.IdealLinearStepSize != LinearStepSize + || _config.StepAngle != StepAngle; + + public StepperControllerConfig Save() + { + return Modified ? new StepperControllerConfig + { + Name = Name, + Simulated = Simulated, + StepMode = StepMode, + CustomStepSize = CustomStepSize, + MaxAcceleration = MaxAcceleration, + MaxDeceleration = MaxDeceleration, + CurrentLimit = CurrentLimit, + StartingSpeed = StartingSpeed, + MaxSpeed = MaxSpeed, + IdealLinearStepSize = LinearStepSize, + SpoolRadius = InitialSpoolRadius, + FilterPaperThickness = FilterPaperThickness, + StepAngle = StepAngle, + DynamicStepCalculation = DynamicStepCalculation, + PortName = Port + } : _config; + } +} diff --git a/UI/Backend/Factories/StepperControllerDeviceControlViewModelFactory.cs b/UI/Features/Devices/StepperController/StepperControllerDeviceControlViewModelFactory.cs similarity index 87% rename from UI/Backend/Factories/StepperControllerDeviceControlViewModelFactory.cs rename to UI/Features/Devices/StepperController/StepperControllerDeviceControlViewModelFactory.cs index 41052ecd..cf1cd5e5 100644 --- a/UI/Backend/Factories/StepperControllerDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/StepperController/StepperControllerDeviceControlViewModelFactory.cs @@ -1,12 +1,12 @@ -using Ares.Services.Device; +using Ares.Services.Device; using DynamicData; using Google.Protobuf.WellKnownTypes; using TicStepperController.Messaging; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.StepperController; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.StepperController; public class StepperControllerDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { @@ -32,3 +32,4 @@ protected override async Task> GetAvailableDe return steppers; } } + diff --git a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsListView.razor b/UI/Features/Devices/StepperController/StepperControllerSettingsListView.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsListView.razor rename to UI/Features/Devices/StepperController/StepperControllerSettingsListView.razor index a9100cae..5a797e7b 100644 --- a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsListView.razor +++ b/UI/Features/Devices/StepperController/StepperControllerSettingsListView.razor @@ -1,6 +1,6 @@ @page "/settings/device/stepper_controller" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.StepperController +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,4 +62,5 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerSettingsListViewModel.cs b/UI/Features/Devices/StepperController/StepperControllerSettingsListViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerSettingsListViewModel.cs rename to UI/Features/Devices/StepperController/StepperControllerSettingsListViewModel.cs index 67b9762f..1dc0c3bf 100644 --- a/UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerSettingsListViewModel.cs +++ b/UI/Features/Devices/StepperController/StepperControllerSettingsListViewModel.cs @@ -1,57 +1,57 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using TicStepperController; -using TicStepperController.Config; -using TicStepperController.Messaging; - -namespace UI.Backend.ViewModels.Settings.Device.StepperController; - -public partial class StepperControllerSettingsListViewModel : ReactiveObject -{ - private readonly StepperControllerRpc.StepperControllerRpcClient _stepperControllerClient; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public StepperControllerSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, StepperControllerRpc.StepperControllerRpcClient stepperControllerClient, IMessenger messenger) - { - _devicesClient = devicesClient; - _stepperControllerClient = stepperControllerClient; - _messenger = messenger; - UpdateConfigs(); - } - - [Reactive] - public partial IEnumerable? SettingsViewModels { get; private set; } - - private void UpdateViewModels(IEnumerable deviceConfigs) - { - var viewModels = deviceConfigs.Select(config => new StepperControllerSettingsViewModel(config, _stepperControllerClient, _devicesClient, _messenger, OnConfigRemoved)); - SettingsViewModels = viewModels; - } - - public StepperControllerConfigEditViewModel GetNewConfigEditViewModel() - => new(_stepperControllerClient, _devicesClient); - - private Task UpdateConfigs() - { - SettingsViewModels = null; - return _devicesClient - .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IStepperController).FullName }) - .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); - } - - public async Task OnConfigRemoved() - { - SettingsViewModels = null; - await UpdateConfigs(); - } - - public async Task AddNewConfig(StepperControllerConfig config) - { - await _stepperControllerClient.AddStepperControllerAsync(config); - await UpdateConfigs(); - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using TicStepperController; +using TicStepperController.Config; +using TicStepperController.Messaging; + +namespace UI.Features.Devices.StepperController; + +public partial class StepperControllerSettingsListViewModel : ReactiveObject +{ + private readonly StepperControllerRpc.StepperControllerRpcClient _stepperControllerClient; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public StepperControllerSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, StepperControllerRpc.StepperControllerRpcClient stepperControllerClient, IMessenger messenger) + { + _devicesClient = devicesClient; + _stepperControllerClient = stepperControllerClient; + _messenger = messenger; + UpdateConfigs(); + } + + [Reactive] + public partial IEnumerable? SettingsViewModels { get; private set; } + + private void UpdateViewModels(IEnumerable deviceConfigs) + { + var viewModels = deviceConfigs.Select(config => new StepperControllerSettingsViewModel(config, _stepperControllerClient, _devicesClient, _messenger, OnConfigRemoved)); + SettingsViewModels = viewModels; + } + + public StepperControllerConfigEditViewModel GetNewConfigEditViewModel() + => new(_stepperControllerClient, _devicesClient); + + private Task UpdateConfigs() + { + SettingsViewModels = null; + return _devicesClient + .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IStepperController).FullName }) + .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); + } + + public async Task OnConfigRemoved() + { + SettingsViewModels = null; + await UpdateConfigs(); + } + + public async Task AddNewConfig(StepperControllerConfig config) + { + await _stepperControllerClient.AddStepperControllerAsync(config); + await UpdateConfigs(); + } +} diff --git a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsView.razor b/UI/Features/Devices/StepperController/StepperControllerSettingsView.razor similarity index 93% rename from UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsView.razor rename to UI/Features/Devices/StepperController/StepperControllerSettingsView.razor index d7a357f2..39d91b05 100644 --- a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsView.razor +++ b/UI/Features/Devices/StepperController/StepperControllerSettingsView.razor @@ -1,74 +1,75 @@ -@using UI.Backend.ViewModels.Settings.Device.StepperController -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - - -
-
- Port - - @ViewModel!.StepperControllerConfig.PortName -
- - @if (ViewModel!.DeviceActive) - { -
- @if (_changing) - { -
-
- -
-
- } -
-
-
- } - else if(_initActive) - { - - } -
-
- -@code { - private bool _initActive; - private bool _changing; - - private string Name => ViewModel!.StepperControllerConfig.Name + (ViewModel!.StepperControllerConfig.Simulated ? " -- (Simulated)" : ""); - - protected override async Task OnInitializedAsync() - { - _initActive = true; - StateHasChanged(); - await ViewModel!.Init(); - _initActive = false; - StateHasChanged(); - } - - private async Task EditCallback() - { - var viewModel = ViewModel!.EditViewModel; - var result = await DialogService.OpenAsync("Edit Stepper", ds => - @); - - _changing = true; - if (result is true && viewModel.Modified) - { - await ViewModel!.Save(); - NotificationService.Success("Config has been saved"); - } - _changing = false; - } - - private async Task RemoveCallback() - { - var result = await DialogService.Confirm($"Delete {ViewModel!.StepperControllerConfig.Name}?"); - if (result is true) - { - await ViewModel!.Remove(); - } - } -} \ No newline at end of file +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + + +
+
+ Port - + @ViewModel!.StepperControllerConfig.PortName +
+ + @if (ViewModel!.DeviceActive) + { +
+ @if (_changing) + { +
+
+ +
+
+ } +
+
+
+ } + else if(_initActive) + { + + } +
+
+ +@code { + private bool _initActive; + private bool _changing; + + private string Name => ViewModel!.StepperControllerConfig.Name + (ViewModel!.StepperControllerConfig.Simulated ? " -- (Simulated)" : ""); + + protected override async Task OnInitializedAsync() + { + _initActive = true; + StateHasChanged(); + await ViewModel!.Init(); + _initActive = false; + StateHasChanged(); + } + + private async Task EditCallback() + { + var viewModel = ViewModel!.EditViewModel; + var result = await DialogService.OpenAsync("Edit Stepper", ds => + @); + + _changing = true; + if (result is true && viewModel.Modified) + { + await ViewModel!.Save(); + NotificationService.Success("Config has been saved"); + } + _changing = false; + } + + private async Task RemoveCallback() + { + var result = await DialogService.Confirm($"Delete {ViewModel!.StepperControllerConfig.Name}?"); + if (result is true) + { + await ViewModel!.Remove(); + } + } +} + diff --git a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsView.razor.css b/UI/Features/Devices/StepperController/StepperControllerSettingsView.razor.css similarity index 95% rename from UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsView.razor.css rename to UI/Features/Devices/StepperController/StepperControllerSettingsView.razor.css index 65a3b003..c09905ae 100644 --- a/UI/Pages/Shared/Settings/Device/StepperController/StepperControllerSettingsView.razor.css +++ b/UI/Features/Devices/StepperController/StepperControllerSettingsView.razor.css @@ -1,21 +1,21 @@ -.stepper-settings-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - position: relative; -} - -.stepper-settings-col1 { - display: grid; - align-items: center; - grid-template-columns: 1fr 1fr auto auto; - grid-column-gap: 5px; - max-width: 350px; -} - -.disabled-overlay { - width: 100%; - height: 100%; - z-index: 10; - position: absolute; - background: rgba(0, 0, 0, 0.25); +.stepper-settings-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + position: relative; +} + +.stepper-settings-col1 { + display: grid; + align-items: center; + grid-template-columns: 1fr 1fr auto auto; + grid-column-gap: 5px; + max-width: 350px; +} + +.disabled-overlay { + width: 100%; + height: 100%; + z-index: 10; + position: absolute; + background: rgba(0, 0, 0, 0.25); } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerSettingsViewModel.cs b/UI/Features/Devices/StepperController/StepperControllerSettingsViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerSettingsViewModel.cs rename to UI/Features/Devices/StepperController/StepperControllerSettingsViewModel.cs index 684d28b1..139f2359 100644 --- a/UI/Backend/ViewModels/Settings/Device/StepperController/StepperControllerSettingsViewModel.cs +++ b/UI/Features/Devices/StepperController/StepperControllerSettingsViewModel.cs @@ -1,103 +1,104 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using Grpc.Core; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using TicStepperController.Config; -using TicStepperController.Messaging; -using UI.Backend.Devices; - -namespace UI.Backend.ViewModels.Settings.Device.StepperController; - -public partial class StepperControllerSettingsViewModel : ReactiveObject -{ - private readonly DeviceConfig _deviceConfig; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly StepperControllerRpc.StepperControllerRpcClient _stepperControllerClient; - private readonly IMessenger _messenger; - - public StepperControllerSettingsViewModel(DeviceConfig deviceConfig, - StepperControllerRpc.StepperControllerRpcClient stepperControllerClient, - AresDevices.AresDevicesClient devicesClient, - IMessenger messenger, - Func onRemoveCallback) - { - _deviceConfig = deviceConfig; - StepperControllerConfig = deviceConfig.ConfigData.Unpack(); - _stepperControllerClient = stepperControllerClient; - _messenger = messenger; - _devicesClient = devicesClient; - OnRemoveCallback = onRemoveCallback; - EditViewModel = new StepperControllerConfigEditViewModel(_stepperControllerClient, _devicesClient, StepperControllerConfig); - } - - public StepperControllerConfig StepperControllerConfig { get; } - - public Func OnRemoveCallback { get; } - - public StepperControllerConfigEditViewModel EditViewModel { get; } - - public async Task GetDeviceOperationalStatus() - { - try - { - var status = await _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - DeviceActive = status.OperationalState is OperationalState.Active; - return status; - } - catch(RpcException) - { - return new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered stepper controller with a name {StepperControllerConfig.Name}" }; - } - } - - public Task Activate() - => _devicesClient.ActivateAsync(new DeviceActivateRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - - public async Task Save() - { - var stepperControllerConfig = EditViewModel.Save(); - var updateRequest = new StepperControllerUpdateRequest - { - Id = _deviceConfig.UniqueId, - Config = stepperControllerConfig - }; - - await _stepperControllerClient.UpdateStepperControllerAsync(updateRequest); - } - - public async Task Remove() - { - await _stepperControllerClient.RemoveStepperControllerAsync(new TicRequest { TicId = _deviceConfig.UniqueId }); - _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); - await OnRemoveCallback(); - } - - public async Task Init() - { - var status = await GetDeviceOperationalStatus(); - if(status.OperationalState != OperationalState.Active) - return; - - var state = await _stepperControllerClient.GetStateAsync(new TicRequest { TicId = _deviceConfig.UniqueId }); - - MaxAcceleration = state.MaxAcceleration; - MaxDeceleration = state.MaxDeceleration; - CurrentLimit = state.CurrentLimit; - StartingSpeed = state.StartingSpeed; - CustomStepSize = state.CustomStepSize; - MaxSpeed = state.MaxSpeed; - StepMode = state.StepMode; - } - - [Reactive] - public partial bool DeviceActive { get; private set; } - public uint MaxAcceleration { get; private set; } - public uint MaxDeceleration { get; private set; } - public uint CurrentLimit { get; private set; } - public uint StartingSpeed { get; private set; } - public uint CustomStepSize { get; private set; } - public uint MaxSpeed { get; private set; } - public StepMode StepMode { get; private set; } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using Grpc.Core; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using TicStepperController.Config; +using TicStepperController.Messaging; +using UI.Application.Devices; +using UI.Features.Devices.Shared; + +namespace UI.Features.Devices.StepperController; + +public partial class StepperControllerSettingsViewModel : ReactiveObject +{ + private readonly DeviceConfig _deviceConfig; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly StepperControllerRpc.StepperControllerRpcClient _stepperControllerClient; + private readonly IMessenger _messenger; + + public StepperControllerSettingsViewModel(DeviceConfig deviceConfig, + StepperControllerRpc.StepperControllerRpcClient stepperControllerClient, + AresDevices.AresDevicesClient devicesClient, + IMessenger messenger, + Func onRemoveCallback) + { + _deviceConfig = deviceConfig; + StepperControllerConfig = deviceConfig.ConfigData.Unpack(); + _stepperControllerClient = stepperControllerClient; + _messenger = messenger; + _devicesClient = devicesClient; + OnRemoveCallback = onRemoveCallback; + EditViewModel = new StepperControllerConfigEditViewModel(_stepperControllerClient, _devicesClient, StepperControllerConfig); + } + + public StepperControllerConfig StepperControllerConfig { get; } + + public Func OnRemoveCallback { get; } + + public StepperControllerConfigEditViewModel EditViewModel { get; } + + public async Task GetDeviceOperationalStatus() + { + try + { + var status = await _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + DeviceActive = status.OperationalState is OperationalState.Active; + return status; + } + catch(RpcException) + { + return new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered stepper controller with a name {StepperControllerConfig.Name}" }; + } + } + + public Task Activate() + => _devicesClient.ActivateAsync(new DeviceActivateRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + + public async Task Save() + { + var stepperControllerConfig = EditViewModel.Save(); + var updateRequest = new StepperControllerUpdateRequest + { + Id = _deviceConfig.UniqueId, + Config = stepperControllerConfig + }; + + await _stepperControllerClient.UpdateStepperControllerAsync(updateRequest); + } + + public async Task Remove() + { + await _stepperControllerClient.RemoveStepperControllerAsync(new TicRequest { TicId = _deviceConfig.UniqueId }); + _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); + await OnRemoveCallback(); + } + + public async Task Init() + { + var status = await GetDeviceOperationalStatus(); + if(status.OperationalState != OperationalState.Active) + return; + + var state = await _stepperControllerClient.GetStateAsync(new TicRequest { TicId = _deviceConfig.UniqueId }); + + MaxAcceleration = state.MaxAcceleration; + MaxDeceleration = state.MaxDeceleration; + CurrentLimit = state.CurrentLimit; + StartingSpeed = state.StartingSpeed; + CustomStepSize = state.CustomStepSize; + MaxSpeed = state.MaxSpeed; + StepMode = state.StepMode; + } + + [Reactive] + public partial bool DeviceActive { get; private set; } + public uint MaxAcceleration { get; private set; } + public uint MaxDeceleration { get; private set; } + public uint CurrentLimit { get; private set; } + public uint StartingSpeed { get; private set; } + public uint CustomStepSize { get; private set; } + public uint MaxSpeed { get; private set; } + public StepMode StepMode { get; private set; } +} diff --git a/UI/Backend/ViewModels/Devices/StepperController/StepperControllerViewModel.cs b/UI/Features/Devices/StepperController/StepperControllerViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Devices/StepperController/StepperControllerViewModel.cs rename to UI/Features/Devices/StepperController/StepperControllerViewModel.cs index 0143c027..8a9d3156 100644 --- a/UI/Backend/ViewModels/Devices/StepperController/StepperControllerViewModel.cs +++ b/UI/Features/Devices/StepperController/StepperControllerViewModel.cs @@ -1,134 +1,135 @@ -using ReactiveUI.SourceGenerators; -using TicStepperController.Messaging; -using UI.Pages.Shared.Devices.StepperController; - -namespace UI.Backend.ViewModels.StepperController; - -public partial class StepperControllerViewModel : DeviceUnitControlViewModel, IAsyncDisposable -{ - private readonly StepperControllerRpc.StepperControllerRpcClient _client; - private readonly CancellationTokenSource _stateUpdateTokenSource = new(); - private Task _stateListener = Task.CompletedTask; - - public StepperControllerViewModel(string id, string name, StepperControllerRpc.StepperControllerRpcClient client) : base(id, name) - { - _client = client; - StartStateUpdater(); - ViewType = typeof(StepperControllerWidgetView); - DefaultWidth = 18; - } - - #region Properties - - [Reactive] - public partial uint MaxAcceleration { get; private set; } - [Reactive] - public partial uint MaxDeceleration { get; private set; } - [Reactive] - public partial uint MaxSpeed { get; private set; } - [Reactive] - public partial uint StartingSpeed { get; private set; } - [Reactive] - public partial StepMode StepMode { get; private set; } - [Reactive] - public partial int CurrentPosition { get; private set; } - [Reactive] - public partial int TargetPosition { get; private set; } - [Reactive] - public partial MiscFlags? MiscFlags { get; private set; } - [Reactive] - public partial ErrorStatus? ErrorStatus { get; private set; } - [Reactive] - public partial ErrorsOccurred? ErrorsOccurred { get; private set; } - - #endregion - - #region Actions - - public Task ExitSafeStart() - { - return _client.ExitSafeStartAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; - } - - public Task EnterSafeStart() - { - return _client.EnterSafeStartAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; - } - - public Task HaltAndHold() - { - return _client.HaltAndHoldAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; - } - - public Task NextStep() - { - return _client.NextStepAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; - } - - public Task PreviousStep() - { - return _client.PreviousStepAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; - } - - public Task HalfStep() - { - return _client.HalfStepAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; - } - - public Task SetTargetPosition(int position) - { - return _client.SetTargetPositionAsync(new PositionCommand { TicId = DeviceId, Position = position }).ResponseAsync; - } - - #endregion - - private void StartStateUpdater() - { - _stateListener = Task.Factory.StartNew(async _ => - { - Thread.CurrentThread.Name = "Stepper Controller State Listener View Model Thread"; - var cancelled = false; - lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } - while (!cancelled) - { - await UpdateState(); - await Task.Delay(TimeSpan.FromMilliseconds(500)); - lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } - } - }, - _stateUpdateTokenSource.Token, - TaskCreationOptions.LongRunning); - } - - private void StopStateUpdater() - { - _stateUpdateTokenSource.Cancel(); - } - - private async Task UpdateState() - { - var response = await _client.GetStateAsync(new TicRequest { TicId = DeviceId }); - if (!response.Valid) - return; - - MaxAcceleration = response.MaxAcceleration; - MaxDeceleration = response.MaxDeceleration; - MaxSpeed = response.MaxSpeed; - StartingSpeed = response.StartingSpeed; - StepMode = response.StepMode; - CurrentPosition = response.CurrentPosition; - TargetPosition = response.TargetPosition; - MiscFlags = response.MiscFlags; - ErrorStatus = response.ErrorStatus; - ErrorsOccurred = response.ErrorsOccurred; - } - - public async ValueTask DisposeAsync() - { - StopStateUpdater(); - await _stateListener; - _stateListener.Dispose(); - _stateUpdateTokenSource.Dispose(); - GC.SuppressFinalize(this); - } -} +using ReactiveUI.SourceGenerators; +using TicStepperController.Messaging; +using UI.Application.Devices; + +namespace UI.Features.Devices.StepperController; + +public partial class StepperControllerViewModel : DeviceUnitControlViewModel, IAsyncDisposable +{ + private readonly StepperControllerRpc.StepperControllerRpcClient _client; + private readonly CancellationTokenSource _stateUpdateTokenSource = new(); + private Task _stateListener = Task.CompletedTask; + + public StepperControllerViewModel(string id, string name, StepperControllerRpc.StepperControllerRpcClient client) : base(id, name) + { + _client = client; + StartStateUpdater(); + ViewType = typeof(StepperControllerWidgetView); + DefaultWidth = 18; + } + + #region Properties + + [Reactive] + public partial uint MaxAcceleration { get; private set; } + [Reactive] + public partial uint MaxDeceleration { get; private set; } + [Reactive] + public partial uint MaxSpeed { get; private set; } + [Reactive] + public partial uint StartingSpeed { get; private set; } + [Reactive] + public partial StepMode StepMode { get; private set; } + [Reactive] + public partial int CurrentPosition { get; private set; } + [Reactive] + public partial int TargetPosition { get; private set; } + [Reactive] + public partial MiscFlags? MiscFlags { get; private set; } + [Reactive] + public partial ErrorStatus? ErrorStatus { get; private set; } + [Reactive] + public partial ErrorsOccurred? ErrorsOccurred { get; private set; } + + #endregion + + #region Actions + + public Task ExitSafeStart() + { + return _client.ExitSafeStartAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; + } + + public Task EnterSafeStart() + { + return _client.EnterSafeStartAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; + } + + public Task HaltAndHold() + { + return _client.HaltAndHoldAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; + } + + public Task NextStep() + { + return _client.NextStepAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; + } + + public Task PreviousStep() + { + return _client.PreviousStepAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; + } + + public Task HalfStep() + { + return _client.HalfStepAsync(new TicRequest { TicId = DeviceId }).ResponseAsync; + } + + public Task SetTargetPosition(int position) + { + return _client.SetTargetPositionAsync(new PositionCommand { TicId = DeviceId, Position = position }).ResponseAsync; + } + + #endregion + + private void StartStateUpdater() + { + _stateListener = Task.Factory.StartNew(async _ => + { + Thread.CurrentThread.Name = "Stepper Controller State Listener View Model Thread"; + var cancelled = false; + lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } + while (!cancelled) + { + await UpdateState(); + await Task.Delay(TimeSpan.FromMilliseconds(500)); + lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } + } + }, + _stateUpdateTokenSource.Token, + TaskCreationOptions.LongRunning); + } + + private void StopStateUpdater() + { + _stateUpdateTokenSource.Cancel(); + } + + private async Task UpdateState() + { + var response = await _client.GetStateAsync(new TicRequest { TicId = DeviceId }); + if (!response.Valid) + return; + + MaxAcceleration = response.MaxAcceleration; + MaxDeceleration = response.MaxDeceleration; + MaxSpeed = response.MaxSpeed; + StartingSpeed = response.StartingSpeed; + StepMode = response.StepMode; + CurrentPosition = response.CurrentPosition; + TargetPosition = response.TargetPosition; + MiscFlags = response.MiscFlags; + ErrorStatus = response.ErrorStatus; + ErrorsOccurred = response.ErrorsOccurred; + } + + public async ValueTask DisposeAsync() + { + StopStateUpdater(); + await _stateListener; + _stateListener.Dispose(); + _stateUpdateTokenSource.Dispose(); + GC.SuppressFinalize(this); + } +} + diff --git a/UI/Pages/Shared/Devices/StepperController/StepperControllerWidgetView.razor b/UI/Features/Devices/StepperController/StepperControllerWidgetView.razor similarity index 95% rename from UI/Pages/Shared/Devices/StepperController/StepperControllerWidgetView.razor rename to UI/Features/Devices/StepperController/StepperControllerWidgetView.razor index 8e6e6c78..937da3c7 100644 --- a/UI/Pages/Shared/Devices/StepperController/StepperControllerWidgetView.razor +++ b/UI/Features/Devices/StepperController/StepperControllerWidgetView.razor @@ -1,145 +1,144 @@ -@using UI.Backend.ViewModels.StepperController -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -@inject DialogService DialogService -@inject TooltipService TooltipService - -
-
-
@ViewModel!.DeviceName
-
- - - -
-
- -
- - - -
-
-
-
- - - -
- - - - - - -
-
-
-@code { - ElementReference _errorOpener; - - Task NextStep() - { - return ViewModel!.NextStep(); - } - - Task PreviousStep() - { - return ViewModel!.PreviousStep(); - } - - Task HalfStep() - { - return ViewModel!.HalfStep(); - } - - void ShowErrorToolTip() - { - TooltipService.Open(_errorOpener, ds => - @
- @if (ViewModel!.ErrorsOccurred is not null) - { -
- - - - - -
- } - - @if (ViewModel!.ErrorStatus is not null) - { - @if (ViewModel!.ErrorsOccurred is not null) - { -
- } -
- - - - - - - - - -
- } - - @if (ViewModel!.MiscFlags is not null) - { - @if (ViewModel!.ErrorsOccurred is not null || ViewModel!.ErrorStatus is not null) - { -
- } -
- - - - - -
- } -
, new TooltipOptions() - { - CloseTooltipOnDocumentClick = true, - Duration = 60000, - Style = "background: var(--rz-info-dark);"} - ); - } - - async Task GoToPosition() - { - var newPos = ViewModel!.CurrentPosition; - var test = await DialogService.OpenAsync("Position Mover", svc => - @
- New Position - -
- svc.Close(true)) Text="Move" /> - svc.Close(false)) Text="Cancel" /> -
-
- ); - - if (test is bool testBool && testBool && newPos != ViewModel.CurrentPosition) - { - await ViewModel!.SetTargetPosition(newPos); - } - } +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +@inject DialogService DialogService +@inject TooltipService TooltipService + +
+
+
@ViewModel!.DeviceName
+
+ + + +
+
+ +
+ + + +
+
+
+
+ + + +
+ + + + + + +
+
+
+@code { + ElementReference _errorOpener; + + Task NextStep() + { + return ViewModel!.NextStep(); + } + + Task PreviousStep() + { + return ViewModel!.PreviousStep(); + } + + Task HalfStep() + { + return ViewModel!.HalfStep(); + } + + void ShowErrorToolTip() + { + TooltipService.Open(_errorOpener, ds => + @
+ @if (ViewModel!.ErrorsOccurred is not null) + { +
+ + + + + +
+ } + + @if (ViewModel!.ErrorStatus is not null) + { + @if (ViewModel!.ErrorsOccurred is not null) + { +
+ } +
+ + + + + + + + + +
+ } + + @if (ViewModel!.MiscFlags is not null) + { + @if (ViewModel!.ErrorsOccurred is not null || ViewModel!.ErrorStatus is not null) + { +
+ } +
+ + + + + +
+ } +
, new TooltipOptions() + { + CloseTooltipOnDocumentClick = true, + Duration = 60000, + Style = "background: var(--rz-info-dark);"} + ); + } + + async Task GoToPosition() + { + var newPos = ViewModel!.CurrentPosition; + var test = await DialogService.OpenAsync("Position Mover", svc => + @
+ New Position + +
+ svc.Close(true)) Text="Move" /> + svc.Close(false)) Text="Cancel" /> +
+
+ ); + + if (test is bool testBool && testBool && newPos != ViewModel.CurrentPosition) + { + await ViewModel!.SetTargetPosition(newPos); + } + } } \ No newline at end of file diff --git a/UI/Pages/Shared/Devices/StepperController/StepperControllerWidgetView.razor.css b/UI/Features/Devices/StepperController/StepperControllerWidgetView.razor.css similarity index 94% rename from UI/Pages/Shared/Devices/StepperController/StepperControllerWidgetView.razor.css rename to UI/Features/Devices/StepperController/StepperControllerWidgetView.razor.css index e4e37b9e..1676f6c0 100644 --- a/UI/Pages/Shared/Devices/StepperController/StepperControllerWidgetView.razor.css +++ b/UI/Features/Devices/StepperController/StepperControllerWidgetView.razor.css @@ -1,8 +1,8 @@ -.temperature-display { - font-family: Helvetica, sans-serif; - font-size: 2.5em; -} - -.name-display { - font-size: 0.75em; +.temperature-display { + font-family: Helvetica, sans-serif; + font-size: 2.5em; +} + +.name-display { + font-size: 0.75em; } \ No newline at end of file diff --git a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpConfigEditView.razor b/UI/Features/Devices/SyringePump/SyringePumpConfigEditView.razor similarity index 93% rename from UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpConfigEditView.razor rename to UI/Features/Devices/SyringePump/SyringePumpConfigEditView.razor index 1acc2427..8f1e3c3a 100644 --- a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpConfigEditView.razor +++ b/UI/Features/Devices/SyringePump/SyringePumpConfigEditView.razor @@ -1,32 +1,31 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@using UI.Backend.ViewModels.Settings.Device.SyringePump; - -
- - - @if (ViewModel!.AvailablePorts is not null) - { - @foreach (var port in ViewModel!.AvailablePorts) - { - - } - } - - -
- - Simulated -
- -
- - - -
-@code { - - [Parameter] - public Action OnValidSubmit { get; set; } = delegate - { - }; -} +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+ + + @if (ViewModel!.AvailablePorts is not null) + { + @foreach (var port in ViewModel!.AvailablePorts) + { + + } + } + + +
+ + Simulated +
+ +
+ + + +
+@code { + + [Parameter] + public Action OnValidSubmit { get; set; } = delegate + { + }; +} diff --git a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpConfigEditView.razor.css b/UI/Features/Devices/SyringePump/SyringePumpConfigEditView.razor.css similarity index 92% rename from UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpConfigEditView.razor.css rename to UI/Features/Devices/SyringePump/SyringePumpConfigEditView.razor.css index 0cf08d37..b2c876a3 100644 --- a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpConfigEditView.razor.css +++ b/UI/Features/Devices/SyringePump/SyringePumpConfigEditView.razor.css @@ -1,29 +1,29 @@ -.pump-property-grid { - display: grid; - grid-template: - "name ." 1fr - "port ." 1fr - "simulated ." 1fr - "address ." 1fr - / auto 40px -} - -.pump-name { - grid-area: name; -} - -.pump-port { - grid-area: port; -} - -.pump-id { - grid-area: id; -} - -.pump-simulated { - grid-area: simulated; -} - -.pump-address { - grid-area: address; -} +.pump-property-grid { + display: grid; + grid-template: + "name ." 1fr + "port ." 1fr + "simulated ." 1fr + "address ." 1fr + / auto 40px +} + +.pump-name { + grid-area: name; +} + +.pump-port { + grid-area: port; +} + +.pump-id { + grid-area: id; +} + +.pump-simulated { + grid-area: simulated; +} + +.pump-address { + grid-area: address; +} diff --git a/UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpConfigEditViewModel.cs b/UI/Features/Devices/SyringePump/SyringePumpConfigEditViewModel.cs similarity index 91% rename from UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpConfigEditViewModel.cs rename to UI/Features/Devices/SyringePump/SyringePumpConfigEditViewModel.cs index ea62c57f..bb03252d 100644 --- a/UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpConfigEditViewModel.cs +++ b/UI/Features/Devices/SyringePump/SyringePumpConfigEditViewModel.cs @@ -1,82 +1,82 @@ -using System.ComponentModel.DataAnnotations; -using Ares.Services.Device; -using Ares.SyringePump.Ne1000.Messaging; -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels.Settings.Device.SyringePump; - -public partial class SyringePumpConfigEditViewModel : ReactiveObject -{ - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; - private readonly SyringePumpConfig _syringePumpConfig; - private string? _name; - - public SyringePumpConfigEditViewModel(SyringePumpRpc.SyringePumpRpcClient syringePumpClient, - AresDevices.AresDevicesClient devicesClient, - SyringePumpConfig syringePumpConfig - ) - { - _syringePumpClient = syringePumpClient; - _devicesClient = devicesClient; - _syringePumpConfig = syringePumpConfig; - _ = UpdateAvailableSerialPorts(); - _name = _syringePumpConfig.Name; - Address = _syringePumpConfig.Address; - Port = _syringePumpConfig.PortName; - Simulated = _syringePumpConfig.Simulated; - } - - public SyringePumpConfigEditViewModel(SyringePumpRpc.SyringePumpRpcClient syringePumpClient, AresDevices.AresDevicesClient devicesClient) - { - _syringePumpClient = syringePumpClient; - _devicesClient = devicesClient; - _syringePumpConfig = new SyringePumpConfig(); - _ = UpdateAvailableSerialPorts(); - NewConfig = true; - } - - [Required] - public string? Name - { - get => _name; - - set - { - if (!NewConfig) - return; - - _name = value; - } - } - - [Required] - public string? Port { get; set; } - - // TODO this is here to prevent changing name as the name of the device is used for lookup and stuff - // but maybe we should use some kind of GUID instead to make this a bit more robust and allow name changes. - public bool NewConfig { get; } - - [Required] - public uint? Address { get; set; } - - public bool Simulated { get; set; } - - [Reactive] - public partial IEnumerable? AvailablePorts { get; private set; } - - public bool Modified => _syringePumpConfig.Name != Name || _syringePumpConfig.PortName != Port || _syringePumpConfig.Simulated != Simulated || _syringePumpConfig.Address != Address; - - public async Task UpdateAvailableSerialPorts() - { - AvailablePorts = null; - Port = null; - var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); - AvailablePorts = ports.SerialPorts; - } - - public SyringePumpConfig Save() - => Modified ? new SyringePumpConfig { Address = Address ?? 0, Name = Name, PortName = Port, Simulated = Simulated } : _syringePumpConfig; -} +using Ares.Services.Device; +using Ares.SyringePump.Ne1000.Messaging; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using System.ComponentModel.DataAnnotations; + +namespace UI.Features.Devices.SyringePump; + +public partial class SyringePumpConfigEditViewModel : ReactiveObject +{ + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; + private readonly SyringePumpConfig _syringePumpConfig; + private string? _name; + + public SyringePumpConfigEditViewModel(SyringePumpRpc.SyringePumpRpcClient syringePumpClient, + AresDevices.AresDevicesClient devicesClient, + SyringePumpConfig syringePumpConfig + ) + { + _syringePumpClient = syringePumpClient; + _devicesClient = devicesClient; + _syringePumpConfig = syringePumpConfig; + _ = UpdateAvailableSerialPorts(); + _name = _syringePumpConfig.Name; + Address = _syringePumpConfig.Address; + Port = _syringePumpConfig.PortName; + Simulated = _syringePumpConfig.Simulated; + } + + public SyringePumpConfigEditViewModel(SyringePumpRpc.SyringePumpRpcClient syringePumpClient, AresDevices.AresDevicesClient devicesClient) + { + _syringePumpClient = syringePumpClient; + _devicesClient = devicesClient; + _syringePumpConfig = new SyringePumpConfig(); + _ = UpdateAvailableSerialPorts(); + NewConfig = true; + } + + [Required] + public string? Name + { + get => _name; + + set + { + if (!NewConfig) + return; + + _name = value; + } + } + + [Required] + public string? Port { get; set; } + + // TODO this is here to prevent changing name as the name of the device is used for lookup and stuff + // but maybe we should use some kind of GUID instead to make this a bit more robust and allow name changes. + public bool NewConfig { get; } + + [Required] + public uint? Address { get; set; } + + public bool Simulated { get; set; } + + [Reactive] + public partial IEnumerable? AvailablePorts { get; private set; } + + public bool Modified => _syringePumpConfig.Name != Name || _syringePumpConfig.PortName != Port || _syringePumpConfig.Simulated != Simulated || _syringePumpConfig.Address != Address; + + public async Task UpdateAvailableSerialPorts() + { + AvailablePorts = null; + Port = null; + var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); + AvailablePorts = ports.SerialPorts; + } + + public SyringePumpConfig Save() + => Modified ? new SyringePumpConfig { Address = Address ?? 0, Name = Name, PortName = Port, Simulated = Simulated } : _syringePumpConfig; +} diff --git a/UI/Backend/Factories/SyringePumpDeviceControlViewModelFactory.cs b/UI/Features/Devices/SyringePump/SyringePumpDeviceControlViewModelFactory.cs similarity index 89% rename from UI/Backend/Factories/SyringePumpDeviceControlViewModelFactory.cs rename to UI/Features/Devices/SyringePump/SyringePumpDeviceControlViewModelFactory.cs index 1b2d43d3..43243e42 100644 --- a/UI/Backend/Factories/SyringePumpDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/SyringePump/SyringePumpDeviceControlViewModelFactory.cs @@ -2,12 +2,11 @@ using Ares.SyringePump.Ne1000.Messaging; using DynamicData; using Google.Protobuf.WellKnownTypes; -using System.Reactive.Linq; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.SyringePump; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.SyringePump; public class SyringePumpDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { @@ -32,3 +31,4 @@ protected override async Task> GetAvailableDe return pumps; } } + diff --git a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpSettingsList.razor b/UI/Features/Devices/SyringePump/SyringePumpSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpSettingsList.razor rename to UI/Features/Devices/SyringePump/SyringePumpSettingsList.razor index 5b6c8403..b4d22d45 100644 --- a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpSettingsList.razor +++ b/UI/Features/Devices/SyringePump/SyringePumpSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/syringe-pump" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.SyringePump +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,4 +62,5 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpSettingsListViewModel.cs b/UI/Features/Devices/SyringePump/SyringePumpSettingsListViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpSettingsListViewModel.cs rename to UI/Features/Devices/SyringePump/SyringePumpSettingsListViewModel.cs index 770e7c89..ec994260 100644 --- a/UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpSettingsListViewModel.cs +++ b/UI/Features/Devices/SyringePump/SyringePumpSettingsListViewModel.cs @@ -1,57 +1,57 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using Ares.SyringePump.Ne1000.Messaging; -using CommunityToolkit.Mvvm.Messaging; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using SyringePumpNE1000; - -namespace UI.Backend.ViewModels.Settings.Device.SyringePump; - -public partial class SyringePumpSettingsListViewModel : ReactiveObject -{ - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; - private readonly IMessenger _messenger; - - public SyringePumpSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, SyringePumpRpc.SyringePumpRpcClient syringePumpClient, IMessenger messenger) - { - _devicesClient = devicesClient; - _syringePumpClient = syringePumpClient; - _messenger = messenger; - _ = UpdateConfigs(); - } - - [Reactive] - public partial IEnumerable? SettingsViewModels { get; private set; } - - private void UpdateViewModels(IEnumerable deviceConfigs) - { - var viewModels = deviceConfigs.Select(config => new SyringePumpSettingsViewModel(config, _syringePumpClient, _devicesClient, _messenger, OnConfigRemoved)); - SettingsViewModels = viewModels; - } - - public SyringePumpConfigEditViewModel GetNewConfigEditViewModel() - => new(_syringePumpClient, _devicesClient); - - private async Task UpdateConfigs() - { - SettingsViewModels = null; - var configs = await _devicesClient - .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(ISyringePump).FullName }); - - UpdateViewModels(configs.Configs); - } - - public async Task OnConfigRemoved() - { - SettingsViewModels = null; - await UpdateConfigs(); - } - - public async Task AddNewConfig(SyringePumpConfig config) - { - await _syringePumpClient.AddSyringePumpAsync(config); - await UpdateConfigs(); - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using Ares.SyringePump.Ne1000.Messaging; +using CommunityToolkit.Mvvm.Messaging; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using SyringePumpNE1000; + +namespace UI.Features.Devices.SyringePump; + +public partial class SyringePumpSettingsListViewModel : ReactiveObject +{ + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; + private readonly IMessenger _messenger; + + public SyringePumpSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, SyringePumpRpc.SyringePumpRpcClient syringePumpClient, IMessenger messenger) + { + _devicesClient = devicesClient; + _syringePumpClient = syringePumpClient; + _messenger = messenger; + _ = UpdateConfigs(); + } + + [Reactive] + public partial IEnumerable? SettingsViewModels { get; private set; } + + private void UpdateViewModels(IEnumerable deviceConfigs) + { + var viewModels = deviceConfigs.Select(config => new SyringePumpSettingsViewModel(config, _syringePumpClient, _devicesClient, _messenger, OnConfigRemoved)); + SettingsViewModels = viewModels; + } + + public SyringePumpConfigEditViewModel GetNewConfigEditViewModel() + => new(_syringePumpClient, _devicesClient); + + private async Task UpdateConfigs() + { + SettingsViewModels = null; + var configs = await _devicesClient + .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(ISyringePump).FullName }); + + UpdateViewModels(configs.Configs); + } + + public async Task OnConfigRemoved() + { + SettingsViewModels = null; + await UpdateConfigs(); + } + + public async Task AddNewConfig(SyringePumpConfig config) + { + await _syringePumpClient.AddSyringePumpAsync(config); + await UpdateConfigs(); + } +} diff --git a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpSettingsView.razor b/UI/Features/Devices/SyringePump/SyringePumpSettingsView.razor similarity index 93% rename from UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpSettingsView.razor rename to UI/Features/Devices/SyringePump/SyringePumpSettingsView.razor index 2a43e711..5998bde0 100644 --- a/UI/Pages/Shared/Settings/Device/SyringePump/SyringePumpSettingsView.razor +++ b/UI/Features/Devices/SyringePump/SyringePumpSettingsView.razor @@ -1,42 +1,43 @@ -@using UI.Backend.ViewModels.Settings.Device.SyringePump -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - - -
- @if (ViewModel!.SyringePumpConfig.Simulated) - { - Simulated -- - } - @ViewModel!.SyringePumpConfig.Name - @ViewModel!.SyringePumpConfig.PortName - @($" Address: {ViewModel!.SyringePumpConfig.Address}") -
-
- -@code { - private async Task EditCallback() - { - var viewModel = ViewModel!.EditViewModel; - var result = await DialogService.OpenAsync("Edit Syringe Pump", ds => - @); - - if (result is true && viewModel.Modified) - { - await ViewModel!.Save(); - NotificationService.Success("Config has been saved"); - } - } - - private async Task RemoveCallback() - { - var result = await DialogService.Confirm($"Delete {ViewModel!.SyringePumpConfig.Name}?"); - if (result is true) - { - await ViewModel!.Remove(); - } - } -} \ No newline at end of file +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + + +
+ @if (ViewModel!.SyringePumpConfig.Simulated) + { + Simulated -- + } + @ViewModel!.SyringePumpConfig.Name + @ViewModel!.SyringePumpConfig.PortName + @($" Address: {ViewModel!.SyringePumpConfig.Address}") +
+
+ +@code { + private async Task EditCallback() + { + var viewModel = ViewModel!.EditViewModel; + var result = await DialogService.OpenAsync("Edit Syringe Pump", ds => + @); + + if (result is true && viewModel.Modified) + { + await ViewModel!.Save(); + NotificationService.Success("Config has been saved"); + } + } + + private async Task RemoveCallback() + { + var result = await DialogService.Confirm($"Delete {ViewModel!.SyringePumpConfig.Name}?"); + if (result is true) + { + await ViewModel!.Remove(); + } + } +} + diff --git a/UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpSettingsViewModel.cs b/UI/Features/Devices/SyringePump/SyringePumpSettingsViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpSettingsViewModel.cs rename to UI/Features/Devices/SyringePump/SyringePumpSettingsViewModel.cs index 0fb52f57..2a68981c 100644 --- a/UI/Backend/ViewModels/Settings/Device/SyringePump/SyringePumpSettingsViewModel.cs +++ b/UI/Features/Devices/SyringePump/SyringePumpSettingsViewModel.cs @@ -1,72 +1,73 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using Ares.SyringePump.Ne1000.Messaging; -using CommunityToolkit.Mvvm.Messaging; -using Grpc.Core; -using ReactiveUI; -using UI.Backend.Devices; - -namespace UI.Backend.ViewModels.Settings.Device.SyringePump; - -public class SyringePumpSettingsViewModel : ReactiveObject -{ - private readonly DeviceConfig _deviceConfig; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; - private readonly IMessenger _messenger; - - public SyringePumpSettingsViewModel(DeviceConfig deviceConfig, - SyringePumpRpc.SyringePumpRpcClient syringePumpClient, - AresDevices.AresDevicesClient devicesClient, - IMessenger messenger, - Func onRemoveCallback) - { - _deviceConfig = deviceConfig; - SyringePumpConfig = deviceConfig.ConfigData.Unpack(); - _syringePumpClient = syringePumpClient; - _devicesClient = devicesClient; - _messenger = messenger; - OnRemoveCallback = onRemoveCallback; - EditViewModel = new SyringePumpConfigEditViewModel(_syringePumpClient, _devicesClient, SyringePumpConfig); - } - - public SyringePumpConfig SyringePumpConfig { get; } - - public Func OnRemoveCallback { get; } - - public SyringePumpConfigEditViewModel EditViewModel { get; } - - public Task GetDeviceOperationalStatus() - { - try - { - return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - } - catch(RpcException) - { - return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered syringe pump with a name {SyringePumpConfig.Name}" }); - } - } - - public Task Activate() - => _devicesClient.ActivateAsync(new DeviceActivateRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - - public async Task Save() - { - var syringePumpConfig = EditViewModel.Save(); - var updateRequest = new SyringePumpUpdateRequest - { - Id = _deviceConfig.UniqueId, - Config = syringePumpConfig - }; - - await _syringePumpClient.UpdateSyringePumpAsync(updateRequest); - } - - public async Task Remove() - { - await _syringePumpClient.RemoveSyringePumpAsync(new SyringePumpRemoveRequest { DeviceId = _deviceConfig.UniqueId }); - _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); - await OnRemoveCallback(); - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using Ares.SyringePump.Ne1000.Messaging; +using CommunityToolkit.Mvvm.Messaging; +using Grpc.Core; +using ReactiveUI; +using UI.Application.Devices; +using UI.Features.Devices.Shared; + +namespace UI.Features.Devices.SyringePump; + +public class SyringePumpSettingsViewModel : ReactiveObject +{ + private readonly DeviceConfig _deviceConfig; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; + private readonly IMessenger _messenger; + + public SyringePumpSettingsViewModel(DeviceConfig deviceConfig, + SyringePumpRpc.SyringePumpRpcClient syringePumpClient, + AresDevices.AresDevicesClient devicesClient, + IMessenger messenger, + Func onRemoveCallback) + { + _deviceConfig = deviceConfig; + SyringePumpConfig = deviceConfig.ConfigData.Unpack(); + _syringePumpClient = syringePumpClient; + _devicesClient = devicesClient; + _messenger = messenger; + OnRemoveCallback = onRemoveCallback; + EditViewModel = new SyringePumpConfigEditViewModel(_syringePumpClient, _devicesClient, SyringePumpConfig); + } + + public SyringePumpConfig SyringePumpConfig { get; } + + public Func OnRemoveCallback { get; } + + public SyringePumpConfigEditViewModel EditViewModel { get; } + + public Task GetDeviceOperationalStatus() + { + try + { + return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + } + catch(RpcException) + { + return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered syringe pump with a name {SyringePumpConfig.Name}" }); + } + } + + public Task Activate() + => _devicesClient.ActivateAsync(new DeviceActivateRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + + public async Task Save() + { + var syringePumpConfig = EditViewModel.Save(); + var updateRequest = new SyringePumpUpdateRequest + { + Id = _deviceConfig.UniqueId, + Config = syringePumpConfig + }; + + await _syringePumpClient.UpdateSyringePumpAsync(updateRequest); + } + + public async Task Remove() + { + await _syringePumpClient.RemoveSyringePumpAsync(new SyringePumpRemoveRequest { DeviceId = _deviceConfig.UniqueId }); + _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); + await OnRemoveCallback(); + } +} diff --git a/UI/Pages/Shared/Devices/SyringePump/SyringePumpUnitControl.razor b/UI/Features/Devices/SyringePump/SyringePumpUnitControl.razor similarity index 95% rename from UI/Pages/Shared/Devices/SyringePump/SyringePumpUnitControl.razor rename to UI/Features/Devices/SyringePump/SyringePumpUnitControl.razor index d146a986..51f45157 100644 --- a/UI/Pages/Shared/Devices/SyringePump/SyringePumpUnitControl.razor +++ b/UI/Features/Devices/SyringePump/SyringePumpUnitControl.razor @@ -1,96 +1,96 @@ -@using Ares.SyringePump.Ne1000.Messaging -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -
-

@ViewModel!.DeviceName

-
-

@ViewModel.Status

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Address@ViewModel!.CurrentState.Address -
- - -
-
Diameter@ViewModel.CurrentState.DiameterMm -
- - -
-
Phase@ViewModel.CurrentState.Phase?.Number -
- - -
-
Function@ViewModel.CurrentState.Phase?.Function -
- - -
-
Rate (@($"{ViewModel.CurrentState.RateUnits:G}"))@ViewModel.CurrentState.Phase?.Rate -
- - -
-
Direction@ViewModel.ActiveDirection -
- - -
-
-
-
-
-

Infused: @ViewModel.CurrentState.DispensedVolume

-
-
-

Withdrawn: @ViewModel.CurrentState.WithdrawnVolume

-
-
- - -
-
-
- - -
-
- - -@code { - -} +@using Ares.SyringePump.Ne1000.Messaging +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+

@ViewModel!.DeviceName

+
+

@ViewModel.Status

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Address@ViewModel!.CurrentState.Address +
+ + +
+
Diameter@ViewModel.CurrentState.DiameterMm +
+ + +
+
Phase@ViewModel.CurrentState.Phase?.Number +
+ + +
+
Function@ViewModel.CurrentState.Phase?.Function +
+ + +
+
Rate (@($"{ViewModel.CurrentState.RateUnits:G}"))@ViewModel.CurrentState.Phase?.Rate +
+ + +
+
Direction@ViewModel.ActiveDirection +
+ + +
+
+
+
+
+

Infused: @ViewModel.CurrentState.DispensedVolume

+
+
+

Withdrawn: @ViewModel.CurrentState.WithdrawnVolume

+
+
+ + +
+
+
+ + +
+
+ + +@code { + +} diff --git a/UI/Backend/ViewModels/Devices/SyringePump/SyringePumpUnitControlViewModel.cs b/UI/Features/Devices/SyringePump/SyringePumpUnitControlViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Devices/SyringePump/SyringePumpUnitControlViewModel.cs rename to UI/Features/Devices/SyringePump/SyringePumpUnitControlViewModel.cs index 3b6ad455..4a8cb593 100644 --- a/UI/Backend/ViewModels/Devices/SyringePump/SyringePumpUnitControlViewModel.cs +++ b/UI/Features/Devices/SyringePump/SyringePumpUnitControlViewModel.cs @@ -1,155 +1,156 @@ -using Ares.SyringePump.Ne1000.Messaging; -using DynamicData.Binding; -using ReactiveUI.SourceGenerators; -using UI.Pages.Shared.Devices.SyringePump; - -namespace UI.Backend.ViewModels.SyringePump; - -public partial class SyringePumpUnitControlViewModel : DeviceUnitControlViewModel, IAsyncDisposable -{ - private SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; - private DeviceRequest _deviceRequest; - private CancellationTokenSource _stateCts = new(); - private Task _stateListener = Task.CompletedTask; - public SyringePumpUnitControlViewModel(string id, string syringePumpName, SyringePumpRpc.SyringePumpRpcClient syringePumpClient) : base(id, syringePumpName) - { - _syringePumpClient = syringePumpClient; - _deviceRequest = new DeviceRequest { DeviceId = DeviceId }; - CurrentState = new StateResponse(); - Status = "Not Connected"; - Initialize(); - this.WhenValueChanged(vm => vm.CurrentState).Subscribe(_ => UpdateStatus()); - ViewType = typeof(SyringePumpUnitControl); - DefaultWidth = 35; - } - - private void UpdateStatus() - { - Status = CurrentState.Status switch - { - StatusPrompt.UndefinedStatusPrompt => "Error", - StatusPrompt.PromptI => $"Infusing {CurrentState.Phase.Rate:F} mL/minute", - StatusPrompt.PromptW => $"Withdrawing {CurrentState.Phase.Rate:F} mL/minute", - StatusPrompt.PromptS => "Stopped", - StatusPrompt.PromptP => "Paused", - StatusPrompt.PromptT => "Timed Pause Phase", - StatusPrompt.PromptU => "User Trigger", - StatusPrompt.PromptX => "Purging", - _ => throw new ArgumentOutOfRangeException() - }; - } - - private void Initialize() - { - CurrentState = _syringePumpClient.GetCurrentState(_deviceRequest); - _stateListener = Task.Run(async () => - { - Thread.CurrentThread.Name = "Syringe Pump UI State Listener Thread"; - while(!_stateCts.IsCancellationRequested) - { - CurrentState = await _syringePumpClient.GetCurrentStateAsync(_deviceRequest); - UpdateValues(); - await Task.Delay(TimeSpan.FromSeconds(2)); - } - }, _stateCts.Token); - } - - public async Task SetDiameter() - { - var request = new SetDiameterMmRequest { DeviceRequest = _deviceRequest, DiameterMm = TargetDiameterMm }; - await _syringePumpClient.SetDiameterAsync(request); - } - - public async Task SetAddress() - { - var request = new SetAddressRequest() { DeviceRequest = _deviceRequest, Address = TargetAddress }; - await _syringePumpClient.SetAddressAsync(request); - } - - public async Task SetPhaseFunctionRate() - { - var request = new SetProgramFunctionRateMmpmRequest - { DeviceRequest = _deviceRequest, RateMmpm = TargetRateMmpm }; - await _syringePumpClient.SetProgramFunctionRateAsync(request); - } - - public async Task SetPhaseFunctionDirection() - { - var request = new SetProgramFunctionPumpingDirectionRequest { DeviceRequest = _deviceRequest, Direction = TargetDirection }; - await _syringePumpClient.SetProgramFunctionPumpingDirectionAsync(request); - } - - public async Task SetPhase() - { - var request = new SetPhaseNumberRequest { DeviceRequest = _deviceRequest, Phase = TargetPhase }; - await _syringePumpClient.SetPhaseAsync(request); - } - - public async Task SetPhaseFunction() - { - var request = new SetPhaseFunctionRequest { DeviceRequest = _deviceRequest, Function = TargetFunction }; - await _syringePumpClient.SetPhaseFunctionAsync(request); - } - - public async Task ClearVolumeDispensed() - { - var request = new ClearVolumeDispensedRequest { DeviceRequest = _deviceRequest, Direction = CurrentState.Phase.Direction }; - await _syringePumpClient.ClearVolumeDispensedAsync(request); - } - - public async Task Purge() - { - await _syringePumpClient.PurgePumpAsync(_deviceRequest); - } - - public async Task Start() - { - await _syringePumpClient.StartPumpingProgramAsync(_deviceRequest); - } - - public async Task Stop() - { - await _syringePumpClient.StopPumpingProgramAsync(_deviceRequest); - } - - public async ValueTask DisposeAsync() - { - _stateCts.Cancel(); - await _stateListener; - _stateCts.Dispose(); - - GC.SuppressFinalize(this); - } - - private void UpdateValues() - { - if(CurrentState.Phase is null) - return; - - TargetAddress = CurrentState.Address; - ActiveDirection = CurrentState.Phase.Direction; - } - - [Reactive] - public partial int TargetAddress { get; set; } - [Reactive] - public partial float TargetDiameterMm { get; set; } - [Reactive] - public partial float TargetVolumeMl { get; set; } - [Reactive] - public partial float TargetRateMmpm { get; set; } - [Reactive] - public partial int TargetPhase { get; set; } - [Reactive] - public partial Commands TargetFunction { get; set; } - [Reactive] - public partial Direction TargetDirection { get; set; } - [Reactive] - public partial Direction ActiveDirection { get; set; } - [Reactive] - public partial StateResponse CurrentState { get; private set; } - [Reactive] - public partial string Status { get; private set; } - [Reactive] - public partial VolumeUnit VolumeUnit { get; set; } -} +using Ares.SyringePump.Ne1000.Messaging; +using DynamicData.Binding; +using ReactiveUI.SourceGenerators; +using UI.Application.Devices; + +namespace UI.Features.Devices.SyringePump; + +public partial class SyringePumpUnitControlViewModel : DeviceUnitControlViewModel, IAsyncDisposable +{ + private SyringePumpRpc.SyringePumpRpcClient _syringePumpClient; + private DeviceRequest _deviceRequest; + private CancellationTokenSource _stateCts = new(); + private Task _stateListener = Task.CompletedTask; + public SyringePumpUnitControlViewModel(string id, string syringePumpName, SyringePumpRpc.SyringePumpRpcClient syringePumpClient) : base(id, syringePumpName) + { + _syringePumpClient = syringePumpClient; + _deviceRequest = new DeviceRequest { DeviceId = DeviceId }; + CurrentState = new StateResponse(); + Status = "Not Connected"; + Initialize(); + this.WhenValueChanged(vm => vm.CurrentState).Subscribe(_ => UpdateStatus()); + ViewType = typeof(SyringePumpUnitControl); + DefaultWidth = 35; + } + + private void UpdateStatus() + { + Status = CurrentState.Status switch + { + StatusPrompt.UndefinedStatusPrompt => "Error", + StatusPrompt.PromptI => $"Infusing {CurrentState.Phase.Rate:F} mL/minute", + StatusPrompt.PromptW => $"Withdrawing {CurrentState.Phase.Rate:F} mL/minute", + StatusPrompt.PromptS => "Stopped", + StatusPrompt.PromptP => "Paused", + StatusPrompt.PromptT => "Timed Pause Phase", + StatusPrompt.PromptU => "User Trigger", + StatusPrompt.PromptX => "Purging", + _ => throw new ArgumentOutOfRangeException() + }; + } + + private void Initialize() + { + CurrentState = _syringePumpClient.GetCurrentState(_deviceRequest); + _stateListener = Task.Run(async () => + { + Thread.CurrentThread.Name = "Syringe Pump UI State Listener Thread"; + while(!_stateCts.IsCancellationRequested) + { + CurrentState = await _syringePumpClient.GetCurrentStateAsync(_deviceRequest); + UpdateValues(); + await Task.Delay(TimeSpan.FromSeconds(2)); + } + }, _stateCts.Token); + } + + public async Task SetDiameter() + { + var request = new SetDiameterMmRequest { DeviceRequest = _deviceRequest, DiameterMm = TargetDiameterMm }; + await _syringePumpClient.SetDiameterAsync(request); + } + + public async Task SetAddress() + { + var request = new SetAddressRequest() { DeviceRequest = _deviceRequest, Address = TargetAddress }; + await _syringePumpClient.SetAddressAsync(request); + } + + public async Task SetPhaseFunctionRate() + { + var request = new SetProgramFunctionRateMmpmRequest + { DeviceRequest = _deviceRequest, RateMmpm = TargetRateMmpm }; + await _syringePumpClient.SetProgramFunctionRateAsync(request); + } + + public async Task SetPhaseFunctionDirection() + { + var request = new SetProgramFunctionPumpingDirectionRequest { DeviceRequest = _deviceRequest, Direction = TargetDirection }; + await _syringePumpClient.SetProgramFunctionPumpingDirectionAsync(request); + } + + public async Task SetPhase() + { + var request = new SetPhaseNumberRequest { DeviceRequest = _deviceRequest, Phase = TargetPhase }; + await _syringePumpClient.SetPhaseAsync(request); + } + + public async Task SetPhaseFunction() + { + var request = new SetPhaseFunctionRequest { DeviceRequest = _deviceRequest, Function = TargetFunction }; + await _syringePumpClient.SetPhaseFunctionAsync(request); + } + + public async Task ClearVolumeDispensed() + { + var request = new ClearVolumeDispensedRequest { DeviceRequest = _deviceRequest, Direction = CurrentState.Phase.Direction }; + await _syringePumpClient.ClearVolumeDispensedAsync(request); + } + + public async Task Purge() + { + await _syringePumpClient.PurgePumpAsync(_deviceRequest); + } + + public async Task Start() + { + await _syringePumpClient.StartPumpingProgramAsync(_deviceRequest); + } + + public async Task Stop() + { + await _syringePumpClient.StopPumpingProgramAsync(_deviceRequest); + } + + public async ValueTask DisposeAsync() + { + _stateCts.Cancel(); + await _stateListener; + _stateCts.Dispose(); + + GC.SuppressFinalize(this); + } + + private void UpdateValues() + { + if(CurrentState.Phase is null) + return; + + TargetAddress = CurrentState.Address; + ActiveDirection = CurrentState.Phase.Direction; + } + + [Reactive] + public partial int TargetAddress { get; set; } + [Reactive] + public partial float TargetDiameterMm { get; set; } + [Reactive] + public partial float TargetVolumeMl { get; set; } + [Reactive] + public partial float TargetRateMmpm { get; set; } + [Reactive] + public partial int TargetPhase { get; set; } + [Reactive] + public partial Commands TargetFunction { get; set; } + [Reactive] + public partial Direction TargetDirection { get; set; } + [Reactive] + public partial Direction ActiveDirection { get; set; } + [Reactive] + public partial StateResponse CurrentState { get; private set; } + [Reactive] + public partial string Status { get; private set; } + [Reactive] + public partial VolumeUnit VolumeUnit { get; set; } +} + diff --git a/UI/Pages/Shared/Settings/Device/Tc0304/DataloggerSettingsList.razor b/UI/Features/Devices/Tc0304/DataloggerSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/Tc0304/DataloggerSettingsList.razor rename to UI/Features/Devices/Tc0304/DataloggerSettingsList.razor index eb860cc3..4421ba5a 100644 --- a/UI/Pages/Shared/Settings/Device/Tc0304/DataloggerSettingsList.razor +++ b/UI/Features/Devices/Tc0304/DataloggerSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/tc0304" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.Tc0304 +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -62,4 +62,5 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + diff --git a/UI/Pages/Shared/Settings/Device/Tc0304/DataloggerSettingsView.razor b/UI/Features/Devices/Tc0304/DataloggerSettingsView.razor similarity index 88% rename from UI/Pages/Shared/Settings/Device/Tc0304/DataloggerSettingsView.razor rename to UI/Features/Devices/Tc0304/DataloggerSettingsView.razor index 66699f1d..0894e6c4 100644 --- a/UI/Pages/Shared/Settings/Device/Tc0304/DataloggerSettingsView.razor +++ b/UI/Features/Devices/Tc0304/DataloggerSettingsView.razor @@ -1,42 +1,44 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - - -
- @if (ViewModel!.DataloggerConfig.Simulated) - { - Simulated -- - } - @ViewModel!.DataloggerConfig.Name - @ViewModel!.DataloggerConfig.PortName -
-
- -@code { - private async Task EditCallback() - { - var viewModel = ViewModel!.EditViewModel; - var result = await DialogService.OpenAsync("Edit Datalogger", ds => - @); - - if (result is true && viewModel.Modified) - { - await ViewModel!.Save(); - NotificationService.Success("Config has been saved"); - } - } - -} -@code { - private async Task RemoveCallback() - { - var result = await DialogService.Confirm($"Delete {ViewModel!.DataloggerConfig.Name}?"); - if (result is true) - { - await ViewModel!.Remove(); - } - } -} \ No newline at end of file +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + + +
+ @if (ViewModel!.DataloggerConfig.Simulated) + { + Simulated -- + } + @ViewModel!.DataloggerConfig.Name + @ViewModel!.DataloggerConfig.PortName +
+
+ +@code { + private async Task EditCallback() + { + var viewModel = ViewModel!.EditViewModel; + var result = await DialogService.OpenAsync("Edit Datalogger", ds => + @); + + if (result is true && viewModel.Modified) + { + await ViewModel!.Save(); + NotificationService.Success("Config has been saved"); + } + } + +} +@code { + private async Task RemoveCallback() + { + var result = await DialogService.Confirm($"Delete {ViewModel!.DataloggerConfig.Name}?"); + if (result is true) + { + await ViewModel!.Remove(); + } + } +} + diff --git a/UI/Pages/Shared/Settings/Device/Tc0304/Tc0304ConfigEditView.razor b/UI/Features/Devices/Tc0304/Tc0304ConfigEditView.razor similarity index 89% rename from UI/Pages/Shared/Settings/Device/Tc0304/Tc0304ConfigEditView.razor rename to UI/Features/Devices/Tc0304/Tc0304ConfigEditView.razor index 402b2990..600f4b7f 100644 --- a/UI/Pages/Shared/Settings/Device/Tc0304/Tc0304ConfigEditView.razor +++ b/UI/Features/Devices/Tc0304/Tc0304ConfigEditView.razor @@ -1,38 +1,38 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - -
- - - - @if (ViewModel!.AvailablePorts is not null) - { - @foreach (var port in ViewModel!.AvailablePorts) - { - - } - } - -
- - Simulated -
-
- - - -
-@code { - - [Parameter] - public Action OnValidSubmit { get; set; } = delegate { - }; - - private void PortChanged(ChangeEventArgs obj) - { - if (obj.Value is not string stringValue) - throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); - - ViewModel!.Port = stringValue; - } +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + +
+ + + + @if (ViewModel!.AvailablePorts is not null) + { + @foreach (var port in ViewModel!.AvailablePorts) + { + + } + } + +
+ + Simulated +
+
+ + + +
+@code { + + [Parameter] + public Action OnValidSubmit { get; set; } = delegate { + }; + + private void PortChanged(ChangeEventArgs obj) + { + if (obj.Value is not string stringValue) + throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); + + ViewModel!.Port = stringValue; + } } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304ConfigEditViewModel.cs b/UI/Features/Devices/Tc0304/Tc0304ConfigEditViewModel.cs similarity index 90% rename from UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304ConfigEditViewModel.cs rename to UI/Features/Devices/Tc0304/Tc0304ConfigEditViewModel.cs index a49308ef..fd56fcf1 100644 --- a/UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304ConfigEditViewModel.cs +++ b/UI/Features/Devices/Tc0304/Tc0304ConfigEditViewModel.cs @@ -1,76 +1,76 @@ -using System.ComponentModel.DataAnnotations; -using Ares.Services.Device; -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using Tc0304.Config; -using Tc0304.Services; - -namespace UI.Backend.ViewModels.Settings.Device.Tc0304; - -public partial class Tc0304ConfigEditViewModel : ReactiveObject -{ - private readonly TC0304Rpc.TC0304RpcClient _client; - private readonly Tc0304Config _config; - private readonly AresDevices.AresDevicesClient _devicesClient; - private string? _name; - - public Tc0304ConfigEditViewModel(TC0304Rpc.TC0304RpcClient client, AresDevices.AresDevicesClient devicesClient) - { - _client = client; - _devicesClient = devicesClient; - _config = new Tc0304Config(); - NewConfig = true; - _ = UpdateAvailableSerialPorts(); - } - - public Tc0304ConfigEditViewModel(TC0304Rpc.TC0304RpcClient client, AresDevices.AresDevicesClient devicesClient, Tc0304Config config) - { - _client = client; - _devicesClient = devicesClient; - _config = config; - _ = UpdateAvailableSerialPorts(); - _name = config.Name; - Port = config.PortName; - Simulated = config.Simulated; - } - - [Required] - public string? Name - { - get => _name; - - set - { - if (!NewConfig) - return; - - _name = value; - } - } - - [Required] - public string? Port { get; set; } - - // TODO this is here to prevent changing name as the name of the device is used for lookup and stuff - // but maybe we should use some kind of GUID instead to make this a bit more robust and allow name changes. - public bool NewConfig { get; } - - public bool Simulated { get; set; } - - [Reactive] - public partial IEnumerable? AvailablePorts { get; private set; } - - public bool Modified => _config.Name != Name || _config.PortName != Port || _config.Simulated != Simulated; - - public async Task UpdateAvailableSerialPorts() - { - AvailablePorts = null; - Port = null; - var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); - AvailablePorts = ports.SerialPorts; - } - - public Tc0304Config Save() - => Modified ? new Tc0304Config { Name = Name, PortName = Port, Simulated = Simulated } : _config; -} +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using System.ComponentModel.DataAnnotations; +using Tc0304.Config; +using Tc0304.Services; + +namespace UI.Features.Devices.Tc0304; + +public partial class Tc0304ConfigEditViewModel : ReactiveObject +{ + private readonly TC0304Rpc.TC0304RpcClient _client; + private readonly Tc0304Config _config; + private readonly AresDevices.AresDevicesClient _devicesClient; + private string? _name; + + public Tc0304ConfigEditViewModel(TC0304Rpc.TC0304RpcClient client, AresDevices.AresDevicesClient devicesClient) + { + _client = client; + _devicesClient = devicesClient; + _config = new Tc0304Config(); + NewConfig = true; + _ = UpdateAvailableSerialPorts(); + } + + public Tc0304ConfigEditViewModel(TC0304Rpc.TC0304RpcClient client, AresDevices.AresDevicesClient devicesClient, Tc0304Config config) + { + _client = client; + _devicesClient = devicesClient; + _config = config; + _ = UpdateAvailableSerialPorts(); + _name = config.Name; + Port = config.PortName; + Simulated = config.Simulated; + } + + [Required] + public string? Name + { + get => _name; + + set + { + if (!NewConfig) + return; + + _name = value; + } + } + + [Required] + public string? Port { get; set; } + + // TODO this is here to prevent changing name as the name of the device is used for lookup and stuff + // but maybe we should use some kind of GUID instead to make this a bit more robust and allow name changes. + public bool NewConfig { get; } + + public bool Simulated { get; set; } + + [Reactive] + public partial IEnumerable? AvailablePorts { get; private set; } + + public bool Modified => _config.Name != Name || _config.PortName != Port || _config.Simulated != Simulated; + + public async Task UpdateAvailableSerialPorts() + { + AvailablePorts = null; + Port = null; + var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); + AvailablePorts = ports.SerialPorts; + } + + public Tc0304Config Save() + => Modified ? new Tc0304Config { Name = Name, PortName = Port, Simulated = Simulated } : _config; +} diff --git a/UI/Pages/Shared/Devices/DataLogger/Tc0304ControlWidgetView.razor b/UI/Features/Devices/Tc0304/Tc0304ControlWidgetView.razor similarity index 83% rename from UI/Pages/Shared/Devices/DataLogger/Tc0304ControlWidgetView.razor rename to UI/Features/Devices/Tc0304/Tc0304ControlWidgetView.razor index ebe54d11..95928e57 100644 --- a/UI/Pages/Shared/Devices/DataLogger/Tc0304ControlWidgetView.razor +++ b/UI/Features/Devices/Tc0304/Tc0304ControlWidgetView.razor @@ -1,21 +1,20 @@ -@using UI.Backend.ViewModels.Tc0304 -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -
-
-
@ViewModel!.Probe1Name
-
@(ViewModel!.T1Temp?.ToString() ?? "--")
-
@ViewModel!.Probe3Name
-
@(ViewModel!.T3Temp?.ToString() ?? "--")
-
-
-
@ViewModel!.Probe2Name
-
@(ViewModel!.T2Temp?.ToString() ?? "--")
-
@ViewModel!.Probe4Name
-
@(ViewModel!.T4Temp?.ToString() ?? "--")
-
-
- -@code { - +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +
+
+
@ViewModel!.Probe1Name
+
@(ViewModel!.T1Temp?.ToString() ?? "--")
+
@ViewModel!.Probe3Name
+
@(ViewModel!.T3Temp?.ToString() ?? "--")
+
+
+
@ViewModel!.Probe2Name
+
@(ViewModel!.T2Temp?.ToString() ?? "--")
+
@ViewModel!.Probe4Name
+
@(ViewModel!.T4Temp?.ToString() ?? "--")
+
+
+ +@code { + } \ No newline at end of file diff --git a/UI/Pages/Shared/Devices/DataLogger/Tc0304ControlWidgetView.razor.css b/UI/Features/Devices/Tc0304/Tc0304ControlWidgetView.razor.css similarity index 94% rename from UI/Pages/Shared/Devices/DataLogger/Tc0304ControlWidgetView.razor.css rename to UI/Features/Devices/Tc0304/Tc0304ControlWidgetView.razor.css index e4e37b9e..1676f6c0 100644 --- a/UI/Pages/Shared/Devices/DataLogger/Tc0304ControlWidgetView.razor.css +++ b/UI/Features/Devices/Tc0304/Tc0304ControlWidgetView.razor.css @@ -1,8 +1,8 @@ -.temperature-display { - font-family: Helvetica, sans-serif; - font-size: 2.5em; -} - -.name-display { - font-size: 0.75em; +.temperature-display { + font-family: Helvetica, sans-serif; + font-size: 2.5em; +} + +.name-display { + font-size: 0.75em; } \ No newline at end of file diff --git a/UI/Backend/Factories/Tc0304DeviceControlViewModelFactory.cs b/UI/Features/Devices/Tc0304/Tc0304DeviceControlViewModelFactory.cs similarity index 87% rename from UI/Backend/Factories/Tc0304DeviceControlViewModelFactory.cs rename to UI/Features/Devices/Tc0304/Tc0304DeviceControlViewModelFactory.cs index b2f0f27f..ad29c176 100644 --- a/UI/Backend/Factories/Tc0304DeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/Tc0304/Tc0304DeviceControlViewModelFactory.cs @@ -1,12 +1,12 @@ -using Ares.Services.Device; +using Ares.Services.Device; using DynamicData; using Google.Protobuf.WellKnownTypes; using Tc0304.Services; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Tc0304; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.Tc0304; public class Tc0304DeviceControlViewModelFactory : DeviceConnectorViewModelFactory { @@ -31,3 +31,4 @@ protected override async Task> GetAvailableDe return infos; } } + diff --git a/UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304SettingsListViewModel.cs b/UI/Features/Devices/Tc0304/Tc0304SettingsListViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304SettingsListViewModel.cs rename to UI/Features/Devices/Tc0304/Tc0304SettingsListViewModel.cs index 5c4667df..392999aa 100644 --- a/UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304SettingsListViewModel.cs +++ b/UI/Features/Devices/Tc0304/Tc0304SettingsListViewModel.cs @@ -1,57 +1,57 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using TC0304; -using Tc0304.Config; -using Tc0304.Services; -using CommunityToolkit.Mvvm.Messaging; - -namespace UI.Backend.ViewModels.Settings.Device.Tc0304; - -public partial class Tc0304SettingsListViewModel : ReactiveObject -{ - private readonly TC0304Rpc.TC0304RpcClient _dataloggerClient; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public Tc0304SettingsListViewModel(AresDevices.AresDevicesClient devicesClient, TC0304Rpc.TC0304RpcClient dataloggerClient, IMessenger messenger) - { - _devicesClient = devicesClient; - _dataloggerClient = dataloggerClient; - _messenger = messenger; - UpdateConfigs(); - } - - [Reactive] - public partial IEnumerable? SettingsViewModels { get; private set; } - - private void UpdateViewModels(IEnumerable deviceConfigs) - { - var viewModels = deviceConfigs.Select(config => new Tc0304SettingsViewModel(config, _dataloggerClient, _devicesClient, _messenger, OnConfigRemoved)); - SettingsViewModels = viewModels; - } - - public Tc0304ConfigEditViewModel GetNewConfigEditViewModel() - => new(_dataloggerClient, _devicesClient); - - private Task UpdateConfigs() - { - SettingsViewModels = null; - return _devicesClient - .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IDataloggerThermometer).FullName }) - .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); - } - - public async Task OnConfigRemoved() - { - SettingsViewModels = null; - await UpdateConfigs(); - } - - public async Task AddNewConfig(Tc0304Config config) - { - await _dataloggerClient.AddTc0304Async(config); - await UpdateConfigs(); - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using TC0304; +using Tc0304.Config; +using Tc0304.Services; + +namespace UI.Features.Devices.Tc0304; + +public partial class Tc0304SettingsListViewModel : ReactiveObject +{ + private readonly TC0304Rpc.TC0304RpcClient _dataloggerClient; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public Tc0304SettingsListViewModel(AresDevices.AresDevicesClient devicesClient, TC0304Rpc.TC0304RpcClient dataloggerClient, IMessenger messenger) + { + _devicesClient = devicesClient; + _dataloggerClient = dataloggerClient; + _messenger = messenger; + UpdateConfigs(); + } + + [Reactive] + public partial IEnumerable? SettingsViewModels { get; private set; } + + private void UpdateViewModels(IEnumerable deviceConfigs) + { + var viewModels = deviceConfigs.Select(config => new Tc0304SettingsViewModel(config, _dataloggerClient, _devicesClient, _messenger, OnConfigRemoved)); + SettingsViewModels = viewModels; + } + + public Tc0304ConfigEditViewModel GetNewConfigEditViewModel() + => new(_dataloggerClient, _devicesClient); + + private Task UpdateConfigs() + { + SettingsViewModels = null; + return _devicesClient + .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IDataloggerThermometer).FullName }) + .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); + } + + public async Task OnConfigRemoved() + { + SettingsViewModels = null; + await UpdateConfigs(); + } + + public async Task AddNewConfig(Tc0304Config config) + { + await _dataloggerClient.AddTc0304Async(config); + await UpdateConfigs(); + } +} diff --git a/UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304SettingsViewModel.cs b/UI/Features/Devices/Tc0304/Tc0304SettingsViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304SettingsViewModel.cs rename to UI/Features/Devices/Tc0304/Tc0304SettingsViewModel.cs index bee6b06f..6bd22253 100644 --- a/UI/Backend/ViewModels/Settings/Device/Tc0304/Tc0304SettingsViewModel.cs +++ b/UI/Features/Devices/Tc0304/Tc0304SettingsViewModel.cs @@ -1,78 +1,79 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using Grpc.Core; -using ReactiveUI; -using Tc0304.Config; -using Tc0304.Services; -using UI.Backend.Devices; - -namespace UI.Backend.ViewModels.Settings.Device.Tc0304; - -public class Tc0304SettingsViewModel : ReactiveObject -{ - private readonly TC0304Rpc.TC0304RpcClient _dataloggerClient; - private readonly DeviceConfig _deviceConfig; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public Tc0304SettingsViewModel(DeviceConfig deviceConfig, - TC0304Rpc.TC0304RpcClient dataloggerClient, - AresDevices.AresDevicesClient devicesClient, - IMessenger messenger, - Func onRemoveCallback) - { - _deviceConfig = deviceConfig; - DataloggerConfig = deviceConfig.ConfigData.Unpack(); - _dataloggerClient = dataloggerClient; - _devicesClient = devicesClient; - _messenger = messenger; - OnRemoveCallback = onRemoveCallback; - EditViewModel = new Tc0304ConfigEditViewModel(_dataloggerClient, _devicesClient, DataloggerConfig); - } - - public Tc0304Config DataloggerConfig { get; } - - public Func OnRemoveCallback { get; } - - public Tc0304ConfigEditViewModel EditViewModel { get; } - - public Task GetDeviceOperationalStatus() - { - try - { - return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - } - catch(RpcException) - { - return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered TC0304 datalogger with a name {DataloggerConfig.Name}" }); - } - } - - - - public async Task Save() - { - var dataloggerConfig = EditViewModel.Save(); - var updateRequest = new UpdateTc0304Request - { - Id = _deviceConfig.UniqueId, - Config = dataloggerConfig - }; - - await _dataloggerClient.UpdateTc0304Async(updateRequest); - } - - public Task Activate() - => _devicesClient.ActivateAsync(new DeviceActivateRequest - { - DeviceId = _deviceConfig.UniqueId - }).ResponseAsync; - - public async Task Remove() - { - await _dataloggerClient.RemoveTc0304Async(new Tc0304Request { Tc0304Id = _deviceConfig.UniqueId }); - _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); - await OnRemoveCallback(); - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using Grpc.Core; +using ReactiveUI; +using Tc0304.Config; +using Tc0304.Services; +using UI.Application.Devices; +using UI.Features.Devices.Shared; + +namespace UI.Features.Devices.Tc0304; + +public class Tc0304SettingsViewModel : ReactiveObject +{ + private readonly TC0304Rpc.TC0304RpcClient _dataloggerClient; + private readonly DeviceConfig _deviceConfig; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public Tc0304SettingsViewModel(DeviceConfig deviceConfig, + TC0304Rpc.TC0304RpcClient dataloggerClient, + AresDevices.AresDevicesClient devicesClient, + IMessenger messenger, + Func onRemoveCallback) + { + _deviceConfig = deviceConfig; + DataloggerConfig = deviceConfig.ConfigData.Unpack(); + _dataloggerClient = dataloggerClient; + _devicesClient = devicesClient; + _messenger = messenger; + OnRemoveCallback = onRemoveCallback; + EditViewModel = new Tc0304ConfigEditViewModel(_dataloggerClient, _devicesClient, DataloggerConfig); + } + + public Tc0304Config DataloggerConfig { get; } + + public Func OnRemoveCallback { get; } + + public Tc0304ConfigEditViewModel EditViewModel { get; } + + public Task GetDeviceOperationalStatus() + { + try + { + return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + } + catch(RpcException) + { + return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered TC0304 datalogger with a name {DataloggerConfig.Name}" }); + } + } + + + + public async Task Save() + { + var dataloggerConfig = EditViewModel.Save(); + var updateRequest = new UpdateTc0304Request + { + Id = _deviceConfig.UniqueId, + Config = dataloggerConfig + }; + + await _dataloggerClient.UpdateTc0304Async(updateRequest); + } + + public Task Activate() + => _devicesClient.ActivateAsync(new DeviceActivateRequest + { + DeviceId = _deviceConfig.UniqueId + }).ResponseAsync; + + public async Task Remove() + { + await _dataloggerClient.RemoveTc0304Async(new Tc0304Request { Tc0304Id = _deviceConfig.UniqueId }); + _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); + await OnRemoveCallback(); + } +} diff --git a/UI/Backend/ViewModels/Devices/Tc0304/Tc0304UnitControlViewModel.cs b/UI/Features/Devices/Tc0304/Tc0304UnitControlViewModel.cs similarity index 91% rename from UI/Backend/ViewModels/Devices/Tc0304/Tc0304UnitControlViewModel.cs rename to UI/Features/Devices/Tc0304/Tc0304UnitControlViewModel.cs index c8183d88..1cf852e4 100644 --- a/UI/Backend/ViewModels/Devices/Tc0304/Tc0304UnitControlViewModel.cs +++ b/UI/Features/Devices/Tc0304/Tc0304UnitControlViewModel.cs @@ -1,90 +1,91 @@ -using ReactiveUI.SourceGenerators; -using Tc0304.Services; -using UI.Pages.Shared.Devices.DataLogger; -using UnitsNet; - -namespace UI.Backend.ViewModels.Tc0304; - -public partial class Tc0304UnitControlViewModel : DeviceUnitControlViewModel, IAsyncDisposable -{ - private readonly TC0304Rpc.TC0304RpcClient _client; - private readonly CancellationTokenSource _stateUpdateTokenSource = new(); - private Task _stateListener = Task.CompletedTask; - - public Tc0304UnitControlViewModel(string id, string name, TC0304Rpc.TC0304RpcClient client) : base(id, name) - { - _client = client; - StartStateUpdater(); - - ViewType = typeof(Tc0304ControlWidgetView); - DefaultWidth = 22; - } - - [Reactive] - public partial bool BatteryLow { get; private set; } - - [Reactive] - public partial bool HoldActive { get; private set; } - - [Reactive] - public partial Temperature? T1Temp { get; private set; } - - [Reactive] - public partial Temperature? T2Temp { get; private set; } - - [Reactive] - public partial Temperature? T3Temp { get; private set; } - - [Reactive] - public partial Temperature? T4Temp { get; private set; } - - public string Probe1Name { get; set; } = "Probe 1"; - public string Probe2Name { get; set; } = "Probe 2"; - public string Probe3Name { get; set; } = "Probe 3"; - public string Probe4Name { get; set; } = "Probe 4"; - - - - private void StartStateUpdater() - { - var token = _stateUpdateTokenSource.Token; - - _stateListener = Task.Run(async () => - { - while (!token.IsCancellationRequested) - { - await UpdateState(); - await Task.Delay(TimeSpan.FromMilliseconds(500), token); - } - }, token); - } - - private void StopStateUpdater() - { - _stateUpdateTokenSource.Cancel(); - } - - private async Task UpdateState() - { - var response = await _client.GetTemperaturesAsync(new DeviceRequest { DeviceId = DeviceId }); - - T1Temp = response.HasProbe1C ? Temperature.FromDegreesCelsius(response.Probe1C) : null; - T2Temp = response.HasProbe2C ? Temperature.FromDegreesCelsius(response.Probe2C) : null; - T3Temp = response.HasProbe3C ? Temperature.FromDegreesCelsius(response.Probe3C) : null; - T4Temp = response.HasProbe4C ? Temperature.FromDegreesCelsius(response.Probe4C) : null; - } - - public void Hold() - { - _client.Hold(new DeviceRequest { DeviceId = DeviceId }); - } - - public async ValueTask DisposeAsync() - { - StopStateUpdater(); - await _stateListener; - _stateListener.Dispose(); - _stateUpdateTokenSource.Dispose(); - GC.SuppressFinalize(this); - } -} +using ReactiveUI.SourceGenerators; +using Tc0304.Services; +using UI.Application.Devices; +using UnitsNet; + +namespace UI.Features.Devices.Tc0304; + +public partial class Tc0304UnitControlViewModel : DeviceUnitControlViewModel, IAsyncDisposable +{ + private readonly TC0304Rpc.TC0304RpcClient _client; + private readonly CancellationTokenSource _stateUpdateTokenSource = new(); + private Task _stateListener = Task.CompletedTask; + + public Tc0304UnitControlViewModel(string id, string name, TC0304Rpc.TC0304RpcClient client) : base(id, name) + { + _client = client; + StartStateUpdater(); + + ViewType = typeof(Tc0304ControlWidgetView); + DefaultWidth = 22; + } + + [Reactive] + public partial bool BatteryLow { get; private set; } + + [Reactive] + public partial bool HoldActive { get; private set; } + + [Reactive] + public partial Temperature? T1Temp { get; private set; } + + [Reactive] + public partial Temperature? T2Temp { get; private set; } + + [Reactive] + public partial Temperature? T3Temp { get; private set; } + + [Reactive] + public partial Temperature? T4Temp { get; private set; } + + public string Probe1Name { get; set; } = "Probe 1"; + public string Probe2Name { get; set; } = "Probe 2"; + public string Probe3Name { get; set; } = "Probe 3"; + public string Probe4Name { get; set; } = "Probe 4"; + + + + private void StartStateUpdater() + { + var token = _stateUpdateTokenSource.Token; + + _stateListener = Task.Run(async () => + { + while (!token.IsCancellationRequested) + { + await UpdateState(); + await Task.Delay(TimeSpan.FromMilliseconds(500), token); + } + }, token); + } + + private void StopStateUpdater() + { + _stateUpdateTokenSource.Cancel(); + } + + private async Task UpdateState() + { + var response = await _client.GetTemperaturesAsync(new DeviceRequest { DeviceId = DeviceId }); + + T1Temp = response.HasProbe1C ? Temperature.FromDegreesCelsius(response.Probe1C) : null; + T2Temp = response.HasProbe2C ? Temperature.FromDegreesCelsius(response.Probe2C) : null; + T3Temp = response.HasProbe3C ? Temperature.FromDegreesCelsius(response.Probe3C) : null; + T4Temp = response.HasProbe4C ? Temperature.FromDegreesCelsius(response.Probe4C) : null; + } + + public void Hold() + { + _client.Hold(new DeviceRequest { DeviceId = DeviceId }); + } + + public async ValueTask DisposeAsync() + { + StopStateUpdater(); + await _stateListener; + _stateListener.Dispose(); + _stateUpdateTokenSource.Dispose(); + GC.SuppressFinalize(this); + } +} + diff --git a/UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceConfigEditView.razor b/UI/Features/Devices/TubeFurnace/TubeFurnaceConfigEditView.razor similarity index 93% rename from UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceConfigEditView.razor rename to UI/Features/Devices/TubeFurnace/TubeFurnaceConfigEditView.razor index aefcfd10..f820e0bf 100644 --- a/UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceConfigEditView.razor +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceConfigEditView.razor @@ -1,34 +1,33 @@ -@using UI.Backend.ViewModels.Settings.Device.TubeFurnace - -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - -
- - - - @if (ViewModel!.AvailablePorts is not null) - { - @foreach (var port in ViewModel!.AvailablePorts) - { - - } - } - -
- - Simulated -
- -
- - - -
- -@code { - [Parameter] - public Action OnValidSubmit { get; set; } = delegate - { - }; -} + +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + +
+ + + + @if (ViewModel!.AvailablePorts is not null) + { + @foreach (var port in ViewModel!.AvailablePorts) + { + + } + } + +
+ + Simulated +
+ +
+ + + +
+ +@code { + [Parameter] + public Action OnValidSubmit { get; set; } = delegate + { + }; +} diff --git a/UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceConfigEditViewModel.cs b/UI/Features/Devices/TubeFurnace/TubeFurnaceConfigEditViewModel.cs similarity index 91% rename from UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceConfigEditViewModel.cs rename to UI/Features/Devices/TubeFurnace/TubeFurnaceConfigEditViewModel.cs index cebe83c0..29b7da46 100644 --- a/UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceConfigEditViewModel.cs +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceConfigEditViewModel.cs @@ -1,100 +1,100 @@ -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using System.ComponentModel.DataAnnotations; -using Ares.Services.Device; -using TubeFurnace.Config; -using TubeFurnace.Messaging; - -namespace UI.Backend.ViewModels.Settings.Device.TubeFurnace -{ - public partial class TubeFurnaceConfigEditViewModel : ReactiveObject - { - private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; - private readonly AresDevices.AresDevicesClient _devicesClient; - private TubeFurnaceConfig _config; - private string _name = string.Empty; - - public TubeFurnaceConfigEditViewModel(TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, AresDevices.AresDevicesClient devicesClient) - { - _tubeFurnaceClient = tubeFurnaceClient; - _devicesClient = devicesClient; - _config = new TubeFurnaceConfig(); - NewConfig = true; - _ = UpdateAvailableSerialPorts(); - } - - public TubeFurnaceConfigEditViewModel(TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, AresDevices.AresDevicesClient devicesClient, - TubeFurnaceConfig config) - { - _tubeFurnaceClient = tubeFurnaceClient; - _devicesClient = devicesClient; - _config = config; - _ = UpdateAvailableSerialPorts(); - LoadConfig(config); - } - - public bool NewConfig { get; private set; } - - [Required] - public string Name - { - get => _name; - set - { - if (!NewConfig) - { - return; - } - - this.RaiseAndSetIfChanged(ref _name, value); - } - } - - public void LoadConfig(TubeFurnaceConfig config) - { - Port = config.PortName; - _name = config.Name; - Simulated = config.Simulated; - Address = config.Address; - } - - [Reactive] - public partial IEnumerable? AvailablePorts { get; private set; } - - public async Task UpdateAvailableSerialPorts() - { - AvailablePorts = null; - Port = null; - var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); - AvailablePorts = ports.SerialPorts; - } - - [Reactive] - [Required] - public partial string? Port { get; set; } - - [Required] - public int? Address { get; set; } - - - [Reactive] - public partial bool Simulated { get; set; } - - public bool Modified => _config.Name != Name - || _config.PortName != Port - || _config.Simulated != Simulated - || _config.Address != Address; - - public TubeFurnaceConfig Save() - { - return Modified ? new TubeFurnaceConfig - { - Name = Name, - Simulated = Simulated, - PortName = Port, - Address = Address ?? 0, - } : _config; - } - } -} +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using System.ComponentModel.DataAnnotations; +using TubeFurnace.Config; +using TubeFurnace.Messaging; + +namespace UI.Features.Devices.TubeFurnace +{ + public partial class TubeFurnaceConfigEditViewModel : ReactiveObject + { + private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; + private readonly AresDevices.AresDevicesClient _devicesClient; + private TubeFurnaceConfig _config; + private string _name = string.Empty; + + public TubeFurnaceConfigEditViewModel(TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, AresDevices.AresDevicesClient devicesClient) + { + _tubeFurnaceClient = tubeFurnaceClient; + _devicesClient = devicesClient; + _config = new TubeFurnaceConfig(); + NewConfig = true; + _ = UpdateAvailableSerialPorts(); + } + + public TubeFurnaceConfigEditViewModel(TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, AresDevices.AresDevicesClient devicesClient, + TubeFurnaceConfig config) + { + _tubeFurnaceClient = tubeFurnaceClient; + _devicesClient = devicesClient; + _config = config; + _ = UpdateAvailableSerialPorts(); + LoadConfig(config); + } + + public bool NewConfig { get; private set; } + + [Required] + public string Name + { + get => _name; + set + { + if (!NewConfig) + { + return; + } + + this.RaiseAndSetIfChanged(ref _name, value); + } + } + + public void LoadConfig(TubeFurnaceConfig config) + { + Port = config.PortName; + _name = config.Name; + Simulated = config.Simulated; + Address = config.Address; + } + + [Reactive] + public partial IEnumerable? AvailablePorts { get; private set; } + + public async Task UpdateAvailableSerialPorts() + { + AvailablePorts = null; + Port = null; + var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); + AvailablePorts = ports.SerialPorts; + } + + [Reactive] + [Required] + public partial string? Port { get; set; } + + [Required] + public int? Address { get; set; } + + + [Reactive] + public partial bool Simulated { get; set; } + + public bool Modified => _config.Name != Name + || _config.PortName != Port + || _config.Simulated != Simulated + || _config.Address != Address; + + public TubeFurnaceConfig Save() + { + return Modified ? new TubeFurnaceConfig + { + Name = Name, + Simulated = Simulated, + PortName = Port, + Address = Address ?? 0, + } : _config; + } + } +} diff --git a/UI/Pages/Shared/Devices/TubeFurnace/TubeFurnaceControl.razor b/UI/Features/Devices/TubeFurnace/TubeFurnaceControl.razor similarity index 94% rename from UI/Pages/Shared/Devices/TubeFurnace/TubeFurnaceControl.razor rename to UI/Features/Devices/TubeFurnace/TubeFurnaceControl.razor index c6b05193..7484d4ef 100644 --- a/UI/Pages/Shared/Devices/TubeFurnace/TubeFurnaceControl.razor +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceControl.razor @@ -1,81 +1,80 @@ -@using UI.Backend.ViewModels.TubeFurnace; -@using UnitsNet; -@using UnitsNet.Units; -@inherits ReactiveUI.Blazor.ReactiveComponentBase - -@inject DialogService DialogService -@inject TooltipService TooltipService - -
-
-
@ViewModel!.DeviceName
-
- - @if(ViewModel!.CurrentTemperatureValue != null) - { - - } - else - { - - } -
- -
- -
- -@code { - - private string CurrentTemperatureDisplay - { - get - { - var result = $"PV {Temperature.From(ViewModel!.CurrentTemperatureValue!.Value, ViewModel!.TemperatureUnit):#0.####}"; - if (ViewModel!.TargetTemperatureValue is not null) - { - result += $" - SV {Temperature.From(ViewModel!.TargetTemperatureValue.Value, ViewModel!.TemperatureUnit):#0.####}"; - } - - return result; - } - } - - private Task SubmitTemperature() - { - if (TargetTemperatureValue is null) - return Task.CompletedTask; - - return ViewModel!.SetTargetTemperature(TargetTemperatureValue.Value); - } - - private int? TargetTemperatureValue { get; set; } - - - private string TargetTemperatureDisplay - { - get => ViewModel!.TargetTemperatureValue!.HasValue ? $"{Temperature.From(ViewModel!.TargetTemperatureValue.Value, ViewModel!.TemperatureUnit):#0.####}" : $"Target Temperature {UnitsNet.UnitAbbreviationsCache.Default.GetDefaultAbbreviation(ViewModel!.TemperatureUnit)}"; - } - - private string[] TemperatureUnits - { - get => Temperature.Units.Select(unit => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(unit)).ToArray(); - } - - private string TemperatureUnit - { - get => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(ViewModel!.TemperatureUnit); - set - { - if(UnitsNetSetup.Default.UnitParser.TryParse(value, out var tempUnit)) - { - ViewModel!.TemperatureUnit = tempUnit; - } - } - } -} +@using UnitsNet; +@using UnitsNet.Units; +@inherits ReactiveUI.Blazor.ReactiveComponentBase + +@inject DialogService DialogService +@inject TooltipService TooltipService + +
+
+
@ViewModel!.DeviceName
+
+ + @if(ViewModel!.CurrentTemperatureValue != null) + { + + } + else + { + + } +
+ +
+ +
+ +@code { + + private string CurrentTemperatureDisplay + { + get + { + var result = $"PV {Temperature.From(ViewModel!.CurrentTemperatureValue!.Value, ViewModel!.TemperatureUnit):#0.####}"; + if (ViewModel!.TargetTemperatureValue is not null) + { + result += $" - SV {Temperature.From(ViewModel!.TargetTemperatureValue.Value, ViewModel!.TemperatureUnit):#0.####}"; + } + + return result; + } + } + + private Task SubmitTemperature() + { + if (TargetTemperatureValue is null) + return Task.CompletedTask; + + return ViewModel!.SetTargetTemperature(TargetTemperatureValue.Value); + } + + private int? TargetTemperatureValue { get; set; } + + + private string TargetTemperatureDisplay + { + get => ViewModel!.TargetTemperatureValue!.HasValue ? $"{Temperature.From(ViewModel!.TargetTemperatureValue.Value, ViewModel!.TemperatureUnit):#0.####}" : $"Target Temperature {UnitsNet.UnitAbbreviationsCache.Default.GetDefaultAbbreviation(ViewModel!.TemperatureUnit)}"; + } + + private string[] TemperatureUnits + { + get => Temperature.Units.Select(unit => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(unit)).ToArray(); + } + + private string TemperatureUnit + { + get => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(ViewModel!.TemperatureUnit); + set + { + if(UnitsNetSetup.Default.UnitParser.TryParse(value, out var tempUnit)) + { + ViewModel!.TemperatureUnit = tempUnit; + } + } + } +} diff --git a/UI/Backend/Factories/TubeFurnaceDeviceControlViewModelFactory.cs b/UI/Features/Devices/TubeFurnace/TubeFurnaceDeviceControlViewModelFactory.cs similarity index 88% rename from UI/Backend/Factories/TubeFurnaceDeviceControlViewModelFactory.cs rename to UI/Features/Devices/TubeFurnace/TubeFurnaceDeviceControlViewModelFactory.cs index 253ce152..e90ae4fb 100644 --- a/UI/Backend/Factories/TubeFurnaceDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceDeviceControlViewModelFactory.cs @@ -1,12 +1,12 @@ -using Ares.Services.Device; +using Ares.Services.Device; using DynamicData; using Google.Protobuf.WellKnownTypes; using TubeFurnace.Messaging; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.TubeFurnace; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories +namespace UI.Features.Devices.TubeFurnace { public class TubeFurnaceDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { diff --git a/UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceSettingsListView.razor b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsListView.razor similarity index 93% rename from UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceSettingsListView.razor rename to UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsListView.razor index a1d3b90d..92ad09f5 100644 --- a/UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceSettingsListView.razor +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsListView.razor @@ -1,66 +1,67 @@ -@page "/settings/device/tube_furnace" -@layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.TubeFurnace -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase - -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - -@if (ViewModel!.SettingsViewModels is null) -{ - -} -else -{ -
- @foreach (var settingsViewModel in ViewModel!.SettingsViewModels) - { -
- -
- } - -
-} - -@code { - private async Task AddConfigClick() - { - var viewModel = ViewModel!.GetNewConfigEditViewModel(); - await DialogService.OpenAsync("New Tube Furnace", ds => - @); - } - - private async Task HandleSave(TubeFurnaceConfigEditViewModel viewModel) - { - SavingInProgress = true; - StateHasChanged(); - var config = viewModel.Save(); - try - { - await ViewModel!.AddNewConfig(viewModel.Save()); - DialogService.Close(); - NotificationService.Success($"Successfully added MFC {viewModel.Name}"); - } - catch(Grpc.Core.RpcException e) - { - NotificationService.Error(e.InnerException?.Message ?? e.Message); - } - finally - { - SavingInProgress = false; - StateHasChanged(); - } - } - - private bool SavingInProgress { get; set; } -} \ No newline at end of file +@page "/settings/device/tube_furnace" +@layout DeviceSettingsLayout +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase + +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + +@if (ViewModel!.SettingsViewModels is null) +{ + +} +else +{ +
+ @foreach (var settingsViewModel in ViewModel!.SettingsViewModels) + { +
+ +
+ } + +
+} + +@code { + private async Task AddConfigClick() + { + var viewModel = ViewModel!.GetNewConfigEditViewModel(); + await DialogService.OpenAsync("New Tube Furnace", ds => + @); + } + + private async Task HandleSave(TubeFurnaceConfigEditViewModel viewModel) + { + SavingInProgress = true; + StateHasChanged(); + var config = viewModel.Save(); + try + { + await ViewModel!.AddNewConfig(viewModel.Save()); + DialogService.Close(); + NotificationService.Success($"Successfully added MFC {viewModel.Name}"); + } + catch(Grpc.Core.RpcException e) + { + NotificationService.Error(e.InnerException?.Message ?? e.Message); + } + finally + { + SavingInProgress = false; + StateHasChanged(); + } + } + + private bool SavingInProgress { get; set; } +} + diff --git a/UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceSettingsListViewModel.cs b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsListViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceSettingsListViewModel.cs rename to UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsListViewModel.cs index 6ac976f7..04785116 100644 --- a/UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceSettingsListViewModel.cs +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsListViewModel.cs @@ -1,58 +1,58 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using ReactiveUI.SourceGenerators; -using ReactiveUI; -using LindbergFurnace; -using TubeFurnace.Config; -using TubeFurnace.Messaging; -using CommunityToolkit.Mvvm.Messaging; - -namespace UI.Backend.ViewModels.Settings.Device.TubeFurnace -{ - public partial class TubeFurnaceSettingsListViewModel : ReactiveObject - { - private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public TubeFurnaceSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, IMessenger messenger) - { - _devicesClient = devicesClient; - _tubeFurnaceClient = tubeFurnaceClient; - _messenger = messenger; - UpdateConfigs(); - } - - [Reactive] - public partial IEnumerable? SettingsViewModels { get; private set; } - - private void UpdateViewModels(IEnumerable deviceConfigs) - { - var viewModels = deviceConfigs.Select(config => new TubeFurnaceSettingsViewModel(config, _tubeFurnaceClient, _devicesClient, _messenger, OnConfigRemoved)); - SettingsViewModels = viewModels; - } - - public TubeFurnaceConfigEditViewModel GetNewConfigEditViewModel() - => new(_tubeFurnaceClient, _devicesClient); - - private Task UpdateConfigs() - { - SettingsViewModels = null; - return _devicesClient - .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(ITubeFurnace).FullName }) - .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); - } - - public async Task OnConfigRemoved() - { - SettingsViewModels = null; - await UpdateConfigs(); - } - - public async Task AddNewConfig(TubeFurnaceConfig config) - { - await _tubeFurnaceClient.AddTubeFurnaceAsync(config); - await UpdateConfigs(); - } - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using LindbergFurnace; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using TubeFurnace.Config; +using TubeFurnace.Messaging; + +namespace UI.Features.Devices.TubeFurnace +{ + public partial class TubeFurnaceSettingsListViewModel : ReactiveObject + { + private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public TubeFurnaceSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, IMessenger messenger) + { + _devicesClient = devicesClient; + _tubeFurnaceClient = tubeFurnaceClient; + _messenger = messenger; + UpdateConfigs(); + } + + [Reactive] + public partial IEnumerable? SettingsViewModels { get; private set; } + + private void UpdateViewModels(IEnumerable deviceConfigs) + { + var viewModels = deviceConfigs.Select(config => new TubeFurnaceSettingsViewModel(config, _tubeFurnaceClient, _devicesClient, _messenger, OnConfigRemoved)); + SettingsViewModels = viewModels; + } + + public TubeFurnaceConfigEditViewModel GetNewConfigEditViewModel() + => new(_tubeFurnaceClient, _devicesClient); + + private Task UpdateConfigs() + { + SettingsViewModels = null; + return _devicesClient + .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(ITubeFurnace).FullName }) + .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); + } + + public async Task OnConfigRemoved() + { + SettingsViewModels = null; + await UpdateConfigs(); + } + + public async Task AddNewConfig(TubeFurnaceConfig config) + { + await _tubeFurnaceClient.AddTubeFurnaceAsync(config); + await UpdateConfigs(); + } + } +} diff --git a/UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceSettingsView.razor b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsView.razor similarity index 94% rename from UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceSettingsView.razor rename to UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsView.razor index 30ba72df..bb047ddb 100644 --- a/UI/Pages/Shared/Settings/Device/TubeFurnace/TubeFurnaceSettingsView.razor +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsView.razor @@ -1,74 +1,75 @@ -@using UI.Backend.ViewModels.Settings.Device.TubeFurnace -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - - -
-
- Port - - @ViewModel!.TubeFurnaceConfig.PortName -
- - @if (ViewModel!.DeviceActive) - { -
- @if (_changing) - { -
-
- -
-
- } -
-
-
- } - else if(_initActive) - { - - } -
-
- -@code { - private bool _initActive; - private bool _changing; - - private string Name => ViewModel!.TubeFurnaceConfig.Name + (ViewModel!.TubeFurnaceConfig.Simulated ? " -- (Simulated)" : ""); - - protected override async Task OnInitializedAsync() - { - _initActive = true; - StateHasChanged(); - await ViewModel!.Init(); - _initActive = false; - StateHasChanged(); - } - - private async Task EditCallback() - { - var viewModel = ViewModel!.EditViewModel; - var result = await DialogService.OpenAsync("Edit Stepper", ds => - @); - - _changing = true; - if (result is true && viewModel.Modified) - { - await ViewModel!.Save(); - NotificationService.Success("Config has been saved"); - } - _changing = false; - } - - private async Task RemoveCallback() - { - var result = await DialogService.Confirm($"Delete {ViewModel!.TubeFurnaceConfig.Name}?"); - if (result is true) - { - await ViewModel!.Remove(); - } - } -} \ No newline at end of file +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + + +
+
+ Port - + @ViewModel!.TubeFurnaceConfig.PortName +
+ + @if (ViewModel!.DeviceActive) + { +
+ @if (_changing) + { +
+
+ +
+
+ } +
+
+
+ } + else if(_initActive) + { + + } +
+
+ +@code { + private bool _initActive; + private bool _changing; + + private string Name => ViewModel!.TubeFurnaceConfig.Name + (ViewModel!.TubeFurnaceConfig.Simulated ? " -- (Simulated)" : ""); + + protected override async Task OnInitializedAsync() + { + _initActive = true; + StateHasChanged(); + await ViewModel!.Init(); + _initActive = false; + StateHasChanged(); + } + + private async Task EditCallback() + { + var viewModel = ViewModel!.EditViewModel; + var result = await DialogService.OpenAsync("Edit Stepper", ds => + @); + + _changing = true; + if (result is true && viewModel.Modified) + { + await ViewModel!.Save(); + NotificationService.Success("Config has been saved"); + } + _changing = false; + } + + private async Task RemoveCallback() + { + var result = await DialogService.Confirm($"Delete {ViewModel!.TubeFurnaceConfig.Name}?"); + if (result is true) + { + await ViewModel!.Remove(); + } + } +} + diff --git a/UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceSettingsViewModel.cs b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceSettingsViewModel.cs rename to UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsViewModel.cs index f9936159..bc9b1fd3 100644 --- a/UI/Backend/ViewModels/Settings/Device/TubeFurnace/TubeFurnaceSettingsViewModel.cs +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceSettingsViewModel.cs @@ -1,90 +1,91 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using Grpc.Core; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using TubeFurnace.Config; -using TubeFurnace.Messaging; -using UI.Backend.Devices; - -namespace UI.Backend.ViewModels.Settings.Device.TubeFurnace -{ - public partial class TubeFurnaceSettingsViewModel : ReactiveObject - { - private readonly DeviceConfig _deviceConfig; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; - private readonly IMessenger _messenger; - - public TubeFurnaceSettingsViewModel(DeviceConfig deviceConfig, - TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, - AresDevices.AresDevicesClient devicesClient, - IMessenger messenger, - Func onRemoveCallback) - { - _deviceConfig = deviceConfig; - TubeFurnaceConfig = deviceConfig.ConfigData.Unpack(); - _tubeFurnaceClient = tubeFurnaceClient; - _devicesClient = devicesClient; - _messenger = messenger; - OnRemoveCallback = onRemoveCallback; - EditViewModel = new TubeFurnaceConfigEditViewModel(_tubeFurnaceClient, _devicesClient, TubeFurnaceConfig); - } - - public TubeFurnaceConfig TubeFurnaceConfig { get; } - - public Func OnRemoveCallback { get; } - - public TubeFurnaceConfigEditViewModel EditViewModel { get; } - - public async Task GetDeviceOperationalStatus() - { - try - { - var status = await _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - DeviceActive = status.OperationalState is OperationalState.Active; - return status; - } - catch (RpcException) - { - return new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered stepper controller with a name {TubeFurnaceConfig.Name}" }; - } - } - - public Task Activate() - => _devicesClient.ActivateAsync(new DeviceActivateRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - - public async Task Save() - { - var tubeFurnaceConfig = EditViewModel.Save(); - var updateRequest = new TubeFurnaceUpdateRequest - { - Id = _deviceConfig.UniqueId, - Config = tubeFurnaceConfig - }; - - await _tubeFurnaceClient.UpdateTubeFurnaceAsync(updateRequest); - } - - public async Task Remove() - { - await _tubeFurnaceClient.RemoveTubeFurnaceAsync(new TubeFurnaceRequest { TubeFurnaceId = _deviceConfig.UniqueId }); - _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); - await OnRemoveCallback(); - } - - public async Task Init() - { - var status = await GetDeviceOperationalStatus(); - if (status.OperationalState != OperationalState.Active) - return; - - var state = await _tubeFurnaceClient.GetStateAsync(new TubeFurnaceRequest { TubeFurnaceId = _deviceConfig.UniqueId }); - - } - - [Reactive] - public partial bool DeviceActive { get; private set; } - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using Grpc.Core; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using TubeFurnace.Config; +using TubeFurnace.Messaging; +using UI.Application.Devices; +using UI.Features.Devices.Shared; + +namespace UI.Features.Devices.TubeFurnace +{ + public partial class TubeFurnaceSettingsViewModel : ReactiveObject + { + private readonly DeviceConfig _deviceConfig; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; + private readonly IMessenger _messenger; + + public TubeFurnaceSettingsViewModel(DeviceConfig deviceConfig, + TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient, + AresDevices.AresDevicesClient devicesClient, + IMessenger messenger, + Func onRemoveCallback) + { + _deviceConfig = deviceConfig; + TubeFurnaceConfig = deviceConfig.ConfigData.Unpack(); + _tubeFurnaceClient = tubeFurnaceClient; + _devicesClient = devicesClient; + _messenger = messenger; + OnRemoveCallback = onRemoveCallback; + EditViewModel = new TubeFurnaceConfigEditViewModel(_tubeFurnaceClient, _devicesClient, TubeFurnaceConfig); + } + + public TubeFurnaceConfig TubeFurnaceConfig { get; } + + public Func OnRemoveCallback { get; } + + public TubeFurnaceConfigEditViewModel EditViewModel { get; } + + public async Task GetDeviceOperationalStatus() + { + try + { + var status = await _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + DeviceActive = status.OperationalState is OperationalState.Active; + return status; + } + catch (RpcException) + { + return new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered stepper controller with a name {TubeFurnaceConfig.Name}" }; + } + } + + public Task Activate() + => _devicesClient.ActivateAsync(new DeviceActivateRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + + public async Task Save() + { + var tubeFurnaceConfig = EditViewModel.Save(); + var updateRequest = new TubeFurnaceUpdateRequest + { + Id = _deviceConfig.UniqueId, + Config = tubeFurnaceConfig + }; + + await _tubeFurnaceClient.UpdateTubeFurnaceAsync(updateRequest); + } + + public async Task Remove() + { + await _tubeFurnaceClient.RemoveTubeFurnaceAsync(new TubeFurnaceRequest { TubeFurnaceId = _deviceConfig.UniqueId }); + _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); + await OnRemoveCallback(); + } + + public async Task Init() + { + var status = await GetDeviceOperationalStatus(); + if (status.OperationalState != OperationalState.Active) + return; + + var state = await _tubeFurnaceClient.GetStateAsync(new TubeFurnaceRequest { TubeFurnaceId = _deviceConfig.UniqueId }); + + } + + [Reactive] + public partial bool DeviceActive { get; private set; } + } +} diff --git a/UI/Backend/ViewModels/Devices/TubeFurnace/TubeFurnaceViewModel.cs b/UI/Features/Devices/TubeFurnace/TubeFurnaceViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Devices/TubeFurnace/TubeFurnaceViewModel.cs rename to UI/Features/Devices/TubeFurnace/TubeFurnaceViewModel.cs index 208eced9..9c49d21a 100644 --- a/UI/Backend/ViewModels/Devices/TubeFurnace/TubeFurnaceViewModel.cs +++ b/UI/Features/Devices/TubeFurnace/TubeFurnaceViewModel.cs @@ -1,101 +1,101 @@ -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using TubeFurnace.Messaging; -using UI.Pages.Shared.Devices.TubeFurnace; -using UnitsNet; -using UnitsNet.Units; - -namespace UI.Backend.ViewModels.TubeFurnace; - -public partial class TubeFurnaceViewModel : DeviceUnitControlViewModel, IAsyncDisposable -{ - private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; - private Task _stateListener = Task.CompletedTask; - private CancellationTokenSource _stateUpdateTokenSource; - private TemperatureUnit _temperatureUnit = TemperatureUnit.DegreeCelsius; - - public TubeFurnaceViewModel(string id, string deviceName, TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient) : base(id, deviceName) - { - _tubeFurnaceClient = tubeFurnaceClient; - _stateUpdateTokenSource = new(); - StartStateUpdater(); - - ViewType = typeof(TubeFurnaceControl); - DefaultWidth = 18; - } - - private void StartStateUpdater() - { - _stateListener = Task.Factory.StartNew(async _ => - { - Thread.CurrentThread.Name = "Tube Furnace State Listener View Model Thread"; - var cancelled = false; - lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } - while (!cancelled) - { - await UpdateState(); - await Task.Delay(TimeSpan.FromMilliseconds(500)); - lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } - } - }, - _stateUpdateTokenSource.Token, - TaskCreationOptions.LongRunning); - } - - private async Task UpdateState() - { - var deviceRequest = new TubeFurnaceRequest { TubeFurnaceId = DeviceId }; - await _tubeFurnaceClient.GetSetpointAsync(deviceRequest); - await _tubeFurnaceClient.GetCurrentTemperatureAsync(deviceRequest); - - var state = await _tubeFurnaceClient.GetStateAsync(new TubeFurnaceRequest { TubeFurnaceId = DeviceId }); - - // TODO - CurrentTemperatureValue = Temperature.FromDegreesCelsius(state.CurrentTemperature).As(TemperatureUnit); - TargetTemperatureValue = Temperature.FromDegreesCelsius(state.Setpoint).As(TemperatureUnit); - } - - public async ValueTask DisposeAsync() - { - _stateUpdateTokenSource.Cancel(); - await _stateListener; - _stateListener.Dispose(); - _stateUpdateTokenSource.Dispose(); - GC.SuppressFinalize(this); - } - - public async Task SetTargetTemperature(double value) - { - await _tubeFurnaceClient - .SetSetpointAsync( - new SetSetpointRequest - { - DeviceRequest = new TubeFurnaceRequest - { - TubeFurnaceId = DeviceId - }, - DegreesCelsius = Temperature.From(value, TemperatureUnit).DegreesCelsius - }); - } - - public TemperatureUnit TemperatureUnit - { - get => _temperatureUnit; - set - { - TargetTemperatureValue = TargetTemperatureValue.HasValue ? Temperature.From(TargetTemperatureValue.Value, TemperatureUnit).As(value) : null; - CurrentTemperatureValue = CurrentTemperatureValue.HasValue ? Temperature.From(CurrentTemperatureValue.Value, TemperatureUnit).As(value) : null; - this.RaiseAndSetIfChanged(ref _temperatureUnit, value); - } - } - [Reactive] - public partial double? CurrentTemperatureValue { get; private set; } - [Reactive] - public partial double? TargetTemperatureValue { get; set; } - [Reactive] - public partial DurationUnit RampRateDurationUnit { get; set; } - [Reactive] - public partial double? TargetRampRateTemperatureValue { get; set; } - [Reactive] - public partial double? CurrentRampRateTemperatureValue { get; set; } -} +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using TubeFurnace.Messaging; +using UI.Application.Devices; +using UnitsNet; +using UnitsNet.Units; + +namespace UI.Features.Devices.TubeFurnace; + +public partial class TubeFurnaceViewModel : DeviceUnitControlViewModel, IAsyncDisposable +{ + private readonly TubeFurnaceRpc.TubeFurnaceRpcClient _tubeFurnaceClient; + private Task _stateListener = Task.CompletedTask; + private CancellationTokenSource _stateUpdateTokenSource; + private TemperatureUnit _temperatureUnit = TemperatureUnit.DegreeCelsius; + + public TubeFurnaceViewModel(string id, string deviceName, TubeFurnaceRpc.TubeFurnaceRpcClient tubeFurnaceClient) : base(id, deviceName) + { + _tubeFurnaceClient = tubeFurnaceClient; + _stateUpdateTokenSource = new(); + StartStateUpdater(); + + ViewType = typeof(TubeFurnaceControl); + DefaultWidth = 18; + } + + private void StartStateUpdater() + { + _stateListener = Task.Factory.StartNew(async _ => + { + Thread.CurrentThread.Name = "Tube Furnace State Listener View Model Thread"; + var cancelled = false; + lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } + while (!cancelled) + { + await UpdateState(); + await Task.Delay(TimeSpan.FromMilliseconds(500)); + lock (_stateUpdateTokenSource) { cancelled = _stateUpdateTokenSource.IsCancellationRequested; } + } + }, + _stateUpdateTokenSource.Token, + TaskCreationOptions.LongRunning); + } + + private async Task UpdateState() + { + var deviceRequest = new TubeFurnaceRequest { TubeFurnaceId = DeviceId }; + await _tubeFurnaceClient.GetSetpointAsync(deviceRequest); + await _tubeFurnaceClient.GetCurrentTemperatureAsync(deviceRequest); + + var state = await _tubeFurnaceClient.GetStateAsync(new TubeFurnaceRequest { TubeFurnaceId = DeviceId }); + + // TODO + CurrentTemperatureValue = Temperature.FromDegreesCelsius(state.CurrentTemperature).As(TemperatureUnit); + TargetTemperatureValue = Temperature.FromDegreesCelsius(state.Setpoint).As(TemperatureUnit); + } + + public async ValueTask DisposeAsync() + { + _stateUpdateTokenSource.Cancel(); + await _stateListener; + _stateListener.Dispose(); + _stateUpdateTokenSource.Dispose(); + GC.SuppressFinalize(this); + } + + public async Task SetTargetTemperature(double value) + { + await _tubeFurnaceClient + .SetSetpointAsync( + new SetSetpointRequest + { + DeviceRequest = new TubeFurnaceRequest + { + TubeFurnaceId = DeviceId + }, + DegreesCelsius = Temperature.From(value, TemperatureUnit).DegreesCelsius + }); + } + + public TemperatureUnit TemperatureUnit + { + get => _temperatureUnit; + set + { + TargetTemperatureValue = TargetTemperatureValue.HasValue ? Temperature.From(TargetTemperatureValue.Value, TemperatureUnit).As(value) : null; + CurrentTemperatureValue = CurrentTemperatureValue.HasValue ? Temperature.From(CurrentTemperatureValue.Value, TemperatureUnit).As(value) : null; + this.RaiseAndSetIfChanged(ref _temperatureUnit, value); + } + } + [Reactive] + public partial double? CurrentTemperatureValue { get; private set; } + [Reactive] + public partial double? TargetTemperatureValue { get; set; } + [Reactive] + public partial DurationUnit RampRateDurationUnit { get; set; } + [Reactive] + public partial double? TargetRampRateTemperatureValue { get; set; } + [Reactive] + public partial double? CurrentRampRateTemperatureValue { get; set; } +} diff --git a/UI/Pages/Shared/Settings/Device/ValveController/ValveControllerConfigEditView.razor b/UI/Features/Devices/ValveController/ValveControllerConfigEditView.razor similarity index 87% rename from UI/Pages/Shared/Settings/Device/ValveController/ValveControllerConfigEditView.razor rename to UI/Features/Devices/ValveController/ValveControllerConfigEditView.razor index de5b2ae4..bb056b91 100644 --- a/UI/Pages/Shared/Settings/Device/ValveController/ValveControllerConfigEditView.razor +++ b/UI/Features/Devices/ValveController/ValveControllerConfigEditView.razor @@ -1,39 +1,39 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase - - -
- - - - @if (ViewModel!.AvailablePorts is not null) - { - @foreach (var port in ViewModel!.AvailablePorts) - { - - } - } - -
- - Simulated -
-
- - - -
- -@code { - [Parameter] - public Action OnValidSubmit { get; set; } = delegate - { - }; - - private void PortChanged(ChangeEventArgs obj) - { - if (obj.Value is not string stringValue) - throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); - - ViewModel!.Port = stringValue; - } -} +@inherits ReactiveUI.Blazor.ReactiveComponentBase + + +
+ + + + @if (ViewModel!.AvailablePorts is not null) + { + @foreach (var port in ViewModel!.AvailablePorts) + { + + } + } + +
+ + Simulated +
+
+ + + +
+ +@code { + [Parameter] + public Action OnValidSubmit { get; set; } = delegate + { + }; + + private void PortChanged(ChangeEventArgs obj) + { + if (obj.Value is not string stringValue) + throw new InvalidOperationException($"{obj.Value} is not a string and cannot be used as a port name."); + + ViewModel!.Port = stringValue; + } +} diff --git a/UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerConfigEditViewModel.cs b/UI/Features/Devices/ValveController/ValveControllerConfigEditViewModel.cs similarity index 90% rename from UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerConfigEditViewModel.cs rename to UI/Features/Devices/ValveController/ValveControllerConfigEditViewModel.cs index 43c701e1..82fe18a5 100644 --- a/UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerConfigEditViewModel.cs +++ b/UI/Features/Devices/ValveController/ValveControllerConfigEditViewModel.cs @@ -1,76 +1,76 @@ -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using System.ComponentModel.DataAnnotations; -using Ares.Services.Device; -using ValveController.Config; -using ValveController.Services; - -namespace UI.Backend.ViewModels.Settings.Device.ValveController; - -public partial class ValveControllerConfigEditViewModel : ReactiveObject -{ - private readonly ValveControllerRpc.ValveControllerRpcClient _client; - private readonly ValveControllerConfig _config; - private readonly AresDevices.AresDevicesClient _devicesClient; - private string? _deviceName; - - public ValveControllerConfigEditViewModel(ValveControllerRpc.ValveControllerRpcClient client, AresDevices.AresDevicesClient devicesClient) - { - _client = client; - _devicesClient = devicesClient; - _config = new ValveControllerConfig(); - NewConfig = true; - _ = UpdateAvailableSerialPorts(); - } - - public ValveControllerConfigEditViewModel(ValveControllerRpc.ValveControllerRpcClient client, AresDevices.AresDevicesClient devicesClient, ValveControllerConfig config) - { - _client = client; - _devicesClient = devicesClient; - _config = new ValveControllerConfig(); - NewConfig = true; - _ = UpdateAvailableSerialPorts(); - _deviceName = config.Name; - Port = config.PortName; - Simulated = config.Simulated; - } - - [Required] - public string? Name - { - get => _deviceName; - - set - { - if (!NewConfig) - return; - - _deviceName = value; - } - } - - [Required] - public string? Port { get; set; } - - public bool NewConfig { get; set; } - - public bool Simulated { get; set; } - - [Reactive] - public partial IEnumerable? AvailablePorts { get; private set; } - - public bool Modified => _config.Name != Name || _config.PortName != Port || _config.Simulated != Simulated; - - public async Task UpdateAvailableSerialPorts() - { - AvailablePorts = null; - Port = null; - var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); - AvailablePorts = ports.SerialPorts; - } - - public ValveControllerConfig Save() - => Modified ? new ValveControllerConfig { Name = Name, PortName = Port, Simulated = Simulated } : _config; - -} +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using System.ComponentModel.DataAnnotations; +using ValveController.Config; +using ValveController.Services; + +namespace UI.Features.Devices.ValveController; + +public partial class ValveControllerConfigEditViewModel : ReactiveObject +{ + private readonly ValveControllerRpc.ValveControllerRpcClient _client; + private readonly ValveControllerConfig _config; + private readonly AresDevices.AresDevicesClient _devicesClient; + private string? _deviceName; + + public ValveControllerConfigEditViewModel(ValveControllerRpc.ValveControllerRpcClient client, AresDevices.AresDevicesClient devicesClient) + { + _client = client; + _devicesClient = devicesClient; + _config = new ValveControllerConfig(); + NewConfig = true; + _ = UpdateAvailableSerialPorts(); + } + + public ValveControllerConfigEditViewModel(ValveControllerRpc.ValveControllerRpcClient client, AresDevices.AresDevicesClient devicesClient, ValveControllerConfig config) + { + _client = client; + _devicesClient = devicesClient; + _config = new ValveControllerConfig(); + NewConfig = true; + _ = UpdateAvailableSerialPorts(); + _deviceName = config.Name; + Port = config.PortName; + Simulated = config.Simulated; + } + + [Required] + public string? Name + { + get => _deviceName; + + set + { + if (!NewConfig) + return; + + _deviceName = value; + } + } + + [Required] + public string? Port { get; set; } + + public bool NewConfig { get; set; } + + public bool Simulated { get; set; } + + [Reactive] + public partial IEnumerable? AvailablePorts { get; private set; } + + public bool Modified => _config.Name != Name || _config.PortName != Port || _config.Simulated != Simulated; + + public async Task UpdateAvailableSerialPorts() + { + AvailablePorts = null; + Port = null; + var ports = await _devicesClient.GetServerSerialPortsAsync(new Empty()); + AvailablePorts = ports.SerialPorts; + } + + public ValveControllerConfig Save() + => Modified ? new ValveControllerConfig { Name = Name, PortName = Port, Simulated = Simulated } : _config; + +} diff --git a/UI/Backend/Factories/ValveControllerDeviceControlViewModelFactory.cs b/UI/Features/Devices/ValveController/ValveControllerDeviceControlViewModelFactory.cs similarity index 87% rename from UI/Backend/Factories/ValveControllerDeviceControlViewModelFactory.cs rename to UI/Features/Devices/ValveController/ValveControllerDeviceControlViewModelFactory.cs index 117e3c87..3ba51c8d 100644 --- a/UI/Backend/Factories/ValveControllerDeviceControlViewModelFactory.cs +++ b/UI/Features/Devices/ValveController/ValveControllerDeviceControlViewModelFactory.cs @@ -1,12 +1,12 @@ -using Ares.Services.Device; +using Ares.Services.Device; using DynamicData; using Google.Protobuf.WellKnownTypes; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Devices.ValveController; using ValveController.Services; +using UI.Application.Devices.Repos; +using UI.Infrastructure.Devices; +using UI.Application.Devices; -namespace UI.Backend.Factories; +namespace UI.Features.Devices.ValveController; public class ValveControllerDeviceControlViewModelFactory : DeviceConnectorViewModelFactory { diff --git a/UI/Pages/Shared/Settings/Device/ValveController/ValveControllerSettingsList.razor b/UI/Features/Devices/ValveController/ValveControllerSettingsList.razor similarity index 93% rename from UI/Pages/Shared/Settings/Device/ValveController/ValveControllerSettingsList.razor rename to UI/Features/Devices/ValveController/ValveControllerSettingsList.razor index 855e12ff..54716ef4 100644 --- a/UI/Pages/Shared/Settings/Device/ValveController/ValveControllerSettingsList.razor +++ b/UI/Features/Devices/ValveController/ValveControllerSettingsList.razor @@ -1,66 +1,67 @@ -@page "/settings/device/ValveController" -@layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.ValveController -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase - -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - -@if (ViewModel!.SettingsViewModels is null) -{ - -} -else -{ -
- @foreach (var vm in ViewModel!.SettingsViewModels) - { -
- -
- } - -
-} - - -@code { - private async Task AddConfigClick() - { - var viewModel = ViewModel!.GetNewConfigEditViewModel(); - await DialogService.OpenAsync("New Valve Controller", ds => - @); - } - - private async Task HandleSave(ValveControllerConfigEditViewModel viewModel) - { - SavingInProgress = true; - StateHasChanged(); - try - { - await ViewModel!.AddNewConfig(viewModel.Save()); - DialogService.Close(); - NotificationService.Success($"Successfully added Valve Controller {viewModel.Name}"); - } - catch(Grpc.Core.RpcException e) - { - NotificationService.Error(e.InnerException?.Message ?? e.Message); - } - finally - { - SavingInProgress = false; - StateHasChanged(); - } - } - - private bool SavingInProgress { get; set; } -} \ No newline at end of file +@page "/settings/device/ValveController" +@layout DeviceSettingsLayout +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase + +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + +@if (ViewModel!.SettingsViewModels is null) +{ + +} +else +{ +
+ @foreach (var vm in ViewModel!.SettingsViewModels) + { +
+ +
+ } + +
+} + + +@code { + private async Task AddConfigClick() + { + var viewModel = ViewModel!.GetNewConfigEditViewModel(); + await DialogService.OpenAsync("New Valve Controller", ds => + @); + } + + private async Task HandleSave(ValveControllerConfigEditViewModel viewModel) + { + SavingInProgress = true; + StateHasChanged(); + try + { + await ViewModel!.AddNewConfig(viewModel.Save()); + DialogService.Close(); + NotificationService.Success($"Successfully added Valve Controller {viewModel.Name}"); + } + catch(Grpc.Core.RpcException e) + { + NotificationService.Error(e.InnerException?.Message ?? e.Message); + } + finally + { + SavingInProgress = false; + StateHasChanged(); + } + } + + private bool SavingInProgress { get; set; } +} + diff --git a/UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerSettingsListViewModel.cs b/UI/Features/Devices/ValveController/ValveControllerSettingsListViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerSettingsListViewModel.cs rename to UI/Features/Devices/ValveController/ValveControllerSettingsListViewModel.cs index 6a08bd1d..62790905 100644 --- a/UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerSettingsListViewModel.cs +++ b/UI/Features/Devices/ValveController/ValveControllerSettingsListViewModel.cs @@ -1,59 +1,59 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using ReactiveUI; -using ReactiveUI.SourceGenerators; -using ValveController; -using ValveController.Config; -using ValveController.Services; - -namespace UI.Backend.ViewModels.Settings.Device.ValveController; - -public partial class ValveControllerSettingsListViewModel : ReactiveObject -{ - private readonly ValveControllerRpc.ValveControllerRpcClient _valveControllerClient; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public ValveControllerSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, - ValveControllerRpc.ValveControllerRpcClient valveControllerRpcClient, - IMessenger messenger) - { - _devicesClient = devicesClient; - _valveControllerClient = valveControllerRpcClient; - _messenger = messenger; - UpdateConfigs(); - } - - [Reactive] - public partial IEnumerable? SettingsViewModels { get; private set; } - - private void UpdateViewModels(IEnumerable deviceConfigs) - { - var viewModels = deviceConfigs.Select(config => new ValveControllerSettingsViewModel(config, _valveControllerClient, _devicesClient, _messenger, OnConfigRemoved)); - SettingsViewModels = viewModels; - } - - public ValveControllerConfigEditViewModel GetNewConfigEditViewModel() => new(_valveControllerClient, _devicesClient); - - private Task UpdateConfigs() - { - SettingsViewModels = null; - return _devicesClient - .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IValveController).FullName }) - .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); - } - - private async Task OnConfigRemoved() - { - SettingsViewModels = null; - await UpdateConfigs(); - } - - public async Task AddNewConfig(ValveControllerConfig config) - { - await _valveControllerClient.AddValveControllerAsync(config); - await UpdateConfigs(); - } - -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using ReactiveUI; +using ReactiveUI.SourceGenerators; +using ValveController; +using ValveController.Config; +using ValveController.Services; + +namespace UI.Features.Devices.ValveController; + +public partial class ValveControllerSettingsListViewModel : ReactiveObject +{ + private readonly ValveControllerRpc.ValveControllerRpcClient _valveControllerClient; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public ValveControllerSettingsListViewModel(AresDevices.AresDevicesClient devicesClient, + ValveControllerRpc.ValveControllerRpcClient valveControllerRpcClient, + IMessenger messenger) + { + _devicesClient = devicesClient; + _valveControllerClient = valveControllerRpcClient; + _messenger = messenger; + UpdateConfigs(); + } + + [Reactive] + public partial IEnumerable? SettingsViewModels { get; private set; } + + private void UpdateViewModels(IEnumerable deviceConfigs) + { + var viewModels = deviceConfigs.Select(config => new ValveControllerSettingsViewModel(config, _valveControllerClient, _devicesClient, _messenger, OnConfigRemoved)); + SettingsViewModels = viewModels; + } + + public ValveControllerConfigEditViewModel GetNewConfigEditViewModel() => new(_valveControllerClient, _devicesClient); + + private Task UpdateConfigs() + { + SettingsViewModels = null; + return _devicesClient + .GetAllDeviceConfigsAsync(new DeviceConfigRequest { DeviceType = typeof(IValveController).FullName }) + .ResponseAsync.ContinueWith(task => UpdateViewModels(task.Result.Configs)); + } + + private async Task OnConfigRemoved() + { + SettingsViewModels = null; + await UpdateConfigs(); + } + + public async Task AddNewConfig(ValveControllerConfig config) + { + await _valveControllerClient.AddValveControllerAsync(config); + await UpdateConfigs(); + } + +} diff --git a/UI/Pages/Shared/Settings/Device/ValveController/ValveControllerSettingsView.razor b/UI/Features/Devices/ValveController/ValveControllerSettingsView.razor similarity index 87% rename from UI/Pages/Shared/Settings/Device/ValveController/ValveControllerSettingsView.razor rename to UI/Features/Devices/ValveController/ValveControllerSettingsView.razor index e1c0d707..4bce757d 100644 --- a/UI/Pages/Shared/Settings/Device/ValveController/ValveControllerSettingsView.razor +++ b/UI/Features/Devices/ValveController/ValveControllerSettingsView.razor @@ -1,40 +1,43 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@inject DialogService DialogService -@inject IUiNotificationService NotificationService - - - -
- @if (ViewModel!.ValveControllerConfig.Simulated) - { - Simulated Valve Controller -- - } - - @ViewModel!.ValveControllerConfig.Name - @ViewModel!.ValveControllerConfig.PortName -
-
- -@code { - private async Task EditCallback() - { - var viewModel = ViewModel!.EditViewModel; - var result = await DialogService.OpenAsync("Edit Valve Controller", ds => - @); - - if (result is true && viewModel.Modified) - { - await ViewModel!.Save(); - NotificationService.Success("Config has been saved"); - } - } - - private async Task RemoveCallback() - { - var result = await DialogService.Confirm($"Delete {ViewModel!.ValveControllerConfig.Name}?"); - if (result is true) - { - await ViewModel!.Remove(); - } - } -} +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject DialogService DialogService +@inject IUiNotificationService NotificationService + + + +
+ @if (ViewModel!.ValveControllerConfig.Simulated) + { + Simulated Valve Controller -- + } + + @ViewModel!.ValveControllerConfig.Name + @ViewModel!.ValveControllerConfig.PortName +
+
+ +@code { + private async Task EditCallback() + { + var viewModel = ViewModel!.EditViewModel; + var result = await DialogService.OpenAsync("Edit Valve Controller", ds => + @); + + if (result is true && viewModel.Modified) + { + await ViewModel!.Save(); + NotificationService.Success("Config has been saved"); + } + } + + private async Task RemoveCallback() + { + var result = await DialogService.Confirm($"Delete {ViewModel!.ValveControllerConfig.Name}?"); + if (result is true) + { + await ViewModel!.Remove(); + } + } +} + + diff --git a/UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerSettingsViewModel.cs b/UI/Features/Devices/ValveController/ValveControllerSettingsViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerSettingsViewModel.cs rename to UI/Features/Devices/ValveController/ValveControllerSettingsViewModel.cs index 6e562278..25242b19 100644 --- a/UI/Backend/ViewModels/Settings/Device/ValveController/ValveControllerSettingsViewModel.cs +++ b/UI/Features/Devices/ValveController/ValveControllerSettingsViewModel.cs @@ -1,72 +1,73 @@ -using Ares.Datamodel.Device; -using Ares.Services.Device; -using CommunityToolkit.Mvvm.Messaging; -using Grpc.Core; -using ReactiveUI; -using UI.Backend.Devices; -using ValveController.Config; -using ValveController.Services; - -namespace UI.Backend.ViewModels.Settings.Device.ValveController; - -public class ValveControllerSettingsViewModel : ReactiveObject -{ - private readonly ValveControllerRpc.ValveControllerRpcClient _valveControllerClient; - private readonly DeviceConfig _deviceConfig; - private readonly AresDevices.AresDevicesClient _devicesClient; - private readonly IMessenger _messenger; - - public ValveControllerSettingsViewModel(DeviceConfig deviceConfig, - ValveControllerRpc.ValveControllerRpcClient valveControllerClient, - AresDevices.AresDevicesClient devicesClient, - IMessenger messenger, - Func onRemoveCallback) - { - _valveControllerClient = valveControllerClient; - _deviceConfig = deviceConfig; - ValveControllerConfig = deviceConfig.ConfigData.Unpack(); - _devicesClient = devicesClient; - _messenger = messenger; - OnRemoveCallback = onRemoveCallback; - EditViewModel = new ValveControllerConfigEditViewModel(_valveControllerClient, _devicesClient, ValveControllerConfig); - } - - public ValveControllerConfig ValveControllerConfig { get; } - - public Func OnRemoveCallback { get; } - - public ValveControllerConfigEditViewModel EditViewModel { get; } - - public Task GetDeviceOperationalStatus() - { - try - { - return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; - } - - catch(RpcException) - { - return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered Valve Controller with a name {ValveControllerConfig.Name}" }); - } - } - - public async Task Save() - { - var valveControllerConfig = EditViewModel.Save(); - var updateRequest = new UpdateValveControllerRequest { Id = _deviceConfig.UniqueId, Config = valveControllerConfig }; - await _valveControllerClient.UpdateValveControllersAsync(updateRequest); - } - - public Task Activate() - => _devicesClient.ActivateAsync(new DeviceActivateRequest - { - DeviceId = _deviceConfig.UniqueId - }).ResponseAsync; - - public async Task Remove() - { - await _valveControllerClient.RemoveValveControllerAsync(new ValveControllerRequest { DeviceId = _deviceConfig.UniqueId }); - _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); - await OnRemoveCallback(); - } -} +using Ares.Datamodel.Device; +using Ares.Services.Device; +using CommunityToolkit.Mvvm.Messaging; +using Grpc.Core; +using ReactiveUI; +using UI.Application.Devices; +using UI.Features.Devices.Shared; +using ValveController.Config; +using ValveController.Services; + +namespace UI.Features.Devices.ValveController; + +public class ValveControllerSettingsViewModel : ReactiveObject +{ + private readonly ValveControllerRpc.ValveControllerRpcClient _valveControllerClient; + private readonly DeviceConfig _deviceConfig; + private readonly AresDevices.AresDevicesClient _devicesClient; + private readonly IMessenger _messenger; + + public ValveControllerSettingsViewModel(DeviceConfig deviceConfig, + ValveControllerRpc.ValveControllerRpcClient valveControllerClient, + AresDevices.AresDevicesClient devicesClient, + IMessenger messenger, + Func onRemoveCallback) + { + _valveControllerClient = valveControllerClient; + _deviceConfig = deviceConfig; + ValveControllerConfig = deviceConfig.ConfigData.Unpack(); + _devicesClient = devicesClient; + _messenger = messenger; + OnRemoveCallback = onRemoveCallback; + EditViewModel = new ValveControllerConfigEditViewModel(_valveControllerClient, _devicesClient, ValveControllerConfig); + } + + public ValveControllerConfig ValveControllerConfig { get; } + + public Func OnRemoveCallback { get; } + + public ValveControllerConfigEditViewModel EditViewModel { get; } + + public Task GetDeviceOperationalStatus() + { + try + { + return _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = _deviceConfig.UniqueId }).ResponseAsync; + } + + catch(RpcException) + { + return Task.FromResult(new DeviceOperationalStatus { OperationalState = OperationalState.Error, Message = $"Unable to find a registered Valve Controller with a name {ValveControllerConfig.Name}" }); + } + } + + public async Task Save() + { + var valveControllerConfig = EditViewModel.Save(); + var updateRequest = new UpdateValveControllerRequest { Id = _deviceConfig.UniqueId, Config = valveControllerConfig }; + await _valveControllerClient.UpdateValveControllersAsync(updateRequest); + } + + public Task Activate() + => _devicesClient.ActivateAsync(new DeviceActivateRequest + { + DeviceId = _deviceConfig.UniqueId + }).ResponseAsync; + + public async Task Remove() + { + await _valveControllerClient.RemoveValveControllerAsync(new ValveControllerRequest { DeviceId = _deviceConfig.UniqueId }); + _messenger.Send(new DeviceDeletedMessage(_deviceConfig.UniqueId)); + await OnRemoveCallback(); + } +} diff --git a/UI/Backend/ViewModels/Devices/ValveController/ValveControllerUnitControlViewModel.cs b/UI/Features/Devices/ValveController/ValveControllerUnitControlViewModel.cs similarity index 86% rename from UI/Backend/ViewModels/Devices/ValveController/ValveControllerUnitControlViewModel.cs rename to UI/Features/Devices/ValveController/ValveControllerUnitControlViewModel.cs index baa978d7..d2c6f52b 100644 --- a/UI/Backend/ViewModels/Devices/ValveController/ValveControllerUnitControlViewModel.cs +++ b/UI/Features/Devices/ValveController/ValveControllerUnitControlViewModel.cs @@ -1,35 +1,35 @@ -using UI.Pages.Shared.Devices.ValveController; -using ValveController.Services; - -namespace UI.Backend.ViewModels.Devices.ValveController; - -public class ValveControllerUnitControlViewModel : DeviceUnitControlViewModel -{ - private readonly ValveControllerRpc.ValveControllerRpcClient _client; - - public ValveControllerUnitControlViewModel(string deviceId, string deviceName, ValveControllerRpc.ValveControllerRpcClient client) : base(deviceId, deviceName) - { - _client = client; - ViewType = typeof(ValveControllerWidgetView); - } - - public void EngageRelayOne() - { - _client.EngageRelayOne(new DeviceRequest { DeviceId = DeviceId }); - } - - public void DisengageRelayOne() - { - _client.DisengageRelayOne(new DeviceRequest { DeviceId = DeviceId }); - } - - public void EngageRelayTwo() - { - _client.EngageRelayTwo(new DeviceRequest { DeviceId = DeviceId }); - } - - public void DisengageRelayTwo() - { - _client.DisengageRelayTwo(new DeviceRequest { DeviceId = DeviceId }); - } -} +using UI.Application.Devices; +using ValveController.Services; + +namespace UI.Features.Devices.ValveController; + +public class ValveControllerUnitControlViewModel : DeviceUnitControlViewModel +{ + private readonly ValveControllerRpc.ValveControllerRpcClient _client; + + public ValveControllerUnitControlViewModel(string deviceId, string deviceName, ValveControllerRpc.ValveControllerRpcClient client) : base(deviceId, deviceName) + { + _client = client; + ViewType = typeof(ValveControllerWidgetView); + } + + public void EngageRelayOne() + { + _client.EngageRelayOne(new DeviceRequest { DeviceId = DeviceId }); + } + + public void DisengageRelayOne() + { + _client.DisengageRelayOne(new DeviceRequest { DeviceId = DeviceId }); + } + + public void EngageRelayTwo() + { + _client.EngageRelayTwo(new DeviceRequest { DeviceId = DeviceId }); + } + + public void DisengageRelayTwo() + { + _client.DisengageRelayTwo(new DeviceRequest { DeviceId = DeviceId }); + } +} diff --git a/UI/Pages/Shared/Devices/ValveController/ValveControllerWidgetView.razor b/UI/Features/Devices/ValveController/ValveControllerWidgetView.razor similarity index 92% rename from UI/Pages/Shared/Devices/ValveController/ValveControllerWidgetView.razor rename to UI/Features/Devices/ValveController/ValveControllerWidgetView.razor index f4cd1272..d72b8477 100644 --- a/UI/Pages/Shared/Devices/ValveController/ValveControllerWidgetView.razor +++ b/UI/Features/Devices/ValveController/ValveControllerWidgetView.razor @@ -1,45 +1,44 @@ -@using UI.Backend.ViewModels.Devices.ValveController -@inherits ReactiveUI.Blazor.ReactiveComponentBase -@inject TooltipService tooltipService - - - - Valve Controller - - - - - - - - - - - - - - - - - - - - - - - ViewModel!.DisengageRelayOne()) Icon="lock_open" MouseEnter="@(args => ShowDisengageValveOneTip(args) )" Size="ButtonSize.Large"/> - - - - - - - - - -@code { - void ShowEngageValveOneTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Engage Valve One", options); - void ShowEngageValveTwoTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Engage Valve Two", options); - void ShowDisengageValveOneTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Disengage Valve One", options); - void ShowDisengageValveTwoTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Disengage Valve Two", options); -} +@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inject TooltipService tooltipService + + + + Valve Controller + + + + + + + + + + + + + + + + + + + + + + + ViewModel!.DisengageRelayOne()) Icon="lock_open" MouseEnter="@(args => ShowDisengageValveOneTip(args) )" Size="ButtonSize.Large"/> + + + + + + + + + +@code { + void ShowEngageValveOneTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Engage Valve One", options); + void ShowEngageValveTwoTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Engage Valve Two", options); + void ShowDisengageValveOneTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Disengage Valve One", options); + void ShowDisengageValveTwoTip(ElementReference elementReference, TooltipOptions? options = null) => tooltipService.Open(elementReference, "Disengage Valve Two", options); +} diff --git a/UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserConfigEditView.razor b/UI/Features/Devices/VerdiV6Laser/VerdiLaserConfigEditView.razor similarity index 90% rename from UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserConfigEditView.razor rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserConfigEditView.razor index 42812621..68394ae4 100644 --- a/UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserConfigEditView.razor +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserConfigEditViewModel.cs b/UI/Features/Devices/VerdiV6Laser/VerdiLaserConfigEditViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserConfigEditViewModel.cs rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserConfigEditViewModel.cs index 733d3703..c7750ff1 100644 --- a/UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserConfigEditViewModel.cs +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserConfigEditViewModel.cs @@ -1,12 +1,12 @@ -using Google.Protobuf.WellKnownTypes; +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; using ReactiveUI; using ReactiveUI.SourceGenerators; using System.ComponentModel.DataAnnotations; -using Ares.Services.Device; using VerdiV6.Config; using VerdiV6.Services; -namespace UI.Backend.ViewModels.Settings.Device.VerdiLaser +namespace UI.Features.Devices.VerdiV6Laser { public partial class VerdiLaserConfigEditViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Devices/VerdiV6Laser/VerdiLaserControlWidgetView.razor b/UI/Features/Devices/VerdiV6Laser/VerdiLaserControlWidgetView.razor similarity index 97% rename from UI/Pages/Shared/Devices/VerdiV6Laser/VerdiLaserControlWidgetView.razor rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserControlWidgetView.razor index ccb5463d..8415063a 100644 --- a/UI/Pages/Shared/Devices/VerdiV6Laser/VerdiLaserControlWidgetView.razor +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserControlWidgetView.razor @@ -1,4 +1,4 @@ -@using UI.Backend.ViewModels.Devices.VerdiLaser +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveComponentBase @inject TooltipService tooltipService @inject IUiNotificationService notificationService @@ -39,3 +39,5 @@ } } } + + diff --git a/UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserSettingsList.razor b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsList.razor similarity index 96% rename from UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserSettingsList.razor rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsList.razor index 44cb7366..e91b7eab 100644 --- a/UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserSettingsList.razor +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsList.razor @@ -1,6 +1,6 @@ @page "/settings/device/verdilaser" @layout DeviceSettingsLayout -@using UI.Backend.ViewModels.Settings.Device.VerdiLaser +@using UI.Application.Notifications @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService DialogService @@ -61,4 +61,5 @@ else } private bool SavingInProgress { get; set; } -} \ No newline at end of file +} + diff --git a/UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserSettingsListViewModel.cs b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsListViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserSettingsListViewModel.cs rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsListViewModel.cs index d45a06bb..74f8f0cc 100644 --- a/UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserSettingsListViewModel.cs +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsListViewModel.cs @@ -7,7 +7,7 @@ using VerdiV6.Services; using VerdiV6Laser; -namespace UI.Backend.ViewModels.Settings.Device.VerdiLaser +namespace UI.Features.Devices.VerdiV6Laser { public partial class VerdiLaserSettingsListViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserSettingsView.razor b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsView.razor similarity index 90% rename from UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserSettingsView.razor rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsView.razor index f4014fbe..d0cf2426 100644 --- a/UI/Pages/Shared/Settings/Device/VerdiV6Laser/VerdiLaserSettingsView.razor +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsView.razor @@ -1,4 +1,5 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Application.Notifications +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService @inject IUiNotificationService NotificationService @@ -39,3 +40,5 @@ } } + + diff --git a/UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserSettingsViewModel.cs b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserSettingsViewModel.cs rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsViewModel.cs index d7e015fe..5e870824 100644 --- a/UI/Backend/ViewModels/Settings/Device/VerdiLaser/VerdiLaserSettingsViewModel.cs +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserSettingsViewModel.cs @@ -3,11 +3,12 @@ using CommunityToolkit.Mvvm.Messaging; using Grpc.Core; using ReactiveUI; -using UI.Backend.Devices; +using UI.Application.Devices; +using UI.Features.Devices.Shared; using VerdiV6.Config; using VerdiV6.Services; -namespace UI.Backend.ViewModels.Settings.Device.VerdiLaser +namespace UI.Features.Devices.VerdiV6Laser { public class VerdiLaserSettingsViewModel : ReactiveObject { @@ -51,10 +52,10 @@ public Task GetDeviceOperationalStatus() public async Task Save() { var laserConfig = EditViewModel.Save(); - var updateRequest = new LaserUpdateRequest - { - Id = _deviceConfig.UniqueId, - Config = laserConfig + var updateRequest = new LaserUpdateRequest + { + Id = _deviceConfig.UniqueId, + Config = laserConfig }; await _laserClient.UpdateLaserAsync(updateRequest); } diff --git a/UI/Backend/ViewModels/Devices/VerdiLaser/VerdiLaserUnitControlViewModel.cs b/UI/Features/Devices/VerdiV6Laser/VerdiLaserUnitControlViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Devices/VerdiLaser/VerdiLaserUnitControlViewModel.cs rename to UI/Features/Devices/VerdiV6Laser/VerdiLaserUnitControlViewModel.cs index 612c87bf..833b69c4 100644 --- a/UI/Backend/ViewModels/Devices/VerdiLaser/VerdiLaserUnitControlViewModel.cs +++ b/UI/Features/Devices/VerdiV6Laser/VerdiLaserUnitControlViewModel.cs @@ -1,9 +1,9 @@ -using DynamicData.Binding; +using DynamicData.Binding; using ReactiveUI.SourceGenerators; -using UI.Pages.Shared.Devices.VerdiV6Laser; +using UI.Application.Devices; using VerdiV6.Services; -namespace UI.Backend.ViewModels.Devices.VerdiLaser; +namespace UI.Features.Devices.VerdiV6Laser; public partial class VerdiLaserUnitControlViewModel : DeviceUnitControlViewModel { diff --git a/UI/Features/Devices/_Imports.razor b/UI/Features/Devices/_Imports.razor new file mode 100644 index 00000000..40ca0e3e --- /dev/null +++ b/UI/Features/Devices/_Imports.razor @@ -0,0 +1,2 @@ +@using UI.Features.Devices.Shared +@using UI.Features.Devices.Shared.Settings diff --git a/UI/Pages/Automation/Execution.razor b/UI/Features/Execution/Execution.razor similarity index 95% rename from UI/Pages/Automation/Execution.razor rename to UI/Features/Execution/Execution.razor index 83128bc9..20679b1c 100644 --- a/UI/Pages/Automation/Execution.razor +++ b/UI/Features/Execution/Execution.razor @@ -1,440 +1,440 @@ -@page "/automation/execution" -@using System.Collections.ObjectModel -@using Ares.Datamodel -@using Ares.Services -@using Google.Protobuf.WellKnownTypes -@using Radzen.Blazor -@using System.Threading.Tasks -@using UI.Backend.Extensions -@using UI.Pages.Shared.CampaignEdit -@using UI.Pages.Shared.Execution.Planning -@inject ContextMenuService ContextMenuService -@inject AresAutomation.AresAutomationClient AutomationClient -@inject IUiNotificationService NotificationService -@inject Radzen.DialogService DialogService -@implements IDisposable -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase - -Execution - -
-
-
-
- -
- -
-
- @if (ViewModel!.PlannerAdapterInfos.Any(p => p is not null)) - { -
-
Planners
- @foreach (var planner in ViewModel!.PlannerAdapterInfos.Where(info => info is not null)) - { - @if(planner!.Name == "Manual Planner") - { - - } - - else - { -
-

@planner!.Name

- @if(ViewModel!.PlannerState == PlannerState.PlanningComplete) - { - - } - else if(ViewModel!.PlannerState == PlannerState.PlanningInProgress) - { - - } - else if(ViewModel!.PlannerState == PlannerState.PlanningError) - { - - } -
- } - } -
- } - - @if(ViewModel!.AnalyzerInfo is not null && ViewModel!.AnalyzerInfo.Name != "NONE") - { -
-
Analyzer
-
-

@ViewModel!.AnalyzerInfo.Name

- - @if(ViewModel!.AnalysisState == AnalysisState.AnalysisComplete) - { - - } - else if(ViewModel!.AnalysisState == AnalysisState.AnalysisInProgress) - { - - } - else if(ViewModel!.AnalysisState == AnalysisState.AnalysisError) - { - - } -
-
- } -
-
- -
-
-
- - -
-
- - - @if (ViewModel!.DisplayExecutionSummary && (CampaignStartupStatuses.Any() || ExperimentExecutionStatuses.Any())) - { -
- @if(ExperimentExecutionStatuses.Any()) - { - @for(var i = 0; i < ExperimentExecutionStatuses.Count; i++) - { - var experimentStatus = ExperimentExecutionStatuses[i]; - var latestExperiment = i == ExperimentExecutionStatuses.Count - 1; - var hasStartup = ViewModel!.CampaignTemplate?.StartupTemplate.StepTemplates.Count != 0; - var hasCloseout = ViewModel!.CampaignTemplate?.CloseoutTemplate.StepTemplates.Count != 0; - var expDisplayString = hasStartup ? $"Experiment {i}" : $"Experiment {i + 1}"; -
    -
  • - @if(i == 0 && hasStartup) - { -
    Startup Script
    - } - - else if(hasStartup && hasCloseout && i == ViewModel!.ExperimentsToRun + 1) - { -
    Closeout Script
    - } - - else if(!hasStartup && hasCloseout && i == ViewModel!.ExperimentsToRun) - { -
    Closeout Script
    - } - - else - { -
    @expDisplayString
    - } - -
      - @foreach(var stepStatus in experimentStatus.StepExecutionStatuses) - { -
    • -
      - @stepStatus.StepName -
        - @foreach(var commandStatus in stepStatus.CommandExecutionStatuses) - { -
      • -
        @commandStatus.DeviceName: @commandStatus.CommandName -- @commandStatus.State
        -
      • - } -
      -
      -
    • - } -
    -
  • -
- } - } -
- } - - - @if (!ViewModel!.DisplayExecutionSummary) - { - @if (ViewModel!.CampaignTemplate is not null) - { -
- -
- } - } - -
-
-
-
- - - - -
- - @if (ViewModel!.CurrentStopCondition is not null) - { -
-
-
- Condition: @GenerateStopConditionDisplayName() -
-
@ViewModel!.CurrentStopCondition.Description
-
Campaign will re-plan every @ViewModel!.DesiredReplanRate.ToString() experiment(s)
-
-
- } - -
-
Desired number of experiments
- - -
- -
-
Re-Planning Rate
- - -
- -
-
Desired experiment result
-
- - - -
- -
- -
-
Experiment Notes
-
- -
- -
- -
-
Experiment Tags
- - - @((context.TagName as string)) - - - - - @* --- Section for Adding New Tags --- *@ -
- - - -
-
-
- -
- -
- - - User Confirmation Required - - - ARES requires user approval to continue experimenting. Continue by hitting "Confirm" or play. You can also stop your experiment by hitting the stop button. - - - - - - - -
-
-
-
- -@code { - List campaigns = new(); - Task? _experimentStatusWatcher; - Task? _campaignStateWatcher; - CancellationTokenSource? _experimentStatusWatcherCancellationTokenSource; - CancellationTokenSource? _campaignStateWatcherCancellationTokenSource; - ObservableCollection ExperimentExecutionStatuses { get; } = new ObservableCollection(); - ObservableCollection CampaignStartupStatuses { get; } = new ObservableCollection(); - ObservableCollection CampaignCloseoutStatuses { get; } = new ObservableCollection(); - - private async Task CampaignSelected() - { - await ViewModel!.UpdateCurrentTemplate(); - } - - protected override async Task OnInitializedAsync() - { - await ViewModel!.RefreshCampaigns(); - await ViewModel!.SelectCampaignTemplate(ViewModel!.CampaignTemplate); - await ViewModel!.UpdateCurrentTemplate(); - await ViewModel!.GetAllTags(); - campaigns = ViewModel!.CampaignTemplateSummaries.ToList(); - - var currentStatus = await ViewModel!.GetCampaignExecutionStatus(); - if (currentStatus is not null) - { - foreach (var experimentStatus in currentStatus.ExperimentExecutionStatuses) - { - ExperimentExecutionStatuses.Add(experimentStatus); - } - } - _experimentStatusWatcherCancellationTokenSource = new CancellationTokenSource(); - _experimentStatusWatcher = SetupExperimentWatch(_experimentStatusWatcherCancellationTokenSource.Token); - - _campaignStateWatcherCancellationTokenSource = new CancellationTokenSource(); - _campaignStateWatcher = SetupCampaignStateWatch(_campaignStateWatcherCancellationTokenSource.Token); - } - - protected override void Dispose(bool disposing) - { - _experimentStatusWatcherCancellationTokenSource?.Cancel(); - _campaignStateWatcherCancellationTokenSource?.Cancel(); - base.Dispose(disposing); - } - - private bool ShouldDisplayExperimentSteps(int currentStatusIndex) - { - var isCurrentExperiment = currentStatusIndex == ExperimentExecutionStatuses.Count - 1; - - if (!CampaignCloseoutStatuses.Any()) - return isCurrentExperiment; - - return CampaignCloseoutStatuses.Count != CampaignStartupStatuses.Count && currentStatusIndex == ExperimentExecutionStatuses.Count - 1; - } - - private string GenerateStopConditionDisplayName() - { - if (ViewModel!.CurrentStopCondition is null) - return ""; - - if (ViewModel!.CurrentStopCondition.ActiveCondition == "NumExperimentsRun") - return "Number of Experiments"; - - else if (ViewModel!.CurrentStopCondition.ActiveCondition == "DesiredAnalysisResult") - return "Desired Analysis Result"; - - else - return ""; - } - - protected Task SetupCampaignStateWatch(CancellationToken token) - { - return Task.Run(async () => - { - Thread.CurrentThread.Name = "Campaign State Watch Thread"; - var stream = AutomationClient.GetCampaignExecutionStateStream(new Empty()); - while (await stream.ResponseStream.MoveNext(token) && !token.IsCancellationRequested) - { - var state = stream.ResponseStream.Current; - - ViewModel!.CampaignActive = state.IsActive(); - ViewModel!.CampaignPaused = state.IsPaused(); - ViewModel!.CampaignExecutionState = state.State; - ViewModel!.AnalysisState = state.AnalysisState; - ViewModel!.PlannerState = state.PlannerState; - - if(ViewModel!.CampaignExecutionState == ExecutionState.AwaitingUser) - { - await ViewModel!.ReqeustUserConfirmation(); - } - - - await InvokeAsync(() => StateHasChanged()); - } - }, token); - } - - protected Task SetupExperimentWatch(CancellationToken token) - { - return Task.Run(async () => - { - Thread.CurrentThread.Name = "Experiment State Watch Thread"; - var stream = AutomationClient.GetExecutionStatusStream(new Empty()); - while (await stream.ResponseStream.MoveNext(token) && !token.IsCancellationRequested) - { - var status = stream.ResponseStream.Current; - var existingStatus = ExperimentExecutionStatuses.FirstOrDefault(s => s.ExperimentId == status.ExperimentId); - if (existingStatus is null) - { - ExperimentExecutionStatuses.Add(status); - existingStatus = status; - continue; - } - - var incomingCommands = status.GetCommandExecutionStatuses(); - var existingCommands = existingStatus.GetCommandExecutionStatuses(); - - foreach (var existingCommand in existingCommands) - { - var newCommand = incomingCommands.FirstOrDefault(c => c.CommandId == existingCommand.CommandId); - existingCommand.State = newCommand?.State ?? ExecutionState.Undefined; - } - - await InvokeAsync(() => StateHasChanged()); - } - }, token); - } - - protected async void StartCampaign() - { - var executionEligibility = await AutomationClient.CheckExecutionEligibilityAsync(new Empty()); - - if (!executionEligibility.IsEligible) - { - NotificationService.Error(executionEligibility.Error); - return; - } - - ExperimentExecutionStatuses.Clear(); - var request = new StartCampaignRequest() { UserNotes = ViewModel!.ExecutionNotes}; - if(ViewModel!.SelectedTags is not null) - request.CampaignTags.AddRange(ViewModel!.SelectedTags); - AutomationClient.StartExecution(request); - ViewModel!.DisplayExecutionSummary = true; - } - - System.Type? GetTypeFromString(string? type) - { - return type is null ? null : System.Type.GetType($"UI.Pages.Shared.Execution.Planning.{type}"); - } - - private async Task ExecutionNotesUploaded(UploadChangeEventArgs args) - { - FileInfo? file = args.Files?.FirstOrDefault(); - if(file is null) - return; - - const long maxFileSize = 100; - using var stream = file.OpenReadStream(maxFileSize); - await ViewModel!.ExecutionNotesUploaded(stream); - } -} - - - +@page "/automation/execution" +@using System.Collections.ObjectModel +@using Ares.Datamodel +@using Ares.Services +@using Google.Protobuf.WellKnownTypes +@using UI.Domain.Execution +@using UI.Application.Notifications +@using UI.Features.Execution.Planning +@inject ContextMenuService ContextMenuService +@inject AresAutomation.AresAutomationClient AutomationClient +@inject IUiNotificationService NotificationService +@inject Radzen.DialogService DialogService +@implements IDisposable +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase + +Execution + +
+
+
+
+ +
+ +
+
+ @if (ViewModel!.PlannerAdapterInfos.Any(p => p is not null)) + { +
+
Planners
+ @foreach (var planner in ViewModel!.PlannerAdapterInfos.Where(info => info is not null)) + { + @if(planner!.Name == "Manual Planner") + { + + } + + else + { +
+

@planner!.Name

+ @if(ViewModel!.PlannerState == PlannerState.PlanningComplete) + { + + } + else if(ViewModel!.PlannerState == PlannerState.PlanningInProgress) + { + + } + else if(ViewModel!.PlannerState == PlannerState.PlanningError) + { + + } +
+ } + } +
+ } + + @if(ViewModel!.AnalyzerInfo is not null && ViewModel!.AnalyzerInfo.Name != "NONE") + { +
+
Analyzer
+
+

@ViewModel!.AnalyzerInfo.Name

+ + @if(ViewModel!.AnalysisState == AnalysisState.AnalysisComplete) + { + + } + else if(ViewModel!.AnalysisState == AnalysisState.AnalysisInProgress) + { + + } + else if(ViewModel!.AnalysisState == AnalysisState.AnalysisError) + { + + } +
+
+ } +
+
+ +
+
+
+ + +
+
+ + + @if (ViewModel!.DisplayExecutionSummary && (CampaignStartupStatuses.Any() || ExperimentExecutionStatuses.Any())) + { +
+ @if(ExperimentExecutionStatuses.Any()) + { + @for(var i = 0; i < ExperimentExecutionStatuses.Count; i++) + { + var experimentStatus = ExperimentExecutionStatuses[i]; + var latestExperiment = i == ExperimentExecutionStatuses.Count - 1; + var hasStartup = ViewModel!.CampaignTemplate?.StartupTemplate.StepTemplates.Count != 0; + var hasCloseout = ViewModel!.CampaignTemplate?.CloseoutTemplate.StepTemplates.Count != 0; + var expDisplayString = hasStartup ? $"Experiment {i}" : $"Experiment {i + 1}"; +
    +
  • + @if(i == 0 && hasStartup) + { +
    Startup Script
    + } + + else if(hasStartup && hasCloseout && i == ViewModel!.ExperimentsToRun + 1) + { +
    Closeout Script
    + } + + else if(!hasStartup && hasCloseout && i == ViewModel!.ExperimentsToRun) + { +
    Closeout Script
    + } + + else + { +
    @expDisplayString
    + } + +
      + @foreach(var stepStatus in experimentStatus.StepExecutionStatuses) + { +
    • +
      + @stepStatus.StepName +
        + @foreach(var commandStatus in stepStatus.CommandExecutionStatuses) + { +
      • +
        @commandStatus.DeviceName: @commandStatus.CommandName -- @commandStatus.State
        +
      • + } +
      +
      +
    • + } +
    +
  • +
+ } + } +
+ } + + + @if (!ViewModel!.DisplayExecutionSummary) + { + @if (ViewModel!.CampaignTemplate is not null) + { +
+ +
+ } + } + +
+
+
+
+ + + + +
+ + @if (ViewModel!.CurrentStopCondition is not null) + { +
+
+
+ Condition: @GenerateStopConditionDisplayName() +
+
@ViewModel!.CurrentStopCondition.Description
+
Campaign will re-plan every @ViewModel!.DesiredReplanRate.ToString() experiment(s)
+
+
+ } + +
+
Desired number of experiments
+ + +
+ +
+
Re-Planning Rate
+ + +
+ +
+
Desired experiment result
+
+ + + +
+ +
+ +
+
Experiment Notes
+
+ +
+ +
+ +
+
Experiment Tags
+ + + @((context.TagName as string)) + + + + + @* --- Section for Adding New Tags --- *@ +
+ + + +
+
+
+ +
+ +
+ + + User Confirmation Required + + + ARES requires user approval to continue experimenting. Continue by hitting "Confirm" or play. You can also stop your experiment by hitting the stop button. + + + + + + + +
+
+
+
+ +@code { + List campaigns = new(); + Task? _experimentStatusWatcher; + Task? _campaignStateWatcher; + CancellationTokenSource? _experimentStatusWatcherCancellationTokenSource; + CancellationTokenSource? _campaignStateWatcherCancellationTokenSource; + ObservableCollection ExperimentExecutionStatuses { get; } = new ObservableCollection(); + ObservableCollection CampaignStartupStatuses { get; } = new ObservableCollection(); + ObservableCollection CampaignCloseoutStatuses { get; } = new ObservableCollection(); + + private async Task CampaignSelected() + { + await ViewModel!.UpdateCurrentTemplate(); + } + + protected override async Task OnInitializedAsync() + { + await ViewModel!.RefreshCampaigns(); + await ViewModel!.SelectCampaignTemplate(ViewModel!.CampaignTemplate); + await ViewModel!.UpdateCurrentTemplate(); + await ViewModel!.GetAllTags(); + campaigns = ViewModel!.CampaignTemplateSummaries.ToList(); + + var currentStatus = await ViewModel!.GetCampaignExecutionStatus(); + if (currentStatus is not null) + { + foreach (var experimentStatus in currentStatus.ExperimentExecutionStatuses) + { + ExperimentExecutionStatuses.Add(experimentStatus); + } + } + _experimentStatusWatcherCancellationTokenSource = new CancellationTokenSource(); + _experimentStatusWatcher = SetupExperimentWatch(_experimentStatusWatcherCancellationTokenSource.Token); + + _campaignStateWatcherCancellationTokenSource = new CancellationTokenSource(); + _campaignStateWatcher = SetupCampaignStateWatch(_campaignStateWatcherCancellationTokenSource.Token); + } + + protected override void Dispose(bool disposing) + { + _experimentStatusWatcherCancellationTokenSource?.Cancel(); + _campaignStateWatcherCancellationTokenSource?.Cancel(); + base.Dispose(disposing); + } + + private bool ShouldDisplayExperimentSteps(int currentStatusIndex) + { + var isCurrentExperiment = currentStatusIndex == ExperimentExecutionStatuses.Count - 1; + + if (!CampaignCloseoutStatuses.Any()) + return isCurrentExperiment; + + return CampaignCloseoutStatuses.Count != CampaignStartupStatuses.Count && currentStatusIndex == ExperimentExecutionStatuses.Count - 1; + } + + private string GenerateStopConditionDisplayName() + { + if (ViewModel!.CurrentStopCondition is null) + return ""; + + if (ViewModel!.CurrentStopCondition.ActiveCondition == "NumExperimentsRun") + return "Number of Experiments"; + + else if (ViewModel!.CurrentStopCondition.ActiveCondition == "DesiredAnalysisResult") + return "Desired Analysis Result"; + + else + return ""; + } + + protected Task SetupCampaignStateWatch(CancellationToken token) + { + return Task.Run(async () => + { + Thread.CurrentThread.Name = "Campaign State Watch Thread"; + var stream = AutomationClient.GetCampaignExecutionStateStream(new Empty()); + while (await stream.ResponseStream.MoveNext(token) && !token.IsCancellationRequested) + { + var state = stream.ResponseStream.Current; + + ViewModel!.CampaignActive = state.IsActive(); + ViewModel!.CampaignPaused = state.IsPaused(); + ViewModel!.CampaignExecutionState = state.State; + ViewModel!.AnalysisState = state.AnalysisState; + ViewModel!.PlannerState = state.PlannerState; + + if(ViewModel!.CampaignExecutionState == ExecutionState.AwaitingUser) + { + await ViewModel!.ReqeustUserConfirmation(); + } + + + await InvokeAsync(() => StateHasChanged()); + } + }, token); + } + + protected Task SetupExperimentWatch(CancellationToken token) + { + return Task.Run(async () => + { + Thread.CurrentThread.Name = "Experiment State Watch Thread"; + var stream = AutomationClient.GetExecutionStatusStream(new Empty()); + while (await stream.ResponseStream.MoveNext(token) && !token.IsCancellationRequested) + { + var status = stream.ResponseStream.Current; + var existingStatus = ExperimentExecutionStatuses.FirstOrDefault(s => s.ExperimentId == status.ExperimentId); + if (existingStatus is null) + { + ExperimentExecutionStatuses.Add(status); + existingStatus = status; + continue; + } + + var incomingCommands = status.GetCommandExecutionStatuses(); + var existingCommands = existingStatus.GetCommandExecutionStatuses(); + + foreach (var existingCommand in existingCommands) + { + var newCommand = incomingCommands.FirstOrDefault(c => c.CommandId == existingCommand.CommandId); + existingCommand.State = newCommand?.State ?? ExecutionState.Undefined; + } + + await InvokeAsync(() => StateHasChanged()); + } + }, token); + } + + protected async void StartCampaign() + { + var executionEligibility = await AutomationClient.CheckExecutionEligibilityAsync(new Empty()); + + if (!executionEligibility.IsEligible) + { + NotificationService.Error(executionEligibility.Error); + return; + } + + ExperimentExecutionStatuses.Clear(); + var request = new StartCampaignRequest() { UserNotes = ViewModel!.ExecutionNotes}; + if(ViewModel!.SelectedTags is not null) + request.CampaignTags.AddRange(ViewModel!.SelectedTags); + AutomationClient.StartExecution(request); + ViewModel!.DisplayExecutionSummary = true; + } + + System.Type? GetTypeFromString(string? type) + { + return type is null ? null : System.Type.GetType($"UI.Features.Execution.Planning.{type}"); + } + + private async Task ExecutionNotesUploaded(UploadChangeEventArgs args) + { + FileInfo? file = args.Files?.FirstOrDefault(); + if(file is null) + return; + + const long maxFileSize = 100; + using var stream = file.OpenReadStream(maxFileSize); + await ViewModel!.ExecutionNotesUploaded(stream); + } +} + + + + + diff --git a/UI/Pages/Automation/Execution.razor.css b/UI/Features/Execution/Execution.razor.css similarity index 96% rename from UI/Pages/Automation/Execution.razor.css rename to UI/Features/Execution/Execution.razor.css index 178c2666..be6b2f86 100644 --- a/UI/Pages/Automation/Execution.razor.css +++ b/UI/Features/Execution/Execution.razor.css @@ -1,70 +1,70 @@ -.execution-button { - max-width: 50px; -} - -.analyzer-display { - width: 50%; -} - -.planner-display { - width: 50%; -} - -.padded-item { - padding-top: 0.5rem; -} - -.scrollable-summary { - max-height: 75vh; - overflow: auto; -} - -/* 1. The Container that holds Main Content and Drawer side-by-side */ -.drawer-layout-container { - display: flex; /* This enables the side-by-side layout */ - flex-direction: row; - width: 100%; - height: 100vh; /* Full viewport height */ - overflow: hidden; /* Prevents scrollbar on the container itself */ -} - -/* 2. Main Content Area (The part that shrinks) */ -.main-content-area { - flex-grow: 1; /* Take all available space */ - transition: width 0.3s ease; /* Smooth "shove" animation */ - overflow-y: auto; /* Scroll vertically INSIDE this area */ - min-width: 0; /* Prevents flex items from breaking */ - padding: 1rem; -} - -/* 3. The Side Drawer Wrapper (Animated Width) */ -.side-drawer { - width: 0; /* Default state: Closed (Invisible) */ - flex-shrink: 0; /* Prevent it from being squished */ - transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* Smooth animation */ - overflow-x: hidden; /* Hide content when width is 0 */ - background-color: var(--rz-base-background-color); /* Match Radzen theme */ - border-left: 1px solid var(--rz-border-color); /* Match Radzen border */ - padding: 0rem; - border-radius: .25rem; - height: 100vh; -} - - /* The 'Open' state - toggled via Blazor logic */ - .side-drawer.drawer-open { - width: 400px; /* Target width when open */ - } - -/* 4. The Content Wrapper (Fixed Width) */ -/* Critical: Prevents text reflow/wrapping during the slide animation */ -.drawer-content { - width: 400px; /* Must match the open width */ - height: 100%; - overflow-y: auto; /* Allow scrolling inside the drawer */ -} - -.drawer-inner-card { - height: inherit; - border: none; - border-radius: 0; +.execution-button { + max-width: 50px; +} + +.analyzer-display { + width: 50%; +} + +.planner-display { + width: 50%; +} + +.padded-item { + padding-top: 0.5rem; +} + +.scrollable-summary { + max-height: 75vh; + overflow: auto; +} + +/* 1. The Container that holds Main Content and Drawer side-by-side */ +.drawer-layout-container { + display: flex; /* This enables the side-by-side layout */ + flex-direction: row; + width: 100%; + height: 100vh; /* Full viewport height */ + overflow: hidden; /* Prevents scrollbar on the container itself */ +} + +/* 2. Main Content Area (The part that shrinks) */ +.main-content-area { + flex-grow: 1; /* Take all available space */ + transition: width 0.3s ease; /* Smooth "shove" animation */ + overflow-y: auto; /* Scroll vertically INSIDE this area */ + min-width: 0; /* Prevents flex items from breaking */ + padding: 1rem; +} + +/* 3. The Side Drawer Wrapper (Animated Width) */ +.side-drawer { + width: 0; /* Default state: Closed (Invisible) */ + flex-shrink: 0; /* Prevent it from being squished */ + transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* Smooth animation */ + overflow-x: hidden; /* Hide content when width is 0 */ + background-color: var(--rz-base-background-color); /* Match Radzen theme */ + border-left: 1px solid var(--rz-border-color); /* Match Radzen border */ + padding: 0rem; + border-radius: .25rem; + height: 100vh; +} + + /* The 'Open' state - toggled via Blazor logic */ + .side-drawer.drawer-open { + width: 400px; /* Target width when open */ + } + +/* 4. The Content Wrapper (Fixed Width) */ +/* Critical: Prevents text reflow/wrapping during the slide animation */ +.drawer-content { + width: 400px; /* Must match the open width */ + height: 100%; + overflow-y: auto; /* Allow scrolling inside the drawer */ +} + +.drawer-inner-card { + height: inherit; + border: none; + border-radius: 0; } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Automation/ExecutionViewModel.cs b/UI/Features/Execution/ExecutionViewModel.cs similarity index 98% rename from UI/Backend/ViewModels/Automation/ExecutionViewModel.cs rename to UI/Features/Execution/ExecutionViewModel.cs index 99b8b059..cf53bc2a 100644 --- a/UI/Backend/ViewModels/Automation/ExecutionViewModel.cs +++ b/UI/Features/Execution/ExecutionViewModel.cs @@ -1,19 +1,18 @@ +using Ares.Datamodel; +using Ares.Datamodel.Analyzing; +using Ares.Datamodel.Planning; +using Ares.Datamodel.Templates; +using Ares.Services; using DynamicData; using Google.Protobuf.WellKnownTypes; using ReactiveUI; using ReactiveUI.SourceGenerators; using System.Collections.ObjectModel; -using Ares.Datamodel; -using Ares.Datamodel.Analyzing; -using Ares.Datamodel.Templates; -using Ares.Services; -using UI.Backend.Extensions; -using UI.Services.Notification; -using Ares.Datamodel.Planning; using System.ComponentModel; -using System.Reactive.Linq; +using UI.Application.Notifications; +using UI.Domain.Experiments; -namespace UI.Backend.ViewModels.Automation; +namespace UI.Features.Execution; public partial class ExecutionViewModel : ReactiveObject, INotifyPropertyChanged { @@ -256,3 +255,5 @@ public async Task GetAllTags() public List SelectedTags { get; set; } = []; public string? NewTagName { get; set; } } + + diff --git a/UI/Pages/Shared/Execution/Planning/AresDemoPlanner.razor b/UI/Features/Execution/Planning/AresDemoPlanner.razor similarity index 88% rename from UI/Pages/Shared/Execution/Planning/AresDemoPlanner.razor rename to UI/Features/Execution/Planning/AresDemoPlanner.razor index 05539166..bbf014a2 100644 --- a/UI/Pages/Shared/Execution/Planning/AresDemoPlanner.razor +++ b/UI/Features/Execution/Planning/AresDemoPlanner.razor @@ -1,5 +1,5 @@ -

Demo Planner

- -@code { - -} +

Demo Planner

+ +@code { + +} diff --git a/UI/Pages/Shared/Execution/Planning/BoraasPlanner.razor b/UI/Features/Execution/Planning/BoraasPlanner.razor similarity index 100% rename from UI/Pages/Shared/Execution/Planning/BoraasPlanner.razor rename to UI/Features/Execution/Planning/BoraasPlanner.razor diff --git a/UI/Pages/Shared/Execution/Planning/HolmesPlanner.razor b/UI/Features/Execution/Planning/HolmesPlanner.razor similarity index 100% rename from UI/Pages/Shared/Execution/Planning/HolmesPlanner.razor rename to UI/Features/Execution/Planning/HolmesPlanner.razor diff --git a/UI/Pages/Shared/Execution/Planning/ManualPlanEditor.razor b/UI/Features/Execution/Planning/ManualPlanEditor.razor similarity index 91% rename from UI/Pages/Shared/Execution/Planning/ManualPlanEditor.razor rename to UI/Features/Execution/Planning/ManualPlanEditor.razor index f67107d4..414899cf 100644 --- a/UI/Pages/Shared/Execution/Planning/ManualPlanEditor.razor +++ b/UI/Features/Execution/Planning/ManualPlanEditor.razor @@ -1,6 +1,5 @@ @using Ares.Datamodel.Extensions -@using UI.Backend.ViewModels.Automation.Planning -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject DialogService dialogService diff --git a/UI/Pages/Shared/Execution/Planning/ManualPlanner.razor b/UI/Features/Execution/Planning/ManualPlanner.razor similarity index 89% rename from UI/Pages/Shared/Execution/Planning/ManualPlanner.razor rename to UI/Features/Execution/Planning/ManualPlanner.razor index 39144f2a..8317b9c4 100644 --- a/UI/Pages/Shared/Execution/Planning/ManualPlanner.razor +++ b/UI/Features/Execution/Planning/ManualPlanner.razor @@ -1,37 +1,37 @@ -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase -@inject DialogService dialogService - - - - - - - - - -@code { - [Parameter] - public string Name { get; set; } = "Manual Planner"; - - [Parameter] - public string Type { get; set; } = "ManualPlanner"; - - [Parameter] - public Version Version { get; set; } = new (1, 0); - - private async Task EditManualPlanClick() - { - await dialogService.OpenAsync("Edit Manual Plan"); - } - - private async Task DisplayManualPlannerHelpTab() - { - await dialogService.OpenAsync("The Manual Planner", ds => - @
- ARES provides a manual planner to give the user a way to feed pre-determined values into an experiment manually. - The manual planner accepts both strings and numbers as input. Any entries surrounded by quotation marks (e.g. "value") will be parsed as a string, - otherwise ARES will attempt to parse the entry as a number. -
- , new DialogOptions() { CloseDialogOnOverlayClick = true }); - } -} +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase +@inject DialogService dialogService + + + + + + + + + +@code { + [Parameter] + public string Name { get; set; } = "Manual Planner"; + + [Parameter] + public string Type { get; set; } = "ManualPlanner"; + + [Parameter] + public Version Version { get; set; } = new (1, 0); + + private async Task EditManualPlanClick() + { + await dialogService.OpenAsync("Edit Manual Plan"); + } + + private async Task DisplayManualPlannerHelpTab() + { + await dialogService.OpenAsync("The Manual Planner", ds => + @
+ ARES provides a manual planner to give the user a way to feed pre-determined values into an experiment manually. + The manual planner accepts both strings and numbers as input. Any entries surrounded by quotation marks (e.g. "value") will be parsed as a string, + otherwise ARES will attempt to parse the entry as a number. +
+ , new DialogOptions() { CloseDialogOnOverlayClick = true }); + } +} diff --git a/UI/Backend/ViewModels/Automation/Planning/ManualPlannerDisplayObject.cs b/UI/Features/Execution/Planning/ManualPlannerDisplayObject.cs similarity index 79% rename from UI/Backend/ViewModels/Automation/Planning/ManualPlannerDisplayObject.cs rename to UI/Features/Execution/Planning/ManualPlannerDisplayObject.cs index 8401268e..2142829d 100644 --- a/UI/Backend/ViewModels/Automation/Planning/ManualPlannerDisplayObject.cs +++ b/UI/Features/Execution/Planning/ManualPlannerDisplayObject.cs @@ -1,6 +1,6 @@ using Ares.Datamodel.Planning; -namespace UI.Backend.ViewModels.Automation.Planning; +namespace UI.Features.Execution.Planning; public class ManualPlannerDisplayObject { diff --git a/UI/Backend/ViewModels/Automation/Planning/ManualPlannerViewModel.cs b/UI/Features/Execution/Planning/ManualPlannerViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Automation/Planning/ManualPlannerViewModel.cs rename to UI/Features/Execution/Planning/ManualPlannerViewModel.cs index c9725c16..f7f6142d 100644 --- a/UI/Backend/ViewModels/Automation/Planning/ManualPlannerViewModel.cs +++ b/UI/Features/Execution/Planning/ManualPlannerViewModel.cs @@ -1,112 +1,112 @@ -using Ares.Datamodel; -using Ares.Datamodel.Extensions; -using Ares.Datamodel.Planning; -using Ares.Services; -using Google.Protobuf.WellKnownTypes; -using Microsoft.AspNetCore.Components.Forms; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels.Automation.Planning; - -public partial class ManualPlannerViewModel : ReactiveObject -{ - private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _client; - - public ManualPlannerViewModel(AresPlannerManagementService.AresPlannerManagementServiceClient client) - { - _client = client; - ManualPlannerValues = []; - _ = UpdatePlannerValues(); - } - - public async Task UpdatePlannerValues() - { - var collection = await _client.GetManualPlannerSeedAsync(new Empty()); - ManualPlannerValues = collection.PlannedValues; - } - - public async Task FileUploaded(IBrowserFile file) - { - NumberOfPlannedExperiments = 0; - var collection = new ManualPlannerSetCollection(); - var stream = file.OpenReadStream(); - var reader = new StreamReader(stream); - var result = await reader.ReadLineAsync(); - var header = result?.Split(',', StringSplitOptions.TrimEntries); - if(header is null) - return false; - - PlannerValueHeaders = header.ToList(); - - result = await reader.ReadLineAsync(); - while(result is not null) - { - try - { - var splitResult = result.Split(','); - var plannerSet = new ManualPlannerSet(); - plannerSet.ParameterValues.AddRange(splitResult.Select((s, i) => new ParameterNameValuePair { Name = header[i], Value = ParseToAresValue(s)})); - collection.PlannedValues.Add(plannerSet); - NumberOfPlannedExperiments += 1; - } - catch(Exception) - { - return false; - } - - result = await reader.ReadLineAsync(); - } - - if(!collection.PlannedValues.Any()) - return true; - - await _client.SeedManualPlannerAsync(new ManualPlannerSeed { PlannerValues = collection }); - await UpdatePlannerValues(); - - return true; - } - - private AresValue ParseToAresValue(string item) - { - if(string.IsNullOrEmpty(item)) - return AresValueHelper.CreateNull(); - - if(item.StartsWith("\"") && item.EndsWith("\"") && item.Length > 1) - { - return AresValueHelper.CreateString(item); - } - - var parsed = double.TryParse(item, out var value); - - if(!parsed) - return AresValueHelper.CreateNull(); - - return AresValueHelper.CreateNumber(value); - } - - public Task CreateDisplayData() - { - DisplayObjects.Clear(); - var experimentNumber = 1; - foreach(var item in ManualPlannerValues) - { - var displayObject = new ManualPlannerDisplayObject(); - displayObject.ExperimentNumber = $"{experimentNumber}"; - displayObject.Parameters = item; - DisplayObjects.Add(displayObject); - experimentNumber++; - } - - return Task.CompletedTask; - } - - [Reactive] - public partial IEnumerable ManualPlannerValues { get; private set; } - - public List DisplayObjects { get; set; } = []; - - public List PlannerValueHeaders { get; set; } = []; - - public int NumberOfPlannedExperiments { get; set; } = 0; -} +using Ares.Datamodel; +using Ares.Datamodel.Extensions; +using Ares.Datamodel.Planning; +using Ares.Services; +using Google.Protobuf.WellKnownTypes; +using Microsoft.AspNetCore.Components.Forms; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace UI.Features.Execution.Planning; + +public partial class ManualPlannerViewModel : ReactiveObject +{ + private readonly AresPlannerManagementService.AresPlannerManagementServiceClient _client; + + public ManualPlannerViewModel(AresPlannerManagementService.AresPlannerManagementServiceClient client) + { + _client = client; + ManualPlannerValues = []; + _ = UpdatePlannerValues(); + } + + public async Task UpdatePlannerValues() + { + var collection = await _client.GetManualPlannerSeedAsync(new Empty()); + ManualPlannerValues = collection.PlannedValues; + } + + public async Task FileUploaded(IBrowserFile file) + { + NumberOfPlannedExperiments = 0; + var collection = new ManualPlannerSetCollection(); + var stream = file.OpenReadStream(); + var reader = new StreamReader(stream); + var result = await reader.ReadLineAsync(); + var header = result?.Split(',', StringSplitOptions.TrimEntries); + if(header is null) + return false; + + PlannerValueHeaders = header.ToList(); + + result = await reader.ReadLineAsync(); + while(result is not null) + { + try + { + var splitResult = result.Split(','); + var plannerSet = new ManualPlannerSet(); + plannerSet.ParameterValues.AddRange(splitResult.Select((s, i) => new ParameterNameValuePair { Name = header[i], Value = ParseToAresValue(s)})); + collection.PlannedValues.Add(plannerSet); + NumberOfPlannedExperiments += 1; + } + catch(Exception) + { + return false; + } + + result = await reader.ReadLineAsync(); + } + + if(!collection.PlannedValues.Any()) + return true; + + await _client.SeedManualPlannerAsync(new ManualPlannerSeed { PlannerValues = collection }); + await UpdatePlannerValues(); + + return true; + } + + private AresValue ParseToAresValue(string item) + { + if(string.IsNullOrEmpty(item)) + return AresValueHelper.CreateNull(); + + if(item.StartsWith("\"") && item.EndsWith("\"") && item.Length > 1) + { + return AresValueHelper.CreateString(item); + } + + var parsed = double.TryParse(item, out var value); + + if(!parsed) + return AresValueHelper.CreateNull(); + + return AresValueHelper.CreateNumber(value); + } + + public Task CreateDisplayData() + { + DisplayObjects.Clear(); + var experimentNumber = 1; + foreach(var item in ManualPlannerValues) + { + var displayObject = new ManualPlannerDisplayObject(); + displayObject.ExperimentNumber = $"{experimentNumber}"; + displayObject.Parameters = item; + DisplayObjects.Add(displayObject); + experimentNumber++; + } + + return Task.CompletedTask; + } + + [Reactive] + public partial IEnumerable ManualPlannerValues { get; private set; } + + public List DisplayObjects { get; set; } = []; + + public List PlannerValueHeaders { get; set; } = []; + + public int NumberOfPlannedExperiments { get; set; } = 0; +} diff --git a/UI/Pages/Automation/ExecutionHistory.razor b/UI/Features/ExecutionHistory/ExecutionHistory.razor similarity index 89% rename from UI/Pages/Automation/ExecutionHistory.razor rename to UI/Features/ExecutionHistory/ExecutionHistory.razor index b4de0da9..3458aa77 100644 --- a/UI/Pages/Automation/ExecutionHistory.razor +++ b/UI/Features/ExecutionHistory/ExecutionHistory.razor @@ -1,62 +1,62 @@ -@page "/automation/executionhistory" -@using Ares.Services -@using UI.Backend.Helpers -@inject ContextMenuService ContextMenuService -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase - -Execution History - -

Execution History

- - - -@if(ViewModel!.LoadingExecutionHistory) -{ - -} - -else -{ - @if(ViewModel!.CampaignSummaries.Any()) - { - - - - - - - - - - - - - } - - else - { -

No Execution History to Display

- } -} - -
- - -@code { - LogicalFilterOperator logicalFilterOperator = LogicalFilterOperator.And; - FilterCaseSensitivity filterCaseSensitivity = FilterCaseSensitivity.CaseInsensitive; - - protected override async Task OnInitializedAsync() - { - ViewModel!.LoadingExecutionHistory = true; - await ViewModel!.UpdateExecutionSummaries(); - ViewModel!.LoadingExecutionHistory = false; - StateHasChanged(); - } +@page "/automation/executionhistory" +@using Ares.Services +@using UI.Components.Formatting +@inject ContextMenuService ContextMenuService +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase + +Execution History + +

Execution History

+ + + +@if(ViewModel!.LoadingExecutionHistory) +{ + +} + +else +{ + @if(ViewModel!.CampaignSummaries.Any()) + { + + + + + + + + + + + + + } + + else + { +

No Execution History to Display

+ } +} + +
+ + +@code { + LogicalFilterOperator logicalFilterOperator = LogicalFilterOperator.And; + FilterCaseSensitivity filterCaseSensitivity = FilterCaseSensitivity.CaseInsensitive; + + protected override async Task OnInitializedAsync() + { + ViewModel!.LoadingExecutionHistory = true; + await ViewModel!.UpdateExecutionSummaries(); + ViewModel!.LoadingExecutionHistory = false; + StateHasChanged(); + } } \ No newline at end of file diff --git a/UI/Backend/ViewModels/Automation/ExecutionHistoryViewModel.cs b/UI/Features/ExecutionHistory/ExecutionHistoryViewModel.cs similarity index 92% rename from UI/Backend/ViewModels/Automation/ExecutionHistoryViewModel.cs rename to UI/Features/ExecutionHistory/ExecutionHistoryViewModel.cs index 691d2f40..c38c5d6c 100644 --- a/UI/Backend/ViewModels/Automation/ExecutionHistoryViewModel.cs +++ b/UI/Features/ExecutionHistory/ExecutionHistoryViewModel.cs @@ -1,34 +1,34 @@ -using Ares.Services; -using DynamicData; -using Google.Protobuf.WellKnownTypes; -using ReactiveUI; -using ReactiveUI.SourceGenerators; - -namespace UI.Backend.ViewModels.Automation; - -public partial class ExecutionHistoryViewModel : ReactiveObject -{ - private readonly AresAutomation.AresAutomationClient _automationClient; - - public ExecutionHistoryViewModel(AresAutomation.AresAutomationClient automationClient) - { - _automationClient = automationClient; - CampaignSummaries = []; - LoadingExecutionHistory = false; - } - - public async Task UpdateExecutionSummaries() - { - LoadingExecutionHistory = true; - CampaignSummaries.Clear(); - var response = await _automationClient.GetAvailableCampaignExecutionSummariesAsync(new Empty()); - CampaignSummaries.AddRange(response.AvailableCampaignSummaries); - LoadingExecutionHistory = false; - } - - [Reactive] - public partial IList CampaignSummaries { get; set; } - - [Reactive] - public partial bool LoadingExecutionHistory { get; set; } +using Ares.Services; +using DynamicData; +using Google.Protobuf.WellKnownTypes; +using ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace UI.Features.ExecutionHistory; + +public partial class ExecutionHistoryViewModel : ReactiveObject +{ + private readonly AresAutomation.AresAutomationClient _automationClient; + + public ExecutionHistoryViewModel(AresAutomation.AresAutomationClient automationClient) + { + _automationClient = automationClient; + CampaignSummaries = []; + LoadingExecutionHistory = false; + } + + public async Task UpdateExecutionSummaries() + { + LoadingExecutionHistory = true; + CampaignSummaries.Clear(); + var response = await _automationClient.GetAvailableCampaignExecutionSummariesAsync(new Empty()); + CampaignSummaries.AddRange(response.AvailableCampaignSummaries); + LoadingExecutionHistory = false; + } + + [Reactive] + public partial IList CampaignSummaries { get; set; } + + [Reactive] + public partial bool LoadingExecutionHistory { get; set; } } \ No newline at end of file diff --git a/UI/Pages/Notification/NotificationHistory.razor b/UI/Features/Notifications/NotificationHistory.razor similarity index 96% rename from UI/Pages/Notification/NotificationHistory.razor rename to UI/Features/Notifications/NotificationHistory.razor index 2c232885..b2957c01 100644 --- a/UI/Pages/Notification/NotificationHistory.razor +++ b/UI/Features/Notifications/NotificationHistory.razor @@ -1,8 +1,8 @@ -@page "/notifications" +@page "/notifications" @using Ares.Services -@using UI.Backend.Notifications +@using UI.Features.Notifications @inject ContextMenuService ContextMenuService -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase
@@ -117,4 +117,4 @@ else return aresNotifications.OrderBy(n => n.NotificationSeverity).ToList(); } -} \ No newline at end of file +} diff --git a/UI/Pages/Notification/NotificationHistory.razor.css b/UI/Features/Notifications/NotificationHistory.razor.css similarity index 100% rename from UI/Pages/Notification/NotificationHistory.razor.css rename to UI/Features/Notifications/NotificationHistory.razor.css diff --git a/UI/Backend/ViewModels/NotificationHistoryViewModel.cs b/UI/Features/Notifications/NotificationHistoryViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/NotificationHistoryViewModel.cs rename to UI/Features/Notifications/NotificationHistoryViewModel.cs index 04ca8b53..20a4fc05 100644 --- a/UI/Backend/ViewModels/NotificationHistoryViewModel.cs +++ b/UI/Features/Notifications/NotificationHistoryViewModel.cs @@ -1,8 +1,8 @@ -using Ares.Services; +using Ares.Services; using ReactiveUI; -using UI.Backend.Notifications; +using UI.Application.Notifications; -namespace UI.Backend.ViewModels; +namespace UI.Features.Notifications; public class NotificationHistoryViewModel : ReactiveObject { @@ -63,4 +63,6 @@ public bool ShouldDisplayNotification(AresNotification notification) public bool DisplayInfoNotifications { get; set; } = true; public bool DisplaySuccessNotifications { get; set; } = true; public int NotificationSortMethod { get; set; } = 0; -} \ No newline at end of file +} + + diff --git a/UI/Pages/Shared/Settings/Planning/Layout/PlannerSettingsContainer.razor b/UI/Features/Planning/Settings/Layout/PlannerSettingsContainer.razor similarity index 100% rename from UI/Pages/Shared/Settings/Planning/Layout/PlannerSettingsContainer.razor rename to UI/Features/Planning/Settings/Layout/PlannerSettingsContainer.razor diff --git a/UI/Pages/Shared/Settings/Planning/Layout/PlannerSettingsContainer.razor.css b/UI/Features/Planning/Settings/Layout/PlannerSettingsContainer.razor.css similarity index 100% rename from UI/Pages/Shared/Settings/Planning/Layout/PlannerSettingsContainer.razor.css rename to UI/Features/Planning/Settings/Layout/PlannerSettingsContainer.razor.css diff --git a/UI/Pages/Shared/Settings/Planning/Layout/PlannerSettingsLayout.razor b/UI/Features/Planning/Settings/Layout/PlannerSettingsLayout.razor similarity index 100% rename from UI/Pages/Shared/Settings/Planning/Layout/PlannerSettingsLayout.razor rename to UI/Features/Planning/Settings/Layout/PlannerSettingsLayout.razor diff --git a/UI/Pages/Shared/Settings/Planning/PlannerConfigEditView.razor b/UI/Features/Planning/Settings/PlannerConfigEditView.razor similarity index 84% rename from UI/Pages/Shared/Settings/Planning/PlannerConfigEditView.razor rename to UI/Features/Planning/Settings/PlannerConfigEditView.razor index 189ec137..de406062 100644 --- a/UI/Pages/Shared/Settings/Planning/PlannerConfigEditView.razor +++ b/UI/Features/Planning/Settings/PlannerConfigEditView.razor @@ -1,4 +1,4 @@ -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@inherits ReactiveUI.Blazor.ReactiveComponentBase
diff --git a/UI/Backend/ViewModels/Settings/Planning/PlannerConfigEditViewModel.cs b/UI/Features/Planning/Settings/PlannerConfigEditViewModel.cs similarity index 93% rename from UI/Backend/ViewModels/Settings/Planning/PlannerConfigEditViewModel.cs rename to UI/Features/Planning/Settings/PlannerConfigEditViewModel.cs index 96f9aeb1..99b698fa 100644 --- a/UI/Backend/ViewModels/Settings/Planning/PlannerConfigEditViewModel.cs +++ b/UI/Features/Planning/Settings/PlannerConfigEditViewModel.cs @@ -1,9 +1,9 @@ -using Ares.Datamodel.Planning; +using Ares.Datamodel.Planning; using Ares.Services; using ReactiveUI; -namespace UI.Backend.ViewModels.Settings.Planning +namespace UI.Features.Planning.Settings { public class PlannerConfigEditViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Planning/PlannerSettingsEditorView.razor b/UI/Features/Planning/Settings/PlannerSettingsEditorView.razor similarity index 84% rename from UI/Pages/Shared/Settings/Planning/PlannerSettingsEditorView.razor rename to UI/Features/Planning/Settings/PlannerSettingsEditorView.razor index c8d995c2..a8aa9ed4 100644 --- a/UI/Pages/Shared/Settings/Planning/PlannerSettingsEditorView.razor +++ b/UI/Features/Planning/Settings/PlannerSettingsEditorView.razor @@ -1,5 +1,5 @@ -@using Ares.Datamodel; -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using Ares.Datamodel; +@inherits ReactiveUI.Blazor.ReactiveComponentBase diff --git a/UI/Backend/ViewModels/Settings/Planning/PlannerSettingsEditorViewModel.cs b/UI/Features/Planning/Settings/PlannerSettingsEditorViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/Settings/Planning/PlannerSettingsEditorViewModel.cs rename to UI/Features/Planning/Settings/PlannerSettingsEditorViewModel.cs index a446c857..c0558630 100644 --- a/UI/Backend/ViewModels/Settings/Planning/PlannerSettingsEditorViewModel.cs +++ b/UI/Features/Planning/Settings/PlannerSettingsEditorViewModel.cs @@ -1,10 +1,10 @@ -using Ares.Datamodel; +using Ares.Datamodel; using Ares.Datamodel.Planning; using Ares.Services; using Grpc.Core; using ReactiveUI; -namespace UI.Backend.ViewModels.Settings.Planning; +namespace UI.Features.Planning.Settings; public class PlannerSettingsEditorViewModel : ReactiveObject { diff --git a/UI/Pages/Shared/Settings/Planning/PlannerSettingsList.razor b/UI/Features/Planning/Settings/PlannerSettingsList.razor similarity index 94% rename from UI/Pages/Shared/Settings/Planning/PlannerSettingsList.razor rename to UI/Features/Planning/Settings/PlannerSettingsList.razor index b3ef3a2e..3a762783 100644 --- a/UI/Pages/Shared/Settings/Planning/PlannerSettingsList.razor +++ b/UI/Features/Planning/Settings/PlannerSettingsList.razor @@ -1,8 +1,7 @@ -@page "/settings/planning" +@page "/settings/planning" @using Ares.Services @using Google.Protobuf.WellKnownTypes -@using UI.Backend.ViewModels.Settings.Planning -@using UI.Pages.Shared.Settings.Planning.Layout +@using UI.Features.Planning.Settings.Layout @inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @layout PlannerSettingsLayout diff --git a/UI/Backend/ViewModels/Settings/Planning/PlannerSettingsListViewModel.cs b/UI/Features/Planning/Settings/PlannerSettingsListViewModel.cs similarity index 94% rename from UI/Backend/ViewModels/Settings/Planning/PlannerSettingsListViewModel.cs rename to UI/Features/Planning/Settings/PlannerSettingsListViewModel.cs index 62ae2b59..4996a308 100644 --- a/UI/Backend/ViewModels/Settings/Planning/PlannerSettingsListViewModel.cs +++ b/UI/Features/Planning/Settings/PlannerSettingsListViewModel.cs @@ -1,12 +1,12 @@ -using Ares.Datamodel.Planning; +using Ares.Datamodel.Planning; using Ares.Services; using Google.Protobuf.WellKnownTypes; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.Services.Notification; +using UI.Application.Notifications; -namespace UI.Backend.ViewModels.Settings.Planning; +namespace UI.Features.Planning.Settings; public partial class PlannerSettingsListViewModel : ReactiveObject { @@ -55,3 +55,5 @@ private async Task OnPlannerRemoved() [Reactive] public partial IEnumerable? SettingsViewModels { get; private set; } } + + diff --git a/UI/Pages/Shared/Settings/Planning/PlannerSettingsView.razor b/UI/Features/Planning/Settings/PlannerSettingsView.razor similarity index 92% rename from UI/Pages/Shared/Settings/Planning/PlannerSettingsView.razor rename to UI/Features/Planning/Settings/PlannerSettingsView.razor index 980d9663..32128580 100644 --- a/UI/Pages/Shared/Settings/Planning/PlannerSettingsView.razor +++ b/UI/Features/Planning/Settings/PlannerSettingsView.razor @@ -1,7 +1,7 @@ -@using Ares.Services +@using Ares.Services @using Google.Protobuf.WellKnownTypes -@using UI.Pages.Shared.Settings.Planning.Layout -@inherits ReactiveUI.Blazor.ReactiveComponentBase +@using UI.Features.Planning.Settings.Layout +@inherits ReactiveUI.Blazor.ReactiveComponentBase @inject DialogService DialogService GetPlannerStatus() public void PushNotification(AresNotification notification) => _notificationService.PushNotification(notification); } + + diff --git a/UI/Pages/Safety.razor b/UI/Features/Safety/Safety.razor similarity index 100% rename from UI/Pages/Safety.razor rename to UI/Features/Safety/Safety.razor diff --git a/UI/Pages/ScriptPlayground.razor b/UI/Features/ScriptPlayground/ScriptPlayground.razor similarity index 86% rename from UI/Pages/ScriptPlayground.razor rename to UI/Features/ScriptPlayground/ScriptPlayground.razor index 2eef76b6..5efcc0c5 100644 --- a/UI/Pages/ScriptPlayground.razor +++ b/UI/Features/ScriptPlayground/ScriptPlayground.razor @@ -1,6 +1,10 @@ -@page "/scripting" -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase -@using System.Reactive.Linq +@page "/scripting" +@using UI.Application.Scripting +@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase +@inject IMonacoCompletionProvider CompletionProvider +@inject IMonacoDiagnosticsProvider DiagnosticsProvider +@inject IMonacoSemanticTokensProvider SemanticTokensProvider +@inject IMonacoHoverProvider HoverProvider
@@ -37,10 +41,10 @@ Script Editor
-
@@ -129,3 +133,4 @@ public string Message { get; set; } = string.Empty; } } + diff --git a/UI/Pages/ScriptPlayground.razor.css b/UI/Features/ScriptPlayground/ScriptPlayground.razor.css similarity index 100% rename from UI/Pages/ScriptPlayground.razor.css rename to UI/Features/ScriptPlayground/ScriptPlayground.razor.css diff --git a/UI/Backend/ViewModels/ScriptPlaygroundViewModel.cs b/UI/Features/ScriptPlayground/ScriptPlaygroundViewModel.cs similarity index 67% rename from UI/Backend/ViewModels/ScriptPlaygroundViewModel.cs rename to UI/Features/ScriptPlayground/ScriptPlaygroundViewModel.cs index 3c424ca1..0b1693eb 100644 --- a/UI/Backend/ViewModels/ScriptPlaygroundViewModel.cs +++ b/UI/Features/ScriptPlayground/ScriptPlaygroundViewModel.cs @@ -1,12 +1,11 @@ -using System.Reactive.Linq; -using System.Reactive.Subjects; using Ares.Services; using Grpc.Core; using ReactiveUI; using ReactiveUI.SourceGenerators; -using UI.JsInterops; +using System.Reactive.Linq; +using System.Reactive.Subjects; -namespace UI.Backend.ViewModels; +namespace UI.Features.ScriptPlayground; public partial class ScriptPlaygroundViewModel : ReactiveObject { @@ -15,17 +14,9 @@ public partial class ScriptPlaygroundViewModel : ReactiveObject private readonly ISubject _scriptOutput = new Subject(); public ScriptPlaygroundViewModel( - AresScriptingService.AresScriptingServiceClient scriptingClient, - MonacoCompletionProvider completionProvider, - MonacoDiagnosticsProvider diagnosticsProvider, - MonacoSemanticTokensProvider semanticTokensProvider, - MonacoHoverProvider hoverProvider) + AresScriptingService.AresScriptingServiceClient scriptingClient) { _scriptingClient = scriptingClient; - CompletionProvider = completionProvider; - DiagnosticsProvider = diagnosticsProvider; - SemanticTokensProvider = semanticTokensProvider; - HoverProvider = hoverProvider; ScriptOutput = _scriptOutput.AsObservable(); } @@ -61,8 +52,4 @@ public async Task StopScript() public partial bool ScriptRunning { get; private set; } public IObservable ScriptOutput { get; } - public MonacoCompletionProvider CompletionProvider { get; } - public MonacoDiagnosticsProvider DiagnosticsProvider { get; } - public MonacoSemanticTokensProvider SemanticTokensProvider { get; } - public MonacoHoverProvider HoverProvider { get; } } diff --git a/UI/Services/ServerHealth/AresConnectionStatus.cs b/UI/Features/ServerHealth/AresConnectionStatus.cs similarity index 66% rename from UI/Services/ServerHealth/AresConnectionStatus.cs rename to UI/Features/ServerHealth/AresConnectionStatus.cs index 56ffed8b..03e11e1d 100644 --- a/UI/Services/ServerHealth/AresConnectionStatus.cs +++ b/UI/Features/ServerHealth/AresConnectionStatus.cs @@ -1,9 +1,9 @@ -namespace UI.Services.ServerHealth; - -public enum AresConnectionStatus -{ - Unattempted, - Connected, - Disconnected, - Connecting -} +namespace UI.Features.ServerHealth; + +public enum AresConnectionStatus +{ + Unattempted, + Connected, + Disconnected, + Connecting +} diff --git a/UI/Services/ServerHealthNotification/ServerHealthNotificationService.cs b/UI/Features/ServerHealth/ServerHealthNotificationService.cs similarity index 93% rename from UI/Services/ServerHealthNotification/ServerHealthNotificationService.cs rename to UI/Features/ServerHealth/ServerHealthNotificationService.cs index 8fa2d27e..dfa144e0 100644 --- a/UI/Services/ServerHealthNotification/ServerHealthNotificationService.cs +++ b/UI/Features/ServerHealth/ServerHealthNotificationService.cs @@ -1,8 +1,8 @@ using Ares.Services; -using UI.Services.Notification; -using UI.Services.ServerHealth; +using UI.Application.Notifications; +using UI.Application.Hosting; -namespace UI.Services.ServerHealthNotification; +namespace UI.Features.ServerHealth; /// /// Uses the server health service to grab new state messages and publish them to the notification repo/service @@ -51,3 +51,5 @@ private void ProcessServerState(ServerStatusResponse status) } } } + + diff --git a/UI/Services/ServerHealth/ServerHealthService.cs b/UI/Features/ServerHealth/ServerHealthService.cs similarity index 95% rename from UI/Services/ServerHealth/ServerHealthService.cs rename to UI/Features/ServerHealth/ServerHealthService.cs index b2a67cb7..821b641b 100644 --- a/UI/Services/ServerHealth/ServerHealthService.cs +++ b/UI/Features/ServerHealth/ServerHealthService.cs @@ -1,145 +1,146 @@ -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Ares.Services; -using Google.Protobuf.WellKnownTypes; -using Grpc.Core; -using Grpc.Health.V1; - -namespace UI.Services.ServerHealth; - -/// -/// Responsible for using an ares server info client to get state messages from the server and publishing them -/// to an internal observable -/// -internal class ServerHealthService : ILocalService -{ - private readonly AresServerInfo.AresServerInfoClient _aresServerInfo; - private readonly Health.HealthClient _healthClient; - private readonly ILogger _logger; - private readonly ISubject _serverStatusSubject = new BehaviorSubject(new ServerStatusResponse { ServerStatus = Ares.Services.ServerStatus.Idle, StatusMessage = "Not Connected" }); - - public readonly IObservable ServerStatus; - private Task _heartbeatListener = Task.CompletedTask; - private CancellationTokenSource _serviceCancellationTokenSource = new(); - private Task _stateListener = Task.CompletedTask; - - public ServerHealthService(AresServerInfo.AresServerInfoClient aresServerInfo, Health.HealthClient healthClient, ILogger logger) - { - _aresServerInfo = aresServerInfo; - _healthClient = healthClient; - _logger = logger; - ServerStatus = _serverStatusSubject.AsObservable(); - } - - private bool Running => !_heartbeatListener.IsCompleted || !_stateListener.IsCompleted; - - public AresConnectionStatus AresConnectionStatus { get; private set; } - - public string ServerName { get; private set; } = string.Empty; - - public Version ServerVersion { get; private set; } = new(); - - public async Task Start() - { - if (Running) - return; - - _serviceCancellationTokenSource = new CancellationTokenSource(); - AresConnectionStatus = AresConnectionStatus.Connecting; - try - { - var info = await _aresServerInfo.GetServerInfoAsync(new Empty(), null, null, _serviceCancellationTokenSource.Token); - ServerName = info.ServerName; - ServerVersion = Version.Parse(info.Version); - AresConnectionStatus = AresConnectionStatus.Connected; - } - catch (RpcException e) - { - AresConnectionStatus = AresConnectionStatus.Disconnected; - ServerName = string.Empty; - ServerVersion = new Version(); - _logger.LogError("Failed to start {}: {}", GetType().Name, e.Message); - return; - } - - _stateListener = Task.Run(StartListening, _serviceCancellationTokenSource.Token); - _heartbeatListener = Task.Run(EstablishHeartbeat, _serviceCancellationTokenSource.Token); - } - - public async void Stop() - { - _serviceCancellationTokenSource.Cancel(); - - await Task.WhenAll(_heartbeatListener, _stateListener); - } - - private async void EstablishHeartbeat() - { - var healthRequest = new HealthCheckRequest { Service = string.Empty }; - while (!_serviceCancellationTokenSource.Token.IsCancellationRequested) - try - { - var timeout = TimeSpan.FromSeconds(15); - var timeoutTask = Task.Delay(timeout, _serviceCancellationTokenSource.Token); - var healthTask = _healthClient.CheckAsync(healthRequest, null, null, _serviceCancellationTokenSource.Token).ResponseAsync; - var completedTask = await Task.WhenAny(timeoutTask, healthTask); - - if (completedTask == timeoutTask) - { - HandleDisconnect("Lost connection to server. Health check timed out."); - return; - } - - if (completedTask.IsFaulted && completedTask.Exception?.InnerException is RpcException e) - { - HandleDisconnect(e.Message); - return; - } - - await Task.Delay(timeout); - } - catch (RpcException e) - { - HandleDisconnect(e.Message); - } - catch (OperationCanceledException) - { - HandleDisconnect($"{GetType().Name} stopping."); - } - } - - private void HandleDisconnect(string message = "") - { - ServerName = string.Empty; - ServerVersion = new Version(); - AresConnectionStatus = AresConnectionStatus.Disconnected; - _serverStatusSubject.OnNext(new ServerStatusResponse - { - ServerStatus = Ares.Services.ServerStatus.Error, - StatusMessage = $"Server is offline: {message}" - }); - - _logger.LogError("Disconnected from server: {}", message); - - Stop(); - } - - private async Task StartListening() - { - var statusStream = _aresServerInfo.GetServerStatusStream(new Empty(), null, null, _serviceCancellationTokenSource.Token); - try - { - while (await statusStream.ResponseStream.MoveNext(_serviceCancellationTokenSource.Token) && !_serviceCancellationTokenSource.IsCancellationRequested) - { - var response = statusStream.ResponseStream.Current; - _logger.LogInformation("Received state from server: {}", response.StatusMessage); - _serverStatusSubject.OnNext(response); - } - } - catch (RpcException e) - { - _logger.LogError("Failed to get state from server: {}", e.Message); - HandleDisconnect(e.Message); - } - } -} +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Ares.Services; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Grpc.Health.V1; +using UI.Application.Hosting; + +namespace UI.Features.ServerHealth; + +/// +/// Responsible for using an ares server info client to get state messages from the server and publishing them +/// to an internal observable +/// +internal class ServerHealthService : ILocalService +{ + private readonly AresServerInfo.AresServerInfoClient _aresServerInfo; + private readonly Health.HealthClient _healthClient; + private readonly ILogger _logger; + private readonly ISubject _serverStatusSubject = new BehaviorSubject(new ServerStatusResponse { ServerStatus = Ares.Services.ServerStatus.Idle, StatusMessage = "Not Connected" }); + + public readonly IObservable ServerStatus; + private Task _heartbeatListener = Task.CompletedTask; + private CancellationTokenSource _serviceCancellationTokenSource = new(); + private Task _stateListener = Task.CompletedTask; + + public ServerHealthService(AresServerInfo.AresServerInfoClient aresServerInfo, Health.HealthClient healthClient, ILogger logger) + { + _aresServerInfo = aresServerInfo; + _healthClient = healthClient; + _logger = logger; + ServerStatus = _serverStatusSubject.AsObservable(); + } + + private bool Running => !_heartbeatListener.IsCompleted || !_stateListener.IsCompleted; + + public AresConnectionStatus AresConnectionStatus { get; private set; } + + public string ServerName { get; private set; } = string.Empty; + + public Version ServerVersion { get; private set; } = new(); + + public async Task Start() + { + if (Running) + return; + + _serviceCancellationTokenSource = new CancellationTokenSource(); + AresConnectionStatus = AresConnectionStatus.Connecting; + try + { + var info = await _aresServerInfo.GetServerInfoAsync(new Empty(), null, null, _serviceCancellationTokenSource.Token); + ServerName = info.ServerName; + ServerVersion = Version.Parse(info.Version); + AresConnectionStatus = AresConnectionStatus.Connected; + } + catch (RpcException e) + { + AresConnectionStatus = AresConnectionStatus.Disconnected; + ServerName = string.Empty; + ServerVersion = new Version(); + _logger.LogError("Failed to start {}: {}", GetType().Name, e.Message); + return; + } + + _stateListener = Task.Run(StartListening, _serviceCancellationTokenSource.Token); + _heartbeatListener = Task.Run(EstablishHeartbeat, _serviceCancellationTokenSource.Token); + } + + public async void Stop() + { + _serviceCancellationTokenSource.Cancel(); + + await Task.WhenAll(_heartbeatListener, _stateListener); + } + + private async void EstablishHeartbeat() + { + var healthRequest = new HealthCheckRequest { Service = string.Empty }; + while (!_serviceCancellationTokenSource.Token.IsCancellationRequested) + try + { + var timeout = TimeSpan.FromSeconds(15); + var timeoutTask = Task.Delay(timeout, _serviceCancellationTokenSource.Token); + var healthTask = _healthClient.CheckAsync(healthRequest, null, null, _serviceCancellationTokenSource.Token).ResponseAsync; + var completedTask = await Task.WhenAny(timeoutTask, healthTask); + + if (completedTask == timeoutTask) + { + HandleDisconnect("Lost connection to server. Health check timed out."); + return; + } + + if (completedTask.IsFaulted && completedTask.Exception?.InnerException is RpcException e) + { + HandleDisconnect(e.Message); + return; + } + + await Task.Delay(timeout); + } + catch (RpcException e) + { + HandleDisconnect(e.Message); + } + catch (OperationCanceledException) + { + HandleDisconnect($"{GetType().Name} stopping."); + } + } + + private void HandleDisconnect(string message = "") + { + ServerName = string.Empty; + ServerVersion = new Version(); + AresConnectionStatus = AresConnectionStatus.Disconnected; + _serverStatusSubject.OnNext(new ServerStatusResponse + { + ServerStatus = Ares.Services.ServerStatus.Error, + StatusMessage = $"Server is offline: {message}" + }); + + _logger.LogError("Disconnected from server: {}", message); + + Stop(); + } + + private async Task StartListening() + { + var statusStream = _aresServerInfo.GetServerStatusStream(new Empty(), null, null, _serviceCancellationTokenSource.Token); + try + { + while (await statusStream.ResponseStream.MoveNext(_serviceCancellationTokenSource.Token) && !_serviceCancellationTokenSource.IsCancellationRequested) + { + var response = statusStream.ResponseStream.Current; + _logger.LogInformation("Received state from server: {}", response.StatusMessage); + _serverStatusSubject.OnNext(response); + } + } + catch (RpcException e) + { + _logger.LogError("Failed to get state from server: {}", e.Message); + HandleDisconnect(e.Message); + } + } +} diff --git a/UI/Backend/Extensions/ExportTypeExtensions.cs b/UI/Features/Shared/ExportTypeExtensions.cs similarity index 87% rename from UI/Backend/Extensions/ExportTypeExtensions.cs rename to UI/Features/Shared/ExportTypeExtensions.cs index a9e92b1b..fd1251dc 100644 --- a/UI/Backend/Extensions/ExportTypeExtensions.cs +++ b/UI/Features/Shared/ExportTypeExtensions.cs @@ -1,7 +1,7 @@ - + using Ares.Services; -namespace UI.Backend.Extensions; +namespace UI.Features.Shared; public static class ExportTypeExtensions { diff --git a/UI/Features/_Imports.razor b/UI/Features/_Imports.razor new file mode 100644 index 00000000..f1ccfb3d --- /dev/null +++ b/UI/Features/_Imports.razor @@ -0,0 +1,2 @@ +@using UI.Features.Shared + diff --git a/UI/Authentication/AresAuthenticationState.cs b/UI/Infrastructure/Auth/AresAuthenticationState.cs similarity index 82% rename from UI/Authentication/AresAuthenticationState.cs rename to UI/Infrastructure/Auth/AresAuthenticationState.cs index 244b3403..a4da762d 100644 --- a/UI/Authentication/AresAuthenticationState.cs +++ b/UI/Infrastructure/Auth/AresAuthenticationState.cs @@ -1,12 +1,12 @@ -namespace UI.Authentication; - -public class AresAuthenticationState -{ - public bool Authenticated { get; set; } - - public string? Token { get; set; } - - public DateTime TokenExpiration { get; set; } - - public string? UserName { get; set; } -} +namespace UI.Infrastructure.Auth; + +public class AresAuthenticationState +{ + public bool Authenticated { get; set; } + + public string? Token { get; set; } + + public DateTime TokenExpiration { get; set; } + + public string? UserName { get; set; } +} diff --git a/UI/Backend/ViewModels/DeviceStateLogging/CombinedDeviceGetter.cs b/UI/Infrastructure/DeviceStateLogging/CombinedDeviceGetter.cs similarity index 95% rename from UI/Backend/ViewModels/DeviceStateLogging/CombinedDeviceGetter.cs rename to UI/Infrastructure/DeviceStateLogging/CombinedDeviceGetter.cs index c75d5f44..ac0defb7 100644 --- a/UI/Backend/ViewModels/DeviceStateLogging/CombinedDeviceGetter.cs +++ b/UI/Infrastructure/DeviceStateLogging/CombinedDeviceGetter.cs @@ -1,5 +1,4 @@ -using Ares.Datamodel.Device.Remote; -using Ares.Messages.DeviceStates; +using Ares.Messages.DeviceStates; using Ares.Messages.DeviceStates.Mfc; using Ares.Messages.DeviceStates.SyringePump; using Ares.Messages.DeviceStates.Tc0304; @@ -7,8 +6,9 @@ using Ares.Messages.DeviceStates.TubeFurnace; using Ares.Services.Device; using Google.Protobuf.WellKnownTypes; +using UI.Application.DeviceStateLogging; -namespace UI.Backend.ViewModels.DeviceStateLogging; +namespace UI.Infrastructure.DeviceStateLogging; public class CombinedDeviceGetter : ICombinedDeviceGetter { diff --git a/UI/Backend/Devices/DeviceAdapterManager.cs b/UI/Infrastructure/Devices/DeviceAdapterManager.cs similarity index 94% rename from UI/Backend/Devices/DeviceAdapterManager.cs rename to UI/Infrastructure/Devices/DeviceAdapterManager.cs index 3f0f18a2..d5e041e3 100644 --- a/UI/Backend/Devices/DeviceAdapterManager.cs +++ b/UI/Infrastructure/Devices/DeviceAdapterManager.cs @@ -1,123 +1,124 @@ -using Ares.Services.Device; -using Google.Protobuf.WellKnownTypes; - -namespace UI.Backend.Devices; - -public class DeviceAdapterManager( - AresDevices.AresDevicesClient _devicesClient, - DeviceAdapterRepository _deviceAdapterRepository, - ILoggerFactory _loggerFactory, - ILogger _logger) : IAsyncDisposable -{ - private readonly CancellationTokenSource _cts = new(); - private Task? _pollingTask; - private bool _isErrorState; - private IDictionary _monitors = new Dictionary(); - - public void Activate() - { - _pollingTask = Task.Run(() => PollDevicesAsync(_cts.Token)); - } - - private async Task PollDevicesAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - // let's stick to remote devices for now as the built-int devices have their - // own logic in viewmodels - var devices = await _devicesClient.ListRemoteAresDevicesAsync( - new Empty(), - cancellationToken: cancellationToken); - - if (_isErrorState) - { - _logger.LogInformation("Device polling recovered."); - _isErrorState = false; - } - - await UpdateAdaptersFromDeviceList(devices); - } - catch (OperationCanceledException) - { - // This is expected on shutdown. - break; - } - catch (Exception ex) - { - if (!_isErrorState) - { - _logger.LogError(ex, "Error polling remote Ares devices."); - _isErrorState = true; - } - } - - try - { - await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); - } - catch (OperationCanceledException) - { - // This is expected on shutdown. - break; - } - } - } - - private async Task UpdateAdaptersFromDeviceList(ListAresRemoteDevicesResponse devicesResponse) - { - try - { - var remoteIds = devicesResponse.Devices.Select(d => d.UniqueId).ToHashSet(); - var existingIds = _deviceAdapterRepository.Keys.ToHashSet(); - - var newDevices = remoteIds.Except(existingIds); - var removedDevices = existingIds.Except(remoteIds); - - var newAdapters = newDevices - .Select(id => new RemoteDeviceAdapter(_devicesClient, id, _loggerFactory.CreateLogger())).ToArray(); - - var removedAdapters = _deviceAdapterRepository.Items.Where(da => removedDevices.Contains(da.Id)).ToArray(); - foreach (var adapter in removedAdapters) - { - if (adapter is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.DisposeAsync(); - } - if (_monitors.Remove(adapter.Id, out var monitor)) - { - monitor.Dispose(); - } - } - - foreach (var adapter in newAdapters) - { - _ = adapter.Activate(); - var monitor = new RemoteDeviceAdapterMonitor(adapter, _loggerFactory.CreateLogger()); - _monitors[adapter.Id] = monitor; - } - - _deviceAdapterRepository.Edit(updater => - { - updater.AddOrUpdate(newAdapters); // add/update - updater.RemoveKeys(removedDevices); // remove stale - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error updating device adapter repository."); - } - } - - public async ValueTask DisposeAsync() - { - await _cts.CancelAsync(); - if (_pollingTask is not null) - { - await _pollingTask; - } - _cts.Dispose(); - GC.SuppressFinalize(this); - } -} +using Ares.Services.Device; +using Google.Protobuf.WellKnownTypes; +using UI.Application.Devices.Repos; + +namespace UI.Infrastructure.Devices; + +public class DeviceAdapterManager( + AresDevices.AresDevicesClient _devicesClient, + IDeviceAdapterRepository _deviceAdapterRepository, + ILoggerFactory _loggerFactory, + ILogger _logger) : IAsyncDisposable +{ + private readonly CancellationTokenSource _cts = new(); + private Task? _pollingTask; + private bool _isErrorState; + private IDictionary _monitors = new Dictionary(); + + public void Activate() + { + _pollingTask = Task.Run(() => PollDevicesAsync(_cts.Token)); + } + + private async Task PollDevicesAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + // let's stick to remote devices for now as the built-int devices have their + // own logic in viewmodels + var devices = await _devicesClient.ListRemoteAresDevicesAsync( + new Empty(), + cancellationToken: cancellationToken); + + if (_isErrorState) + { + _logger.LogInformation("Device polling recovered."); + _isErrorState = false; + } + + await UpdateAdaptersFromDeviceList(devices); + } + catch (OperationCanceledException) + { + // This is expected on shutdown. + break; + } + catch (Exception ex) + { + if (!_isErrorState) + { + _logger.LogError(ex, "Error polling remote Ares devices."); + _isErrorState = true; + } + } + + try + { + await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); + } + catch (OperationCanceledException) + { + // This is expected on shutdown. + break; + } + } + } + + private async Task UpdateAdaptersFromDeviceList(ListAresRemoteDevicesResponse devicesResponse) + { + try + { + var remoteIds = devicesResponse.Devices.Select(d => d.UniqueId).ToHashSet(); + var existingIds = _deviceAdapterRepository.Keys.ToHashSet(); + + var newDevices = remoteIds.Except(existingIds); + var removedDevices = existingIds.Except(remoteIds); + + var newAdapters = newDevices + .Select(id => new RemoteDeviceAdapter(_devicesClient, id, _loggerFactory.CreateLogger())).ToArray(); + + var removedAdapters = _deviceAdapterRepository.Items.Where(da => removedDevices.Contains(da.Id)).ToArray(); + foreach (var adapter in removedAdapters) + { + if (adapter is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync(); + } + if (_monitors.Remove(adapter.Id, out var monitor)) + { + monitor.Dispose(); + } + } + + foreach (var adapter in newAdapters) + { + _ = adapter.Activate(); + var monitor = new RemoteDeviceAdapterMonitor(adapter, _loggerFactory.CreateLogger()); + _monitors[adapter.Id] = monitor; + } + + _deviceAdapterRepository.Edit(updater => + { + updater.AddOrUpdate(newAdapters); // add/update + updater.RemoveKeys(removedDevices); // remove stale + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating device adapter repository."); + } + } + + public async ValueTask DisposeAsync() + { + await _cts.CancelAsync(); + if (_pollingTask is not null) + { + await _pollingTask; + } + _cts.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/UI/Backend/Devices/DeviceAdapterRepository.cs b/UI/Infrastructure/Devices/DeviceAdapterRepository.cs similarity index 88% rename from UI/Backend/Devices/DeviceAdapterRepository.cs rename to UI/Infrastructure/Devices/DeviceAdapterRepository.cs index 8fe5c29a..ddd39bf7 100644 --- a/UI/Backend/Devices/DeviceAdapterRepository.cs +++ b/UI/Infrastructure/Devices/DeviceAdapterRepository.cs @@ -1,51 +1,53 @@ -using DynamicData; -using DynamicData.Kernel; - -namespace UI.Backend.Devices; - -public class DeviceAdapterRepository : ISourceCache, IDisposable -{ - private readonly SourceCache _cache = new(device => device.Id); - - public Func KeySelector => _cache.KeySelector; - - public int Count => _cache.Count; - - public IReadOnlyList Items => _cache.Items; - - public IReadOnlyList Keys => _cache.Keys; - - public IReadOnlyDictionary KeyValues => _cache.KeyValues; - - public IObservable CountChanged => _cache.CountChanged; - - public IObservable> Connect(Func? predicate = null, bool suppressEmptyChangeSets = true) - { - return _cache.Connect(predicate, suppressEmptyChangeSets); - } - - public void Dispose() - { - _cache.Dispose(); - } - - public void Edit(Action> updateAction) - { - _cache.Edit(updateAction); - } - - public Optional Lookup(string key) - { - return _cache.Lookup(key); - } - - public IObservable> Preview(Func? predicate = null) - { - return _cache.Preview(predicate); - } - - public IObservable> Watch(string key) - { - return _cache.Watch(key); - } -} +using DynamicData; +using DynamicData.Kernel; +using UI.Application.Devices; +using UI.Application.Devices.Repos; + +namespace UI.Infrastructure.Devices; + +public class DeviceAdapterRepository : IDeviceAdapterRepository +{ + private readonly SourceCache _cache = new(device => device.Id); + + public Func KeySelector => _cache.KeySelector; + + public int Count => _cache.Count; + + public IReadOnlyList Items => _cache.Items; + + public IReadOnlyList Keys => _cache.Keys; + + public IReadOnlyDictionary KeyValues => _cache.KeyValues; + + public IObservable CountChanged => _cache.CountChanged; + + public IObservable> Connect(Func? predicate = null, bool suppressEmptyChangeSets = true) + { + return _cache.Connect(predicate, suppressEmptyChangeSets); + } + + public void Dispose() + { + _cache.Dispose(); + } + + public void Edit(Action> updateAction) + { + _cache.Edit(updateAction); + } + + public Optional Lookup(string key) + { + return _cache.Lookup(key); + } + + public IObservable> Preview(Func? predicate = null) + { + return _cache.Preview(predicate); + } + + public IObservable> Watch(string key) + { + return _cache.Watch(key); + } +} diff --git a/UI/Backend/Factories/DeviceConnectorViewModelFactory.cs b/UI/Infrastructure/Devices/DeviceConnectorViewModelFactory.cs similarity index 94% rename from UI/Backend/Factories/DeviceConnectorViewModelFactory.cs rename to UI/Infrastructure/Devices/DeviceConnectorViewModelFactory.cs index 0dbdfb2a..93ec737a 100644 --- a/UI/Backend/Factories/DeviceConnectorViewModelFactory.cs +++ b/UI/Infrastructure/Devices/DeviceConnectorViewModelFactory.cs @@ -1,14 +1,14 @@ -using Ares.Datamodel.Device; +using Ares.Datamodel.Device; using Ares.Services.Device; using ReactiveUI; using System.Collections.ObjectModel; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; -using UI.Backend.Repos; -using UI.Backend.ViewModels; +using UI.Application.Devices; +using UI.Application.Devices.Repos; -namespace UI.Backend.Factories; +namespace UI.Infrastructure.Devices; public abstract class DeviceConnectorViewModelFactory : ReactiveObject, IAsyncDisposable where TDeviceUnitVm : DeviceUnitControlViewModel { @@ -76,4 +76,4 @@ public async ValueTask DisposeAsync() public ReadOnlyObservableCollection ConnectedDeviceUnitControlVms { get; } -} \ No newline at end of file +} diff --git a/UI/Backend/Devices/RemoteDeviceAdapter.cs b/UI/Infrastructure/Devices/RemoteDeviceAdapter.cs similarity index 98% rename from UI/Backend/Devices/RemoteDeviceAdapter.cs rename to UI/Infrastructure/Devices/RemoteDeviceAdapter.cs index 3453704a..598a3c3f 100644 --- a/UI/Backend/Devices/RemoteDeviceAdapter.cs +++ b/UI/Infrastructure/Devices/RemoteDeviceAdapter.cs @@ -4,14 +4,15 @@ using Ares.Datamodel.Device; using Ares.Services.Device; using Grpc.Core; +using UI.Application.Devices; -namespace UI.Backend.Devices; +namespace UI.Infrastructure.Devices; public sealed class RemoteDeviceAdapter : IAresDeviceAdapter, IAsyncDisposable { private readonly BehaviorSubject _stateSubject = new(new AresStruct()); private readonly BehaviorSubject _connectionStatusSubject = - new BehaviorSubject(ConnectionStatus.Undefined); - + new BehaviorSubject(ConnectionStatus.Undefined); + private readonly AresDevices.AresDevicesClient _devicesClient; private readonly ILogger _logger; private CancellationTokenSource _stateStreamCts = new(); @@ -61,19 +62,19 @@ public async Task Activate() return true; } - public async Task StartStatusUpdate() - { + public async Task StartStatusUpdate() + { await _statusStreamCts.CancelAsync(); _statusStreamCts = new CancellationTokenSource(); var token = _statusStreamCts.Token; - _ = Task.Run(async () => - { - while(!_statusStreamCts.IsCancellationRequested) - { - await UpdateConnectionStatus(); - await Task.Delay(TimeSpan.FromSeconds(5)); - } - }, _statusStreamCts.Token); + _ = Task.Run(async () => + { + while(!_statusStreamCts.IsCancellationRequested) + { + await UpdateConnectionStatus(); + await Task.Delay(TimeSpan.FromSeconds(5)); + } + }, _statusStreamCts.Token); } public async Task StartStateStream() @@ -82,8 +83,8 @@ public async Task StartStateStream() _stateStreamCts = new CancellationTokenSource(); var token = _stateStreamCts.Token; Active = true; - _ = Task.Run(async () => - { + _ = Task.Run(async () => + { try { using var call = _devicesClient.GetDeviceStateStream(new DeviceStateStreamRequest { DeviceId = Id, PollingSettings = _pollingSettings }); @@ -92,13 +93,13 @@ public async Task StartStateStream() await foreach(var state in call.ResponseStream.ReadAllAsync(token)) { _stateSubject.OnNext(state.State); - } - } - catch(Exception e) - { - _logger.LogError("Device stream for {name} has stopped. {ex}", Name, e.Message); - UpdateStatusIfChanged(ConnectionStatus.Disconnected); - } + } + } + catch(Exception e) + { + _logger.LogError("Device stream for {name} has stopped. {ex}", Name, e.Message); + UpdateStatusIfChanged(ConnectionStatus.Disconnected); + } }, _stateStreamCts.Token); Active = false; @@ -111,13 +112,13 @@ internal async Task FetchOperationalStatus() var callOpts = new CallOptions(deadline: DateTime.UtcNow.AddSeconds(5)); var status = await _devicesClient.GetDeviceStatusAsync(new DeviceStatusRequest { DeviceId = Id }, callOpts); _logger.LogInformation("Fetched operational status for device {name}.", Name); - if(status.OperationalState == OperationalState.Active) - { - UpdateStatusIfChanged(ConnectionStatus.ConnectedToDevice); + if(status.OperationalState == OperationalState.Active) + { + UpdateStatusIfChanged(ConnectionStatus.ConnectedToDevice); } - else + else { - UpdateStatusIfChanged(ConnectionStatus.ConnectedToService); + UpdateStatusIfChanged(ConnectionStatus.ConnectedToService); } OperationalStatus = status; } diff --git a/UI/Backend/Devices/RemoteDeviceAdapterMonitor.cs b/UI/Infrastructure/Devices/RemoteDeviceAdapterMonitor.cs similarity index 96% rename from UI/Backend/Devices/RemoteDeviceAdapterMonitor.cs rename to UI/Infrastructure/Devices/RemoteDeviceAdapterMonitor.cs index 03230cc8..ec2c9f98 100644 --- a/UI/Backend/Devices/RemoteDeviceAdapterMonitor.cs +++ b/UI/Infrastructure/Devices/RemoteDeviceAdapterMonitor.cs @@ -1,143 +1,144 @@ using System.Reactive.Disposables; using System.Reactive.Linq; +using UI.Application.Devices; -namespace UI.Backend.Devices; +namespace UI.Infrastructure.Devices; public class RemoteDeviceAdapterMonitor(IAresDeviceAdapter deviceAdapter, ILogger logger) : IDisposable -{ - private IDisposable _deviceStatusWatcher = Disposable.Empty; - private Task _reconnectLoop = Task.CompletedTask; - private CancellationTokenSource _reconnectCts = new(); - - // Interlocked-backed state - private int _reconnectInProgress = 0; // 0=false, 1=true - private int _reconnectAttempts = 0; - private int _activated = 0; // 0=false, 1=true - private readonly IAresDeviceAdapter _deviceAdapter = deviceAdapter; - private readonly ILogger _logger = logger; - - public void Activate() - { - // ensure one-time activation - if(Interlocked.Exchange(ref _activated, 1) == 1) - { - _logger.LogDebug("Monitor already activated {}.", _deviceAdapter.Name); - return; - } - - _deviceStatusWatcher = _deviceAdapter.ConnectionStatusStream - .DistinctUntilChanged() - .Subscribe(async status => - { - try - { - if(status == ConnectionStatus.Disconnected) - { - StartReconnectionLoop(); - } - else if(status == ConnectionStatus.ConnectedToService) - { - await EndReconnectionLoop().ConfigureAwait(false); - _logger.LogInformation("Device connected; activating adapter."); - await _deviceAdapter.Activate().ConfigureAwait(false); - } - } - catch(Exception ex) - { - _logger.LogError(ex, "Error handling status {Status}", status); - } - }); - } - - private void StartReconnectionLoop() - { - // fast exit if already running - if(Interlocked.Exchange(ref _reconnectInProgress, 1) == 1) - return; - - // reset/replace CTS defensively - try { _reconnectCts.Cancel(); } catch { /* ignore */ } - _reconnectCts.Dispose(); - _reconnectCts = new CancellationTokenSource(); - - Interlocked.Exchange(ref _reconnectAttempts, 0); - _logger.LogWarning("Starting reconnection loop."); - - _reconnectLoop = Task.Run(async () => - { - var token = _reconnectCts.Token; - - while(!token.IsCancellationRequested) - { - try - { - await _deviceAdapter.UpdateConnectionStatus().ConfigureAwait(false); - - var attempts = Interlocked.Increment(ref _reconnectAttempts); - - // linear backoff (cap 30s) - var baseSeconds = Math.Min(attempts * 5, 30); - - // jitter ±20% - var jitter = (int)Math.Round(baseSeconds * 0.2); - var waitSeconds = Math.Max(1, baseSeconds + Random.Shared.Next(-jitter, jitter + 1)); - - _logger.LogInformation("Reconnect attempt {Attempt}. Waiting {Delay}s.", attempts, waitSeconds); - await Task.Delay(TimeSpan.FromSeconds(waitSeconds), token).ConfigureAwait(false); - } - catch(OperationCanceledException) - { - break; // expected on cancellation - } - catch(Exception ex) - { - _logger.LogError(ex, "Reconnection attempt errored."); - try { await Task.Delay(TimeSpan.FromSeconds(2), token).ConfigureAwait(false); } catch { break; } - } - } - }, _reconnectCts.Token); - } - - private async Task EndReconnectionLoop() - { - // only if we were running - if(Interlocked.Exchange(ref _reconnectInProgress, 0) == 0) - return; - - _logger.LogInformation("Ending reconnection loop."); - - try - { - await _reconnectCts.CancelAsync().ConfigureAwait(false); - try - { - await _reconnectLoop.ConfigureAwait(false); - } - catch(OperationCanceledException) - { - // ignore - } - } - finally - { - _reconnectCts.Dispose(); - _reconnectCts = new CancellationTokenSource(); - Interlocked.Exchange(ref _reconnectAttempts, 0); - } - } - - public void Dispose() - { - // best-effort, non-throwing shutdown - try { _deviceStatusWatcher.Dispose(); } catch { /* ignore */ } - - try - { - // cancel loop quickly; avoid long blocking in Dispose() - _ = EndReconnectionLoop(); - } - catch { /* ignore */ } - - GC.SuppressFinalize(this); +{ + private IDisposable _deviceStatusWatcher = Disposable.Empty; + private Task _reconnectLoop = Task.CompletedTask; + private CancellationTokenSource _reconnectCts = new(); + + // Interlocked-backed state + private int _reconnectInProgress = 0; // 0=false, 1=true + private int _reconnectAttempts = 0; + private int _activated = 0; // 0=false, 1=true + private readonly IAresDeviceAdapter _deviceAdapter = deviceAdapter; + private readonly ILogger _logger = logger; + + public void Activate() + { + // ensure one-time activation + if(Interlocked.Exchange(ref _activated, 1) == 1) + { + _logger.LogDebug("Monitor already activated {}.", _deviceAdapter.Name); + return; + } + + _deviceStatusWatcher = _deviceAdapter.ConnectionStatusStream + .DistinctUntilChanged() + .Subscribe(async status => + { + try + { + if(status == ConnectionStatus.Disconnected) + { + StartReconnectionLoop(); + } + else if(status == ConnectionStatus.ConnectedToService) + { + await EndReconnectionLoop().ConfigureAwait(false); + _logger.LogInformation("Device connected; activating adapter."); + await _deviceAdapter.Activate().ConfigureAwait(false); + } + } + catch(Exception ex) + { + _logger.LogError(ex, "Error handling status {Status}", status); + } + }); + } + + private void StartReconnectionLoop() + { + // fast exit if already running + if(Interlocked.Exchange(ref _reconnectInProgress, 1) == 1) + return; + + // reset/replace CTS defensively + try { _reconnectCts.Cancel(); } catch { /* ignore */ } + _reconnectCts.Dispose(); + _reconnectCts = new CancellationTokenSource(); + + Interlocked.Exchange(ref _reconnectAttempts, 0); + _logger.LogWarning("Starting reconnection loop."); + + _reconnectLoop = Task.Run(async () => + { + var token = _reconnectCts.Token; + + while(!token.IsCancellationRequested) + { + try + { + await _deviceAdapter.UpdateConnectionStatus().ConfigureAwait(false); + + var attempts = Interlocked.Increment(ref _reconnectAttempts); + + // linear backoff (cap 30s) + var baseSeconds = Math.Min(attempts * 5, 30); + + // jitter ±20% + var jitter = (int)Math.Round(baseSeconds * 0.2); + var waitSeconds = Math.Max(1, baseSeconds + Random.Shared.Next(-jitter, jitter + 1)); + + _logger.LogInformation("Reconnect attempt {Attempt}. Waiting {Delay}s.", attempts, waitSeconds); + await Task.Delay(TimeSpan.FromSeconds(waitSeconds), token).ConfigureAwait(false); + } + catch(OperationCanceledException) + { + break; // expected on cancellation + } + catch(Exception ex) + { + _logger.LogError(ex, "Reconnection attempt errored."); + try { await Task.Delay(TimeSpan.FromSeconds(2), token).ConfigureAwait(false); } catch { break; } + } + } + }, _reconnectCts.Token); + } + + private async Task EndReconnectionLoop() + { + // only if we were running + if(Interlocked.Exchange(ref _reconnectInProgress, 0) == 0) + return; + + _logger.LogInformation("Ending reconnection loop."); + + try + { + await _reconnectCts.CancelAsync().ConfigureAwait(false); + try + { + await _reconnectLoop.ConfigureAwait(false); + } + catch(OperationCanceledException) + { + // ignore + } + } + finally + { + _reconnectCts.Dispose(); + _reconnectCts = new CancellationTokenSource(); + Interlocked.Exchange(ref _reconnectAttempts, 0); + } + } + + public void Dispose() + { + // best-effort, non-throwing shutdown + try { _deviceStatusWatcher.Dispose(); } catch { /* ignore */ } + + try + { + // cancel loop quickly; avoid long blocking in Dispose() + _ = EndReconnectionLoop(); + } + catch { /* ignore */ } + + GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/UI/Backend/ViewModels/SerialDeviceConnectorViewModel.cs b/UI/Infrastructure/Devices/SerialDeviceConnectorViewModel.cs similarity index 95% rename from UI/Backend/ViewModels/SerialDeviceConnectorViewModel.cs rename to UI/Infrastructure/Devices/SerialDeviceConnectorViewModel.cs index 13083e74..0d7b3eec 100644 --- a/UI/Backend/ViewModels/SerialDeviceConnectorViewModel.cs +++ b/UI/Infrastructure/Devices/SerialDeviceConnectorViewModel.cs @@ -1,98 +1,99 @@ -using System.Collections.ObjectModel; -using System.Reactive.Disposables; -using Ares.Datamodel.Device; -using Ares.Services.Device; -using DynamicData; -using ReactiveUI; - -namespace UI.Backend.ViewModels; - -public abstract class SerialDeviceConnectorViewModel : ReactiveObject, IAsyncDisposable where TDeviceUnitVm : DeviceUnitControlViewModel -{ - private readonly ISourceCache _connectedSerialDeviceUnitControlVmsSource = - new SourceCache(vm => vm.DeviceId); - private readonly AresDevices.AresDevicesClient _devicesClient; - private IDisposable _deviceUpdater = Disposable.Empty; - private CancellationTokenSource _deviceUpdaterTokenSource = new CancellationTokenSource(); - - public SerialDeviceConnectorViewModel(AresDevices.AresDevicesClient devicesClient) - { - _devicesClient = devicesClient; - - _connectedSerialDeviceUnitControlVmsSource.Connect().Bind(out var connectedSerialDeviceUnitControlVms).Subscribe(); - ConnectedSerialDeviceUnitControlVms = connectedSerialDeviceUnitControlVms; - } - - protected abstract Task GetDeviceDescriptions(); - - public void Start(TimeSpan interval) - { - _deviceUpdater = Task.Factory.StartNew(async () => - { - while(!_deviceUpdaterTokenSource.Token.IsCancellationRequested) - { - await UpdateAvailableDevices(); - await Task.Delay(interval, _deviceUpdaterTokenSource.Token); - } - }, _deviceUpdaterTokenSource.Token); - } - - private async Task UpdateAvailableDevices() - { - var descriptions = await GetDeviceDescriptions(); - foreach(var description in descriptions) - { - var deviceStatusRequest = new DeviceStatusRequest { DeviceId = description.Id }; - var deviceOperationalStatusResponse = await _devicesClient.GetDeviceStatusAsync(deviceStatusRequest); - - if(deviceOperationalStatusResponse.OperationalState == OperationalState.Active) - { - if(ConnectedSerialDeviceUnitControlVms.Any(vm => vm.DeviceId == description.Id)) - continue; - - var unitVm = CreateUnitVm(description); - _connectedSerialDeviceUnitControlVmsSource.AddOrUpdate(unitVm); - continue; - } - - if(deviceOperationalStatusResponse.OperationalState == OperationalState.Inactive) - { - if(ConnectedSerialDeviceUnitControlVms.Any(vm => vm.DeviceId == description.Id)) - _connectedSerialDeviceUnitControlVmsSource.Remove(description.Id); - - continue; - } - - if(deviceOperationalStatusResponse.OperationalState == OperationalState.Error) - if(ConnectedSerialDeviceUnitControlVms.Any(vm => vm.DeviceId == description.Id)) - _connectedSerialDeviceUnitControlVmsSource.Remove(description.Id); - } - } - - protected abstract TDeviceUnitVm CreateUnitVm(AresDeviceDescription description); - - protected AresDevices.AresDevicesClient DevicesClient => _devicesClient; - - public async ValueTask DisposeAsync() - { - _deviceUpdaterTokenSource.Cancel(); - _deviceUpdater.Dispose(); - var vms = ConnectedSerialDeviceUnitControlVms.OfType(); - foreach(var vm in vms) - { - await vm.DisposeAsync(); - } - } - - public string[]? DiscoveredSerialPorts { get; set; } - - public string? SelectedSerialPort { get; set; } - - public string[]? DiscoveredDeviceNames { get; set; } - - //public string? SelectedDeviceName { get; set; } - - public string? SelectedDeviceId { get; set; } - public ReadOnlyObservableCollection ConnectedSerialDeviceUnitControlVms { get; } - -} \ No newline at end of file +using System.Collections.ObjectModel; +using System.Reactive.Disposables; +using Ares.Datamodel.Device; +using Ares.Services.Device; +using DynamicData; +using ReactiveUI; +using UI.Application.Devices; + +namespace UI.Infrastructure.Devices; + +public abstract class SerialDeviceConnectorViewModel : ReactiveObject, IAsyncDisposable where TDeviceUnitVm : DeviceUnitControlViewModel +{ + private readonly ISourceCache _connectedSerialDeviceUnitControlVmsSource = + new SourceCache(vm => vm.DeviceId); + private readonly AresDevices.AresDevicesClient _devicesClient; + private IDisposable _deviceUpdater = Disposable.Empty; + private CancellationTokenSource _deviceUpdaterTokenSource = new CancellationTokenSource(); + + public SerialDeviceConnectorViewModel(AresDevices.AresDevicesClient devicesClient) + { + _devicesClient = devicesClient; + + _connectedSerialDeviceUnitControlVmsSource.Connect().Bind(out var connectedSerialDeviceUnitControlVms).Subscribe(); + ConnectedSerialDeviceUnitControlVms = connectedSerialDeviceUnitControlVms; + } + + protected abstract Task GetDeviceDescriptions(); + + public void Start(TimeSpan interval) + { + _deviceUpdater = Task.Factory.StartNew(async () => + { + while(!_deviceUpdaterTokenSource.Token.IsCancellationRequested) + { + await UpdateAvailableDevices(); + await Task.Delay(interval, _deviceUpdaterTokenSource.Token); + } + }, _deviceUpdaterTokenSource.Token); + } + + private async Task UpdateAvailableDevices() + { + var descriptions = await GetDeviceDescriptions(); + foreach(var description in descriptions) + { + var deviceStatusRequest = new DeviceStatusRequest { DeviceId = description.Id }; + var deviceOperationalStatusResponse = await _devicesClient.GetDeviceStatusAsync(deviceStatusRequest); + + if(deviceOperationalStatusResponse.OperationalState == OperationalState.Active) + { + if(ConnectedSerialDeviceUnitControlVms.Any(vm => vm.DeviceId == description.Id)) + continue; + + var unitVm = CreateUnitVm(description); + _connectedSerialDeviceUnitControlVmsSource.AddOrUpdate(unitVm); + continue; + } + + if(deviceOperationalStatusResponse.OperationalState == OperationalState.Inactive) + { + if(ConnectedSerialDeviceUnitControlVms.Any(vm => vm.DeviceId == description.Id)) + _connectedSerialDeviceUnitControlVmsSource.Remove(description.Id); + + continue; + } + + if(deviceOperationalStatusResponse.OperationalState == OperationalState.Error) + if(ConnectedSerialDeviceUnitControlVms.Any(vm => vm.DeviceId == description.Id)) + _connectedSerialDeviceUnitControlVmsSource.Remove(description.Id); + } + } + + protected abstract TDeviceUnitVm CreateUnitVm(AresDeviceDescription description); + + protected AresDevices.AresDevicesClient DevicesClient => _devicesClient; + + public async ValueTask DisposeAsync() + { + _deviceUpdaterTokenSource.Cancel(); + _deviceUpdater.Dispose(); + var vms = ConnectedSerialDeviceUnitControlVms.OfType(); + foreach(var vm in vms) + { + await vm.DisposeAsync(); + } + } + + public string[]? DiscoveredSerialPorts { get; set; } + + public string? SelectedSerialPort { get; set; } + + public string[]? DiscoveredDeviceNames { get; set; } + + //public string? SelectedDeviceName { get; set; } + + public string? SelectedDeviceId { get; set; } + public ReadOnlyObservableCollection ConnectedSerialDeviceUnitControlVms { get; } + +} diff --git a/UI/Backend/ViewModels/UsbDeviceConnectorViewModel.cs b/UI/Infrastructure/Devices/UsbDeviceConnectorViewModel.cs similarity index 96% rename from UI/Backend/ViewModels/UsbDeviceConnectorViewModel.cs rename to UI/Infrastructure/Devices/UsbDeviceConnectorViewModel.cs index 1297389f..d0400550 100644 --- a/UI/Backend/ViewModels/UsbDeviceConnectorViewModel.cs +++ b/UI/Infrastructure/Devices/UsbDeviceConnectorViewModel.cs @@ -1,12 +1,13 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Reactive.Disposables; using System.Reactive.Linq; using Ares.Datamodel.Device; using Ares.Services.Device; using DynamicData; using ReactiveUI; +using UI.Application.Devices; -namespace UI.Backend.ViewModels; +namespace UI.Infrastructure.Devices; public abstract class UsbDeviceConnectorViewModel : ReactiveObject, IAsyncDisposable where TDeviceUnitVm : DeviceUnitControlViewModel { @@ -81,3 +82,4 @@ public async ValueTask DisposeAsync() public ReadOnlyObservableCollection ConnectedUsbDeviceUnitControlVms { get; } } + diff --git a/UI/Services/Dialog/RadzenUiDialogService.cs b/UI/Infrastructure/Dialog/RadzenUiDialogService.cs similarity index 91% rename from UI/Services/Dialog/RadzenUiDialogService.cs rename to UI/Infrastructure/Dialog/RadzenUiDialogService.cs index 3558f030..51a2acf9 100644 --- a/UI/Services/Dialog/RadzenUiDialogService.cs +++ b/UI/Infrastructure/Dialog/RadzenUiDialogService.cs @@ -1,6 +1,7 @@ using Radzen; +using UI.Application.Dialog; -namespace UI.Services.Dialog; +namespace UI.Infrastructure.Dialog; internal sealed class RadzenUiDialogService : IUiDialogService { @@ -27,3 +28,4 @@ public async Task ConfirmAsync(string message, string? title = null, UiCon return result.GetValueOrDefault(); } } + diff --git a/UI/Services/Grpc/ClientManager.cs b/UI/Infrastructure/Grpc/ClientManager.cs similarity index 93% rename from UI/Services/Grpc/ClientManager.cs rename to UI/Infrastructure/Grpc/ClientManager.cs index 42d50bcb..4ebb92ab 100644 --- a/UI/Services/Grpc/ClientManager.cs +++ b/UI/Infrastructure/Grpc/ClientManager.cs @@ -1,74 +1,74 @@ -using Grpc.Core; -using Grpc.Net.Client; -using Microsoft.Extensions.Options; -using System.Security.Cryptography.X509Certificates; -using UI.Authentication; -using UI.Settings; - -namespace UI.Services.Grpc; - -internal class ClientManager : IClientManager -{ - private readonly AresAuthenticationState _authenticationState; - private readonly CertificateSettings _certificateOptions; - private readonly ILogger _logger; - private readonly RemoteServiceSettings _remoteServiceSettings; - private GrpcChannel? _channel; - - public ClientManager(IOptions remoteOptions, - IOptions certificateOptions, - AresAuthenticationState authenticationState, - ILogger logger) - { - _certificateOptions = certificateOptions.Value; - _authenticationState = authenticationState; - _logger = logger; - _remoteServiceSettings = remoteOptions.Value; - CreateChannel().GetAwaiter().GetResult(); - } - - private async Task CreateChannel() - { - if (_channel is not null) - { - await _channel.ShutdownAsync(); - _channel.Dispose(); - } - - if (_remoteServiceSettings.ServerPort == 0 || string.IsNullOrEmpty(_remoteServiceSettings.ServerHost)) - return; - - var handler = new HttpClientHandler(); - try - { - var cert = new X509Certificate2(_certificateOptions.Path ?? "", _certificateOptions.Password); - handler.ClientCertificates.Add(cert); - handler.CheckCertificateRevocationList = false; - } - catch (Exception) - { - _logger.LogWarning("Unable to create a secure gRPC channel as the specified certificate was not found. Ensure that appsettings.json has the correct certificate path."); - // throw new InvalidOperationException("Unable to create a secure gRPC channel as the specified certificate was not found. Ensure that appsettings.json has the correct certificate path."); - } - - var serverUri = new UriBuilder("https", _remoteServiceSettings.ServerHost, _remoteServiceSettings.ServerPort ?? 443).Uri; - var opts = new GrpcChannelOptions - { - HttpHandler = handler - }; - - _channel = GrpcChannel.ForAddress(serverUri, opts); - } - - public T GetClient() where T : ClientBase - { - if (_channel is null) - throw new NullReferenceException($"Couldn't create a client as {nameof(GrpcChannel)} was not created"); - - var client = (T?)Activator.CreateInstance(typeof(T), _channel); - if (client is null) - throw new InvalidOperationException($"Unable to create client of type {nameof(T)}"); - - return client; - } -} +using Grpc.Core; +using Grpc.Net.Client; +using Microsoft.Extensions.Options; +using System.Security.Cryptography.X509Certificates; +using UI.Application.Settings; +using UI.Infrastructure.Auth; + +namespace UI.Infrastructure.Grpc; + +internal class ClientManager : IClientManager +{ + private readonly AresAuthenticationState _authenticationState; + private readonly CertificateSettings _certificateOptions; + private readonly ILogger _logger; + private readonly RemoteServiceSettings _remoteServiceSettings; + private GrpcChannel? _channel; + + public ClientManager(IOptions remoteOptions, + IOptions certificateOptions, + AresAuthenticationState authenticationState, + ILogger logger) + { + _certificateOptions = certificateOptions.Value; + _authenticationState = authenticationState; + _logger = logger; + _remoteServiceSettings = remoteOptions.Value; + CreateChannel().GetAwaiter().GetResult(); + } + + private async Task CreateChannel() + { + if (_channel is not null) + { + await _channel.ShutdownAsync(); + _channel.Dispose(); + } + + if (_remoteServiceSettings.ServerPort == 0 || string.IsNullOrEmpty(_remoteServiceSettings.ServerHost)) + return; + + var handler = new HttpClientHandler(); + try + { + var cert = new X509Certificate2(_certificateOptions.Path ?? "", _certificateOptions.Password); + handler.ClientCertificates.Add(cert); + handler.CheckCertificateRevocationList = false; + } + catch (Exception) + { + _logger.LogWarning("Unable to create a secure gRPC channel as the specified certificate was not found. Ensure that appsettings.json has the correct certificate path."); + // throw new InvalidOperationException("Unable to create a secure gRPC channel as the specified certificate was not found. Ensure that appsettings.json has the correct certificate path."); + } + + var serverUri = new UriBuilder("https", _remoteServiceSettings.ServerHost, _remoteServiceSettings.ServerPort ?? 443).Uri; + var opts = new GrpcChannelOptions + { + HttpHandler = handler + }; + + _channel = GrpcChannel.ForAddress(serverUri, opts); + } + + public T GetClient() where T : ClientBase + { + if (_channel is null) + throw new NullReferenceException($"Couldn't create a client as {nameof(GrpcChannel)} was not created"); + + var client = (T?)Activator.CreateInstance(typeof(T), _channel); + if (client is null) + throw new InvalidOperationException($"Unable to create client of type {nameof(T)}"); + + return client; + } +} diff --git a/UI/Services/Grpc/IClientManager.cs b/UI/Infrastructure/Grpc/IClientManager.cs similarity index 61% rename from UI/Services/Grpc/IClientManager.cs rename to UI/Infrastructure/Grpc/IClientManager.cs index 834e8a4d..2b364d46 100644 --- a/UI/Services/Grpc/IClientManager.cs +++ b/UI/Infrastructure/Grpc/IClientManager.cs @@ -1,8 +1,8 @@ -using Grpc.Core; - -namespace UI.Services.Grpc; - -public interface IClientManager -{ - public T GetClient() where T : ClientBase; -} +using Grpc.Core; + +namespace UI.Infrastructure.Grpc; + +public interface IClientManager +{ + public T GetClient() where T : ClientBase; +} diff --git a/UI/JsInterops/CompletionItemExtensions.cs b/UI/Infrastructure/Monaco/Interops/CompletionItemExtensions.cs similarity index 97% rename from UI/JsInterops/CompletionItemExtensions.cs rename to UI/Infrastructure/Monaco/Interops/CompletionItemExtensions.cs index 4370b2df..e08a53f0 100644 --- a/UI/JsInterops/CompletionItemExtensions.cs +++ b/UI/Infrastructure/Monaco/Interops/CompletionItemExtensions.cs @@ -3,7 +3,7 @@ using AresCompletionItem = Ares.Datamodel.Scripting.CompletionItem; using AresCompletionItemKind = Ares.Datamodel.Scripting.CompletionItemKind; -namespace UI.JsInterops; +namespace UI.Infrastructure.Monaco.Interops; public static class CompletionItemExtensions { diff --git a/UI/JsInterops/MonacoCompletionProvider.cs b/UI/Infrastructure/Monaco/Interops/MonacoCompletionProvider.cs similarity index 82% rename from UI/JsInterops/MonacoCompletionProvider.cs rename to UI/Infrastructure/Monaco/Interops/MonacoCompletionProvider.cs index 6756bb72..780d23a8 100644 --- a/UI/JsInterops/MonacoCompletionProvider.cs +++ b/UI/Infrastructure/Monaco/Interops/MonacoCompletionProvider.cs @@ -1,10 +1,11 @@ using Ares.Services; using Microsoft.JSInterop; +using UI.Application.Scripting; using MonacoCompletionItem = BlazorMonaco.Languages.CompletionItem; -namespace UI.JsInterops; +namespace UI.Infrastructure.Monaco.Interops; -public class MonacoCompletionProvider(AresScriptingService.AresScriptingServiceClient aresScriptingServiceClient) +public class MonacoCompletionProvider(AresScriptingService.AresScriptingServiceClient aresScriptingServiceClient) : IMonacoCompletionProvider { private readonly AresScriptingService.AresScriptingServiceClient _aresScriptingServiceClient = aresScriptingServiceClient; @@ -23,3 +24,4 @@ public async Task GetCompletionItems(string script, int } } + diff --git a/UI/JsInterops/MonacoDiagnosticsProvider.cs b/UI/Infrastructure/Monaco/Interops/MonacoDiagnosticsProvider.cs similarity index 72% rename from UI/JsInterops/MonacoDiagnosticsProvider.cs rename to UI/Infrastructure/Monaco/Interops/MonacoDiagnosticsProvider.cs index ad6472a3..304604bb 100644 --- a/UI/JsInterops/MonacoDiagnosticsProvider.cs +++ b/UI/Infrastructure/Monaco/Interops/MonacoDiagnosticsProvider.cs @@ -1,9 +1,10 @@ using Ares.Services; using Microsoft.JSInterop; +using UI.Application.Scripting; -namespace UI.JsInterops; +namespace UI.Infrastructure.Monaco.Interops; -public sealed class MonacoDiagnosticsProvider(AresScriptingService.AresScriptingServiceClient aresScriptingServiceClient) +public sealed class MonacoDiagnosticsProvider(AresScriptingService.AresScriptingServiceClient aresScriptingServiceClient) : IMonacoDiagnosticsProvider { private readonly AresScriptingService.AresScriptingServiceClient _aresScriptingServiceClient = aresScriptingServiceClient; @@ -26,12 +27,5 @@ public async Task GetDiagnostics(string script) )).ToArray(); } - public record MonacoDiagnostic( - int StartLineNumber, - int StartColumn, - int EndLineNumber, - int EndColumn, - string Message, - int Severity, - string? Code); } + diff --git a/UI/JsInterops/MonacoHoverProvider.cs b/UI/Infrastructure/Monaco/Interops/MonacoHoverProvider.cs similarity index 97% rename from UI/JsInterops/MonacoHoverProvider.cs rename to UI/Infrastructure/Monaco/Interops/MonacoHoverProvider.cs index cf91eba8..2069df83 100644 --- a/UI/JsInterops/MonacoHoverProvider.cs +++ b/UI/Infrastructure/Monaco/Interops/MonacoHoverProvider.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Antlr4.Runtime; using Ares.Datamodel; using Ares.Datamodel.Extensions; @@ -11,13 +7,13 @@ using AresScript; using AresScript.Generated; using AresScript.Interpreters; -using AresScript.ScriptAnalysis; using Google.Protobuf.WellKnownTypes; using Microsoft.JSInterop; +using UI.Application.Scripting; -namespace UI.JsInterops; +namespace UI.Infrastructure.Monaco.Interops; -public sealed class MonacoHoverProvider(AresScriptingService.AresScriptingServiceClient aresScriptingServiceClient) +public sealed class MonacoHoverProvider(AresScriptingService.AresScriptingServiceClient aresScriptingServiceClient) : IMonacoHoverProvider { private readonly AresScriptingService.AresScriptingServiceClient _aresScriptingServiceClient = aresScriptingServiceClient; private AutocompleteCatalog? _cachedCatalog; @@ -278,3 +274,4 @@ private static string AppendInferredSchema(string? markdown, string identifier, return sb.ToString(); } } + diff --git a/UI/JsInterops/MonacoSemanticTokensProvider.cs b/UI/Infrastructure/Monaco/Interops/MonacoSemanticTokensProvider.cs similarity index 93% rename from UI/JsInterops/MonacoSemanticTokensProvider.cs rename to UI/Infrastructure/Monaco/Interops/MonacoSemanticTokensProvider.cs index 293375e9..e7debeec 100644 --- a/UI/JsInterops/MonacoSemanticTokensProvider.cs +++ b/UI/Infrastructure/Monaco/Interops/MonacoSemanticTokensProvider.cs @@ -3,10 +3,11 @@ using AresScript; using AresScript.Generated; using Antlr4.Runtime.Misc; +using UI.Application.Scripting; -namespace UI.JsInterops; +namespace UI.Infrastructure.Monaco.Interops; -public sealed class MonacoSemanticTokensProvider +public sealed class MonacoSemanticTokensProvider : IMonacoSemanticTokensProvider { [JSInvokable] public SemanticToken[] GetSemanticTokens(string script) @@ -34,8 +35,6 @@ public SemanticToken[] GetSemanticTokens(string script) } } - public record SemanticToken(int Line, int StartColumn, int Length, string Type); - private sealed class SemanticTokenCollector : AresLangBaseVisitor { public List Tokens { get; } = []; @@ -95,3 +94,4 @@ private void AddToken(IToken token, string type) } } } + diff --git a/UI/Scripts/monaco/ares-autocomplete-setup.ts b/UI/Infrastructure/Monaco/Scripts/ares-autocomplete-setup.ts similarity index 100% rename from UI/Scripts/monaco/ares-autocomplete-setup.ts rename to UI/Infrastructure/Monaco/Scripts/ares-autocomplete-setup.ts diff --git a/UI/Scripts/monaco/ares-diagnostics-setup.ts b/UI/Infrastructure/Monaco/Scripts/ares-diagnostics-setup.ts similarity index 100% rename from UI/Scripts/monaco/ares-diagnostics-setup.ts rename to UI/Infrastructure/Monaco/Scripts/ares-diagnostics-setup.ts diff --git a/UI/Scripts/monaco/ares-hover-setup.ts b/UI/Infrastructure/Monaco/Scripts/ares-hover-setup.ts similarity index 100% rename from UI/Scripts/monaco/ares-hover-setup.ts rename to UI/Infrastructure/Monaco/Scripts/ares-hover-setup.ts diff --git a/UI/Scripts/monaco/ares-semantic-setup.ts b/UI/Infrastructure/Monaco/Scripts/ares-semantic-setup.ts similarity index 100% rename from UI/Scripts/monaco/ares-semantic-setup.ts rename to UI/Infrastructure/Monaco/Scripts/ares-semantic-setup.ts diff --git a/UI/Scripts/monaco/ares-setup.ts b/UI/Infrastructure/Monaco/Scripts/ares-setup.ts similarity index 100% rename from UI/Scripts/monaco/ares-setup.ts rename to UI/Infrastructure/Monaco/Scripts/ares-setup.ts diff --git a/UI/Scripts/monaco/areslang.language-configuration.ts b/UI/Infrastructure/Monaco/Scripts/areslang.language-configuration.ts similarity index 100% rename from UI/Scripts/monaco/areslang.language-configuration.ts rename to UI/Infrastructure/Monaco/Scripts/areslang.language-configuration.ts diff --git a/UI/Scripts/monaco/areslang.monarch.ts b/UI/Infrastructure/Monaco/Scripts/areslang.monarch.ts similarity index 100% rename from UI/Scripts/monaco/areslang.monarch.ts rename to UI/Infrastructure/Monaco/Scripts/areslang.monarch.ts diff --git a/UI/Scripts/globals.d.ts b/UI/Infrastructure/Monaco/Scripts/globals.d.ts similarity index 100% rename from UI/Scripts/globals.d.ts rename to UI/Infrastructure/Monaco/Scripts/globals.d.ts diff --git a/UI/Services/Notification/NotificationReceivingService.cs b/UI/Infrastructure/Notifications/NotificationReceivingService.cs similarity index 97% rename from UI/Services/Notification/NotificationReceivingService.cs rename to UI/Infrastructure/Notifications/NotificationReceivingService.cs index 957e420c..61c48e1f 100644 --- a/UI/Services/Notification/NotificationReceivingService.cs +++ b/UI/Infrastructure/Notifications/NotificationReceivingService.cs @@ -2,9 +2,9 @@ using Google.Protobuf.WellKnownTypes; using Grpc.Core; using NuGet.Packaging; -using UI.Backend.Notifications; +using UI.Application.Notifications; -namespace UI.Services.Notification; +namespace UI.Infrastructure.Notifications; public class NotificationReceivingService : INotificationReceivingService { @@ -111,3 +111,4 @@ private static UiNotificationSeverity ConvertToUiSeverity(Severity aresSeverity) }; } } + diff --git a/UI/Infrastructure/Notifications/NotificationRepository.cs b/UI/Infrastructure/Notifications/NotificationRepository.cs new file mode 100644 index 00000000..fb4556d1 --- /dev/null +++ b/UI/Infrastructure/Notifications/NotificationRepository.cs @@ -0,0 +1,9 @@ +using Ares.Services; +using UI.Application.Notifications; + +namespace UI.Infrastructure.Notifications; + +public class NotificationRepository : List, INotificationRepository +{ +} + diff --git a/UI/Services/Notification/RadzenUiNotificationService.cs b/UI/Infrastructure/Notifications/RadzenUiNotificationService.cs similarity index 92% rename from UI/Services/Notification/RadzenUiNotificationService.cs rename to UI/Infrastructure/Notifications/RadzenUiNotificationService.cs index 2d25ad06..0e75a706 100644 --- a/UI/Services/Notification/RadzenUiNotificationService.cs +++ b/UI/Infrastructure/Notifications/RadzenUiNotificationService.cs @@ -1,6 +1,7 @@ using Radzen; +using UI.Application.Notifications; -namespace UI.Services.Notification; +namespace UI.Infrastructure.Notifications; internal sealed class RadzenUiNotificationService : IUiNotificationService { @@ -36,3 +37,4 @@ private static NotificationSeverity ConvertSeverity(UiNotificationSeverity sever }; } } + diff --git a/UI/Data/ApplicationDbContext.cs b/UI/Infrastructure/Persistence/ApplicationDbContext.cs similarity index 84% rename from UI/Data/ApplicationDbContext.cs rename to UI/Infrastructure/Persistence/ApplicationDbContext.cs index 9006400b..48869e2e 100644 --- a/UI/Data/ApplicationDbContext.cs +++ b/UI/Infrastructure/Persistence/ApplicationDbContext.cs @@ -1,12 +1,12 @@ using Microsoft.EntityFrameworkCore; - -namespace UI.Data -{ + +namespace UI.Infrastructure.Persistence +{ public class ApplicationDbContext : DbContext - { - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } - } + { + public ApplicationDbContext(DbContextOptions options) + : base(options) + { + } + } } diff --git a/UI/Pages/Automation/CustomStepBuilder.razor b/UI/Pages/Automation/CustomStepBuilder.razor deleted file mode 100644 index 4134efbe..00000000 --- a/UI/Pages/Automation/CustomStepBuilder.razor +++ /dev/null @@ -1,27 +0,0 @@ -@page "/automation/customstepbuilder" -@inject ContextMenuService ContextMenuService -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase - -Custom Step Builder - - - - -@code { - - - void ShowContextMenuWithItems(MouseEventArgs args) - { - ContextMenuService.Open(args, - new List { - new(){ Text = "Context menu item 1", Value = 1 }, - new(){ Text = "Context menu item 2", Value = 2 }, - new(){ Text = "Context menu item 3", Value = 3 }, - }, OnMenuItemClick); - } - - void OnMenuItemClick(MenuItemEventArgs args) - { - ContextMenuService.Close(); - } -} \ No newline at end of file diff --git a/UI/Pages/Index.razor b/UI/Pages/Index.razor index 2beeb74d..11ca5008 100644 --- a/UI/Pages/Index.razor +++ b/UI/Pages/Index.razor @@ -1,31 +1,16 @@ -@page "/" -@using UI.Backend.Devices -@using UI.Backend.Repos -@using UI.Backend.ViewModels -@using UI.Pages.Shared.Devices.CM3Camera -@using UI.Pages.Shared.Devices.ChemyxPump -@using UI.Pages.Shared.Devices.DataLogger -@using UI.Pages.Shared.Devices.LaserChiller -@using UI.Pages.Shared.Devices.Mfc -@using UI.Pages.Shared.Devices.Remote -@using UI.Pages.Shared.Devices.RestDevice -@using UI.Pages.Shared.Devices.SerialRestDevice -@using UI.Pages.Shared.Devices.SyringePump -@using UI.Pages.Shared.Devices.TubeFurnace -@using UI.Pages.Shared.Devices.StepperController -@using UI.Pages.Shared.Devices.ValveController -@using UI.Pages.Shared.Devices.Servo -@using UI.Pages.Shared.Devices.VerdiV6Laser -@using UI.Pages.Shared.Misc +@page "/" +@using UI.Application.Devices +@using UI.Application.Devices.Repos +@using UI.Features.DeviceStateLogging +@using UI.Features.Devices.Shared -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase @inject IJSRuntime JS @inject IDeviceControlViewModelRepo ViewModelRepo Dashboard
- +
@@ -96,4 +81,4 @@ } _objRef?.Dispose(); } -} \ No newline at end of file +} diff --git a/UI/NotFoundPage.razor b/UI/Pages/NotFound.razor similarity index 94% rename from UI/NotFoundPage.razor rename to UI/Pages/NotFound.razor index 5532768b..eec6a58d 100644 --- a/UI/NotFoundPage.razor +++ b/UI/Pages/NotFound.razor @@ -1,5 +1,6 @@ @page "/404" @inject NavigationManager Nav +@layout MainLayout Not Found diff --git a/UI/Pages/Project.razor b/UI/Pages/Project.razor deleted file mode 100644 index 44188af8..00000000 --- a/UI/Pages/Project.razor +++ /dev/null @@ -1,25 +0,0 @@ -@page "/project" -@inject ContextMenuService ContextMenuService -@inherits ReactiveUI.Blazor.ReactiveInjectableComponentBase - -Project - - - - -@code { - void ShowContextMenuWithItems(MouseEventArgs args) - { - ContextMenuService.Open(args, - new List { - new(){ Text = "Context menu item 1", Value = 1 }, - new(){ Text = "Context menu item 2", Value = 2 }, - new(){ Text = "Context menu item 3", Value = 3 }, - }, OnMenuItemClick); - } - - void OnMenuItemClick(MenuItemEventArgs args) - { - ContextMenuService.Close(); - } -} \ No newline at end of file diff --git a/UI/Pages/Shared/Execution/ExecutionController.razor b/UI/Pages/Shared/Execution/ExecutionController.razor deleted file mode 100644 index d15bb347..00000000 --- a/UI/Pages/Shared/Execution/ExecutionController.razor +++ /dev/null @@ -1,5 +0,0 @@ -

ExecutionController

- -@code { - -} diff --git a/UI/Pages/Shared/Execution/ExecutionMonitor.razor b/UI/Pages/Shared/Execution/ExecutionMonitor.razor deleted file mode 100644 index fbfbe07d..00000000 --- a/UI/Pages/Shared/Execution/ExecutionMonitor.razor +++ /dev/null @@ -1,5 +0,0 @@ -

ExecutionMonitor

- -@code { - -} diff --git a/UI/Pages/Shared/Settings/PlaceholderSettings.razor b/UI/Pages/Shared/Settings/PlaceholderSettings.razor deleted file mode 100644 index fe8cc907..00000000 --- a/UI/Pages/Shared/Settings/PlaceholderSettings.razor +++ /dev/null @@ -1,22 +0,0 @@ -@page "/settings/amogus" -@layout SettingsLayout - - - ⠀⠀⠀ ⠀⠀ ⣠⡴⠞⠛⠛⠛⠛⢶⣄⠀⠀
-⠀⠀⠀⢀⡾⠃⠀⠀⠀⠀⠀⠀⠀⠈⢷⡄⠀⠀
-⠀⠀⣼⡷⠶⠒⠒⠒⠶⢶⣄⠀ ⠀⢻⡆⠀⠀⠀
-⢠⡞⠁⠀⠀⠀⠀⠀⠀⠀⢻⡄⠀⠈⣧⠀⠀⠀⠀⠀
-⢸⡅⠀⠀⠀⠀⠀⠀⠀⠀⣼⠃⠀⠀⣿⠛⠛⢻⡆⠀⠀
-⠀⢹⡷⠶⣤⣤⣤⣤⣤⡾⠃⠀⠀⢻⠀⠀⠈⣧⠀⠀
-⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡄⠀⠀⣿⠀⠀⠀
-⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⣿⠀⠀⠀
-⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⣿⠀⠀⠀
-⠀⠸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢷⣤⡴⠟⠀⠀⠀
-⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠶⢤⣤⣤⠶⢾⡿
-⠀⠀⠸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡾⠋
-⠀⠀⠀⠈⠛⠶⠶⠶⣤⣀⠀⠀⠀⠀⢠⡶⠞⠛⠒⠛⠋⠁⠀⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠳⠶⠴⠶⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
-
-@code { - -} diff --git a/UI/Pages/Shared/Settings/PlaceholderSettings.razor.css b/UI/Pages/Shared/Settings/PlaceholderSettings.razor.css deleted file mode 100644 index 5cb1baed..00000000 --- a/UI/Pages/Shared/Settings/PlaceholderSettings.razor.css +++ /dev/null @@ -1,7 +0,0 @@ -.amogus { - color: red; -} - -.amogus-visor { - color: lightcyan; -} diff --git a/UI/Program.cs b/UI/Program.cs index cb3aaaed..7c7c7a97 100644 --- a/UI/Program.cs +++ b/UI/Program.cs @@ -3,11 +3,12 @@ using Radzen; using Serilog; using UI; -using UI.Backend.Helpers; -using UI.Data; -using UI.Services.Grpc; -using UI.Services.Notification; -using UI.Settings; +using UI.Infrastructure.Grpc; +using UI.Application.Notifications; +using UI.Infrastructure.Notifications; +using UI.Infrastructure.Persistence; +using UI.Components.Formatting; +using UI.Application.Settings; var builder = WebApplication.CreateBuilder(args); @@ -110,3 +111,4 @@ static void ConfigureDatabaseServices(IServiceCollection services, Configuration throw new InvalidOperationException($"Unsupported database provider: {provider}. Available provider values: {string.Join(',', sqlConnectionStrings.AsEnumerable().Select(scs => scs.Key.Split(':').LastOrDefault()).Where(s => s != "ConnectionStrings"))}"); } } + diff --git a/UI/Routes.razor b/UI/Routes.razor index 5ad737e6..dfd0171c 100644 --- a/UI/Routes.razor +++ b/UI/Routes.razor @@ -1,11 +1,7 @@ - +@using UI.Pages + - - - - - \ No newline at end of file diff --git a/UI/Scripts/monaco/README.md b/UI/Scripts/monaco/README.md deleted file mode 100644 index 2b3d12c1..00000000 --- a/UI/Scripts/monaco/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Monaco Editor ARES Language Integration - -This directory contains the Monaco Editor language definition for ARES. - -## Integration with BlazorMonaco - -To use this language in a Blazor project using the `BlazorMonaco` package, follow these steps: - -1. **Compile the scripts**: Ensure TypeScript is compiled so the output lives under `wwwroot/js/monaco/`. -2. **Register the language**: Import and call the registration function in your Razor component. - -### Example Razor Component Usage - -```csharp -@inject IJSRuntime JSRuntime - - - -@code { - private MonacoEditor _editor; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - // Import the setup script as a JS Module - // Ensure the path matches your actual file structure in wwwroot - var module = await JSRuntime.InvokeAsync("import", "./js/monaco/ares-setup.js"); - - // Register the language with Monaco - await module.InvokeVoidAsync("registerAresLanguage"); - } - } - - private StandaloneEditorConstructionOptions EditorConstructionOptions(MonacoEditor editor) - { - return new StandaloneEditorConstructionOptions - { - Language = "ares", - Theme = "vs-dark", - AutomaticLayout = true, - Value = "# Welcome to ARES\ndef main():\n return True\n" - }; - } -} -``` - -## Files -- `areslang.monarch.ts`: Tokenization rules (syntax highlighting). -- `areslang.language-configuration.ts`: Language features (brackets, comments, indentation). -- `ares-setup.ts`: Glue script to register the language with the Monaco global instance. diff --git a/UI/ServiceCollectionExtensions.cs b/UI/ServiceCollectionExtensions.cs index 69a07bf2..d9dd7f8b 100644 --- a/UI/ServiceCollectionExtensions.cs +++ b/UI/ServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using Ares.Alicat.Mfc.Messaging; +using Ares.Alicat.Mfc.Messaging; using Ares.Messages; using Ares.Messages.DeviceStates.Chiller; using Ares.Messages.DeviceStates.Mfc; @@ -21,46 +21,64 @@ using Tc0304.Services; using TicStepperController.Messaging; using TubeFurnace.Messaging; -using UI.Authentication; -using UI.Backend.Devices; -using UI.Backend.Factories; -using UI.Backend.Helpers; -using UI.Backend.Notifications; -using UI.Backend.Repos; -using UI.Backend.ViewModels; -using UI.Backend.ViewModels.Automation; -using UI.Backend.ViewModels.Automation.CampaignEdit; -using UI.Backend.ViewModels.Automation.CampaignEdit.Factories; -using UI.Backend.ViewModels.Automation.Planning; -using UI.Backend.ViewModels.DeviceStateLogging; -using UI.Backend.ViewModels.Factories; -using UI.Backend.ViewModels.Misc; -using UI.Backend.ViewModels.Settings.Analysis; -using UI.Backend.ViewModels.Settings.Device.ChemyxPump; -using UI.Backend.ViewModels.Settings.Device.CM3Camera; -using UI.Backend.ViewModels.Settings.Device.LaserChiller; -using UI.Backend.ViewModels.Settings.Device.Mfc; -using UI.Backend.ViewModels.Settings.Device.Remote; -using UI.Backend.ViewModels.Settings.Device.RestDevice; -using UI.Backend.ViewModels.Settings.Device.SerialRestDevice; -using UI.Backend.ViewModels.Settings.Device.Servo; -using UI.Backend.ViewModels.Settings.Device.StepperController; -using UI.Backend.ViewModels.Settings.Device.SyringePump; -using UI.Backend.ViewModels.Settings.Device.Tc0304; -using UI.Backend.ViewModels.Settings.Device.TubeFurnace; -using UI.Backend.ViewModels.Settings.Device.ValveController; -using UI.Backend.ViewModels.Settings.Device.VerdiLaser; -using UI.Backend.ViewModels.Settings.Logging; -using UI.Backend.ViewModels.Settings.Planning; -using UI.JsInterops; -using UI.Services.CampaignEdit; -using UI.Services.Dialog; -using UI.Services.Grpc; -using UI.Services.Notification; -using UI.Services.ServerHealth; -using UI.Services.ServerHealthNotification; +using UI.Features.Analyzing.Settings; +using UI.Features.DeviceStateLogging.Settings; +using UI.Features.Notifications; +using UI.Features.Planning; +using UI.Features.Planning.Settings; +using UI.Features.Auth; +using UI.Features.CampaignEdit; +using UI.Features.CampaignEdit.Factories; +using UI.Features.CampaignEdit.ViewModels; +using UI.Features.Devices.ChemyxPump; +using UI.Features.Devices.CM3Camera; +using UI.Features.Devices.Mfc; +using UI.Features.Devices.Remote; +using UI.Features.Devices.Servo; +using UI.Features.Devices.StepperController; +using UI.Features.Devices.SyringePump; +using UI.Features.Devices.Tc0304; +using UI.Features.Devices.TubeFurnace; +using UI.Features.Devices.ValveController; +using UI.Features.DeviceStateExport; +using UI.Features.DeviceStateLogging; +using UI.Application.Notifications; +using UI.Infrastructure.Monaco.Interops; +using UI.Infrastructure.Dialog; +using UI.Infrastructure.Grpc; +using UI.Infrastructure.Notifications; +using UI.Features.ServerHealth; using ValveController.Services; using VerdiV6.Services; +using CampaignDesignerViewModel = UI.Features.CampaignEdit.ViewModels.CampaignDesignerViewModel; +using ChemyxPumpSettingsListViewModel = UI.Features.Devices.ChemyxPump.ChemyxPumpSettingsListViewModel; +using CM3CameraSettingsListViewModel = UI.Features.Devices.CM3Camera.CM3CameraSettingsListViewModel; +using DataViewerViewModel = UI.Features.DataViewer.DataViewerViewModel; +using DeviceStatesViewModel = UI.Features.DeviceStateExport.DeviceStatesViewModel; +using ExecutionHistoryViewModel = UI.Features.ExecutionHistory.ExecutionHistoryViewModel; +using ExecutionViewModel = UI.Features.Execution.ExecutionViewModel; +using LaserChillerSettingsListViewModel = UI.Features.Devices.LaserChiller.LaserChillerSettingsListViewModel; +using ManualPlannerViewModel = UI.Features.Execution.Planning.ManualPlannerViewModel; +using MfcSettingsListViewModel = UI.Features.Devices.Mfc.MfcSettingsListViewModel; +using RemoteDeviceSettingsListViewModel = UI.Features.Devices.Remote.RemoteDeviceSettingsListViewModel; +using RestDeviceSettingsListViewModel = UI.Features.Devices.RestDevice.RestDeviceSettingsListViewModel; +using ScriptPlaygroundViewModel = UI.Features.ScriptPlayground.ScriptPlaygroundViewModel; +using SerialRestDeviceSettingsListViewModel = UI.Features.Devices.SerialRestDevice.SerialRestDeviceSettingsListViewModel; +using ServoSettingsListViewModel = UI.Features.Devices.Servo.ServoSettingsListViewModel; +using StepperControllerSettingsListViewModel = UI.Features.Devices.StepperController.StepperControllerSettingsListViewModel; +using SyringePumpSettingsListViewModel = UI.Features.Devices.SyringePump.SyringePumpSettingsListViewModel; +using Tc0304SettingsListViewModel = UI.Features.Devices.Tc0304.Tc0304SettingsListViewModel; +using TubeFurnaceSettingsListViewModel = UI.Features.Devices.TubeFurnace.TubeFurnaceSettingsListViewModel; +using ValveControllerSettingsListViewModel = UI.Features.Devices.ValveController.ValveControllerSettingsListViewModel; +using VerdiLaserSettingsListViewModel = UI.Features.Devices.VerdiV6Laser.VerdiLaserSettingsListViewModel; +using UI.Application.Dialog; +using UI.Application.Scripting; +using UI.Components.Formatting; +using UI.Infrastructure.DeviceStateLogging; +using UI.Application.DeviceStateLogging; +using UI.Infrastructure.Auth; +using UI.Infrastructure.Devices; +using UI.Application.Devices.Repos; namespace UI; @@ -85,12 +103,16 @@ public static void LoadAresModules(this IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(provider => provider.GetRequiredService()); + services.AddSingleton(provider => provider.GetRequiredService()); + services.AddSingleton(provider => provider.GetRequiredService()); + services.AddSingleton(provider => provider.GetRequiredService()); } public static void BindClients(this IServiceCollection services) @@ -142,15 +164,10 @@ public static void BindClients(this IServiceCollection services) private static void BindViewModels(this IServiceCollection services) { services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddTransient(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -179,7 +196,7 @@ private static void BindViewModels(this IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); } private static void BindViewModelFactories(this IServiceCollection services) @@ -208,3 +225,4 @@ private static void BindViewModelFactories(this IServiceCollection services) services.AddSingleton(); } } + diff --git a/UI/ServiceStarter.cs b/UI/ServiceStarter.cs index 19788dc7..020397da 100644 --- a/UI/ServiceStarter.cs +++ b/UI/ServiceStarter.cs @@ -1,7 +1,16 @@ -using UI.Backend.Devices; -using UI.Backend.Factories; -using UI.Backend.Repos; -using UI.Services.Notification; +using UI.Features.Devices.ChemyxPump; +using UI.Features.Devices.CM3Camera; +using UI.Features.Devices.Mfc; +using UI.Features.Devices.Remote; +using UI.Features.Devices.Servo; +using UI.Features.Devices.StepperController; +using UI.Features.Devices.SyringePump; +using UI.Features.Devices.Tc0304; +using UI.Features.Devices.TubeFurnace; +using UI.Features.Devices.ValveController; +using UI.Application.Notifications; +using UI.Infrastructure.Devices; +using UI.Application.Devices.Repos; namespace UI; @@ -84,4 +93,5 @@ public async Task StopAsync(CancellationToken cancellationToken) await _remoteDeviceViewModelFactory.DisposeAsync(); await _chemyxPumpViewModelFactory.DisposeAsync(); } -} \ No newline at end of file +} + diff --git a/UI/Services/Notification/INotificationReceivingService.cs b/UI/Services/Notification/INotificationReceivingService.cs deleted file mode 100644 index b4e733d1..00000000 --- a/UI/Services/Notification/INotificationReceivingService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Ares.Services; - -namespace UI.Services.Notification; - -public interface INotificationReceivingService -{ - void StartNotificationStream(); - void PushNotification(AresNotification notification); -} diff --git a/UI/UI.csproj b/UI/UI.csproj index ef23ffef..9f4ce9e9 100644 --- a/UI/UI.csproj +++ b/UI/UI.csproj @@ -7,6 +7,27 @@ aspnet-UI-87962D8C-B573-4F3B-852F-E1DA10F52308 + + RZ10012 + + + + RZ10012 + + + + + + + + + + + + + + + @@ -37,10 +58,9 @@ - - - - + + + @@ -68,7 +88,15 @@ - + + + + + + + + + diff --git a/UI/_Imports.razor b/UI/_Imports.razor index 5df472fc..c4659004 100644 --- a/UI/_Imports.razor +++ b/UI/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -7,11 +7,16 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop @using UI -@using UI.Pages.Shared -@using UI.Services.Notification +@using UI.Components +@using UI.Components.Experiments +@using UI.Components.Layouts +@using UI.Components.Nav +@using UI.Application.Notifications @using Radzen @using Radzen.Blazor @using BlazorMonaco @using BlazorMonaco.Editor @using BlazorMonaco.Languages @using static Microsoft.AspNetCore.Components.Web.RenderMode + + diff --git a/UI/tsconfig.json b/UI/tsconfig.json index 34081e64..a3d17ec8 100644 --- a/UI/tsconfig.json +++ b/UI/tsconfig.json @@ -4,13 +4,13 @@ "moduleResolution": "bundler", "target": "ES6", "sourceMap": true, - "outDir": "wwwroot/js", - "rootDir": "Scripts", + "outDir": "wwwroot/js/monaco", + "rootDir": "Infrastructure/Monaco/Scripts", "strict": true, "esModuleInterop": true }, "include": [ - "Scripts/**/*" + "Infrastructure/Monaco/Scripts/**/*" ], "exclude": [ "node_modules",