Skip to content

Commit

Permalink
🆕 feat: test GetRelativeUriWithQueryParameters
Browse files Browse the repository at this point in the history
  • Loading branch information
capdiem committed Dec 26, 2023
1 parent f3f2df9 commit 528afe6
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using BlazorComponent;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace Masa.Blazor;

/// <summary>
/// A abstract class for components that use the <see cref="MInteractiveTrigger{TValue}"/>
/// or <see cref="MInteractiveTriggers{TValue}"/> component as a trigger.
/// </summary>
public abstract class MInteractivePopup : ComponentBase, IOutsideClickJsCallback, IAsyncDisposable
{
[Inject] private OutsideClickJSModule OutsideClickJSModule { get; set; } = null!;

[Inject] protected NavigationManager NavigationManager { get; set; } = null!;

/// <summary>
/// The query name of url for trigger a interactive popup.
/// </summary>
[Parameter] public string QueryName { get; set; } = null!;

/// <summary>
/// The activator selector.
/// </summary>
[Parameter] public string Activator { get; set; } = null!;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await OutsideClickJSModule.InitializeAsync(this, Activator, ".m-interactive-trigger__popup");
}
}

public async Task HandleOnOutsideClickAsync()
{
// TODO: https://github.com/dotnet/aspnetcore/issues/52705
await Task.Delay(100);
NavigationManager.NavigateWithQueryParameter(QueryName, (string?)null);
}

async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
await DisposeAsync();
await OutsideClickJSModule.UnbindAndDisposeAsync();
}
catch (JSDisconnectedException)
{
// ignore
}
}

protected virtual Task DisposeAsync() => Task.CompletedTask;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@namespace Masa.Blazor
@typeparam TValue
@using Microsoft.AspNetCore.Components.Rendering
@inherits MInteractiveTriggerBase2<TValue, TValue>

@base.BuildRenderTree

