From 0a4cb95c98d4b6f7c3e404535996f706c659e13e Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Thu, 2 Jul 2020 09:00:17 +0200 Subject: simplify navigation within most admin menus --- client/src/app/+admin/admin.component.html | 8 +- client/src/app/+admin/admin.component.scss | 3 + client/src/app/+admin/admin.component.ts | 71 +++++++++-- .../src/app/+admin/follows/follows.component.html | 12 -- .../+admin/moderation/moderation.component.html | 14 --- .../app/+admin/moderation/moderation.component.ts | 21 +--- client/src/app/+my-account/my-account.component.ts | 2 +- client/src/app/+my-account/my-account.module.ts | 3 - .../+my-account/top-menu-dropdown.component.html | 50 -------- .../+my-account/top-menu-dropdown.component.scss | 56 --------- .../app/+my-account/top-menu-dropdown.component.ts | 131 --------------------- .../shared/shared-icons/global-icon.component.ts | 1 + client/src/app/shared/shared-main/misc/index.ts | 1 + .../misc/top-menu-dropdown.component.html | 50 ++++++++ .../misc/top-menu-dropdown.component.scss | 56 +++++++++ .../misc/top-menu-dropdown.component.ts | 131 +++++++++++++++++++++ .../app/shared/shared-main/shared-main.module.ts | 10 +- client/src/assets/images/feather/log-in.svg | 1 + 18 files changed, 314 insertions(+), 307 deletions(-) create mode 100644 client/src/app/+admin/admin.component.scss delete mode 100644 client/src/app/+my-account/top-menu-dropdown.component.html delete mode 100644 client/src/app/+my-account/top-menu-dropdown.component.scss delete mode 100644 client/src/app/+my-account/top-menu-dropdown.component.ts create mode 100644 client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html create mode 100644 client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss create mode 100644 client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts create mode 100644 client/src/assets/images/feather/log-in.svg (limited to 'client') diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html index 76d297c52..3999252be 100644 --- a/client/src/app/+admin/admin.component.html +++ b/client/src/app/+admin/admin.component.html @@ -1,11 +1,5 @@
- +
diff --git a/client/src/app/+admin/admin.component.scss b/client/src/app/+admin/admin.component.scss new file mode 100644 index 000000000..ef8965c3f --- /dev/null +++ b/client/src/app/+admin/admin.component.scss @@ -0,0 +1,3 @@ +my-top-menu-dropdown { + flex-grow: 1; +} diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index e47c7a8f4..c1c160ad1 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts @@ -3,12 +3,15 @@ import { AuthService } from '@app/core' import { ListOverflowItem } from '@app/shared/shared-main' import { I18n } from '@ngx-translate/i18n-polyfill' import { UserRight } from '@shared/models' +import { TopMenuDropdownParam } from '@app/shared/shared-main/misc/top-menu-dropdown.component' @Component({ - templateUrl: './admin.component.html' + templateUrl: './admin.component.html', + styleUrls: [ './admin.component.scss' ] }) export class AdminComponent implements OnInit { items: ListOverflowItem[] = [] + menuEntries: TopMenuDropdownParam[] = [] constructor ( private auth: AuthService, @@ -16,12 +19,58 @@ export class AdminComponent implements OnInit { ) {} ngOnInit () { - if (this.hasUsersRight()) this.items.push({ label: this.i18n('Users'), routerLink: '/admin/users' }) - if (this.hasServerFollowRight()) this.items.push({ label: this.i18n('Follows & redundancies'), routerLink: '/admin/follows' }) - if (this.hasVideoAbusesRight() || this.hasVideoBlocklistRight()) this.items.push({ label: this.i18n('Moderation'), routerLink: '/admin/moderation' }) - if (this.hasConfigRight()) this.items.push({ label: this.i18n('Configuration'), routerLink: '/admin/config' }) - if (this.hasPluginsRight()) this.items.push({ label: this.i18n('Plugins/Themes'), routerLink: '/admin/plugins' }) - if (this.hasJobsRight() || this.hasLogsRight() || this.hasDebugRight()) this.items.push({ label: this.i18n('System'), routerLink: '/admin/system' }) + const federationItems: TopMenuDropdownParam = { + label: this.i18n('Federation'), + children: [ + { + label: this.i18n('Instances you follow'), + routerLink: '/admin/follows/following-list', + iconName: 'sign-out' + }, + { + label: this.i18n('Instances following you'), + routerLink: '/admin/follows/followers-list', + iconName: 'sign-in' + }, + { + label: this.i18n('Video redundancies'), + routerLink: '/admin/follows/video-redundancies-list', + iconName: 'videos' + } + ] + } + + const moderationItems: TopMenuDropdownParam = { + label: this.i18n('Moderation'), + children: [] + } + if (this.hasVideoAbusesRight()) moderationItems.children.push({ + label: this.i18n('Video reports'), + routerLink: '/admin/moderation/video-abuses/list', + iconName: 'flag' + }) + if (this.hasVideoBlocklistRight()) moderationItems.children.push({ + label: this.i18n('Video blocks'), + routerLink: '/admin/moderation/video-blocks/list', + iconName: 'cross' + }) + if (this.hasAccountsBlocklistRight()) moderationItems.children.push({ + label: this.i18n('Muted accounts'), + routerLink: '/admin/moderation/blocklist/accounts', + iconName: 'user' + }) + if (this.hasServersBlocklistRight()) moderationItems.children.push({ + label: this.i18n('Muted servers'), + routerLink: '/admin/moderation/blocklist/servers', + iconName: 'server' + }) + + if (this.hasUsersRight()) this.menuEntries.push({ label: this.i18n('Users'), routerLink: '/admin/users' }) + if (this.hasServerFollowRight()) this.menuEntries.push(federationItems) + if (this.hasVideoAbusesRight() || this.hasVideoBlocklistRight()) this.menuEntries.push(moderationItems) + if (this.hasConfigRight()) this.menuEntries.push({ label: this.i18n('Configuration'), routerLink: '/admin/config' }) + if (this.hasPluginsRight()) this.menuEntries.push({ label: this.i18n('Plugins/Themes'), routerLink: '/admin/plugins' }) + if (this.hasJobsRight() || this.hasLogsRight() || this.hasDebugRight()) this.menuEntries.push({ label: this.i18n('System'), routerLink: '/admin/system' }) } hasUsersRight () { @@ -40,6 +89,14 @@ export class AdminComponent implements OnInit { return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) } + hasAccountsBlocklistRight () { + return this.auth.getUser().hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST) + } + + hasServersBlocklistRight () { + return this.auth.getUser().hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST) + } + hasConfigRight () { return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION) } diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html index 8c3129394..0680b43f9 100644 --- a/client/src/app/+admin/follows/follows.component.html +++ b/client/src/app/+admin/follows/follows.component.html @@ -1,13 +1 @@ -
-

