Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSOE-652: Better favicon inclusion in Lombiq.BaseTheme #79

Merged
merged 25 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b04a0fc
Revert "revert last 3 commits"
sarahelsaig Jun 25, 2023
ec69585
Merge branch 'dev' into issue/OSOE-652
sarahelsaig Jun 30, 2023
770d2c4
Merge remote-tracking branch 'origin/dev' into issue/OSOE-652
sarahelsaig Jul 1, 2023
a396556
Update action.
sarahelsaig Jul 1, 2023
20151ef
add model
sarahelsaig Jul 3, 2023
5edddd3
Fix editor metadata.
sarahelsaig Jul 3, 2023
0b994f8
Make the site icon use the site setting.
sarahelsaig Jul 3, 2023
5e8454d
Don't build menu if the site setting is disabled.
sarahelsaig Jul 3, 2023
41db82f
Add timestamp to the site setting to track when it was last modified.
sarahelsaig Jul 3, 2023
e28c067
Use the timestamp to ensure the icon is always up to date.
sarahelsaig Jul 3, 2023
f56f599
Don't redirect favicon.ico.
sarahelsaig Jul 3, 2023
16d650c
Don't show the error box even for a split second.
sarahelsaig Jul 3, 2023
312bf88
Add recipe migrations class.
sarahelsaig Jul 3, 2023
9f02ae6
Move icon.
sarahelsaig Jul 3, 2023
ad3151a
Add recipe migration for media.
sarahelsaig Jul 3, 2023
620c8e8
Add site settings to the recipe.
sarahelsaig Jul 3, 2023
28698a1
Inject the icon via ResourceFilter instead.
sarahelsaig Jul 3, 2023
deaec45
Training docs.
sarahelsaig Jul 3, 2023
290b9fc
Fix spacing.
sarahelsaig Jul 3, 2023
8b2fd45
Add static favicons too.
sarahelsaig Jul 4, 2023
7b32dd9
Add UI test.
sarahelsaig Jul 4, 2023
1432cb5
Fix whitespace.
sarahelsaig Jul 4, 2023
598d933
OfAnyVisibility
sarahelsaig Jul 4, 2023
235bced
Merge remote-tracking branch 'origin/dev' into issue/OSOE-652
sarahelsaig Jul 4, 2023
5d1f29b
Fix UI test.
sarahelsaig Jul 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions Lombiq.BaseTheme.Samples/Manifest.cs
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions Lombiq.BaseTheme.Samples/Migrations/RecipeMigrations.cs
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions Lombiq.BaseTheme.Samples/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 6 additions & 15 deletions Lombiq.BaseTheme.Samples/Startup.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -24,24 +24,15 @@ public override void ConfigureServices(IServiceCollection services)

// This service provides configuration to the ResourceFilterMiddleware.
services.AddScoped<IResourceFilterProvider, ResourceFilters>();

// The recipe migration is used to add the media items and Base Theme settings required for the correct favicon.
services.AddDataMigration<RecipeMigrations>();
}

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 <link> 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
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Atata;
using Lombiq.Tests.UI.Extensions;
using Lombiq.Tests.UI.Services;
using OpenQA.Selenium;
Expand Down Expand Up @@ -87,4 +88,35 @@ await context.SetContentPickerByDisplayTextAsync(
.Trim()
.ShouldBe("My Content");
}

public static async Task TestBaseThemeSiteSettingsAsync(
this UITestContext context,
Func<Task> 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"));
}
}
20 changes: 20 additions & 0 deletions Lombiq.BaseTheme/Attributes/DerivedThemeAttribute.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Indicates a theme derived from <c>Lombiq.BaseTheme</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public sealed class DerivedThemeAttribute : ThemeAttribute
{
public IEnumerable<LinkEntry> Links { get; set; }
public string Favicon { get; set; }

public DerivedThemeAttribute() =>
BaseTheme = FeatureIds.BaseTheme;
}
123 changes: 123 additions & 0 deletions Lombiq.BaseTheme/Controllers/AdminController.cs
Original file line number Diff line number Diff line change
@@ -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<AdminController> H;

public AdminController(
IClock clock,
INotifier notifier,
ISiteService siteService,
IShapeFactory shapeFactory,
IHtmlLocalizer<AdminController> htmlLocalizer)
{
_clock = clock;
_notifier = notifier;
_siteService = siteService;
_shapeFactory = shapeFactory;

H = htmlLocalizer;
}

public async Task<IActionResult> Index()
{
var section = (await _siteService.LoadSiteSettingsAsync()).As<BaseThemeSettings>();

var model = new BaseThemeSettingsViewModel
{
HideMenu = section.HideMenu,
Icon = section.Icon,
Editor = await _shapeFactory.CreateAsync<EditMediaFieldViewModel>("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<string, object>
{
[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<IActionResult> Update([FromForm] BaseThemeSettingsViewModel viewModel)
{
var siteSettings = await _siteService.LoadSiteSettingsAsync();
siteSettings.Alter<BaseThemeSettings>(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<BaseThemeSettingsPart>();
}

public class BaseThemeSettingsPart : ContentPart
{
public MediaField Icon { get; set; } = new();
}
}
1 change: 1 addition & 0 deletions Lombiq.BaseTheme/Lombiq.BaseTheme.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<ItemGroup>
<PackageReference Include="OrchardCore.Theme.Targets" Version="1.6.0" />
<PackageReference Include="OrchardCore.Menu" Version="1.6.0" />
<PackageReference Include="OrchardCore.Media" Version="1.6.0" />
<PackageReference Include="OrchardCore.ContentManagement" Version="1.6.0" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.6.0" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.6.0" />
Expand Down
6 changes: 5 additions & 1 deletion Lombiq.BaseTheme/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
)]
8 changes: 8 additions & 0 deletions Lombiq.BaseTheme/Models/BaseThemeSettings.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
27 changes: 27 additions & 0 deletions Lombiq.BaseTheme/Navigation/BaseThemeSettingsAdminMenu.cs
Original file line number Diff line number Diff line change
@@ -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<BaseThemeSettingsAdminMenu> 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<AdminController>(_hca.HttpContext, controller => controller.Index())
.Permission(BaseThemeSettingsPermissions.ManageBaseThemeSettings)
.LocalNav()
)));
}
13 changes: 13 additions & 0 deletions Lombiq.BaseTheme/Permissions/BaseThemeSettingsPermissions.cs
Original file line number Diff line number Diff line change
@@ -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<Permission> AdminPermissions => new[] { ManageBaseThemeSettings };
}
Loading