From 99e56c73a1af3a6f764b036465dd0fa31386abef Mon Sep 17 00:00:00 2001 From: Jamie Pollock Date: Fri, 26 Jan 2024 15:21:55 -0500 Subject: [PATCH] feat (Image THRs): bulk rework of drop-image drop-image is no more! It is now replaced by tag helpers that use drop-model instead. - picture[drop-model] will be used for pictures - picture[drop-model] > img will be used for controlling the markup of an img tag within a picture. This is an optional element as the parent tag will know to add an img if one is missing - img[drop-model] will be used when no picture is required --- .../Images/DropImageTagHelperRendererBase.cs | 211 ++---------------- .../DropPictureImageTagHelperRendererBase.cs | 58 +++++ .../DropPictureTagHelperRendererBase.cs | 183 +++++++++++++++ .../Images/IDropImageTagHelperRenderer.cs | 2 +- .../IDropPictureImageTagHelperRenderer.cs | 12 + .../Images/IDropPictureTagHelperRenderer.cs | 12 + .../RhythmDropBuilderExtensions.cs | 26 +++ ...efaultDropPictureImageTagHelperRenderer.cs | 11 + .../DefaultDropPictureTagHelperRenderer.cs | 11 + .../RhythmDropBuilderExtensions.cs | 22 ++ .../TagHelpers/DropImageTagHelper.cs | 8 +- .../TagHelpers/DropImageTagHelperBase.cs | 41 ++++ .../TagHelpers/DropPictureImageTagHelper.cs | 59 +++++ .../TagHelpers/DropPictureTagHelper.cs | 52 +++++ .../DefaultImageTagHelperRendererTests.cs | 22 -- 15 files changed, 510 insertions(+), 220 deletions(-) create mode 100644 src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureImageTagHelperRendererBase.cs create mode 100644 src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureTagHelperRendererBase.cs create mode 100644 src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureImageTagHelperRenderer.cs create mode 100644 src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureTagHelperRenderer.cs create mode 100644 src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureImageTagHelperRenderer.cs create mode 100644 src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureTagHelperRenderer.cs create mode 100644 src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelperBase.cs create mode 100644 src/Rhythm.Drop.Web/TagHelpers/DropPictureImageTagHelper.cs create mode 100644 src/Rhythm.Drop.Web/TagHelpers/DropPictureTagHelper.cs diff --git a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropImageTagHelperRendererBase.cs b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropImageTagHelperRendererBase.cs index 9b49d74..5f89784 100644 --- a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropImageTagHelperRendererBase.cs +++ b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropImageTagHelperRendererBase.cs @@ -1,234 +1,59 @@ namespace Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; -using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Razor.TagHelpers; using Rhythm.Drop.Models.Images; using Rhythm.Drop.Web.Infrastructure.TagHelperRenderers; using System.Threading.Tasks; /// -/// A base class for rendering when used in a . +/// A base implementation for rendering a when used in a as an img HTML tag (without a picture HTML tag). /// public abstract class DropImageTagHelperRendererBase : TagHelperRendererBase, IDropImageTagHelperRenderer { /// - protected override async Task RenderModelAsync(DropImageTagHelperRendererContext model, TagHelperContext context, TagHelperOutput output) + protected override Task RenderModelAsync(DropImageTagHelperRendererContext model, TagHelperContext context, TagHelperOutput output) { if (model.Image is null) { output.SuppressOutput(); - return; + return Task.CompletedTask; } - await Task.Run(() => - { - var image = model.Image; - - if (ShouldRenderOutputAsPicture(image, context, output)) - { - RenderOutputAsPicture(image, model.LoadingMode, context, output); - } - else - { - RenderOutputAsImg(image, model.LoadingMode, context, output); - } - }); - } - - /// - /// Determines whether the output should be rendered as a picture HTML tag or a single img HTML tag. - /// - /// The image. - /// The context. - /// The output. - /// A . If the output should be rendered a picture HTML tag. Otherwise it should be rendered a single img HTML tag. - /// - protected virtual bool ShouldRenderOutputAsPicture(IImage image, TagHelperContext context, TagHelperOutput output) - { - return image.Sources.Count > 0; - } - - /// - /// Renders a and as a picture HTML tag. - /// - /// The image. - /// The loading mode. - /// The context. - /// The output. - /// - /// This is intended to be a developer friendly extension point to modify the a picture tag. - /// This can be replaced entirely or the base method can be used as a starting point for modifications. - /// - protected virtual void RenderOutputAsPicture(IImage image, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) - { - output.TagName = "picture"; - output.TagMode = TagMode.StartTagAndEndTag; - - foreach (var source in image.Sources) - { - var sourceHtml = BuildImageSourceHtmlContent(source, renderMode, context, output); - - output.Content.AppendHtml(sourceHtml); - } + var image = model.Image; - var imgHtml = BuildImgHtmlContent(image, renderMode, context, output); - output.Content.AppendHtml(imgHtml); - } - - /// - /// Renders a and as a single img HTML tag. - /// - /// The image. - /// The loading mode. - /// The context. - /// The output. - /// - /// This is intended to be a developer friendly extension point to modify the img tag when no picture tag is present. - /// This can be replaced entirely or the base method can be used as a starting point for modifications. - /// - protected virtual void RenderOutputAsImg(IImage image, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) - { output.TagName = "img"; - output.Attributes.SetAttribute("src", image.Url); - output.Attributes.SetAttribute("alt", image.AltText); - - if (renderMode is LoadingMode.Lazy) - { - output.Attributes.SetAttribute("loading", "lazy"); - } - - if (image.Width > 0) - { - output.Attributes.SetAttribute("width", image.Width); - } - - if (image.Height > 0) - { - output.Attributes.SetAttribute("height", image.Height); - } - output.TagMode = TagMode.SelfClosing; - } - /// - /// Creates a used by . - /// - /// The image. - /// The loading mode. - /// The context. - /// The output. - /// A . - /// - /// This is intended to be a developer friendly extension point to modify the img tag used by a picture tag. - /// This can be replaced entirely or the base method can be used as a starting point for modifications. - /// - protected virtual TagBuilder CreateImgTagBuilder(IImage image, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) - { - var tagBuilder = new TagBuilder("img"); - tagBuilder.Attributes.Add("src", image.Url); - tagBuilder.Attributes.Add("alt", image.AltText); - - if (renderMode is LoadingMode.Lazy) - { - tagBuilder.Attributes.Add("loading", "lazy"); - } - - if (image.Width > 0) - { - tagBuilder.Attributes.Add("width", image.Width.ToString()); - } + SetTagAttributes(image, model.LoadingMode, context, output); - if (image.Height > 0) - { - tagBuilder.Attributes.Add("height", image.Height.ToString()); - } - - return tagBuilder; + return Task.CompletedTask; } /// - /// Creates a used by . + /// Sets the attributes of the tag helper output. /// - /// The image source. - /// The loading mode. + /// The model. + /// The loading mode. /// The context. /// The output. - /// A . - /// - /// This is intended to be a developer friendly extension point to modify the source tag used by a picture tag. - /// This can be replaced entirely or the base method can be used as a starting point for modifications. - /// - protected virtual TagBuilder CreateImageSourceTagBuilder(IImageSource source, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) + protected virtual void SetTagAttributes(IImage model, LoadingMode loadingMode, TagHelperContext context, TagHelperOutput output) { - var tagBuilder = new TagBuilder("source"); - - tagBuilder.Attributes.Add("srcset", source.SourceSet.ToMarkupString()); - - if (source.MediaQuery is not null) - { - tagBuilder.Attributes.Add("media", source.MediaQuery.ToMarkupString()); - } + output.Attributes.SetAttribute("src", model.Url); + output.Attributes.SetAttribute("alt", model.AltText); - if (string.IsNullOrEmpty(source.MimeType) is false) + if (loadingMode is LoadingMode.Lazy) { - tagBuilder.Attributes.Add("type", source.MimeType); + output.Attributes.SetAttribute("loading", "lazy"); } - if (source.Width > 0) + if (model.Width > 0) { - tagBuilder.Attributes.Add("width", source.Width.ToString()); + output.Attributes.SetAttribute("width", model.Width); } - if (source.Height > 0) + if (model.Height > 0) { - tagBuilder.Attributes.Add("height", source.Height.ToString()); + output.Attributes.SetAttribute("height", model.Height); } - - return tagBuilder; - } - - /// - /// Builds the required by a img HTML tag when rendered within an picture HTML tag. - /// - /// The image. - /// The loading mode. - /// The context. - /// The output. - /// A . - /// - /// This is intended to be a developer friendly extension point to modify the source tag used by a picture tag. - /// This should only bn replaced entirely if you do not intend to use the output from . - /// - protected IHtmlContent BuildImgHtmlContent(IImage image, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) - { - var tagBuilder = CreateImgTagBuilder(image, renderMode, context, output); - - return tagBuilder.RenderSelfClosingTag(); - } - - /// - /// Builds the required by a source HTML tag when rendered within an picture HTML tag. - /// - /// The image source. - /// The loading mode. - /// The context. - /// The output. - /// A . - /// - /// This is intended to be a developer friendly extension point to modify the source tag used by a picture tag. - /// This should only bn replaced entirely if you do not intend to use the output from . - /// - protected IHtmlContent BuildImageSourceHtmlContent(IImageSource source, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) - { - var tagBuilder = CreateImageSourceTagBuilder(source, renderMode, context, output); - - return tagBuilder.RenderSelfClosingTag(); - } - - /// - protected override async Task RenderNullOrInvalidAsync(TagHelperContext context, TagHelperOutput output) - { - await Task.Run(output.SuppressOutput); } } \ No newline at end of file diff --git a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureImageTagHelperRendererBase.cs b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureImageTagHelperRendererBase.cs new file mode 100644 index 0000000..01e6c3a --- /dev/null +++ b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureImageTagHelperRendererBase.cs @@ -0,0 +1,58 @@ +namespace Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; + +using Microsoft.AspNetCore.Razor.TagHelpers; +using Rhythm.Drop.Models.Images; +using System.Threading.Tasks; + +/// +/// A base implementation for rendering a when used in a as an img HTML tag within a picture HTML tag. +/// +public abstract class DropPictureImageTagHelperRendererBase : TagHelperRendererBase, IDropPictureImageTagHelperRenderer +{ + /// + protected override Task RenderModelAsync(DropImageTagHelperRendererContext model, TagHelperContext context, TagHelperOutput output) + { + if (model.Image is null) + { + output.SuppressOutput(); + return Task.CompletedTask; + } + + var image = model.Image; + + output.TagName = "img"; + output.TagMode = TagMode.SelfClosing; + + SetTagAttributes(image, model.LoadingMode, context, output); + + return Task.CompletedTask; + } + + /// + /// Sets the attributes of the tag helper output. + /// + /// The model. + /// The loading mode. + /// The context. + /// The output. + protected virtual void SetTagAttributes(IImage model, LoadingMode loadingMode, TagHelperContext context, TagHelperOutput output) + { + output.Attributes.SetAttribute("src", model.Url); + output.Attributes.SetAttribute("alt", model.AltText); + + if (loadingMode is LoadingMode.Lazy) + { + output.Attributes.SetAttribute("loading", "lazy"); + } + + if (model.Width > 0) + { + output.Attributes.SetAttribute("width", model.Width); + } + + if (model.Height > 0) + { + output.Attributes.SetAttribute("height", model.Height); + } + } +} \ No newline at end of file diff --git a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureTagHelperRendererBase.cs b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureTagHelperRendererBase.cs new file mode 100644 index 0000000..d061734 --- /dev/null +++ b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/DropPictureTagHelperRendererBase.cs @@ -0,0 +1,183 @@ +namespace Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; + +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Rhythm.Drop.Models.Images; +using System.Threading.Tasks; + +/// +/// A base implementation for rendering a when used in a as a picture HTML tag. +/// +public abstract class DropPictureTagHelperRendererBase : TagHelperRendererBase, IDropPictureTagHelperRenderer +{ + /// + protected override async Task RenderModelAsync(DropImageTagHelperRendererContext model, TagHelperContext context, TagHelperOutput output) + { + if (model.Image is null) + { + output.SuppressOutput(); + return; + } + + var image = model.Image; + var loadingMode = model.LoadingMode; + + output.TagName = "picture"; + output.TagMode = TagMode.StartTagAndEndTag; + + AppendSourcesContent(image, loadingMode, context, output); + await AppendImageContentAsync(image, loadingMode, context, output); + } + + /// + /// Appends the collection to the tag helper output. + /// + /// The model. + /// The loading mode. + /// The context. + /// The output. + protected virtual void AppendSourcesContent(IImage model, LoadingMode loadingMode, TagHelperContext context, TagHelperOutput output) + { + foreach (var source in model.Sources) + { + var sourceHtml = BuildImageSourceHtmlContent(source, loadingMode, context, output); + output.PreContent.AppendHtml(sourceHtml); + } + } + + /// + /// Appends the image HTML tag to the tag helper output if one doesn't already exist. + /// + /// The model. + /// The loading mode. + /// The context. + /// The output. + /// A . + protected virtual async Task AppendImageContentAsync(IImage model, LoadingMode loadingMode, TagHelperContext context, TagHelperOutput output) + { + var childContent = await output.GetChildContentAsync(); + if (childContent.IsEmptyOrWhiteSpace is false) + { + return; + } + + var imgHtml = BuildImgHtmlContent(model, loadingMode, context, output); + output.Content.AppendHtml(imgHtml); + } + + /// + /// Creates a used by . + /// + /// The image. + /// The loading mode. + /// The context. + /// The output. + /// A . + /// + /// This is intended to be a developer friendly extension point to modify the img tag used by a picture tag. + /// This can be replaced entirely or the base method can be used as a starting point for modifications. + /// + protected virtual TagBuilder CreateImgTagBuilder(IImage image, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) + { + var tagBuilder = new TagBuilder("img"); + tagBuilder.Attributes.Add("src", image.Url); + tagBuilder.Attributes.Add("alt", image.AltText); + + if (renderMode is LoadingMode.Lazy) + { + tagBuilder.Attributes.Add("loading", "lazy"); + } + + if (image.Width > 0) + { + tagBuilder.Attributes.Add("width", image.Width.ToString()); + } + + if (image.Height > 0) + { + tagBuilder.Attributes.Add("height", image.Height.ToString()); + } + + return tagBuilder; + } + + /// + /// Creates a used by . + /// + /// The image source. + /// The loading mode. + /// The context. + /// The output. + /// A . + /// + /// This is intended to be a developer friendly extension point to modify the source tag used by a picture tag. + /// This can be replaced entirely or the base method can be used as a starting point for modifications. + /// + protected virtual TagBuilder CreateImageSourceTagBuilder(IImageSource source, LoadingMode loadingMode, TagHelperContext context, TagHelperOutput output) + { + var tagBuilder = new TagBuilder("source"); + + tagBuilder.Attributes.Add("srcset", source.SourceSet.ToMarkupString()); + + if (source.MediaQuery is not null) + { + tagBuilder.Attributes.Add("media", source.MediaQuery.ToMarkupString()); + } + + if (string.IsNullOrEmpty(source.MimeType) is false) + { + tagBuilder.Attributes.Add("type", source.MimeType); + } + + if (source.Width > 0) + { + tagBuilder.Attributes.Add("width", source.Width.ToString()); + } + + if (source.Height > 0) + { + tagBuilder.Attributes.Add("height", source.Height.ToString()); + } + + return tagBuilder; + } + + /// + /// Builds the required by a img HTML tag when rendered within an picture HTML tag. + /// + /// The image. + /// The loading mode. + /// The context. + /// The output. + /// A . + /// + /// This is intended to be a developer friendly extension point to modify the source tag used by a picture tag. + /// This should only bn replaced entirely if you do not intend to use the output from . + /// + protected IHtmlContent BuildImgHtmlContent(IImage image, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output) + { + var tagBuilder = CreateImgTagBuilder(image, renderMode, context, output); + + return tagBuilder.RenderSelfClosingTag(); + } + + /// + /// Builds the required by a source HTML tag when rendered within an picture HTML tag. + /// + /// The image source. + /// The loading mode. + /// The context. + /// The output. + /// A . + /// + /// This is intended to be a developer friendly extension point to modify the source tag used by a picture tag. + /// This should only bn replaced entirely if you do not intend to use the output from . + /// + protected IHtmlContent BuildImageSourceHtmlContent(IImageSource source, LoadingMode loadingMode, TagHelperContext context, TagHelperOutput output) + { + var tagBuilder = CreateImageSourceTagBuilder(source, loadingMode, context, output); + + return tagBuilder.RenderSelfClosingTag(); + } +} \ No newline at end of file diff --git a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropImageTagHelperRenderer.cs b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropImageTagHelperRenderer.cs index 5349e4d..f245195 100644 --- a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropImageTagHelperRenderer.cs +++ b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropImageTagHelperRenderer.cs @@ -5,7 +5,7 @@ using Rhythm.Drop.Web.Infrastructure.TagHelperRenderers; /// -/// A contract for rendering when used in a . +/// A contract for rendering a when used in a as an img HTML tag (without a picture HTML tag). /// /// This contract exists for dependency injection purposes. Check out the abstract implementations for a more guided implementation. public interface IDropImageTagHelperRenderer : ITagHelperRenderer diff --git a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureImageTagHelperRenderer.cs b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureImageTagHelperRenderer.cs new file mode 100644 index 0000000..3ebd39f --- /dev/null +++ b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureImageTagHelperRenderer.cs @@ -0,0 +1,12 @@ +namespace Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; + +using Microsoft.AspNetCore.Razor.TagHelpers; +using Rhythm.Drop.Models.Images; + +/// +/// A contract for rendering a when used in a as an img HTML tag within a picture HTML tag. +/// +/// This contract exists for dependency injection purposes. Check out the abstract implementations for a more guided implementation. +public interface IDropPictureImageTagHelperRenderer : ITagHelperRenderer +{ +} \ No newline at end of file diff --git a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureTagHelperRenderer.cs b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureTagHelperRenderer.cs new file mode 100644 index 0000000..2878f3e --- /dev/null +++ b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/Images/IDropPictureTagHelperRenderer.cs @@ -0,0 +1,12 @@ +namespace Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; + +using Microsoft.AspNetCore.Razor.TagHelpers; +using Rhythm.Drop.Models.Images; + +/// +/// A contract for rendering a when used in a as a picture HTML tag. +/// +/// This contract exists for dependency injection purposes. Check out the abstract implementations for a more guided implementation. +public interface IDropPictureTagHelperRenderer : ITagHelperRenderer +{ +} \ No newline at end of file diff --git a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/RhythmDropBuilderExtensions.cs b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/RhythmDropBuilderExtensions.cs index 5426518..ee245cd 100644 --- a/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/RhythmDropBuilderExtensions.cs +++ b/src/Rhythm.Drop.Web.Infrastructure/TagHelperRenderers/RhythmDropBuilderExtensions.cs @@ -62,4 +62,30 @@ public static IRhythmDropBuilder SetDropImageTagHelperRenderer + /// Sets the drop picture tag helper renderer. + /// + /// The current builder. + /// The type of the new drop picture tag helper renderer. + /// Returns the current . + public static IRhythmDropBuilder SetDropPictureTagHelperRenderer(this IRhythmDropBuilder builder) where TDropPictureTagHelperRenderer : class, IDropPictureTagHelperRenderer + { + builder.Services.Replace(ServiceLifetime.Scoped); + + return builder; + } + + /// + /// Sets the drop picture image tag helper renderer. + /// + /// The current builder. + /// The type of the new drop picture image tag helper renderer. + /// Returns the current . + public static IRhythmDropBuilder SetDropPictureImageTagHelperRenderer(this IRhythmDropBuilder builder) where TDropPictureImageTagHelperRenderer : class, IDropPictureImageTagHelperRenderer + { + builder.Services.Replace(ServiceLifetime.Scoped); + + return builder; + } } \ No newline at end of file diff --git a/src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureImageTagHelperRenderer.cs b/src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureImageTagHelperRenderer.cs new file mode 100644 index 0000000..576f547 --- /dev/null +++ b/src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureImageTagHelperRenderer.cs @@ -0,0 +1,11 @@ +namespace Rhythm.Drop.Web.TagHelperRenderers.Images; + +using Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; + +/// +/// The default implementation of . +/// +/// This implementation should cover most scenarios but can be replaced if needed on a project-by-project basis. +internal sealed class DefaultDropPictureImageTagHelperRenderer : DropPictureImageTagHelperRendererBase +{ +} diff --git a/src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureTagHelperRenderer.cs b/src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureTagHelperRenderer.cs new file mode 100644 index 0000000..d3775ea --- /dev/null +++ b/src/Rhythm.Drop.Web/TagHelperRenderers/Images/DefaultDropPictureTagHelperRenderer.cs @@ -0,0 +1,11 @@ +namespace Rhythm.Drop.Web.TagHelperRenderers.Images; + +using Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; + +/// +/// The default implementation of . +/// +/// This implementation should cover most scenarios but can be replaced if needed on a project-by-project basis. +internal sealed class DefaultDropPictureTagHelperRenderer : DropPictureTagHelperRendererBase +{ +} \ No newline at end of file diff --git a/src/Rhythm.Drop.Web/TagHelperRenderers/RhythmDropBuilderExtensions.cs b/src/Rhythm.Drop.Web/TagHelperRenderers/RhythmDropBuilderExtensions.cs index 46d8d35..e485d92 100644 --- a/src/Rhythm.Drop.Web/TagHelperRenderers/RhythmDropBuilderExtensions.cs +++ b/src/Rhythm.Drop.Web/TagHelperRenderers/RhythmDropBuilderExtensions.cs @@ -23,6 +23,8 @@ public static IRhythmDropBuilder AddTagHelperRenderers(this IRhythmDropBuilder b .SetDefaultDropAttributesTagHelperRenderer() .SetDefaultDropComponentsTagHelperRenderer() .SetDefaultDropImageTagHelperRenderer() + .SetDefaultDropPictureTagHelperRenderer() + .SetDefaultDropPictureImageTagHelperRenderer() .SetDefaultDropLinkTagHelperRenderer(); } @@ -65,4 +67,24 @@ public static IRhythmDropBuilder SetDefaultDropImageTagHelperRenderer(this IRhyt { return builder.SetDropImageTagHelperRenderer(); } + + /// + /// Sets the default drop picture tag helper renderer. + /// + /// The current builder. + /// Returns the current . + public static IRhythmDropBuilder SetDefaultDropPictureTagHelperRenderer(this IRhythmDropBuilder builder) + { + return builder.SetDropPictureTagHelperRenderer(); + } + + /// + /// Sets the default drop picture image tag helper renderer. + /// + /// The current builder. + /// Returns the current . + public static IRhythmDropBuilder SetDefaultDropPictureImageTagHelperRenderer(this IRhythmDropBuilder builder) + { + return builder.SetDropPictureImageTagHelperRenderer(); + } } \ No newline at end of file diff --git a/src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelper.cs b/src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelper.cs index 407fb59..acaa705 100644 --- a/src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelper.cs +++ b/src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelper.cs @@ -10,8 +10,8 @@ /// A tag helper that renders a . /// /// The tag helper renderer. -[HtmlTargetElement("drop-image", TagStructure = TagStructure.WithoutEndTag)] -public sealed class DropImageTagHelper(IDropImageTagHelperRenderer tagHelperRenderer) : TagHelper +[HtmlTargetElement(ImgTagName, Attributes = DropModelAttributeName, TagStructure = TagStructure.WithoutEndTag)] +public sealed class DropImageTagHelper(IDropImageTagHelperRenderer tagHelperRenderer) : DropImageTagHelperBase { /// /// The tag helper renderer. @@ -21,13 +21,13 @@ public sealed class DropImageTagHelper(IDropImageTagHelperRenderer tagHelperRend /// /// Gets or sets the model. /// - [HtmlAttributeName("model")] + [HtmlAttributeName(DropModelAttributeName)] public IImage? Model { get; set; } /// /// Gets or sets the loading mode for this tag helper. /// - [HtmlAttributeName("loading-mode")] + [HtmlAttributeName(LoadingModeAttributeName)] public LoadingMode LoadingMode { get; set; } = LoadingMode.Default; /// diff --git a/src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelperBase.cs b/src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelperBase.cs new file mode 100644 index 0000000..6d10019 --- /dev/null +++ b/src/Rhythm.Drop.Web/TagHelpers/DropImageTagHelperBase.cs @@ -0,0 +1,41 @@ +namespace Rhythm.Drop.Web.TagHelpers; + +using Rhythm.Drop.Models.Images; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Rhythm.Drop.Web.Infrastructure; + +/// +/// A base tag helper used to render a a picture HTML tag. +/// +public abstract class DropImageTagHelperBase : TagHelper +{ + /// + /// The tag name of a picture HTML tag. + /// + protected const string PictureTagName = "picture"; + + /// + /// The tag name of an img HTML tag. + /// + protected const string ImgTagName = "img"; + + /// + /// The attribute name used to get the model. + /// + protected const string DropModelAttributeName = "drop-model"; + + /// + /// The attribute name used to get the . + /// + protected const string LoadingModeAttributeName = "loading-mode"; + + /// + /// The name of the context item used to get and set the . + /// + protected const string DropModelContextItemName = "Drop-Model"; + + /// + /// The name of the context item used to get and set the . + /// + protected const string DropLoadingModeContextItemName = "Drop-LoadingMode"; +} \ No newline at end of file diff --git a/src/Rhythm.Drop.Web/TagHelpers/DropPictureImageTagHelper.cs b/src/Rhythm.Drop.Web/TagHelpers/DropPictureImageTagHelper.cs new file mode 100644 index 0000000..fd8b5a2 --- /dev/null +++ b/src/Rhythm.Drop.Web/TagHelpers/DropPictureImageTagHelper.cs @@ -0,0 +1,59 @@ +namespace Rhythm.Drop.Web.TagHelpers; + +using Microsoft.AspNetCore.Razor.TagHelpers; +using Rhythm.Drop.Models.Images; +using Rhythm.Drop.Web.Infrastructure; +using Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +/// +/// A tag helper used within a . +/// This tag helper is ignored if not used within a and not rendered if the passed down to this tag helper is . +/// +/// The tag helper renderer. +[HtmlTargetElement(ImgTagName, ParentTag = PictureTagName, TagStructure = TagStructure.NormalOrSelfClosing)] +public sealed class DropPictureImageTagHelper(IDropPictureImageTagHelperRenderer tagHelperRenderer) : DropImageTagHelperBase +{ + /// + /// The tag helper renderer. + /// + private readonly IDropPictureImageTagHelperRenderer _tagHelperRenderer = tagHelperRenderer; + + /// + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (TryGetModelFromContext(context, out var model) is false) + { + await base.ProcessAsync(context, output); + return; + } + + var loadingMode = GetLoadingModeOrDefaultFromContext(context); + var rendererContext = new DropImageTagHelperRendererContext(model, loadingMode); + + await _tagHelperRenderer.RenderAsync(rendererContext, context, output); + } + + private static bool TryGetModelFromContext(TagHelperContext context, [NotNullWhen(true)] out IImage? model) + { + if (context.Items.ContainsKey(DropLoadingModeContextItemName) is false) + { + model = default; + return false; + } + + model = context.Items[DropModelContextItemName] as IImage; + return model is not null; + } + + private static LoadingMode GetLoadingModeOrDefaultFromContext(TagHelperContext context) + { + if (context.Items.ContainsKey(DropLoadingModeContextItemName) is false) + { + return LoadingMode.Default; + } + + return context.Items[DropLoadingModeContextItemName] as LoadingMode? ?? LoadingMode.Default; + } +} \ No newline at end of file diff --git a/src/Rhythm.Drop.Web/TagHelpers/DropPictureTagHelper.cs b/src/Rhythm.Drop.Web/TagHelpers/DropPictureTagHelper.cs new file mode 100644 index 0000000..78dc6b8 --- /dev/null +++ b/src/Rhythm.Drop.Web/TagHelpers/DropPictureTagHelper.cs @@ -0,0 +1,52 @@ +namespace Rhythm.Drop.Web.TagHelpers; + +using Microsoft.AspNetCore.Razor.TagHelpers; +using Rhythm.Drop.Models.Images; +using Rhythm.Drop.Web.Infrastructure; +using Rhythm.Drop.Web.Infrastructure.TagHelperRenderers.Images; +using System.Threading.Tasks; + +/// +/// A picture tag that renders a . +/// +/// The tag helper renderer. +[HtmlTargetElement(PictureTagName, Attributes = DropModelAttributeName, TagStructure = TagStructure.NormalOrSelfClosing)] +[RestrictChildren(ImgTagName)] +public sealed class DropPictureTagHelper(IDropPictureTagHelperRenderer tagHelperRenderer) : DropImageTagHelperBase +{ + /// + /// The tag helper renderer. + /// + private readonly IDropPictureTagHelperRenderer _tagHelperRenderer = tagHelperRenderer; + + /// + /// Gets or sets the image. + /// + [HtmlAttributeName(DropModelAttributeName)] + public IImage? Model { get; set; } + + /// + /// Gets or sets the render mode. + /// + [HtmlAttributeName(LoadingModeAttributeName)] + public LoadingMode RenderMode { get; set; } = LoadingMode.Default; + + /// + public override void Init(TagHelperContext context) + { + if (Model is not null) + { + // set the context items to be used by any child tags. + context.Items[DropModelContextItemName] = Model; + context.Items[DropLoadingModeContextItemName] = RenderMode; + } + } + + /// + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var renderContext = new DropImageTagHelperRendererContext(Model, RenderMode); + + await _tagHelperRenderer.RenderAsync(renderContext, context, output); + } +} \ No newline at end of file diff --git a/tests/Rhythm.Drop.Web.Tests/TagHelperRenderers/Images/DefaultImageTagHelperRendererTests.cs b/tests/Rhythm.Drop.Web.Tests/TagHelperRenderers/Images/DefaultImageTagHelperRendererTests.cs index fd4d518..c3b2fcc 100644 --- a/tests/Rhythm.Drop.Web.Tests/TagHelperRenderers/Images/DefaultImageTagHelperRendererTests.cs +++ b/tests/Rhythm.Drop.Web.Tests/TagHelperRenderers/Images/DefaultImageTagHelperRendererTests.cs @@ -79,26 +79,4 @@ public async Task RenderAsync_With_Simple_Image_And_Dimensions_Returns_Output_Wi Assert.That(output.Attributes, Has.One.Matches(x => x.Name == "height" && x.Value.ToString() == image.Height.ToString())); }); } - - - [Test] - public async Task RenderAsync_With_Image_That_Has_Sources_Returns_Output_With_Picture_TagName() - { - // arrange - var tagHelperRenderer = new DefaultDropImageTagHelperRenderer(); - var image = new Image("/image.gif", "Test", default, default, [new ImageSource("/image2.gif")]); - var rendererContext = new DropImageTagHelperRendererContext(image, LoadingMode.Default); - var context = CreateTagHelperContext(DefaultTagName); - var output = CreateTagHelperOutput(DefaultTagName); - - // act - await tagHelperRenderer.RenderAsync(rendererContext, context, output); - - // assert - Assert.Multiple(() => - { - Assert.That(output.TagName, Is.EqualTo("picture")); - Assert.That(output.Content.IsModified, Is.True); - }); - } }