From 174c06422031534b27d6977f7de40efbf5d4c889 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Wed, 11 Dec 2024 17:22:34 +0100 Subject: [PATCH 01/14] First implementation --- COMET.Web.Common/COMET.Web.Common.csproj | 1 + COMET.Web.Common/Components/CardView.razor | 49 +++++ COMET.Web.Common/Components/CardView.razor.cs | 191 ++++++++++++++++++ .../Components/CardView.razor.css | 13 ++ .../ModelEditor/DetailsPanelEditor.razor | 55 +++-- .../ModelEditor/ElementDefinitionTable.razor | 2 +- .../ElementDefinitionTable.razor.css | 2 +- COMETwebapp/wwwroot/css/app.css | 11 + COMETwebapp/wwwroot/css/vars.css | 4 +- 9 files changed, 312 insertions(+), 16 deletions(-) create mode 100644 COMET.Web.Common/Components/CardView.razor create mode 100644 COMET.Web.Common/Components/CardView.razor.cs create mode 100644 COMET.Web.Common/Components/CardView.razor.css diff --git a/COMET.Web.Common/COMET.Web.Common.csproj b/COMET.Web.Common/COMET.Web.Common.csproj index 90d67d8b..e3f4216a 100644 --- a/COMET.Web.Common/COMET.Web.Common.csproj +++ b/COMET.Web.Common/COMET.Web.Common.csproj @@ -36,6 +36,7 @@ + diff --git a/COMET.Web.Common/Components/CardView.razor b/COMET.Web.Common/Components/CardView.razor new file mode 100644 index 00000000..28f4ca4b --- /dev/null +++ b/COMET.Web.Common/Components/CardView.razor @@ -0,0 +1,49 @@ + +@namespace COMET.Web.Common.Components +@typeparam T where T : class +@inherits DisposableComponent + +

+ +

+
+
+ + +
+
+
+ @this.ItemTemplate(@context) +
+
+
+
+
+
+
\ No newline at end of file diff --git a/COMET.Web.Common/Components/CardView.razor.cs b/COMET.Web.Common/Components/CardView.razor.cs new file mode 100644 index 00000000..c841b0de --- /dev/null +++ b/COMET.Web.Common/Components/CardView.razor.cs @@ -0,0 +1,191 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2023-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, Nabil Abbar +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 +// Annex A and Annex C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMET.Web.Common.Components +{ + using FastMember; + + using Microsoft.AspNetCore.Components; + using Microsoft.AspNetCore.Components.Web.Virtualization; + + /// + /// Component used to show a CardView based on a specific type + /// + public partial class CardView : DisposableComponent where T : class + { + /// + /// Gets or sets the item template for the list. + /// + [Parameter] + public RenderFragment? ItemTemplate { get; set; } + + /// + /// Gets or sets the list of items of type T to use + /// + [Parameter] + public ICollection Items { get; set; } = new List(); + + /// + /// Gets or sets a collection of propertynames of type to perform search on + /// + [Parameter] + public string[] SearchFields { get; set; } + + /// + /// Gets or sets the fixed height of a Card, used to calculate the amout of items to load into the DOM in px + /// + [Parameter] + public float ItemSize { get; set; } = 150; + + /// + /// Gets or sets the minimum width of a Card + /// + [Parameter] + public float MinWidth { get; set; } = 250; + + /// + /// A reference to the component for loading items + /// + private Virtualize? virtualize; // Reference to the Virtualize component + + /// + /// The FastMember to use to perform actions on instances of + /// + private TypeAccessor typeAccessor = TypeAccessor.Create(typeof(T)); + + /// + /// The selected Card in the CardView + /// + private T selected; + + /// + /// Gets the dragged node used in drag and drop interactions + /// + private T draggedNode { get; set; } + + /// + /// Gets or sets the term where to search/filter item of + /// + private string searchTerm { get; set; } = string.Empty; + + /// + /// Gets the class to visually show a Card to be selected or unselected + /// + /// + /// + private string GetSelectedClass(T vm) + { + return vm == this.selected ? "selected" : ""; + } + + /// + /// Set the selected item + /// + /// + private void selectOption(T item) + { + this.selected = item; + } + + /// + /// Method invoked when a node is dragged + /// + /// The dragged node + private void OnDragNode(T node) + { + this.draggedNode = node; + } + + /// + /// Method invoked when a node is dropped + /// + /// The target node where the has been dropped + /// A + private async Task OnDropNode(T targetNode) + { + //TODO: Implement + // var targetFolder = (Folder)targetNode.Thing; + + // switch (this.DraggedNode?.Thing) + // { + // case File file: + // await this.ViewModel.FileHandlerViewModel.MoveFile(file, targetFolder); + // break; + // case Folder folder: + // await this.ViewModel.FolderHandlerViewModel.MoveFolder(folder, targetFolder); + // break; + // } + } + + /// + /// Filters the list of items to show in the UI based on the + /// + /// The request to perform filtering of the items list + /// an waitable + private async ValueTask> LoadItems(ItemsProviderRequest request) + { + // Filter items based on the SearchTerm + var filteredItems = string.IsNullOrWhiteSpace(this.searchTerm) + ? this.Items + : this.Items.Where(item => this.FilterItem(item, this.searchTerm)).ToList(); + + // Return paged items for virtualization + var items = filteredItems.Skip(request.StartIndex).Take(request.Count).ToList(); + return new ItemsProviderResult(items, filteredItems.Count); + } + + /// + /// Used to filter items based on the + /// + /// The item to perform searching on + /// The string to search for + /// True if the item's selected properties contain the value to search for, otherwise false + private bool FilterItem(T item, string query) + { + var props = this.typeAccessor.GetMembers(); + + return props.Where(x => this.SearchFields.Contains(x.Name)).Any(prop => + { + var value = this.typeAccessor[item, prop.Name].ToString(); + return value != null && value.Contains(query, StringComparison.OrdinalIgnoreCase); + }); + } + + /// + /// A method that is executed when the user changes the search input element + /// + /// The from the UI element's event + /// + private async Task OnSearchTextChanged(ChangeEventArgs e) + { + this.searchTerm = e.Value?.ToString() ?? string.Empty; + + if (this.virtualize != null) + { + await this.virtualize.RefreshDataAsync(); // Tell Virtualize to refresh data + } + } + } +} diff --git a/COMET.Web.Common/Components/CardView.razor.css b/COMET.Web.Common/Components/CardView.razor.css new file mode 100644 index 00000000..053bbc47 --- /dev/null +++ b/COMET.Web.Common/Components/CardView.razor.css @@ -0,0 +1,13 @@ +.card { + margin-bottom: 10px; + background-color: #f9f9fb; +} + +.card:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.card.selected { + border: 2px solid var(--bs-primary, var(--primary)); +} + diff --git a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor index 2d260b0e..92810001 100644 --- a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor +++ b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor @@ -28,15 +28,46 @@ @if (this.ViewModel.SelectedSystemNode != null) { - - - - - - - - - - - -} \ No newline at end of file + + +
+
+
@context.ParameterTypeName
+
+
+ +
+
+
+
+ @context.ModelCode +
+
+
+
+ @context.SwitchValue: +
+
+ Published: +
+
+
+
@context.ActualValue
+
@context.PublishedValue
+
+
+
+} + +@code +{ + private string[] searchFields = + [ + nameof(ElementDefinitionDetailsRowViewModel.ParameterTypeName), + nameof(ElementDefinitionDetailsRowViewModel.Owner), + nameof(ElementDefinitionDetailsRowViewModel.ModelCode), + nameof(ElementDefinitionDetailsRowViewModel.ActualValue), + nameof(ElementDefinitionDetailsRowViewModel.PublishedValue), + nameof(ElementDefinitionDetailsRowViewModel.SwitchValue) + ]; +} diff --git a/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor b/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor index 95e817d2..d6fb40b9 100644 --- a/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor +++ b/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor @@ -85,7 +85,7 @@ -
+
diff --git a/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor.css b/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor.css index c81656e7..67aafaa3 100644 --- a/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor.css +++ b/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor.css @@ -6,5 +6,5 @@ position: sticky; top: 0px; max-height: 80vh; - overflow: auto; + overflow:hidden; } diff --git a/COMETwebapp/wwwroot/css/app.css b/COMETwebapp/wwwroot/css/app.css index 2f4bcb17..23a63cff 100644 --- a/COMETwebapp/wwwroot/css/app.css +++ b/COMETwebapp/wwwroot/css/app.css @@ -302,3 +302,14 @@ sub { .validation-container:not(:has(li)) { display: none; } + +.starion-pill { + margin: 1px; + height: 19px; + width: auto; + border-radius: 30px; + background-color: white; + border: 1px solid dimgray; + color: dimgray; + padding-top: 0px; +} \ No newline at end of file diff --git a/COMETwebapp/wwwroot/css/vars.css b/COMETwebapp/wwwroot/css/vars.css index d90dde54..f4081555 100644 --- a/COMETwebapp/wwwroot/css/vars.css +++ b/COMETwebapp/wwwroot/css/vars.css @@ -59,7 +59,7 @@ --spacing-18: 4.5rem; --spacing-19: 4.75rem; --spacing-20: 5rem; - --dxbl-listbox-font-size: 0.98rem; - --bs-body-font-size: 0.98rem; + --dxbl-listbox-font-size: 0.88rem; + --bs-body-font-size: 0.88rem; --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } From 815b0a266752d68e9a44bc63c6aa8889372e492d Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 13 Dec 2024 09:05:42 +0100 Subject: [PATCH 02/14] Next step card view --- COMET.Web.Common/COMET.Web.Common.csproj | 1 + .../Components/CardView/CardField.cs | 80 +++++++++++++++++++ .../Components/{ => CardView}/CardView.razor | 19 ++++- .../{ => CardView}/CardView.razor.cs | 63 +++++++++++++-- .../{ => CardView}/CardView.razor.css | 1 - COMET.Web.Common/_Imports.razor | 1 + .../ModelEditor/DetailsPanelEditor.razor | 16 ++-- COMETwebapp/_Imports.razor | 1 + COMETwebapp/wwwroot/css/app.css | 6 +- 9 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 COMET.Web.Common/Components/CardView/CardField.cs rename COMET.Web.Common/Components/{ => CardView}/CardView.razor (73%) rename COMET.Web.Common/Components/{ => CardView}/CardView.razor.cs (79%) rename COMET.Web.Common/Components/{ => CardView}/CardView.razor.css (99%) diff --git a/COMET.Web.Common/COMET.Web.Common.csproj b/COMET.Web.Common/COMET.Web.Common.csproj index e3f4216a..b35f7bfa 100644 --- a/COMET.Web.Common/COMET.Web.Common.csproj +++ b/COMET.Web.Common/COMET.Web.Common.csproj @@ -44,6 +44,7 @@ + diff --git a/COMET.Web.Common/Components/CardView/CardField.cs b/COMET.Web.Common/Components/CardView/CardField.cs new file mode 100644 index 00000000..c8296c2a --- /dev/null +++ b/COMET.Web.Common/Components/CardView/CardField.cs @@ -0,0 +1,80 @@ + +namespace COMET.Web.Common.Components.CardView +{ + using System.Text.RegularExpressions; + + using FastMember; + + using Microsoft.AspNetCore.Components; + using Microsoft.AspNetCore.Components.Rendering; + + public class CardField : ComponentBase + { + private static TypeAccessor typeAccessor { get; set; } + + static CardField() + { + typeAccessor = TypeAccessor.Create(typeof(T)); + } + + [CascadingParameter(Name="CardView")] + private CardView CardView { get; set; } + + [CascadingParameter(Name = "SearchTerm")] + private string SearchTerm { get; set; } + + [Parameter] + public T Context { get; set; } + + [Parameter] + public string FieldName { get; set; } + + [Parameter] + public bool AllowSort { get; set; } = true; + + [Parameter] + public bool AllowSearch { get; set; } = true; + + protected override void OnInitialized() + { + base.OnInitialized(); + this.CardView.RegisterCardField(this); + } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + base.BuildRenderTree(builder); + var value = typeAccessor[this.Context, this.FieldName].ToString(); + + if (this.AllowSearch && !string.IsNullOrWhiteSpace(value) && !string.IsNullOrWhiteSpace(this.SearchTerm)) + { + var separatorPattern = $"({this.SearchTerm})"; + var result = Regex.Split(value, separatorPattern, RegexOptions.IgnoreCase); + var elementCounter = 0; + + foreach (var element in result) + { + if (string.Equals(element, this.SearchTerm, StringComparison.OrdinalIgnoreCase)) + { + builder.OpenElement(elementCounter, "span"); + elementCounter++; + builder.AddAttribute(elementCounter, "class", "search-mark"); + elementCounter++; + builder.AddContent(elementCounter, element); + elementCounter++; + builder.CloseElement(); + } + else + { + builder.AddContent(elementCounter, element); + elementCounter++; + } + } + } + else + { + builder.AddContent(0, value); + } + } + } +} \ No newline at end of file diff --git a/COMET.Web.Common/Components/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor similarity index 73% rename from COMET.Web.Common/Components/CardView.razor rename to COMET.Web.Common/Components/CardView/CardView.razor index 28f4ca4b..0a31c95b 100644 --- a/COMET.Web.Common/Components/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -20,11 +20,18 @@ // limitations under the License. -------------------------------------------------------------------------------> @namespace COMET.Web.Common.Components -@typeparam T where T : class +@typeparam T @inherits DisposableComponent -

