Add user layout management to grids with toolbar actions
- Introduced separate auto-save and user-save layout storage keys - Added IMgGridBase methods for saving, loading, resetting, and checking user layouts - Updated grid toolbar with "Layout" menu (load, save, reset) and new icons - Improved layout persistence logic and default layout restore - Enabled forced grid re-render on layout reset - Adjusted grid pager and page size defaults - Updated related components to use new storage keys - Fixed minor bugs and set RELEASE log level to Debug
This commit is contained in:
parent
4dbeb507fe
commit
c468583afd
|
|
@ -61,12 +61,36 @@ public interface IMgGridBase : IGrid
|
|||
/// Whether the grid/wrapper is currently in fullscreen mode
|
||||
/// </summary>
|
||||
bool IsFullscreen { get; }
|
||||
string LayoutStorageKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage key for automatic layout persistence
|
||||
/// </summary>
|
||||
string AutomaticLayoutStorageKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Toggles fullscreen mode for the grid (or wrapper if available)
|
||||
/// </summary>
|
||||
void ToggleFullscreen();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current layout to user storage (manual save)
|
||||
/// </summary>
|
||||
Task SaveUserLayoutAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Loads layout from user storage (manual load)
|
||||
/// </summary>
|
||||
Task LoadUserLayoutAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the layout by clearing auto-saved layout and reloading the page
|
||||
/// </summary>
|
||||
Task ResetLayoutAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a user-saved layout exists without loading it
|
||||
/// </summary>
|
||||
Task<bool> HasUserLayoutAsync();
|
||||
}
|
||||
|
||||
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
||||
|
|
@ -81,6 +105,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
protected bool IsFirstInitializeParameters;
|
||||
protected bool IsFirstInitializeParameterCore;
|
||||
private bool _isDisposed;
|
||||
private Guid _gridRenderKey = Guid.NewGuid();
|
||||
|
||||
private TSignalRDataSource? _dataSource = null!;
|
||||
private AcObservableCollection<TDataItem>? _dataSourceParam = [];
|
||||
|
|
@ -107,7 +132,12 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
return current;
|
||||
}
|
||||
|
||||
public string LayoutStorageKey
|
||||
/// <summary>
|
||||
/// Gets the user layout storage key (replaces AutoSave with UserSave)
|
||||
/// </summary>
|
||||
private string UserLayoutStorageKey => AutomaticLayoutStorageKey.Replace("_AutoSave_", "_UserSave_");
|
||||
|
||||
public string AutomaticLayoutStorageKey
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -184,6 +214,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
// Standalone fullscreen mode - Bootstrap 5 fullscreen overlay
|
||||
contentBuilder.OpenElement(0, "div");
|
||||
contentBuilder.AddAttribute(1, "class", "mg-fullscreen-overlay");
|
||||
contentBuilder.SetKey(_gridRenderKey);
|
||||
|
||||
// Header
|
||||
contentBuilder.OpenElement(2, "div");
|
||||
|
|
@ -217,7 +248,12 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
}
|
||||
else
|
||||
{
|
||||
// Normal mode - use key for forced re-render on reset
|
||||
contentBuilder.OpenElement(0, "div");
|
||||
contentBuilder.SetKey(_gridRenderKey);
|
||||
contentBuilder.AddAttribute(1, "style", "display: contents;");
|
||||
base.BuildRenderTree(contentBuilder);
|
||||
contentBuilder.CloseElement();
|
||||
}
|
||||
}));
|
||||
builder.CloseComponent();
|
||||
|
|
@ -709,21 +745,46 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
/// </summary>
|
||||
protected virtual int GetLayoutUserId() => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the default layout (before any saved layout is loaded) for reset functionality
|
||||
/// </summary>
|
||||
private string? _defaultLayoutJson = null;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a layout exists in localStorage without loading its content
|
||||
/// </summary>
|
||||
protected virtual async Task<string?> GetStorageItem(string localStorageKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await JSRuntime.InvokeAsync<string>("localStorage.getItem", localStorageKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Mute exceptions for the server prerender stage
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
||||
{
|
||||
e.Layout = await LoadLayoutFromLocalStorageAsync(LayoutStorageKey);
|
||||
// Save the default layout before loading any saved layout
|
||||
_defaultLayoutJson ??= JsonSerializer.Serialize(SaveLayout());
|
||||
|
||||
e.Layout = await LoadLayoutFromLocalStorageAsync(AutomaticLayoutStorageKey);
|
||||
}
|
||||
|
||||
private async Task Grid_LayoutAutoSaving(GridPersistentLayoutEventArgs e)
|
||||
{
|
||||
await SaveLayoutToLocalStorageAsync(e.Layout, LayoutStorageKey);
|
||||
await SaveLayoutToLocalStorageAsync(e.Layout, AutomaticLayoutStorageKey);
|
||||
}
|
||||
|
||||
protected virtual async Task<GridPersistentLayout?> LoadLayoutFromLocalStorageAsync(string localStorageKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await JSRuntime.InvokeAsync<string>("localStorage.getItem", localStorageKey);
|
||||
var json = await GetStorageItem(localStorageKey);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
return JsonSerializer.Deserialize<GridPersistentLayout>(json);
|
||||
|
|
@ -749,10 +810,75 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
}
|
||||
}
|
||||
|
||||
protected virtual async Task RemoveLayoutFromLocalStorageAsync(string localStorageKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("localStorage.removeItem", localStorageKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Mute exceptions for the server prerender stage
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveUserLayoutAsync()
|
||||
{
|
||||
var layout = SaveLayout();
|
||||
|
||||
await SaveLayoutToLocalStorageAsync(layout, UserLayoutStorageKey);
|
||||
await SaveLayoutToLocalStorageAsync(layout, AutomaticLayoutStorageKey);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task LoadUserLayoutAsync()
|
||||
{
|
||||
var layout = await LoadLayoutFromLocalStorageAsync(UserLayoutStorageKey);
|
||||
if (layout != null)
|
||||
{
|
||||
LoadLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ResetLayoutAsync()
|
||||
{
|
||||
await RemoveLayoutFromLocalStorageAsync(AutomaticLayoutStorageKey);
|
||||
|
||||
// Restore the default layout if available
|
||||
if (!string.IsNullOrWhiteSpace(_defaultLayoutJson))
|
||||
{
|
||||
var defaultLayout = JsonSerializer.Deserialize<GridPersistentLayout>(_defaultLayoutJson);
|
||||
if (defaultLayout != null)
|
||||
LoadLayout(defaultLayout);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> HasUserLayoutAsync()
|
||||
{
|
||||
return !(await GetStorageItem(UserLayoutStorageKey)).IsNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Add);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Force grid re-initialization
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task ForceRenderAsync()
|
||||
{
|
||||
// Force grid re-initialization by changing the render key
|
||||
_gridRenderKey = Guid.NewGuid();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public Task UpdateDataItem(TDataItem dataItem) => _dataSource.Update(dataItem, true);
|
||||
|
||||
public Task UpdateDataItemAsync(TDataItem dataItem)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,13 @@
|
|||
@if (!OnlyGridEditTools)
|
||||
{
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Column Chooser")" BeginGroup="true" Click="ColumnChooserItem_Click" IconCssClass="grid-column-chooser" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Layout")" IconCssClass="grid-layout">
|
||||
<Items>
|
||||
<DxToolbarItem Text="Load Layout" Click="LoadLayout_Click" IconCssClass="grid-layout-load" Enabled="@_hasUserLayout" />
|
||||
<DxToolbarItem Text="Save Layout" Click="SaveLayout_Click" IconCssClass="grid-layout-save" />
|
||||
<DxToolbarItem BeginGroup="true" Text="Reset Layout" Click="ResetLayout_Click" IconCssClass="grid-layout-reset" />
|
||||
</Items>
|
||||
</DxToolbarItem>
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Export")" IconCssClass="grid-export" Visible="false" Enabled="@(HasFocusedRow && !IsEditing)">
|
||||
<Items>
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To CSV")" Click="ExportCsvItem_Click" IconCssClass="grid-export-xlsx" />
|
||||
|
|
@ -28,7 +35,7 @@
|
|||
}
|
||||
</MgGridToolbarBase>
|
||||
|
||||
@code {
|
||||
@code {
|
||||
[Parameter] public bool OnlyGridEditTools { get; set; } = false;
|
||||
[Parameter] public IMgGridBase Grid { get; set; } = null!;
|
||||
[Parameter] public RenderFragment? ToolbarItemsExtended { get; set; }
|
||||
|
|
@ -38,6 +45,7 @@
|
|||
public MgGridToolbarBase GridToolbar { get; set; } = null!;
|
||||
const string ExportFileName = "ExportResult";
|
||||
|
||||
private bool _hasUserLayout;
|
||||
private bool _isReloadInProgress;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -70,8 +78,9 @@
|
|||
/// </summary>
|
||||
private string FullscreenIconCssClass => IsFullscreenMode ? "grid-fullscreen-exit" : "grid-fullscreen";
|
||||
|
||||
protected override void OnInitialized()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_hasUserLayout = await Grid.HasUserLayoutAsync();
|
||||
}
|
||||
|
||||
async Task ReloadData_Click(ToolbarItemClickEventArgs e)
|
||||
|
|
@ -151,4 +160,20 @@
|
|||
{
|
||||
await Grid.ExportToPdfAsync(ExportFileName);
|
||||
}
|
||||
|
||||
async Task LoadLayout_Click()
|
||||
{
|
||||
await Grid.LoadUserLayoutAsync();
|
||||
}
|
||||
|
||||
async Task SaveLayout_Click()
|
||||
{
|
||||
await Grid.SaveUserLayoutAsync();
|
||||
_hasUserLayout = true;
|
||||
}
|
||||
|
||||
async Task ResetLayout_Click()
|
||||
{
|
||||
await Grid.ResetLayoutAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
}
|
||||
|
||||
private string GetStorageKey() => _currentGrid != null
|
||||
? $"Splitter_{_currentGrid.LayoutStorageKey}"
|
||||
? $"Splitter_{_currentGrid.AutomaticLayoutStorageKey}"
|
||||
: null!;
|
||||
|
||||
private async Task LoadSavedSizeAsync()
|
||||
|
|
|
|||
Loading…
Reference in New Issue