From df95e64567536da61a2b76915ccbe7274bc15122 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 29 Dec 2024 13:31:06 +0100 Subject: [PATCH 1/2] Wizard - Refactored code into separate view models per step Wizard - Skip layout and surface steps when no devices are found Workshop - Fixed crash when opening library --- .../StartupWizard/StartupWizardView.axaml | 52 ++++- .../StartupWizard/StartupWizardView.axaml.cs | 24 +- .../StartupWizard/StartupWizardViewModel.cs | 206 ++---------------- ...evicesStep.axaml => DevicesStepView.axaml} | 7 +- ...Step.axaml.cs => DevicesStepView.axaml.cs} | 5 +- .../Steps/DevicesStepViewModel.cs | 33 +++ ...{FinishStep.axaml => FinishStepView.axaml} | 5 +- ...eStep.axaml.cs => FinishStepView.axaml.cs} | 5 +- .../Steps/FinishStepViewModel.cs | 13 ++ ...ayoutsStep.axaml => LayoutsStepView.axaml} | 7 +- ...Step.axaml.cs => LayoutsStepView.axaml.cs} | 5 +- .../Steps/LayoutsStepViewModel.cs | 17 ++ .../StartupWizard/Steps/SettingsStep.axaml | 123 ----------- .../Steps/SettingsStepView.axaml | 126 +++++++++++ .../Steps/SettingsStepView.axaml.cs | 14 ++ .../Steps/SettingsStepViewModel.cs | 131 +++++++++++ ...urfaceStep.axaml => SurfaceStepView.axaml} | 5 +- ...Step.axaml.cs => SurfaceStepView.axaml.cs} | 5 +- .../Steps/SurfaceStepViewModel.cs | 35 +++ .../StartupWizard/Steps/WelcomeStep.axaml | 61 ------ .../StartupWizard/Steps/WelcomeStep.axaml.cs | 13 -- .../StartupWizard/Steps/WelcomeStepView.axaml | 24 ++ ...Step.axaml.cs => WelcomeStepView.axaml.cs} | 5 +- .../Steps/WelcomeStepViewModel.cs | 12 + .../StartupWizard/WizardStepViewModel.cs | 20 ++ .../Library/Tabs/InstalledTabViewModel.cs | 5 +- .../Library/Tabs/SubmissionsTabViewModel.cs | 5 +- 27 files changed, 536 insertions(+), 427 deletions(-) rename src/Artemis.UI/Screens/StartupWizard/Steps/{DevicesStep.axaml => DevicesStepView.axaml} (92%) rename src/Artemis.UI/Screens/StartupWizard/Steps/{DevicesStep.axaml.cs => DevicesStepView.axaml.cs} (52%) create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs rename src/Artemis.UI/Screens/StartupWizard/Steps/{FinishStep.axaml => FinishStepView.axaml} (94%) rename src/Artemis.UI/Screens/StartupWizard/Steps/{SurfaceStep.axaml.cs => FinishStepView.axaml.cs} (53%) create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepViewModel.cs rename src/Artemis.UI/Screens/StartupWizard/Steps/{LayoutsStep.axaml => LayoutsStepView.axaml} (89%) rename src/Artemis.UI/Screens/StartupWizard/Steps/{LayoutsStep.axaml.cs => LayoutsStepView.axaml.cs} (54%) create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepViewModel.cs delete mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml.cs create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs rename src/Artemis.UI/Screens/StartupWizard/Steps/{SurfaceStep.axaml => SurfaceStepView.axaml} (95%) rename src/Artemis.UI/Screens/StartupWizard/Steps/{SettingsStep.axaml.cs => SurfaceStepView.axaml.cs} (52%) create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepViewModel.cs delete mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml delete mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml.cs create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepView.axaml rename src/Artemis.UI/Screens/StartupWizard/Steps/{FinishStep.axaml.cs => WelcomeStepView.axaml.cs} (52%) create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepViewModel.cs create mode 100644 src/Artemis.UI/Screens/StartupWizard/WizardStepViewModel.cs diff --git a/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml b/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml index 819035d92..67286303f 100644 --- a/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml +++ b/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml @@ -4,6 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard" + xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:ui="clr-namespace:Artemis.UI" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.StartupWizard.StartupWizardView" x:DataType="startupWizard:StartupWizardViewModel" @@ -12,14 +14,50 @@ Width="1000" Height="735" WindowStartupLocation="CenterOwner"> - - + + + + + Artemis 2 + - - - - - + + + + + + + + + + + + + + + + PolyForm Noncommercial License 1.0.0 + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml.cs index 71c72bb2e..21a17eff0 100644 --- a/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml.cs +++ b/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.axaml.cs @@ -18,23 +18,19 @@ public partial class StartupWizardView : ReactiveAppWindow ViewModel.WhenAnyValue(vm => vm.CurrentStep).Subscribe(ApplyCurrentStep).DisposeWith(d)); + this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d)); } - private void ApplyCurrentStep(int step) + private void Navigate(WizardStepViewModel viewModel) { - if (step == 1) - Frame.NavigateToType(typeof(WelcomeStep), null, new FrameNavigationOptions()); - else if (step == 2) - Frame.NavigateToType(typeof(DevicesStep), null, new FrameNavigationOptions()); - else if (step == 3) - Frame.NavigateToType(typeof(LayoutsStep), null, new FrameNavigationOptions()); - else if (step == 4) - Frame.NavigateToType(typeof(SurfaceStep), null, new FrameNavigationOptions()); - else if (step == 5) - Frame.NavigateToType(typeof(SettingsStep), null, new FrameNavigationOptions()); - else if (step == 6) - Frame.NavigateToType(typeof(FinishStep), null, new FrameNavigationOptions()); + try + { + Frame.NavigateFromObject(viewModel); + } + catch (Exception e) + { + ViewModel?.WindowService.ShowExceptionDialog("Wizard screen failed to activate", e); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs index 7366bbfaf..e112dacff 100644 --- a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs +++ b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs @@ -1,212 +1,46 @@ using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Disposables; -using System.Threading.Tasks; using Artemis.Core; -using Artemis.Core.DeviceProviders; -using Artemis.Core.Services; -using Artemis.UI.DryIoc.Factories; -using Artemis.UI.Screens.Plugins; -using Artemis.UI.Screens.Workshop.LayoutFinder; +using Artemis.UI.Screens.StartupWizard.Steps; using Artemis.UI.Shared; -using Artemis.UI.Shared.Providers; using Artemis.UI.Shared.Services; using DryIoc; using PropertyChanged.SourceGenerator; -using ReactiveUI; namespace Artemis.UI.Screens.StartupWizard; public partial class StartupWizardViewModel : DialogViewModelBase { - private readonly IAutoRunProvider? _autoRunProvider; - private readonly IProtocolProvider? _protocolProvider; - private readonly ISettingsService _settingsService; - private readonly IWindowService _windowService; - private readonly IDeviceService _deviceService; - private readonly Func _getPluginFeatureViewModel; - - [Notify] private int _currentStep; - [Notify] private bool _showContinue; - [Notify] private bool _showFinish; - [Notify] private bool _showGoBack; + private readonly IContainer _container; + [Notify] private WizardStepViewModel _screen; - public StartupWizardViewModel(IContainer container, - ISettingsService settingsService, - IPluginManagementService pluginManagementService, - IWindowService windowService, - IDeviceService deviceService, - LayoutFinderViewModel layoutFinderViewModel, - Func getPluginFeatureViewModel) + public StartupWizardViewModel(IContainer container, IWindowService windowService) { - _settingsService = settingsService; - _windowService = windowService; - _deviceService = deviceService; - _getPluginFeatureViewModel = getPluginFeatureViewModel; - _autoRunProvider = container.Resolve(IfUnresolved.ReturnDefault); - _protocolProvider = container.Resolve(IfUnresolved.ReturnDefault); + _container = container; + _screen = _container.Resolve(); + _screen.Wizard = this; - Continue = ReactiveCommand.Create(ExecuteContinue); - GoBack = ReactiveCommand.Create(ExecuteGoBack); - SkipOrFinishWizard = ReactiveCommand.Create(ExecuteSkipOrFinishWizard); - SelectLayout = ReactiveCommand.Create(ExecuteSelectLayout); + WindowService = windowService; Version = $"Version {Constants.CurrentVersion}"; - - // Take all compatible device providers and create a view model for them - DeviceProviders = new ObservableCollection(pluginManagementService.GetAllPlugins() - .Where(p => p.Info.IsCompatible) - .SelectMany(p => p.Features.Where(f => f.FeatureType.IsAssignableTo(typeof(DeviceProvider)))) - .OrderBy(f => f.Name) - .Select(f => _getPluginFeatureViewModel(f))); - LayoutFinderViewModel = layoutFinderViewModel; - - CurrentStep = 1; - SetupButtons(); - - this.WhenActivated(d => - { - UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged; - UIUseProtocol.SettingChanged += UIUseProtocolOnSettingChanged; - UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged; - - Disposable.Create(() => - { - UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged; - UIUseProtocol.SettingChanged -= UIUseProtocolOnSettingChanged; - UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged; - - _settingsService.SaveAllSettings(); - }).DisposeWith(d); - }); } - public ReactiveCommand Continue { get; } - public ReactiveCommand GoBack { get; } - public ReactiveCommand SkipOrFinishWizard { get; } - public ReactiveCommand SelectLayout { get; } - + public IWindowService WindowService { get; } public string Version { get; } - public ObservableCollection DeviceProviders { get; } - public LayoutFinderViewModel LayoutFinderViewModel { get; } - - public bool IsAutoRunSupported => _autoRunProvider != null; - public PluginSetting UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false); - public PluginSetting UIUseProtocol => _settingsService.GetSetting("UI.UseProtocol", true); - public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15); - public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true); - public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true); - public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", true); - - private void ExecuteGoBack() + public void ChangeScreen() where TWizardStepViewModel : WizardStepViewModel { - if (CurrentStep > 1) - CurrentStep--; - - // Skip the settings step if none of it's contents are supported - if (CurrentStep == 5 && !IsAutoRunSupported) - CurrentStep--; - - SetupButtons(); + try + { + Screen = _container.Resolve(); + Screen.Wizard = this; + } + catch (Exception e) + { + WindowService.ShowExceptionDialog("Wizard screen failed to activate", e); + } } - private void ExecuteContinue() + public void SkipOrFinishWizard() { - if (CurrentStep < 6) - CurrentStep++; - - // Skip the settings step if none of it's contents are supported - if (CurrentStep == 5 && !IsAutoRunSupported) - CurrentStep++; - - SetupButtons(); - } - - private void SetupButtons() - { - ShowContinue = CurrentStep != 4 && CurrentStep < 6; - ShowGoBack = CurrentStep > 1; - ShowFinish = CurrentStep == 6; - } - - private void ExecuteSkipOrFinishWizard() - { - PluginSetting setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false); - setting.Value = true; - setting.Save(); - Close(true); } - - - private void ExecuteSelectLayout(string layout) - { - // TODO: Implement the layout - _deviceService.AutoArrangeDevices(); - - ExecuteContinue(); - } - - private async Task ApplyAutoRun() - { - if (_autoRunProvider == null) - return; - - try - { - if (UIAutoRun.Value) - await _autoRunProvider.EnableAutoRun(false, UIAutoRunDelay.Value); - else - await _autoRunProvider.DisableAutoRun(); - } - catch (Exception exception) - { - _windowService.ShowExceptionDialog("Failed to apply auto-run", exception); - } - } - - private void ApplyProtocolAssociation() - { - if (_protocolProvider == null) - return; - - try - { - if (UIUseProtocol.Value) - _protocolProvider.AssociateWithProtocol("artemis"); - else - _protocolProvider.DisassociateWithProtocol("artemis"); - } - catch (Exception exception) - { - _windowService.ShowExceptionDialog("Failed to apply protocol association", exception); - } - } - - private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e) - { - await ApplyAutoRun(); - } - - private void UIUseProtocolOnSettingChanged(object? sender, EventArgs e) - { - ApplyProtocolAssociation(); - } - - private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e) - { - if (_autoRunProvider == null || !UIAutoRun.Value) - return; - - try - { - await _autoRunProvider.EnableAutoRun(true, UIAutoRunDelay.Value); - } - catch (Exception exception) - { - _windowService.ShowExceptionDialog("Failed to apply auto-run", exception); - } - } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.axaml similarity index 92% rename from src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStep.axaml rename to src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.axaml index 49ac924a0..aaed17364 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStep.axaml +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.axaml @@ -2,10 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard" + xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.StartupWizard.Steps.DevicesStep" - x:DataType="startupWizard:StartupWizardViewModel"> + x:Class="Artemis.UI.Screens.StartupWizard.Steps.DevicesStepView" + x:DataType="steps:DevicesStepViewModel"> @@ -35,7 +35,6 @@ - Note: To avoid possible instability it's recommended to disable the device providers of brands you don't own. diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStep.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.axaml.cs similarity index 52% rename from src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStep.axaml.cs rename to src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.axaml.cs index 2e8e356fc..a2435a438 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStep.axaml.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.axaml.cs @@ -1,11 +1,12 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Screens.StartupWizard.Steps; -public partial class DevicesStep : UserControl +public partial class DevicesStepView : ReactiveUserControl { - public DevicesStep() + public DevicesStepView() { InitializeComponent(); } diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs new file mode 100644 index 000000000..ed2f364d9 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs @@ -0,0 +1,33 @@ +using ReactiveUI; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using Artemis.Core; +using Artemis.Core.DeviceProviders; +using Artemis.Core.Services; + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public class DevicesStepViewModel : WizardStepViewModel +{ + public DevicesStepViewModel(IPluginManagementService pluginManagementService, Func getPluginFeatureViewModel, IDeviceService deviceService) + { + // Take all compatible device providers and create a view model for them + DeviceProviders = new ObservableCollection(pluginManagementService.GetAllPlugins() + .Where(p => p.Info.IsCompatible) + .SelectMany(p => p.Features.Where(f => f.FeatureType.IsAssignableTo(typeof(DeviceProvider)))) + .OrderBy(f => f.Name) + .Select(getPluginFeatureViewModel)); + + Continue = ReactiveCommand.Create(() => + { + if (deviceService.EnabledDevices.Count == 0) + Wizard.ChangeScreen(); + else + Wizard.ChangeScreen(); + }); + GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + } + + public ObservableCollection DeviceProviders { get; } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepView.axaml similarity index 94% rename from src/Artemis.UI/Screens/StartupWizard/Steps/FinishStep.axaml rename to src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepView.axaml index 419e96112..700d526ae 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStep.axaml +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepView.axaml @@ -4,9 +4,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStep" - x:DataType="startupWizard:StartupWizardViewModel"> + x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStepView" + x:DataType="steps:FinishStepViewModel"> All finished! diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStep.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepView.axaml.cs similarity index 53% rename from src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStep.axaml.cs rename to src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepView.axaml.cs index a099d29a8..338263d3e 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStep.axaml.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepView.axaml.cs @@ -1,11 +1,12 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Screens.StartupWizard.Steps; -public partial class SurfaceStep : UserControl +public partial class FinishStepView : ReactiveUserControl { - public SurfaceStep() + public FinishStepView() { InitializeComponent(); } diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepViewModel.cs new file mode 100644 index 000000000..b877d4a00 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStepViewModel.cs @@ -0,0 +1,13 @@ +using Artemis.UI.Shared; +using ReactiveUI; + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public class FinishStepViewModel : WizardStepViewModel +{ + public FinishStepViewModel() + { + GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + ShowFinish = true; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepView.axaml similarity index 89% rename from src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStep.axaml rename to src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepView.axaml index 40810d841..38a459eb6 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStep.axaml +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepView.axaml @@ -3,11 +3,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard" + xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.StartupWizard.Steps.LayoutsStep" - x:DataType="startupWizard:StartupWizardViewModel"> + x:Class="Artemis.UI.Screens.StartupWizard.Steps.LayoutsStepView" + x:DataType="steps:LayoutsStepViewModel"> - + Device layouts provide Artemis with an image of your devices and exact LED positions. diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStep.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepView.axaml.cs similarity index 54% rename from src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStep.axaml.cs rename to src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepView.axaml.cs index b07123e59..f4b37597a 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStep.axaml.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepView.axaml.cs @@ -1,12 +1,13 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Screens.StartupWizard.Steps; -public partial class LayoutsStep : UserControl +public partial class LayoutsStepView : ReactiveUserControl { - public LayoutsStep() + public LayoutsStepView() { InitializeComponent(); } diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepViewModel.cs new file mode 100644 index 000000000..bcf39fe38 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/LayoutsStepViewModel.cs @@ -0,0 +1,17 @@ +using Artemis.UI.Screens.Workshop.LayoutFinder; +using ReactiveUI; + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public class LayoutsStepViewModel : WizardStepViewModel +{ + public LayoutsStepViewModel(LayoutFinderViewModel layoutFinderViewModel) + { + LayoutFinderViewModel = layoutFinderViewModel; + + Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + } + + public LayoutFinderViewModel LayoutFinderViewModel { get; } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml deleted file mode 100644 index d8955f74e..000000000 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - Artemis comes with a variety of settings you can change to tweak everything to your liking. - - - Below you can find a few relevant settings, many more can be changed later on the settings page. - - - - - - Auto-run - - - - - - - Auto-run on startup - - - - - - - - Hide window on auto-run - - - - - - - - - - Associate with Artemis links - - Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser. - - - - - - - - - - - Startup delay - - Set the amount of seconds to wait before auto-running Artemis. - - - If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. - - - - - - - - - sec - - - - - - - - - - - Updating - - - - - - - Check for updates - - - If enabled, we'll check for updates on startup and periodically while running. - - - - - - - - - - - - Auto-install updates - - - If enabled, new updates will automatically be installed. - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml new file mode 100644 index 000000000..091650d32 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml @@ -0,0 +1,126 @@ + + + + + + Artemis comes with a variety of settings you can change to tweak everything to your liking. + + + Below you can find a few relevant settings, many more can be changed later on the settings page. + + + + + + Auto-run + + + + + + + Auto-run on startup + + + + + + + + Hide window on auto-run + + + + + + + + + + Associate with Artemis links + + Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser. + + + + + + + + + + + Startup delay + + Set the amount of seconds to wait before auto-running Artemis. + + + If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. + + + + + + + + + sec + + + + + + + + + + + Updating + + + + + + + Check for updates + + + If enabled, we'll check for updates on startup and periodically while running. + + + + + + + + + + + + Auto-install updates + + + If enabled, new updates will automatically be installed. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml.cs new file mode 100644 index 000000000..3fab97fe7 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public partial class SettingsStepView : ReactiveUserControl +{ + public SettingsStepView() + { + InitializeComponent(); + } + +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs new file mode 100644 index 000000000..db38edc29 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.DeviceProviders; +using Artemis.Core.Services; +using Artemis.UI.DryIoc.Factories; +using Artemis.UI.Screens.Plugins; +using Artemis.UI.Screens.Workshop.LayoutFinder; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Providers; +using Artemis.UI.Shared.Services; +using DryIoc; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public class SettingsStepViewModel : WizardStepViewModel +{ + private readonly ISettingsService _settingsService; + private readonly IWindowService _windowService; + private readonly IAutoRunProvider? _autoRunProvider; + private readonly IProtocolProvider? _protocolProvider; + + public SettingsStepViewModel(IContainer container, ISettingsService settingsService, IDeviceService deviceService, IWindowService windowService) + { + _settingsService = settingsService; + _windowService = windowService; + _autoRunProvider = container.Resolve(IfUnresolved.ReturnDefault); + _protocolProvider = container.Resolve(IfUnresolved.ReturnDefault); + + Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + GoBack = ReactiveCommand.Create(() => + { + if (deviceService.EnabledDevices.Count == 0) + Wizard.ChangeScreen(); + else + Wizard.ChangeScreen(); + }); + + this.WhenActivated(d => + { + UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged; + UIUseProtocol.SettingChanged += UIUseProtocolOnSettingChanged; + UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged; + + Disposable.Create(() => + { + UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged; + UIUseProtocol.SettingChanged -= UIUseProtocolOnSettingChanged; + UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged; + + _settingsService.SaveAllSettings(); + }).DisposeWith(d); + }); + } + + public PluginSetting UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false); + public PluginSetting UIUseProtocol => _settingsService.GetSetting("UI.UseProtocol", true); + public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15); + public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true); + public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true); + public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", true); + public bool IsAutoRunSupported => _autoRunProvider != null; + + private async Task ApplyAutoRun() + { + if (_autoRunProvider == null) + return; + + try + { + if (UIAutoRun.Value) + await _autoRunProvider.EnableAutoRun(false, UIAutoRunDelay.Value); + else + await _autoRunProvider.DisableAutoRun(); + } + catch (Exception exception) + { + _windowService.ShowExceptionDialog("Failed to apply auto-run", exception); + } + } + + private void ApplyProtocolAssociation() + { + if (_protocolProvider == null) + return; + + try + { + if (UIUseProtocol.Value) + _protocolProvider.AssociateWithProtocol("artemis"); + else + _protocolProvider.DisassociateWithProtocol("artemis"); + } + catch (Exception exception) + { + _windowService.ShowExceptionDialog("Failed to apply protocol association", exception); + } + } + + private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e) + { + await ApplyAutoRun(); + } + + private void UIUseProtocolOnSettingChanged(object? sender, EventArgs e) + { + ApplyProtocolAssociation(); + } + + private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e) + { + if (_autoRunProvider == null || !UIAutoRun.Value) + return; + + try + { + await _autoRunProvider.EnableAutoRun(true, UIAutoRunDelay.Value); + } + catch (Exception exception) + { + _windowService.ShowExceptionDialog("Failed to apply auto-run", exception); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepView.axaml similarity index 95% rename from src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStep.axaml rename to src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepView.axaml index bae89bde5..8819c7437 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStep.axaml +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepView.axaml @@ -4,9 +4,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.StartupWizard.Steps.SurfaceStep" - x:DataType="startupWizard:StartupWizardViewModel"> + x:Class="Artemis.UI.Screens.StartupWizard.Steps.SurfaceStepView" + x:DataType="steps:SurfaceStepViewModel"> diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepView.axaml.cs similarity index 52% rename from src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml.cs rename to src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepView.axaml.cs index cb34b052e..93041439f 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepView.axaml.cs @@ -1,11 +1,12 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Screens.StartupWizard.Steps; -public partial class SettingsStep : UserControl +public partial class SurfaceStepView : ReactiveUserControl { - public SettingsStep() + public SurfaceStepView() { InitializeComponent(); } diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepViewModel.cs new file mode 100644 index 000000000..04989be43 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SurfaceStepViewModel.cs @@ -0,0 +1,35 @@ +using System.Reactive; +using Artemis.UI.Shared; +using ReactiveUI; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using Artemis.Core; +using Artemis.Core.DeviceProviders; +using Artemis.Core.Services; + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public class SurfaceStepViewModel : WizardStepViewModel +{ + private readonly IDeviceService _deviceService; + + public SurfaceStepViewModel(IDeviceService deviceService) + { + _deviceService = deviceService; + SelectLayout = ReactiveCommand.Create(ExecuteSelectLayout); + + Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + } + + public ReactiveCommand SelectLayout { get; set; } + + private void ExecuteSelectLayout(string layout) + { + // TODO: Implement the layout + _deviceService.AutoArrangeDevices(); + + Wizard.ChangeScreen(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml deleted file mode 100644 index 6baaee4f4..000000000 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - Artemis 2 - - - - - - - - - - - - - - - - - - PolyForm Noncommercial License 1.0.0 - - - - - - Welcome to the Artemis startup wizard! - - - In this wizard we'll walk you through the initial configuration of Artemis. - - - Before you can start you need to tell Artemis which devices you want to use and where they are placed on your desk. - - - PS: You can also skip the wizard and set things up yourself. - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml.cs deleted file mode 100644 index 3fb6b20af..000000000 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStep.axaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace Artemis.UI.Screens.StartupWizard.Steps; - -public partial class WelcomeStep : UserControl -{ - public WelcomeStep() - { - InitializeComponent(); - } - -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepView.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepView.axaml new file mode 100644 index 000000000..0bd99585f --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepView.axaml @@ -0,0 +1,24 @@ + + + + Welcome to the Artemis startup wizard! + + + In this wizard we'll walk you through the initial configuration of Artemis. + + + Before you can start you need to tell Artemis which devices you want to use and where they are placed on your desk. + + + PS: You can also skip the wizard and set things up yourself. + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStep.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepView.axaml.cs similarity index 52% rename from src/Artemis.UI/Screens/StartupWizard/Steps/FinishStep.axaml.cs rename to src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepView.axaml.cs index 536f71cd1..7a65985e7 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/FinishStep.axaml.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepView.axaml.cs @@ -1,11 +1,12 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Screens.StartupWizard.Steps; -public partial class FinishStep : UserControl +public partial class WelcomeStepView : ReactiveUserControl { - public FinishStep() + public WelcomeStepView() { InitializeComponent(); } diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepViewModel.cs new file mode 100644 index 000000000..6a4946fcf --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/WelcomeStepViewModel.cs @@ -0,0 +1,12 @@ +using ReactiveUI; + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public class WelcomeStepViewModel : WizardStepViewModel +{ + public WelcomeStepViewModel() + { + Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + ShowGoBack = false; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/WizardStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/WizardStepViewModel.cs new file mode 100644 index 000000000..83ebe2172 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/WizardStepViewModel.cs @@ -0,0 +1,20 @@ +using System.Reactive; +using Artemis.UI.Shared; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + +namespace Artemis.UI.Screens.StartupWizard; + +public abstract partial class WizardStepViewModel : ValidatableViewModelBase +{ + [Notify] private ReactiveCommand? _secondary; + [Notify] private ReactiveCommand? _continue; + [Notify] private ReactiveCommand? _goBack; + [Notify] private string _continueText = "Continue"; + [Notify] private string? _secondaryText; + [Notify] private bool _showFinish; + [Notify] private bool _showGoBack = true; + [Notify] private bool _showHeader = true; + + public StartupWizardViewModel Wizard { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabViewModel.cs index 3a3267ecb..670c19e04 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabViewModel.cs @@ -25,7 +25,10 @@ public partial class InstalledTabViewModel : RoutableScreen public InstalledTabViewModel(IWorkshopService workshopService, IRouter router, Func getInstalledTabItemViewModel) { - IObservable> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate); + IObservable> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput) + .Throttle(TimeSpan.FromMilliseconds(100)) + .ObserveOn(RxApp.MainThreadScheduler) + .Select(CreatePredicate); _entries.Connect() .Filter(searchFilter) diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs index d84907dcb..caefeab1e 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs @@ -38,7 +38,10 @@ public partial class SubmissionsTabViewModel : RoutableScreen IWorkshopService workshopService, Func getSubmissionsTabItemViewModel) { - IObservable> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate); + IObservable> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput) + .Throttle(TimeSpan.FromMilliseconds(100)) + .ObserveOn(RxApp.MainThreadScheduler) + .Select(CreatePredicate); _client = client; _windowService = windowService; From cfdcbe44c9478a515c4338354d124c047e063e44 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 4 Feb 2025 20:52:09 +0100 Subject: [PATCH 2/2] Meta - Update dependencies --- src/Artemis.Core/Artemis.Core.csproj | 2 +- .../Artemis.Storage.Legacy.csproj | 2 +- src/Artemis.Storage/Artemis.Storage.csproj | 2 +- src/Artemis.Storage/ArtemisDbContext.cs | 15 +- ...4194848_DevicesClearBrokenJson.Designer.cs | 337 ++++++++++++++++++ .../20250204194848_DevicesClearBrokenJson.cs | 22 ++ .../ArtemisDbContextModelSnapshot.cs | 57 +-- src/Artemis.UI.Linux/Artemis.UI.Linux.csproj | 2 +- src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj | 2 +- .../Artemis.UI.Shared.csproj | 2 +- .../Artemis.UI.Windows.csproj | 2 +- src/Artemis.UI/Artemis.UI.csproj | 2 +- .../Steps/ValidateEmailStepViewModel.cs | 2 +- .../Artemis.WebClient.Updating.csproj | 2 +- .../Artemis.WebClient.Workshop.csproj | 2 +- src/Directory.Packages.props | 49 +-- 16 files changed, 418 insertions(+), 84 deletions(-) create mode 100644 src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.Designer.cs create mode 100644 src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 71aeb0167..2c50920de 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 false false Artemis.Core diff --git a/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj b/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj index 2da8bd8c3..ede098742 100644 --- a/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj +++ b/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable diff --git a/src/Artemis.Storage/Artemis.Storage.csproj b/src/Artemis.Storage/Artemis.Storage.csproj index fa7b8fe61..62503b361 100644 --- a/src/Artemis.Storage/Artemis.Storage.csproj +++ b/src/Artemis.Storage/Artemis.Storage.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 false x64 enable diff --git a/src/Artemis.Storage/ArtemisDbContext.cs b/src/Artemis.Storage/ArtemisDbContext.cs index de514f79a..0893def84 100644 --- a/src/Artemis.Storage/ArtemisDbContext.cs +++ b/src/Artemis.Storage/ArtemisDbContext.cs @@ -8,6 +8,7 @@ using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Workshop; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace Artemis.Storage; @@ -35,9 +36,17 @@ public class ArtemisDbContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() - .OwnsOne(d => d.InputIdentifiers, builder => builder.ToJson()) - .OwnsOne(d => d.InputMappings, builder => builder.ToJson()); - + .Property(e => e.InputIdentifiers) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions) ?? new List()); + + modelBuilder.Entity() + .Property(e => e.InputMappings) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions) ?? new List()); + modelBuilder.Entity() .Property(e => e.Metadata) .HasConversion( diff --git a/src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.Designer.cs b/src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.Designer.cs new file mode 100644 index 000000000..08808feae --- /dev/null +++ b/src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.Designer.cs @@ -0,0 +1,337 @@ +// +using System; +using Artemis.Storage; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + [DbContext(typeof(ArtemisDbContext))] + [Migration("20250204194848_DevicesClearBrokenJson")] + partial class DevicesClearBrokenJson + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.1"); + + modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstalledAt"); + + b.HasIndex("Version") + .IsUnique(); + + b.ToTable("Releases"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PluginGuid") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PluginGuid") + .IsUnique(); + + b.ToTable("Plugins"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PluginEntityId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PluginEntityId"); + + b.ToTable("PluginFeatures"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PluginGuid") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PluginGuid"); + + b.HasIndex("Name", "PluginGuid") + .IsUnique(); + + b.ToTable("PluginSettings"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCollapsed") + .HasColumnType("INTEGER"); + + b.Property("IsSuspended") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProfileCategories"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Profile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileCategoryId") + .HasColumnType("TEXT"); + + b.Property("ProfileConfiguration") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProfileCategoryId"); + + b.ToTable("ProfileContainers"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.Property("Id") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("BlueScale") + .HasColumnType("REAL"); + + b.PrimitiveCollection("Categories") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceProvider") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("GreenScale") + .HasColumnType("REAL"); + + b.Property("InputIdentifiers") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("InputMappings") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LayoutParameter") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("LayoutType") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LogicalLayout") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("PhysicalLayout") + .HasColumnType("INTEGER"); + + b.Property("RedScale") + .HasColumnType("REAL"); + + b.Property("Rotation") + .HasColumnType("REAL"); + + b.Property("Scale") + .HasColumnType("REAL"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("ZIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("AutoUpdate") + .HasColumnType("INTEGER"); + + b.Property("Categories") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Downloads") + .HasColumnType("INTEGER"); + + b.Property("EntryId") + .HasColumnType("INTEGER"); + + b.Property("EntryType") + .HasColumnType("INTEGER"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("IsOfficial") + .HasColumnType("INTEGER"); + + b.Property("LatestReleaseId") + .HasColumnType("INTEGER"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ReleaseId") + .HasColumnType("INTEGER"); + + b.Property("ReleaseVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null) + .WithMany("Features") + .HasForeignKey("PluginEntityId"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory") + .WithMany("ProfileConfigurations") + .HasForeignKey("ProfileCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProfileCategory"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Navigation("ProfileConfigurations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.cs b/src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.cs new file mode 100644 index 000000000..4aea1a315 --- /dev/null +++ b/src/Artemis.Storage/Migrations/20250204194848_DevicesClearBrokenJson.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + /// + public partial class DevicesClearBrokenJson : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("UPDATE Devices SET InputMappings = \"[]\", InputIdentifiers = \"[]\""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("UPDATE Devices SET InputMappings = '{\"Capacity\":0}', InputIdentifiers = '{\"Capacity\":0}'"); + } + } +} diff --git a/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs index 04a34e87b..dfccb79fd 100644 --- a/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs +++ b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Artemis.Storage.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + modelBuilder.HasAnnotation("ProductVersion", "9.0.1"); modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b => { @@ -177,7 +177,7 @@ namespace Artemis.Storage.Migrations b.Property("BlueScale") .HasColumnType("REAL"); - b.Property("Categories") + b.PrimitiveCollection("Categories") .IsRequired() .HasColumnType("TEXT"); @@ -189,6 +189,14 @@ namespace Artemis.Storage.Migrations b.Property("GreenScale") .HasColumnType("REAL"); + b.Property("InputIdentifiers") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("InputMappings") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("IsEnabled") .HasColumnType("INTEGER"); @@ -311,51 +319,6 @@ namespace Artemis.Storage.Migrations b.Navigation("ProfileCategory"); }); - modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => - { - b.OwnsOne("System.Collections.Generic.List", "InputIdentifiers", b1 => - { - b1.Property("DeviceEntityId") - .HasColumnType("TEXT"); - - b1.Property("Capacity") - .HasColumnType("INTEGER"); - - b1.HasKey("DeviceEntityId"); - - b1.ToTable("Devices"); - - b1.ToJson("InputIdentifiers"); - - b1.WithOwner() - .HasForeignKey("DeviceEntityId"); - }); - - b.OwnsOne("System.Collections.Generic.List", "InputMappings", b1 => - { - b1.Property("DeviceEntityId") - .HasColumnType("TEXT"); - - b1.Property("Capacity") - .HasColumnType("INTEGER"); - - b1.HasKey("DeviceEntityId"); - - b1.ToTable("Devices"); - - b1.ToJson("InputMappings"); - - b1.WithOwner() - .HasForeignKey("DeviceEntityId"); - }); - - b.Navigation("InputIdentifiers") - .IsRequired(); - - b.Navigation("InputMappings") - .IsRequired(); - }); - modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => { b.Navigation("Features"); diff --git a/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj b/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj index ed8f08fda..64844bc6c 100644 --- a/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj +++ b/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj @@ -1,7 +1,7 @@ WinExe - net8.0 + net9.0 enable x64 x64 diff --git a/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj b/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj index f322b73d7..816f3cd77 100644 --- a/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj +++ b/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj @@ -1,7 +1,7 @@  WinExe - net8.0 + net9.0 enable x64 x64 diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index 42faecb23..ef6ed4e95 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -1,7 +1,7 @@ Library - net8.0 + net9.0 enable bin\ x64 diff --git a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj index 0547102a2..074722051 100644 --- a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj +++ b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj @@ -1,7 +1,7 @@ WinExe - net8.0-windows10.0.17763.0 + net9.0-windows10.0.17763.0 enable x64 bin diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 83e51d23a..268327347 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -1,7 +1,7 @@ Library - net8.0 + net9.0 enable bin/ x64 diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ValidateEmailStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ValidateEmailStepViewModel.cs index 1321df067..b07414ce4 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ValidateEmailStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ValidateEmailStepViewModel.cs @@ -26,7 +26,7 @@ public class ValidateEmailStepViewModel : SubmissionViewModel Continue = ReactiveCommand.Create(() =>{}, Observable.Never()); Refresh = ReactiveCommand.CreateFromTask(ExecuteRefresh); - Resend = ReactiveCommand.Create(() => Utilities.OpenUrl(WorkshopConstants.AUTHORITY_URL + "/account/confirm/resend")); + Resend = ReactiveCommand.Create(() => Utilities.OpenUrl(WorkshopConstants.AUTHORITY_URL + "/account/email/confirm/resend")); ShowGoBack = false; ShowHeader = false; diff --git a/src/Artemis.WebClient.Updating/Artemis.WebClient.Updating.csproj b/src/Artemis.WebClient.Updating/Artemis.WebClient.Updating.csproj index f5d00c0f4..4ca738864 100644 --- a/src/Artemis.WebClient.Updating/Artemis.WebClient.Updating.csproj +++ b/src/Artemis.WebClient.Updating/Artemis.WebClient.Updating.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 enable enable x64 diff --git a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj index a51abe913..c3c32e403 100644 --- a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj +++ b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 enable enable x64 diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 7860fef04..95bbeb892 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,62 +5,65 @@ - + - + - + - + - + - - + + - + - + - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + - - - - - + + + + + \ No newline at end of file