Merge pull request #499 from sbwalker/master

Added support for module resource management
This commit is contained in:
Shaun Walker 2020-05-18 09:46:32 -04:00 committed by GitHub
commit a13208e65d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 183 additions and 128 deletions

View File

@ -20,6 +20,26 @@
}
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Edit Html/Text";
public override List<Resource> Resources
{
get
{
List<Resource> resources = new List<Resource>();
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css", Integrity = "", CrossOrigin = "" });
// the following resources should be declared in the RichTextEditor component however the framework currently only supports resource management for modules and themes
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.bubble.css", Integrity = "", CrossOrigin = "" });
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.snow.css", Integrity = "", CrossOrigin = "" });
resources.Add(new Resource { ResourceType = ResourceType.Script, Url = "js/quill1.3.6.min.js", Integrity = "", CrossOrigin = "" });
resources.Add(new Resource { ResourceType = ResourceType.Script, Url = "js/quill-blot-formatter.min.js", Integrity = "", CrossOrigin = "" });
resources.Add(new Resource { ResourceType = ResourceType.Script, Url = "js/quill-interop.js", Integrity = "", CrossOrigin = "" });
return resources;
}
}
private RichTextEditor RichTextEditorHtml;
private string _content = null;
private string _createdby;
@ -27,10 +47,6 @@
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Edit Html/Text";
protected override async Task OnInitializedAsync()
{
try

View File

@ -11,6 +11,16 @@
}
@code {
public override List<Resource> Resources
{
get
{
List<Resource> resources = new List<Resource>();
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css", Integrity = "", CrossOrigin = "" });
return resources;
}
}
private string content = "";
protected override async Task OnParametersSetAsync()

View File

@ -6,6 +6,7 @@ using Oqtane.Services;
using System;
using Oqtane.Enums;
using Oqtane.UI;
using System.Collections.Generic;
namespace Oqtane.Modules
{
@ -37,6 +38,9 @@ namespace Oqtane.Modules
public virtual bool UseAdminContainer { get { return true; } }
public virtual List<Resource> Resources { get; set; }
// path method
public string ModulePath()

View File

@ -33,7 +33,7 @@
get
{
List<Resource> resources = new List<Resource>();
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = "Themes/" + GetType().Namespace + "/Theme.css", Integrity = "", CrossOrigin = "" });
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css", Integrity = "", CrossOrigin = "" });
return resources;
}
}

View File

@ -149,7 +149,7 @@
<label for="Pane" class="control-label">Pane: </label>
<select class="form-control" @bind="@_pane">
<option value="">&lt;Select Pane&gt;</option>
@foreach (string pane in PageState.Page.Panes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
@foreach (string pane in PageState.Page.Panes)
{
<option value="@pane">@pane Pane</option>
}
@ -275,7 +275,7 @@
}
}
var panes = PageState.Page.Panes.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries);
var panes = PageState.Page.Panes;
_pane = panes.Count() == 1 ? panes.SingleOrDefault() : "";
var themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerTypes(themes);

View File

@ -60,7 +60,7 @@ namespace Oqtane.Themes.Controls
actionList.Add(new ActionViewModel {Name = "Move To Bottom", Action = async (s, m) => await MoveBottom(s, m)});
}
foreach (string pane in PageState.Page.Panes.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries))
foreach (string pane in PageState.Page.Panes)
{
if (pane != ModuleState.Pane)
{

View File

@ -25,7 +25,7 @@
{
List<Resource> resources = new List<Resource>();
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css", Integrity = "sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM", CrossOrigin = "anonymous" });
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = "Themes/" + GetType().Namespace + "/Theme.css", Integrity = "", CrossOrigin = "" });
resources.Add(new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css", Integrity = "", CrossOrigin = "" });
return resources;
}
}

View File