- +

+ + + + + +
+

@@ -39,7 +46,11 @@ @ondragstart="@(() => this.OnDragNode(@context))" @ondrop="@(() => this.OnDropNode(@context))">
- @this.ItemTemplate(@context) + + + @this.ItemTemplate(@context) + +
diff --git a/COMET.Web.Common/Components/CardView.razor.cs b/COMET.Web.Common/Components/CardView/CardView.razor.cs similarity index 79% rename from COMET.Web.Common/Components/CardView.razor.cs rename to COMET.Web.Common/Components/CardView/CardView.razor.cs index c841b0de..611b6459 100644 --- a/COMET.Web.Common/Components/CardView.razor.cs +++ b/COMET.Web.Common/Components/CardView/CardView.razor.cs @@ -25,15 +25,20 @@ namespace COMET.Web.Common.Components { + using COMET.Web.Common.Components.CardView; + using FastMember; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web.Virtualization; + using System.Linq.Dynamic; + using System.Linq.Dynamic.Core; + /// /// Component used to show a CardView based on a specific type /// - public partial class CardView : DisposableComponent where T : class + public partial class CardView : DisposableComponent { /// /// Gets or sets the item template for the list. @@ -50,8 +55,12 @@ public partial class CardView : DisposableComponent where T : class /// /// Gets or sets a collection of propertynames of type to perform search on /// - [Parameter] - public string[] SearchFields { get; set; } + public HashSet SearchFields { get; set; } = []; + + /// + /// Gets or sets a collection of propertynames of type to perform sorting on + /// + public SortedSet SortFields { get; set; } = [string.Empty]; /// /// Gets or sets the fixed height of a Card, used to calculate the amout of items to load into the DOM in px @@ -65,6 +74,10 @@ public partial class CardView : DisposableComponent where T : class [Parameter] public float MinWidth { get; set; } = 250; + public bool AllowSort { get; set; } = false; + + public bool AllowSearch { get; set; } = false; + /// /// A reference to the component for loading items /// @@ -90,6 +103,8 @@ public partial class CardView : DisposableComponent where T : class /// private string searchTerm { get; set; } = string.Empty; + public string SelectedSortField { get; set; } + /// /// Gets the class to visually show a Card to be selected or unselected /// @@ -97,7 +112,7 @@ public partial class CardView : DisposableComponent where T : class /// private string GetSelectedClass(T vm) { - return vm == this.selected ? "selected" : ""; + return vm.Equals(this.selected) ? "selected" : ""; } /// @@ -152,8 +167,14 @@ private async ValueTask> LoadItems(ItemsProviderRequest r : this.Items.Where(item => this.FilterItem(item, this.searchTerm)).ToList(); // Return paged items for virtualization - var items = filteredItems.Skip(request.StartIndex).Take(request.Count).ToList(); - return new ItemsProviderResult(items, filteredItems.Count); + var items = filteredItems.Skip(request.StartIndex).Take(request.Count); + + if (!string.IsNullOrWhiteSpace(this.SelectedSortField)) + { + items = items.AsQueryable().OrderBy(this.SelectedSortField); + } + + return new ItemsProviderResult(items.ToList(), filteredItems.Count); } /// @@ -172,7 +193,7 @@ private bool FilterItem(T item, string query) return value != null && value.Contains(query, StringComparison.OrdinalIgnoreCase); }); } - + /// /// A method that is executed when the user changes the search input element /// @@ -187,5 +208,33 @@ private async Task OnSearchTextChanged(ChangeEventArgs e) await this.virtualize.RefreshDataAsync(); // Tell Virtualize to refresh data } } + + internal void RegisterCardField(CardField cardField) + { + if (cardField.AllowSort) + { + if (this.SortFields.Add(cardField.FieldName)) + { + this.AllowSort = true; + this.StateHasChanged(); + } + } + + if (cardField.AllowSearch) + { + if (this.SearchFields.Add(cardField.FieldName)) + { + this.AllowSearch = true; + this.StateHasChanged(); + } + } + } + + private void OnSelectedItemChanged(string arg) + { + this.SelectedSortField = arg ?? string.Empty; + + this.virtualize?.RefreshDataAsync(); // Tell Virtualize to refresh data + } } } diff --git a/COMET.Web.Common/Components/CardView.razor.css b/COMET.Web.Common/Components/CardView/CardView.razor.css similarity index 99% rename from COMET.Web.Common/Components/CardView.razor.css rename to COMET.Web.Common/Components/CardView/CardView.razor.css index 053bbc47..4be8b3eb 100644 --- a/COMET.Web.Common/Components/CardView.razor.css +++ b/COMET.Web.Common/Components/CardView/CardView.razor.css @@ -10,4 +10,3 @@ .card.selected { border: 2px solid var(--bs-primary, var(--primary)); } - diff --git a/COMET.Web.Common/_Imports.razor b/COMET.Web.Common/_Imports.razor index e2da291b..ecb41a86 100644 --- a/COMET.Web.Common/_Imports.razor +++ b/COMET.Web.Common/_Imports.razor @@ -2,6 +2,7 @@ @using COMET.Web.Common.Components.Applications @using COMET.Web.Common.Components.Selectors @using COMET.Web.Common.Components.ParameterTypeEditors +@using COMET.Web.Common.Components.CardView @using COMET.Web.Common.Shared @using COMET.Web.Common.Shared.TopMenuEntry @using System.Net.Http diff --git a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor index 92810001..213ad787 100644 --- a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor +++ b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor @@ -28,32 +28,32 @@ @if (this.ViewModel.SelectedSystemNode != null) { - +
-
@context.ParameterTypeName
+
- +
- @context.ModelCode +
- @context.SwitchValue: + :
Published:
-
@context.ActualValue
-
@context.PublishedValue
+
+
@@ -61,7 +61,7 @@ @code { - private string[] searchFields = + private string[] searchAndSortFields = [ nameof(ElementDefinitionDetailsRowViewModel.ParameterTypeName), nameof(ElementDefinitionDetailsRowViewModel.Owner), diff --git a/COMETwebapp/_Imports.razor b/COMETwebapp/_Imports.razor index 4f12a364..c8047d07 100644 --- a/COMETwebapp/_Imports.razor +++ b/COMETwebapp/_Imports.razor @@ -40,6 +40,7 @@ @using COMET.Web.Common.Components.ParameterTypeEditors @using COMET.Web.Common.Components.Selectors @using COMET.Web.Common.Components.ValueSetRenderers +@using COMET.Web.Common.Components.CardView @using COMET.Web.Common.Pages @using Blazor.Diagrams.Core; @using Blazor.Diagrams.Core.Models; diff --git a/COMETwebapp/wwwroot/css/app.css b/COMETwebapp/wwwroot/css/app.css index 23a63cff..e8d71525 100644 --- a/COMETwebapp/wwwroot/css/app.css +++ b/COMETwebapp/wwwroot/css/app.css @@ -312,4 +312,8 @@ sub { border: 1px solid dimgray; color: dimgray; padding-top: 0px; -} \ No newline at end of file +} + +.search-mark { + background-color: yellow !important; +} From afb39c949c3b3a774d56cdc21ab5db2b57ff5a29 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 13 Dec 2024 12:57:47 +0100 Subject: [PATCH 03/14] Finish implementation --- .../Components/CardView/CardField.cs | 65 +++++++++- .../Components/CardView/CardView.razor | 21 +-- .../Components/CardView/CardView.razor.cs | 122 +++++++++++------- COMET.Web.Common/wwwroot/css/styles.css | 35 +++++ .../Common/DataItemDetailsComponent.razor.css | 2 +- .../ModelEditor/DetailsPanelEditor.razor | 24 ++-- .../ModelEditor/ElementDefinitionTable.razor | 1 - .../ElementDefinitionDetailsViewModel.cs | 2 +- .../IElementDefinitionDetailsViewModel.cs | 2 +- 9 files changed, 197 insertions(+), 77 deletions(-) diff --git a/COMET.Web.Common/Components/CardView/CardField.cs b/COMET.Web.Common/Components/CardView/CardField.cs index c8296c2a..24238f25 100644 --- a/COMET.Web.Common/Components/CardView/CardField.cs +++ b/COMET.Web.Common/Components/CardView/CardField.cs @@ -1,4 +1,28 @@ - +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2023-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, Nabil Abbar +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 +// Annex A and Annex C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// -------------------------------------------------------------------------------------------------------------------- + namespace COMET.Web.Common.Components.CardView { using System.Text.RegularExpressions; @@ -8,39 +32,76 @@ namespace COMET.Web.Common.Components.CardView using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; + /// + /// A component that represents a data field in a 's ItemTemplate + /// + /// public class CardField : ComponentBase { + /// + /// The used to read properties from the instance of . + /// This is a static property on a generic type, so it will have different static values for each used generic type in the application + /// private static TypeAccessor typeAccessor { get; set; } + /// + /// Initializes the static properties of this class + /// static CardField() { typeAccessor = TypeAccessor.Create(typeof(T)); } + /// + /// Gets or sets The parent t + /// [CascadingParameter(Name="CardView")] private CardView CardView { get; set; } + /// + /// The SearchTerm of the used to visually show the SearchTerm in this + /// [CascadingParameter(Name = "SearchTerm")] private string SearchTerm { get; set; } + /// + /// Gets or sets the context of this + /// [Parameter] public T Context { get; set; } + /// + /// Gets or sets the FieldName (propertyname of ) to show in the UI + /// [Parameter] public string FieldName { get; set; } + /// + /// Gets or sets a value indicating that sorting is allowed for this + /// [Parameter] public bool AllowSort { get; set; } = true; + /// + /// Gets or sets a value indicating that searching is allowed for this + /// [Parameter] public bool AllowSearch { get; set; } = true; + /// + /// Method invoked when the component is ready to start, having received its + /// initial parameters from its parent in the render tree. + /// protected override void OnInitialized() { base.OnInitialized(); - this.CardView.RegisterCardField(this); + this.CardView.InitializeCardField(this); } + /// + /// Renders the component to the supplied . + /// + /// A that will receive the render output. protected override void BuildRenderTree(RenderTreeBuilder builder) { base.BuildRenderTree(builder); diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 0a31c95b..4f86e930 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -23,24 +23,29 @@ @typeparam T @inherits DisposableComponent -

+

- - +
+ + + + + +

-
+
-
+
@@ -52,6 +51,18 @@ public partial class CardView : DisposableComponent [Parameter] public ICollection Items { get; set; } = new List(); + /// + /// Gets or sets the fixed height of a Card, used to calculate the amout of items to load into the DOM in px + /// + [Parameter] + public float ItemSize { get; set; } = 150; + + /// + /// Gets or sets the minimum width of a Card + /// + [Parameter] + public float MinWidth { get; set; } = 250; + /// /// Gets or sets a collection of propertynames of type to perform search on /// @@ -63,21 +74,21 @@ public partial class CardView : DisposableComponent public SortedSet SortFields { get; set; } = [string.Empty]; /// - /// Gets or sets the fixed height of a Card, used to calculate the amout of items to load into the DOM in px + /// Gets or sets a value indication that sorting is allowed /// - [Parameter] - public float ItemSize { get; set; } = 150; + public bool AllowSort { get; set; } = false; /// - /// Gets or sets the minimum width of a Card + /// Gets or sets a value indication that searching is allowed /// - [Parameter] - public float MinWidth { get; set; } = 250; - - public bool AllowSort { get; set; } = false; - public bool AllowSearch { get; set; } = false; + /// + /// hold a reference to the previously selected Item. + /// Typically used to check for changes in Items collection + /// + private ICollection previousItems = null; + /// /// A reference to the component for loading items /// @@ -99,17 +110,20 @@ public partial class CardView : DisposableComponent private T draggedNode { get; set; } /// - /// Gets or sets the term where to search/filter item of + /// Gets or sets the term where to search/filter items on /// private string searchTerm { get; set; } = string.Empty; - public string SelectedSortField { get; set; } + /// + /// Gets or sets the term where to sort items on + /// + private string selectedSortField { get; set; } = string.Empty; /// /// Gets the class to visually show a Card to be selected or unselected /// /// - /// + /// A string that retrurns the css class for selected Card private string GetSelectedClass(T vm) { return vm.Equals(this.selected) ? "selected" : ""; @@ -118,8 +132,8 @@ private string GetSelectedClass(T vm) /// /// Set the selected item /// - /// - private void selectOption(T item) + /// The item + private void selectItem(T item) { this.selected = item; } @@ -140,18 +154,7 @@ private void OnDragNode(T node) /// A private async Task OnDropNode(T targetNode) { - //TODO: Implement - // var targetFolder = (Folder)targetNode.Thing; - - // switch (this.DraggedNode?.Thing) - // { - // case File file: - // await this.ViewModel.FileHandlerViewModel.MoveFile(file, targetFolder); - // break; - // case Folder folder: - // await this.ViewModel.FolderHandlerViewModel.MoveFolder(folder, targetFolder); - // break; - // } + //not implemented yet } /// @@ -162,16 +165,16 @@ private async Task OnDropNode(T targetNode) private async ValueTask> LoadItems(ItemsProviderRequest request) { // Filter items based on the SearchTerm - var filteredItems = string.IsNullOrWhiteSpace(this.searchTerm) + var filteredItems = !this.AllowSearch || string.IsNullOrWhiteSpace(this.searchTerm) ? this.Items : this.Items.Where(item => this.FilterItem(item, this.searchTerm)).ToList(); // Return paged items for virtualization var items = filteredItems.Skip(request.StartIndex).Take(request.Count); - if (!string.IsNullOrWhiteSpace(this.SelectedSortField)) + if (this.AllowSort && !string.IsNullOrWhiteSpace(this.selectedSortField)) { - items = items.AsQueryable().OrderBy(this.SelectedSortField); + items = items.AsQueryable().OrderBy(this.selectedSortField); } return new ItemsProviderResult(items.ToList(), filteredItems.Count); @@ -185,31 +188,36 @@ private async ValueTask> LoadItems(ItemsProviderRequest r /// True if the item's selected properties contain the value to search for, otherwise false private bool FilterItem(T item, string query) { - var props = this.typeAccessor.GetMembers(); - - return props.Where(x => this.SearchFields.Contains(x.Name)).Any(prop => + if (this.AllowSearch) { - var value = this.typeAccessor[item, prop.Name].ToString(); - return value != null && value.Contains(query, StringComparison.OrdinalIgnoreCase); - }); + var props = this.typeAccessor.GetMembers(); + + return props.Where(x => this.SearchFields.Contains(x.Name)).Any(prop => + { + var value = this.typeAccessor[item, prop.Name].ToString(); + return value != null && value.Contains(query, StringComparison.OrdinalIgnoreCase); + }); + } + + return true; } /// /// A method that is executed when the user changes the search input element /// - /// The from the UI element's event - /// - private async Task OnSearchTextChanged(ChangeEventArgs e) + /// The text from the UI element's event + private void OnSearchTextChanged(string value) { - this.searchTerm = e.Value?.ToString() ?? string.Empty; + this.searchTerm = value ?? string.Empty; - if (this.virtualize != null) - { - await this.virtualize.RefreshDataAsync(); // Tell Virtualize to refresh data - } + this.virtualize?.RefreshDataAsync(); // Tell Virtualize to refresh data } - internal void RegisterCardField(CardField cardField) + /// + /// Initializes the + /// + /// the + internal void InitializeCardField(CardField cardField) { if (cardField.AllowSort) { @@ -230,11 +238,31 @@ internal void RegisterCardField(CardField cardField) } } - private void OnSelectedItemChanged(string arg) + /// + /// Handles the selection of a the Sort item + /// + /// + private void OnSelectedSortItemChanged(string arg) { - this.SelectedSortField = arg ?? string.Empty; + this.selectedSortField = arg ?? string.Empty; this.virtualize?.RefreshDataAsync(); // Tell Virtualize to refresh data } + + /// + /// Raised when a parameter is set + /// + /// An awaitable + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + if (!Equals(this.previousItems, this.Items) && this.virtualize != null) + { + await this.virtualize.RefreshDataAsync(); // Tell Virtualize to refresh data + } + + this.previousItems = this.Items; + } } } diff --git a/COMET.Web.Common/wwwroot/css/styles.css b/COMET.Web.Common/wwwroot/css/styles.css index 719b4eed..946a1700 100644 --- a/COMET.Web.Common/wwwroot/css/styles.css +++ b/COMET.Web.Common/wwwroot/css/styles.css @@ -572,3 +572,38 @@ width: 25%; margin: 0; } + +.inline-search-icon { + display: flex; + align-items: center; + position: relative; + padding-left: 20px !important; /* Leave space for the icon */ +} + + .inline-search-icon::before { + content: '\e08f'; /* Unicode for an example icon (change this for your preferred icon) */ + font-family: 'Icons'; + top:3px; + position: absolute; + left: 5px; /* Position of the icon */ + color: #757575; /* Icon color */ + font-size: 16px; /* Icon size */ + } + +.inline-sort-icon { + display: flex; + align-items: center; + position: relative; + padding-left: 20px !important; /* Leave space for the icon */ +} + + .inline-sort-icon::before { + content: '\e0bf'; /* Unicode for an example icon (change this for your preferred icon) */ + font-family: 'Icons'; + vertical-align: middle; + position: absolute; + top: 3px; + left: 5px; /* Position of the icon */ + color: #757575; /* Icon color */ + font-size: 16px; /* Icon size */ + } \ No newline at end of file diff --git a/COMETwebapp/Components/Common/DataItemDetailsComponent.razor.css b/COMETwebapp/Components/Common/DataItemDetailsComponent.razor.css index b737c6d7..013c5918 100644 --- a/COMETwebapp/Components/Common/DataItemDetailsComponent.razor.css +++ b/COMETwebapp/Components/Common/DataItemDetailsComponent.razor.css @@ -1,5 +1,5 @@ .data-item-details-section { - padding: 30px; + padding: 15px; border: 1px dashed; border-radius: 10px; height: 100%; diff --git a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor index 213ad787..8ef86c23 100644 --- a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor +++ b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor @@ -28,7 +28,7 @@ @if (this.ViewModel.SelectedSystemNode != null) { - +
@@ -45,29 +45,21 @@
- : + + + + Actual:
Published:
-
+
+ +
} - -@code -{ - private string[] searchAndSortFields = - [ - nameof(ElementDefinitionDetailsRowViewModel.ParameterTypeName), - nameof(ElementDefinitionDetailsRowViewModel.Owner), - nameof(ElementDefinitionDetailsRowViewModel.ModelCode), - nameof(ElementDefinitionDetailsRowViewModel.ActualValue), - nameof(ElementDefinitionDetailsRowViewModel.PublishedValue), - nameof(ElementDefinitionDetailsRowViewModel.SwitchValue) - ]; -} diff --git a/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor b/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor index d6fb40b9..b1b9ccdf 100644 --- a/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor +++ b/COMETwebapp/Components/ModelEditor/ElementDefinitionTable.razor @@ -75,7 +75,6 @@ Width="100%" CssClass="model-editor-details">
-

Panel Editor

@if (this.ViewModel.SelectedElementDefinition is not null) { diff --git a/COMETwebapp/ViewModels/Components/SystemRepresentation/ElementDefinitionDetailsViewModel.cs b/COMETwebapp/ViewModels/Components/SystemRepresentation/ElementDefinitionDetailsViewModel.cs index cbfb4096..a40c87f6 100644 --- a/COMETwebapp/ViewModels/Components/SystemRepresentation/ElementDefinitionDetailsViewModel.cs +++ b/COMETwebapp/ViewModels/Components/SystemRepresentation/ElementDefinitionDetailsViewModel.cs @@ -53,6 +53,6 @@ public ElementBase SelectedSystemNode /// /// A collection of /// - public IEnumerable Rows { get; set; } + public ICollection Rows { get; set; } } } diff --git a/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs b/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs index ba343ca6..f88364b1 100644 --- a/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs +++ b/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs @@ -40,6 +40,6 @@ public interface IElementDefinitionDetailsViewModel /// /// A collection of /// - IEnumerable Rows { get; set; } + ICollection Rows { get; set; } } } From 480060d61703ab725651868669f1073ed9cfc47e Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 13 Dec 2024 13:52:01 +0100 Subject: [PATCH 04/14] Changes according to SQ --- .../Components/CardView/CardField.cs | 2 +- .../Components/CardView/CardView.razor | 2 +- .../Components/CardView/CardView.razor.cs | 36 ++++++++----------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/COMET.Web.Common/Components/CardView/CardField.cs b/COMET.Web.Common/Components/CardView/CardField.cs index 24238f25..f2cba9f6 100644 --- a/COMET.Web.Common/Components/CardView/CardField.cs +++ b/COMET.Web.Common/Components/CardView/CardField.cs @@ -138,4 +138,4 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) } } } -} \ No newline at end of file +} diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 4f86e930..19b98b0c 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -------------------------------------------------------------------------------> -@namespace COMET.Web.Common.Components +@namespace COMET.Web.Common.Components.CardView @typeparam T @inherits DisposableComponent diff --git a/COMET.Web.Common/Components/CardView/CardView.razor.cs b/COMET.Web.Common/Components/CardView/CardView.razor.cs index 07010003..c249d5b4 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor.cs +++ b/COMET.Web.Common/Components/CardView/CardView.razor.cs @@ -23,17 +23,15 @@ // // -------------------------------------------------------------------------------------------------------------------- -namespace COMET.Web.Common.Components +namespace COMET.Web.Common.Components.CardView { - using COMET.Web.Common.Components.CardView; + using System.Linq.Dynamic.Core; using FastMember; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web.Virtualization; - using System.Linq.Dynamic.Core; - /// /// Component used to show a CardView based on a specific type /// @@ -64,30 +62,30 @@ public partial class CardView : DisposableComponent public float MinWidth { get; set; } = 250; /// - /// Gets or sets a collection of propertynames of type to perform search on + /// Gets or sets a collection of propertynames of type to perform search on /// - public HashSet SearchFields { get; set; } = []; + public HashSet SearchFields { get; private set; } = []; /// /// Gets or sets a collection of propertynames of type to perform sorting on /// - public SortedSet SortFields { get; set; } = [string.Empty]; + public SortedSet SortFields { get; private set; } = [string.Empty]; /// /// Gets or sets a value indication that sorting is allowed /// - public bool AllowSort { get; set; } = false; + public bool AllowSort { get; set; } /// /// Gets or sets a value indication that searching is allowed /// - public bool AllowSearch { get; set; } = false; + public bool AllowSearch { get; set; } /// /// hold a reference to the previously selected Item. /// Typically used to check for changes in Items collection /// - private ICollection previousItems = null; + private ICollection previousItems; /// /// A reference to the component for loading items @@ -219,22 +217,16 @@ private void OnSearchTextChanged(string value) /// the internal void InitializeCardField(CardField cardField) { - if (cardField.AllowSort) + if (cardField.AllowSort && this.SortFields.Add(cardField.FieldName)) { - if (this.SortFields.Add(cardField.FieldName)) - { - this.AllowSort = true; - this.StateHasChanged(); - } + this.AllowSort = true; + this.StateHasChanged(); } - if (cardField.AllowSearch) + if (cardField.AllowSearch && this.SearchFields.Add(cardField.FieldName)) { - if (this.SearchFields.Add(cardField.FieldName)) - { - this.AllowSearch = true; - this.StateHasChanged(); - } + this.AllowSearch = true; + this.StateHasChanged(); } } From 7da12bfb30d0e1b4121ec0ad5af28cb4f276c014 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 13 Dec 2024 17:08:20 +0100 Subject: [PATCH 05/14] Add CardView Tests --- .../CardView/CardViewTestFixture.cs | 303 ++++++++++++++++++ .../Components/CardView/CardView.razor | 4 +- 2 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs diff --git a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs new file mode 100644 index 00000000..c02b042f --- /dev/null +++ b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs @@ -0,0 +1,303 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2023-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, Nabil Abbar +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The CDP4-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 CDP4-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 COMET.Web.Common.Tests.Components.CardView +{ + using Bunit; + + using COMET.Web.Common.Components.CardView; + + using DevExpress.Blazor.Internal; + + using Microsoft.AspNetCore.Components; + using Microsoft.Extensions.DependencyInjection; + + using NUnit.Framework; + + using TestContext = Bunit.TestContext; + + public class CardViewTestFixture + { + private TestContext context; + private TestClass testClass1 = new (); + private TestClass testClass2 = new (); + private TestClass testClass3 = new (); + private TestClass[] testClasses; + + private static RenderFragment NormalTemplate() + { + RenderFragment Template(TestClass child) => + builder => + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Context", child); + builder.AddAttribute(2, "FieldName", "Id"); + builder.CloseComponent(); + builder.OpenComponent>(3); + builder.AddAttribute(4, "Context", child); + builder.AddAttribute(5, "FieldName", "Name"); + builder.CloseComponent(); + }; + + return Template; + } + + private static RenderFragment NoSearchAndSortTemplate() + { + RenderFragment Template(TestClass child) => + builder => + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Context", child); + builder.AddAttribute(2, "FieldName", "Id"); + builder.AddAttribute(3, "AllowSearch", false); + builder.AddAttribute(4, "AllowSort", false); + builder.CloseComponent(); + builder.OpenComponent>(5); + builder.AddAttribute(6, "Context", child); + builder.AddAttribute(7, "FieldName", "Name"); + builder.AddAttribute(8, "AllowSearch", false); + builder.AddAttribute(9, "AllowSort", false); + builder.CloseComponent(); + }; + + return Template; + } + + [SetUp] + public void Setup() + { + this.context = new TestContext(); + this.testClasses = [this.testClass1, this.testClass2, this.testClass3]; + + this.context.Services.AddDevExpressBlazor(_ => ConfigureJsInterop(this.context.JSInterop)); + + this.context.JSInterop.SetupVoid("DxBlazor.AdaptiveDropDown.init"); + this.context.JSInterop.SetupVoid("DxBlazor.DropDown.getReference"); + this.context.JSInterop.SetupVoid("DxBlazor.ComboBox.loadModule"); + this.context.JSInterop.SetupVoid("DxBlazor.Input.loadModule"); + } + + [Test] + public void VerifyComponent() + { + var component = this.context.RenderComponent>(parameters => + { + parameters + .Add(p => p.Items, this.testClasses) + .Add(p => p.ItemSize, 150) + .Add(p => p.ItemTemplate, NormalTemplate()); + }); + + var cardView = component; + + Assert.Multiple(() => + { + Assert.That(cardView.Instance.AllowSort, Is.True); + Assert.That(cardView.Instance.AllowSearch, Is.True); + Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new [] {"Id", "Name"})); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + }); + + var textBoxParentComponent = component.Find("#search-textbox"); + + Assert.Multiple(() => + { + Assert.That(textBoxParentComponent, Is.Not.Null); + Assert.That(textBoxParentComponent.Attributes.Single(x => x.Name == "style").Value.Contains("visibility:block"), Is.True); + }); + + var comboBoxParentComponent = component.Find("#sort-dropdown"); + + Assert.Multiple(() => + { + Assert.That(comboBoxParentComponent, Is.Not.Null); + Assert.That(comboBoxParentComponent.Attributes.Single(x => x.Name == "style").Value.Contains("visibility:block"), Is.True); + }); + + var cardFields = component.FindComponents>(); + + Assert.Multiple(() => + { + Assert.That(cardFields.Count, Is.EqualTo(6)); + + foreach (var cardField in cardFields) + { + Assert.That(cardField.Instance.AllowSearch, Is.True); + Assert.That(cardField.Instance.AllowSort, Is.True); + } + + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Id.ToString())).Count, Is.EqualTo(1)); + }); + } + + [Test] + public void VerifyComponentWithoutSortAndSearch() + { + var component = this.context.RenderComponent>(parameters => + { + parameters + .Add(p => p.Items, this.testClasses) + .Add(p => p.ItemSize, 150) + .Add(p => p.ItemTemplate, NoSearchAndSortTemplate()); + }); + + var cardView = component; + + Assert.Multiple(() => + { + Assert.That(cardView.Instance.AllowSort, Is.False); + Assert.That(cardView.Instance.AllowSearch, Is.False); + Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); + Assert.That(cardView.Instance.SearchFields.Count, Is.EqualTo(0)); + Assert.That(cardView.Instance.SortFields.Count, Is.EqualTo(1)); + }); + + var textBoxParentComponent = component.Find("#search-textbox"); + + Assert.Multiple(() => + { + Assert.That(textBoxParentComponent, Is.Not.Null); + Assert.That(textBoxParentComponent.Attributes.Single(x => x.Name == "style").Value.Contains("visibility:hidden"), Is.True); + }); + + var comboBoxParentComponent = component.Find("#sort-dropdown"); + + Assert.Multiple(() => + { + Assert.That(comboBoxParentComponent, Is.Not.Null); + Assert.That(comboBoxParentComponent.Attributes.Single(x => x.Name == "style").Value.Contains("visibility:hidden"), Is.True); + }); + + var cardFields = component.FindComponents>(); + + Assert.Multiple(() => + { + Assert.That(cardFields.Count, Is.EqualTo(6)); + + foreach (var cardField in cardFields) + { + Assert.That(cardField.Instance.AllowSearch, Is.False); + Assert.That(cardField.Instance.AllowSort, Is.False); + } + + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Id.ToString())).Count, Is.EqualTo(1)); + }); + } + + [Test] + public void VerifyComponentRerenders() + { + var component = this.context.RenderComponent>(parameters => + { + parameters + .Add(p => p.Items, this.testClasses) + .Add(p => p.ItemSize, 150) + .Add(p => p.ItemTemplate, NoSearchAndSortTemplate()); + }); + + var cardFields = component.FindComponents>(); + + Assert.Multiple(() => + { + Assert.That(cardFields.Count, Is.EqualTo(6)); + + foreach (var cardField in cardFields) + { + Assert.That(cardField.Instance.AllowSearch, Is.False); + Assert.That(cardField.Instance.AllowSort, Is.False); + } + + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Id.ToString())).Count, Is.EqualTo(1)); + }); + + // create new collection of items + this.testClass1 = new TestClass(); + this.testClass2 = new TestClass(); + this.testClass3 = new TestClass(); + this.testClasses = [this.testClass1, this.testClass2, this.testClass3]; + + component.SetParametersAndRender(parameters => parameters + .Add(p => p.Items, this.testClasses)); + + cardFields = component.FindComponents>(); + + Assert.Multiple(() => + { + Assert.That(cardFields.Count, Is.EqualTo(6)); + + foreach (var cardField in cardFields) + { + Assert.That(cardField.Instance.AllowSearch, Is.False); + Assert.That(cardField.Instance.AllowSort, Is.False); + } + + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Id.ToString())).Count, Is.EqualTo(1)); + }); + } + + /// + /// Configure the for DevExpress + /// + /// The to configure + private static void ConfigureJsInterop(BunitJSInterop interop) + { + interop.Mode = JSRuntimeMode.Loose; + + var rootModule = interop.SetupModule("./_content/DevExpress.Blazor/dx-blazor.js"); + rootModule.Mode = JSRuntimeMode.Strict; + + rootModule.Setup("getDeviceInfo", _ => true) + .SetResult(new DeviceInfo(false)); + } + } + + public class TestClass + { + public Guid Id { get; } = Guid.NewGuid(); + + public string Name { get; } = $"Name-{DateTime.Now.Ticks}"; + } +} diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 19b98b0c..290d5d57 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -26,11 +26,11 @@

- -
+ + From 5724f4a7606129e32c7d3b21b3b0c86f2d43d1f7 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 13 Dec 2024 17:09:27 +0100 Subject: [PATCH 06/14] Add newline --- COMET.Web.Common/Components/CardView/CardView.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 290d5d57..8fba6946 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -62,4 +62,4 @@ - \ No newline at end of file + From 80befddbf57d3ef1df128390e82ad1fee4dc5e30 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 09:13:38 +0100 Subject: [PATCH 07/14] Remove drag/drop --- .../Components/CardView/CardView.razor | 7 +----- .../Components/CardView/CardView.razor.cs | 24 ------------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 8fba6946..7b724ee8 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -44,12 +44,7 @@
+ @onclick="@(() => this.selectItem(@context))">
diff --git a/COMET.Web.Common/Components/CardView/CardView.razor.cs b/COMET.Web.Common/Components/CardView/CardView.razor.cs index c249d5b4..ac64d55d 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor.cs +++ b/COMET.Web.Common/Components/CardView/CardView.razor.cs @@ -102,11 +102,6 @@ public partial class CardView : DisposableComponent /// private T selected; - /// - /// Gets the dragged node used in drag and drop interactions - /// - private T draggedNode { get; set; } - /// /// Gets or sets the term where to search/filter items on /// @@ -136,25 +131,6 @@ private void selectItem(T item) this.selected = item; } - /// - /// Method invoked when a node is dragged - /// - /// The dragged node - private void OnDragNode(T node) - { - this.draggedNode = node; - } - - /// - /// Method invoked when a node is dropped - /// - /// The target node where the has been dropped - /// A - private async Task OnDropNode(T targetNode) - { - //not implemented yet - } - /// /// Filters the list of items to show in the UI based on the /// From 20837c6716c773dd4d838b49d08bdb575b4f0960 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 09:29:56 +0100 Subject: [PATCH 08/14] Unit Test selected item --- .../CardView/CardViewTestFixture.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs index c02b042f..13c2957e 100644 --- a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs +++ b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs @@ -32,6 +32,7 @@ namespace COMET.Web.Common.Tests.Components.CardView using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using NUnit.Framework; @@ -278,6 +279,39 @@ public void VerifyComponentRerenders() }); } + [Test] + public void VerifySelectComponent() + { + var component = this.context.RenderComponent>(parameters => + { + parameters + .Add(p => p.Items, this.testClasses) + .Add(p => p.ItemSize, 150) + .Add(p => p.ItemTemplate, NormalTemplate()); + }); + + var cardView = component; + + Assert.Multiple(() => + { + Assert.That(cardView.Instance.AllowSort, Is.True); + Assert.That(cardView.Instance.AllowSearch, Is.True); + Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new[] { "Id", "Name" })); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + }); + + var firstCardField = component.Find(".card"); + firstCardField.Click(); + + var selectedCardField = component.Find(".selected"); + + Assert.Multiple(() => + { + Assert.That(firstCardField.InnerHtml, Is.EqualTo(selectedCardField.InnerHtml)); + }); + } + /// /// Configure the for DevExpress /// From bdc0929cbf2f8f42edfee567a3f68c82ceb90bd7 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 13:07:23 +0100 Subject: [PATCH 09/14] Test Search and Sort --- .../CardView/CardViewTestFixture.cs | 147 +++++++++++++++++- .../Components/CardView/CardView.razor | 6 +- .../Components/CardView/CardView.razor.cs | 12 +- 3 files changed, 155 insertions(+), 10 deletions(-) diff --git a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs index 13c2957e..a15c0af5 100644 --- a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs +++ b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs @@ -28,11 +28,11 @@ namespace COMET.Web.Common.Tests.Components.CardView using COMET.Web.Common.Components.CardView; + using DevExpress.Blazor; using DevExpress.Blazor.Internal; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; - using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using NUnit.Framework; @@ -312,6 +312,151 @@ public void VerifySelectComponent() }); } + [Test] + public async Task VerifySearchComponent() + { + var component = this.context.RenderComponent>(parameters => + { + parameters + .Add(p => p.Items, this.testClasses) + .Add(p => p.ItemSize, 150) + .Add(p => p.ItemTemplate, NormalTemplate()); + }); + + var cardView = component; + + Assert.Multiple(() => + { + Assert.That(cardView.Instance.AllowSort, Is.True); + Assert.That(cardView.Instance.AllowSearch, Is.True); + Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new[] { "Id", "Name" })); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + }); + + var textBoxParentComponent = component.Find("#search-textbox"); + + Assert.Multiple(() => + { + Assert.That(textBoxParentComponent, Is.Not.Null); + Assert.That(textBoxParentComponent.Attributes.Single(x => x.Name == "style").Value.Contains("visibility:block"), Is.True); + }); + + var textBoxComponent = component.FindComponent(); + await component.InvokeAsync(() => textBoxComponent.Instance.TextChanged.InvokeAsync(this.testClass1.Id.ToString())); + + var cardFields = component.FindComponents>(); + + Assert.Multiple(() => + { + Assert.That(cardFields.Count, Is.EqualTo(2)); + + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Name)).ToList().Count, Is.EqualTo(0)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Name)).ToList().Count, Is.EqualTo(0)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Id.ToString()) && x.Markup.Contains("search-mark")).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Id.ToString())).ToList().Count, Is.EqualTo(0)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Id.ToString())).Count, Is.EqualTo(0)); + }); + + await component.InvokeAsync(async () => await textBoxComponent.Instance.TextChanged.InvokeAsync(string.Empty)); + + cardFields = component.FindComponents>(); + + Assert.Multiple(() => + { + Assert.That(cardFields.Count, Is.EqualTo(6)); + + Assert.That(cardFields.Where(x => x.Markup.Contains("search-mark")).ToList().Count, Is.EqualTo(0)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Id.ToString())).Count, Is.EqualTo(1)); + }); + } + + [Test] + public async Task VerifySortComponent() + { + var component = this.context.RenderComponent>(parameters => + { + parameters + .Add(p => p.Items, this.testClasses) + .Add(p => p.ItemSize, 150) + .Add(p => p.ItemTemplate, NormalTemplate()); + }); + + var cardView = component; + + Assert.Multiple(() => + { + Assert.That(cardView.Instance.AllowSort, Is.True); + Assert.That(cardView.Instance.AllowSearch, Is.True); + Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new[] { "Id", "Name" })); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + Assert.That(cardView.Instance.SelectedSortField == string.Empty); + }); + + var sortParentComponent = component.Find("#sort-dropdown"); + + Assert.Multiple(() => + { + Assert.That(sortParentComponent, Is.Not.Null); + Assert.That(sortParentComponent.Attributes.Single(x => x.Name == "style").Value.Contains("visibility:block"), Is.True); + }); + + var cardFields = component.FindComponents>(); + + Assert.Multiple(() => + { + Assert.That(cardFields.Count, Is.EqualTo(6)); + + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Name)).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass1.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass2.Id.ToString())).ToList().Count, Is.EqualTo(1)); + Assert.That(cardFields.Where(x => x.Markup.Contains(this.testClass3.Id.ToString())).Count, Is.EqualTo(1)); + }); + + await component.InvokeAsync(() => component.Instance.OnSelectedSortItemChanged("Id")); + + component.Render(); + + cardFields = component.FindComponents>(); + + //var sortedTestClasses = this.testClasses.OrderBy(x => x.Id.ToString()).Select(x => x.Name).ToList(); + var sortedTestClasses = this.testClasses.OrderBy(x => x.Id).Select(x => x.Name).ToList(); + var sortedCarFields = cardFields.Where(x => x.Markup.StartsWith("Name-")).Select(x => x.Markup).ToList(); + + Assert.Multiple(() => + { + Assert.That(component.Instance.SelectedSortField == "Id"); + Assert.That(sortedTestClasses[0], Is.EqualTo(sortedCarFields[0])); + Assert.That(sortedTestClasses[1], Is.EqualTo(sortedCarFields[1])); + Assert.That(sortedTestClasses[2], Is.EqualTo(sortedCarFields[2])); + }); + + await component.InvokeAsync(() => component.Instance.OnSelectedSortItemChanged(string.Empty)); + + component.Render(); + + cardFields = component.FindComponents>(); + + sortedCarFields = cardFields.Where(x => x.Markup.StartsWith("Name-")).Select(x => x.Markup).ToList(); + + Assert.Multiple(() => + { + Assert.That(component.Instance.SelectedSortField == string.Empty); + Assert.That(this.testClasses[0].Name, Is.EqualTo(sortedCarFields[0])); + Assert.That(this.testClasses[1].Name, Is.EqualTo(sortedCarFields[1])); + Assert.That(this.testClasses[2].Name, Is.EqualTo(sortedCarFields[2])); + }); + } + /// /// Configure the for DevExpress /// diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 7b724ee8..9c73b84f 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -27,12 +27,12 @@ diff --git a/COMET.Web.Common/Components/CardView/CardView.razor.cs b/COMET.Web.Common/Components/CardView/CardView.razor.cs index ac64d55d..6ad477ac 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor.cs +++ b/COMET.Web.Common/Components/CardView/CardView.razor.cs @@ -110,7 +110,7 @@ public partial class CardView : DisposableComponent /// /// Gets or sets the term where to sort items on /// - private string selectedSortField { get; set; } = string.Empty; + public string SelectedSortField { get; set; } = string.Empty; /// /// Gets the class to visually show a Card to be selected or unselected @@ -146,9 +146,9 @@ private async ValueTask> LoadItems(ItemsProviderRequest r // Return paged items for virtualization var items = filteredItems.Skip(request.StartIndex).Take(request.Count); - if (this.AllowSort && !string.IsNullOrWhiteSpace(this.selectedSortField)) + if (this.AllowSort && !string.IsNullOrWhiteSpace(this.SelectedSortField)) { - items = items.AsQueryable().OrderBy(this.selectedSortField); + items = items.AsQueryable().OrderBy(this.SelectedSortField); } return new ItemsProviderResult(items.ToList(), filteredItems.Count); @@ -209,10 +209,10 @@ internal void InitializeCardField(CardField cardField) /// /// Handles the selection of a the Sort item /// - /// - private void OnSelectedSortItemChanged(string arg) + /// + public void OnSelectedSortItemChanged(string newVal) { - this.selectedSortField = arg ?? string.Empty; + this.SelectedSortField = newVal ?? string.Empty; this.virtualize?.RefreshDataAsync(); // Tell Virtualize to refresh data } From 0a3f437a0ff737b218d65b80ae1b0d83771b16bb Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 13:13:59 +0100 Subject: [PATCH 10/14] Security risk --- COMET.Web.Common/Components/CardView/CardField.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COMET.Web.Common/Components/CardView/CardField.cs b/COMET.Web.Common/Components/CardView/CardField.cs index f2cba9f6..48de25d7 100644 --- a/COMET.Web.Common/Components/CardView/CardField.cs +++ b/COMET.Web.Common/Components/CardView/CardField.cs @@ -110,7 +110,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) if (this.AllowSearch && !string.IsNullOrWhiteSpace(value) && !string.IsNullOrWhiteSpace(this.SearchTerm)) { var separatorPattern = $"({this.SearchTerm})"; - var result = Regex.Split(value, separatorPattern, RegexOptions.IgnoreCase); + var result = Regex.Split(value, separatorPattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(30)); var elementCounter = 0; foreach (var element in result) From 0a34b2e1c24c7995f5e012f7425f5af12c84ef45 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 13:24:22 +0100 Subject: [PATCH 11/14] Styles typo --- COMET.Web.Common/wwwroot/css/styles.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/COMET.Web.Common/wwwroot/css/styles.css b/COMET.Web.Common/wwwroot/css/styles.css index 946a1700..83c84789 100644 --- a/COMET.Web.Common/wwwroot/css/styles.css +++ b/COMET.Web.Common/wwwroot/css/styles.css @@ -582,7 +582,7 @@ .inline-search-icon::before { content: '\e08f'; /* Unicode for an example icon (change this for your preferred icon) */ - font-family: 'Icons'; + font-family: Icons; top:3px; position: absolute; left: 5px; /* Position of the icon */ @@ -599,7 +599,7 @@ .inline-sort-icon::before { content: '\e0bf'; /* Unicode for an example icon (change this for your preferred icon) */ - font-family: 'Icons'; + font-family: Icons; vertical-align: middle; position: absolute; top: 3px; From 92c284c0cc79875332766442e37555379da35873 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 13:59:47 +0100 Subject: [PATCH 12/14] SQ recommendations --- .../CardView/CardViewTestFixture.cs | 18 +++++++------ .../Components/CardView/CardField.cs | 6 ++--- .../Components/CardView/CardView.razor | 6 ++--- .../Components/CardView/CardView.razor.cs | 26 +++++++++---------- .../IElementDefinitionDetailsViewModel.cs | 2 +- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs index a15c0af5..3ba04fe4 100644 --- a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs +++ b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs @@ -45,6 +45,8 @@ public class CardViewTestFixture private TestClass testClass2 = new (); private TestClass testClass3 = new (); private TestClass[] testClasses; + private string[] searchFields = new[] { "Id", "Name" }; + private string[] sortFields = new[] { string.Empty, "Id", "Name" }; private static RenderFragment NormalTemplate() { @@ -118,8 +120,8 @@ public void VerifyComponent() Assert.That(cardView.Instance.AllowSort, Is.True); Assert.That(cardView.Instance.AllowSearch, Is.True); Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); - Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new [] {"Id", "Name"})); - Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(this.searchFields)); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(this.sortFields)); }); var textBoxParentComponent = component.Find("#search-textbox"); @@ -297,8 +299,8 @@ public void VerifySelectComponent() Assert.That(cardView.Instance.AllowSort, Is.True); Assert.That(cardView.Instance.AllowSearch, Is.True); Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); - Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new[] { "Id", "Name" })); - Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(this.searchFields)); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(this.sortFields)); }); var firstCardField = component.Find(".card"); @@ -330,8 +332,8 @@ public async Task VerifySearchComponent() Assert.That(cardView.Instance.AllowSort, Is.True); Assert.That(cardView.Instance.AllowSearch, Is.True); Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); - Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new[] { "Id", "Name" })); - Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(this.searchFields)); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(this.sortFields)); }); var textBoxParentComponent = component.Find("#search-textbox"); @@ -395,8 +397,8 @@ public async Task VerifySortComponent() Assert.That(cardView.Instance.AllowSort, Is.True); Assert.That(cardView.Instance.AllowSearch, Is.True); Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); - Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(new[] { "Id", "Name" })); - Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(new[] { string.Empty, "Id", "Name" })); + Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(this.searchFields)); + Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(this.sortFields)); Assert.That(cardView.Instance.SelectedSortField == string.Empty); }); diff --git a/COMET.Web.Common/Components/CardView/CardField.cs b/COMET.Web.Common/Components/CardView/CardField.cs index 48de25d7..68ff33ac 100644 --- a/COMET.Web.Common/Components/CardView/CardField.cs +++ b/COMET.Web.Common/Components/CardView/CardField.cs @@ -39,10 +39,10 @@ namespace COMET.Web.Common.Components.CardView public class CardField : ComponentBase { /// - /// The used to read properties from the instance of . + /// The used to read properties from the instance of T. /// This is a static property on a generic type, so it will have different static values for each used generic type in the application /// - private static TypeAccessor typeAccessor { get; set; } + private static TypeAccessor typeAccessor; /// /// Initializes the static properties of this class @@ -71,7 +71,7 @@ static CardField() public T Context { get; set; } /// - /// Gets or sets the FieldName (propertyname of ) to show in the UI + /// Gets or sets the FieldName (propertyname of T) to show in the UI /// [Parameter] public string FieldName { get; set; } diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 9c73b84f..b941bd4a 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -31,7 +31,7 @@ @@ -44,10 +44,10 @@
+ @onclick="@(() => this.SelectItem(@context))">
- + @this.ItemTemplate(@context) diff --git a/COMET.Web.Common/Components/CardView/CardView.razor.cs b/COMET.Web.Common/Components/CardView/CardView.razor.cs index 6ad477ac..3c868352 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor.cs +++ b/COMET.Web.Common/Components/CardView/CardView.razor.cs @@ -62,12 +62,12 @@ public partial class CardView : DisposableComponent public float MinWidth { get; set; } = 250; /// - /// Gets or sets a collection of propertynames of type to perform search on + /// Gets or sets a collection of propertynames of type T to perform search on /// public HashSet SearchFields { get; private set; } = []; /// - /// Gets or sets a collection of propertynames of type to perform sorting on + /// Gets or sets a collection of propertynames of type T to perform sorting on /// public SortedSet SortFields { get; private set; } = [string.Empty]; @@ -93,7 +93,7 @@ public partial class CardView : DisposableComponent private Virtualize? virtualize; // Reference to the Virtualize component /// - /// The FastMember to use to perform actions on instances of + /// The FastMember to use to perform actions on instances of type T /// private TypeAccessor typeAccessor = TypeAccessor.Create(typeof(T)); @@ -105,7 +105,7 @@ public partial class CardView : DisposableComponent /// /// Gets or sets the term where to search/filter items on /// - private string searchTerm { get; set; } = string.Empty; + public string SearchTerm { get; set; } = string.Empty; /// /// Gets or sets the term where to sort items on @@ -125,23 +125,23 @@ private string GetSelectedClass(T vm) /// /// Set the selected item /// - /// The item - private void selectItem(T item) + /// The item of type T + public void SelectItem(T item) { this.selected = item; } /// - /// Filters the list of items to show in the UI based on the + /// Filters the list of items to show in the UI based on the /// /// The request to perform filtering of the items list /// an waitable - private async ValueTask> LoadItems(ItemsProviderRequest request) + private ValueTask> LoadItems(ItemsProviderRequest request) { // Filter items based on the SearchTerm - var filteredItems = !this.AllowSearch || string.IsNullOrWhiteSpace(this.searchTerm) + var filteredItems = !this.AllowSearch || string.IsNullOrWhiteSpace(this.SearchTerm) ? this.Items - : this.Items.Where(item => this.FilterItem(item, this.searchTerm)).ToList(); + : this.Items.Where(item => this.FilterItem(item, this.SearchTerm)).ToList(); // Return paged items for virtualization var items = filteredItems.Skip(request.StartIndex).Take(request.Count); @@ -151,11 +151,11 @@ private async ValueTask> LoadItems(ItemsProviderRequest r items = items.AsQueryable().OrderBy(this.SelectedSortField); } - return new ItemsProviderResult(items.ToList(), filteredItems.Count); + return new ValueTask>(new ItemsProviderResult(items.ToList(), filteredItems.Count)); } /// - /// Used to filter items based on the + /// Used to filter items based on the /// /// The item to perform searching on /// The string to search for @@ -182,7 +182,7 @@ private bool FilterItem(T item, string query) /// The text from the UI element's event private void OnSearchTextChanged(string value) { - this.searchTerm = value ?? string.Empty; + this.SearchTerm = value ?? string.Empty; this.virtualize?.RefreshDataAsync(); // Tell Virtualize to refresh data } diff --git a/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs b/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs index f88364b1..3ed43d4c 100644 --- a/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs +++ b/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs @@ -40,6 +40,6 @@ public interface IElementDefinitionDetailsViewModel /// /// A collection of /// - ICollection Rows { get; set; } + ICollection Rows { get; } } } From c650c9d263b81890fce2a8087a9a066ceac8cec7 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 14:01:08 +0100 Subject: [PATCH 13/14] Error in SQ recommendations --- .../SystemRepresentation/IElementDefinitionDetailsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs b/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs index 3ed43d4c..f88364b1 100644 --- a/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs +++ b/COMETwebapp/ViewModels/Components/SystemRepresentation/IElementDefinitionDetailsViewModel.cs @@ -40,6 +40,6 @@ public interface IElementDefinitionDetailsViewModel /// /// A collection of /// - ICollection Rows { get; } + ICollection Rows { get; set; } } } From 7e203fe129811db7caf4243e15e8a8a5418bd4e1 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 16 Dec 2024 14:32:42 +0100 Subject: [PATCH 14/14] NewLine --- COMET.Web.Common/Components/CardView/CardView.razor | 1 + 1 file changed, 1 insertion(+) diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index b941bd4a..7bf4a1ab 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -19,6 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -------------------------------------------------------------------------------> + @namespace COMET.Web.Common.Components.CardView @typeparam T @inherits DisposableComponent
- + - +
-