Merge pull request #456 from sbwalker/master

added classes to all theme controls, added mobile support to Oqtane theme
This commit is contained in:
Shaun Walker 2020-05-08 17:29:51 -04:00 committed by GitHub
commit c5d12d0430
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 278 additions and 230 deletions

View File

@ -85,8 +85,8 @@
private ElementReference _toolBar; private ElementReference _toolBar;
private bool _filemanagervisible = false; private bool _filemanagervisible = false;
private FileManager _fileManager; private FileManager _fileManager;
private string _original = string.Empty;
private string _content = string.Empty; private string _content = string.Empty;
private string _original = string.Empty;
private string _message = string.Empty; private string _message = string.Empty;
[Parameter] [Parameter]
@ -110,8 +110,7 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
_original = Content; _content = Content; // raw HTML
_content = _original;
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
@ -129,47 +128,56 @@
await RichTextEditorInterop.LoadEditorContent( await RichTextEditorInterop.LoadEditorContent(
JsRuntime, JsRuntime,
_editorElement, _original); _editorElement, Content);
// preserve a copy of the rich text content ( Quill sanitizes content so we need to retrieve it from the editor )
_original = await RichTextEditorInterop.GetHtml(
JsRuntime,
_editorElement);
} }
} }
public async Task<string> GetText() public void CloseFileManager()
{ {
return await RichTextEditorInterop.GetText( _filemanagervisible = false;
_message = string.Empty;
StateHasChanged();
}
public async Task RefreshRichText()
{
await RichTextEditorInterop.LoadEditorContent(
JsRuntime,
_editorElement, _content);
}
public async Task RefreshRawHtml()
{
_content = await RichTextEditorInterop.GetHtml(
JsRuntime, JsRuntime,
_editorElement); _editorElement);
StateHasChanged();
} }
public async Task<string> GetHtml() public async Task<string> GetHtml()
{ {
if (_original != _content) // get rich text content
string content = await RichTextEditorInterop.GetHtml(
JsRuntime,
_editorElement);
if (_original != content)
{ {
// raw html content changed // rich text content has changed - return it
return _content; return content;
} }
else else
{ {
// return rich text content // return raw html content
return await RichTextEditorInterop.GetHtml( return _content;
JsRuntime,
_editorElement);
} }
} }
public async Task<string> GetContent()
{
return await RichTextEditorInterop.GetContent(
JsRuntime,
_editorElement);
}
public async Task EnableEditor(bool mode)
{
await RichTextEditorInterop.EnableEditor(
JsRuntime,
_editorElement, mode);
}
public async Task InsertImage() public async Task InsertImage()
{ {
if (_filemanagervisible) if (_filemanagervisible)
@ -196,25 +204,25 @@
StateHasChanged(); StateHasChanged();
} }
public void CloseFileManager() // other rich text editor methods which can be used by developers
public async Task<string> GetText()
{ {
_filemanagervisible = false; return await RichTextEditorInterop.GetText(
_message = string.Empty;
StateHasChanged();
}
public async Task RefreshRichText()
{
await RichTextEditorInterop.LoadEditorContent(
JsRuntime,
_editorElement, _content);
}
public async Task RefreshRawHtml()
{
_content = await RichTextEditorInterop.GetHtml(
JsRuntime, JsRuntime,
_editorElement); _editorElement);
StateHasChanged(); }
public async Task<string> GetContent()
{
return await RichTextEditorInterop.GetContent(
JsRuntime,
_editorElement);
}
public async Task EnableEditor(bool mode)
{
await RichTextEditorInterop.EnableEditor(
JsRuntime,
_editorElement, mode);
} }
} }

View File

@ -7,15 +7,7 @@
<div class="sidebar"> <div class="sidebar">
<nav class="navbar"> <nav class="navbar">
<Logo /> <Logo /><Menu Orientation="Vertical" />
<button class="navbar-toggler" aria-expanded="false" aria-controls="navbarSupportedContent"
aria-label="Toggle navigation" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<Menu Orientation="Vertical" />
</div>
</nav> </nav>
</div> </div>

