Enable server-roaming for grid layouts; EKÁER depot guard
Extended MgGridBase to support server-roaming of user grid layouts via SignalR, controlled by the new EnableServerLayouts parameter. Updated Save/Load/HasUserLayoutAsync logic and documentation. Added SignalR API methods for grid layout storage. In EKÁER, added TryDepotError to require PartnerDepot before XML generation and enforced gross weight > 0 per line. Updated docs and settings.local.json for new features and automation.
This commit is contained in:
parent
bd65d70d60
commit
076162f6ee
|
|
@ -26,7 +26,7 @@ public interface IMgGridBase : IGrid
|
||||||
bool IsSyncing { get; }
|
bool IsSyncing { get; }
|
||||||
|
|
||||||
string Caption { get; set; }
|
string Caption { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current edit state of the grid (None, New, Edit)
|
/// Current edit state of the grid (None, New, Edit)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -51,7 +51,7 @@ public interface IMgGridBase : IGrid
|
||||||
/// Navigates to the next row in the grid
|
/// Navigates to the next row in the grid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void StepNextRow();
|
void StepNextRow();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// InfoPanel instance for displaying row details (from wrapper)
|
/// InfoPanel instance for displaying row details (from wrapper)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -73,22 +73,24 @@ public interface IMgGridBase : IGrid
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the current layout to user storage (manual save)
|
/// Saves the current layout to user storage (manual save); roams it to the server when server layouts are enabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task SaveUserLayoutAsync();
|
Task SaveUserLayoutAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads layout from user storage (manual load)
|
/// Loads layout from user storage (manual load); prefers the server copy when server layouts are enabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task LoadUserLayoutAsync();
|
Task LoadUserLayoutAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets the layout by clearing auto-saved layout and reloading the page
|
/// Resets the layout by clearing the auto-saved layout and restoring the default layout.
|
||||||
|
/// Saved copies (local UserSave and server) are not touched — Load brings them back.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task ResetLayoutAsync();
|
Task ResetLayoutAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a user-saved layout exists without loading it
|
/// Checks if a user-saved layout exists locally without loading it;
|
||||||
|
/// returns true optimistically when server layouts are enabled (no server round-trip)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<bool> HasUserLayoutAsync();
|
Task<bool> HasUserLayoutAsync();
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +155,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
public MgGridWithInfoPanel? GridWrapper { get; set; }
|
public MgGridWithInfoPanel? GridWrapper { get; set; }
|
||||||
|
|
||||||
private object _focusedDataItem;
|
private object _focusedDataItem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// InfoPanel instance for displaying row details.
|
/// InfoPanel instance for displaying row details.
|
||||||
/// First checks own wrapper, then gets InfoPanel from root grid.
|
/// First checks own wrapper, then gets InfoPanel from root grid.
|
||||||
|
|
@ -165,16 +167,16 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
// First check if we have a direct wrapper with InfoPanel
|
// First check if we have a direct wrapper with InfoPanel
|
||||||
if (GridWrapper?.InfoPanelInstance != null)
|
if (GridWrapper?.InfoPanelInstance != null)
|
||||||
return GridWrapper.InfoPanelInstance;
|
return GridWrapper.InfoPanelInstance;
|
||||||
|
|
||||||
// Get InfoPanel from root grid (handles nested grids)
|
// Get InfoPanel from root grid (handles nested grids)
|
||||||
var rootGrid = GetRootGrid();
|
var rootGrid = GetRootGrid();
|
||||||
if (rootGrid != this)
|
if (rootGrid != this)
|
||||||
return rootGrid.InfoPanelInstance;
|
return rootGrid.InfoPanelInstance;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsFullscreen => GridWrapper?.IsFullscreen ?? _isStandaloneFullscreen;
|
public bool IsFullscreen => GridWrapper?.IsFullscreen ?? _isStandaloneFullscreen;
|
||||||
|
|
||||||
|
|
@ -195,7 +197,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
InvokeAsync(StateHasChanged);
|
InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MgGridBase() : base()
|
public MgGridBase() : base()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +267,14 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
|
|
||||||
[Inject] protected IJSRuntime JSRuntime { get; set; } = null!;
|
[Inject] protected IJSRuntime JSRuntime { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When true, the UserSave layout tier also roams via the SignalR server endpoint
|
||||||
|
/// (AcSignalRTags.Get/Save/DeleteGridLayoutTag): SaveUserLayoutAsync pushes to the server after the
|
||||||
|
/// local save, LoadUserLayoutAsync prefers the server copy. The server must implement the tags.
|
||||||
|
/// Server communication happens exclusively on user clicks (Save/Load).
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public bool EnableServerLayouts { get; set; } = true;
|
||||||
|
|
||||||
[Parameter] public TLoggerClient Logger { get; set; }
|
[Parameter] public TLoggerClient Logger { get; set; }
|
||||||
[Parameter] public string GridName { get; set; }
|
[Parameter] public string GridName { get; set; }
|
||||||
[Parameter] public IId<TId>? ParentDataItem { get; set; }
|
[Parameter] public IId<TId>? ParentDataItem { get; set; }
|
||||||
|
|
@ -272,7 +282,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
[Parameter] public object[]? ContextIds { get; set; }
|
[Parameter] public object[]? ContextIds { get; set; }
|
||||||
|
|
||||||
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
|
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
|
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -360,7 +370,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
private void SetWorkingReferenceList(AcObservableCollection<TDataItem>? referenceList)
|
private void SetWorkingReferenceList(AcObservableCollection<TDataItem>? referenceList)
|
||||||
{
|
{
|
||||||
_dataSource?.SetWorkingReferenceList(referenceList);
|
_dataSource?.SetWorkingReferenceList(referenceList);
|
||||||
|
|
||||||
SetGridData(referenceList);
|
SetGridData(referenceList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -391,21 +401,21 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
_dataSource.FilterText = FilterText;
|
_dataSource.FilterText = FilterText;
|
||||||
|
|
||||||
SetGridData(_dataSource.GetReferenceInnerList());
|
SetGridData(_dataSource.GetReferenceInnerList());
|
||||||
|
|
||||||
_dataSource.OnDataSourceLoaded += OnDataSourceLoaded;
|
_dataSource.OnDataSourceLoaded += OnDataSourceLoaded;
|
||||||
_dataSource.OnDataSourceItemChanged += OnDataSourceItemChanged;
|
_dataSource.OnDataSourceItemChanged += OnDataSourceItemChanged;
|
||||||
_dataSource.OnSyncingStateChanged += OnDataSourceSyncingStateChanged;
|
_dataSource.OnSyncingStateChanged += OnDataSourceSyncingStateChanged;
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDataSourceSyncingStateChanged(bool isSyncing)
|
private void OnDataSourceSyncingStateChanged(bool isSyncing)
|
||||||
{
|
{
|
||||||
if (_isDisposed) return;
|
if (_isDisposed) return;
|
||||||
|
|
||||||
// Forward the event to external subscribers
|
// Forward the event to external subscribers
|
||||||
//OnSyncingStateChanged?.Invoke(isSyncing);
|
//OnSyncingStateChanged?.Invoke(isSyncing);
|
||||||
|
|
||||||
// Trigger UI update
|
// Trigger UI update
|
||||||
InvokeAsync(StateHasChanged);
|
InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
@ -429,7 +439,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
private async Task OnDataSourceLoaded()
|
private async Task OnDataSourceLoaded()
|
||||||
{
|
{
|
||||||
if (_isDisposed) return;
|
if (_isDisposed) return;
|
||||||
|
|
||||||
Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}");
|
Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}");
|
||||||
|
|
||||||
await InvokeAsync(() => SetGridData(_dataSource!.GetReferenceInnerList()));
|
await InvokeAsync(() => SetGridData(_dataSource!.GetReferenceInnerList()));
|
||||||
|
|
@ -498,7 +508,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
|
|
||||||
protected PropertyInfo? GetDataItemPropertyInfo(string propertyName)
|
protected PropertyInfo? GetDataItemPropertyInfo(string propertyName)
|
||||||
=> typeof(TDataItem).GetProperty(propertyName);
|
=> typeof(TDataItem).GetProperty(propertyName);
|
||||||
|
|
||||||
protected virtual async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
protected virtual async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||||
{
|
{
|
||||||
var editModel = (e.EditModel as TDataItem)!;
|
var editModel = (e.EditModel as TDataItem)!;
|
||||||
|
|
@ -546,11 +556,11 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
{
|
{
|
||||||
infoPanelInstance.ClearEditMode();
|
infoPanelInstance.ClearEditMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frissítjük az InfoPanel-t az új sor adataival
|
// Frissítjük az InfoPanel-t az új sor adataival
|
||||||
infoPanelInstance.RefreshData(this, e.DataItem, e.VisibleIndex);
|
infoPanelInstance.RefreshData(this, e.DataItem, e.VisibleIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
await OnGridFocusedRowChanged.InvokeAsync(e);
|
await OnGridFocusedRowChanged.InvokeAsync(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,11 +730,11 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
{
|
{
|
||||||
if (GridName.IsNullOrWhiteSpace()) GridName = $"{typeof(TDataItem).Name}Grid";
|
if (GridName.IsNullOrWhiteSpace()) GridName = $"{typeof(TDataItem).Name}Grid";
|
||||||
_gridLogName = $"[{GridName}]";
|
_gridLogName = $"[{GridName}]";
|
||||||
|
|
||||||
// Set default AutoSaveLayoutName if not provided
|
// Set default AutoSaveLayoutName if not provided
|
||||||
if (AutoSaveLayoutName.IsNullOrWhiteSpace())
|
if (AutoSaveLayoutName.IsNullOrWhiteSpace())
|
||||||
AutoSaveLayoutName = $"Grid{typeof(TDataItem).Name}";
|
AutoSaveLayoutName = $"Grid{typeof(TDataItem).Name}";
|
||||||
|
|
||||||
// Set up layout auto-loading/saving
|
// Set up layout auto-loading/saving
|
||||||
LayoutAutoLoading = Grid_LayoutAutoLoading;
|
LayoutAutoLoading = Grid_LayoutAutoLoading;
|
||||||
LayoutAutoSaving = Grid_LayoutAutoSaving;
|
LayoutAutoSaving = Grid_LayoutAutoSaving;
|
||||||
|
|
@ -767,6 +777,25 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the UserSave layout JSON from the server endpoint (roaming). Best-effort: returns null when
|
||||||
|
/// the server has no copy or is unreachable — callers fall back to localStorage.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<string?> GetServerLayoutJsonAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await SignalRClient.GetByIdAsync<string>(AcSignalRTags.GetGridLayoutTag, [UserLayoutStorageKey, GetLayoutUserId()]);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Server roaming is best-effort — fall back to the local copy
|
||||||
|
Logger.Warning($"[{GetType().Name}] Server layout GET failed — falling back to the local copy; key: {UserLayoutStorageKey}; error: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
private async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
||||||
{
|
{
|
||||||
// Save the default layout before loading any saved layout
|
// Save the default layout before loading any saved layout
|
||||||
|
|
@ -786,7 +815,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
{
|
{
|
||||||
var json = await GetStorageItem(localStorageKey);
|
var json = await GetStorageItem(localStorageKey);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(json))
|
if (!string.IsNullOrWhiteSpace(json))
|
||||||
return JsonSerializer.Deserialize<GridPersistentLayout>(json);
|
return JsonSerializer.Deserialize<GridPersistentLayout>(json);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
@ -829,13 +858,54 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
var layout = SaveLayout();
|
var layout = SaveLayout();
|
||||||
|
|
||||||
await SaveLayoutToLocalStorageAsync(layout, UserLayoutStorageKey);
|
await SaveLayoutToLocalStorageAsync(layout, UserLayoutStorageKey);
|
||||||
await SaveLayoutToLocalStorageAsync(layout, AutomaticLayoutStorageKey);
|
|
||||||
|
if (EnableServerLayouts)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(layout);
|
||||||
|
await SignalRClient.PostAsync<bool>(AcSignalRTags.SaveGridLayoutTag, [UserLayoutStorageKey, json, GetLayoutUserId()]);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Server roaming is best-effort — the local copy is already persisted
|
||||||
|
Logger.Warning($"[{GetType().Name}] Server layout SAVE failed — the local copy is persisted; key: {UserLayoutStorageKey}; error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task LoadUserLayoutAsync()
|
public async Task LoadUserLayoutAsync()
|
||||||
{
|
{
|
||||||
var layout = await LoadLayoutFromLocalStorageAsync(UserLayoutStorageKey);
|
GridPersistentLayout? layout = null;
|
||||||
|
|
||||||
|
if (EnableServerLayouts)
|
||||||
|
{
|
||||||
|
var json = await GetServerLayoutJsonAsync();
|
||||||
|
|
||||||
|
if (!json.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
layout = JsonSerializer.Deserialize<GridPersistentLayout>(json!);
|
||||||
|
|
||||||
|
if (layout != null)
|
||||||
|
{
|
||||||
|
await SaveLayoutToLocalStorageAsync(layout, UserLayoutStorageKey);
|
||||||
|
await SaveLayoutToLocalStorageAsync(layout, AutomaticLayoutStorageKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Corrupt server copy — fall back to the local one
|
||||||
|
Logger.Warning($"[{GetType().Name}] Server layout copy could not be applied — falling back to the local copy; key: {UserLayoutStorageKey}; error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: local UserSave copy (offline, or no server copy yet)
|
||||||
|
layout ??= await LoadLayoutFromLocalStorageAsync(UserLayoutStorageKey);
|
||||||
|
|
||||||
if (layout != null)
|
if (layout != null)
|
||||||
{
|
{
|
||||||
LoadLayout(layout);
|
LoadLayout(layout);
|
||||||
|
|
@ -860,7 +930,13 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> HasUserLayoutAsync()
|
public async Task<bool> HasUserLayoutAsync()
|
||||||
{
|
{
|
||||||
return !(await GetStorageItem(UserLayoutStorageKey)).IsNullOrWhiteSpace();
|
// Local check only — server communication happens exclusively on user clicks (Save/Load)
|
||||||
|
if (!(await GetStorageItem(UserLayoutStorageKey)).IsNullOrWhiteSpace())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// With server layouts enabled we can't know without a round-trip — report true optimistically;
|
||||||
|
// LoadUserLayoutAsync no-ops when neither the server nor localStorage has a copy
|
||||||
|
return EnableServerLayouts;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -876,7 +952,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
{
|
{
|
||||||
// Force grid re-initialization by changing the render key
|
// Force grid re-initialization by changing the render key
|
||||||
_gridRenderKey = Guid.NewGuid();
|
_gridRenderKey = Guid.NewGuid();
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,30 @@ Splitter_GridOrder_Master_AutoSave_42 ← splitter pane size
|
||||||
|
|
||||||
| Method | Behavior |
|
| Method | Behavior |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `SaveUserLayoutAsync()` | Saves current layout to both UserSave AND AutoSave keys |
|
| `SaveUserLayoutAsync()` | Saves to the local UserSave key first (the AutoSave key is already current — `LayoutAutoSaving` fires on every user-made layout change), then (when `EnableServerLayouts`) pushes the layout JSON to the server |
|
||||||
| `LoadUserLayoutAsync()` | Loads from UserSave key (if exists) |
|
| `LoadUserLayoutAsync()` | Prefers the server copy (when `EnableServerLayouts`) — writing it back to the local UserSave + AutoSave keys so it survives a page refresh — and falls back to the local UserSave copy; then applies the layout |
|
||||||
| `ResetLayoutAsync()` | Removes AutoSave key, restores in-memory `_defaultLayoutJson` |
|
| `ResetLayoutAsync()` | Removes AutoSave key, restores in-memory `_defaultLayoutJson` (never touches the server copy) |
|
||||||
| `HasUserLayoutAsync()` | Checks if UserSave key exists in localStorage |
|
| `HasUserLayoutAsync()` | Local check; when `EnableServerLayouts` and no local copy exists, returns `true` optimistically — no server round-trip |
|
||||||
|
|
||||||
|
## Server Roaming (UserSave tier)
|
||||||
|
|
||||||
|
`EnableServerLayouts` (default `true`) roams the **UserSave** tier through the grid's own `SignalRClient`,
|
||||||
|
using the framework tags defined in `AcSignalRTags` (AyCode.Services):
|
||||||
|
|
||||||
|
| Tag | Value | Params (in order) | Returns |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `GetGridLayoutTag` | 90010 | `key` (string), `userId` (int) | layout JSON, or null |
|
||||||
|
| `SaveGridLayoutTag` | 90011 | `key`, `layoutJson`, `userId` | bool |
|
||||||
|
| `DeleteGridLayoutTag` | 90012 | `key`, `userId` | bool |
|
||||||
|
|
||||||
|
- The `key` is the full composite UserSave storage key (it already contains the user id); the server treats
|
||||||
|
it as an opaque string. `userId` comes from `GetLayoutUserId()`.
|
||||||
|
- **Server communication happens exclusively on user clicks** (Save/Load layout toolbar buttons) — never on
|
||||||
|
grid init, toolbar init, detail-row expand, or prerender.
|
||||||
|
- All server calls are **best-effort** (`try/catch`): offline, or when the server does not implement the
|
||||||
|
tags, behavior degrades to the local-only flow without errors.
|
||||||
|
- The server must implement the three tags; the storage schema is the consumer's choice.
|
||||||
|
- The `AutoSave` and `Splitter` tiers never touch the server.
|
||||||
|
|
||||||
## Persisted State
|
## Persisted State
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ These are bundled into a `SignalRCrudTags` during `OnInitializedAsync`. See `AyC
|
||||||
| `Caption` | `string` | `typeof(TDataItem).Name` | Grid title (shown in fullscreen header) |
|
| `Caption` | `string` | `typeof(TDataItem).Name` | Grid title (shown in fullscreen header) |
|
||||||
| `GridName` | `string` | `"{TDataItem.Name}Grid"` | Name used in log messages |
|
| `GridName` | `string` | `"{TDataItem.Name}Grid"` | Name used in log messages |
|
||||||
| `AutoSaveLayoutName` | `string?` | `"Grid{TDataItem.Name}"` | Base name for layout storage keys |
|
| `AutoSaveLayoutName` | `string?` | `"Grid{TDataItem.Name}"` | Base name for layout storage keys |
|
||||||
|
| `EnableServerLayouts` | `bool` | `true` | Roams the UserSave layout tier via the SignalR server endpoint (see `MGGRID_LAYOUT.md` → Server Roaming). Server calls happen only on the Save/Load layout buttons. |
|
||||||
|
|
||||||
## Event Callbacks
|
## Event Callbacks
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ The public contract exposed to companion components (toolbar, InfoPanel, wrapper
|
||||||
| `SaveUserLayoutAsync()` | `Task` | Save layout manually |
|
| `SaveUserLayoutAsync()` | `Task` | Save layout manually |
|
||||||
| `LoadUserLayoutAsync()` | `Task` | Load manually saved layout |
|
| `LoadUserLayoutAsync()` | `Task` | Load manually saved layout |
|
||||||
| `ResetLayoutAsync()` | `Task` | Reset to default layout |
|
| `ResetLayoutAsync()` | `Task` | Reset to default layout |
|
||||||
| `HasUserLayoutAsync()` | `Task<bool>` | Check if manual save exists |
|
| `HasUserLayoutAsync()` | `Task<bool>` | Check if manual save exists (optimistic `true` when server layouts are enabled) |
|
||||||
|
|
||||||
## Event Args Classes
|
## Event Args Classes
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue