From 5cdcd79c98d4ddd84a2951e69250746e23e47b5b Mon Sep 17 00:00:00 2001 From: AL <26797547+Al12rs@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:48:45 +0100 Subject: [PATCH 1/3] Use dictionary to store LeftMenus rather than ReadOnlyObservableCollection --- .../Controls/Spine/SpineViewModel.cs | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs b/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs index a2f9fe975..902bf5269 100644 --- a/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs +++ b/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs @@ -48,7 +48,7 @@ public class SpineViewModel : AViewModel, ISpineViewModel private ISpineItemViewModel? _activeSpineItem; - private ReadOnlyObservableCollection _leftMenus = new([]); + private Dictionary _leftMenus = new([]); private readonly IConnection _conn; [Reactive] public ILeftMenuViewModel? LeftMenuViewModel { get; private set; } @@ -117,26 +117,34 @@ public SpineViewModel( // Create Left Menus for each workspace on demand workspaceController.AllWorkspaces .ToObservableChangeSet() - .Transform(workspace => + .OnItemAdded(workspace => + { + if (_leftMenus.TryGetValue(workspace.Id, out _)) { - try - { - var leftMenu = workspaceAttachmentsFactory.CreateLeftMenuFor( - workspace.Context, - workspace.Id, - workspaceController - ); + return; + } + + try + { + var leftMenu = workspaceAttachmentsFactory.CreateLeftMenuFor( + workspace.Context, + workspace.Id, + workspaceController + ); - return leftMenu ?? new EmptyLeftMenuViewModel(workspace.Id, message: $"Missing {workspace.Context.GetType()}"); - } - catch (Exception e) - { - _logger.LogError(e, "Exception while creating left menu for context {Context}", workspace.Context); - return new EmptyLeftMenuViewModel(workspace.Id, message: $"Error for {workspace.Context.GetType()}"); - } + _leftMenus.Add( + workspace.Id, leftMenu ?? new EmptyLeftMenuViewModel(workspace.Id, message: $"Missing {workspace.Context.GetType()}") + ); } - ) - .Bind(out _leftMenus) + catch (Exception e) + { + _logger.LogError(e, "Exception while creating left menu for context {Context}", workspace.Context); + _leftMenus.Add( + workspace.Id, new EmptyLeftMenuViewModel(workspace.Id, message: $"Error for {workspace.Context.GetType()}") + ); + } + }) + .OnItemRemoved(workspace => _leftMenus.Remove(workspace.Id, out _)) .SubscribeWithErrorLogging() .DisposeWith(disposables); @@ -163,7 +171,7 @@ public SpineViewModel( // Update the LeftMenuViewModel when the active workspace changes workspaceController.WhenAnyValue(controller => controller.ActiveWorkspace) .Select(workspace => workspace.Id) - .Select(workspaceId => _leftMenus.FirstOrDefault(menu => menu.WorkspaceId == workspaceId)) + .Select(workspaceId => _leftMenus.GetValueOrDefault(workspaceId)) .BindToVM(this, vm => vm.LeftMenuViewModel) .DisposeWith(disposables); From d196d6a5aeec58b312165fa287709a90113457fe Mon Sep 17 00:00:00 2001 From: AL <26797547+Al12rs@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:46:35 +0100 Subject: [PATCH 2/3] Actually unregister loadout workspaces when loadouts are deleted --- .../Controls/Spine/SpineViewModel.cs | 48 ++++++++++--------- .../IWorkspaceController.cs | 6 +++ .../WorkspaceController.cs | 20 ++++++-- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs b/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs index 902bf5269..3f64cbacc 100644 --- a/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs +++ b/src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs @@ -88,29 +88,33 @@ public SpineViewModel( var workspaceController = windowManager.ActiveWorkspaceController; this.WhenActivated(disposables => - { - var loadouts = Loadout.ObserveAll(_conn); + { + var loadouts = Loadout.ObserveAll(_conn); - loadouts - .Filter(loadout => loadout.IsVisible()) - .TransformAsync(async loadout => - { - await using var iconStream = await ((IGame)loadout.InstallationInstance.Game).Icon.GetStreamAsync(); - - var vm = serviceProvider.GetRequiredService(); - vm.Name = loadout.InstallationInstance.Game.Name + " - " + loadout.Name; - vm.Image = LoadImageFromStream(iconStream); - vm.LoadoutBadgeViewModel = new LoadoutBadgeViewModel(_conn, _syncService, hideOnSingleLoadout: true); - vm.LoadoutBadgeViewModel.LoadoutValue = loadout; - vm.IsActive = false; - vm.WorkspaceContext = new LoadoutContext { LoadoutId = loadout.LoadoutId }; - vm.Click = ReactiveCommand.Create(() => ChangeToLoadoutWorkspace(loadout.LoadoutId)); - return vm; - } - ) - .Sort(LoadoutComparerInstance) - .OnUI() - .Bind(out _loadoutSpineItems) + loadouts + .Filter(loadout => loadout.IsVisible()) + .TransformAsync(async loadout => + { + await using var iconStream = await ((IGame)loadout.InstallationInstance.Game).Icon.GetStreamAsync(); + + var vm = serviceProvider.GetRequiredService(); + vm.Name = loadout.InstallationInstance.Game.Name + " - " + loadout.Name; + vm.Image = LoadImageFromStream(iconStream); + vm.LoadoutBadgeViewModel = new LoadoutBadgeViewModel(_conn, _syncService, hideOnSingleLoadout: true); + vm.LoadoutBadgeViewModel.LoadoutValue = loadout; + vm.IsActive = false; + vm.WorkspaceContext = new LoadoutContext { LoadoutId = loadout.LoadoutId }; + vm.Click = ReactiveCommand.Create(() => ChangeToLoadoutWorkspace(loadout.LoadoutId)); + return vm; + } + ) + .OnUI() + .OnItemRemoved(loadoutSpineItem => + { + if (loadoutSpineItem.WorkspaceContext is LoadoutContext loadoutContext) + workspaceController.UnregisterWorkspaceByContext(context => loadoutContext == context); + }) + .SortAndBind(out _loadoutSpineItems, LoadoutComparerInstance) .SubscribeWithErrorLogging() .DisposeWith(disposables); diff --git a/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/IWorkspaceController.cs b/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/IWorkspaceController.cs index 759a755e0..afbf02944 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/IWorkspaceController.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/IWorkspaceController.cs @@ -84,6 +84,12 @@ public interface IWorkspaceController /// public IWorkspaceViewModel ChangeOrCreateWorkspaceByContext(Func predicate, Func> getPageData, Func getWorkspaceContext) where TContext : IWorkspaceContext; + + /// + /// Unregisters a workspace identified by its ContextId. + /// + public void UnregisterWorkspaceByContext(Func predicate) where TContext : IWorkspaceContext; + /// /// Adds a new panel to a workspace. /// diff --git a/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/WorkspaceController.cs b/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/WorkspaceController.cs index 5b2ba7cca..365514973 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/WorkspaceController.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceController/WorkspaceController.cs @@ -151,6 +151,7 @@ public IWorkspaceViewModel CreateWorkspace(Optional context, return vm; } + private void AddDefaultPanel(WorkspaceViewModel vm) { @@ -164,13 +165,24 @@ private void AddDefaultPanel(WorkspaceViewModel vm) addPanelBehavior ); } - - private void UnregisterWorkspace(WorkspaceViewModel workspaceViewModel) + + public void UnregisterWorkspaceByContext(Func predicate) + where TContext : IWorkspaceContext { - //TODO: currently unused, we have no cases where we unregister a workspace - // will need this when we support removing loadouts Dispatcher.UIThread.VerifyAccess(); + var workspaces = FindWorkspacesByContext(); + var existingWorkspace = workspaces.FirstOrOptional(tuple => predicate(tuple.Item2)); + if (!existingWorkspace.HasValue) return; + var workspace = existingWorkspace.Value.Item1; + + UnregisterWorkspace(workspace); + } + + private void UnregisterWorkspace(IWorkspaceViewModel workspaceViewModel) + { + Dispatcher.UIThread.VerifyAccess(); + _workspaces.Remove(workspaceViewModel.Id); if (ReferenceEquals(ActiveWorkspace, workspaceViewModel) && AllWorkspaces.Count > 0) From 1ca26215b5dce261060cd9b0c5a7c45d9483e262 Mon Sep 17 00:00:00 2001 From: AL <26797547+Al12rs@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:31:00 +0100 Subject: [PATCH 3/3] Prefer SortAndBind over obsolete Sort --- .../WorkspaceSystem/Workspace/WorkspaceViewModel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs index 5efe55883..3d24f399a 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs @@ -83,8 +83,7 @@ public WorkspaceViewModel( _panelSource .Connect() - .Sort(PanelComparer.Instance) - .Bind(out _panels) + .SortAndBind(out _panels, PanelComparer.Instance) .Do(_ => UpdateStates()) .Do(_ => UpdateResizers()) .SubscribeWithErrorLogging();