Skip to content

Commit

Permalink
feat (Image THRs): bulk rework of drop-image
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jamiepollock committed Jan 26, 2024
1 parent 89f212f commit 99e56c7
Show file tree
Hide file tree
Showing 15 changed files with 510 additions and 220 deletions.
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// A base class for rendering <see cref="IImage"/> when used in a <see cref="TagHelper"/>.
/// A base implementation for rendering a <see cref="IImage"/> when used in a <see cref="TagHelper"/> as an img HTML tag (without a picture HTML tag).
/// </summary>
public abstract class DropImageTagHelperRendererBase : TagHelperRendererBase<DropImageTagHelperRendererContext>, IDropImageTagHelperRenderer
{
/// <inheritdoc/>
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);
}
});
}

/// <summary>
/// Determines whether the output should be rendered as a picture HTML tag or a single img HTML tag.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <returns><para>A <see cref="bool"/>. If <see langword="true" /> the output should be rendered a picture HTML tag. Otherwise it should be rendered a single img HTML tag.</para>
/// </returns>
protected virtual bool ShouldRenderOutputAsPicture(IImage image, TagHelperContext context, TagHelperOutput output)
{
return image.Sources.Count > 0;
}

/// <summary>
/// Renders a <see cref="IImage"/> and <see cref="TagHelperOutput"/> as a picture HTML tag.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="renderMode">The loading mode.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <remarks>
/// <para>This is intended to be a developer friendly extension point to modify the a picture tag.</para>
/// <para>This can be replaced entirely or the base method can be used as a starting point for modifications.</para>
/// </remarks>
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);
}

/// <summary>
/// Renders a <see cref="IImage"/> and <see cref="TagHelperOutput"/> as a single img HTML tag.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="renderMode">The loading mode.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <remarks>
/// <para>This is intended to be a developer friendly extension point to modify the img tag when no picture tag is present.</para>
/// <para>This can be replaced entirely or the base method can be used as a starting point for modifications.</para>
/// </remarks>
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;
}

/// <summary>
/// Creates a <see cref="TagBuilder"/> used by <see cref="BuildImgHtmlContent(IImage, LoadingMode, TagHelperContext, TagHelperOutput)"/>.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="renderMode">The loading mode.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <returns>A <see cref="TagBuilder"/>.</returns>
/// <remarks>
/// <para>This is intended to be a developer friendly extension point to modify the img tag used by a picture tag.</para>
/// <para>This can be replaced entirely or the base method can be used as a starting point for modifications.</para>
/// </remarks>
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;
}

/// <summary>
/// Creates a <see cref="TagBuilder"/> used by <see cref="BuildImageSourceHtmlContent(IImageSource, LoadingMode, TagHelperContext, TagHelperOutput)"/>.
/// Sets the attributes of the tag helper output.
/// </summary>
/// <param name="source">The image source.</param>
/// <param name="renderMode">The loading mode.</param>
/// <param name="model">The model.</param>
/// <param name="loadingMode">The loading mode.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <returns>A <see cref="TagBuilder"/>.</returns>
/// <remarks>
/// <para>This is intended to be a developer friendly extension point to modify the source tag used by a picture tag.</para>
/// <para>This can be replaced entirely or the base method can be used as a starting point for modifications.</para>
/// </remarks>
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;
}

/// <summary>
/// Builds the <see cref="IHtmlContent"/> required by a img HTML tag when rendered within an picture HTML tag.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="renderMode">The loading mode.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <returns>A <see cref="IHtmlContent"/>.</returns>
/// <remarks>
/// <para>This is intended to be a developer friendly extension point to modify the source tag used by a picture tag.</para>
/// <para>This should only bn replaced entirely if you do not intend to use the output from <see cref="CreateImgTagBuilder(IImage, LoadingMode, TagHelperContext, TagHelperOutput)"/>.</para>
/// </remarks>
protected IHtmlContent BuildImgHtmlContent(IImage image, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output)
{
var tagBuilder = CreateImgTagBuilder(image, renderMode, context, output);

return tagBuilder.RenderSelfClosingTag();
}

/// <summary>
/// Builds the <see cref="IHtmlContent"/> required by a source HTML tag when rendered within an picture HTML tag.
/// </summary>
/// <param name="source">The image source.</param>
/// <param name="renderMode">The loading mode.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <returns>A <see cref="IHtmlContent"/>.</returns>
/// <remarks>
/// <para>This is intended to be a developer friendly extension point to modify the source tag used by a picture tag.</para>
/// <para>This should only bn replaced entirely if you do not intend to use the output from <see cref="CreateImageSourceTagBuilder(IImageSource, LoadingMode, TagHelperContext, TagHelperOutput)"/>.</para>
/// </remarks>
protected IHtmlContent BuildImageSourceHtmlContent(IImageSource source, LoadingMode renderMode, TagHelperContext context, TagHelperOutput output)
{
var tagBuilder = CreateImageSourceTagBuilder(source, renderMode, context, output);

return tagBuilder.RenderSelfClosingTag();
}

/// <inheritdoc/>
protected override async Task RenderNullOrInvalidAsync(TagHelperContext context, TagHelperOutput output)
{
await Task.Run(output.SuppressOutput);
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// A base implementation for rendering a <see cref="IImage"/> when used in a <see cref="TagHelper"/> as an img HTML tag within a picture HTML tag.
/// </summary>
public abstract class DropPictureImageTagHelperRendererBase : TagHelperRendererBase<DropImageTagHelperRendererContext>, IDropPictureImageTagHelperRenderer
{
/// <inheritdoc/>
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;
}

/// <summary>
/// Sets the attributes of the tag helper output.
/// </summary>
/// <param name="model">The model.</param>
/// <param name="loadingMode">The loading mode.</param>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
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);
}
}
}
Loading

0 comments on commit 99e56c7

Please sign in to comment.