diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs index ea73a5673f..5811cc22c1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs @@ -1,6 +1,11 @@ -namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout; +using System; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout; public static class StandardLayouts { public const string Application = "Application"; + public const string Account = "Account"; + public const string Public = "Public"; + public const string Empty = "Empty"; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs new file mode 100644 index 0000000000..6bba37e0f8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class AbpThemingOptions +{ + public ThemeDictionary Themes { get; } + + public string DefaultThemeName { get; set; } + + public AbpThemingOptions() + { + Themes = new ThemeDictionary(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs new file mode 100644 index 0000000000..c85a25bf19 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class DefaultThemeManager : IThemeManager, IScopedDependency, IServiceProviderAccessor +{ + public IServiceProvider ServiceProvider { get; } + public ITheme CurrentTheme => GetCurrentTheme(); + + private ITheme _currentTheme; + + protected IThemeSelector ThemeSelector { get; } + + public DefaultThemeManager( + IServiceProvider serviceProvider, + IThemeSelector themeSelector) + { + ServiceProvider = serviceProvider; + ThemeSelector = themeSelector; + } + + protected virtual ITheme GetCurrentTheme() + { + if (_currentTheme != null) + { + return _currentTheme; + } + + _currentTheme = (ITheme)ServiceProvider.GetRequiredService(ThemeSelector.GetCurrentThemeInfo().ThemeType); + return CurrentTheme; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs new file mode 100644 index 0000000000..7e08ed1d97 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class DefaultThemeSelector : IThemeSelector, ITransientDependency +{ + protected AbpThemingOptions Options { get; } + + public DefaultThemeSelector(IOptions options) + { + Options = options.Value; + } + + public virtual ThemeInfo GetCurrentThemeInfo() + { + if (!Options.Themes.Any()) + { + throw new AbpException($"No theme registered! Use {nameof(AbpThemingOptions)} to register themes."); + } + + if (Options.DefaultThemeName == null) + { + return Options.Themes.Values.First(); + } + + var themeInfo = Options.Themes.Values.FirstOrDefault(t => t.Name == Options.DefaultThemeName); + if (themeInfo == null) + { + throw new AbpException("Default theme is configured but it's not found in the registered themes: " + Options.DefaultThemeName); + } + + return themeInfo; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs new file mode 100644 index 0000000000..f379252f52 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs @@ -0,0 +1,8 @@ +using System; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface ITheme +{ + Type GetLayout(string name, bool fallbackToDefault = true); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs new file mode 100644 index 0000000000..75bb35e8d8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface IThemeManager +{ + ITheme CurrentTheme { get; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs new file mode 100644 index 0000000000..6cf92c4cfa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface IThemeSelector +{ + ThemeInfo GetCurrentThemeInfo(); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs new file mode 100644 index 0000000000..bf33a1f083 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class ThemeDictionary : Dictionary +{ + public ThemeInfo Add() + where TTheme : ITheme + { + return Add(typeof(TTheme)); + } + + public ThemeInfo Add(Type themeType) + { + if (ContainsKey(themeType)) + { + throw new AbpException("This theme is already added before: " + themeType.AssemblyQualifiedName); + } + + return this[themeType] = new ThemeInfo(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs new file mode 100644 index 0000000000..3236f491fe --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.Theming.Layout; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public static class ThemeExtensions +{ + public static Type GetApplicationLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Application, fallbackToDefault); + } + + public static Type GetAccountLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Account, fallbackToDefault); + } + + public static Type GetPublicLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Public, fallbackToDefault); + } + + public static Type GetEmptyLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Empty, fallbackToDefault); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs new file mode 100644 index 0000000000..a17f6c08ba --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs @@ -0,0 +1,24 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class ThemeInfo +{ + public Type ThemeType { get; } + + public string Name { get; } + + public ThemeInfo([NotNull] Type themeType) + { + Check.NotNull(themeType, nameof(themeType)); + + if (!typeof(ITheme).IsAssignableFrom(themeType)) + { + throw new AbpException($"Given {nameof(themeType)} ({themeType.AssemblyQualifiedName}) should be type of {typeof(ITheme).AssemblyQualifiedName}"); + } + + ThemeType = themeType; + Name = ThemeNameAttribute.GetName(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs new file mode 100644 index 0000000000..fe4e6de371 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +[AttributeUsage(AttributeTargets.Class)] +public class ThemeNameAttribute : Attribute +{ + public string Name { get; set; } + + public ThemeNameAttribute(string name) + { + Name = name; + } + + public static string GetName(Type themeType) + { + return themeType + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault()?.Name ?? themeType.Name; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs index f20432c665..a983d239e3 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs @@ -1,12 +1,24 @@ using Volo.Abp.AspNetCore.Components.Web.Theming; +using Volo.Abp.AspNetCore.Components.Web.Theming.Theming; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme; [DependsOn( typeof(AbpAspNetCoreComponentsWebThemingModule) - )] +)] public class AbpAspNetCoreComponentsWebBasicThemeModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Themes.Add(); -} + if (options.DefaultThemeName == null) + { + options.DefaultThemeName = BasicTheme.Name; + } + }); + } +} \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs new file mode 100644 index 0000000000..2017cc8001 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic; +using Volo.Abp.AspNetCore.Components.Web.Theming.Layout; +using Volo.Abp.AspNetCore.Components.Web.Theming.Theming; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme; + +[ThemeName(Name)] +public class BasicTheme : ITheme, ITransientDependency +{ + public const string Name = "Basic"; + + public virtual Type GetLayout(string name, bool fallbackToDefault = true) + { + switch (name) + { + case StandardLayouts.Application: + case StandardLayouts.Account: + case StandardLayouts.Empty: + return typeof(MainLayout); + default: + return fallbackToDefault ? typeof(MainLayout) : typeof(NullLayout); + } + } +} \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor new file mode 100644 index 0000000000..4e04080071 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body \ No newline at end of file