From 2f606ccb2a7e082b4cd6e8d770fae902a4fa09e2 Mon Sep 17 00:00:00 2001 From: mergehez Date: Fri, 11 Oct 2024 00:00:18 +0200 Subject: [PATCH] Added feature: Merging props --- .../Extensions/InertiaExtensions.cs | 16 ++++- InertiaNetCore/Inertia.cs | 3 + InertiaNetCore/Models/InertiaPage.cs | 1 + InertiaNetCore/Models/InertiaProps.cs | 12 ++-- InertiaNetCore/Response.cs | 65 +++++++++++++------ InertiaNetCore/ResponseFactory.cs | 2 + InertiaNetCore/Utils/DeferProp.cs | 1 + InertiaNetCore/Utils/Interfaces.cs | 11 ++-- InertiaNetCore/Utils/LazyProp.cs | 2 +- InertiaNetCore/Utils/MergeProp.cs | 19 ++++++ README.md | 2 +- 11 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 InertiaNetCore/Utils/MergeProp.cs diff --git a/InertiaNetCore/Extensions/InertiaExtensions.cs b/InertiaNetCore/Extensions/InertiaExtensions.cs index 450d3a4..c6a33ef 100644 --- a/InertiaNetCore/Extensions/InertiaExtensions.cs +++ b/InertiaNetCore/Extensions/InertiaExtensions.cs @@ -6,14 +6,26 @@ namespace InertiaNetCore.Extensions; internal static class InertiaExtensions { - internal static List GetPartialData(this ActionContext context) + private static List GetInertiaHeaderData(this ActionContext context, string header) { - return context.HttpContext.Request.Headers.TryGetValue("X-Inertia-Partial-Data", out var data) + return context.HttpContext.Request.Headers.TryGetValue(header, out var data) ? data.FirstOrDefault()?.Split(",") .Where(s => !string.IsNullOrEmpty(s)) .ToList() ?? [] : []; } + internal static List GetPartialData(this ActionContext context) + { + return context.GetInertiaHeaderData("X-Inertia-Partial-Data"); + } + internal static List GetInertiaExcepts(this ActionContext context) + { + return context.GetInertiaHeaderData("X-Inertia-Partial-Except"); + } + internal static List GetInertiaResetData(this ActionContext context) + { + return context.GetInertiaHeaderData("X-Inertia-Reset"); + } internal static bool IsInertiaPartialComponent(this ActionContext context, string component) { diff --git a/InertiaNetCore/Inertia.cs b/InertiaNetCore/Inertia.cs index 827570a..4bcafee 100644 --- a/InertiaNetCore/Inertia.cs +++ b/InertiaNetCore/Inertia.cs @@ -44,4 +44,7 @@ public static class Inertia public static AlwaysProp Always(Func callback) => _factory.Always(callback); public static AlwaysProp Always(Func> callback) => _factory.Always(callback); + + public static MergeProp Merge(Func callback) => _factory.Merge(callback); + public static MergeProp Merge(Func> callback) => _factory.Merge(callback); } diff --git a/InertiaNetCore/Models/InertiaPage.cs b/InertiaNetCore/Models/InertiaPage.cs index 1cb5c8d..a936893 100644 --- a/InertiaNetCore/Models/InertiaPage.cs +++ b/InertiaNetCore/Models/InertiaPage.cs @@ -4,6 +4,7 @@ public readonly record struct InertiaPage { public required InertiaProps Props { get; init; } public required Dictionary> DeferredProps { get; init; } + public required List MergeProps { get; init; } public required string Component { get; init; } public required string? Version { get; init; } public required string Url { get; init; } diff --git a/InertiaNetCore/Models/InertiaProps.cs b/InertiaNetCore/Models/InertiaProps.cs index 37d8e11..03a90e8 100644 --- a/InertiaNetCore/Models/InertiaProps.cs +++ b/InertiaNetCore/Models/InertiaProps.cs @@ -4,19 +4,19 @@ namespace InertiaNetCore.Models; public class InertiaProps : Dictionary { - internal async Task ToProcessedProps(List? partials) + internal async Task ToProcessedProps(bool isPartial, List partials, List excepts) { var props = new InertiaProps(); - if(partials is not null && partials.Count == 0) - partials = null; - foreach (var (key, value) in this) { - if(partials is null && value is IIgnoreFirstProp) + if(isPartial && excepts.Contains(key, StringComparer.InvariantCultureIgnoreCase)) + continue; + + if(!isPartial && value is IIgnoreFirstProp) continue; - if(partials is not null && value is not IAlwaysProp && !partials.Contains(key, StringComparer.InvariantCultureIgnoreCase)) + if(isPartial && value is not IAlwaysProp && !partials.Contains(key, StringComparer.InvariantCultureIgnoreCase)) continue; props.Add(key, value switch diff --git a/InertiaNetCore/Response.cs b/InertiaNetCore/Response.cs index de48bc1..67d5436 100644 --- a/InertiaNetCore/Response.cs +++ b/InertiaNetCore/Response.cs @@ -13,7 +13,7 @@ public class Response(string component, InertiaProps props, string? version, Ine : IActionResult { private IDictionary? _viewData; - + public async Task ExecuteResultAsync(ActionContext context) { var page = new InertiaPage @@ -23,8 +23,9 @@ public async Task ExecuteResultAsync(ActionContext context) Url = context.HttpContext.RequestedUri(), Props = await GetFinalProps(context), DeferredProps = GetDeferredProps(context), + MergeProps = GetMergeProps(context) }; - + if (!context.HttpContext.IsInertiaRequest()) { var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), context.ModelState) @@ -45,33 +46,36 @@ public async Task ExecuteResultAsync(ActionContext context) context.HttpContext.Response.Headers.Append("X-Inertia", "true"); context.HttpContext.Response.Headers.Append("Vary", "Accept"); context.HttpContext.Response.StatusCode = 200; - - var jsonResult = new JsonResult(page, jsonSerializerOptions); + + var jsonResult = new JsonResult(page, options.JsonSerializerOptions); await jsonResult.ExecuteResultAsync(context); } } - + private async Task GetFinalProps(ActionContext context) { - var partials = context.IsInertiaPartialComponent(component) ? context.GetPartialData() : null; + var isPartial = context.IsInertiaPartialComponent(component); + var partials = isPartial ? context.GetPartialData() : []; + var excepts = isPartial ? context.GetInertiaExcepts() : []; + var shared = context.HttpContext.Features.Get(); - var flash = context.HttpContext.Features.Get() + var flash = context.HttpContext.Features.Get() ?? InertiaFlashMessages.FromSession(context.HttpContext); var errors = GetErrors(context); - - var finalProps = await props.ToProcessedProps(partials); - + + var finalProps = await props.ToProcessedProps(isPartial, partials, excepts); + finalProps = finalProps .Merge(shared?.GetData()) .AddTimeStamp() .AddFlash(flash.GetData()) .AddErrors(errors); - + flash.Clear(false); - + return finalProps; } - + private Dictionary> GetDeferredProps(ActionContext context) { if (context.IsInertiaPartialComponent(component)) @@ -95,7 +99,26 @@ private Dictionary> GetDeferredProps(ActionContext context) g => g.Select(x => x.Key).ToList() ); } - + + private List GetMergeProps(ActionContext context) + { + var resetData = context.GetInertiaResetData(); + + var tmp = new Dictionary(); + + foreach (var (key, value) in props) + { + if (value is IMergeableProp { Merge: true } && !resetData.Contains(key)) + tmp[key] = key; + } + + // apply json serialization options to dictionary keys before grouping them + var jsonOptions = options.JsonSerializerOptions as JsonSerializerOptions; + tmp = JsonSerializer.Deserialize>(JsonSerializer.Serialize(tmp, jsonOptions), jsonOptions); + + return tmp!.Select(prop => prop.Key).ToList(); + } + private static Dictionary GetErrors(ActionContext context) { var sessionErrors = context.HttpContext.Session.GetString("errors"); @@ -103,18 +126,18 @@ private static Dictionary GetErrors(ActionContext context) { var errors = JsonSerializer.Deserialize>(sessionErrors); context.HttpContext.Session.Remove("errors"); - - if(errors is not null) + + if (errors is not null) return errors; } - - if (context.ModelState.IsValid) + + if (context.ModelState.IsValid) return []; - + return context.ModelState.ToDictionary( kv => kv.Key, kv => kv.Value?.Errors.FirstOrDefault()?.ErrorMessage ?? "" - ); + ); } public Response WithViewData(IDictionary viewData) @@ -122,4 +145,4 @@ public Response WithViewData(IDictionary viewData) _viewData = viewData; return this; } -} +} \ No newline at end of file diff --git a/InertiaNetCore/ResponseFactory.cs b/InertiaNetCore/ResponseFactory.cs index 84434b0..9df54d3 100644 --- a/InertiaNetCore/ResponseFactory.cs +++ b/InertiaNetCore/ResponseFactory.cs @@ -117,4 +117,6 @@ public void Flash(string key, string? value) public DeferredProp Defer(Func> callback, string? group) => new(callback, group); public AlwaysProp Always(Func callback) => new(callback); public AlwaysProp Always(Func> callback) => new(callback); + public MergeProp Merge(Func callback) => new(callback); + public MergeProp Merge(Func> callback) => new(callback); } diff --git a/InertiaNetCore/Utils/DeferProp.cs b/InertiaNetCore/Utils/DeferProp.cs index 2d083da..ea252e7 100644 --- a/InertiaNetCore/Utils/DeferProp.cs +++ b/InertiaNetCore/Utils/DeferProp.cs @@ -6,6 +6,7 @@ namespace InertiaNetCore.Utils; /// public class DeferredProp : InvokableProp, IDeferredProp { + public bool Merge { get; set; } public string? Group { get; } public DeferredProp(Func callback, string? group) : base(callback) diff --git a/InertiaNetCore/Utils/Interfaces.cs b/InertiaNetCore/Utils/Interfaces.cs index 8535f46..cabbccc 100644 --- a/InertiaNetCore/Utils/Interfaces.cs +++ b/InertiaNetCore/Utils/Interfaces.cs @@ -17,9 +17,12 @@ internal interface IAlwaysProp; internal interface IIgnoreFirstProp; -internal interface ILazyProp : IIgnoreFirstProp; - -internal interface IDeferredProp : IIgnoreFirstProp +internal interface IDeferredProp : IIgnoreFirstProp, IMergeableProp { string? Group { get; } -} \ No newline at end of file +} + +public interface IMergeableProp +{ + bool Merge { get; set; } +} diff --git a/InertiaNetCore/Utils/LazyProp.cs b/InertiaNetCore/Utils/LazyProp.cs index b1f338c..3880b26 100644 --- a/InertiaNetCore/Utils/LazyProp.cs +++ b/InertiaNetCore/Utils/LazyProp.cs @@ -5,7 +5,7 @@ namespace InertiaNetCore.Utils; /// OPTIONALLY included on partial reloads (you should call router.reload({ only: ["propName"] }))
/// ONLY evaluated when needed /// -public class LazyProp : InvokableProp, ILazyProp +public class LazyProp : InvokableProp, IIgnoreFirstProp { public LazyProp(Func callback) : base(callback) { diff --git a/InertiaNetCore/Utils/MergeProp.cs b/InertiaNetCore/Utils/MergeProp.cs new file mode 100644 index 0000000..7c17eca --- /dev/null +++ b/InertiaNetCore/Utils/MergeProp.cs @@ -0,0 +1,19 @@ +namespace InertiaNetCore.Utils; + +/// +/// By default, Inertia overwrites props with the same name when reloading a page. +/// However, there are instances, such as pagination or infinite scrolling, where that is not the desired behavior. +/// In these cases, you can merge props instead of overwriting them. +/// +public class MergeProp : InvokableProp, IMergeableProp +{ + public bool Merge { get; set; } = true; + + public MergeProp(Func callback) : base(callback) + { + } + + public MergeProp(Func> callbackAsync) : base(callbackAsync) + { + } +} \ No newline at end of file diff --git a/README.md b/README.md index fc0c4ec..3825ef2 100644 --- a/README.md +++ b/README.md @@ -334,5 +334,5 @@ export default defineConfig({ ## Work in progress - [x] Deferred props -- [ ] Merging props +- [x] Merging props - [ ] History encryption \ No newline at end of file