@ -238,21 +238,7 @@
{
page = await ProcessPage(page, site, user);
_pagestate = new PageState
{
Alias = alias,
Site = site,
Pages = pages,
Page = page,
User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring,
ModuleId = moduleid,
Action = action,
Runtime = runtime
};
if (PageState != null && (PageState.ModuleId != _pagestate.ModuleId || PageState.Action != _pagestate.Action))
if (PageState != null && (PageState.ModuleId != moduleid || PageState.Action != action))
{
reload = Reload.Page;
}
@ -260,15 +246,29 @@
if (PageState == null || reload >= Reload.Page)
{
modules = await ModuleService.GetModulesAsync(site.SiteId);
modules = ProcessModules(modules, page.PageId, _pagestate.ModuleId, _pagestate.Action, page.Panes, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
(page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
}
else
{
modules = PageState.Modules;
}
_pagestate.Modules = modules;
_pagestate.EditMode = editmode;
_pagestate.LastSyncDate = lastsyncdate;
_pagestate = new PageState
{
Alias = alias,
Site = site,
Pages = pages,
Page = page,
User = user,
Modules = modules,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring,
ModuleId = moduleid,
Action = action,
EditMode = editmode,
LastSyncDate = lastsyncdate,
Runtime = runtime
};
OnStateChange?.Invoke(_pagestate);
}
@ -356,18 +356,17 @@
page.LayoutType = site.DefaultLayoutType;
}
page.Panes = new List<string>();
page.Resources = new List<Resource>();
string panes = "";
Type themetype = Type.GetType(page.ThemeType);
var themeobject = Activator.CreateInstance(themetype);
if (themeobject != null)
{
page.Panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null);
panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null);
var resources = (List<Resource>)themetype.GetProperty("Resources").GetValue(themeobject, null);
if (resources != null)
{
page.Resources.AddRange(resources);
}
page.Resources = ManagePageResources(page.Resources, resources);
}
if (!string.IsNullOrEmpty(page.LayoutType))
@ -376,9 +375,11 @@
themeobject = Activator.CreateInstance(themetype);
if (themeobject != null)
{
page.Panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null);
panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null);
}
}
page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
catch
{
@ -388,12 +389,12 @@
return page;
}
private List<Module> ProcessModules(List<Module> modules, int pageid, int moduleid, string control, string panes, string defaultcontainertype)
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string control, string defaultcontainertype)
{
var paneindex = new Dictionary<string, int>();
foreach (Module module in modules)
{
if (module.PageId == pageid || module.ModuleId == moduleid)
if (module.PageId == page.PageId || module.ModuleId == moduleid)
{
var typename = string.Empty;
if (module.ModuleDefinition != null)
@ -419,49 +420,53 @@
}
}
module.ModuleType = typename.Replace(Constants.ActionToken, control);
// admin controls need to load additional metadata from the IModuleControl interface
if (moduleid == module.ModuleId)
{
typename = module.ModuleType;
// check for core module actions component
if (Constants.DefaultModuleActions.Contains(control))
{
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, control);
}
Type moduletype = Type.GetType(typename);
if (moduletype != null)
{
var moduleobject = Activator.CreateInstance(moduletype);
module.SecurityAccessLevel = (SecurityAccessLevel)moduletype.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null);
module.ControlTitle = (string)moduletype.GetProperty("Title").GetValue(moduleobject);
module.Actions = (string)moduletype.GetProperty("Actions").GetValue(moduleobject);
module.UseAdminContainer = (bool)moduletype.GetProperty("UseAdminContainer").GetValue(moduleobject);
}
}
}
else
{
module.ModuleType = typename.Replace(Constants.ActionToken, Constants.DefaultAction);
}
// get additional metadata from IModuleControl interface
typename = module.ModuleType;
if (Constants.DefaultModuleActions.Contains(control))
{
// core framework module action components
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, control);
}
Type moduletype = Type.GetType(typename);
if (moduletype != null)
{
var moduleobject = Activator.CreateInstance(moduletype);
var resources = (List<Resource>)moduletype.GetProperty("Resources").GetValue(moduleobject, null);
page.Resources = ManagePageResources(page.Resources, resources);
// additional metadata needed for admin components
if (module.ModuleId == moduleid && control != "")
{
module.SecurityAccessLevel = (SecurityAccessLevel)moduletype.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null);
module.ControlTitle = (string)moduletype.GetProperty("Title").GetValue(moduleobject);
module.Actions = (string)moduletype.GetProperty("Actions").GetValue(moduleobject);
module.UseAdminContainer = (bool)moduletype.GetProperty("UseAdminContainer").GetValue(moduleobject);
}
}
// ensure module's pane exists in current page and if not, assign it to the Admin pane
if (panes == null || !panes.ToLower().Contains(module.Pane.ToLower()))
if (page.Panes == null || page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
{
module.Pane = Constants.AdminPane;
}
// calculate module position within pane
if (paneindex.ContainsKey(module.Pane))
if (paneindex.ContainsKey(module.Pane.ToLower()))
{
paneindex[module.Pane] += 1;
paneindex[module.Pane.ToLower()] += 1;
}
else
{
paneindex.Add(module.Pane, 0);
paneindex.Add(module.Pane.ToLower(), 0);
}
module.PaneModuleIndex = paneindex[module.Pane];
module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
if (string.IsNullOrEmpty(module.ContainerType))
{
@ -470,16 +475,32 @@
}
}
foreach (Module module in modules.Where(item => item.PageId == pageid))
foreach (Module module in modules.Where(item => item.PageId == page.PageId))
{
module.PaneModuleCount = paneindex[module.Pane] + 1;
module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1;
}
return modules;
return (page, modules);
}
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources)
{
if (resources != null)
{
foreach (var resource in resources)
{
// ensure resource does not exist already
if (pageresources.Find(item => item.Url == resource.Url) == null)
{
pageresources.Add(resource);
}
}
}
return pageresources;
}
private Runtime GetRuntime()
=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
? Runtime.WebAssembly
: Runtime.Server;
=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
? Runtime.WebAssembly
: Runtime.Server;
}