@code {

protected override string ComponentName => nameof(MInteractiveTrigger<TValue>);

protected override void RenderLinkContent(RenderTreeBuilder __builder)
{
var value = IsInteractive ? default : InteractiveValue;

@RenderSingleLink(QueryName, value, InteractiveValue)
}

protected override bool CheckInteractive()
{
return EqualityComparer<TValue>.Default.Equals(QueryValue, InteractiveValue);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@namespace Masa.Blazor
@using Microsoft.AspNetCore.Components.Rendering
@inherits CssProviderComponentBase
@typeparam TValue
@typeparam TInteractiveValue
@inject NavigationManager NavigationManager

<div class="@CssProvider.GetClass()"
style="@CssProvider.GetStyle()"
id="@ElementId">
@RenderLinkContent
@RenderPopupContent
</div>

@code {

protected abstract void RenderLinkContent(RenderTreeBuilder __builder);

protected RenderFragment RenderSingleLink(string name, TValue? value, TValue? interactiveValue) => __builder =>
{
<a class="@CssProvider.GetClass("link")"
style="@CssProvider.GetStyle("link")"
href="@NavigationManager.GetRelativeUriWithQueryParameters(new Dictionary<string, object?>() { { name, value } })">
@ChildContent?.Invoke(interactiveValue)
</a>
};

private void RenderPopupContent(RenderTreeBuilder __builder)
{
@if (WithPopup)
{
<div class="@CssProvider.GetClass("popup")"
style="@CssProvider.GetStyle("popup")">
@RenderInteractiveComponent
</div>
}
else
{
@RenderInteractiveComponent
}
}

private void RenderInteractiveComponent(RenderTreeBuilder __builder)
{
@if (IsInteractive)
{
<DynamicComponent Type="@InteractiveComponentType"
Parameters="@ComputedInteractiveComponentParameters" />
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using BlazorComponent;
using Microsoft.AspNetCore.Components;

namespace Masa.Blazor;

using BemIt;

/// <summary>
/// A abstract class of interactive trigger component.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <typeparam name="TInteractiveValue"></typeparam>

#if NET8_0_OR_GREATER
[StreamRendering]
#endif

public abstract partial class MInteractiveTriggerBase2<TValue, TInteractiveValue> : CssProviderComponentBase
{
[Parameter] public bool DisableLinkOnInteractive { get; set; }

/// <summary>
/// The name of query parameter.
/// </summary>
[Parameter] [EditorRequired] public string QueryName { get; set; } = null!;

/// <summary>
/// The value of query parameter.
/// </summary>
[Parameter] public TValue? QueryValue { get; set; }

/// <summary>
/// A value that is used to determine whether the component is interactive.
/// </summary>
[Parameter] [EditorRequired] public virtual TInteractiveValue InteractiveValue { get; set; } = default!;

/// <summary>
/// The <see cref="Type"/> of interactive component.
/// </summary>
[Parameter] [EditorRequired] public Type InteractiveComponentType { get; set; } = null!;

/// <summary>
/// The parameters of interactive component.
/// </summary>
[Parameter] public IDictionary<string, object?>? InteractiveComponentParameters { get; set; }

[Parameter] public RenderFragment<TValue?>? ChildContent { get; set; }

/// <summary>
/// Determines whether a built-in popup is needed to display the interactive component.
/// </summary>
[Parameter] public bool WithPopup { get; set; }

[Parameter] public string? PopupClass { get; set; }

[Parameter] public string? PopupStyle { get; set; }

/// <summary>
/// The top position of popup.
/// </summary>
[Parameter] public int? Top { get; set; }

/// <summary>
/// The right position of popup.
/// </summary>
[Parameter] public int? Right { get; set; }

/// <summary>
/// The bottom position of popup.
/// </summary>
[Parameter] public int? Bottom { get; set; }

/// <summary>
/// The left position of popup.
/// </summary>
[Parameter] public int? Left { get; set; }

private bool _active;

private string ElementId => $"_int_trigger_{QueryName}";

protected virtual string ComponentName => nameof(MInteractiveTriggerBase<TValue, TInteractiveValue>);

protected string? Activator => $"#{ElementId} > a";

public bool IsInteractive { get; private set; }

private IDictionary<string, object?> ComputedInteractiveComponentParameters
{
get
{
InteractiveComponentParameters ??= new Dictionary<string, object?>();

if (InteractiveComponentType.IsAssignableTo(typeof(MInteractivePopup)))
{
InteractiveComponentParameters.TryAdd(nameof(QueryName), QueryName);
InteractiveComponentParameters.TryAdd(nameof(Activator), Activator);
}

return InteractiveComponentParameters;
}
}

protected override async Task OnInitializedAsync()
{
QueryName.ThrowIfNull(ComponentName);
InteractiveValue.ThrowIfNull(ComponentName);
InteractiveComponentType.ThrowIfNull(ComponentName);

IsInteractive = CheckInteractive();

if (IsInteractive)
{
// The html generated from the server is rendered on the page
// before a short delay. With delay and [StreamRendering],
// set active to true, there will be a short transition animation.

await Task.Delay(1);

_active = true;
}
}

protected abstract bool CheckInteractive();

protected override void SetComponentCss()
{
CssProvider.UseBem("m-interactive-trigger")
.Element("link",
css => { css.Modifiers(m => m.Modifier("disabled", IsInteractive && DisableLinkOnInteractive)); })
.Element("popup", css => { css.Modifiers(m => m.Modifier("active", _active).AddClass(PopupClass)); },
style =>
{
style.AddIf($"top: {Top}px", () => Top.HasValue)
.AddIf($"right: {Right}px", () => Right.HasValue)
.AddIf($"bottom: {Bottom}px", () => Bottom.HasValue)
.AddIf($"left: {Left}px", () => Left.HasValue)
.Add(PopupStyle);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@namespace Masa.Blazor
@typeparam TValue
@using Microsoft.AspNetCore.Components.Rendering
@inherits MInteractiveTriggerBase2<TValue, IEnumerable<TValue>>

@base.BuildRenderTree

@code {

protected override string ComponentName => nameof(MInteractiveTriggers<TValue>);

protected override void OnInitialized()
{
base.OnInitialized();

InteractiveValue.ThrowIfNull(ComponentName);
}

protected override void RenderLinkContent(RenderTreeBuilder __builder)
{
foreach (var interactiveValue in InteractiveValue!)
{
var value = EqualityComparer<TValue?>.Default.Equals(QueryValue, interactiveValue) ? default : interactiveValue;
@RenderSingleLink(QueryName, value, interactiveValue)
}
}

protected override bool CheckInteractive()
{
return InteractiveValue!.Any(val => EqualityComparer<TValue>.Default.Equals(QueryValue, val));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,19 @@
<MCard Color="transparent" Flat Dark Class="footer-content__item footer-content__focus">
<MCardTitle>关注我们</MCardTitle>
<MCardText>
<MInteractiveTriggers Class="d-inline"
QueryName="@nameof(FollowUs)"
QueryValue="@FollowUs"
InteractiveValue="@(new[] { "wechat", "qq" })"
InteractiveComponentType="typeof(FollowUsBtn)"
InteractiveComponentParameters="@FollowUsParameters">
<MInteractiveTriggers2 Class="d-inline"
QueryName="@nameof(FollowUs)"
QueryValue="@FollowUs"
InteractiveValue="@(new[] { "wechat", "qq" })"
InteractiveComponentType="typeof(FollowUsBtn)"
InteractiveComponentParameters="@FollowUsParameters">
<MButton Outlined
Icon
Color="white"
Class="follow-us-icon mr-9">
<MIcon>@(context == "wechat" ? "mdi-wechat" : "mdi-qqchat")</MIcon>
</MButton>
</MInteractiveTriggers>
</MInteractiveTriggers2>

<MButton Class="follow-us-icon"
Outlined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@inject NavigationManager Navigation

<AppBar Class="hidden-md-and-up" BarHeight="48" LogoHeight="30">
<MButton Icon Href="@Navigation.GetUriWithQueryParameter(nameof(Aside), true)">
<MButton Icon Href="@Navigation.GetRelativeUriWithQueryParameter(nameof(Aside), true)">
<MIcon>mdi-menu</MIcon>
</MButton>
</AppBar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

if (prevVisible && !visible)
{
Navigation.NavigateTo(Navigation.GetUriWithQueryParameter(QueryName, (string?)null));
Navigation.NavigateTo(Navigation.GetRelativeUriWithQueryParameter(QueryName, (string?)null));
}
}

Expand Down
31 changes: 31 additions & 0 deletions src/MASA.OfficialWebsite.WebApp/NavigationManagerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Components;

namespace MASA.OfficialWebsite.WebApp;

public static class NavigationManagerExtensions
{
public static string GetRelativeUriWithQueryParameters(this NavigationManager navigationManager, IReadOnlyDictionary<string, object?> parameters)
{
var uriWithQueryParameters = navigationManager.GetUriWithQueryParameters(parameters);
return navigationManager.ToRelativeUriWithQueryParameters(uriWithQueryParameters);
}

public static string GetRelativeUriWithQueryParameter(this NavigationManager navigationManager, string name, bool? value)
{
var uriWithQueryParameter = navigationManager.GetUriWithQueryParameter(name, value);
return navigationManager.ToRelativeUriWithQueryParameters(uriWithQueryParameter);
}

public static string GetRelativeUriWithQueryParameter(this NavigationManager navigationManager, string name, string? value)
{
var uriWithQueryParameter = navigationManager.GetUriWithQueryParameter(name, value);
return navigationManager.ToRelativeUriWithQueryParameters(uriWithQueryParameter);
}

private static string ToRelativeUriWithQueryParameters(this NavigationManager navigationManager, string absoluteUriWithQueryParameters)
{
var baseUri = navigationManager.BaseUri;
var relativeUriWithQueryParameters = absoluteUriWithQueryParameters.Replace(baseUri, string.Empty);
return relativeUriWithQueryParameters.StartsWith("/") ? relativeUriWithQueryParameters : "/" + relativeUriWithQueryParameters;
}
}

0 comments on commit 528afe6

Please sign in to comment.