From e41d9008b3b805cfad9561434156e92ed1c99c2c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 May 2023 18:08:15 -0400 Subject: [PATCH] ability to specify Resources in IModule and ITheme interfaces,, fixed module settings for personalized pages --- .../Modules/Admin/Modules/Settings.razor | 14 +- Oqtane.Client/UI/SiteRouter.razor | 894 +++++++++--------- Oqtane.Client/UI/ThemeBuilder.razor | 4 +- Oqtane.Server/Controllers/SiteController.cs | 7 +- .../Repository/Interfaces/IThemeRepository.cs | 1 + .../Repository/ModuleDefinitionRepository.cs | 1 + Oqtane.Server/Repository/ThemeRepository.cs | 18 + Oqtane.Shared/Models/ModuleDefinition.cs | 4 + Oqtane.Shared/Models/Resource.cs | 2 +- Oqtane.Shared/Models/Site.cs | 3 + Oqtane.Shared/Models/Theme.cs | 4 + 11 files changed, 506 insertions(+), 446 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index 68df3167..6767f011 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -44,12 +44,20 @@
diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index a68bd1c1..8465faa8 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -22,201 +22,201 @@ @DynamicComponent @code { - private string _absoluteUri; - private bool _navigationInterceptionEnabled; - private PageState _pagestate; - private string _error = ""; + private string _absoluteUri; + private bool _navigationInterceptionEnabled; + private PageState _pagestate; + private string _error = ""; - [Parameter] - public string Runtime { get; set; } + [Parameter] + public string Runtime { get; set; } - [Parameter] - public string RenderMode { get; set; } + [Parameter] + public string RenderMode { get; set; } - [Parameter] - public int VisitorId { get; set; } + [Parameter] + public int VisitorId { get; set; } - [CascadingParameter] - PageState PageState { get; set; } + [CascadingParameter] + PageState PageState { get; set; } - [Parameter] - public Action OnStateChange { get; set; } + [Parameter] + public Action OnStateChange { get; set; } - private RenderFragment DynamicComponent { get; set; } + private RenderFragment DynamicComponent { get; set; } - protected override void OnInitialized() - { - _absoluteUri = NavigationManager.Uri; - NavigationManager.LocationChanged += LocationChanged; + protected override void OnInitialized() + { + _absoluteUri = NavigationManager.Uri; + NavigationManager.LocationChanged += LocationChanged; - DynamicComponent = builder => - { - if (PageState != null) - { - builder.OpenComponent(0, Type.GetType(Constants.PageComponent)); - builder.CloseComponent(); - } - }; - } + DynamicComponent = builder => + { + if (PageState != null) + { + builder.OpenComponent(0, Type.GetType(Constants.PageComponent)); + builder.CloseComponent(); + } + }; + } - public void Dispose() - { - NavigationManager.LocationChanged -= LocationChanged; - } + public void Dispose() + { + NavigationManager.LocationChanged -= LocationChanged; + } - protected override async Task OnParametersSetAsync() - { - if (PageState == null) - { - await Refresh(); - } - } + protected override async Task OnParametersSetAsync() + { + if (PageState == null) + { + await Refresh(); + } + } - [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] - private async Task Refresh() - { - Site site; - Page page; - User user = null; - var editmode = false; - var refresh = false; - var lastsyncdate = DateTime.UtcNow.AddHours(-1); - var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime); - _error = ""; + [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] + private async Task Refresh() + { + Site site; + Page page; + User user = null; + var editmode = false; + var refresh = false; + var lastsyncdate = DateTime.UtcNow.AddHours(-1); + var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime); + _error = ""; - Route route = new Route(_absoluteUri, SiteState.Alias.Path); - int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1; - var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction; - var querystring = ParseQueryString(route.Query); - var returnurl = ""; - if (querystring.ContainsKey("returnurl")) - { - returnurl = WebUtility.UrlDecode(querystring["returnurl"]); - } + Route route = new Route(_absoluteUri, SiteState.Alias.Path); + int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1; + var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction; + var querystring = ParseQueryString(route.Query); + var returnurl = ""; + if (querystring.ContainsKey("returnurl")) + { + returnurl = WebUtility.UrlDecode(querystring["returnurl"]); + } - // reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias - if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path))) - { - if (querystring.ContainsKey("reload") && querystring["reload"] == "post") - { - // post back so that the cookies are set correctly - required on any change to the principal - var interop = new Interop(JSRuntime); - var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) }; - string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/"); - await interop.SubmitForm(url, fields); - return; - } - else - { - NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true); - return; - } - } + // reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias + if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path))) + { + if (querystring.ContainsKey("reload") && querystring["reload"] == "post") + { + // post back so that the cookies are set correctly - required on any change to the principal + var interop = new Interop(JSRuntime); + var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) }; + string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/"); + await interop.SubmitForm(url, fields); + return; + } + else + { + NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true); + return; + } + } - // the refresh parameter is used to refresh the client-side PageState - if (querystring.ContainsKey("refresh")) - { - refresh = true; - } + // the refresh parameter is used to refresh the client-side PageState + if (querystring.ContainsKey("refresh")) + { + refresh = true; + } - if (PageState != null) - { - editmode = PageState.EditMode; - lastsyncdate = PageState.LastSyncDate; - } - if (PageState?.Page.Path != route.PagePath) - { - editmode = false; // reset edit mode when navigating to different page - } + if (PageState != null) + { + editmode = PageState.EditMode; + lastsyncdate = PageState.LastSyncDate; + } + if (PageState?.Page.Path != route.PagePath) + { + editmode = false; // reset edit mode when navigating to different page + } - // get user - if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) - { - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - if (authState.User.Identity.IsAuthenticated) - { - user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId); - if (user != null) - { - user.IsAuthenticated = authState.User.Identity.IsAuthenticated; - } - } - } - else - { - user = PageState.User; - } + // get user + if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + if (authState.User.Identity.IsAuthenticated) + { + user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId); + if (user != null) + { + user.IsAuthenticated = authState.User.Identity.IsAuthenticated; + } + } + } + else + { + user = PageState.User; + } - // process any sync events - var sync = await SyncService.GetSyncAsync(lastsyncdate); - lastsyncdate = sync.SyncDate; - if (sync.SyncEvents.Any()) - { - // reload client application if server was restarted or site runtime/rendermode was modified - if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload))) - { - NavigationManager.NavigateTo(_absoluteUri, true); - return; - } - // when site information has changed the PageState needs to be refreshed - if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId)) - { - refresh = true; - } - // when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed - if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId)) - { - refresh = true; - } - } + // process any sync events + var sync = await SyncService.GetSyncAsync(lastsyncdate); + lastsyncdate = sync.SyncDate; + if (sync.SyncEvents.Any()) + { + // reload client application if server was restarted or site runtime/rendermode was modified + if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload))) + { + NavigationManager.NavigateTo(_absoluteUri, true); + return; + } + // when site information has changed the PageState needs to be refreshed + if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId)) + { + refresh = true; + } + // when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed + if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId)) + { + refresh = true; + } + } - if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) - { - site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId); - refresh = true; - } - else - { - site = PageState.Site; - } + if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) + { + site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId); + refresh = true; + } + else + { + site = PageState.Site; + } - if (site != null) - { - if (PageState == null || refresh || PageState.Page.Path != route.PagePath) - { - page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); - } - else - { - page = PageState.Page; - } + if (site != null) + { + if (PageState == null || refresh || PageState.Page.Path != route.PagePath) + { + page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); + } + else + { + page = PageState.Page; + } - if (page == null && route.PagePath == "") // naked path refers to site home page - { - if (site.HomePageId != null) - { - page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId); - } - if (page == null) - { - // fallback to use the first page in the collection - page = site.Pages.FirstOrDefault(); - } - } + if (page == null && route.PagePath == "") // naked path refers to site home page + { + if (site.HomePageId != null) + { + page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId); + } + if (page == null) + { + // fallback to use the first page in the collection + page = site.Pages.FirstOrDefault(); + } + } - if (page != null) - { - // check if user is authorized to view page - if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList)) - { - // load additional metadata for current page - page = await ProcessPage(page, site, user); + if (page != null) + { + // check if user is authorized to view page + if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList)) + { + // load additional metadata for current page + page = await ProcessPage(page, site, user, SiteState.Alias); - // load additional metadata for modules - (page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); + // load additional metadata for modules + (page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias); - // populate page state (which acts as a client-side cache for subsequent requests) - _pagestate = new PageState + // populate page state (which acts as a client-side cache for subsequent requests) + _pagestate = new PageState { Alias = SiteState.Alias, Site = site, @@ -236,295 +236,311 @@ ReturnUrl = returnurl }; - OnStateChange?.Invoke(_pagestate); - await ScrollToFragment(_pagestate.Uri); - } - } - else // page not found - { - // look for url mapping - var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath); - if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) - { - var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl; - NavigationManager.NavigateTo(url, false); - } - else // not mapped - { - if (user == null) - { - // redirect to login page if user not logged in as they may need to be authenticated - NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); - } - else - { - if (route.PagePath != "404") - { - // redirect to 404 page - NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); - } - else - { - // redirect to home page as a fallback - NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); - } - } - } - } - } - else - { - // site does not exist - } - } + OnStateChange?.Invoke(_pagestate); + await ScrollToFragment(_pagestate.Uri); + } + } + else // page not found + { + // look for url mapping + var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath); + if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) + { + var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl; + NavigationManager.NavigateTo(url, false); + } + else // not mapped + { + if (user == null) + { + // redirect to login page if user not logged in as they may need to be authenticated + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); + } + else + { + if (route.PagePath != "404") + { + // redirect to 404 page + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); + } + else + { + // redirect to home page as a fallback + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); + } + } + } + } + } + else + { + // site does not exist + } + } - private async void LocationChanged(object sender, LocationChangedEventArgs args) - { - _absoluteUri = args.Location; - await Refresh(); - } + private async void LocationChanged(object sender, LocationChangedEventArgs args) + { + _absoluteUri = args.Location; + await Refresh(); + } - Task IHandleAfterRender.OnAfterRenderAsync() - { - if (!_navigationInterceptionEnabled) - { - _navigationInterceptionEnabled = true; - return NavigationInterception.EnableNavigationInterceptionAsync(); - } - return Task.CompletedTask; - } + Task IHandleAfterRender.OnAfterRenderAsync() + { + if (!_navigationInterceptionEnabled) + { + _navigationInterceptionEnabled = true; + return NavigationInterception.EnableNavigationInterceptionAsync(); + } + return Task.CompletedTask; + } - private Dictionary ParseQueryString(string query) - { - Dictionary querystring = new Dictionary(StringComparer.OrdinalIgnoreCase); // case insensistive keys - if (!string.IsNullOrEmpty(query)) - { - if (query.StartsWith("?")) - { - query = query.Substring(1); // ignore "?" - } - foreach (string kvp in query.Split('&', StringSplitOptions.RemoveEmptyEntries)) - { - if (kvp != "") - { - if (kvp.Contains("=")) - { - string[] pair = kvp.Split('='); - if (!querystring.ContainsKey(pair[0])) - { - querystring.Add(pair[0], pair[1]); - } - } - else - { - if (!querystring.ContainsKey(kvp)) - { - querystring.Add(kvp, "true"); // default parameter when no value is provided - } - } - } - } - } - return querystring; - } + private Dictionary ParseQueryString(string query) + { + Dictionary querystring = new Dictionary(StringComparer.OrdinalIgnoreCase); // case insensistive keys + if (!string.IsNullOrEmpty(query)) + { + if (query.StartsWith("?")) + { + query = query.Substring(1); // ignore "?" + } + foreach (string kvp in query.Split('&', StringSplitOptions.RemoveEmptyEntries)) + { + if (kvp != "") + { + if (kvp.Contains("=")) + { + string[] pair = kvp.Split('='); + if (!querystring.ContainsKey(pair[0])) + { + querystring.Add(pair[0], pair[1]); + } + } + else + { + if (!querystring.ContainsKey(kvp)) + { + querystring.Add(kvp, "true"); // default parameter when no value is provided + } + } + } + } + } + return querystring; + } - private async Task ProcessPage(Page page, Site site, User user) - { - try - { - if (page.IsPersonalizable && user != null) - { - // load the personalized page - page = await PageService.GetPageAsync(page.PageId, user.UserId); - } + private async Task ProcessPage(Page page, Site site, User user, Alias alias) + { + try + { + if (page.IsPersonalizable && user != null) + { + // load the personalized page + page = await PageService.GetPageAsync(page.PageId, user.UserId); + } - if (string.IsNullOrEmpty(page.ThemeType)) - { - page.ThemeType = site.DefaultThemeType; - } + if (string.IsNullOrEmpty(page.ThemeType)) + { + page.ThemeType = site.DefaultThemeType; + } - page.Panes = new List(); - page.Resources = new List(); + page.Panes = new List(); + page.Resources = new List(); - string panes = ""; - Type themetype = Type.GetType(page.ThemeType); - if (themetype == null) - { - // fallback - page.ThemeType = Constants.DefaultTheme; - themetype = Type.GetType(Constants.DefaultTheme); - } - if (themetype != null) - { - var themeobject = Activator.CreateInstance(themetype) as IThemeControl; - if (themeobject != null) - { - if (!string.IsNullOrEmpty(themeobject.Panes)) - { - panes = themeobject.Panes; - } - page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page); - } - } - if (!string.IsNullOrEmpty(panes)) - { - page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(); - if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin)) - { - _error = "The Current Theme Does Not Contain A Default Or Admin Pane"; - } - } - else - { - page.Panes.Add(PaneNames.Admin); - _error = "The Current Theme Does Not Contain Any Panes"; - } - } - catch - { - // error loading theme or layout - } + // get theme resources + var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType)); + if (theme != null) + { + page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName)); + } - return page; - } + string panes = ""; + Type themetype = Type.GetType(page.ThemeType); + if (themetype == null) + { + // fallback + page.ThemeType = Constants.DefaultTheme; + themetype = Type.GetType(Constants.DefaultTheme); + } + if (themetype != null) + { + var themeobject = Activator.CreateInstance(themetype) as IThemeControl; + if (themeobject != null) + { + if (!string.IsNullOrEmpty(themeobject.Panes)) + { + panes = themeobject.Panes; + } + page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace); + } + } + if (!string.IsNullOrEmpty(panes)) + { + page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(); + if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin)) + { + _error = "The Current Theme Does Not Contain A Default Or Admin Pane"; + } + } + else + { + page.Panes.Add(PaneNames.Admin); + _error = "The Current Theme Does Not Contain Any Panes"; + } + } + catch + { + // error loading theme or layout + } - private (Page Page, List Modules) ProcessModules(Page page, List modules, int moduleid, string action, string defaultcontainertype) - { - var paneindex = new Dictionary(); - foreach (Module module in modules) - { - // initialize module control properties - module.SecurityAccessLevel = SecurityAccessLevel.Host; - module.ControlTitle = ""; - module.Actions = ""; - module.UseAdminContainer = false; - module.PaneModuleIndex = -1; - module.PaneModuleCount = 0; + return page; + } - if ((module.PageId == page.PageId || module.ModuleId == moduleid)) - { - var typename = Constants.ErrorModule; - if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime))) - { - typename = module.ModuleDefinition.ControlTypeTemplate; + private (Page Page, List Modules) ProcessModules(Page page, List modules, int moduleid, string action, string defaultcontainertype, Alias alias) + { + var paneindex = new Dictionary(); + foreach (Module module in modules) + { + // initialize module control properties + module.SecurityAccessLevel = SecurityAccessLevel.Host; + module.ControlTitle = ""; + module.Actions = ""; + module.UseAdminContainer = false; + module.PaneModuleIndex = -1; + module.PaneModuleCount = 0; - // handle default action - if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) - { - action = module.ModuleDefinition.DefaultAction; - } + if ((module.PageId == page.PageId || module.ModuleId == moduleid)) + { + var typename = Constants.ErrorModule; + if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime))) + { + typename = module.ModuleDefinition.ControlTypeTemplate; - // check if the module defines custom action routes - if (module.ModuleDefinition.ControlTypeRoutes != "") - { - foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries)) - { - if (route.StartsWith(action + "=")) - { - typename = route.Replace(action + "=", ""); - } - } - } - } + // handle default action + if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) + { + action = module.ModuleDefinition.DefaultAction; + } - // ensure component exists and implements IModuleControl - module.ModuleType = ""; - if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase)) - { - typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action); - } - else - { - typename = typename.Replace(Constants.ActionToken, action); - } - Type moduletype = Type.GetType(typename, false, true); // case insensitive - if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl))) - { - module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name - } + // check if the module defines custom action routes + if (module.ModuleDefinition.ControlTypeRoutes != "") + { + foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries)) + { + if (route.StartsWith(action + "=")) + { + typename = route.Replace(action + "=", ""); + } + } + } - // get additional metadata from IModuleControl interface - if (moduletype != null && module.ModuleType != "") - { - // retrieve module component resources - var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; - page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module); - if (action.ToLower() == "settings" && module.ModuleDefinition != null) - { - // settings components are embedded within a framework settings module - moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true); - if (moduletype != null) - { - moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; - page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module); - } - } + // get module resources + page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName)); + } - // additional metadata needed for admin components - if (module.ModuleId == moduleid && action != "") - { - module.SecurityAccessLevel = moduleobject.SecurityAccessLevel; - module.ControlTitle = moduleobject.Title; - module.Actions = moduleobject.Actions; - module.UseAdminContainer = moduleobject.UseAdminContainer; - } - } + // ensure component exists and implements IModuleControl + module.ModuleType = ""; + if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase)) + { + typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action); + } + else + { + typename = typename.Replace(Constants.ActionToken, action); + } + Type moduletype = Type.GetType(typename, false, true); // case insensitive + if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl))) + { + module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name + } - // validate that module's pane exists in current page - if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1) - { - // fallback to default pane if it exists - if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1) - { - module.Pane = PaneNames.Default; - } - else // otherwise admin pane (legacy) - { - module.Pane = PaneNames.Admin; - } - } + // get additional metadata from IModuleControl interface + if (moduletype != null && module.ModuleType != "") + { + // retrieve module component resources + var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; + page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); + if (action.ToLower() == "settings" && module.ModuleDefinition != null) + { + // settings components are embedded within a framework settings module + moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true); + if (moduletype != null) + { + moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; + page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); + } + } - // calculate module position within pane - if (paneindex.ContainsKey(module.Pane.ToLower())) - { - paneindex[module.Pane.ToLower()] += 1; - } - else - { - paneindex.Add(module.Pane.ToLower(), 0); - } + // additional metadata needed for admin components + if (module.ModuleId == moduleid && action != "") + { + module.SecurityAccessLevel = moduleobject.SecurityAccessLevel; + module.ControlTitle = moduleobject.Title; + module.Actions = moduleobject.Actions; + module.UseAdminContainer = moduleobject.UseAdminContainer; + } + } - module.PaneModuleIndex = paneindex[module.Pane.ToLower()]; + // validate that module's pane exists in current page + if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1) + { + // fallback to default pane if it exists + if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1) + { + module.Pane = PaneNames.Default; + } + else // otherwise admin pane (legacy) + { + module.Pane = PaneNames.Admin; + } + } - // container fallback - if (string.IsNullOrEmpty(module.ContainerType)) - { - module.ContainerType = defaultcontainertype; - } - } - } + // calculate module position within pane + if (paneindex.ContainsKey(module.Pane.ToLower())) + { + paneindex[module.Pane.ToLower()] += 1; + } + else + { + paneindex.Add(module.Pane.ToLower(), 0); + } - foreach (Module module in modules.Where(item => item.PageId == page.PageId)) - { - if (paneindex.ContainsKey(module.Pane.ToLower())) - { - module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1; - } - } + module.PaneModuleIndex = paneindex[module.Pane.ToLower()]; - return (page, modules); - } + // container fallback + if (string.IsNullOrEmpty(module.ContainerType)) + { + module.ContainerType = defaultcontainertype; + } + } + } - private List ManagePageResources(List pageresources, List resources, ResourceLevel level) - { - if (resources != null) - { - foreach (var resource in resources) - { - // ensure resource does not exist already - if (pageresources.Find(item => item.Url == resource.Url) == null) - { - resource.Level = level; + foreach (Module module in modules.Where(item => item.PageId == page.PageId)) + { + if (paneindex.ContainsKey(module.Pane.ToLower())) + { + module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1; + } + } + + return (page, modules); + } + + private List ManagePageResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name) + { + if (resources != null) + { + foreach (var resource in resources) + { + if (!resource.Url.Contains("://") && resource.Url.StartsWith("~/")) + { + // create local path + resource.Url = resource.Url.Replace("~", alias.BaseUrl + "/" + type + "/" + name); + } + + // ensure resource does not exist already + if (pageresources.Find(item => item.Url == resource.Url) == null) + { + resource.Level = level; pageresources.Add(resource); } } diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index 64d4f82b..49017206 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -6,9 +6,9 @@ @DynamicComponent @code { - [CascadingParameter] PageState PageState { get; set; } + [CascadingParameter] PageState PageState { get; set; } - RenderFragment DynamicComponent { get; set; } + RenderFragment DynamicComponent { get; set; } protected override void OnParametersSet() { diff --git a/Oqtane.Server/Controllers/SiteController.cs b/Oqtane.Server/Controllers/SiteController.cs index 895744a1..95de3a92 100644 --- a/Oqtane.Server/Controllers/SiteController.cs +++ b/Oqtane.Server/Controllers/SiteController.cs @@ -21,6 +21,7 @@ namespace Oqtane.Controllers { private readonly ISiteRepository _sites; private readonly IPageRepository _pages; + private readonly IThemeRepository _themes; private readonly IModuleRepository _modules; private readonly IPageModuleRepository _pageModules; private readonly IModuleDefinitionRepository _moduleDefinitions; @@ -32,10 +33,11 @@ namespace Oqtane.Controllers private readonly IMemoryCache _cache; private readonly Alias _alias; - public SiteController(ISiteRepository sites, IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache) + public SiteController(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IModuleRepository modules, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache) { _sites = sites; _pages = pages; + _themes = themes; _modules = modules; _pageModules = pageModules; _moduleDefinitions = moduleDefinitions; @@ -144,6 +146,9 @@ namespace Oqtane.Controllers var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture); site.Languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) }); + // themes + site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList()); + return site; } else diff --git a/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs b/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs index 61dfc677..a7a81eee 100644 --- a/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IThemeRepository.cs @@ -6,6 +6,7 @@ namespace Oqtane.Repository public interface IThemeRepository { IEnumerable GetThemes(); + List FilterThemes(List themes); void DeleteTheme(string ThemeName); } } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 5f2efd9e..53b9c663 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -79,6 +79,7 @@ namespace Oqtane.Repository ModuleDefinition.SettingsType = moduleDefinition.SettingsType; ModuleDefinition.ControlTypeTemplate = moduleDefinition.ControlTypeTemplate; ModuleDefinition.IsPortable = moduleDefinition.IsPortable; + ModuleDefinition.Resources = moduleDefinition.Resources; } return ModuleDefinition; diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 3794f4ab..4ca2c40e 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -144,6 +144,24 @@ namespace Oqtane.Repository return themes; } + public List FilterThemes(List themes) + { + var Themes = new List(); + + foreach (Theme theme in themes) + { + var Theme = new Theme(); + Theme.ThemeName = theme.ThemeName; + Theme.Name = theme.Name; + Theme.Resources = theme.Resources; + Theme.Themes = theme.Themes; + Theme.Containers = theme.Containers; + Themes.Add(Theme); + } + + return Themes; + } + public void DeleteTheme(string ThemeName) { _cache.Remove("themes"); diff --git a/Oqtane.Shared/Models/ModuleDefinition.cs b/Oqtane.Shared/Models/ModuleDefinition.cs index 7912165f..f287428e 100644 --- a/Oqtane.Shared/Models/ModuleDefinition.cs +++ b/Oqtane.Shared/Models/ModuleDefinition.cs @@ -34,6 +34,7 @@ namespace Oqtane.Models PackageName = ""; Runtimes = ""; Template = ""; + Resources = null; } /// @@ -106,6 +107,9 @@ namespace Oqtane.Models [NotMapped] public string PackageName { get; set; } // added in 2.1.0 + [NotMapped] + public List Resources { get; set; } // added in 4.0.0 + // internal properties [NotMapped] public int SiteId { get; set; } diff --git a/Oqtane.Shared/Models/Resource.cs b/Oqtane.Shared/Models/Resource.cs index 7afe1474..254af250 100644 --- a/Oqtane.Shared/Models/Resource.cs +++ b/Oqtane.Shared/Models/Resource.cs @@ -23,7 +23,7 @@ namespace Oqtane.Models get => _url; set { - _url = (value.Contains("://")) ? value : (!value.StartsWith("/") ? "/" : "") + value; + _url = (value.Contains("://")) ? value : (!value.StartsWith("/") && !value.StartsWith("~") ? "/" : "") + value; } } diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index 383b5668..02712c50 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -105,6 +105,9 @@ namespace Oqtane.Models [NotMapped] public List Languages { get; set; } + [NotMapped] + public List Themes { get; set; } + #region IDeletable Properties public string DeletedBy { get; set; } diff --git a/Oqtane.Shared/Models/Theme.cs b/Oqtane.Shared/Models/Theme.cs index 0f0be718..1ff07b34 100644 --- a/Oqtane.Shared/Models/Theme.cs +++ b/Oqtane.Shared/Models/Theme.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models { @@ -21,6 +22,7 @@ namespace Oqtane.Models ThemeSettingsType = ""; ContainerSettingsType = ""; PackageName = ""; + Resources = null; } /// @@ -67,6 +69,8 @@ namespace Oqtane.Models public string ThemeSettingsType { get; set; } // added in 2.0.2 public string ContainerSettingsType { get; set; } // added in 2.0.2 public string PackageName { get; set; } // added in 2.1.0 + public List Resources { get; set; } // added in 4.0.0 + // internal properties public string AssemblyName { get; set; }