View File

@ -23,22 +23,26 @@
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
}
// manage page resources- they cannot be removed first and then added because the browser will "flash" and result in a poor user experience - they need to be updated
int index = 0;
// update page resources
int stylesheet = 0;
int script = 0;
foreach (Resource resource in PageState.Page.Resources)
{
index += 1;
switch (resource.ResourceType)
{
case ResourceType.Stylesheet:
await interop.IncludeLink("app-resource" + index.ToString("00"), "stylesheet", resource.Url, "text/css", resource.Integrity, resource.CrossOrigin);
stylesheet += 1;
await interop.IncludeLink("app-stylesheet" + stylesheet.ToString("00"), "stylesheet", resource.Url, "text/css", resource.Integrity ?? "", resource.CrossOrigin ?? "");
break;
case ResourceType.Script:
script += 1;
await interop.IncludeScript("app-script" + script.ToString("00"), resource.Url, "", "body", resource.Integrity ?? "", resource.CrossOrigin ?? "");
break;
}
}
// remove any page resources references which are no longer required for this page
await interop.RemoveElementsById("app-resource", "app-resource" + (index + 1).ToString("00"), "");
await interop.RemoveElementsById("app-stylesheet", "app-stylesheet" + (stylesheet + 1).ToString("00"), "");
await interop.RemoveElementsById("app-script", "app-script" + (script + 1).ToString("00"), "");
// add favicon
if (PageState.Site.FaviconFileId != null)

View File

@ -14,8 +14,6 @@
<link id="fav-icon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<!-- stub the PWA manifest but defer the assignment of href -->
<link id="pwa-manifest" rel="manifest" />
<link href="css/quill/quill1.3.6.bubble.css" rel="stylesheet" />
<link href="css/quill/quill1.3.6.snow.css" rel="stylesheet" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="css/app.css" rel="stylesheet" />
</head>
@ -25,10 +23,7 @@
<component type="typeof(Oqtane.App)" render-mode="Server" />
</app>
<script src="js/site.js"></script>
<script src="js/interop.js"></script>
<script src="js/quill1.3.6.min.js"></script>
<script src="js/quill-blot-formatter.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

