From 97907c97eb35b75ad133e6fe8aa76adef9ec4812 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 20 Nov 2019 19:25:29 +0100 Subject: [PATCH] Implemented profile creation, deletion and activation --- src/Artemis.Core/Models/Profile/Folder.cs | 2 +- src/Artemis.Core/Models/Profile/Layer.cs | 2 +- src/Artemis.Core/Models/Profile/Profile.cs | 4 +- src/Artemis.Core/Models/Surface/Device.cs | 10 +- src/Artemis.Core/Models/Surface/Surface.cs | 9 -- .../Plugins/Abstract/ProfileModule.cs | 6 +- .../Storage/Interfaces/IProfileService.cs | 3 + .../Services/Storage/ProfileService.cs | 25 +++-- .../Services/Storage/SurfaceService.cs | 2 - .../Entities/Profile/ProfileEntity.cs | 6 ++ src/Artemis.UI/Artemis.UI.csproj | 6 ++ .../Dialogs/ProfileCreateView.xaml | 37 +++++++ .../Dialogs/ProfileCreateViewModel.cs | 30 ++++++ .../SurfaceCreateViewModelValidator.cs | 12 +++ .../ProfileEditor/ProfileEditorViewModel.cs | 99 ++++++++++++++++--- .../Visualization/ProfileViewModel.cs | 11 ++- .../Services/Dialog/DialogService.cs | 2 +- 17 files changed, 217 insertions(+), 49 deletions(-) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateView.xaml create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/SurfaceCreateViewModelValidator.cs diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 7b57b90e5..d1de94683 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -8,7 +8,7 @@ using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile { - public class Folder : ProfileElement + public sealed class Folder : ProfileElement { public Folder(Profile profile, Folder folder, string name) { diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index b1f5b3380..15b2c3082 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -8,7 +8,7 @@ using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile { - public class Layer : ProfileElement + public sealed class Layer : ProfileElement { internal Layer(Profile profile, Folder folder, string name) { diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 9569b2372..f85a88f3d 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -10,7 +10,7 @@ using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile { - public class Profile : ProfileElement + public sealed class Profile : ProfileElement { internal Profile(PluginInfo pluginInfo, string name) { @@ -21,6 +21,8 @@ namespace Artemis.Core.Models.Profile Name = name; Children = new List {new Folder(this, null, "Root folder")}; + + ApplyToEntity(); } internal Profile(PluginInfo pluginInfo, ProfileEntity profileEntity, IPluginService pluginService) diff --git a/src/Artemis.Core/Models/Surface/Device.cs b/src/Artemis.Core/Models/Surface/Device.cs index 00d6ca386..f52589e34 100644 --- a/src/Artemis.Core/Models/Surface/Device.cs +++ b/src/Artemis.Core/Models/Surface/Device.cs @@ -71,8 +71,7 @@ namespace Artemis.Core.Models.Surface internal void ApplyToEntity() { - // Other properties are mapped computed - + // Other properties are computed DeviceEntity.DeviceHashCode = RgbDevice.GetDeviceHashCode(); } @@ -102,13 +101,6 @@ namespace Artemis.Core.Models.Surface RenderPath = path; } - internal void Destroy() - { - DeviceEntity = null; - RgbDevice = null; - Surface = null; - } - public override string ToString() { return $"[{RgbDevice.DeviceInfo.DeviceType}] {RgbDevice.DeviceInfo.DeviceName} - {X}.{Y}.{ZIndex}"; diff --git a/src/Artemis.Core/Models/Surface/Surface.cs b/src/Artemis.Core/Models/Surface/Surface.cs index 035275c5e..22ae9fc7a 100644 --- a/src/Artemis.Core/Models/Surface/Surface.cs +++ b/src/Artemis.Core/Models/Surface/Surface.cs @@ -61,15 +61,6 @@ namespace Artemis.Core.Models.Surface } } - internal void Destroy() - { - SurfaceEntity = null; - - foreach (var deviceConfiguration in Devices) - deviceConfiguration.Destroy(); - Devices.Clear(); - } - public void UpdateScale(double value) { Scale = value; diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index a72e7bb5d..f4f969438 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -38,13 +38,13 @@ namespace Artemis.Core.Plugins.Abstract { lock (this) { - if (profile == null) - throw new ArgumentNullException(nameof(profile)); + if (profile == ActiveProfile) + return; ActiveProfile?.Deactivate(); ActiveProfile = profile; - ActiveProfile.Activate(); + ActiveProfile?.Activate(); } OnActiveProfileChanged(); diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index a40aa2666..ee144a633 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -7,7 +7,10 @@ namespace Artemis.Core.Services.Storage.Interfaces { public interface IProfileService : IArtemisService { + Profile CreateProfile(ProfileModule module, string name); List GetProfiles(ProfileModule module); Profile GetActiveProfile(ProfileModule module); + void UpdateProfile(Profile profile, bool includeChildren); + void DeleteProfile(Profile profile); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 54d42d150..582e284ed 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -1,11 +1,9 @@ using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage.Interfaces; -using Artemis.Storage.Repositories; using Artemis.Storage.Repositories.Interfaces; namespace Artemis.Core.Services.Storage @@ -29,13 +27,22 @@ namespace Artemis.Core.Services.Storage var profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); var profiles = new List(); foreach (var profileEntity in profileEntities) - profiles.Add(new Profile(module.PluginInfo, profileEntity, _pluginService)); + { + // If the profile entity matches the module's currently active profile, use that instead + if (module.ActiveProfile != null && module.ActiveProfile.EntityId == profileEntity.Id) + profiles.Add(module.ActiveProfile); + else + profiles.Add(new Profile(module.PluginInfo, profileEntity, _pluginService)); + } return profiles; } public Profile GetActiveProfile(ProfileModule module) { + if (module.ActiveProfile != null) + return module.ActiveProfile; + var profileEntity = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid).FirstOrDefault(p => p.IsActive); if (profileEntity == null) return null; @@ -46,20 +53,26 @@ namespace Artemis.Core.Services.Storage public Profile CreateProfile(ProfileModule module, string name) { var profile = new Profile(module.PluginInfo, name); - + _profileRepository.Add(profile.ProfileEntity); + return profile; } + public void DeleteProfile(Profile profile) + { + _profileRepository.Remove(profile.ProfileEntity); + } + public void UpdateProfile(Profile profile, bool includeChildren) { profile.ApplyToEntity(); if (includeChildren) { foreach (var profileElement in profile.Children) - { profileElement.ApplyToEntity(); - } } + + _profileRepository.Save(profile.ProfileEntity); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index 82ff82963..1853e701f 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -123,8 +123,6 @@ namespace Artemis.Core.Services.Storage lock (_surfaceConfigurations) { var entity = surface.SurfaceEntity; - surface.Destroy(); - _surfaceConfigurations.Remove(surface); _surfaceRepository.Remove(entity); } diff --git a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs index 4ab95fec1..07c88923b 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs @@ -5,6 +5,12 @@ namespace Artemis.Storage.Entities.Profile { public class ProfileEntity { + public ProfileEntity() + { + Folders = new List(); + Layers = new List(); + } + public Guid Id { get; set; } public Guid PluginGuid { get; set; } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 3a82aa9b4..c9b70d8d9 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -159,6 +159,8 @@ True Resources.resx + + @@ -200,6 +202,10 @@ Code + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateView.xaml new file mode 100644 index 000000000..59a23bb40 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateView.xaml @@ -0,0 +1,37 @@ + + + + Add a new profile + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateViewModel.cs new file mode 100644 index 000000000..e3d0ec753 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/ProfileCreateViewModel.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Artemis.UI.ViewModels.Dialogs; +using Stylet; + +namespace Artemis.UI.Screens.Module.ProfileEditor.Dialogs +{ + public class ProfileCreateViewModel : DialogViewModelBase + { + public ProfileCreateViewModel(IModelValidator validator) : base(validator) + { + } + + public string ProfileName { get; set; } + + public async Task Accept() + { + await ValidateAsync(); + + if (HasErrors) + return; + + Session.Close(ProfileName); + } + + public void Cancel() + { + Session.Close(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/SurfaceCreateViewModelValidator.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/SurfaceCreateViewModelValidator.cs new file mode 100644 index 000000000..10ad86a51 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Dialogs/SurfaceCreateViewModelValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace Artemis.UI.Screens.Module.ProfileEditor.Dialogs +{ + public class ProfileCreateViewModelValidator : AbstractValidator + { + public ProfileCreateViewModelValidator() + { + RuleFor(m => m.ProfileName).NotEmpty().WithMessage("Profile name may not be empty"); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs index bf4b67a1e..8ec78d52c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs @@ -5,11 +5,13 @@ using System.Threading.Tasks; using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Services.Storage.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.Dialogs; using Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions; using Artemis.UI.Screens.Module.ProfileEditor.ElementProperties; using Artemis.UI.Screens.Module.ProfileEditor.LayerElements; using Artemis.UI.Screens.Module.ProfileEditor.Layers; using Artemis.UI.Screens.Module.ProfileEditor.Visualization; +using Artemis.UI.Services.Interfaces; using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor @@ -17,10 +19,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor public class ProfileEditorViewModel : Conductor.Collection.AllActive { private readonly IProfileService _profileService; + private readonly IDialogService _dialogService; + private Profile _selectedProfile; - public ProfileEditorViewModel(ProfileModule module, ICollection viewModels, IProfileService profileService) + public ProfileEditorViewModel(ProfileModule module, ICollection viewModels, IProfileService profileService, IDialogService dialogService) { _profileService = profileService; + _dialogService = dialogService; DisplayName = "Profile editor"; Module = module; @@ -30,13 +35,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor LayerElementsViewModel = (LayerElementsViewModel) viewModels.First(vm => vm is LayerElementsViewModel); LayersViewModel = (LayersViewModel) viewModels.First(vm => vm is LayersViewModel); ProfileViewModel = (ProfileViewModel) viewModels.First(vm => vm is ProfileViewModel); + Profiles = new BindableCollection(); Items.AddRange(viewModels); module.ActiveProfileChanged += ModuleOnActiveProfileChanged; } - public Core.Plugins.Abstract.Module Module { get; } + public ProfileModule Module { get; } public DisplayConditionsViewModel DisplayConditionsViewModel { get; } public ElementPropertiesViewModel ElementPropertiesViewModel { get; } public LayerElementsViewModel LayerElementsViewModel { get; } @@ -44,20 +50,73 @@ namespace Artemis.UI.Screens.Module.ProfileEditor public ProfileViewModel ProfileViewModel { get; } public BindableCollection Profiles { get; set; } - public Profile SelectedProfile { get; set; } - public bool CanDeleteActiveProfile => SelectedProfile != null; + + public Profile SelectedProfile + { + get => _selectedProfile; + set + { + if (_selectedProfile == value) + return; + + var old = _selectedProfile; + _selectedProfile = value; + ChangeActiveProfile(old); + } + } + + private void ChangeActiveProfile(Profile oldProfile) + { + Module.ChangeActiveProfile(_selectedProfile); + if (_selectedProfile != null) + _profileService.UpdateProfile(_selectedProfile, false); + if (oldProfile != null) + _profileService.UpdateProfile(oldProfile, false); + } + + public bool CanDeleteActiveProfile => SelectedProfile != null && Profiles.Count > 1; + + public Profile CreateProfile(string name) + { + var profile = _profileService.CreateProfile(Module, name); + Profiles.Add(profile); + return profile; + } public async Task AddProfile() { + var result = await _dialogService.ShowDialog(); + if (result is string name) + CreateProfile(name); } public async Task DeleteActiveProfile() { + var result = await _dialogService.ShowConfirmDialog( + "Delete active profile", + "Are you sure you want to delete your currently active profile? This cannot be undone." + ); + + if (!result || !CanDeleteActiveProfile) + return; + + var profile = SelectedProfile; + var index = Profiles.IndexOf(profile); + + // Get a new active profile + var newActiveProfile = index - 1 > -1 ? Profiles[index - 1] : Profiles[index + 1]; + + // Activate the new active profile + Module.ChangeActiveProfile(newActiveProfile); + + // Remove the old one + Profiles.Remove(profile); + _profileService.DeleteProfile(profile); } private void ModuleOnActiveProfileChanged(object sender, EventArgs e) { - SelectedProfile = ((ProfileModule) Module).ActiveProfile; + SelectedProfile = Profiles.FirstOrDefault(p => p == Module.ActiveProfile); } protected override void OnActivate() @@ -68,14 +127,30 @@ namespace Artemis.UI.Screens.Module.ProfileEditor private void LoadProfiles() { - var profiles = _profileService.GetProfiles((ProfileModule) Module); - Profiles.Clear(); - Profiles.AddRange(profiles); + // Get all profiles from the database + var profiles = _profileService.GetProfiles(Module); + var activeProfile = _profileService.GetActiveProfile(Module); + if (activeProfile == null) + { + activeProfile = CreateProfile("Default"); + profiles.Add(activeProfile); + } + + // GetActiveProfile can return a duplicate because inactive profiles aren't kept in memory, make sure it's unique in the profiles list + profiles = profiles.Where(p => p.EntityId != activeProfile.EntityId).ToList(); + profiles.Add(activeProfile); + + Execute.OnUIThread(() => + { + // Populate the UI collection + Profiles.Clear(); + Profiles.AddRange(profiles.OrderBy(p => p.Name)); -// if (!profiles.Any()) -// { -// var profile = new Profile(Module.PluginInfo, "Default"); -// } + SelectedProfile = activeProfile; + }); + + if (!activeProfile.IsActivated) + Module.ChangeActiveProfile(activeProfile); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index 24ddf9d50..88117800b 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -91,11 +91,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private void UpdateLeds(object sender, CustomUpdateData customUpdateData) { - if (IsInitializing) + lock (Devices) + { + if (IsInitializing) IsInitializing = Devices.Any(d => !d.AddedLeds); - - foreach (var profileDeviceViewModel in Devices) - profileDeviceViewModel.Update(); + + foreach (var profileDeviceViewModel in Devices) + profileDeviceViewModel.Update(); + } } protected override void OnActivate() diff --git a/src/Artemis.UI/Services/Dialog/DialogService.cs b/src/Artemis.UI/Services/Dialog/DialogService.cs index 7dcead687..2cedcec8e 100644 --- a/src/Artemis.UI/Services/Dialog/DialogService.cs +++ b/src/Artemis.UI/Services/Dialog/DialogService.cs @@ -64,7 +64,7 @@ namespace Artemis.UI.Services _viewManager.BindViewToModel(view, viewModel); if (identifier == null) - return await DialogHost.Show(view, viewModel.OnDialogClosed); + return await DialogHost.Show(view, viewModel.OnDialogOpened, viewModel.OnDialogClosed); return await DialogHost.Show(view, identifier, viewModel.OnDialogOpened, viewModel.OnDialogClosed); } }