View File

@ -4,25 +4,31 @@
@if (BreadCrumbPages.Any()) @if (BreadCrumbPages.Any())
{ {
<span class="app-breadcrumbs">
<ol class="breadcrumb"> <ol class="breadcrumb">
@foreach (var p in BreadCrumbPages) @foreach (var p in BreadCrumbPages)
{ {
<li class="breadcrumb-item @ActiveClass(p)"> if (p.PageId == PageState.Page.PageId)
{
<li class="breadcrumb-item active">
<a href="@NavigateUrl(p.Path)">@p.Name</a> <a href="@NavigateUrl(p.Path)">@p.Name</a>
</li> </li>
} }
else
{
<li class="breadcrumb-item">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
}
</ol> </ol>
</span>
} }
@code { @code {
protected IEnumerable<Page> BreadCrumbPages => GetBreadCrumbPages().Reverse().ToList(); protected IEnumerable<Page> BreadCrumbPages => GetBreadCrumbPages().Reverse().ToList();
protected string ActiveClass(Page page)
{
return (page.PageId == PageState.Page.PageId) ? " active" : string.Empty;
}
private IEnumerable<Page> GetBreadCrumbPages() private IEnumerable<Page> GetBreadCrumbPages()
{ {
var page = PageState.Page; var page = PageState.Page;

View File

@ -1,5 +1,4 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@using Oqtane.Enums
@inherits ThemeControlBase @inherits ThemeControlBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager

View File

@ -2,6 +2,7 @@
@inherits LoginBase @inherits LoginBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
<span class="app-login">
<AuthorizeView> <AuthorizeView>
<Authorizing> <Authorizing>
<text>...</text> <text>...</text>
@ -13,3 +14,4 @@
<button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button> <button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button>
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
</span>

View File

@ -1,22 +1,13 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
@if (PageState.Site.LogoFileId != null) @if (PageState.Site.LogoFileId != null)
{ {
<a href="@Href"> <span class="app-logo">
<a href="@PageState.Alias.Path">
<img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" /> <img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
</a> </a>
</span>
} }
@code {
string Href
{
get
{
var uri = new Uri(NavigationManager.Uri);
return $"{uri.Scheme}://{uri.Authority}";
}
}
}

View File

@ -3,11 +3,10 @@
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@if (MenuPages.Any()) @if (MenuPages.Any())
{ {
<div class="app-menu"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="app-menu">
<div class="collapse navbar-collapse" id="Menu"> <div class="collapse navbar-collapse" id="Menu">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
@foreach (var p in MenuPages) @foreach (var p in MenuPages)

View File

@ -3,7 +3,11 @@
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@if (MenuPages.Any()) @if (MenuPages.Any())
{ {
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="app-menu"> <div class="app-menu">
<div class="collapse navbar-collapse" id="Menu">
<ul class="nav flex-column"> <ul class="nav flex-column">
@foreach (var p in MenuPages) @foreach (var p in MenuPages)
{ {
@ -19,10 +23,10 @@
<span class="oi oi-@p.Icon" aria-hidden="true"></span> <span class="oi oi-@p.Icon" aria-hidden="true"></span>
} }
@p.Name @p.Name
</a> </a>
</li> </li>
} }
</ul> </ul>
</div> </div>
</div>
} }

View File

@ -4,6 +4,7 @@
@if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions)) @if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions))
{ {
<div class="app-moduleactions">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a> <a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a>
<div class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);"> <div class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);">
@foreach (var action in Actions) @foreach (var action in Actions)
@ -18,4 +19,5 @@
} }
} }
</div> </div>
</div>
} }

View File

