From 07a43f52b4b542d5a5db5bcbb0d30135cde7bea8 Mon Sep 17 00:00:00 2001
From: Charlie <13856015+KyrneDev@users.noreply.github.com>
Date: Mon, 7 Dec 2020 12:14:22 -0800
Subject: [PATCH] AdminUX Overhaul Small Patches (#2468)
---
js/src/admin/components/AdminNav.js | 34 +++++-
js/src/admin/components/ExtensionPage.js | 61 ++++------
js/src/admin/components/ExtensionsWidget.js | 52 +++++----
js/src/admin/utils/ExtensionData.js | 2 +
less/admin/AdminHeader.less | 1 +
less/admin/AdminNav.less | 41 ++++---
less/admin/DashboardPage.less | 9 +-
less/admin/ExtensionPage.less | 6 +-
less/admin/ExtensionWidget.less | 116 ++++++++++----------
less/admin/PermissionsPage.less | 9 +-
src/Extension/Extension.php | 44 ++++++++
11 files changed, 231 insertions(+), 144 deletions(-)
diff --git a/js/src/admin/components/AdminNav.js b/js/src/admin/components/AdminNav.js
index ecf96ec83..8ee4b9699 100644
--- a/js/src/admin/components/AdminNav.js
+++ b/js/src/admin/components/AdminNav.js
@@ -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.
*
@@ -29,6 +57,8 @@ export default class AdminNav extends Component {
items() {
const items = new ItemList();
+ items.add('category-core',
{app.translator.trans('core.admin.nav.categories.core')}
);
+
items.add(
'dashboard',
@@ -88,7 +118,7 @@ export default class AdminNav extends Component {
Object.keys(categorizedExtensions).map((category) => {
if (!this.query()) {
items.add(
- category,
+ `category-${category}`,
{app.translator.trans(`core.admin.nav.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)) {
items.add(
- extension.id,
+ `extension-${extension.id}`,
- {app.translator.trans('core.admin.extension.enable_to_see')}
+ {app.translator.trans('core.admin.extension.enable_to_see')}
) : (
{this.sections().toArray()}
@@ -105,7 +105,7 @@ export default class ExtensionPage extends Page {
{app.extensionData.extensionHasPermissions(this.extension.id) ? (
ExtensionPermissionGrid.component({ extensionId: this.extension.id })
) : (
- {app.translator.trans('core.admin.extension.no_permissions')}
+ {app.translator.trans('core.admin.extension.no_permissions')}
)}
,
@@ -130,7 +130,7 @@ export default class ExtensionPage extends Page {
{this.submitButton()}
) : (
- {app.translator.trans('core.admin.extension.no_settings')}
+ {app.translator.trans('core.admin.extension.no_settings')}
)}
@@ -170,17 +170,15 @@ export default class ExtensionPage extends Page {
infoItems() {
const items = new ItemList();
- if (this.extension.authors) {
+ const links = this.extension.links;
+
+ if (links.authors.length) {
let authors = [];
- Object.keys(this.extension.authors).map((author, i) => {
- const link = this.extension.authors[author].homepage
- ? this.extension.authors[author].homepage
- : 'mailto:' + this.extension.authors[author].email;
-
+ links.authors.map((author) => {
authors.push(
-
- {this.extension.authors[author].name}
+
+ {author.name}
);
});
@@ -188,35 +186,17 @@ export default class ExtensionPage extends Page {
items.add('authors', [icon('fas fa-user'), {punctuateSeries(authors)}]);
}
- 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) => {
- const info = this.extension.extra['flarum-extension'].info;
-
- if (info && info[field]) {
- infoData[field] = {
- icon: this.infoFields[field],
- href: info[field],
- };
+ if (links[field]) {
+ items.add(
+ field,
+
+ {app.translator.trans(`core.admin.extension.info_links.${field}`)}
+
+ );
}
});
- Object.entries(infoData).map(([field, value]) => {
- items.add(
- field,
-
- {app.translator.trans(`core.admin.extension.info_links.${field}`)}
-
- );
- });
-
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
* any standard type.
*
+ * Alternatively, you can pass a callback that will be executed in ExtensionPage's
+ * context to include custom JSX elements.
+ *
* @example
*
* {
@@ -258,6 +241,10 @@ export default class ExtensionPage extends Page {
* @returns {JSX.Element}
*/
buildSettingComponent(entry) {
+ if (typeof entry === 'function') {
+ return entry.call(this);
+ }
+
const setting = entry.setting;
const value = this.setting([setting])();
if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) {
diff --git a/js/src/admin/components/ExtensionsWidget.js b/js/src/admin/components/ExtensionsWidget.js
index 626fbe54d..f8a28d51d 100644
--- a/js/src/admin/components/ExtensionsWidget.js
+++ b/js/src/admin/components/ExtensionsWidget.js
@@ -15,33 +15,31 @@ export default class ExtensionsWidget extends DashboardWidget {
return (
-
- {Object.keys(categories).map((category) => {
- if (categorizedExtensions[category]) {
- return (
-
-
{app.translator.trans(`core.admin.nav.categories.${category}`)}
-
-
- );
- }
- })}
-
+ {Object.keys(categories).map((category) => {
+ if (categorizedExtensions[category]) {
+ return (
+
+
{app.translator.trans(`core.admin.nav.categories.${category}`)}
+
+
+ );
+ }
+ })}
);
}
diff --git a/js/src/admin/utils/ExtensionData.js b/js/src/admin/utils/ExtensionData.js
index 336a45f23..25f7a90a3 100644
--- a/js/src/admin/utils/ExtensionData.js
+++ b/js/src/admin/utils/ExtensionData.js
@@ -26,6 +26,8 @@ export default class ExtensionData {
/**
* This function registers your settings with Flarum
*
+ * It takes either a settings object or a callback.
+ *
* @example
*
* .registerSetting({
diff --git a/less/admin/AdminHeader.less b/less/admin/AdminHeader.less
index dd9c9414a..298105c6e 100644
--- a/less/admin/AdminHeader.less
+++ b/less/admin/AdminHeader.less
@@ -11,6 +11,7 @@
.AdminHeader-description {
margin: 0;
+ color: @control-color;
}
.icon {
diff --git a/less/admin/AdminNav.less b/less/admin/AdminNav.less
index 2a71e45ee..bafdbca7d 100644
--- a/less/admin/AdminNav.less
+++ b/less/admin/AdminNav.less
@@ -41,16 +41,13 @@
}
@media @tablet {
- .item-search{
- display: none;
- }
-
- .ExtensionItem, .item-search {
- display: none !important;
- }
-
- .ExtensionListTitle {
- display: none !important;
+ .AdminNav {
+ .item-search,
+ li[class^="item-category"],
+ li[class^="item-extension"],
+ .AdminLinkButton-description {
+ display: none !important;
+ }
}
}
@@ -80,7 +77,7 @@
}
-@media @desktop, @desktop-hd {
+@media @desktop-up {
.App-nav {
position: absolute;
top: @header-height;
@@ -107,36 +104,47 @@
margin-bottom: 20px;
}
+ .item-category-core {
+ > .ExtensionListTitle {
+ margin-top: 10px;
+ }
+ }
+
> li {
> a {
padding: 10px 10px 10px 45px;
display: block;
text-decoration: none;
}
+
> a,
> a:hover,
&.active > a {
color: @text-color;
}
+
> a:hover {
background: @control-bg;
}
+
&.active > a {
- background: @primary-color;
+ background: @control-color;
font-weight: normal;
+ color: @body-bg;
.Button-label,
.Button-icon {
- color: @body-bg;
font-weight: bold;
}
}
+
.Button-icon {
float: left;
font-size: 13px !important;
margin-left: -25px !important;
margin-top: 4px !important;
}
+
.Button-label {
padding-left: 5px;
font-size: 14px;
@@ -152,7 +160,7 @@
.ExtensionListTitle {
color: @muted-color;
text-transform: uppercase;
- margin: 25px 0 15px 15px;
+ margin: 25px 0 8px 15px;
}
.ExtensionIcon {
@@ -180,6 +188,11 @@
}
+.AdminLinkButton-description {
+ white-space: normal;
+ padding-left: 5px;
+}
+
.ExtensionListItem-Dot {
height: 10px;
width: 10px;
diff --git a/less/admin/DashboardPage.less b/less/admin/DashboardPage.less
index 57c623ba7..589b5f281 100644
--- a/less/admin/DashboardPage.less
+++ b/less/admin/DashboardPage.less
@@ -10,17 +10,21 @@
border-radius: @border-radius;
padding: 20px;
margin-bottom: 20px;
+
+ .Button {
+ .Button--color(@control-color, @body-bg)
+ }
}
.StatusWidget {
color: @muted-color;
- > ul {
+ >ul {
margin: 0;
padding: 0;
list-style-type: none;
- > li {
+ >li {
display: inline-block;
margin-right: 30px;
vertical-align: middle;
@@ -31,6 +35,7 @@
overflow: hidden;
text-overflow: ellipsis;
}
+
&.item-tools {
float: right;
margin-right: 0;
diff --git a/less/admin/ExtensionPage.less b/less/admin/ExtensionPage.less
index 5bb5cefb6..dc98c415d 100644
--- a/less/admin/ExtensionPage.less
+++ b/less/admin/ExtensionPage.less
@@ -119,6 +119,11 @@
}
}
+ .ExtensionPage-settings, .ExtensionPage-permissions {
+ .ExtensionPage-subHeader {
+ margin: 5px 0px;
+ }
+ }
.ExtensionPage-settings {
margin-top: 20px;
@@ -132,7 +137,6 @@
.ExtensionPage-subHeader {
color: @muted-color;
font-weight: normal;
- text-align: center;
}
diff --git a/less/admin/ExtensionWidget.less b/less/admin/ExtensionWidget.less
index 689aec6ec..92437af56 100644
--- a/less/admin/ExtensionWidget.less
+++ b/less/admin/ExtensionWidget.less
@@ -4,77 +4,75 @@
}
.ExtensionsWidget-list {
- > .container {
- padding: 0;
- background-color: @body-bg;
+ padding: 0;
+ background-color: @body-bg;
- .ExtensionList-Category {
- background: @control-bg;
- padding: 20px 0 20px 20px;
- margin-bottom: 20px;
- border-radius: @border-radius;
+ .ExtensionList-Category {
+ background: @control-bg;
+ padding: 20px 0 20px 20px;
+ margin-bottom: 20px;
+ border-radius: @border-radius;
- .ExtensionList-Label {
- margin-top: 0;
- color: @muted-color;
- }
- }
-
- .ExtensionGroup {
- margin-bottom: 20px;
-
- h3 {
- color: @muted-color;
- text-transform: uppercase;
- font-size: 12px;
- margin: 0 0 10px;
- }
- }
-
- .ExtensionList {
- padding: 0;
- list-style: none;
- display: grid;
- grid-gap: 10px;
- grid-template-columns: repeat(auto-fit, 90px);
- margin-bottom: 0;
-
- > li {
- text-align: left;
- position: relative;
- display: block;
- }
- }
- }
-
- .ExtensionListItem.disabled {
- .ExtensionListItem-title {
- opacity: 0.5;
+ .ExtensionList-Label {
+ margin-top: 0;
color: @muted-color;
}
+ }
- .ExtensionListItem-icon {
- opacity: 0.5;
+ .ExtensionGroup {
+ margin-bottom: 20px;
+
+ h3 {
+ color: @muted-color;
+ text-transform: uppercase;
+ font-size: 12px;
+ margin: 0 0 10px;
}
}
- .ExtensionListItem {
- transition: .15s ease-in-out;
+ .ExtensionList {
+ padding: 0;
+ list-style: none;
+ display: grid;
+ grid-gap: 10px;
+ grid-template-columns: repeat(auto-fit, 90px);
+ margin-bottom: 0;
- &:hover {
- transform: scale(1.05);
- }
-
- .ExtensionListItem-title {
+ > li {
+ text-align: left;
+ position: relative;
display: block;
- text-align: center;
- margin-top: 5px;
- color: @text-color;
}
+ }
+}
- a:hover {
- text-decoration: none;
- }
+.ExtensionListItem.disabled {
+ .ExtensionListItem-title {
+ opacity: 0.5;
+ color: @muted-color;
+ }
+
+ .ExtensionListItem-icon {
+ opacity: 0.5;
+ }
+}
+
+.ExtensionListItem {
+ transition: .15s ease-in-out;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+
+ .ExtensionListItem-title {
+ display: block;
+ text-align: center;
+ margin-top: 5px;
+ color: @text-color;
+ }
+
+ a:hover {
+ text-decoration: none;
}
}
diff --git a/less/admin/PermissionsPage.less b/less/admin/PermissionsPage.less
index ac7b01135..50b72ed34 100644
--- a/less/admin/PermissionsPage.less
+++ b/less/admin/PermissionsPage.less
@@ -5,7 +5,11 @@
display: block;
margin-left: 30px;
overflow-x: auto;
- padding: 8px 0 8px;
+ padding: 10px 0 8px;
+
+ > .container {
+ padding: 0 10px;
+ }
}
.Group {
width: 90px;
@@ -42,6 +46,7 @@
.PermissionGrid {
white-space: nowrap;
+ padding-left: 0 12px;
td, th {
padding: 5px;
@@ -117,7 +122,7 @@
}
.PermissionGrid-section {
td, th {
- padding-top: 20px;
+ padding-top: 10px;
}
}
.PermissionGrid-child {
diff --git a/src/Extension/Extension.php b/src/Extension/Extension.php
index ea7b90ed0..cdda23d67 100644
--- a/src/Extension/Extension.php
+++ b/src/Extension/Extension.php
@@ -346,6 +346,49 @@ class Extension implements Arrayable
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.
*
@@ -413,6 +456,7 @@ class Extension implements Arrayable
'hasAssets' => $this->hasAssets(),
'hasMigrations' => $this->hasMigrations(),
'extensionDependencyIds' => $this->getExtensionDependencyIds(),
+ 'links' => $this->getLinks(),
], $this->composerJson);
}
}