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}`)}

-
    - {categorizedExtensions[category].map((extension) => { - return ( -
  • - -
    - - {extension.icon ? icon(extension.icon.name) : ''} - - {extension.extra['flarum-extension'].title} -
    - -
  • - ); - })} -
-
- ); - } - })} -
+ {Object.keys(categories).map((category) => { + if (categorizedExtensions[category]) { + return ( +
+

{app.translator.trans(`core.admin.nav.categories.${category}`)}

+
    + {categorizedExtensions[category].map((extension) => { + return ( +
  • + +
    + + {extension.icon ? icon(extension.icon.name) : ''} + + {extension.extra['flarum-extension'].title} +
    + +
  • + ); + })} +
+
+ ); + } + })}
); } 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); } }