@ -2,7 +2,9 @@
@inherits ContainerBase @inherits ContainerBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
<span class="app-moduletitle">
@((MarkupString)title) @((MarkupString)title)
</span>
@code { @code {
private string title = ""; private string title = "";

View File

@ -3,6 +3,7 @@
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<span class="app-profile">
<AuthorizeView> <AuthorizeView>
<Authorizing> <Authorizing>
<text>...</text> <text>...</text>
@ -17,7 +18,7 @@
} }
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
</span>
@code { @code {

View File

@ -3,7 +3,10 @@
<main role="main"> <main role="main">
<nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top"> <nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top">
<Logo /><Menu Orientation="Horizontal" /><div class="ml-md-auto"><UserProfile /> <Login /> <ControlPanel /></div> <Logo /><Menu Orientation="Horizontal" />
<div class="controls ml-md-auto">
<div class="controls-group"><UserProfile /> <Login /> <ControlPanel /></div>
</div>
</nav> </nav>
<div class="container"> <div class="container">
<PaneLayout /> <PaneLayout />

View File

@ -1,4 +1,6 @@
@namespace Oqtane.UI @using System.Diagnostics.CodeAnalysis
@using System.Runtime.InteropServices
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider @inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState @inject SiteState SiteState
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@ -11,9 +13,6 @@
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject ILogService LogService @inject ILogService LogService
@using System.Diagnostics.CodeAnalysis
@using Oqtane.Enums
@using System.Runtime.InteropServices
@implements IHandleAfterRender @implements IHandleAfterRender
@DynamicComponent @DynamicComponent

View File

@ -18,3 +18,4 @@
@using Oqtane.Themes @using Oqtane.Themes
@using Oqtane.Themes.Controls @using Oqtane.Themes.Controls
@using Oqtane.UI @using Oqtane.UI
@using Oqtane.Enums

View File

@ -197,11 +197,8 @@ namespace Oqtane.Repository
|| Attribute.IsDefined(modulecontroltype, typeof(OqtaneIgnoreAttribute)) || Attribute.IsDefined(modulecontroltype, typeof(OqtaneIgnoreAttribute))
) continue; ) continue;
string[] typename = modulecontroltype.AssemblyQualifiedName?.Split(',').Select(item => item.Trim()).ToArray(); string moduleNamespace = modulecontroltype.Namespace;
string[] segments = typename[0].Split('.'); string qualifiedModuleType = moduleNamespace + ", " + modulecontroltype.Assembly.GetName().Name;
Array.Resize(ref segments, segments.Length - 1);
string moduleType = string.Join(".", segments);
string qualifiedModuleType = moduleType + ", " + typename[1];
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType); int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1) if (index == -1)
@ -210,7 +207,7 @@ namespace Oqtane.Repository
Type moduletype = assembly Type moduletype = assembly
.GetTypes() .GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleType)) .Where(item => item.Namespace.StartsWith(moduleNamespace))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule))); .FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null) if (moduletype != null)
{ {
@ -223,8 +220,8 @@ namespace Oqtane.Repository
// set default property values // set default property values
moduledefinition = new ModuleDefinition moduledefinition = new ModuleDefinition
{ {
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1), Name = moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1), Description = "Manage " + moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "") Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
}; };
} }
@ -232,7 +229,7 @@ namespace Oqtane.Repository
// set internal properties // set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType; moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1]; moduledefinition.ControlTypeTemplate = moduleNamespace + "." + Constants.ActionToken + ", " + modulecontroltype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name; moduledefinition.AssemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(moduledefinition.Categories)) if (string.IsNullOrEmpty(moduledefinition.Categories))
@ -269,7 +266,7 @@ namespace Oqtane.Repository
{ {
foreach (string action in actions.Split(',')) foreach (string action in actions.Split(','))
{ {
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + typename[1] + ";"); moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + modulecontroltype.Assembly.GetName().Name + ";");
} }
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes; using Oqtane.Themes;
namespace Oqtane.Repository namespace Oqtane.Repository
@ -46,20 +47,22 @@ namespace Oqtane.Repository
Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray(); Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray();
foreach (Type themeControlType in themeControlTypes) foreach (Type themeControlType in themeControlTypes)
{ {
if (themeControlType.Name != "ThemeBase") // Check if type should be ignored
{ if (themeControlType.Name == "ThemeBase"
string[] typename = themeControlType.AssemblyQualifiedName.Split(',').Select(item => item.Trim()).ToList().ToArray(); || themeControlType.IsGenericType
string[] segments = typename[0].Split('.'); || Attribute.IsDefined(themeControlType, typeof(OqtaneIgnoreAttribute))
Array.Resize(ref segments, segments.Length - 1); ) continue;
string @namespace = string.Join(".", segments);
int index = themes.FindIndex(item => item.ThemeName == @namespace); string themeNamespace = themeControlType.Namespace;
string qualifiedModuleType = themeNamespace + ", " + themeControlType.Assembly.GetName().Name;
int index = themes.FindIndex(item => item.ThemeName == themeNamespace);
if (index == -1) if (index == -1)
{ {
// determine if this theme implements ITheme // determine if this theme implements ITheme
Type themetype = assembly.GetTypes() Type themetype = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault(); .Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themetype != null) if (themetype != null)
{ {
@ -75,25 +78,25 @@ namespace Oqtane.Repository
}; };
} }
// set internal properties // set internal properties
theme.ThemeName = @namespace; theme.ThemeName = themeNamespace;
theme.ThemeControls = ""; theme.ThemeControls = "";
theme.PaneLayouts = ""; theme.PaneLayouts = "";
theme.ContainerControls = ""; theme.ContainerControls = "";
theme.AssemblyName = assembly.FullName.Split(",")[0]; theme.AssemblyName = assembly.FullName.Split(",")[0];
themes.Add(theme); themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == @namespace); index = themes.FindIndex(item => item.ThemeName == themeNamespace);
} }
theme = themes[index]; theme = themes[index];
theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";"); theme.ThemeControls += (themeControlType.FullName + ", " + themeControlType.Assembly.GetName().Name + ";");
// layouts // layouts
Type[] layouttypes = assembly.GetTypes() Type[] layouttypes = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray(); .Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray();
foreach (Type layouttype in layouttypes) foreach (Type layouttype in layouttypes)
{ {
string panelayout = layouttype.FullName + ", " + typename[1] + ";"; string panelayout = layouttype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.PaneLayouts.Contains(panelayout)) if (!theme.PaneLayouts.Contains(panelayout))
{ {
theme.PaneLayouts += panelayout; theme.PaneLayouts += panelayout;
@ -103,11 +106,11 @@ namespace Oqtane.Repository
// containers // containers
Type[] containertypes = assembly.GetTypes() Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray(); .Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes) foreach (Type containertype in containertypes)
{ {
string container = containertype.FullName + ", " + typename[1] + ";"; string container = containertype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.ContainerControls.Contains(container)) if (!theme.ContainerControls.Contains(container))
{ {
theme.ContainerControls += container; theme.ContainerControls += container;
@ -116,7 +119,6 @@ namespace Oqtane.Repository
themes[index] = theme; themes[index] = theme;
} }
}
return themes; return themes;
} }
} }

View File

@ -83,6 +83,7 @@
.navbar-toggler { .navbar-toggler {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
margin-left: auto;
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {

View File

@ -4,6 +4,13 @@ body {
padding-top: 7rem; padding-top: 7rem;
} }
.controls {
z-index: 2000;
padding-top: 15px;
padding-bottom: 15px;
margin-right: 10px;
}
.app-menu .nav-item { .app-menu .nav-item {
font-size: 0.9rem; font-size: 0.9rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
@ -33,3 +40,35 @@ body {
vertical-align: text-top; vertical-align: text-top;
top: -2px; top: -2px;
} }
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
margin-left: auto;
}
@media (max-width: 767px) {
.navbar {
position: fixed;
top: 60px;
width: 100%;
}
.controls {
height: 60px;
top: 15px;
position: fixed;
top: 0px;
width: 100%;
background-color: rgb(0, 0, 0);
}
.controls-group {
float: right;
margin-right: 25px;
}
.container {
position: relative;
top: 30px;
}
}