diff --git a/COMETwebapp.Tests/Components/Tabs/OpenTabTestFixture.cs b/COMETwebapp.Tests/Components/Tabs/OpenTabTestFixture.cs index e920f5d4..386a7d97 100644 --- a/COMETwebapp.Tests/Components/Tabs/OpenTabTestFixture.cs +++ b/COMETwebapp.Tests/Components/Tabs/OpenTabTestFixture.cs @@ -105,9 +105,11 @@ public void VerifyOnInitialized() [Test] public async Task VerifyOpenButton() { + var panel = new TabPanelInformation(); + var openButton = this.renderer.FindComponents().First(x => x.Instance.Id == "opentab__button"); await this.renderer.InvokeAsync(openButton.Instance.Click.InvokeAsync); - this.viewModel.Verify(x => x.OpenTab(), Times.Once); + this.viewModel.Verify(x => x.OpenTab(It.IsAny()), Times.Once); var bookEditorBodyComponent = new TabbedApplication { @@ -118,7 +120,7 @@ public async Task VerifyOpenButton() this.viewModel.Setup(x => x.SelectedApplication).Returns(bookEditorBodyComponent); await this.renderer.InvokeAsync(openButton.Instance.Click.InvokeAsync); - this.viewModel.Verify(x => x.OpenTab(), Times.Exactly(2)); + this.viewModel.Verify(x => x.OpenTab(It.IsAny()), Times.Exactly(2)); var modelDashboardBodyComponent = new TabbedApplication { @@ -130,7 +132,7 @@ public async Task VerifyOpenButton() this.viewModel.Setup(x => x.SelectedApplication).Returns(modelDashboardBodyComponent); this.renderer.Render(); await this.renderer.InvokeAsync(openButton.Instance.Click.InvokeAsync); - this.viewModel.Verify(x => x.OpenTab(), Times.Exactly(3)); + this.viewModel.Verify(x => x.OpenTab(It.IsAny()), Times.Exactly(3)); } } } diff --git a/COMETwebapp.Tests/Components/Tabs/TabsPanelComponentTestFixture.cs b/COMETwebapp.Tests/Components/Tabs/TabsPanelComponentTestFixture.cs new file mode 100644 index 00000000..aeb1511a --- /dev/null +++ b/COMETwebapp.Tests/Components/Tabs/TabsPanelComponentTestFixture.cs @@ -0,0 +1,140 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// 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.Tests.Components.Tabs +{ + using Bunit; + + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using COMET.Web.Common.Model.Configuration; + using COMET.Web.Common.Services.ConfigurationService; + using COMET.Web.Common.Test.Helpers; + + using COMETwebapp.Components.EngineeringModel; + using COMETwebapp.Components.Tabs; + using COMETwebapp.Model; + using COMETwebapp.Utilities; + using COMETwebapp.ViewModels.Components.EngineeringModel; + using COMETwebapp.ViewModels.Components.EngineeringModel.Options; + using COMETwebapp.ViewModels.Components.EngineeringModel.Rows; + using COMETwebapp.ViewModels.Pages; + + using DevExpress.Blazor; + + using DynamicData; + + using Microsoft.Extensions.DependencyInjection; + + using Moq; + + using NUnit.Framework; + + using TestContext = Bunit.TestContext; + + [TestFixture] + public class TabsPanelComponentTestFixture + { + private TestContext context; + private IRenderedComponent renderer; + private Mock viewModel; + private Mock engineeringModelBodyViewModel; + private Iteration iteration; + + [SetUp] + public void SetUp() + { + this.context = new TestContext(); + this.context.ConfigureDevExpressBlazor(); + + var engineeringModelBodyApplication = Applications.ExistingApplications.OfType().First(x => x.Url == WebAppConstantValues.EngineeringModelPage); + + var optionsTableViewModel = new Mock(); + optionsTableViewModel.Setup(x => x.Rows).Returns(new SourceList()); + optionsTableViewModel.Setup(x => x.CurrentThing).Returns(new Option()); + this.engineeringModelBodyViewModel = new Mock(); + this.engineeringModelBodyViewModel.Setup(x => x.OptionsTableViewModel).Returns(optionsTableViewModel.Object); + + this.iteration = new Iteration + { + IterationSetup = new IterationSetup + { + Container = new EngineeringModelSetup() + } + }; + + var configuration = new Mock(); + configuration.Setup(x => x.ServerConfiguration).Returns(new ServerConfiguration()); + + this.viewModel = new Mock(); + var openTabs = new SourceList(); + openTabs.Add(new TabbedApplicationInformation(this.engineeringModelBodyViewModel.Object, typeof(EngineeringModelBody), this.iteration)); + this.viewModel.Setup(x => x.OpenTabs).Returns(openTabs); + this.viewModel.Setup(x => x.CurrentTab).Returns(openTabs.Items.First()); + this.viewModel.Setup(x => x.SelectedApplication).Returns(engineeringModelBodyApplication); + this.viewModel.Setup(x => x.SidePanels).Returns(new SourceList()); + + this.context.Services.AddSingleton(this.viewModel.Object); + this.context.Services.AddSingleton(this.engineeringModelBodyViewModel.Object); + this.context.Services.AddSingleton(configuration.Object); + + 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.CssClass, "css-test-class"); + parameters.Add(p => p.IsSidePanelAvailable, true); + parameters.Add(p => p.Tabs, this.viewModel.Object.OpenTabs.Items.ToList()); + }); + } + + [TearDown] + public void Teardown() + { + this.context.CleanContext(); + this.context.Dispose(); + } + + [Test] + public async Task VerifyAddSidePanel() + { + var sidePanelButton = this.renderer.FindComponents().First(x => x.Instance.Id == "new-side-panel-button"); + await this.renderer.InvokeAsync(sidePanelButton.Instance.Click.InvokeAsync); + + this.viewModel.VerifySet(x => x.CurrentTab = null, Times.Once); + } + + [Test] + public void VerifyComponent() + { + Assert.Multiple(() => + { + Assert.That(this.renderer.Instance.ViewModel, Is.EqualTo(this.viewModel.Object)); + Assert.That(this.renderer.Instance, Is.Not.Null); + Assert.That(this.renderer.Markup, Does.Contain("css-test-class")); + }); + } + } +} diff --git a/COMETwebapp.Tests/Pages/TabsTestFixture.cs b/COMETwebapp.Tests/Pages/TabsTestFixture.cs index 026c4843..16939a81 100644 --- a/COMETwebapp.Tests/Pages/TabsTestFixture.cs +++ b/COMETwebapp.Tests/Pages/TabsTestFixture.cs @@ -72,8 +72,12 @@ public void Setup() var engineeringModelBodyApplication = Applications.ExistingApplications.OfType().First(x => x.Url == WebAppConstantValues.EngineeringModelPage); this.viewModel = new Mock(); + + var sidePanels = new SourceList(); + sidePanels.Add(new TabPanelInformation()); this.viewModel.Setup(x => x.OpenTabs).Returns(new SourceList()); this.viewModel.Setup(x => x.SelectedApplication).Returns(engineeringModelBodyApplication); + this.viewModel.Setup(x => x.SidePanels).Returns(sidePanels); var optionsTableViewModel = new Mock(); optionsTableViewModel.Setup(x => x.Rows).Returns(new SourceList()); diff --git a/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs index 267f8ebe..2c78cc78 100644 --- a/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs @@ -93,7 +93,8 @@ public void Teardown() [Test] public async Task VerifyOpenIterationAndModel() { - await this.viewModel.OpenTab(); + var panel = new TabPanelInformation(); + await this.viewModel.OpenTab(panel); Assert.Multiple(() => { @@ -106,18 +107,18 @@ public async Task VerifyOpenIterationAndModel() this.viewModel.SelectedEngineeringModel = ((EngineeringModel)this.sessionService.Object.OpenIterations.Items.First().Container).EngineeringModelSetup; this.viewModel.SelectedIterationSetup = new IterationData(this.viewModel.SelectedEngineeringModel.IterationSetup[0]); this.viewModel.SelectedDomainOfExpertise = new DomainOfExpertise(); - await this.viewModel.OpenTab(); + await this.viewModel.OpenTab(panel); Assert.Multiple(() => { - this.tabsViewModel.Verify(x => x.CreateNewTab(It.IsAny(), It.IsAny()), Times.Once); + this.tabsViewModel.Verify(x => x.CreateNewTab(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); this.sessionService.Verify(x => x.ReadIteration(It.IsAny(), It.IsAny()), Times.Once); }); var newEngineeringModel = new EngineeringModel { EngineeringModelSetup = this.viewModel.SelectedEngineeringModel }; this.sessionService.Setup(x => x.OpenEngineeringModels).Returns([newEngineeringModel]); - await this.viewModel.OpenTab(); + await this.viewModel.OpenTab(panel); this.sessionService.Verify(x => x.SwitchDomain(It.IsAny(), It.IsAny()), Times.Once); } } diff --git a/COMETwebapp.Tests/ViewModels/Pages/TabsViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Pages/TabsViewModelTestFixture.cs index 052905fd..40473fb3 100644 --- a/COMETwebapp.Tests/ViewModels/Pages/TabsViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Pages/TabsViewModelTestFixture.cs @@ -107,6 +107,7 @@ public void VerifyTabRemoval() var engineeringModelApplication2 = this.viewModel.AvailableApplications.First(x => x.Url == WebAppConstantValues.EngineeringModelPage); this.viewModel.CreateNewTab(engineeringModelApplication1, Guid.Empty); this.viewModel.CreateNewTab(engineeringModelApplication2, Guid.Empty); + this.viewModel.SidePanels.Add(new TabPanelInformation()); var removedTab = this.viewModel.OpenTabs.Items.ElementAt(1); diff --git a/COMETwebapp/Components/Tabs/OpenTab.razor.cs b/COMETwebapp/Components/Tabs/OpenTab.razor.cs index 8586d110..185ffa71 100644 --- a/COMETwebapp/Components/Tabs/OpenTab.razor.cs +++ b/COMETwebapp/Components/Tabs/OpenTab.razor.cs @@ -28,8 +28,8 @@ namespace COMETwebapp.Components.Tabs using COMET.Web.Common.Components; using COMET.Web.Common.Extensions; - using COMET.Web.Common.ViewModels.Components.Applications; + using COMETwebapp.Model; using COMETwebapp.ViewModels.Components.Common.OpenTab; using Microsoft.AspNetCore.Components; @@ -60,12 +60,18 @@ public partial class OpenTab : OpenModel public Action OnTabOpened { get; set; } /// - /// Gets the condition to check if the selected application thing type is an + /// Gets or sets the panel to open the new tab, if any + /// + [Parameter] + public TabPanelInformation Panel { get; set; } + + /// + /// Gets the condition to check if the selected application thing type is an /// private bool IsEngineeringModelView => this.ViewModel.SelectedApplication?.ThingTypeOfInterest == typeof(EngineeringModel); /// - /// Gets the condition to check if the selected application thing type is an + /// Gets the condition to check if the selected application thing type is an /// private bool IsIterationView => this.ViewModel.SelectedApplication?.ThingTypeOfInterest == typeof(Iteration); @@ -99,7 +105,7 @@ protected override bool AreRequiredFieldSelected() /// A private async Task OpenModelAndNavigateToView() { - await this.ViewModel.OpenTab(); + await this.ViewModel.OpenTab(this.Panel); await this.InvokeAsync(this.StateHasChanged); this.OnTabOpened?.Invoke(); } diff --git a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor new file mode 100644 index 00000000..684ebf9b --- /dev/null +++ b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor @@ -0,0 +1,63 @@ + +@using CDP4Common.CommonData +@inherits DisposableComponent + +
+ @if (this.Handler.CurrentTab is not null) + { +
+ + @foreach (var tab in this.Tabs) + { + + } + + + + @if (this.IsSidePanelAvailable) + { + + + + } + +
+
+ +
+ } +
diff --git a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs new file mode 100644 index 00000000..04a1125c --- /dev/null +++ b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs @@ -0,0 +1,123 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// 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.Components.Tabs +{ + using CDP4Common.EngineeringModelData; + + using COMET.Web.Common.Components; + + using COMETwebapp.Model; + using COMETwebapp.ViewModels.Pages; + + using DynamicData; + + using Microsoft.AspNetCore.Components; + + /// + /// Core component for the Tabs page + /// + public partial class TabsPanelComponent : DisposableComponent + { + /// + /// Gets or sets the custom css class to be used in the container component + /// + [Parameter] + public string CssClass { get; set; } + + /// + /// Gets or sets the tab handler to be used + /// + [Parameter] + public ITabHandler Handler { get; set; } + + /// + /// Gets or sets the + /// + [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 + /// + [Parameter] + public Action OnOpenTabClick { get; set; } + + /// + /// Gets or sets the method to be executed when the remove tab button is clicked + /// + [Parameter] + public EventCallback OnRemoveTabClick { get; set; } + + /// + /// Gets or sets the method to be executed when the tab is clicked + /// + [Parameter] + public EventCallback<(TabbedApplicationInformation, ITabHandler)> OnTabClick { get; set; } + + /// + /// Gets or sets the condition to check if the side panel should be available + /// + [Parameter] + public bool IsSidePanelAvailable { get; set; } + + /// + /// Gets the tab text for the given object of interest + /// + /// The object of interest to get its tab text + /// The tab text + private static string GetTabText(object objectOfInterest) + { + return objectOfInterest switch + { + Iteration iteration => iteration.QueryName(), + EngineeringModel engineeringModel => engineeringModel.EngineeringModelSetup.Name, + _ => string.Empty + }; + } + + /// + /// Adds a new side panel to the tabs page + /// + private void AddSidePanel() + { + var currentTab = this.ViewModel.CurrentTab; + + var newPanel = new TabPanelInformation + { + CurrentTab = currentTab + }; + + currentTab.Panel = newPanel; + this.ViewModel.SidePanels.Add(newPanel); + this.ViewModel.CurrentTab = this.ViewModel.OpenTabs.Items.LastOrDefault(x => x.ComponentType == this.ViewModel.SelectedApplication.ComponentType && x.Panel == null); + } + } +} diff --git a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.css b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.css new file mode 100644 index 00000000..86083126 --- /dev/null +++ b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.css @@ -0,0 +1,20 @@ +.tabs-row { + background-color: var(--colors-primary-25); + border-bottom: solid 1px var(--colors-gray-100); + padding: 16px; + border-radius: 24px 0px 0px 0px; + min-width: 60vw; +} + +#tabs-page-content { + min-width: 60vw; +} + +.panel-view { + overflow-x: auto; + width: 100%; +} + +.panel-view:not(:first-child) { + border-left: solid 1px var(--colors-gray-100); +} diff --git a/COMETwebapp/Model/ITabHandler.cs b/COMETwebapp/Model/ITabHandler.cs new file mode 100644 index 00000000..2ca83d55 --- /dev/null +++ b/COMETwebapp/Model/ITabHandler.cs @@ -0,0 +1,37 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// 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 new file mode 100644 index 00000000..3ee8e30a --- /dev/null +++ b/COMETwebapp/Model/TabPanelInformation.cs @@ -0,0 +1,44 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// 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 required information related to a panel + /// + public class TabPanelInformation : ITabHandler + { + /// + /// Initializes a new instance of the class. + /// + public TabPanelInformation() + { + } + + /// + /// Gets or sets the current tab + /// + public TabbedApplicationInformation CurrentTab { get; set; } + } +} diff --git a/COMETwebapp/Model/TabbedApplicationInformation.cs b/COMETwebapp/Model/TabbedApplicationInformation.cs index c80f172d..276fab50 100644 --- a/COMETwebapp/Model/TabbedApplicationInformation.cs +++ b/COMETwebapp/Model/TabbedApplicationInformation.cs @@ -58,5 +58,10 @@ 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 507129c7..e6a1c9d2 100644 --- a/COMETwebapp/Pages/Tabs.razor +++ b/COMETwebapp/Pages/Tabs.razor @@ -22,35 +22,39 @@ @attribute [Route(WebAppConstantValues.TabsPage)] @using COMETwebapp.Components.Tabs @using COMETwebapp.Utilities -@using CDP4Common.CommonData @inherits DisposableComponent -@if (this.ViewModel.CurrentTab is not null) -{ -
+
+ @if (this.ViewModel.CurrentTab is not null) + { + - @foreach (var tab in this.ViewModel.OpenTabs.Items.Where(x => x.ComponentType == this.ViewModel.SelectedApplication?.ComponentType)) + @foreach (var panelsGrouping in this.OpenTabsFromSelectedApplication.Where(x => x.Panel is not null).GroupBy(x => x.Panel)) { - + } - - -
-} -else -{ -
-
- + } + else + { +
+
+ +
-
-} + } +
+ OnTabOpened="@(() => this.SetOpenTabVisibility(false))" + Panel="@(this.SelectedSidePanel)"/> - -@if (this.ViewModel.CurrentTab is not null) -{ -
- -
-} \ No newline at end of file diff --git a/COMETwebapp/Pages/Tabs.razor.cs b/COMETwebapp/Pages/Tabs.razor.cs index 5852d4e3..df347b6e 100644 --- a/COMETwebapp/Pages/Tabs.razor.cs +++ b/COMETwebapp/Pages/Tabs.razor.cs @@ -24,8 +24,6 @@ namespace COMETwebapp.Pages { - using CDP4Common.EngineeringModelData; - using COMET.Web.Common.Extensions; using COMETwebapp.Model; @@ -42,6 +40,16 @@ namespace COMETwebapp.Pages /// public partial class Tabs { + /// + /// Gets or sets the selected side panel + /// + private TabPanelInformation SelectedSidePanel { get; set; } + + /// + /// Collection of open tabs that belong from the selected application + /// + private IEnumerable OpenTabsFromSelectedApplication => this.ViewModel.OpenTabs.Items.Where(x => x.ComponentType == this.ViewModel.SelectedApplication?.ComponentType); + /// /// Gets or sets the injected /// @@ -73,9 +81,10 @@ protected override void OnInitialized() /// Method executed when a tab is clicked /// /// The tab to be set - private void OnTabClick(TabbedApplicationInformation tabbedApplicationInformation) + /// The tab handler to handle the tab click + private static void OnTabClick(TabbedApplicationInformation tabbedApplicationInformation, ITabHandler tabHandler) { - this.ViewModel.CurrentTab = tabbedApplicationInformation; + tabHandler.CurrentTab = tabbedApplicationInformation; } /// @@ -98,18 +107,13 @@ private void SetOpenTabVisibility(bool visibility) } /// - /// Gets the tab text for the given object of interest + /// Method executed when the open tab button is clicked /// - /// The object of interest to get its tab text - /// The tab text - private static string GetTabText(object objectOfInterest) + /// The side panel to be set, if any + private void OnOpenTabClick(TabPanelInformation sidePanel = null) { - return objectOfInterest switch - { - Iteration iteration => iteration.QueryName(), - CDP4Common.EngineeringModelData.EngineeringModel engineeringModel => engineeringModel.EngineeringModelSetup.Name, - _ => string.Empty - }; + this.SelectedSidePanel = sidePanel; + this.SetOpenTabVisibility(true); } } } diff --git a/COMETwebapp/Pages/Tabs.razor.css b/COMETwebapp/Pages/Tabs.razor.css index 7a3250fe..71454a9f 100644 --- a/COMETwebapp/Pages/Tabs.razor.css +++ b/COMETwebapp/Pages/Tabs.razor.css @@ -1,11 +1,4 @@ -.tabs-row { - background-color: var(--colors-primary-25); - border-bottom: solid 1px var(--colors-gray-100); - padding: 16px; - border-radius: 24px 0px 0px 0px; -} - -.open-tab-box { +.open-tab-box { width: 500px; align-items: center; display: flex; @@ -17,4 +10,5 @@ justify-content: center; align-items: center; height: 90vh; -} \ No newline at end of file + width: 100%; +} diff --git a/COMETwebapp/ViewModels/Components/Common/OpenTab/IOpenTabViewModel.cs b/COMETwebapp/ViewModels/Components/Common/OpenTab/IOpenTabViewModel.cs index f28250cc..4dd9c61e 100644 --- a/COMETwebapp/ViewModels/Components/Common/OpenTab/IOpenTabViewModel.cs +++ b/COMETwebapp/ViewModels/Components/Common/OpenTab/IOpenTabViewModel.cs @@ -59,7 +59,8 @@ public interface IOpenTabViewModel : IOpenModelViewModel /// /// Opens the based on the selected field /// + /// The for which the new tab will be opened /// A - Task OpenTab(); + Task OpenTab(TabPanelInformation panel); } } diff --git a/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs b/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs index 822f82cc..71d1c08d 100644 --- a/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs +++ b/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs @@ -30,7 +30,6 @@ namespace COMETwebapp.ViewModels.Components.Common.OpenTab using COMET.Web.Common.Services.ConfigurationService; using COMET.Web.Common.Services.SessionManagement; using COMET.Web.Common.ViewModels.Components; - using COMET.Web.Common.ViewModels.Components.Applications; using COMETwebapp.Model; using COMETwebapp.ViewModels.Pages; @@ -104,8 +103,9 @@ public TabbedApplication SelectedApplication /// /// Opens the based on the selected field /// + /// The for which the new tab will be opened /// A - public async Task OpenTab() + public async Task OpenTab(TabPanelInformation panel) { var result = new Result(); var isIteration = this.SelectedApplication?.ThingTypeOfInterest == typeof(Iteration); @@ -124,7 +124,7 @@ public async Task OpenTab() if (result.IsSuccess) { - this.tabsViewModel.CreateNewTab(this.SelectedApplication, isIteration ? this.SelectedEngineeringModelIteration.Iid : this.SelectedEngineeringModel.EngineeringModelIid); + this.tabsViewModel.CreateNewTab(this.SelectedApplication, isIteration ? this.SelectedEngineeringModelIteration.Iid : this.SelectedEngineeringModel.EngineeringModelIid, panel); } } } diff --git a/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs b/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs index 51ff9ff8..0d46bffe 100644 --- a/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs +++ b/COMETwebapp/ViewModels/Pages/ITabsViewModel.cs @@ -33,7 +33,7 @@ namespace COMETwebapp.ViewModels.Pages /// /// The contains logic and behavior that are required to support multi-tabs application /// - public interface ITabsViewModel + public interface ITabsViewModel : ITabHandler { /// /// Gets the collection of all @@ -51,9 +51,9 @@ public interface ITabsViewModel TabbedApplication SelectedApplication { get; set; } /// - /// Gets or sets the current tab + /// Gets the collection of all s /// - TabbedApplicationInformation CurrentTab { get; set; } + SourceList SidePanels { get; } /// /// Creates a new tab and sets it to current @@ -63,6 +63,7 @@ public interface ITabsViewModel /// The id of the object of interest, which can be an or an /// /// - void CreateNewTab(TabbedApplication application, Guid objectOfInterestId); + /// The panel to open the new tab in + void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, TabPanelInformation sidePanel = null); } } diff --git a/COMETwebapp/ViewModels/Pages/TabsViewModel.cs b/COMETwebapp/ViewModels/Pages/TabsViewModel.cs index ba5c690b..bacfe704 100644 --- a/COMETwebapp/ViewModels/Pages/TabsViewModel.cs +++ b/COMETwebapp/ViewModels/Pages/TabsViewModel.cs @@ -80,6 +80,11 @@ public TabsViewModel(ISessionService sessionService, IServiceProvider servicePro /// public SourceList OpenTabs { get; } = new(); + /// + /// Gets the collection of all s + /// + public SourceList SidePanels { get; } = new(); + /// /// Gets or sets the current tab /// @@ -111,7 +116,8 @@ public TabbedApplication SelectedApplication /// The id of the object of interest, which can be an or an /// /// - public void CreateNewTab(TabbedApplication application, Guid objectOfInterestId) + /// The panel to open the new tab in + public void CreateNewTab(TabbedApplication application, Guid objectOfInterestId, TabPanelInformation sidePanel = null) { if (this.serviceProvider.GetService(application.ViewModelType) is not IApplicationBaseViewModel viewModel) { @@ -131,13 +137,24 @@ public void CreateNewTab(TabbedApplication application, Guid objectOfInterestId) thingOfInterest = this.sessionService.OpenEngineeringModels.FirstOrDefault(x => x.Iid == objectOfInterestId); } + var tabToCreate = new TabbedApplicationInformation(viewModel, application.ComponentType, thingOfInterest); + if (thingOfInterest != null) { - this.OpenTabs.Add(new TabbedApplicationInformation(viewModel, application.ComponentType, thingOfInterest)); + this.OpenTabs.Add(tabToCreate); } this.SelectedApplication = application; - this.CurrentTab = this.OpenTabs.Items.LastOrDefault(); + + if (sidePanel == null) + { + this.CurrentTab = tabToCreate; + } + else + { + sidePanel.CurrentTab = tabToCreate; + tabToCreate.Panel = sidePanel; + } } /// @@ -150,7 +167,7 @@ private void OnSelectedApplicationChange() return; } - this.CurrentTab = this.OpenTabs.Items.FirstOrDefault(x => x.ComponentType == this.SelectedApplication.ComponentType); + this.CurrentTab = this.OpenTabs.Items.FirstOrDefault(x => x.ComponentType == this.SelectedApplication.ComponentType && x.Panel == null); } /// @@ -196,13 +213,35 @@ private void OnOpenTabRemoved(IChangeSet changeSet } } + this.SetCurrentTabAfterTabRemoval(changeSet, this); + + foreach (var panel in this.SidePanels.Items) + { + this.SetCurrentTabAfterTabRemoval(changeSet, panel); + } + } + + /// + /// 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) + { var wasCurrentTabRemoved = changeSet .Select(x => x.Item.Current) - .Contains(this.CurrentTab); + .Contains(handler.CurrentTab); + + var selectedSidePanel = handler is TabPanelInformation ? handler : null; if (wasCurrentTabRemoved) { - this.CurrentTab = this.OpenTabs.Items.FirstOrDefault(x => x.ComponentType == this.SelectedApplication.ComponentType); + handler.CurrentTab = this.OpenTabs.Items.FirstOrDefault(x => x.ComponentType == this.SelectedApplication.ComponentType && x.Panel == selectedSidePanel); + } + + if (selectedSidePanel != null && handler.CurrentTab == null) + { + this.SidePanels.Remove((TabPanelInformation)handler); } } }