diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
index 93c21c9..4f95af2 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
@@ -26,7 +26,7 @@ public interface IMgGridBase : IGrid
bool IsSyncing { get; }
string Caption { get; set; }
-
+
///
/// Current edit state of the grid (None, New, Edit)
///
@@ -51,7 +51,7 @@ public interface IMgGridBase : IGrid
/// Navigates to the next row in the grid
///
void StepNextRow();
-
+
///
/// InfoPanel instance for displaying row details (from wrapper)
///
@@ -73,22 +73,24 @@ public interface IMgGridBase : IGrid
void ToggleFullscreen();
///
- /// 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
///
Task SaveUserLayoutAsync();
///
- /// Loads layout from user storage (manual load)
+ /// Loads layout from user storage (manual load); prefers the server copy when server layouts are enabled
///
Task LoadUserLayoutAsync();
///
- /// 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.
///
Task ResetLayoutAsync();
///
- /// 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)
///
Task HasUserLayoutAsync();
}
@@ -153,7 +155,7 @@ public abstract class MgGridBase
/// InfoPanel instance for displaying row details.
/// First checks own wrapper, then gets InfoPanel from root grid.
@@ -165,16 +167,16 @@ public abstract class MgGridBase
public bool IsFullscreen => GridWrapper?.IsFullscreen ?? _isStandaloneFullscreen;
@@ -195,7 +197,7 @@ public abstract class MgGridBase
+ /// 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).
+ ///
+ [Parameter] public bool EnableServerLayouts { get; set; } = true;
+
[Parameter] public TLoggerClient Logger { get; set; }
[Parameter] public string GridName { get; set; }
[Parameter] public IId? ParentDataItem { get; set; }
@@ -272,7 +282,7 @@ public abstract class MgGridBase
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
///
@@ -360,7 +370,7 @@ public abstract class MgGridBase? referenceList)
{
_dataSource?.SetWorkingReferenceList(referenceList);
-
+
SetGridData(referenceList);
}
@@ -391,21 +401,21 @@ public abstract class MgGridBase SetGridData(_dataSource!.GetReferenceInnerList()));
@@ -498,7 +508,7 @@ public abstract class MgGridBase typeof(TDataItem).GetProperty(propertyName);
-
+
protected virtual async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
{
var editModel = (e.EditModel as TDataItem)!;
@@ -546,11 +556,11 @@ public abstract class MgGridBase
+ /// 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.
+ ///
+ private async Task GetServerLayoutJsonAsync()
+ {
+ try
+ {
+ return await SignalRClient.GetByIdAsync(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)
{
// Save the default layout before loading any saved layout
@@ -786,7 +815,7 @@ public abstract class MgGridBase(json);
}
catch
@@ -829,13 +858,54 @@ public abstract class MgGridBase(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}");
+ }
+ }
}
///
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(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)
{
LoadLayout(layout);
@@ -860,7 +930,13 @@ public abstract class MgGridBase
public async Task 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
@@ -876,7 +952,7 @@ public abstract class MgGridBase` | Check if manual save exists |
+| `HasUserLayoutAsync()` | `Task` | Check if manual save exists (optimistic `true` when server layouts are enabled) |
## Event Args Classes