View File

@ -0,0 +1 @@
/* HtmlText Module Custom Styles */

View File

@ -264,53 +264,5 @@ window.interop = {
request.send(data);
}
}
},
createQuill: function (
quillElement, toolBar, readOnly,
placeholder, theme, debugLevel) {
Quill.register('modules/blotFormatter', QuillBlotFormatter.default);
var options = {
debug: debugLevel,
modules: {
toolbar: toolBar,
blotFormatter: {}
},
placeholder: placeholder,
readOnly: readOnly,
theme: theme
};
new Quill(quillElement, options);
},
getQuillContent: function (editorElement) {
return JSON.stringify(editorElement.__quill.getContents());
},
getQuillText: function (editorElement) {
return editorElement.__quill.getText();
},
getQuillHTML: function (editorElement) {
return editorElement.__quill.root.innerHTML;
},
loadQuillContent: function (editorElement, editorContent) {
return editorElement.__quill.root.innerHTML = editorContent;
},
enableQuillEditor: function (editorElement, mode) {
editorElement.__quill.enable(mode);
},
insertQuillImage: function (quillElement, imageURL) {
var Delta = Quill.import('delta');
editorIndex = 0;
if (quillElement.__quill.getSelection() !== null) {
editorIndex = quillElement.__quill.getSelection().index;
}
return quillElement.__quill.updateContents(
new Delta()
.retain(editorIndex)
.insert({ image: imageURL },
{ alt: imageURL }));
}
};

View File

@ -0,0 +1,50 @@
window.interop = {
createQuill: function (
quillElement, toolBar, readOnly,
placeholder, theme, debugLevel) {
Quill.register('modules/blotFormatter', QuillBlotFormatter.default);
var options = {
debug: debugLevel,
modules: {
toolbar: toolBar,
blotFormatter: {}
},
placeholder: placeholder,
readOnly: readOnly,
theme: theme
};
new Quill(quillElement, options);
},
getQuillContent: function (editorElement) {
return JSON.stringify(editorElement.__quill.getContents());
},
getQuillText: function (editorElement) {
return editorElement.__quill.getText();
},
getQuillHTML: function (editorElement) {
return editorElement.__quill.root.innerHTML;
},
loadQuillContent: function (editorElement, editorContent) {
return editorElement.__quill.root.innerHTML = editorContent;
},
enableQuillEditor: function (editorElement, mode) {
editorElement.__quill.enable(mode);
},
insertQuillImage: function (quillElement, imageURL) {
var Delta = Quill.import('delta');
editorIndex = 0;
if (quillElement.__quill.getSelection() !== null) {
editorIndex = quillElement.__quill.getSelection().index;
}
return quillElement.__quill.updateContents(
new Delta()
.retain(editorIndex)
.insert({ image: imageURL },
{ alt: imageURL }));
}
};

View File

@ -1 +0,0 @@

View File

@ -1,4 +1,6 @@
using Oqtane.Shared;
using Oqtane.Models;
using Oqtane.Shared;
using System.Collections.Generic;
namespace Oqtane.Modules
{
@ -8,5 +10,6 @@ namespace Oqtane.Modules
string Title { get; } // title to display for this control - defaults to module title
string Actions { get; } // allows for routing by configuration rather than by convention ( comma delimited ) - defaults to using component file name
bool UseAdminContainer { get; } // container for embedding module control - defaults to true. false will suppress the default modal UI popup behavior and render the component in the page.
List<Resource> Resources { get; } // identifies all resources in a module
}
}

View File

@ -32,7 +32,7 @@ namespace Oqtane.Models
public bool IsDeleted { get; set; }
[NotMapped]
public string Panes { get; set; }
public List<string> Panes { get; set; }
[NotMapped]
public List<Resource> Resources { get; set; }
[NotMapped]