diff --git a/Lombiq.BaseTheme.Samples/Manifest.cs b/Lombiq.BaseTheme.Samples/Manifest.cs index 3c2613d..29ccf98 100644 --- a/Lombiq.BaseTheme.Samples/Manifest.cs +++ b/Lombiq.BaseTheme.Samples/Manifest.cs @@ -1,19 +1,18 @@ -using OrchardCore.DisplayManagement.Manifest; -using static Lombiq.BaseTheme.Constants.FeatureIds; +using Lombiq.BaseTheme.Attributes; // Theme manifests in Orchard Core are similar to module manifests (see "Module manifest" section in the Training Demo), -// except you have to use the Theme attribute and set the BaseTheme value to the constant at -// Lombiq.BaseTheme.Constants.FeatureIds.BaseTheme from the Lombiq.BaseTheme project. -[assembly: Theme( +// except you have to use the Theme attribute. DerivedTheme is a specific variant of Theme where the BaseTheme property +// is automatically set to the constant at Lombiq.BaseTheme.Constants.FeatureIds.BaseTheme from the Lombiq.BaseTheme +// project and it has some additional properties. +[assembly: DerivedTheme( Name = "Lombiq Base Theme - Samples", Author = "Lombiq Technologies", Version = "0.0.1", Website = "https://github.com/Lombiq/Orchard-Base-Theme", Description = "A sample theme that builds on Lombiq Base Theme.", - // A base theme is another theme project. Orchard Core Display Management first searches your theme and then the - // base theme for template alternates. Besides that, it's similar to a dependency in modules, so any services - // registered in the base theme are also accessible. - BaseTheme = BaseTheme + // This is a new property in DerivedTheme. By setting it to a static resource you can define a default icon for this + // theme. You can define other "link" resources too, using the Link property. + Favicon = "~/Lombiq.BaseTheme.Samples/icons/favicon.ico" )] // Steps you need to do outside of this project: diff --git a/Lombiq.BaseTheme.Samples/Migrations/Icons/favicon.ico b/Lombiq.BaseTheme.Samples/Migrations/Icons/favicon.ico new file mode 100644 index 0000000..ef7cf89 Binary files /dev/null and b/Lombiq.BaseTheme.Samples/Migrations/Icons/favicon.ico differ diff --git a/Lombiq.BaseTheme.Samples/Migrations/Icons/oc-favicon.ico b/Lombiq.BaseTheme.Samples/Migrations/Icons/oc-favicon.ico new file mode 100644 index 0000000..6e58066 Binary files /dev/null and b/Lombiq.BaseTheme.Samples/Migrations/Icons/oc-favicon.ico differ diff --git a/Lombiq.BaseTheme.Samples/Migrations/Lombiq.BaseTheme.Samples.UpdateFrom0.recipe.json b/Lombiq.BaseTheme.Samples/Migrations/Lombiq.BaseTheme.Samples.UpdateFrom0.recipe.json new file mode 100644 index 0000000..eacf22f --- /dev/null +++ b/Lombiq.BaseTheme.Samples/Migrations/Lombiq.BaseTheme.Samples.UpdateFrom0.recipe.json @@ -0,0 +1,33 @@ +{ + "steps": [ + // You can add an icon as a regular media file and then pick it from the editor, or just have media and settings + // steps in your recipe like below. + { + "name": "media", + "Files": [ + { + "SourcePath": "Icons/favicon.ico", + "TargetPath": "Icons/favicon.ico" + }, + { + "SourcePath": "Icons/oc-favicon.ico", + "TargetPath": "Icons/oc-favicon.ico" + } + ] + }, + { + "name": "settings", + "BaseThemeSettings": { + // The HideMenu can be used if you don't want to use the Boostrap main menu widget injected by the base theme. + "HideMenu": false, + // You can set a single icon using a media path, so the same as the TargetPath in the recipe's media step. + "Icon": "Icons/favicon.ico", + // The time stamp is a UTC DateTime.Ticks value, you can make it any random long number in the recipe because + // the chance that the next save will have the exact same ticks is vanishingly low. + "TimeStamp": 638240128517358149 + } + } + ] +} + +// END OF TRAINING SECTION: Set up favicon using recipe migrations diff --git a/Lombiq.BaseTheme.Samples/Migrations/RecipeMigrations.cs b/Lombiq.BaseTheme.Samples/Migrations/RecipeMigrations.cs new file mode 100644 index 0000000..5c0f37b --- /dev/null +++ b/Lombiq.BaseTheme.Samples/Migrations/RecipeMigrations.cs @@ -0,0 +1,20 @@ +using Lombiq.HelpfulLibraries.OrchardCore.Data; +using OrchardCore.Recipes.Services; + +namespace Lombiq.BaseTheme.Samples.Migrations; + +// Migrations based on the RecipeMigrationsBase class have a default CreateAsync method that invokes the recipe in the +// same directory called "{module-or-theme-id}.UpdateFrom0.recipe.json". For any subsequent update migrations, you can +// create an UpdateFrom1Async, UpdateFrom2Async, etc as usual, but all you have to put in it is ExecuteAsync(N) to +// invoke the corresponding "{module-or-theme-id}.UpdateFromN.recipe.json" recipe and return the incremented version +// number. +// If you just want a static default icon, check out the DerivedTheme.Favicon in Manifest.cs! +public class RecipeMigrations : RecipeMigrationsBase +{ + public RecipeMigrations(IRecipeMigrator recipeMigrator) + : base(recipeMigrator) + { + } +} + +// NEXT STATION: Migrations/Lombiq.BaseTheme.Samples.UpdateFrom0.recipe.json diff --git a/Lombiq.BaseTheme.Samples/Readme.md b/Lombiq.BaseTheme.Samples/Readme.md index b35112e..34811b5 100644 --- a/Lombiq.BaseTheme.Samples/Readme.md +++ b/Lombiq.BaseTheme.Samples/Readme.md @@ -21,3 +21,4 @@ You can start with any of the top-level sections, but the indented sections shou - [Layout injection](Views/Widget-LayoutInjection.cshtml) - [Sass styling and structure](Assets/Styles/site.scss) - [Front-end navigation via the `"main"` menu](Services/AccountNavigationProvider.cs) +- [Set up favicon using recipe migrations](Migrations/RecipeMigrations.cs) diff --git a/Lombiq.BaseTheme.Samples/Services/AccountNavigationProvider.cs b/Lombiq.BaseTheme.Samples/Services/AccountNavigationProvider.cs index 7bc06ef..541640f 100644 --- a/Lombiq.BaseTheme.Samples/Services/AccountNavigationProvider.cs +++ b/Lombiq.BaseTheme.Samples/Services/AccountNavigationProvider.cs @@ -55,3 +55,5 @@ protected override void Build(NavigationBuilder builder) } // END OF TRAINING SECTION: Front-end navigation via the "main" menu + +// NEXT STATION: Migrations/RecipeMigrations.cs diff --git a/Lombiq.BaseTheme.Samples/Startup.cs b/Lombiq.BaseTheme.Samples/Startup.cs index 7dd2b94..f70882c 100644 --- a/Lombiq.BaseTheme.Samples/Startup.cs +++ b/Lombiq.BaseTheme.Samples/Startup.cs @@ -1,15 +1,15 @@ +using Lombiq.BaseTheme.Samples.Migrations; using Lombiq.DataTables.Samples.Navigation; using Lombiq.HelpfulLibraries.OrchardCore.ResourceManagement; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using OrchardCore.Data.Migration; using OrchardCore.Modules; using OrchardCore.Navigation; using OrchardCore.ResourceManagement; using System; -using System.Threading.Tasks; -using static Lombiq.BaseTheme.Samples.Constants.FeatureIds; namespace Lombiq.BaseTheme.Samples; @@ -24,24 +24,15 @@ public override void ConfigureServices(IServiceCollection services) // This service provides configuration to the ResourceFilterMiddleware. services.AddScoped(); + + // The recipe migration is used to add the media items and Base Theme settings required for the correct favicon. + services.AddDataMigration(); } - public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) - { + public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => // In this theme we inject the style resources using the ResourceFilterMiddleware which needs to be enabled with // this extension. See: https://github.com/Lombiq/Helpful-Libraries/blob/dev/Lombiq.HelpfulLibraries.OrchardCore/Docs/ResourceManagement.md app.UseResourceFilters(); - - // Certain browsers expect the site's favicon to be in the default location and try to load it from anyway. If - // you add a element you will still get an unnecessary lost GET request because of that, and of course it - // contributes to the page size. It's better to change what ~/favicon.ico means instead. - // See https://orcharddojo.net/blog/how-to-add-a-favicon-under-favicon-ico-in-orchard-core-orchard-nuggets - app.Map("/favicon.ico", appBuilder => appBuilder.Run(context => - { - context.Response.Redirect($"/{BaseThemeSamples}/icons/favicon.ico", permanent: true); - return Task.CompletedTask; - })); - } } // NEXT STATION: Services/ResourceFilters.cs diff --git a/Lombiq.BaseTheme.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.BaseTheme.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs index b7e4ce9..f1931e5 100644 --- a/Lombiq.BaseTheme.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.BaseTheme.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -1,3 +1,4 @@ +using Atata; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Services; using OpenQA.Selenium; @@ -87,4 +88,35 @@ await context.SetContentPickerByDisplayTextAsync( .Trim() .ShouldBe("My Content"); } + + public static async Task TestBaseThemeSiteSettingsAsync( + this UITestContext context, + Func selectFromMediaLibraryAsync = null, + By byIcon = null) + { + await context.GoToAdminRelativeUrlAsync("/Lombiq.BaseTheme/Admin/Index"); + await context.SetCheckboxValueAsync(By.Id("HideMenu"), isChecked: true); + + var byDeleteButton = By.CssSelector("#Editor .delete-button").OfAnyVisibility(); + while (context.Exists(byDeleteButton.Safely())) await context.ClickReliablyOnAsync(byDeleteButton); + + selectFromMediaLibraryAsync ??= async () => + { + await context.ClickReliablyOnAsync(By.XPath("//div[contains(@class, 'folder-name') and contains(., 'Icons')]")); + await context.ClickReliablyOnAsync(By.XPath( + "//tr[contains(@class, 'media-item') and .//div[contains(@class, 'media-name-cell') and contains(., ' oc-favicon.ico ')]]")); + await context.ClickReliablyOnAsync(By.ClassName("mediaFieldSelectButton")); + }; + byIcon ??= By.CssSelector("head link[href*='/media/Icons/oc-favicon.ico'][rel='shortcut icon'][type='image/x-icon']"); + + await context.ClickReliablyOnAsync(By.CssSelector("#Editor .btn-group .btn-secondary:not([disabled]):not(.disabled)")); + await selectFromMediaLibraryAsync(); + + await context.ClickReliablyOnAsync(By.ClassName("save")); + context.ShouldBeSuccess("Site settings updated successfully."); + + await context.GoToHomePageAsync(); + context.Exists(byIcon.OfAnyVisibility()); + context.Missing(By.CssSelector("#navigation .menuWidget__content")); + } } diff --git a/Lombiq.BaseTheme/Attributes/DerivedThemeAttribute.cs b/Lombiq.BaseTheme/Attributes/DerivedThemeAttribute.cs new file mode 100644 index 0000000..d5f47a5 --- /dev/null +++ b/Lombiq.BaseTheme/Attributes/DerivedThemeAttribute.cs @@ -0,0 +1,20 @@ +using Lombiq.BaseTheme.Constants; +using OrchardCore.DisplayManagement.Manifest; +using OrchardCore.ResourceManagement; +using System; +using System.Collections.Generic; + +namespace Lombiq.BaseTheme.Attributes; + +/// +/// Indicates a theme derived from Lombiq.BaseTheme. +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] +public sealed class DerivedThemeAttribute : ThemeAttribute +{ + public IEnumerable Links { get; set; } + public string Favicon { get; set; } + + public DerivedThemeAttribute() => + BaseTheme = FeatureIds.BaseTheme; +} diff --git a/Lombiq.BaseTheme/Controllers/AdminController.cs b/Lombiq.BaseTheme/Controllers/AdminController.cs new file mode 100644 index 0000000..1379b4c --- /dev/null +++ b/Lombiq.BaseTheme/Controllers/AdminController.cs @@ -0,0 +1,123 @@ +using Lombiq.BaseTheme.Models; +using Lombiq.BaseTheme.ViewModels; +using Lombiq.HelpfulExtensions.Extensions.ContentTypes; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Localization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OrchardCore.ContentManagement; +using OrchardCore.ContentManagement.Metadata.Models; +using OrchardCore.DisplayManagement; +using OrchardCore.DisplayManagement.Notify; +using OrchardCore.Entities; +using OrchardCore.Media.Fields; +using OrchardCore.Media.Settings; +using OrchardCore.Media.ViewModels; +using OrchardCore.Modules; +using OrchardCore.Settings; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Lombiq.BaseTheme.Controllers; + +// This controller is there for editing the BaseThemeSettings. We can't use a site settings driver for this, because you +// can't declare admin-accessible shapes in a site theme. +public class AdminController : Controller +{ + private readonly IClock _clock; + private readonly INotifier _notifier; + private readonly ISiteService _siteService; + private readonly IShapeFactory _shapeFactory; + private readonly IHtmlLocalizer H; + + public AdminController( + IClock clock, + INotifier notifier, + ISiteService siteService, + IShapeFactory shapeFactory, + IHtmlLocalizer htmlLocalizer) + { + _clock = clock; + _notifier = notifier; + _siteService = siteService; + _shapeFactory = shapeFactory; + + H = htmlLocalizer; + } + + public async Task Index() + { + var section = (await _siteService.LoadSiteSettingsAsync()).As(); + + var model = new BaseThemeSettingsViewModel + { + HideMenu = section.HideMenu, + Icon = section.Icon, + Editor = await _shapeFactory.CreateAsync("MediaField_Edit", editor => + { + var part = CreatePart(section); + + editor.Paths = string.IsNullOrWhiteSpace(section.Icon) + ? "[]" + : JsonConvert.SerializeObject(new[] { new { path = section.Icon } }); + editor.Field = part.Icon; + editor.Part = part; + editor.PartFieldDefinition = new ContentPartFieldDefinition( + new ContentFieldDefinition(nameof(BaseThemeSettingsPart.Icon)), + nameof(BaseThemeSettingsPart.Icon), + JObject.FromObject(new Dictionary + { + [nameof(MediaFieldSettings)] = new MediaFieldSettings { Multiple = false }, + })) + { + PartDefinition = new ContentPartDefinition(nameof(BaseThemeSettingsPart)), + }; + }), + }; + + model.Editor.Metadata.OnDisplaying(context => context.DisplayContext.HtmlFieldPrefix = nameof(model.Editor)); + + return View(model); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Update([FromForm] BaseThemeSettingsViewModel viewModel) + { + var siteSettings = await _siteService.LoadSiteSettingsAsync(); + siteSettings.Alter(nameof(BaseThemeSettings), settings => + { + settings.TimeStamp = _clock.UtcNow.Ticks; + settings.Icon = viewModel.Icon; + settings.HideMenu = viewModel.HideMenu; + }); + + await _siteService.UpdateSiteSettingsAsync(siteSettings); + await _notifier.SuccessAsync(H["Site settings updated successfully."]); + + return RedirectToAction(nameof(Index)); + } + + private static BaseThemeSettingsPart CreatePart(BaseThemeSettings section) + { + var content = new ContentItem { ContentType = ContentTypes.Empty }; + + content.Weld(new BaseThemeSettingsPart + { + ContentItem = content, + Icon = new MediaField + { + ContentItem = content, + MediaTexts = new[] { section.Icon }, + Paths = new[] { section.Icon }, + }, + }); + + return content.As(); + } + + public class BaseThemeSettingsPart : ContentPart + { + public MediaField Icon { get; set; } = new(); + } +} diff --git a/Lombiq.BaseTheme/Lombiq.BaseTheme.csproj b/Lombiq.BaseTheme/Lombiq.BaseTheme.csproj index 73e7284..12fac32 100644 --- a/Lombiq.BaseTheme/Lombiq.BaseTheme.csproj +++ b/Lombiq.BaseTheme/Lombiq.BaseTheme.csproj @@ -34,6 +34,7 @@ + diff --git a/Lombiq.BaseTheme/Manifest.cs b/Lombiq.BaseTheme/Manifest.cs index 3d95563..0aedbb2 100644 --- a/Lombiq.BaseTheme/Manifest.cs +++ b/Lombiq.BaseTheme/Manifest.cs @@ -8,5 +8,9 @@ Description = "The base frontend theme for shared code that is not specific to a specific project's theme." + "Warning: themes using this as the base remove the stock Bootstrap resource. If you switch to a different " + "theme, please reload the tenant from Configuration → Tenants in the admin menu.", - Dependencies = new[] { Lombiq.HelpfulExtensions.FeatureIds.Widgets } + Dependencies = new[] + { + Lombiq.HelpfulExtensions.FeatureIds.ContentTypes, + Lombiq.HelpfulExtensions.FeatureIds.Widgets, + } )] diff --git a/Lombiq.BaseTheme/Models/BaseThemeSettings.cs b/Lombiq.BaseTheme/Models/BaseThemeSettings.cs new file mode 100644 index 0000000..7f8592b --- /dev/null +++ b/Lombiq.BaseTheme/Models/BaseThemeSettings.cs @@ -0,0 +1,8 @@ +namespace Lombiq.BaseTheme.Models; + +public class BaseThemeSettings +{ + public long TimeStamp { get; set; } + public string Icon { get; set; } + public bool HideMenu { get; set; } +} diff --git a/Lombiq.BaseTheme/Navigation/BaseThemeSettingsAdminMenu.cs b/Lombiq.BaseTheme/Navigation/BaseThemeSettingsAdminMenu.cs new file mode 100644 index 0000000..cb49508 --- /dev/null +++ b/Lombiq.BaseTheme/Navigation/BaseThemeSettingsAdminMenu.cs @@ -0,0 +1,27 @@ +using Lombiq.BaseTheme.Controllers; +using Lombiq.BaseTheme.Permissions; +using Lombiq.HelpfulLibraries.OrchardCore.Navigation; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Localization; +using OrchardCore.Navigation; + +namespace Lombiq.BaseTheme.Navigation; + +public class BaseThemeSettingsAdminMenu : AdminMenuNavigationProviderBase +{ + public BaseThemeSettingsAdminMenu( + IHttpContextAccessor hca, + IStringLocalizer stringLocalizer) + : base(hca, stringLocalizer) + { + } + + protected override void Build(NavigationBuilder builder) => + builder.Add(T["Configuration"], configuration => configuration + .Add(T["Settings"], settings => settings + .Add(T["Base Theme"], T["Base Theme"], baseTheme => baseTheme + .ActionTask(_hca.HttpContext, controller => controller.Index()) + .Permission(BaseThemeSettingsPermissions.ManageBaseThemeSettings) + .LocalNav() + ))); +} diff --git a/Lombiq.BaseTheme/Permissions/BaseThemeSettingsPermissions.cs b/Lombiq.BaseTheme/Permissions/BaseThemeSettingsPermissions.cs new file mode 100644 index 0000000..4512f0a --- /dev/null +++ b/Lombiq.BaseTheme/Permissions/BaseThemeSettingsPermissions.cs @@ -0,0 +1,13 @@ +using Lombiq.HelpfulLibraries.OrchardCore.Users; +using OrchardCore.Security.Permissions; +using System.Collections.Generic; + +namespace Lombiq.BaseTheme.Permissions; + +public class BaseThemeSettingsPermissions : AdminPermissionBase +{ + public static readonly Permission ManageBaseThemeSettings = + new(nameof(ManageBaseThemeSettings), "Manage Lombiq.BaseTheme Settings."); + + protected override IEnumerable AdminPermissions => new[] { ManageBaseThemeSettings }; +} diff --git a/Lombiq.BaseTheme/Services/IconResourceFilter.cs b/Lombiq.BaseTheme/Services/IconResourceFilter.cs new file mode 100644 index 0000000..afd31e4 --- /dev/null +++ b/Lombiq.BaseTheme/Services/IconResourceFilter.cs @@ -0,0 +1,70 @@ +using Lombiq.BaseTheme.Attributes; +using Lombiq.BaseTheme.Models; +using Lombiq.HelpfulLibraries.OrchardCore.ResourceManagement; +using OrchardCore; +using OrchardCore.Entities; +using OrchardCore.Media; +using OrchardCore.ResourceManagement; +using OrchardCore.Settings; +using OrchardCore.Themes.Services; +using System; +using System.Collections.Generic; + +namespace Lombiq.BaseTheme.Services; + +public class IconResourceFilter : IResourceFilterProvider +{ + private readonly IOrchardHelper _orchardHelper; + private readonly IMediaFileStore _mediaFileStore; + private readonly ISiteService _siteService; + private readonly ISiteThemeService _siteThemeService; + + public IconResourceFilter( + IOrchardHelper orchardHelper, + IMediaFileStore mediaFileStore, + ISiteService siteService, + ISiteThemeService siteThemeService) + { + _orchardHelper = orchardHelper; + _mediaFileStore = mediaFileStore; + _siteService = siteService; + _siteThemeService = siteThemeService; + } + + public void AddResourceFilter(ResourceFilterBuilder builder) => + builder + .Always() + .ExecuteTask(async resourceManager => + { + // Use static link resources as a fallback. + var currentTheme = await _siteThemeService.GetSiteThemeAsync(); + if (currentTheme.Manifest.ModuleInfo is DerivedThemeAttribute theme) + { + if (!string.IsNullOrEmpty(theme.Favicon)) + { + var icon = theme.Favicon.StartsWithOrdinal("~/") + ? _orchardHelper.ResourceUrl(theme.Favicon) + : theme.Favicon; + AddIcon(resourceManager, icon); + } + + theme.Links?.ForEach(resourceManager.RegisterLink); + } + + // If the site setting icon is set, that should take priority. + if ((await _siteService.GetSiteSettingsAsync()).As() is { } settings && + !string.IsNullOrEmpty(settings.Icon)) + { + var path = _mediaFileStore.MapPathToPublicUrl(settings.Icon); + AddIcon(resourceManager, $"{path}?at={settings.TimeStamp.ToTechnicalString()}"); + } + }); + + private static void AddIcon(IResourceManager resourceManager, string href) => + resourceManager.RegisterLink(new LinkEntry + { + Href = href, + Rel = "shortcut icon", + Type = "image/x-icon", + }); +} diff --git a/Lombiq.BaseTheme/Services/MainMenuWidgetFilter.cs b/Lombiq.BaseTheme/Services/MainMenuWidgetFilter.cs index 6dc4a01..b7fb21f 100644 --- a/Lombiq.BaseTheme/Services/MainMenuWidgetFilter.cs +++ b/Lombiq.BaseTheme/Services/MainMenuWidgetFilter.cs @@ -1,4 +1,6 @@ -using Lombiq.HelpfulExtensions.Extensions.Widgets; +using Lombiq.BaseTheme.Constants; +using Lombiq.BaseTheme.Models; +using Lombiq.HelpfulExtensions.Extensions.Widgets; using Lombiq.HelpfulExtensions.Extensions.Widgets.ViewModels; using Lombiq.HelpfulLibraries.OrchardCore.Mvc; using Lombiq.HelpfulLibraries.OrchardCore.Navigation; @@ -6,9 +8,10 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using OrchardCore.DisplayManagement; using OrchardCore.DisplayManagement.Layout; +using OrchardCore.Entities; using OrchardCore.Navigation; +using OrchardCore.Settings; using System.Threading.Tasks; -using static Lombiq.BaseTheme.Constants.ZoneNames; namespace Lombiq.BaseTheme.Services; @@ -17,8 +20,9 @@ public class MainMenuWidgetFilter : WidgetFilterBase private readonly INavigationManager _navigationManager; private readonly IActionContextAccessor _actionContextAccessor; private readonly ICssClassHolder _cssClassHolder; + private readonly ISiteService _siteService; - protected override string ZoneName => Navigation; + protected override string ZoneName => ZoneNames.Navigation; protected override string ViewName => WidgetTypes.MenuWidget; protected override bool FrontEndOnly => true; @@ -28,19 +32,24 @@ public MainMenuWidgetFilter( IShapeFactory shapeFactory, INavigationManager navigationManager, IActionContextAccessor actionContextAccessor, - ICssClassHolder cssClassHolder) + ICssClassHolder cssClassHolder, + ISiteService siteService) : base(requiredPermission: null, authorizationService, layoutAccessor, shapeFactory) { _navigationManager = navigationManager; _actionContextAccessor = actionContextAccessor; _cssClassHolder = cssClassHolder; + _siteService = siteService; } protected override async Task GetViewModelAsync() { + var siteSettings = await _siteService.GetSiteSettingsAsync(); + if (siteSettings.As()?.HideMenu == true) return null; + // Add the