Add URL link support to grid columns and info panel
- Introduced UrlLink parameter to MgGridDataColumn for clickable cell links with property placeholder support. - Updated MgGridInfoPanel to render links for columns with UrlLink. - Added unit tests for UrlLink rendering and value replacement. - Added DynamicColumnAddingEventArgs and OnDynamicColumnAttributeAdding for dynamic column customization. - Refactored layout storage key logic and enabled persistent info panel splitter size in MgGridWithInfoPanel. - Updated product/order grids to use MgGridDataColumn with UrlLink and switched ProductDtos to AcObservableCollection for reactivity. - Added AddPartner method to IFruitBankDataControllerCommon and FruitBankSignalRClient. - Miscellaneous fixes: logger initialization, code cleanup, and improved comments.
This commit is contained in:
parent
bad4e50c17
commit
4dbeb507fe
|
|
@ -1,3 +1,4 @@
|
||||||
|
using AyCode.Blazor.Components.Components.Grids;
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using AyCode.Services.Server.Tests.SignalRs;
|
using AyCode.Services.Server.Tests.SignalRs;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
|
|
@ -50,20 +51,29 @@ public class MgGridBaseTests : BunitTestContext
|
||||||
[TestCleanup]
|
[TestCleanup]
|
||||||
public void TestTeardown() => Context?.Dispose();
|
public void TestTeardown() => Context?.Dispose();
|
||||||
|
|
||||||
private IRenderedComponent<TestMgGridOrderItem> RenderTestGrid(string autoSaveLayoutName = "TestGrid")
|
private IRenderedComponent<TestMgGridOrderItem> RenderTestGrid(
|
||||||
|
string autoSaveLayoutName = "TestGrid",
|
||||||
|
System.Action<DynamicColumnAddingEventArgs>? onDynamicColumnAttributeAdding = null)
|
||||||
{
|
{
|
||||||
var dataSource = new TestGridOrderItemDataSource(_client, _crudTags);
|
var dataSource = new TestGridOrderItemDataSource(_client, _crudTags);
|
||||||
|
|
||||||
return Context!.Render<TestMgGridOrderItem>(parameters => parameters
|
return Context!.Render<TestMgGridOrderItem>(parameters =>
|
||||||
.Add(p => p.DataSource, dataSource)
|
{
|
||||||
.Add(p => p.Logger, _logger)
|
parameters
|
||||||
.Add(p => p.SignalRClient, _client)
|
.Add(p => p.DataSource, dataSource)
|
||||||
.Add(p => p.AutoSaveLayoutName, autoSaveLayoutName)
|
.Add(p => p.Logger, _logger)
|
||||||
.Add(p => p.GetAllMessageTag, _crudTags.GetAllMessageTag)
|
.Add(p => p.SignalRClient, _client)
|
||||||
.Add(p => p.AddMessageTag, _crudTags.AddMessageTag)
|
.Add(p => p.AutoSaveLayoutName, autoSaveLayoutName)
|
||||||
.Add(p => p.UpdateMessageTag, _crudTags.UpdateMessageTag)
|
.Add(p => p.GetAllMessageTag, _crudTags.GetAllMessageTag)
|
||||||
.Add(p => p.RemoveMessageTag, _crudTags.RemoveMessageTag)
|
.Add(p => p.AddMessageTag, _crudTags.AddMessageTag)
|
||||||
);
|
.Add(p => p.UpdateMessageTag, _crudTags.UpdateMessageTag)
|
||||||
|
.Add(p => p.RemoveMessageTag, _crudTags.RemoveMessageTag);
|
||||||
|
|
||||||
|
if (onDynamicColumnAttributeAdding != null)
|
||||||
|
{
|
||||||
|
parameters.Add(p => p.OnDynamicColumnAttributeAdding, onDynamicColumnAttributeAdding);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Layout Persistence Tests
|
#region Layout Persistence Tests
|
||||||
|
|
@ -178,4 +188,39 @@ public class MgGridBaseTests : BunitTestContext
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region MgGridDataColumn UrlLink Tests
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MgGridDataColumn_UrlLink_ShouldRenderLinkWithReplacedValues()
|
||||||
|
{
|
||||||
|
// Arrange - Render grid with UrlLink on Id column
|
||||||
|
var cut = RenderTestGrid(onDynamicColumnAttributeAdding: args =>
|
||||||
|
{
|
||||||
|
if (args.FieldName == nameof(TestOrderItem.Id))
|
||||||
|
{
|
||||||
|
args.AdditionalAttributes[nameof(MgGridDataColumn.UrlLink)] = "https://example.com/edit/{Id}";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for data source to load
|
||||||
|
await cut.Instance.WaitForDataSourceLoadedAsync();
|
||||||
|
|
||||||
|
// Get the first row's Id value from the grid
|
||||||
|
var firstRowId = cut.Instance.GetRowValue(0, nameof(TestOrderItem.Id));
|
||||||
|
Assert.IsNotNull(firstRowId, "First row should have an Id value");
|
||||||
|
|
||||||
|
// Build the expected URL with the actual Id value
|
||||||
|
var expectedUrl = $"https://example.com/edit/{firstRowId}";
|
||||||
|
|
||||||
|
// Find the anchor element with the exact expected href
|
||||||
|
var anchor = cut.Find($"a[href=\"{expectedUrl}\"]");
|
||||||
|
|
||||||
|
// Assert - The anchor should exist and its text content should be the Id value
|
||||||
|
Assert.IsNotNull(anchor, $"Should find anchor with href='{expectedUrl}'");
|
||||||
|
Assert.AreEqual(firstRowId.ToString(), anchor.TextContent,
|
||||||
|
$"Anchor text should be the Id value '{firstRowId}', but was '{anchor.TextContent}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using AyCode.Services.SignalRs;
|
||||||
using DevExpress.Blazor;
|
using DevExpress.Blazor;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Tests.Grids;
|
namespace AyCode.Blazor.Components.Tests.Grids;
|
||||||
|
|
||||||
|
|
@ -28,6 +29,22 @@ public class TestGridOrderItemDataSource : TestOrderItemObservableDataSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args for dynamic column adding event.
|
||||||
|
/// Provides a delegate to add custom attributes to the column.
|
||||||
|
/// </summary>
|
||||||
|
public class DynamicColumnAddingEventArgs
|
||||||
|
{
|
||||||
|
public required string FieldName { get; init; }
|
||||||
|
public required PropertyInfo PropertyInfo { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary of additional attributes to add to the column.
|
||||||
|
/// Key is the attribute name, value is the attribute value.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object?> AdditionalAttributes { get; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base test implementation of MgGridBase for testing grid functionality.
|
/// Base test implementation of MgGridBase for testing grid functionality.
|
||||||
/// Overrides layout persistence to use in-memory storage for testing.
|
/// Overrides layout persistence to use in-memory storage for testing.
|
||||||
|
|
@ -54,6 +71,13 @@ public abstract class TestMgGridBase<TSignalRDataSource, TDataItem, TId, TLogger
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDataSourceLoaded { get; private set; }
|
public bool IsDataSourceLoaded { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event called when a dynamic column is being added. Allows customization of column properties.
|
||||||
|
/// Add attributes to eventArgs.AdditionalAttributes dictionary.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public Action<DynamicColumnAddingEventArgs>? OnDynamicColumnAttributeAdding { get; set; }
|
||||||
|
|
||||||
public void SetTestUserId(int userId) => _testUserId = userId;
|
public void SetTestUserId(int userId) => _testUserId = userId;
|
||||||
public int GetTestUserId() => _testUserId;
|
public int GetTestUserId() => _testUserId;
|
||||||
protected override int GetLayoutUserId() => _testUserId;
|
protected override int GetLayoutUserId() => _testUserId;
|
||||||
|
|
@ -101,7 +125,7 @@ public abstract class TestMgGridBase<TSignalRDataSource, TDataItem, TId, TLogger
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds grid columns from TDataItem properties using reflection
|
/// Builds grid columns from TDataItem properties using reflection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void BuildColumnsFromDataItem(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder)
|
private void BuildColumnsFromDataItem(RenderTreeBuilder builder)
|
||||||
{
|
{
|
||||||
var properties = typeof(TDataItem).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
var properties = typeof(TDataItem).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
.Where(p => p.CanRead && IsSimpleType(p.PropertyType))
|
.Where(p => p.CanRead && IsSimpleType(p.PropertyType))
|
||||||
|
|
@ -110,9 +134,25 @@ public abstract class TestMgGridBase<TSignalRDataSource, TDataItem, TId, TLogger
|
||||||
var seq = 0;
|
var seq = 0;
|
||||||
foreach (var property in properties)
|
foreach (var property in properties)
|
||||||
{
|
{
|
||||||
builder.OpenComponent<DxGridDataColumn>(seq++);
|
// Create event args and invoke the event
|
||||||
builder.AddAttribute(seq++, nameof(DxGridDataColumn.FieldName), property.Name);
|
var eventArgs = new DynamicColumnAddingEventArgs
|
||||||
builder.AddAttribute(seq++, nameof(DxGridDataColumn.Width), GetDefaultWidth(property.PropertyType));
|
{
|
||||||
|
FieldName = property.Name,
|
||||||
|
PropertyInfo = property
|
||||||
|
};
|
||||||
|
OnDynamicColumnAttributeAdding?.Invoke(eventArgs);
|
||||||
|
|
||||||
|
builder.OpenComponent<MgGridDataColumn>(seq++);
|
||||||
|
builder.AddAttribute(seq++, nameof(MgGridDataColumn.Name), property.Name);
|
||||||
|
builder.AddAttribute(seq++, nameof(MgGridDataColumn.FieldName), property.Name);
|
||||||
|
builder.AddAttribute(seq++, nameof(MgGridDataColumn.Width), GetDefaultWidth(property.PropertyType));
|
||||||
|
|
||||||
|
// Add additional attributes from the event
|
||||||
|
foreach (var attr in eventArgs.AdditionalAttributes)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(seq++, attr.Key, attr.Value);
|
||||||
|
}
|
||||||
|
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DevExpress.Blazor.Internal;
|
using DevExpress.Blazor.Internal;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
namespace AyCode.Blazor.Components.Components.Grids;
|
||||||
|
|
||||||
|
|
@ -60,6 +61,7 @@ public interface IMgGridBase : IGrid
|
||||||
/// Whether the grid/wrapper is currently in fullscreen mode
|
/// Whether the grid/wrapper is currently in fullscreen mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsFullscreen { get; }
|
bool IsFullscreen { get; }
|
||||||
|
string LayoutStorageKey { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggles fullscreen mode for the grid (or wrapper if available)
|
/// Toggles fullscreen mode for the grid (or wrapper if available)
|
||||||
|
|
@ -105,6 +107,15 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string LayoutStorageKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var masterDetailName = IsMasterGrid ? "Master" : ParentDataItem!.GetType().Name;
|
||||||
|
return $"{AutoSaveLayoutName}_{masterDetailName}_AutoSave_{GetLayoutUserId()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reference to the wrapper component for grid-InfoPanel communication
|
/// Reference to the wrapper component for grid-InfoPanel communication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -682,6 +693,9 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
LayoutAutoLoading = Grid_LayoutAutoLoading;
|
LayoutAutoLoading = Grid_LayoutAutoLoading;
|
||||||
LayoutAutoSaving = Grid_LayoutAutoSaving;
|
LayoutAutoSaving = Grid_LayoutAutoSaving;
|
||||||
|
|
||||||
|
// Register this grid with the wrapper for splitter size persistence
|
||||||
|
GridWrapper?.RegisterGrid(this);
|
||||||
|
|
||||||
IsFirstInitializeParameters = true;
|
IsFirstInitializeParameters = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -697,16 +711,12 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
|
|
||||||
private async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
private async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
||||||
{
|
{
|
||||||
var masterDetailName = IsMasterGrid ? "Master" : ParentDataItem!.GetType().Name;
|
e.Layout = await LoadLayoutFromLocalStorageAsync(LayoutStorageKey);
|
||||||
var layoutKey = $"{AutoSaveLayoutName}_{masterDetailName}_AutoSave_{GetLayoutUserId()}";
|
|
||||||
e.Layout = await LoadLayoutFromLocalStorageAsync(layoutKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Grid_LayoutAutoSaving(GridPersistentLayoutEventArgs e)
|
private async Task Grid_LayoutAutoSaving(GridPersistentLayoutEventArgs e)
|
||||||
{
|
{
|
||||||
var masterDetailName = IsMasterGrid ? "Master" : ParentDataItem!.GetType().Name;
|
await SaveLayoutToLocalStorageAsync(e.Layout, LayoutStorageKey);
|
||||||
var layoutKey = $"{AutoSaveLayoutName}_{masterDetailName}_AutoSave_{GetLayoutUserId()}";
|
|
||||||
await SaveLayoutToLocalStorageAsync(e.Layout, layoutKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual async Task<GridPersistentLayout?> LoadLayoutFromLocalStorageAsync(string localStorageKey)
|
protected virtual async Task<GridPersistentLayout?> LoadLayoutFromLocalStorageAsync(string localStorageKey)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using DevExpress.Blazor;
|
using DevExpress.Blazor;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
namespace AyCode.Blazor.Components.Components.Grids;
|
||||||
|
|
||||||
|
|
@ -8,6 +10,9 @@ namespace AyCode.Blazor.Components.Components.Grids;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MgGridDataColumn : DxGridDataColumn
|
public class MgGridDataColumn : DxGridDataColumn
|
||||||
{
|
{
|
||||||
|
private string? _urlLink;
|
||||||
|
private bool _isInitialized;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this column should be visible in the InfoPanel. Default is true.
|
/// Whether this column should be visible in the InfoPanel. Default is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -25,4 +30,64 @@ public class MgGridDataColumn : DxGridDataColumn
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int InfoPanelOrder { get; set; } = int.MaxValue;
|
public int InfoPanelOrder { get; set; } = int.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL template with {property} placeholders that will be replaced with row values.
|
||||||
|
/// Example: https://shop.fruitbank.hu/Admin/Order/Edit/{Id}/{OrderId}
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public string? UrlLink
|
||||||
|
{
|
||||||
|
get => _urlLink;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_urlLink == value) return;
|
||||||
|
_urlLink = value;
|
||||||
|
if (_isInitialized) UpdateCellDisplayTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
base.OnParametersSet();
|
||||||
|
_isInitialized = true;
|
||||||
|
UpdateCellDisplayTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCellDisplayTemplate()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(_urlLink))
|
||||||
|
{
|
||||||
|
CellDisplayTemplate = context => builder =>
|
||||||
|
{
|
||||||
|
var url = BuildUrlFromTemplate(_urlLink, context.DataItem);
|
||||||
|
builder.OpenElement(0, "a");
|
||||||
|
builder.AddAttribute(1, "href", url);
|
||||||
|
builder.AddAttribute(2, "target", "_blank");
|
||||||
|
builder.AddAttribute(3, "style", "text-decoration: none;");
|
||||||
|
builder.AddContent(4, context.DisplayText);
|
||||||
|
builder.CloseElement();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces {property} placeholders in the template with values from the data item.
|
||||||
|
/// Exposed for unit testing.
|
||||||
|
/// </summary>
|
||||||
|
internal static string BuildUrlFromTemplate(string template, object? dataItem)
|
||||||
|
{
|
||||||
|
if (dataItem == null) return template;
|
||||||
|
return Regex.Replace(template, "{([^}]+)}", match =>
|
||||||
|
{
|
||||||
|
var propName = match.Groups[1].Value;
|
||||||
|
var prop = dataItem.GetType().GetProperty(propName);
|
||||||
|
if (prop != null)
|
||||||
|
{
|
||||||
|
var value = prop.GetValue(dataItem);
|
||||||
|
return value?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
return match.Value;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RenderCellContent(value, displayText)(builder);
|
RenderCellContent(value, displayText, column, dataItem)(builder);
|
||||||
}
|
}
|
||||||
builder.CloseElement();
|
builder.CloseElement();
|
||||||
|
|
||||||
|
|
@ -359,11 +359,27 @@
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private RenderFragment RenderCellContent(object? value, string displayText)
|
private RenderFragment RenderCellContent(object? value, string displayText, DxGridDataColumn? column = null, object? dataItem = null)
|
||||||
{
|
{
|
||||||
return builder =>
|
return builder =>
|
||||||
{
|
{
|
||||||
var seq = 0;
|
var seq = 0;
|
||||||
|
|
||||||
|
// Check if column has UrlLink
|
||||||
|
if (column is MgGridDataColumn mgColumn && !string.IsNullOrWhiteSpace(mgColumn.UrlLink) && dataItem != null)
|
||||||
|
{
|
||||||
|
var url = MgGridDataColumn.BuildUrlFromTemplate(mgColumn.UrlLink, dataItem);
|
||||||
|
|
||||||
|
builder.OpenElement(seq++, "a");
|
||||||
|
builder.AddAttribute(seq++, "href", url);
|
||||||
|
builder.AddAttribute(seq++, "target", "_blank");
|
||||||
|
builder.AddAttribute(seq++, "class", "mg-info-panel-link");
|
||||||
|
builder.AddAttribute(seq++, "title", displayText);
|
||||||
|
builder.AddContent(seq++, displayText);
|
||||||
|
builder.CloseElement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
builder.OpenElement(seq++, "span");
|
builder.OpenElement(seq++, "span");
|
||||||
builder.AddAttribute(seq++, "class", "mg-info-panel-value");
|
builder.AddAttribute(seq++, "class", "mg-info-panel-value");
|
||||||
builder.AddAttribute(seq++, "title", displayText);
|
builder.AddAttribute(seq++, "title", displayText);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
|
@inject Microsoft.JSInterop.IJSRuntime JSRuntime
|
||||||
|
|
||||||
<CascadingValue Value="this">
|
<CascadingValue Value="this">
|
||||||
@if (_isFullscreen)
|
@if (_isFullscreen)
|
||||||
|
|
@ -23,6 +24,8 @@
|
||||||
private IInfoPanelBase? _infoPanelInstance;
|
private IInfoPanelBase? _infoPanelInstance;
|
||||||
private IMgGridBase? _currentGrid;
|
private IMgGridBase? _currentGrid;
|
||||||
private bool _isFullscreen;
|
private bool _isFullscreen;
|
||||||
|
private string _currentInfoPanelSize = "400px";
|
||||||
|
private bool _sizeLoaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The grid content to display in the left pane
|
/// The grid content to display in the left pane
|
||||||
|
|
@ -77,6 +80,12 @@
|
||||||
public void RegisterGrid(IMgGridBase grid)
|
public void RegisterGrid(IMgGridBase grid)
|
||||||
{
|
{
|
||||||
_currentGrid = grid;
|
_currentGrid = grid;
|
||||||
|
|
||||||
|
// Load saved size when grid is registered
|
||||||
|
if (!_sizeLoaded)
|
||||||
|
{
|
||||||
|
_ = LoadSavedSizeAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -106,6 +115,69 @@
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetStorageKey() => _currentGrid != null
|
||||||
|
? $"Splitter_{_currentGrid.LayoutStorageKey}"
|
||||||
|
: null!;
|
||||||
|
|
||||||
|
private async Task LoadSavedSizeAsync()
|
||||||
|
{
|
||||||
|
if (_currentGrid == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var storageKey = GetStorageKey();
|
||||||
|
var savedSize = await JSRuntime.InvokeAsync<string>("localStorage.getItem", storageKey);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(savedSize))
|
||||||
|
{
|
||||||
|
_currentInfoPanelSize = savedSize;
|
||||||
|
_sizeLoaded = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentInfoPanelSize = InfoPanelSize;
|
||||||
|
_sizeLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Mute exceptions for the server prerender stage
|
||||||
|
_currentInfoPanelSize = InfoPanelSize;
|
||||||
|
_sizeLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSizeAsync(string size)
|
||||||
|
{
|
||||||
|
if (_currentGrid == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var storageKey = GetStorageKey();
|
||||||
|
await JSRuntime.InvokeVoidAsync("localStorage.setItem", storageKey, size);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Mute exceptions for the server prerender stage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnInfoPanelSizeChanged(string newSize)
|
||||||
|
{
|
||||||
|
_currentInfoPanelSize = newSize;
|
||||||
|
await SaveSizeAsync(newSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (!_sizeLoaded)
|
||||||
|
{
|
||||||
|
_currentInfoPanelSize = InfoPanelSize;
|
||||||
|
}
|
||||||
|
base.OnParametersSet();
|
||||||
|
}
|
||||||
|
|
||||||
private RenderFragment RenderMainContent() => __builder =>
|
private RenderFragment RenderMainContent() => __builder =>
|
||||||
{
|
{
|
||||||
if (ShowInfoPanel)
|
if (ShowInfoPanel)
|
||||||
|
|
@ -115,7 +187,8 @@
|
||||||
<DxSplitterPane>
|
<DxSplitterPane>
|
||||||
@GridContent
|
@GridContent
|
||||||
</DxSplitterPane>
|
</DxSplitterPane>
|
||||||
<DxSplitterPane Size="@InfoPanelSize" MinSize="0px" MaxSize="100%" AllowCollapse="true" CssClass="mg-info-panel-pane">
|
<DxSplitterPane Size="@_currentInfoPanelSize" MinSize="0px" MaxSize="100%" AllowCollapse="false" CssClass="mg-info-panel-pane"
|
||||||
|
SizeChanged="OnInfoPanelSizeChanged">
|
||||||
@if (ChildContent != null)
|
@if (ChildContent != null)
|
||||||
{
|
{
|
||||||
@ChildContent
|
@ChildContent
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue