-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
SPAL-15: MVC display condition
- Loading branch information
Showing
21 changed files
with
539 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ node_modules/ | |
*.user | ||
.pnpm-debug.log | ||
.editorconfig | ||
*.orig |
31 changes: 31 additions & 0 deletions
31
Lombiq.HelpfulExtensions/Extensions/Widgets/Drivers/ConditionDisplayDriver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using Lombiq.HelpfulExtensions.Extensions.Widgets.ViewModels; | ||
using Lombiq.HelpfulLibraries.OrchardCore.Contents; | ||
using OrchardCore.DisplayManagement.Handlers; | ||
using OrchardCore.DisplayManagement.Views; | ||
using OrchardCore.Rules; | ||
using System.Collections.Generic; | ||
|
||
namespace Lombiq.HelpfulExtensions.Extensions.Widgets.Drivers; | ||
|
||
public abstract class ConditionDisplayDriver<TCondition> : DisplayDriver<Condition, TCondition> | ||
where TCondition : Condition | ||
{ | ||
public override IDisplayResult Display(TCondition model) => | ||
Combine( | ||
InitializeDisplayType(CommonContentDisplayTypes.Summary, model), | ||
InitializeDisplayType(CommonContentDisplayTypes.Thumbnail, model)); | ||
|
||
public override IDisplayResult Edit(TCondition model) => | ||
Combine( | ||
InitializeDisplayType(CommonContentDisplayTypes.Detail, model, "Title"), | ||
GetEditor(model)); | ||
|
||
protected abstract ConditionViewModel GetConditionViewModel(TCondition condition); | ||
protected abstract IDisplayResult GetEditor(TCondition model); | ||
|
||
private ShapeResult InitializeDisplayType(string displayType, TCondition model, string shapeTypeSuffix = null) => | ||
Initialize<ConditionViewModel>( | ||
string.Join('_', new[] { "Condition", "Fields", displayType, shapeTypeSuffix }.WhereNot(string.IsNullOrEmpty)), | ||
target => GetConditionViewModel(model).CopyTo(target)) | ||
.Location(displayType, CommonLocationNames.Content); | ||
} |
90 changes: 90 additions & 0 deletions
90
Lombiq.HelpfulExtensions/Extensions/Widgets/Drivers/MvcConditionDisplayDriver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
using Lombiq.HelpfulExtensions.Extensions.Widgets.Models; | ||
using Lombiq.HelpfulExtensions.Extensions.Widgets.ViewModels; | ||
using Microsoft.AspNetCore.Html; | ||
using Microsoft.AspNetCore.Mvc.Localization; | ||
using Microsoft.Extensions.Localization; | ||
using Newtonsoft.Json; | ||
using OrchardCore.DisplayManagement.ModelBinding; | ||
using OrchardCore.DisplayManagement.Views; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace Lombiq.HelpfulExtensions.Extensions.Widgets.Drivers; | ||
|
||
public class MvcConditionDisplayDriver : ConditionDisplayDriver<MvcCondition> | ||
{ | ||
private readonly IHtmlLocalizer<MvcConditionDisplayDriver> H; | ||
private readonly IStringLocalizer<MvcConditionDisplayDriver> T; | ||
public MvcConditionDisplayDriver( | ||
IHtmlLocalizer<MvcConditionDisplayDriver> htmlLocalizer, | ||
IStringLocalizer<MvcConditionDisplayDriver> stringLocalizer) | ||
{ | ||
H = htmlLocalizer; | ||
T = stringLocalizer; | ||
} | ||
|
||
protected override IDisplayResult GetEditor(MvcCondition model) => | ||
Initialize<MvcConditionViewModel>("MvcCondition_Fields_Edit", viewModel => | ||
{ | ||
viewModel.Area = model.Area; | ||
viewModel.Controller = model.Controller; | ||
viewModel.Action = model.Action; | ||
|
||
foreach (var (key, value) in model.OtherRouteValues) | ||
{ | ||
viewModel.OtherRouteNames.Add(key); | ||
viewModel.OtherRouteValues.Add(value); | ||
} | ||
}).PlaceInContent(); | ||
|
||
public override async Task<IDisplayResult> UpdateAsync(MvcCondition model, IUpdateModel updater) | ||
{ | ||
var viewModel = new MvcConditionViewModel(); | ||
if (await updater.TryUpdateModelAsync(viewModel, Prefix)) | ||
{ | ||
if (viewModel.OtherRouteNames.Count != viewModel.OtherRouteValues.Count) | ||
{ | ||
updater.ModelState.AddModelError( | ||
nameof(viewModel.OtherRouteNames), | ||
T["The count of other route value names didn't match the count of other route values."]); | ||
} | ||
|
||
model.Area = viewModel.Area; | ||
model.Controller = viewModel.Controller; | ||
model.Action = viewModel.Action; | ||
|
||
model.OtherRouteValues.Clear(); | ||
for (var i = 0; i < viewModel.OtherRouteNames.Count; i++) | ||
{ | ||
model.OtherRouteValues[viewModel.OtherRouteNames[i]] = viewModel.OtherRouteValues[i]; | ||
} | ||
} | ||
|
||
return Edit(model); | ||
} | ||
|
||
protected override ConditionViewModel GetConditionViewModel(MvcCondition condition) | ||
{ | ||
IHtmlContentBuilder summaryHint = new HtmlContentBuilder(); | ||
|
||
static IHtmlContentBuilder AppendIfNotEmpty(IHtmlContentBuilder summaryHint, string value, IHtmlContent label) => | ||
string.IsNullOrEmpty(value) ? summaryHint : summaryHint.AppendHtml(label).AppendHtml(" "); | ||
|
||
summaryHint = AppendIfNotEmpty(summaryHint, condition.Area, H["Area: \"{0}\"", condition.Area]); | ||
summaryHint = AppendIfNotEmpty(summaryHint, condition.Controller, H["Controller: \"{0}\"", condition.Controller]); | ||
summaryHint = AppendIfNotEmpty(summaryHint, condition.Action, H["Action: \"{0}\"", condition.Action]); | ||
|
||
if (condition.OtherRouteValues.Any()) | ||
{ | ||
summaryHint = summaryHint.AppendHtml( | ||
H["Other route values: {0}", JsonConvert.SerializeObject(condition.OtherRouteValues)]); | ||
} | ||
|
||
return new ConditionViewModel | ||
{ | ||
Title = H["MVC condition"], | ||
Description = H["An MVC condition evaluates the currently route values such as controller and action name."], | ||
SummaryHint = summaryHint, | ||
}; | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
Lombiq.HelpfulExtensions/Extensions/Widgets/Drivers/MvcConditionEvaluatorDriver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Lombiq.HelpfulExtensions.Extensions.Widgets.Models; | ||
using Microsoft.AspNetCore.Http; | ||
using OrchardCore.ContentManagement.Display.ContentDisplay; | ||
using OrchardCore.Rules; | ||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace Lombiq.HelpfulExtensions.Extensions.Widgets.Drivers; | ||
|
||
public class MvcConditionEvaluatorDriver : ContentDisplayDriver, IConditionEvaluator | ||
{ | ||
private readonly IHttpContextAccessor _hca; | ||
|
||
public MvcConditionEvaluatorDriver(IHttpContextAccessor hca) => | ||
_hca = hca; | ||
|
||
public ValueTask<bool> EvaluateAsync(Condition condition) => new(Evaluate((MvcCondition)condition)); | ||
|
||
private bool Evaluate(MvcCondition condition) => | ||
MatchRouteValue("area", condition.Area) && | ||
MatchRouteValue("controller", condition.Controller) && | ||
MatchRouteValue("action", condition.Action) && | ||
(!condition.OtherRouteValues.Any() || condition.OtherRouteValues.All(pair => MatchRouteValue(pair.Key, pair.Value))); | ||
|
||
private bool MatchRouteValue(string name, string value) | ||
{ | ||
// Ignore this match operation if the target value is not set. | ||
if (string.IsNullOrWhiteSpace(value)) return true; | ||
|
||
return _hca.HttpContext?.Request.RouteValues.TryGetValue(name, out var routeValue) == true && | ||
value.EqualsOrdinalIgnoreCase(routeValue?.ToString()); | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
Lombiq.HelpfulExtensions/Extensions/Widgets/Liquid/MenuWidgetLiquidFilter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
using Fluid; | ||
using Fluid.Values; | ||
using Lombiq.HelpfulExtensions.Extensions.Widgets.ViewModels; | ||
using Lombiq.HelpfulLibraries.OrchardCore.Liquid; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Mvc.Infrastructure; | ||
using Microsoft.AspNetCore.Mvc.Routing; | ||
using Microsoft.Extensions.Localization; | ||
using Newtonsoft.Json; | ||
using Newtonsoft.Json.Linq; | ||
using OrchardCore.Liquid; | ||
using OrchardCore.Navigation; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Threading.Tasks; | ||
|
||
namespace Lombiq.HelpfulExtensions.Extensions.Widgets.Liquid; | ||
|
||
public class MenuWidgetLiquidFilter : ILiquidFilter | ||
{ | ||
private readonly ILiquidContentDisplayService _liquidContentDisplayService; | ||
private readonly Lazy<IUrlHelper> _urlHelperLazy; | ||
private readonly IStringLocalizer<MenuWidgetLiquidFilter> T; | ||
|
||
public MenuWidgetLiquidFilter( | ||
IActionContextAccessor actionContextAccessor, | ||
ILiquidContentDisplayService liquidContentDisplayService, | ||
IStringLocalizer<MenuWidgetLiquidFilter> stringLocalizer, | ||
IUrlHelperFactory urlHelperFactory) | ||
{ | ||
_liquidContentDisplayService = liquidContentDisplayService; | ||
|
||
_urlHelperLazy = new Lazy<IUrlHelper>(() => | ||
urlHelperFactory.GetUrlHelper(actionContextAccessor.ActionContext!)); | ||
|
||
T = stringLocalizer; | ||
} | ||
|
||
public ValueTask<FluidValue> ProcessAsync(FluidValue input, FilterArguments arguments, LiquidTemplateContext context) | ||
{ | ||
bool noWrapper, localNav; | ||
string classes; | ||
noWrapper = arguments[nameof(noWrapper)].ToBooleanValue(); | ||
localNav = arguments[nameof(localNav)].ToBooleanValue(); | ||
classes = arguments[nameof(classes)].ToStringValue(); | ||
|
||
var converter = new LocalizedStringJsonConverter(T); | ||
var serializer = new JsonSerializer(); | ||
serializer.Converters.Add(converter); | ||
var serializerSettings = new JsonSerializerSettings(); | ||
serializerSettings.Converters.Add(converter); | ||
|
||
var menuItems = input?.Type switch | ||
{ | ||
FluidValues.String => JsonConvert.DeserializeObject<IList<MenuItem>>( | ||
input!.ToStringValue(), | ||
serializerSettings), | ||
FluidValues.Object => input!.ToObjectValue() switch | ||
{ | ||
IEnumerable<MenuItem> enumerable => enumerable.AsList(), | ||
MenuItem single => new[] { single }, | ||
JArray jArray => jArray.ToObject<IList<MenuItem>>(serializer), | ||
JObject jObject => new[] { jObject.ToObject<MenuItem>(serializer) }, | ||
_ => null, | ||
}, | ||
_ => null, | ||
}; | ||
|
||
UpdateMenuItems(menuItems, localNav); | ||
|
||
return _liquidContentDisplayService.DisplayNewAsync<MenuWidgetViewModel>( | ||
WidgetTypes.MenuWidget, | ||
model => | ||
{ | ||
model.NoWrapper = noWrapper; | ||
model.MenuItems = menuItems ?? Array.Empty<MenuItem>(); | ||
model.HtmlClasses = classes; | ||
}); | ||
} | ||
|
||
private void UpdateMenuItems(IEnumerable<MenuItem> menuItems, bool localNav) | ||
{ | ||
if (menuItems == null) return; | ||
|
||
foreach (var item in menuItems) | ||
{ | ||
if (!string.IsNullOrEmpty(item.Url)) | ||
{ | ||
var finalUrl = _urlHelperLazy.Value.Content(item.Url); | ||
item.Url = finalUrl; | ||
item.Href = finalUrl; | ||
} | ||
|
||
item.LocalNav = localNav || item.LocalNav; | ||
|
||
UpdateMenuItems(item.Items, localNav); | ||
} | ||
} | ||
|
||
public class LocalizedStringJsonConverter : JsonConverter<LocalizedString> | ||
{ | ||
private readonly IStringLocalizer T; | ||
|
||
public LocalizedStringJsonConverter(IStringLocalizer stringLocalizer) => | ||
T = stringLocalizer; | ||
|
||
public override void WriteJson(JsonWriter writer, LocalizedString value, JsonSerializer serializer) => | ||
writer.WriteValue(value?.Value); | ||
|
||
[SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "We don't want to handle other token types.")] | ||
public override LocalizedString ReadJson( | ||
JsonReader reader, | ||
Type objectType, | ||
LocalizedString existingValue, | ||
bool hasExistingValue, | ||
JsonSerializer serializer) | ||
{ | ||
switch (reader.TokenType) | ||
{ | ||
case JsonToken.String: | ||
return JToken.ReadFrom(reader).ToObject<string>() is { } text ? T[text] : null; | ||
case JsonToken.StartObject: | ||
var data = new Dictionary<string, string>( | ||
JToken.ReadFrom(reader).ToObject<Dictionary<string, string>>(), | ||
StringComparer.OrdinalIgnoreCase); | ||
return new LocalizedString(data[nameof(LocalizedString.Name)], data[nameof(LocalizedString.Value)]); | ||
default: | ||
throw new InvalidOperationException("Unable to parse JSON!"); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
Lombiq.HelpfulExtensions/Extensions/Widgets/Models/ContentItemWidget.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using OrchardCore.ContentFields.Fields; | ||
using OrchardCore.ContentManagement; | ||
|
||
namespace Lombiq.HelpfulExtensions.Extensions.Widgets.Models; | ||
|
||
public class ContentItemWidget : ContentPart | ||
{ | ||
public ContentPickerField ContentToDisplay { get; set; } = new(); | ||
public TextField DisplayType { get; set; } = new(); | ||
public TextField GroupId { get; set; } = new(); | ||
} |
12 changes: 12 additions & 0 deletions
12
Lombiq.HelpfulExtensions/Extensions/Widgets/Models/MvcCondition.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using OrchardCore.Rules; | ||
using System.Collections.Generic; | ||
|
||
namespace Lombiq.HelpfulExtensions.Extensions.Widgets.Models; | ||
|
||
public class MvcCondition : Condition | ||
{ | ||
public string Area { get; set; } | ||
public string Controller { get; set; } | ||
public string Action { get; set; } | ||
public IDictionary<string, string> OtherRouteValues { get; } = new Dictionary<string, string>(); | ||
} |
Oops, something went wrong.