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 --- .../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 +- 6 files changed, 243 insertions(+), 6 deletions(-) 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 (limited to 'client/src/app/shared') 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: [ -- cgit v1.2.3