From e3d27a0e16acc67c299101e1f95160e8cb9cc4fb Mon Sep 17 00:00:00 2001 From: Joao Rua Date: Mon, 22 Jul 2024 14:54:00 +0100 Subject: [PATCH] tabs in the same panel are now reorderable + panels number limited to 2 TODO - Move tabs between panels - Document SortableList and download its js file - Refactors - Unit tests --- .../Tabs/TabsPanelComponentTestFixture.cs | 2 +- .../Components/Shared/SortableList.razor | 89 +++++++++++++++++ .../Components/Shared/SortableList.razor.css | 13 +++ .../Components/Shared/SortableList.razor.js | 34 +++++++ .../Components/Tabs/TabsPanelComponent.razor | 41 ++++---- .../Tabs/TabsPanelComponent.razor.cs | 35 +++---- COMETwebapp/Model/ITabHandler.cs | 37 ------- COMETwebapp/Model/TabPanelInformation.cs | 22 ++++- .../Model/TabbedApplicationInformation.cs | 5 - COMETwebapp/Pages/Tabs.razor | 26 +++-- COMETwebapp/Pages/Tabs.razor.cs | 34 ++++--- COMETwebapp/Pages/_Host.cshtml | 1 + .../SideBarEntry/ApplicationsSideBar.razor.cs | 7 +- .../ViewModels/Pages/ITabsViewModel.cs | 22 ++--- COMETwebapp/ViewModels/Pages/TabsViewModel.cs | 96 ++++++++----------- 15 files changed, 279 insertions(+), 185 deletions(-) create mode 100644 COMETwebapp/Components/Shared/SortableList.razor create mode 100644 COMETwebapp/Components/Shared/SortableList.razor.css create mode 100644 COMETwebapp/Components/Shared/SortableList.razor.js delete mode 100644 COMETwebapp/Model/ITabHandler.cs diff --git a/COMETwebapp.Tests/Components/Tabs/TabsPanelComponentTestFixture.cs b/COMETwebapp.Tests/Components/Tabs/TabsPanelComponentTestFixture.cs index 1e3b8680..157204b9 100644 --- a/COMETwebapp.Tests/Components/Tabs/TabsPanelComponentTestFixture.cs +++ b/COMETwebapp.Tests/Components/Tabs/TabsPanelComponentTestFixture.cs @@ -114,7 +114,7 @@ public void SetUp() this.renderer = this.context.RenderComponent(parameters => { parameters.Add(p => p.ViewModel, this.viewModel.Object); - parameters.Add(p => p.Handler, this.viewModel.Object); + parameters.Add(p => p.Panel, this.viewModel.Object); parameters.Add(p => p.CssClass, "css-test-class"); parameters.Add(p => p.IsSidePanelAvailable, true); parameters.Add(p => p.Tabs, this.viewModel.Object.OpenTabs.Items.ToList()); diff --git a/COMETwebapp/Components/Shared/SortableList.razor b/COMETwebapp/Components/Shared/SortableList.razor new file mode 100644 index 00000000..65e487df --- /dev/null +++ b/COMETwebapp/Components/Shared/SortableList.razor @@ -0,0 +1,89 @@ +@using System.Collections.Generic +@using System.Diagnostics.CodeAnalysis + +@inject IJSRuntime JS + +@typeparam T + +
+ @foreach (var item in this.Items) + { + @if (this.SortableItemTemplate is not null) + { + @(this.SortableItemTemplate(item)) + } + } +
+ +@code { + + [Parameter] + public RenderFragment? SortableItemTemplate { get; set; } + + [Parameter, AllowNull] + public List Items { get; set; } + + [Parameter] + public EventCallback<(int oldIndex, int newIndex)> OnUpdate { get; set; } + + [Parameter] + public EventCallback<(int oldIndex, int newIndex)> OnRemove { get; set; } + + [Parameter] + public string Id { get; set; } = Guid.NewGuid().ToString(); + + [Parameter] + public string Group { get; set; } = Guid.NewGuid().ToString(); + + [Parameter] + public string? Pull { get; set; } + + [Parameter] + public bool Put { get; set; } = true; + + [Parameter] + public bool Sort { get; set; } = true; + + [Parameter] + public string Handle { get; set; } = string.Empty; + + [Parameter] + public string? Filter { get; set; } + + [Parameter] + public bool ForceFallback { get; set; } = true; + + /// + /// Gets or sets the custom css class to be applied in the container component + /// + [Parameter] + public string CssClass { get; set; } + + private DotNetObjectReference>? selfReference; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + this.selfReference = DotNetObjectReference.Create(this); + var module = await this.JS.InvokeAsync("import", "./Components/Shared/SortableList.razor.js"); + await module.InvokeAsync("init", this.Id, this.Group, this.Pull, this.Put, this.Sort, this.Handle, this.Filter, this.selfReference, this.ForceFallback); + } + } + + [JSInvokable] + public void OnUpdateJS(int oldIndex, int newIndex) + { + // invoke the OnUpdate event passing in the oldIndex and the newIndex + this.OnUpdate.InvokeAsync((oldIndex, newIndex)); + } + + [JSInvokable] + public void OnRemoveJS(int oldIndex, int newIndex) + { + // remove the item from the list + this.OnRemove.InvokeAsync((oldIndex, newIndex)); + } + + public void Dispose() => this.selfReference?.Dispose(); +} diff --git a/COMETwebapp/Components/Shared/SortableList.razor.css b/COMETwebapp/Components/Shared/SortableList.razor.css new file mode 100644 index 00000000..ff69fa3f --- /dev/null +++ b/COMETwebapp/Components/Shared/SortableList.razor.css @@ -0,0 +1,13 @@ +/* + you need the ::deep identifier if you are using scoped styles like this + because scoped styles are only applied to markup in the component, not + to the markup inside the render fragment. +*/ + +::deep .sortable-ghost { + visibility: hidden; +} + +::deep .sortable-fallback { + opacity: 1 !important +} \ No newline at end of file diff --git a/COMETwebapp/Components/Shared/SortableList.razor.js b/COMETwebapp/Components/Shared/SortableList.razor.js new file mode 100644 index 00000000..0c4fae6b --- /dev/null +++ b/COMETwebapp/Components/Shared/SortableList.razor.js @@ -0,0 +1,34 @@ +export function init(id, group, pull, put, sort, handle, filter, component, forceFallback) { + var sortable = new Sortable(document.getElementById(id), { + animation: 200, + group: { + name: group, + pull: pull || true, + put: put + }, + filter: filter || undefined, + sort: sort, + forceFallback: forceFallback, + handle: handle || undefined, + onUpdate: (event) => { + // Revert the DOM to match the .NET state + event.item.remove(); + event.to.insertBefore(event.item, event.to.childNodes[event.oldIndex]); + + // Notify .NET to update its model and re-render + component.invokeMethodAsync('OnUpdateJS', event.oldDraggableIndex, event.newDraggableIndex); + }, + onRemove: (event) => { + if (event.pullMode === 'clone') { + // Remove the clone + event.clone.remove(); + } + + event.item.remove(); + event.from.insertBefore(event.item, event.from.childNodes[event.oldIndex]); + + // Notify .NET to update its model and re-render + component.invokeMethodAsync('OnRemoveJS', event.oldDraggableIndex, event.newDraggableIndex); + } + }); +} diff --git a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor index f774bf9b..47d878f8 100644 --- a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor +++ b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor @@ -21,27 +21,30 @@ -------------------------------------------------------------------------------> @using COMETwebapp.Model @using CDP4Common.CommonData + @inherits DisposableComponent
- @if (this.Handler.CurrentTab is not null) + @if (this.Panel.CurrentTab is not null) { -
+
- @foreach (var tab in this.Tabs) - { - - } + + + + + +
- +
}
diff --git a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs index a62908dd..76e8678d 100644 --- a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs +++ b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs @@ -53,7 +53,7 @@ public partial class TabsPanelComponent : DisposableComponent /// Gets or sets the tab handler to be used /// [Parameter] - public ITabHandler Handler { get; set; } + public TabPanelInformation Panel { get; set; } /// /// Gets or sets the @@ -61,12 +61,6 @@ public partial class TabsPanelComponent : DisposableComponent [Parameter] public ITabsViewModel ViewModel { get; set; } - /// - /// Gets or sets the tabs to be displayed - /// - [Parameter] - public List Tabs { get; set; } = []; - /// /// Gets or sets the method to be executed when the open tab button is clicked /// @@ -89,7 +83,7 @@ public partial class TabsPanelComponent : DisposableComponent /// Gets or sets the method to be executed when the tab is clicked /// [Parameter] - public EventCallback<(TabbedApplicationInformation, ITabHandler)> OnTabClick { get; set; } + public EventCallback<(TabbedApplicationInformation, TabPanelInformation)> OnTabClick { get; set; } /// /// Gets or sets the condition to check if the side panel should be available @@ -103,6 +97,17 @@ public partial class TabsPanelComponent : DisposableComponent [Inject] public ISessionService SessionService { get; set; } + /// + /// Sorts the tabs by the means of drag and drop + /// + /// The dragged tab old index + /// The dragged tab new index + /// A + private void SortTabs(int oldIndex, int newIndex) + { + this.Panel.OpenTabs.Move(oldIndex, newIndex); + } + /// /// Gets the tab text for the given object of interest /// @@ -158,16 +163,14 @@ private string GetCaptionText(object objectOfInterest) /// private void AddSidePanel() { - var currentTab = this.ViewModel.CurrentTab; + var currentTab = this.ViewModel.MainPanel.CurrentTab; + this.ViewModel.SidePanel.OpenTabs.Add(currentTab); + this.ViewModel.SidePanel.CurrentTab = currentTab; - var newPanel = new TabPanelInformation - { - CurrentTab = currentTab - }; + this.ViewModel.MainPanel.OpenTabs.Remove(currentTab); + this.ViewModel.MainPanel.CurrentTab = this.ViewModel.MainPanel.OpenTabs.Items.FirstOrDefault(); - currentTab.Panel = newPanel; - this.ViewModel.SidePanels.Add(newPanel); - this.ViewModel.CurrentTab = this.ViewModel.OpenTabs.Items.FirstOrDefault(x => x.Panel == null); + // todo: make open tabs reactive so when there are no open tabs, the current tab is set to null } } } diff --git a/COMETwebapp/Model/ITabHandler.cs b/COMETwebapp/Model/ITabHandler.cs deleted file mode 100644 index 2ca83d55..00000000 --- a/COMETwebapp/Model/ITabHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) 2024 Starion Group S.A. -// -// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua -// -// This file is part of COMET WEB Community Edition -// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. -// -// The COMET WEB Community Edition is free software; you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public -// License as published by the Free Software Foundation; either -// version 3 of the License, or (at your option) any later version. -// -// The COMET WEB Community Edition is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace COMETwebapp.Model -{ - /// - /// The provides properties to be used by classes that handle tabs - /// - public interface ITabHandler - { - /// - /// Gets or sets the current tab - /// - TabbedApplicationInformation CurrentTab { get; set; } - } -} diff --git a/COMETwebapp/Model/TabPanelInformation.cs b/COMETwebapp/Model/TabPanelInformation.cs index 3ee8e30a..8b0d9b51 100644 --- a/COMETwebapp/Model/TabPanelInformation.cs +++ b/COMETwebapp/Model/TabPanelInformation.cs @@ -24,11 +24,20 @@ namespace COMETwebapp.Model { + using DynamicData; + + using ReactiveUI; + /// /// The provides required information related to a panel /// - public class TabPanelInformation : ITabHandler + public class TabPanelInformation : ReactiveObject { + /// + /// Backing field for the property + /// + private TabbedApplicationInformation currentTab; + /// /// Initializes a new instance of the class. /// @@ -39,6 +48,15 @@ public TabPanelInformation() /// /// Gets or sets the current tab /// - public TabbedApplicationInformation CurrentTab { get; set; } + public TabbedApplicationInformation CurrentTab + { + get => this.currentTab; + set => this.RaiseAndSetIfChanged(ref this.currentTab, value); + } + + /// + /// Gets the collection of all contained by the panel + /// + public SourceList OpenTabs { get; set; } = new(); } } diff --git a/COMETwebapp/Model/TabbedApplicationInformation.cs b/COMETwebapp/Model/TabbedApplicationInformation.cs index 276fab50..c80f172d 100644 --- a/COMETwebapp/Model/TabbedApplicationInformation.cs +++ b/COMETwebapp/Model/TabbedApplicationInformation.cs @@ -58,10 +58,5 @@ public TabbedApplicationInformation(IApplicationBaseViewModel applicationBaseVie /// Gets the object of interest /// public object ObjectOfInterest { get; } - - /// - /// Gets or sets the - /// - public TabPanelInformation Panel { get; set; } } } diff --git a/COMETwebapp/Pages/Tabs.razor b/COMETwebapp/Pages/Tabs.razor index 85e40a22..c66566c4 100644 --- a/COMETwebapp/Pages/Tabs.razor +++ b/COMETwebapp/Pages/Tabs.razor @@ -25,33 +25,31 @@ @inherits DisposableComponent
- @if (this.ViewModel.CurrentTab is not null) + @if (this.ViewModel.MainPanel.CurrentTab is not null) { - + OnRemoveTabClick="@(tab => this.OnRemoveTabClick(tab, this.ViewModel.MainPanel))" + OnOpenTabClick="@(() => this.OnOpenTabClick(this.ViewModel.MainPanel))" + IsSidePanelAvailable="@(this.ViewModel.SidePanel.OpenTabs.Count == 0)"/> - @foreach (var panelsGrouping in this.OpenTabs.Where(x => x.Panel is not null).GroupBy(x => x.Panel)) + @if (this.ViewModel.SidePanel.CurrentTab is not null) { - + OnCreateTabForModel="@(tab => this.OnCreateTabForModel(tab))" + OnRemoveTabClick="@(tab => this.OnRemoveTabClick(tab, this.ViewModel.SidePanel))" + OnOpenTabClick="@(() => this.OnOpenTabClick(this.ViewModel.SidePanel))" /> } } else {
- +
} @@ -65,7 +63,7 @@ Closed="@(() => this.ResetOpenTabPopup())"> diff --git a/COMETwebapp/Pages/Tabs.razor.cs b/COMETwebapp/Pages/Tabs.razor.cs index 22eec333..bc5ab1bc 100644 --- a/COMETwebapp/Pages/Tabs.razor.cs +++ b/COMETwebapp/Pages/Tabs.razor.cs @@ -44,14 +44,9 @@ namespace COMETwebapp.Pages public partial class Tabs { /// - /// Gets or sets the selected side panel + /// Gets or sets the selected panel /// - private TabPanelInformation SelectedSidePanel { get; set; } - - /// - /// Collection of open tabs that belong from view model sourcelist - /// - private IEnumerable OpenTabs => this.ViewModel.OpenTabs.Items; + private TabPanelInformation SelectedPanel { get; set; } /// /// The model id to fill the opentab form, if needed @@ -95,29 +90,29 @@ protected override void OnInitialized() this.Disposables.Add(this.WhenAnyValue( x => x.ViewModel.SelectedApplication, - x => x.ViewModel.CurrentTab) + x => x.ViewModel.MainPanel.CurrentTab, + x => x.ViewModel.SidePanel.CurrentTab) .SubscribeAsync(_ => this.InvokeAsync(this.StateHasChanged))); - - this.Disposables.Add(this.ViewModel.OpenTabs.CountChanged.SubscribeAsync(_ => this.InvokeAsync(this.StateHasChanged))); } /// /// Method executed when a tab is clicked /// /// The tab to be set - /// The tab handler to handle the tab click - private static void OnTabClick(TabbedApplicationInformation tabbedApplicationInformation, ITabHandler tabHandler) + /// The tab panel to handle the tab click + private static void OnTabClick(TabbedApplicationInformation tabbedApplicationInformation, TabPanelInformation panel) { - tabHandler.CurrentTab = tabbedApplicationInformation; + panel.CurrentTab = tabbedApplicationInformation; } /// /// Method executed when the remove tab button is clicked /// /// The tab to be removed - private void OnRemoveTabClick(TabbedApplicationInformation tabbedApplicationInformation) + /// The tab panel to handle the tab click + private void OnRemoveTabClick(TabbedApplicationInformation tabbedApplicationInformation, TabPanelInformation panel) { - this.ViewModel.OpenTabs.Remove(tabbedApplicationInformation); + panel.OpenTabs.Remove(tabbedApplicationInformation); } /// @@ -133,10 +128,10 @@ private void SetOpenTabVisibility(bool visibility) /// /// Method executed when the open tab button is clicked /// - /// The side panel to be set, if any - private void OnOpenTabClick(TabPanelInformation sidePanel = null) + /// The panel to be set + private void OnOpenTabClick(TabPanelInformation sidePanel) { - this.SelectedSidePanel = sidePanel; + this.SelectedPanel = sidePanel; this.SetOpenTabVisibility(true); } @@ -168,6 +163,9 @@ private void OnCreateTabForModel(TabbedApplicationInformation tabbedApplicationI return; } + var isTabFromMainPanel = this.ViewModel.MainPanel.OpenTabs.Items.Contains(tabbedApplicationInformation); + this.SelectedPanel = isTabFromMainPanel ? this.ViewModel.MainPanel : this.ViewModel.SidePanel; + this.IterationId = iterationOfInterest.Iid; this.ModelId = ((CDP4Common.EngineeringModelData.EngineeringModel)iterationOfInterest.Container).Iid; this.DomainId = this.SessionService.GetDomainOfExpertise(iterationOfInterest).Iid; diff --git a/COMETwebapp/Pages/_Host.cshtml b/COMETwebapp/Pages/_Host.cshtml index 4e9fdcad..fddc037e 100644 --- a/COMETwebapp/Pages/_Host.cshtml +++ b/COMETwebapp/Pages/_Host.cshtml @@ -95,6 +95,7 @@
+ diff --git a/COMETwebapp/Shared/SideBarEntry/ApplicationsSideBar.razor.cs b/COMETwebapp/Shared/SideBarEntry/ApplicationsSideBar.razor.cs index 00716ff6..241c2bc5 100644 --- a/COMETwebapp/Shared/SideBarEntry/ApplicationsSideBar.razor.cs +++ b/COMETwebapp/Shared/SideBarEntry/ApplicationsSideBar.razor.cs @@ -81,9 +81,10 @@ protected override void OnInitialized() base.OnInitialized(); this.NavigationManager.LocationChanged += this.OnLocationChanged; - this.Disposables.Add(this.WhenAnyValue(x => - x.TabsViewModel.SelectedApplication, - x => x.TabsViewModel.CurrentTab + this.Disposables.Add(this.WhenAnyValue( + x => x.TabsViewModel.SelectedApplication, + x => x.TabsViewModel.MainPanel.CurrentTab, + x => x.TabsViewModel.SidePanel.CurrentTab ).SubscribeAsync(_ => this.InvokeAsync(this.StateHasChanged))); } diff --git a/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs b/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs index 0d46bffe..ce01cf4d 100644 --- a/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs +++ b/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs @@ -28,18 +28,11 @@ namespace COMETwebapp.ViewModels.Pages using COMETwebapp.Model; - using DynamicData; - /// /// The contains logic and behavior that are required to support multi-tabs application /// - public interface ITabsViewModel : ITabHandler + public interface ITabsViewModel { - /// - /// Gets the collection of all - /// - SourceList OpenTabs { get; } - /// /// Gets the collection of available /// @@ -51,9 +44,14 @@ public interface ITabsViewModel : ITabHandler TabbedApplication SelectedApplication { get; set; } /// - /// Gets the collection of all s + /// Gets the side tab panel information + /// + TabPanelInformation SidePanel { get; } + + /// + /// Gets the main tab panel information /// - SourceList SidePanels { get; } + TabPanelInformation MainPanel { get; } /// /// Creates a new tab and sets it to current @@ -63,7 +61,7 @@ public interface ITabsViewModel : ITabHandler /// The id of the object of interest, which can be an or an /// /// - /// The panel to open the new tab in - void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, TabPanelInformation sidePanel = null); + /// The panel to open the new tab in + void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, TabPanelInformation panel); } } diff --git a/COMETwebapp/ViewModels/Pages/TabsViewModel.cs b/COMETwebapp/ViewModels/Pages/TabsViewModel.cs index eb7c8175..75d1e270 100644 --- a/COMETwebapp/ViewModels/Pages/TabsViewModel.cs +++ b/COMETwebapp/ViewModels/Pages/TabsViewModel.cs @@ -51,11 +51,6 @@ public class TabsViewModel : DisposableObject, ITabsViewModel /// private readonly ISessionService sessionService; - /// - /// Backing field for - /// - private TabbedApplicationInformation currentTab; - /// /// Backing field for /// @@ -71,29 +66,27 @@ public TabsViewModel(ISessionService sessionService, IServiceProvider servicePro this.sessionService = sessionService; this.serviceProvider = serviceProvider; this.Disposables.Add(this.WhenAnyValue(x => x.SelectedApplication).Subscribe(_ => this.OnSelectedApplicationChange())); - this.Disposables.Add(this.WhenAnyValue(x => x.CurrentTab).Subscribe(_ => this.OnCurrentTabChange())); + this.Disposables.Add(this.WhenAnyValue(x => x.MainPanel.CurrentTab).Subscribe(_ => this.OnCurrentTabChange(this.MainPanel))); + this.Disposables.Add(this.WhenAnyValue(x => x.SidePanel.CurrentTab).Subscribe(_ => this.OnCurrentTabChange(this.SidePanel))); this.Disposables.Add(this.sessionService.OpenIterations.CountChanged.Subscribe(this.CloseTabIfIterationClosed)); - this.Disposables.Add(this.OpenTabs.Connect().WhereReasonsAre(ListChangeReason.Remove, ListChangeReason.RemoveRange).Subscribe(this.OnOpenTabRemoved)); + this.Disposables.Add(this.MainPanel.OpenTabs.Connect().WhereReasonsAre(ListChangeReason.Remove, ListChangeReason.RemoveRange).Subscribe(this.OnOpenTabRemoved)); + this.Disposables.Add(this.SidePanel.OpenTabs.Connect().WhereReasonsAre(ListChangeReason.Remove, ListChangeReason.RemoveRange).Subscribe(this.OnOpenTabRemoved)); } /// /// Gets the collection of all /// - public SourceList OpenTabs { get; } = new(); + private IEnumerable OpenTabs => [.. this.MainPanel.OpenTabs.Items, .. this.SidePanel.OpenTabs.Items]; /// - /// Gets the collection of all s + /// Gets the side tab panel information /// - public SourceList SidePanels { get; } = new(); + public TabPanelInformation SidePanel { get; } = new(); /// - /// Gets or sets the current tab + /// Gets the main tab panel information /// - public TabbedApplicationInformation CurrentTab - { - get => this.currentTab; - set => this.RaiseAndSetIfChanged(ref this.currentTab, value); - } + public TabPanelInformation MainPanel { get; } = new(); /// /// Gets the collection of available @@ -117,8 +110,8 @@ public TabbedApplication SelectedApplication /// The id of the object of interest, which can be an or an /// /// - /// The panel to open the new tab in - public void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, TabPanelInformation sidePanel = null) + /// The panel to open the new tab in + public void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, TabPanelInformation panel) { if (this.serviceProvider.GetService(application.ViewModelType) is not IApplicationBaseViewModel viewModel) { @@ -139,18 +132,9 @@ public void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, } var tabToCreate = new TabbedApplicationInformation(viewModel, application.ComponentType, thingOfInterest); - this.OpenTabs.Add(tabToCreate); + panel.OpenTabs.Add(tabToCreate); this.SelectedApplication = application; - - if (sidePanel == null) - { - this.CurrentTab = tabToCreate; - } - else - { - sidePanel.CurrentTab = tabToCreate; - tabToCreate.Panel = sidePanel; - } + panel.CurrentTab = tabToCreate; } /// @@ -158,25 +142,26 @@ public void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, /// private void OnSelectedApplicationChange() { - if (this.SelectedApplication == null || this.CurrentTab?.ComponentType == this.SelectedApplication?.ComponentType) + if (this.SelectedApplication == null || this.MainPanel.CurrentTab?.ComponentType == this.SelectedApplication?.ComponentType) { return; } - this.CurrentTab = this.OpenTabs.Items.FirstOrDefault(x => x.ComponentType == this.SelectedApplication.ComponentType && x.Panel == null); + this.MainPanel.CurrentTab = this.MainPanel.OpenTabs.Items.FirstOrDefault(x => x.ComponentType == this.SelectedApplication.ComponentType); } /// - /// Method executed everytime the changes + /// Method executed everytime the changes /// - private void OnCurrentTabChange() + /// The panel where the current tab changed + private void OnCurrentTabChange(TabPanelInformation panel) { - if (this.CurrentTab == null) + if (panel.CurrentTab == null) { return; } - this.SelectedApplication = Applications.ExistingApplications.OfType().FirstOrDefault(x => x.ComponentType == this.CurrentTab.ComponentType); + this.SelectedApplication = Applications.ExistingApplications.OfType().FirstOrDefault(x => x.ComponentType == panel.CurrentTab.ComponentType); } /// @@ -185,20 +170,26 @@ private void OnCurrentTabChange() /// The new number of open iterations private void CloseTabIfIterationClosed(int numberOfIterations) { - var iterationTabsToClose = this.OpenTabs.Items + var iterationTabsToClose = this.OpenTabs .Where(x => x.ObjectOfInterest is Iteration && !this.sessionService.OpenIterations.Items.Contains(x.ObjectOfInterest)) .ToList(); - var engineeringModelTabsToClose = this.OpenTabs.Items + var engineeringModelTabsToClose = this.OpenTabs .Where(x => x.ObjectOfInterest is EngineeringModel && !this.sessionService.OpenEngineeringModels.Contains(x.ObjectOfInterest)) .ToList(); - this.OpenTabs.RemoveMany([.. iterationTabsToClose, .. engineeringModelTabsToClose]); + List thingTabsToClose = [.. iterationTabsToClose, .. engineeringModelTabsToClose]; - if (numberOfIterations == 0) + this.MainPanel.OpenTabs.RemoveMany(thingTabsToClose); + this.SidePanel.OpenTabs.RemoveMany(thingTabsToClose); + + if (numberOfIterations != 0) { - this.CurrentTab = null; + return; } + + this.MainPanel.CurrentTab = null; + this.SidePanel.CurrentTab = null; } /// @@ -222,35 +213,24 @@ private void OnOpenTabRemoved(IChangeSet changeSet } } - this.SetCurrentTabAfterTabRemoval(changeSet, this); - - foreach (var panel in this.SidePanels.Items) - { - this.SetCurrentTabAfterTabRemoval(changeSet, panel); - } + SetCurrentTabAfterTabRemoval(changeSet, this.MainPanel); + SetCurrentTabAfterTabRemoval(changeSet, this.SidePanel); } /// - /// Sets the current tab in a after a tab removal, if needed + /// Sets the current tab in a after a tab removal, if needed /// /// The change set to be used to check deletions - /// The to set its current tab - private void SetCurrentTabAfterTabRemoval(IChangeSet changeSet, ITabHandler handler) + /// The to set its current tab + private static void SetCurrentTabAfterTabRemoval(IChangeSet changeSet, TabPanelInformation panel) { var wasCurrentTabRemoved = changeSet .Select(x => x.Item.Current) - .Contains(handler.CurrentTab); - - var selectedSidePanel = handler is TabPanelInformation ? handler : null; + .Contains(panel.CurrentTab); if (wasCurrentTabRemoved) { - handler.CurrentTab = this.OpenTabs.Items.FirstOrDefault(x => x.Panel == selectedSidePanel); - } - - if (selectedSidePanel != null && handler.CurrentTab == null) - { - this.SidePanels.Remove((TabPanelInformation)handler); + panel.CurrentTab = panel.OpenTabs.Items.FirstOrDefault(); } } }