diff --git a/COMET.Web.Common.Tests/Components/IndexComponentTestFixture.cs b/COMET.Web.Common.Tests/Components/IndexComponentTestFixture.cs index c9130b38..f9522088 100644 --- a/COMET.Web.Common.Tests/Components/IndexComponentTestFixture.cs +++ b/COMET.Web.Common.Tests/Components/IndexComponentTestFixture.cs @@ -24,6 +24,7 @@ namespace COMET.Web.Common.Tests.Components { + using System.Collections.ObjectModel; using System.Reflection; using Bunit; @@ -70,6 +71,7 @@ public class IndexComponentTestFixture private TestAuthorizationContext authorization; private SourceList sourceList; private Mock registrationService; + private readonly Guid modelId = Guid.NewGuid(); [SetUp] public void Setup() @@ -82,6 +84,7 @@ public void Setup() this.serverConnectionService.Setup(x => x.ServerConfiguration).Returns(serverConfiguration); this.sourceList = new SourceList(); this.sessionService.Setup(x => x.OpenIterations).Returns(this.sourceList); + this.sessionService.Setup(x => x.OpenEngineeringModels).Returns([]); this.authenticationService = new Mock(); @@ -157,7 +160,7 @@ public void VerifyIndexPageWithRedirectionAuthorized() var engineeringModelSetup = new EngineeringModelSetup { - Iid = Guid.NewGuid(), + Iid = this.modelId, IterationSetup = { new IterationSetup @@ -167,6 +170,12 @@ public void VerifyIndexPageWithRedirectionAuthorized() } }; + var engineeringModel = new EngineeringModel + { + Iid = this.modelId, + EngineeringModelSetup = engineeringModelSetup + }; + var queries = new Dictionary { [QueryKeys.DomainKey] = domain.Iid.ToShortGuid(), @@ -183,6 +192,7 @@ public void VerifyIndexPageWithRedirectionAuthorized() Assert.That(openModel.Instance.ViewModel.SelectedEngineeringModel, Is.Null); this.sessionService.Setup(x => x.GetParticipantModels()).Returns(new List { engineeringModelSetup }); + this.sessionService.Setup(x => x.OpenEngineeringModels).Returns(new ReadOnlyCollection([engineeringModel])); renderer = this.context.RenderComponent(parameters => parameters.Add(p => p.Redirect, url)); diff --git a/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs b/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs index 9bb938ac..735ad160 100644 --- a/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs +++ b/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs @@ -214,7 +214,7 @@ public virtual async Task> OpenSession() /// The of the to select public void PreSelectIteration(Guid modelId, Guid iterationId, Guid domainId) { - this.selectedEngineeringModel = this.AvailableEngineeringModelSetups.FirstOrDefault(x => x.Iid == modelId); + this.selectedEngineeringModel = this.sessionService.OpenEngineeringModels.FirstOrDefault(x => x.Iid == modelId)?.EngineeringModelSetup; var iterationSetup = this.SelectedEngineeringModel?.IterationSetup.Find(x => x.IterationIid == iterationId); if (iterationSetup != null) diff --git a/COMETwebapp.Tests/Pages/TabsTestFixture.cs b/COMETwebapp.Tests/Pages/TabsTestFixture.cs index 16939a81..14db8dee 100644 --- a/COMETwebapp.Tests/Pages/TabsTestFixture.cs +++ b/COMETwebapp.Tests/Pages/TabsTestFixture.cs @@ -31,6 +31,7 @@ namespace COMETwebapp.Tests.Pages using COMET.Web.Common.Model.Configuration; using COMET.Web.Common.Services.ConfigurationService; + using COMET.Web.Common.Services.SessionManagement; using COMET.Web.Common.Services.StringTableService; using COMET.Web.Common.Test.Helpers; using COMET.Web.Common.ViewModels.Components; @@ -46,6 +47,8 @@ namespace COMETwebapp.Tests.Pages using COMETwebapp.ViewModels.Components.EngineeringModel.Rows; using COMETwebapp.ViewModels.Pages; + using DevExpress.Blazor; + using DynamicData; using Microsoft.Extensions.DependencyInjection; @@ -90,12 +93,16 @@ public void Setup() IterationSetup = new IterationSetup { Container = new EngineeringModelSetup() - } + }, + Container = new EngineeringModel() }; var configuration = new Mock(); configuration.Setup(x => x.ServerConfiguration).Returns(new ServerConfiguration()); + var sessionService = new Mock(); + sessionService.Setup(x => x.GetDomainOfExpertise(It.IsAny())).Returns(new DomainOfExpertise()); + this.context.ConfigureDevExpressBlazor(); this.context.Services.AddSingleton(this.viewModel.Object); this.context.Services.AddSingleton(this.engineeringModelBodyViewModel.Object); @@ -103,6 +110,7 @@ public void Setup() this.context.Services.AddSingleton(new Mock().Object); this.context.Services.AddSingleton(new Mock().Object); this.context.Services.AddSingleton(new Mock().Object); + this.context.Services.AddSingleton(sessionService.Object); this.renderer = this.context.RenderComponent(); } @@ -140,6 +148,32 @@ public async Task VerifyTabComponents() Assert.That(this.renderer.Instance.IsOpenTabVisible, Is.True); } + [Test] + public async Task VerifyTabCustomButton() + { + var openTabs = new SourceList(); + var tabToOpen = new TabbedApplicationInformation(this.engineeringModelBodyViewModel.Object, typeof(EngineeringModelBody), this.iteration); + openTabs.Add(tabToOpen); + this.viewModel.Setup(x => x.OpenTabs).Returns(openTabs); + this.viewModel.Setup(x => x.CurrentTab).Returns(tabToOpen); + this.renderer.Render(); + + var tabCustomButton = this.renderer.FindComponents().First(x => x.Instance.Id == "tab-custom-option-button"); + await this.renderer.InvokeAsync(tabCustomButton.Instance.Click.InvokeAsync); + Assert.That(this.renderer.Instance.IsOpenTabVisible, Is.True); + + var openTabComponent = this.renderer.FindComponent(); + await this.renderer.InvokeAsync(openTabComponent.Instance.OnCancel.Invoke); + Assert.That(this.renderer.Instance.IsOpenTabVisible, Is.False); + + tabToOpen = new TabbedApplicationInformation(this.engineeringModelBodyViewModel.Object, typeof(EngineeringModelBody), null); + openTabs.ReplaceAt(0, tabToOpen); + this.renderer.Render(); + tabCustomButton = this.renderer.FindComponents().First(x => x.Instance.Id == "tab-custom-option-button"); + await this.renderer.InvokeAsync(tabCustomButton.Instance.Click.InvokeAsync); + Assert.That(this.renderer.Instance.IsOpenTabVisible, Is.False); + } + [Test] public void VerifyTabsPage() { diff --git a/COMETwebapp/Components/Tabs/OpenTab.razor b/COMETwebapp/Components/Tabs/OpenTab.razor index c26880ab..c0f37170 100644 --- a/COMETwebapp/Components/Tabs/OpenTab.razor +++ b/COMETwebapp/Components/Tabs/OpenTab.razor @@ -49,7 +49,8 @@ Data="@(this.ViewModel.EngineeringModelSetups)" TextFieldName="@nameof(EngineeringModelSetup.Name)" @bind-Value="@(this.ViewModel.SelectedEngineeringModel)" - NullText="Select an Engineering Model"/> + NullText="Select an Engineering Model" + Enabled="@(this.ModelId == Guid.Empty)" /> @@ -57,7 +58,8 @@ Data="@(this.ViewModel.AvailablesDomainOfExpertises)" TextFieldName="@nameof(DomainOfExpertise.Name)" @bind-Value="@(this.ViewModel.SelectedDomainOfExpertise)" - NullText="Select a Domain of Expertise"> + NullText="Select a Domain of Expertise" + Enabled="@(this.DomainId == Guid.Empty)"> @if (this.ViewModel.IsCurrentModelOpened) { @@ -79,7 +81,8 @@ TextFieldName="@nameof(IterationData.IterationName)" @bind-Value="@(this.ViewModel.SelectedIterationSetup)" NullText="Select an Iteration" - ReadOnly="@(this.ViewModel.IsCurrentModelOpened)"/> + ReadOnly="@(this.ViewModel.IsCurrentModelOpened)" + Enabled="@(this.IterationId == Guid.Empty)" /> } @@ -107,4 +110,4 @@ RenderStyleMode="ButtonRenderStyleMode.Outline"/> } - \ No newline at end of file + diff --git a/COMETwebapp/Components/Tabs/TabComponent.razor b/COMETwebapp/Components/Tabs/TabComponent.razor index aa2acb41..c0b230b3 100644 --- a/COMETwebapp/Components/Tabs/TabComponent.razor +++ b/COMETwebapp/Components/Tabs/TabComponent.razor @@ -25,12 +25,23 @@ @onclick="@(() => this.OnClick?.Invoke())"> @(this.Text) + + @if (this.CustomOptionIcon is not null) + { + + + + } + CssClass="px-1" + Id="tab-button"> - - \ No newline at end of file + diff --git a/COMETwebapp/Components/Tabs/TabComponent.razor.cs b/COMETwebapp/Components/Tabs/TabComponent.razor.cs index 5ac97112..a3790502 100644 --- a/COMETwebapp/Components/Tabs/TabComponent.razor.cs +++ b/COMETwebapp/Components/Tabs/TabComponent.razor.cs @@ -40,11 +40,17 @@ public partial class TabComponent : DisposableComponent public string Text { get; set; } /// - /// Gets or sets the icon to be displayed + /// Gets or sets the icon to be displayed on the right side of a tab /// [Parameter] public Type Icon { get; set; } + /// + /// Gets or sets the icon to be displayed as an option icon from a tab + /// + [Parameter] + public Type CustomOptionIcon { get; set; } + /// /// Gets or sets the action to be executed when the tab is clicked /// @@ -57,6 +63,12 @@ public partial class TabComponent : DisposableComponent [Parameter] public Action OnIconClick { get; set; } + /// + /// Gets or sets the action to be executed when the custom option icon button is clicked + /// + [Parameter] + public Action OnCustomOptionIconClick { get; set; } + /// /// Gets or sets the condition to check if this tab is the current one /// diff --git a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor index 92e7e026..d07fe76e 100644 --- a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor +++ b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor @@ -31,8 +31,10 @@ { } diff --git a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs index 04a1125c..3ae5831c 100644 --- a/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs +++ b/COMETwebapp/Components/Tabs/TabsPanelComponent.razor.cs @@ -70,6 +70,12 @@ public partial class TabsPanelComponent : DisposableComponent [Parameter] public Action OnOpenTabClick { get; set; } + /// + /// Gets or sets the method to be executed when the open new tab for the selected model button is clicked + /// + [Parameter] + public EventCallback OnCreateTabForModel { get; set; } + /// /// Gets or sets the method to be executed when the remove tab button is clicked /// diff --git a/COMETwebapp/Pages/Tabs.razor b/COMETwebapp/Pages/Tabs.razor index e6a1c9d2..3d029b73 100644 --- a/COMETwebapp/Pages/Tabs.razor +++ b/COMETwebapp/Pages/Tabs.razor @@ -30,6 +30,7 @@ + CloseOnEscape="true" + Closed="@(() => this.ResetOpenTabPopup())"> + Panel="@(this.SelectedSidePanel)" + ModelId="this.ModelId" + IterationId="this.IterationId" + DomainId="this.DomainId"/> diff --git a/COMETwebapp/Pages/Tabs.razor.cs b/COMETwebapp/Pages/Tabs.razor.cs index df347b6e..feb2419c 100644 --- a/COMETwebapp/Pages/Tabs.razor.cs +++ b/COMETwebapp/Pages/Tabs.razor.cs @@ -24,7 +24,10 @@ namespace COMETwebapp.Pages { + using CDP4Common.EngineeringModelData; + using COMET.Web.Common.Extensions; + using COMET.Web.Common.Services.SessionManagement; using COMETwebapp.Model; using COMETwebapp.ViewModels.Pages; @@ -50,12 +53,33 @@ public partial class Tabs /// private IEnumerable OpenTabsFromSelectedApplication => this.ViewModel.OpenTabs.Items.Where(x => x.ComponentType == this.ViewModel.SelectedApplication?.ComponentType); + /// + /// The model id to fill the opentab form, if needed + /// + private Guid ModelId { get; set; } + + /// + /// The iteration id to fill the opentab form, if needed + /// + private Guid IterationId { get; set; } + + /// + /// The domain id to fill the opentab form, if needed + /// + private Guid DomainId { get; set; } + /// /// Gets or sets the injected /// [Inject] public ITabsViewModel ViewModel { get; set; } + /// + /// Gets or sets the injected + /// + [Inject] + public ISessionService SessionService { get; set; } + /// /// Gets the open tab component visibility /// @@ -113,7 +137,41 @@ private void SetOpenTabVisibility(bool visibility) private void OnOpenTabClick(TabPanelInformation sidePanel = null) { this.SelectedSidePanel = sidePanel; - this.SetOpenTabVisibility(true); + this.SetOpenTabVisibility(true); + } + + /// + /// Resets the preset id's used to fill the open tab form + /// + private void ResetOpenTabPopup() + { + this.DomainId = Guid.Empty; + this.IterationId = Guid.Empty; + this.ModelId = Guid.Empty; + } + + /// + /// Method executed when the button to open a new view of the selected model is clicked + /// + /// The selected tab that contains the model of interest + private void OnCreateTabForModel(TabbedApplicationInformation tabbedApplicationInformation) + { + var iterationOfInterest = tabbedApplicationInformation.ObjectOfInterest switch + { + Iteration iteration => iteration, + CDP4Common.EngineeringModelData.EngineeringModel model => model.Iteration.First(x => x.IterationSetup.FrozenOn == null), + _ => null + }; + + if (iterationOfInterest == null) + { + return; + } + + this.IterationId = iterationOfInterest.Iid; + this.ModelId = ((CDP4Common.EngineeringModelData.EngineeringModel)iterationOfInterest.Container).Iid; + this.DomainId = this.SessionService.GetDomainOfExpertise(iterationOfInterest).Iid; + this.SetOpenTabVisibility(true); } } }