added classes to all theme controls, added mobile support to Oqtane theme

This commit is contained in:
Shaun Walker 2020-05-08 17:30:35 -04:00
parent aaf2c96374
commit c2ed71ab0d
18 changed files with 278 additions and 230 deletions

View File

@ -85,8 +85,8 @@
private ElementReference _toolBar;
private bool _filemanagervisible = false;
private FileManager _fileManager;
private string _original = string.Empty;
private string _content = string.Empty;
private string _original = string.Empty;
private string _message = string.Empty;
[Parameter]
@ -110,8 +110,7 @@
protected override void OnInitialized()
{
_original = Content;
_content = _original;
_content = Content; // raw HTML
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@ -129,45 +128,54 @@
await RichTextEditorInterop.LoadEditorContent(
JsRuntime,
_editorElement, _original);
}
}
_editorElement, Content);
public async Task<string> GetText()
{
return await RichTextEditorInterop.GetText(
JsRuntime,
_editorElement);
}
public async Task<string> GetHtml()
{
if (_original != _content)
{
// raw html content changed
return _content;
}
else
{
// return rich text content
return await RichTextEditorInterop.GetHtml(
// 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> GetContent()
public void CloseFileManager()
{
return await RichTextEditorInterop.GetContent(
JsRuntime,
_editorElement);
_filemanagervisible = false;
_message = string.Empty;
StateHasChanged();
}
public async Task EnableEditor(bool mode)
public async Task RefreshRichText()
{
await RichTextEditorInterop.EnableEditor(
await RichTextEditorInterop.LoadEditorContent(
JsRuntime,
_editorElement, mode);
_editorElement, _content);
}
public async Task RefreshRawHtml()
{
_content = await RichTextEditorInterop.GetHtml(
JsRuntime,
_editorElement);
StateHasChanged();
}
public async Task<string> GetHtml()
{
// get rich text content
string content = await RichTextEditorInterop.GetHtml(
JsRuntime,
_editorElement);
if (_original != content)
{
// rich text content has changed - return it
return content;
}
else
{
// return raw html content
return _content;
}
}
public async Task InsertImage()
@ -196,25 +204,25 @@
StateHasChanged();
}
public void CloseFileManager()
// other rich text editor methods which can be used by developers
public async Task<string> 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(
return await RichTextEditorInterop.GetText(
JsRuntime,
_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">
<nav class="navbar">
<Logo />
<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>
<Logo /><Menu Orientation="Vertical" />
</nav>
</div>

View File

@ -4,24 +4,30 @@
@if (BreadCrumbPages.Any())
{
<ol class="breadcrumb">
@foreach (var p in BreadCrumbPages)
{
<li class="breadcrumb-item @ActiveClass(p)">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
</ol>
<span class="app-breadcrumbs">
<ol class="breadcrumb">
@foreach (var p in BreadCrumbPages)
{
if (p.PageId == PageState.Page.PageId)
{
<li class="breadcrumb-item active">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
else
{
<li class="breadcrumb-item">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
}
</ol>
</span>
}
@code {
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()
{

View File

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

View File

@ -2,14 +2,16 @@
@inherits LoginBase
@attribute [OqtaneIgnore]
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="LogoutUser">Logout</button>
</Authorized>
<NotAuthorized>
<button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button>
</NotAuthorized>
</AuthorizeView>
<span class="app-login">
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="LogoutUser">Logout</button>
</Authorized>
<NotAuthorized>
<button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button>
</NotAuthorized>
</AuthorizeView>
</span>

View File

@ -1,22 +1,13 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
@if (PageState.Site.LogoFileId != null)
{
<a href="@Href">
<img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name"/>
</a>
<span class="app-logo">
<a href="@PageState.Alias.Path">
<img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
</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]
@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">
<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="collapse navbar-collapse" id="Menu">
<ul class="navbar-nav mr-auto">
@foreach (var p in MenuPages)

View File

@ -1,28 +1,32 @@
@namespace Oqtane.Themes.Controls
@inherits MenuBase
@inherits MenuBase
@attribute [OqtaneIgnore]
@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">
<ul class="nav flex-column">
@foreach (var p in MenuPages)
{
<li class="nav-item px-3">
<a href="@GetUrl(p)" class="nav-link" style="padding-left:@((p.Level + 1) * 15)px !important;" target="@GetTarget(p)">
<div class="collapse navbar-collapse" id="Menu">
<ul class="nav flex-column">
@foreach (var p in MenuPages)
{
<li class="nav-item px-3">
<a href="@GetUrl(p)" class="nav-link" style="padding-left:@((p.Level + 1) * 15)px !important;" target="@GetTarget(p)">
@if (p.HasChildren)
{
<i class="oi oi-chevron-right"></i>
}
@if (p.Icon != string.Empty)
{
<span class="oi oi-@p.Icon" aria-hidden="true"></span>
}
@p.Name
</a>
</li>
}
</ul>
@if (p.HasChildren)
{
<i class="oi oi-chevron-right"></i>
}
@if (p.Icon != string.Empty)
{
<span class="oi oi-@p.Icon" aria-hidden="true"></span>
}
@p.Name
</a>
</li>
}
</ul>
</div>
</div>
}

View File

@ -4,18 +4,20 @@
@if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions))
{
<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);">
@foreach (var action in Actions)
{
if (string.IsNullOrEmpty(action.Name))
{
<div class="dropdown-divider"></div>
}
else
{
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">@action.Name</a>
}
}
<div class="app-moduleactions">
<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);">
@foreach (var action in Actions)
{
if (string.IsNullOrEmpty(action.Name))
{
<div class="dropdown-divider"></div>
}
else
{
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">@action.Name</a>
}
}
</div>
</div>
}
}

View File

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

View File

@ -3,21 +3,22 @@
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="UpdateProfile">@context.User.Identity.Name</button>
</Authorized>
<NotAuthorized>
@if (PageState.Site.AllowRegistration)
{
<button type="button" class="btn btn-primary" @onclick="RegisterUser">Register</button>
}
</NotAuthorized>
</AuthorizeView>
<span class="app-profile">
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="UpdateProfile">@context.User.Identity.Name</button>
</Authorized>
<NotAuthorized>
@if (PageState.Site.AllowRegistration)
{
<button type="button" class="btn btn-primary" @onclick="RegisterUser">Register</button>
}
</NotAuthorized>
</AuthorizeView>
</span>
@code {

View File

@ -3,7 +3,10 @@
<main role="main">
<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>
<div class="container">
<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 SiteState SiteState
@inject NavigationManager NavigationManager
@ -11,9 +13,6 @@
@inject IModuleService ModuleService
@inject IModuleDefinitionService ModuleDefinitionService
@inject ILogService LogService
@using System.Diagnostics.CodeAnalysis
@using Oqtane.Enums
@using System.Runtime.InteropServices
@implements IHandleAfterRender
@DynamicComponent
@ -89,7 +88,7 @@
// get path
var path = uri.LocalPath.Substring(1);
// parse querystring
// parse querystring
var querystring = ParseQueryString(uri.Query);
// the reload parameter is used during user login/logout
@ -237,7 +236,7 @@
}
// check if user is authorized to view page
if (UserSecurity.IsAuthorized(user,PermissionNames.View, page.Permissions))
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions))
{
page = await ProcessPage(page, site, user);

View File

@ -17,4 +17,5 @@
@using Oqtane.Shared
@using Oqtane.Themes
@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))
) continue;
string[] typename = modulecontroltype.AssemblyQualifiedName?.Split(',').Select(item => item.Trim()).ToArray();
string[] segments = typename[0].Split('.');
Array.Resize(ref segments, segments.Length - 1);
string moduleType = string.Join(".", segments);
string qualifiedModuleType = moduleType + ", " + typename[1];
string moduleNamespace = modulecontroltype.Namespace;
string qualifiedModuleType = moduleNamespace + ", " + modulecontroltype.Assembly.GetName().Name;
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
@ -210,7 +207,7 @@ namespace Oqtane.Repository
Type moduletype = assembly
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleType))
.Where(item => item.Namespace.StartsWith(moduleNamespace))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null)
{
@ -223,8 +220,8 @@ namespace Oqtane.Repository
// set default property values
moduledefinition = new ModuleDefinition
{
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Name = moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Description = "Manage " + moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
};
}
@ -232,7 +229,7 @@ namespace Oqtane.Repository
// set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType;
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;
if (string.IsNullOrEmpty(moduledefinition.Categories))
@ -269,7 +266,7 @@ namespace Oqtane.Repository
{
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.Reflection;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes;
namespace Oqtane.Repository
@ -46,76 +47,77 @@ namespace Oqtane.Repository
Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray();
foreach (Type themeControlType in themeControlTypes)
{
if (themeControlType.Name != "ThemeBase")
// Check if type should be ignored
if (themeControlType.Name == "ThemeBase"
|| themeControlType.IsGenericType
|| Attribute.IsDefined(themeControlType, typeof(OqtaneIgnoreAttribute))
) continue;
string themeNamespace = themeControlType.Namespace;
string qualifiedModuleType = themeNamespace + ", " + themeControlType.Assembly.GetName().Name;
int index = themes.FindIndex(item => item.ThemeName == themeNamespace);
if (index == -1)
{
string[] typename = themeControlType.AssemblyQualifiedName.Split(',').Select(item => item.Trim()).ToList().ToArray();
string[] segments = typename[0].Split('.');
Array.Resize(ref segments, segments.Length - 1);
string @namespace = string.Join(".", segments);
int index = themes.FindIndex(item => item.ThemeName == @namespace);
if (index == -1)
{
// determine if this theme implements ITheme
Type themetype = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themetype != null)
{
var themeobject = Activator.CreateInstance(themetype);
theme = (Theme)themetype.GetProperty("Theme").GetValue(themeobject);
}
else
{
theme = new Theme
{
Name = themeControlType.Name,
Version = new Version(1, 0, 0).ToString()
};
}
// set internal properties
theme.ThemeName = @namespace;
theme.ThemeControls = "";
theme.PaneLayouts = "";
theme.ContainerControls = "";
theme.AssemblyName = assembly.FullName.Split(",")[0];
themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == @namespace);
}
theme = themes[index];
theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";");
// layouts
Type[] layouttypes = assembly.GetTypes()
// determine if this theme implements ITheme
Type themetype = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace))
.Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray();
foreach (Type layouttype in layouttypes)
.Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themetype != null)
{
string panelayout = layouttype.FullName + ", " + typename[1] + ";";
if (!theme.PaneLayouts.Contains(panelayout))
{
theme.PaneLayouts += panelayout;
}
var themeobject = Activator.CreateInstance(themetype);
theme = (Theme)themetype.GetProperty("Theme").GetValue(themeobject);
}
// containers
Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes)
else
{
string container = containertype.FullName + ", " + typename[1] + ";";
if (!theme.ContainerControls.Contains(container))
theme = new Theme
{
theme.ContainerControls += container;
}
Name = themeControlType.Name,
Version = new Version(1, 0, 0).ToString()
};
}
themes[index] = theme;
// set internal properties
theme.ThemeName = themeNamespace;
theme.ThemeControls = "";
theme.PaneLayouts = "";
theme.ContainerControls = "";
theme.AssemblyName = assembly.FullName.Split(",")[0];
themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == themeNamespace);
}
theme = themes[index];
theme.ThemeControls += (themeControlType.FullName + ", " + themeControlType.Assembly.GetName().Name + ";");
// layouts
Type[] layouttypes = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray();
foreach (Type layouttype in layouttypes)
{
string panelayout = layouttype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.PaneLayouts.Contains(panelayout))
{
theme.PaneLayouts += panelayout;
}
}
// containers
Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes)
{
string container = containertype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.ContainerControls.Contains(container))
{
theme.ContainerControls += container;
}
}
themes[index] = theme;
}
return themes;
}

View File

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

View File

@ -4,6 +4,13 @@ body {
padding-top: 7rem;
}
.controls {
z-index: 2000;
padding-top: 15px;
padding-bottom: 15px;
margin-right: 10px;
}
.app-menu .nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
@ -33,3 +40,35 @@ body {
vertical-align: text-top;
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;
}
}