AdminUX Overhaul Small Patches (#2468)

This commit is contained in:
Charlie
2020-12-07 12:14:22 -08:00
committed by GitHub
parent 9e9118fa0d
commit 07a43f52b4
11 changed files with 231 additions and 144 deletions

View File

@ -21,6 +21,34 @@ export default class AdminNav extends Component {
); );
} }
oncreate(vnode) {
super.oncreate(vnode);
this.scrollToActive();
}
onupdate() {
this.scrollToActive();
}
scrollToActive() {
const children = $('.Dropdown-menu').children('.active');
const nav = $('#admin-navigation');
const time = app.previous.type ? 250 : 0;
if (
children.length > 0 &&
(children[0].offsetTop > nav.scrollTop() + nav.outerHeight() || children[0].offsetTop + children[0].offsetHeight < nav.scrollTop())
) {
nav.animate(
{
scrollTop: children[0].offsetTop - nav.height() / 2,
},
time
);
}
}
/** /**
* Build an item list of main links to show in the admin navigation. * Build an item list of main links to show in the admin navigation.
* *
@ -29,6 +57,8 @@ export default class AdminNav extends Component {
items() { items() {
const items = new ItemList(); const items = new ItemList();
items.add('category-core', <h4 className="ExtensionListTitle">{app.translator.trans('core.admin.nav.categories.core')}</h4>);
items.add( items.add(
'dashboard', 'dashboard',
<LinkButton href={app.route('dashboard')} icon="far fa-chart-bar" title={app.translator.trans('core.admin.nav.dashboard_title')}> <LinkButton href={app.route('dashboard')} icon="far fa-chart-bar" title={app.translator.trans('core.admin.nav.dashboard_title')}>
@ -88,7 +118,7 @@ export default class AdminNav extends Component {
Object.keys(categorizedExtensions).map((category) => { Object.keys(categorizedExtensions).map((category) => {
if (!this.query()) { if (!this.query()) {
items.add( items.add(
category, `category-${category}`,
<h4 className="ExtensionListTitle">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>, <h4 className="ExtensionListTitle">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>,
categories[category] categories[category]
); );
@ -100,7 +130,7 @@ export default class AdminNav extends Component {
if (!query || title.toUpperCase().includes(query) || extension.description.toUpperCase().includes(query)) { if (!query || title.toUpperCase().includes(query) || extension.description.toUpperCase().includes(query)) {
items.add( items.add(
extension.id, `extension-${extension.id}`,
<ExtensionLinkButton <ExtensionLinkButton
href={app.route('extension', { id: extension.id })} href={app.route('extension', { id: extension.id })}
extensionId={extension.id} extensionId={extension.id}

View File

@ -12,7 +12,6 @@ import Stream from '../../common/utils/Stream';
import LoadingModal from './LoadingModal'; import LoadingModal from './LoadingModal';
import ExtensionPermissionGrid from './ExtensionPermissionGrid'; import ExtensionPermissionGrid from './ExtensionPermissionGrid';
import saveSettings from '../utils/saveSettings'; import saveSettings from '../utils/saveSettings';
import ExtensionData from '../utils/ExtensionData';
import isExtensionEnabled from '../utils/isExtensionEnabled'; import isExtensionEnabled from '../utils/isExtensionEnabled';
export default class ExtensionPage extends Page { export default class ExtensionPage extends Page {
@ -30,6 +29,7 @@ export default class ExtensionPage extends Page {
support: 'fas fa-life-ring', support: 'fas fa-life-ring',
website: 'fas fa-link', website: 'fas fa-link',
donate: 'fas fa-donate', donate: 'fas fa-donate',
source: 'fas fa-code',
}; };
// Backwards compatibility layer will be removed in // Backwards compatibility layer will be removed in
@ -49,7 +49,7 @@ export default class ExtensionPage extends Page {
{this.header()} {this.header()}
{!this.isEnabled() ? ( {!this.isEnabled() ? (
<div className="container"> <div className="container">
<h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.enable_to_see')}</h2> <h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.enable_to_see')}</h3>
</div> </div>
) : ( ) : (
<div className="ExtensionPage-body">{this.sections().toArray()}</div> <div className="ExtensionPage-body">{this.sections().toArray()}</div>
@ -105,7 +105,7 @@ export default class ExtensionPage extends Page {
{app.extensionData.extensionHasPermissions(this.extension.id) ? ( {app.extensionData.extensionHasPermissions(this.extension.id) ? (
ExtensionPermissionGrid.component({ extensionId: this.extension.id }) ExtensionPermissionGrid.component({ extensionId: this.extension.id })
) : ( ) : (
<h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_permissions')}</h2> <h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_permissions')}</h3>
)} )}
</div> </div>
</div>, </div>,
@ -130,7 +130,7 @@ export default class ExtensionPage extends Page {
<div className="Form-group">{this.submitButton()}</div> <div className="Form-group">{this.submitButton()}</div>
</div> </div>
) : ( ) : (
<h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h2> <h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
)} )}
</div> </div>
</div> </div>
@ -170,17 +170,15 @@ export default class ExtensionPage extends Page {
infoItems() { infoItems() {
const items = new ItemList(); const items = new ItemList();
if (this.extension.authors) { const links = this.extension.links;
if (links.authors.length) {
let authors = []; let authors = [];
Object.keys(this.extension.authors).map((author, i) => { links.authors.map((author) => {
const link = this.extension.authors[author].homepage
? this.extension.authors[author].homepage
: 'mailto:' + this.extension.authors[author].email;
authors.push( authors.push(
<Link href={link} external={true} target="_blank"> <Link href={author.link} external={true} target="_blank">
{this.extension.authors[author].name} {author.name}
</Link> </Link>
); );
}); });
@ -188,33 +186,15 @@ export default class ExtensionPage extends Page {
items.add('authors', [icon('fas fa-user'), <span>{punctuateSeries(authors)}</span>]); items.add('authors', [icon('fas fa-user'), <span>{punctuateSeries(authors)}</span>]);
} }
const infoData = {};
if (this.extension.source || this.extension.support) {
infoData.source = {
icon: 'fas fa-code',
href: this.extension.source ? this.extension.source.url : this.extension.support.source,
};
}
Object.keys(this.infoFields).map((field) => { Object.keys(this.infoFields).map((field) => {
const info = this.extension.extra['flarum-extension'].info; if (links[field]) {
if (info && info[field]) {
infoData[field] = {
icon: this.infoFields[field],
href: info[field],
};
}
});
Object.entries(infoData).map(([field, value]) => {
items.add( items.add(
field, field,
<LinkButton href={value.href} icon={value.icon} external={true} target="_blank"> <LinkButton href={links[field]} icon={this.infoFields[field]} external={true} target="_blank">
{app.translator.trans(`core.admin.extension.info_links.${field}`)} {app.translator.trans(`core.admin.extension.info_links.${field}`)}
</LinkButton> </LinkButton>
); );
}
}); });
return items; return items;
@ -233,6 +213,9 @@ export default class ExtensionPage extends Page {
* Depending on the type of input, you can set the type to 'bool', 'select', or * Depending on the type of input, you can set the type to 'bool', 'select', or
* any standard <input> type. * any standard <input> type.
* *
* Alternatively, you can pass a callback that will be executed in ExtensionPage's
* context to include custom JSX elements.
*
* @example * @example
* *
* { * {
@ -258,6 +241,10 @@ export default class ExtensionPage extends Page {
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
buildSettingComponent(entry) { buildSettingComponent(entry) {
if (typeof entry === 'function') {
return entry.call(this);
}
const setting = entry.setting; const setting = entry.setting;
const value = this.setting([setting])(); const value = this.setting([setting])();
if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) { if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) {

View File

@ -15,7 +15,6 @@ export default class ExtensionsWidget extends DashboardWidget {
return ( return (
<div className="ExtensionsWidget-list"> <div className="ExtensionsWidget-list">
<div className="container">
{Object.keys(categories).map((category) => { {Object.keys(categories).map((category) => {
if (categorizedExtensions[category]) { if (categorizedExtensions[category]) {
return ( return (
@ -42,7 +41,6 @@ export default class ExtensionsWidget extends DashboardWidget {
} }
})} })}
</div> </div>
</div>
); );
} }
} }

View File

@ -26,6 +26,8 @@ export default class ExtensionData {
/** /**
* This function registers your settings with Flarum * This function registers your settings with Flarum
* *
* It takes either a settings object or a callback.
*
* @example * @example
* *
* .registerSetting({ * .registerSetting({

View File

@ -11,6 +11,7 @@
.AdminHeader-description { .AdminHeader-description {
margin: 0; margin: 0;
color: @control-color;
} }
.icon { .icon {

View File

@ -41,16 +41,13 @@
} }
@media @tablet { @media @tablet {
.item-search{ .AdminNav {
display: none; .item-search,
} li[class^="item-category"],
li[class^="item-extension"],
.ExtensionItem, .item-search { .AdminLinkButton-description {
display: none !important; display: none !important;
} }
.ExtensionListTitle {
display: none !important;
} }
} }
@ -80,7 +77,7 @@
} }
@media @desktop, @desktop-hd { @media @desktop-up {
.App-nav { .App-nav {
position: absolute; position: absolute;
top: @header-height; top: @header-height;
@ -107,36 +104,47 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
.item-category-core {
> .ExtensionListTitle {
margin-top: 10px;
}
}
> li { > li {
> a { > a {
padding: 10px 10px 10px 45px; padding: 10px 10px 10px 45px;
display: block; display: block;
text-decoration: none; text-decoration: none;
} }
> a, > a,
> a:hover, > a:hover,
&.active > a { &.active > a {
color: @text-color; color: @text-color;
} }
> a:hover { > a:hover {
background: @control-bg; background: @control-bg;
} }
&.active > a { &.active > a {
background: @primary-color; background: @control-color;
font-weight: normal; font-weight: normal;
color: @body-bg;
.Button-label, .Button-label,
.Button-icon { .Button-icon {
color: @body-bg;
font-weight: bold; font-weight: bold;
} }
} }
.Button-icon { .Button-icon {
float: left; float: left;
font-size: 13px !important; font-size: 13px !important;
margin-left: -25px !important; margin-left: -25px !important;
margin-top: 4px !important; margin-top: 4px !important;
} }
.Button-label { .Button-label {
padding-left: 5px; padding-left: 5px;
font-size: 14px; font-size: 14px;
@ -152,7 +160,7 @@
.ExtensionListTitle { .ExtensionListTitle {
color: @muted-color; color: @muted-color;
text-transform: uppercase; text-transform: uppercase;
margin: 25px 0 15px 15px; margin: 25px 0 8px 15px;
} }
.ExtensionIcon { .ExtensionIcon {
@ -180,6 +188,11 @@
} }
.AdminLinkButton-description {
white-space: normal;
padding-left: 5px;
}
.ExtensionListItem-Dot { .ExtensionListItem-Dot {
height: 10px; height: 10px;
width: 10px; width: 10px;

View File

@ -10,17 +10,21 @@
border-radius: @border-radius; border-radius: @border-radius;
padding: 20px; padding: 20px;
margin-bottom: 20px; margin-bottom: 20px;
.Button {
.Button--color(@control-color, @body-bg)
}
} }
.StatusWidget { .StatusWidget {
color: @muted-color; color: @muted-color;
> ul { >ul {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style-type: none; list-style-type: none;
> li { >li {
display: inline-block; display: inline-block;
margin-right: 30px; margin-right: 30px;
vertical-align: middle; vertical-align: middle;
@ -31,6 +35,7 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
&.item-tools { &.item-tools {
float: right; float: right;
margin-right: 0; margin-right: 0;

View File

@ -119,6 +119,11 @@
} }
} }
.ExtensionPage-settings, .ExtensionPage-permissions {
.ExtensionPage-subHeader {
margin: 5px 0px;
}
}
.ExtensionPage-settings { .ExtensionPage-settings {
margin-top: 20px; margin-top: 20px;
@ -132,7 +137,6 @@
.ExtensionPage-subHeader { .ExtensionPage-subHeader {
color: @muted-color; color: @muted-color;
font-weight: normal; font-weight: normal;
text-align: center;
} }

View File

@ -4,7 +4,6 @@
} }
.ExtensionsWidget-list { .ExtensionsWidget-list {
> .container {
padding: 0; padding: 0;
background-color: @body-bg; background-color: @body-bg;
@ -45,9 +44,9 @@
display: block; display: block;
} }
} }
} }
.ExtensionListItem.disabled { .ExtensionListItem.disabled {
.ExtensionListItem-title { .ExtensionListItem-title {
opacity: 0.5; opacity: 0.5;
color: @muted-color; color: @muted-color;
@ -56,9 +55,9 @@
.ExtensionListItem-icon { .ExtensionListItem-icon {
opacity: 0.5; opacity: 0.5;
} }
} }
.ExtensionListItem { .ExtensionListItem {
transition: .15s ease-in-out; transition: .15s ease-in-out;
&:hover { &:hover {
@ -75,7 +74,6 @@
a:hover { a:hover {
text-decoration: none; text-decoration: none;
} }
}
} }
.ExtensionIcon { .ExtensionIcon {

View File

@ -5,7 +5,11 @@
display: block; display: block;
margin-left: 30px; margin-left: 30px;
overflow-x: auto; overflow-x: auto;
padding: 8px 0 8px; padding: 10px 0 8px;
> .container {
padding: 0 10px;
}
} }
.Group { .Group {
width: 90px; width: 90px;
@ -42,6 +46,7 @@
.PermissionGrid { .PermissionGrid {
white-space: nowrap; white-space: nowrap;
padding-left: 0 12px;
td, th { td, th {
padding: 5px; padding: 5px;
@ -117,7 +122,7 @@
} }
.PermissionGrid-section { .PermissionGrid-section {
td, th { td, th {
padding-top: 20px; padding-top: 10px;
} }
} }
.PermissionGrid-child { .PermissionGrid-child {

View File

@ -346,6 +346,49 @@ class Extension implements Arrayable
return null; return null;
} }
/**
* Compile a list of links for this extension.
*/
public function getLinks()
{
$links = [];
if (($sourceUrl = $this->composerJsonAttribute('source.url')) || ($sourceUrl = $this->composerJsonAttribute('support.source'))) {
$links['source'] = $sourceUrl;
}
if (($discussUrl = $this->composerJsonAttribute('support.forum'))) {
$links['discuss'] = $discussUrl;
}
if (($documentationUrl = $this->composerJsonAttribute('support.docs'))) {
$links['documentation'] = $documentationUrl;
}
if (($websiteUrl = $this->composerJsonAttribute('homepage'))) {
$links['website'] = $websiteUrl;
}
if (($supportEmail = $this->composerJsonAttribute('support.email'))) {
$links['support'] = "mailto:$supportEmail";
}
if (($funding = $this->composerJsonAttribute('funding')) && count($funding)) {
$links['donate'] = $funding[0]['url'];
}
$links['authors'] = [];
foreach ((array) $this->composerJsonAttribute('authors') as $author) {
$links['authors'][] = [
'name' => Arr::get($author, 'name'),
'link' => Arr::get($author, 'homepage') ?? (Arr::get($author, 'email') ? 'mailto:'.Arr::get($author, 'email') : ''),
];
}
return array_merge($links, $this->composerJsonAttribute('flarum-extension.links') ?? []);
}
/** /**
* Tests whether the extension has assets. * Tests whether the extension has assets.
* *
@ -413,6 +456,7 @@ class Extension implements Arrayable
'hasAssets' => $this->hasAssets(), 'hasAssets' => $this->hasAssets(),
'hasMigrations' => $this->hasMigrations(), 'hasMigrations' => $this->hasMigrations(),
'extensionDependencyIds' => $this->getExtensionDependencyIds(), 'extensionDependencyIds' => $this->getExtensionDependencyIds(),
'links' => $this->getLinks(),
], $this->composerJson); ], $this->composerJson);
} }
} }