Follows & redundancies

- - -
- diff --git a/client/src/app/+admin/moderation/moderation.component.html b/client/src/app/+admin/moderation/moderation.component.html index 7bab63c33..90c6b6463 100644 --- a/client/src/app/+admin/moderation/moderation.component.html +++ b/client/src/app/+admin/moderation/moderation.component.html @@ -1,15 +1 @@ -
-

Moderation

- - -
- \ No newline at end of file diff --git a/client/src/app/+admin/moderation/moderation.component.ts b/client/src/app/+admin/moderation/moderation.component.ts index 806f9d100..b0f5eb224 100644 --- a/client/src/app/+admin/moderation/moderation.component.ts +++ b/client/src/app/+admin/moderation/moderation.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core' -import { AuthService, ServerService } from '@app/core' -import { UserRight } from '@shared/models' +import { ServerService } from '@app/core' @Component({ templateUrl: './moderation.component.html', @@ -10,29 +9,11 @@ export class ModerationComponent implements OnInit { autoBlockVideosEnabled = false constructor ( - private auth: AuthService, private serverService: ServerService ) { } ngOnInit (): void { this.serverService.getConfig() .subscribe(config => this.autoBlockVideosEnabled = config.autoBlacklist.videos.ofUsers.enabled) - - } - - hasVideoAbusesRight () { - return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES) - } - - hasVideoBlocklistRight () { - return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) - } - - hasAccountsBlocklistRight () { - return this.auth.getUser().hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST) - } - - hasServersBlocklistRight () { - return this.auth.getUser().hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST) } } diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts index 85b2795c5..abc823c62 100644 --- a/client/src/app/+my-account/my-account.component.ts +++ b/client/src/app/+my-account/my-account.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core' import { ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { ServerConfig } from '@shared/models' -import { TopMenuDropdownParam } from './top-menu-dropdown.component' +import { TopMenuDropdownParam } from '../shared/shared-main/misc/top-menu-dropdown.component' @Component({ selector: 'my-my-account', diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 8604b8b60..0ce243844 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts @@ -33,7 +33,6 @@ import { MyAccountVideoPlaylistsComponent } from './my-account-video-playlists/m import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component' import { VideoChangeOwnershipComponent } from './my-account-videos/video-change-ownership/video-change-ownership.component' import { MyAccountComponent } from './my-account.component' -import { TopMenuDropdownComponent } from './top-menu-dropdown.component' @NgModule({ imports: [ @@ -79,8 +78,6 @@ import { TopMenuDropdownComponent } from './top-menu-dropdown.component' MyAccountVideoPlaylistUpdateComponent, MyAccountVideoPlaylistsComponent, MyAccountVideoPlaylistElementsComponent, - - TopMenuDropdownComponent ], exports: [ diff --git a/client/src/app/+my-account/top-menu-dropdown.component.html b/client/src/app/+my-account/top-menu-dropdown.component.html deleted file mode 100644 index aeaceb662..000000000 --- a/client/src/app/+my-account/top-menu-dropdown.component.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/client/src/app/+my-account/top-menu-dropdown.component.scss b/client/src/app/+my-account/top-menu-dropdown.component.scss deleted file mode 100644 index 84dd7dce3..000000000 --- a/client/src/app/+my-account/top-menu-dropdown.component.scss +++ /dev/null @@ -1,56 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -.parent-entry { - span[role=button] { - cursor: pointer; - } - - a { - display: block; - } -} - -::ng-deep .dropdown-toggle::after { - position: relative; - top: 2px; -} - -::ng-deep .dropdown-menu { - margin-top: 0 !important; -} - -.icon { - @include dropdown-with-icon-item; - - top: -1px; -} - -.sub-menu.no-scroll { - overflow-x: hidden; -} - -.modal-body { - .hidden { - display: none; - } - - a { - @include disable-default-a-behaviour; - - color: currentColor; - box-sizing: border-box; - display: block; - font-size: 1.2rem; - padding: 9px 12px; - text-align: initial; - text-transform: unset; - width: 100%; - - &.active { - color: pvar(--mainBackgroundColor) !important; - background-color: pvar(--mainHoverColor); - opacity: .9; - } - } -} diff --git a/client/src/app/+my-account/top-menu-dropdown.component.ts b/client/src/app/+my-account/top-menu-dropdown.component.ts deleted file mode 100644 index 5909db0b5..000000000 --- a/client/src/app/+my-account/top-menu-dropdown.component.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Subscription } from 'rxjs' -import { filter, take } from 'rxjs/operators' -import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' -import { NavigationEnd, Router } from '@angular/router' -import { MenuService, ScreenService } from '@app/core' -import { GlobalIconName } from '@app/shared/shared-icons' -import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' - -export type TopMenuDropdownParam = { - label: string - routerLink?: string - - children?: { - label: string - routerLink: string - - iconName?: GlobalIconName - }[] -} - -@Component({ - selector: 'my-top-menu-dropdown', - templateUrl: './top-menu-dropdown.component.html', - styleUrls: [ './top-menu-dropdown.component.scss' ] -}) -export class TopMenuDropdownComponent implements OnInit, OnDestroy { - @Input() menuEntries: TopMenuDropdownParam[] = [] - - @ViewChild('modal', { static: true }) modal: NgbModal - - suffixLabels: { [ parentLabel: string ]: string } - hasIcons = false - isModalOpened = false - currentMenuEntryIndex: number - - private openedOnHover = false - private routeSub: Subscription - - constructor ( - private router: Router, - private modalService: NgbModal, - private screen: ScreenService, - private menuService: MenuService - ) { } - - get isInSmallView () { - let marginLeft = 0 - if (this.menuService.isMenuDisplayed) { - marginLeft = this.menuService.menuWidth - } - - return this.screen.isInSmallView(marginLeft) - } - - ngOnInit () { - this.updateChildLabels(window.location.pathname) - - this.routeSub = this.router.events - .pipe(filter(event => event instanceof NavigationEnd)) - .subscribe(() => this.updateChildLabels(window.location.pathname)) - - this.hasIcons = this.menuEntries.some( - e => e.children && e.children.some(c => !!c.iconName) - ) - } - - ngOnDestroy () { - if (this.routeSub) this.routeSub.unsubscribe() - } - - openDropdownOnHover (dropdown: NgbDropdown) { - this.openedOnHover = true - dropdown.open() - - // Menu was closed - dropdown.openChange - .pipe(take(1)) - .subscribe(() => this.openedOnHover = false) - } - - dropdownAnchorClicked (dropdown: NgbDropdown) { - if (this.openedOnHover) { - this.openedOnHover = false - return - } - - return dropdown.toggle() - } - - closeDropdownIfHovered (dropdown: NgbDropdown) { - if (this.openedOnHover === false) return - - dropdown.close() - this.openedOnHover = false - } - - openModal (index: number) { - this.currentMenuEntryIndex = index - this.isModalOpened = true - - this.modalService.open(this.modal, { - centered: true, - beforeDismiss: async () => { - this.onModalDismiss() - return true - } - }) - } - - onModalDismiss () { - this.isModalOpened = false - } - - dismissOtherModals () { - this.modalService.dismissAll() - } - - private updateChildLabels (path: string) { - this.suffixLabels = {} - - for (const entry of this.menuEntries) { - if (!entry.children) continue - - for (const child of entry.children) { - if (path.startsWith(child.routerLink)) { - this.suffixLabels[entry.label] = child.label - } - } - } - } -} diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts index 75ab9e8f5..7f7315f06 100644 --- a/client/src/app/shared/shared-icons/global-icon.component.ts +++ b/client/src/app/shared/shared-icons/global-icon.component.ts @@ -34,6 +34,7 @@ const icons = { 'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default, 'inbox-full': require('!!raw-loader?!../../../assets/images/feather/inbox-full.svg').default, 'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default, + 'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default, 'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default, 'ownership-change': require('!!raw-loader?!../../../assets/images/feather/share.svg').default, 'share': require('!!raw-loader?!../../../assets/images/feather/share-2.svg').default, diff --git a/client/src/app/shared/shared-main/misc/index.ts b/client/src/app/shared/shared-main/misc/index.ts index d3e7e4be7..e806fd2f2 100644 --- a/client/src/app/shared/shared-main/misc/index.ts +++ b/client/src/app/shared/shared-main/misc/index.ts @@ -1,2 +1,3 @@ export * from './help.component' export * from './list-overflow.component' +export * from './top-menu-dropdown.component' diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html new file mode 100644 index 000000000..aeaceb662 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html @@ -0,0 +1,50 @@ + + + + + diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss new file mode 100644 index 000000000..84dd7dce3 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss @@ -0,0 +1,56 @@ +@import '_variables'; +@import '_mixins'; + +.parent-entry { + span[role=button] { + cursor: pointer; + } + + a { + display: block; + } +} + +::ng-deep .dropdown-toggle::after { + position: relative; + top: 2px; +} + +::ng-deep .dropdown-menu { + margin-top: 0 !important; +} + +.icon { + @include dropdown-with-icon-item; + + top: -1px; +} + +.sub-menu.no-scroll { + overflow-x: hidden; +} + +.modal-body { + .hidden { + display: none; + } + + a { + @include disable-default-a-behaviour; + + color: currentColor; + box-sizing: border-box; + display: block; + font-size: 1.2rem; + padding: 9px 12px; + text-align: initial; + text-transform: unset; + width: 100%; + + &.active { + color: pvar(--mainBackgroundColor) !important; + background-color: pvar(--mainHoverColor); + opacity: .9; + } + } +} diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts new file mode 100644 index 000000000..5909db0b5 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts @@ -0,0 +1,131 @@ +import { Subscription } from 'rxjs' +import { filter, take } from 'rxjs/operators' +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' +import { NavigationEnd, Router } from '@angular/router' +import { MenuService, ScreenService } from '@app/core' +import { GlobalIconName } from '@app/shared/shared-icons' +import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' + +export type TopMenuDropdownParam = { + label: string + routerLink?: string + + children?: { + label: string + routerLink: string + + iconName?: GlobalIconName + }[] +} + +@Component({ + selector: 'my-top-menu-dropdown', + templateUrl: './top-menu-dropdown.component.html', + styleUrls: [ './top-menu-dropdown.component.scss' ] +}) +export class TopMenuDropdownComponent implements OnInit, OnDestroy { + @Input() menuEntries: TopMenuDropdownParam[] = [] + + @ViewChild('modal', { static: true }) modal: NgbModal + + suffixLabels: { [ parentLabel: string ]: string } + hasIcons = false + isModalOpened = false + currentMenuEntryIndex: number + + private openedOnHover = false + private routeSub: Subscription + + constructor ( + private router: Router, + private modalService: NgbModal, + private screen: ScreenService, + private menuService: MenuService + ) { } + + get isInSmallView () { + let marginLeft = 0 + if (this.menuService.isMenuDisplayed) { + marginLeft = this.menuService.menuWidth + } + + return this.screen.isInSmallView(marginLeft) + } + + ngOnInit () { + this.updateChildLabels(window.location.pathname) + + this.routeSub = this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(() => this.updateChildLabels(window.location.pathname)) + + this.hasIcons = this.menuEntries.some( + e => e.children && e.children.some(c => !!c.iconName) + ) + } + + ngOnDestroy () { + if (this.routeSub) this.routeSub.unsubscribe() + } + + openDropdownOnHover (dropdown: NgbDropdown) { + this.openedOnHover = true + dropdown.open() + + // Menu was closed + dropdown.openChange + .pipe(take(1)) + .subscribe(() => this.openedOnHover = false) + } + + dropdownAnchorClicked (dropdown: NgbDropdown) { + if (this.openedOnHover) { + this.openedOnHover = false + return + } + + return dropdown.toggle() + } + + closeDropdownIfHovered (dropdown: NgbDropdown) { + if (this.openedOnHover === false) return + + dropdown.close() + this.openedOnHover = false + } + + openModal (index: number) { + this.currentMenuEntryIndex = index + this.isModalOpened = true + + this.modalService.open(this.modal, { + centered: true, + beforeDismiss: async () => { + this.onModalDismiss() + return true + } + }) + } + + onModalDismiss () { + this.isModalOpened = false + } + + dismissOtherModals () { + this.modalService.dismissAll() + } + + private updateChildLabels (path: string) { + this.suffixLabels = {} + + for (const entry of this.menuEntries) { + if (!entry.children) continue + + for (const child of entry.children) { + if (path.startsWith(child.routerLink)) { + this.suffixLabels[entry.label] = child.label + } + } + } + } +} diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index fd96a42a0..04e3eb0af 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts @@ -25,7 +25,7 @@ import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditBu import { DateToggleComponent } from './date' import { FeedComponent } from './feeds' import { LoaderComponent, SmallLoaderComponent } from './loaders' -import { HelpComponent, ListOverflowComponent } from './misc' +import { HelpComponent, ListOverflowComponent, TopMenuDropdownComponent } from './misc' import { UserHistoryService, UserNotificationsComponent, UserNotificationService } from './users' import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' import { VideoCaptionService } from './video-caption' @@ -81,10 +81,9 @@ import { AUTH_INTERCEPTOR_PROVIDER } from './auth' HelpComponent, ListOverflowComponent, + TopMenuDropdownComponent, UserNotificationsComponent, - - FeedComponent ], exports: [ @@ -131,10 +130,9 @@ import { AUTH_INTERCEPTOR_PROVIDER } from './auth' HelpComponent, ListOverflowComponent, + TopMenuDropdownComponent, - UserNotificationsComponent, - - FeedComponent + UserNotificationsComponent ], providers: [ diff --git a/client/src/assets/images/feather/log-in.svg b/client/src/assets/images/feather/log-in.svg new file mode 100644 index 000000000..ba0da59a1 --- /dev/null +++ b/client/src/assets/images/feather/log-in.svg @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.3