From 24e7916c6897bbb38e057cdf1a102286006be964 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Wed, 5 Feb 2020 20:54:37 +0100 Subject: [PATCH] Add ListOverflow component to prevent sub-menu overflow --- .../contact-admin-modal.component.ts | 2 +- .../src/app/+accounts/accounts.component.html | 10 +- .../src/app/+accounts/accounts.component.ts | 8 ++ client/src/app/+admin/admin.component.html | 26 +--- client/src/app/+admin/admin.component.ts | 22 +++- .../moderation-comment-modal.component.ts | 2 +- .../my-account-accept-ownership.component.ts | 2 +- .../video-change-ownership.component.ts | 2 +- .../video-channels.component.html | 10 +- .../video-channels.component.ts | 8 ++ .../app/menu/language-chooser.component.ts | 2 +- ...instance-config-warning-modal.component.ts | 2 +- .../src/app/modal/welcome-modal.component.ts | 3 +- .../shared/misc/list-overflow.component.html | 35 ++++++ .../shared/misc/list-overflow.component.scss | 61 ++++++++++ .../shared/misc/list-overflow.component.ts | 114 ++++++++++++++++++ .../moderation/user-ban-modal.component.ts | 2 +- client/src/app/shared/shared.module.ts | 3 + .../video/modals/video-blacklist.component.ts | 2 +- .../video/modals/video-download.component.ts | 2 +- .../video/modals/video-report.component.ts | 2 +- .../video-caption-add-modal.component.ts | 2 +- .../modal/video-share.component.ts | 2 +- .../modal/video-support.component.ts | 2 +- client/src/sass/application.scss | 2 +- client/src/sass/bootstrap.scss | 8 +- 26 files changed, 283 insertions(+), 53 deletions(-) create mode 100644 client/src/app/shared/misc/list-overflow.component.html create mode 100644 client/src/app/shared/misc/list-overflow.component.scss create mode 100644 client/src/app/shared/misc/list-overflow.component.ts diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts index 2ed41e741..d5e146b82 100644 --- a/client/src/app/+about/about-instance/contact-admin-modal.component.ts +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts @@ -51,7 +51,7 @@ export class ContactAdminModalComponent extends FormReactive implements OnInit { } show () { - this.openedModal = this.modalService.open(this.modal, { keyboard: false }) + this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) } hide () { diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 915bee0a2..b982fba9a 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html @@ -38,12 +38,12 @@ - diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index a8157de0e..4fea0e4ed 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts @@ -10,6 +10,7 @@ import { User, UserRight } from '../../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' +import { ListOverflowItem } from '@app/shared/misc/list-overflow.component' @Component({ templateUrl: './accounts.component.html', @@ -19,6 +20,7 @@ export class AccountsComponent implements OnInit, OnDestroy { account: Account accountUser: User videoChannels: VideoChannel[] = [] + links: ListOverflowItem[] = [] isAccountManageable = false accountFollowerTitle = '' @@ -70,6 +72,12 @@ export class AccountsComponent implements OnInit, OnDestroy { err => this.notifier.error(err.message) ) + + this.links = [ + { label: this.i18n('Video channels'), routerLink: 'video-channels' }, + { label: this.i18n('Videos'), routerLink: 'videos' }, + { label: this.i18n('About'), routerLink: 'about' } + ] } ngOnDestroy () { diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html index 0d06aaedc..6c98fe453 100644 --- a/client/src/app/+admin/admin.component.html +++ b/client/src/app/+admin/admin.component.html @@ -1,28 +1,10 @@
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index b23999d40..9662522dc 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts @@ -1,12 +1,28 @@ -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { UserRight } from '../../../../shared' import { AuthService } from '../core/auth/auth.service' +import { ListOverflowItem } from '@app/shared/misc/list-overflow.component' +import { I18n } from '@ngx-translate/i18n-polyfill' @Component({ templateUrl: './admin.component.html' }) -export class AdminComponent { - constructor (private auth: AuthService) {} +export class AdminComponent implements OnInit { + items: ListOverflowItem[] = [] + + constructor ( + private auth: AuthService, + private i18n: I18n + ) {} + + 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.hasVideoBlacklistRight()) 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' }) + } hasUsersRight () { return this.auth.getUser().hasRight(UserRight.MANAGE_USERS) diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts index f8a5ef8cb..29f90194b 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts @@ -38,7 +38,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI openModal (abuseToComment: VideoAbuse) { this.abuseToComment = abuseToComment - this.openedModal = this.modalService.open(this.modal) + this.openedModal = this.modalService.open(this.modal, { centered: true }) this.form.patchValue({ moderationComment: this.abuseToComment.moderationComment diff --git a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts index 6df929ec9..d5682914e 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts +++ b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts @@ -53,7 +53,7 @@ export class MyAccountAcceptOwnershipComponent extends FormReactive implements O show (videoChangeOwnership: VideoChangeOwnership) { this.videoChangeOwnership = videoChangeOwnership this.modalService - .open(this.modal) + .open(this.modal, { centered: true }) .result .then(() => this.acceptOwnership()) .catch(() => this.videoChangeOwnership = undefined) diff --git a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts index 36d1ea091..f4e2b5955 100644 --- a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts +++ b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts @@ -43,7 +43,7 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni show (video: Video) { this.video = video this.modalService - .open(this.modal) + .open(this.modal, { centered: true }) .result .then(() => this.changeOwnership()) .catch((_) => _) // Called when closing (cancel) the modal without validating, do nothing diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html index debda9948..735a8f2c8 100644 --- a/client/src/app/+video-channels/video-channels.component.html +++ b/client/src/app/+video-channels/video-channels.component.html @@ -29,10 +29,12 @@
- diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts index 5ca9581a8..0889ca854 100644 --- a/client/src/app/+video-channels/video-channels.component.ts +++ b/client/src/app/+video-channels/video-channels.component.ts @@ -9,6 +9,7 @@ import { AuthService, Notifier } from '@app/core' import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' import { I18n } from '@ngx-translate/i18n-polyfill' +import { ListOverflowItem } from '@app/shared/misc/list-overflow.component' @Component({ templateUrl: './video-channels.component.html', @@ -19,6 +20,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { videoChannel: VideoChannel hotkeys: Hotkey[] + links: ListOverflowItem[] = [] isChannelManageable = false private routeSub: Subscription @@ -62,6 +64,12 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { }, undefined, this.i18n('Subscribe to the account')) ] if (this.isUserLoggedIn()) this.hotkeysService.add(this.hotkeys) + + this.links = [ + { label: this.i18n('Videos'), routerLink: 'videos' }, + { label: this.i18n('Video playlists'), routerLink: 'video-playlists' }, + { label: this.i18n('About'), routerLink: 'about' } + ] } ngOnDestroy () { diff --git a/client/src/app/menu/language-chooser.component.ts b/client/src/app/menu/language-chooser.component.ts index 4a6e4c75a..43f622dfb 100644 --- a/client/src/app/menu/language-chooser.component.ts +++ b/client/src/app/menu/language-chooser.component.ts @@ -21,7 +21,7 @@ export class LanguageChooserComponent { } show () { - this.modalService.open(this.modal) + this.modalService.open(this.modal, { centered: true }) } buildLanguageLink (lang: { id: string }) { diff --git a/client/src/app/modal/instance-config-warning-modal.component.ts b/client/src/app/modal/instance-config-warning-modal.component.ts index 742a7dd41..5e1433548 100644 --- a/client/src/app/modal/instance-config-warning-modal.component.ts +++ b/client/src/app/modal/instance-config-warning-modal.component.ts @@ -24,7 +24,7 @@ export class InstanceConfigWarningModalComponent { show (about: About) { this.about = about - const ref = this.modalService.open(this.modal) + const ref = this.modalService.open(this.modal, { centered: true }) ref.result.finally(() => { if (this.stopDisplayModal === true) this.doNotOpenAgain() diff --git a/client/src/app/modal/welcome-modal.component.ts b/client/src/app/modal/welcome-modal.component.ts index 19a147b85..e022776e3 100644 --- a/client/src/app/modal/welcome-modal.component.ts +++ b/client/src/app/modal/welcome-modal.component.ts @@ -18,7 +18,8 @@ export class WelcomeModalComponent { ) { } show () { - this.modalService.open(this.modal,{ + this.modalService.open(this.modal, { + centered: true, backdrop: 'static', keyboard: false, size: 'lg' diff --git a/client/src/app/shared/misc/list-overflow.component.html b/client/src/app/shared/misc/list-overflow.component.html new file mode 100644 index 000000000..986572801 --- /dev/null +++ b/client/src/app/shared/misc/list-overflow.component.html @@ -0,0 +1,35 @@ +
+ + + + + + + +
+ + +
+ + {{ item.label }} + +
+
+
+
+ + + + diff --git a/client/src/app/shared/misc/list-overflow.component.scss b/client/src/app/shared/misc/list-overflow.component.scss new file mode 100644 index 000000000..e26100aca --- /dev/null +++ b/client/src/app/shared/misc/list-overflow.component.scss @@ -0,0 +1,61 @@ +@import '_mixins'; + +:host { + width: 100%; +} + +.list-overflow-parent { + overflow: hidden; +} + +.list-overflow-menu { + position: absolute; + right: 0; +} + +button { + width: 30px; + border: none; + + &::after { + display: none; + } + + &.routeActive { + &::after { + display: inherit; + border: 2px solid var(--mainColor); + position: relative; + right: 95%; + top: 50%; + } + } +} + +::ng-deep .dropdown-menu { + margin-top: 0 !important; + position: static; + right: auto; + bottom: auto +} + +.modal-body { + 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: var(--mainBackgroundColor) !important; + background-color: var(--mainHoverColor); + opacity: .9; + } + } +} diff --git a/client/src/app/shared/misc/list-overflow.component.ts b/client/src/app/shared/misc/list-overflow.component.ts new file mode 100644 index 000000000..4f92c0f7c --- /dev/null +++ b/client/src/app/shared/misc/list-overflow.component.ts @@ -0,0 +1,114 @@ +import { + Component, + Input, + TemplateRef, + ViewChildren, + ViewChild, + QueryList, + ChangeDetectionStrategy, + ElementRef, + ChangeDetectorRef, + HostListener +} from '@angular/core' +import { NgbModal, NgbDropdown } from '@ng-bootstrap/ng-bootstrap' +import { uniqueId, lowerFirst } from 'lodash-es' +import { ScreenService } from './screen.service' +import { take } from 'rxjs/operators' + +export interface ListOverflowItem { + label: string + routerLink: string | any[] +} + +@Component({ + selector: 'list-overflow', + templateUrl: './list-overflow.component.html', + styleUrls: [ './list-overflow.component.scss' ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ListOverflowComponent { + @ViewChild('modal', { static: true }) modal: ElementRef + @ViewChild('itemsParent', { static: true }) parent: ElementRef + @ViewChildren('itemsRendered') itemsRendered: QueryList + @Input() items: T[] + @Input() itemTemplate: TemplateRef<{item: T}> + + showItemsUntilIndexExcluded: number + active = false + isInTouchScreen = false + isInMobileView = false + + private openedOnHover = false + + constructor ( + private cdr: ChangeDetectorRef, + private modalService: NgbModal, + private screenService: ScreenService + ) {} + + isMenuDisplayed () { + return !!this.showItemsUntilIndexExcluded + } + + @HostListener('window:resize', ['$event']) + onWindowResize () { + this.isInTouchScreen = !!this.screenService.isInTouchScreen() + this.isInMobileView = !!this.screenService.isInMobileView() + + const parentWidth = this.parent.nativeElement.getBoundingClientRect().width + let showItemsUntilIndexExcluded: number + let accWidth = 0 + + for (const [index, el] of this.itemsRendered.toArray().entries()) { + accWidth += el.nativeElement.getBoundingClientRect().width + if (showItemsUntilIndexExcluded === undefined) { + showItemsUntilIndexExcluded = (parentWidth < accWidth) ? index : undefined + } + + const e = document.getElementById(this.getId(index)) + const shouldBeVisible = showItemsUntilIndexExcluded ? index < showItemsUntilIndexExcluded : true + e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden' + } + + this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded + this.cdr.markForCheck() + } + + 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 + } + + toggleModal () { + this.modalService.open(this.modal, { centered: true }) + } + + dismissOtherModals () { + this.modalService.dismissAll() + } + + getId (id: number | string = uniqueId()): string { + return lowerFirst(this.constructor.name) + '_' + id + } +} diff --git a/client/src/app/shared/moderation/user-ban-modal.component.ts b/client/src/app/shared/moderation/user-ban-modal.component.ts index cf0e1577a..1647e3691 100644 --- a/client/src/app/shared/moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/moderation/user-ban-modal.component.ts @@ -39,7 +39,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit { openModal (user: User | User[]) { this.usersToBan = user - this.openedModal = this.modalService.open(this.modal) + this.openedModal = this.modalService.open(this.modal, { centered: true }) } hide () { diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 759de7020..98211c727 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -5,6 +5,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { RouterModule } from '@angular/router' import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' import { HelpComponent } from '@app/shared/misc/help.component' +import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component' import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' import { SharedModule as PrimeSharedModule } from 'primeng/api' @@ -156,6 +157,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard' InfiniteScrollerDirective, TextareaAutoResizeDirective, HelpComponent, + ListOverflowComponent, ReactiveFileComponent, PeertubeCheckboxComponent, @@ -227,6 +229,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard' InfiniteScrollerDirective, TextareaAutoResizeDirective, HelpComponent, + ListOverflowComponent, InputReadonlyCopyComponent, ReactiveFileComponent, diff --git a/client/src/app/shared/video/modals/video-blacklist.component.ts b/client/src/app/shared/video/modals/video-blacklist.component.ts index bdd9c7b99..6ef9c250b 100644 --- a/client/src/app/shared/video/modals/video-blacklist.component.ts +++ b/client/src/app/shared/video/modals/video-blacklist.component.ts @@ -46,7 +46,7 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { } show () { - this.openedModal = this.modalService.open(this.modal, { keyboard: false }) + this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) } hide () { diff --git a/client/src/app/shared/video/modals/video-download.component.ts b/client/src/app/shared/video/modals/video-download.component.ts index c1ceca263..6909c4279 100644 --- a/client/src/app/shared/video/modals/video-download.component.ts +++ b/client/src/app/shared/video/modals/video-download.component.ts @@ -48,7 +48,7 @@ export class VideoDownloadComponent { this.video = video this.videoCaptions = videoCaptions && videoCaptions.length ? videoCaptions : undefined - this.activeModal = this.modalService.open(this.modal) + this.activeModal = this.modalService.open(this.modal, { centered: true }) this.resolutionId = this.getVideoFiles()[0].resolution.id if (this.videoCaptions) this.subtitleLanguageId = this.videoCaptions[0].language.id diff --git a/client/src/app/shared/video/modals/video-report.component.ts b/client/src/app/shared/video/modals/video-report.component.ts index ee991fade..988fa03d4 100644 --- a/client/src/app/shared/video/modals/video-report.component.ts +++ b/client/src/app/shared/video/modals/video-report.component.ts @@ -53,7 +53,7 @@ export class VideoReportComponent extends FormReactive implements OnInit { } show () { - this.openedModal = this.modalService.open(this.modal, { keyboard: false }) + this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) } hide () { diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts index 1a9bf5171..9856aac9e 100644 --- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts @@ -56,7 +56,7 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni show () { this.closingModal = false - this.openedModal = this.modalService.open(this.modal, { keyboard: false }) + this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) } hide () { diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.ts b/client/src/app/videos/+video-watch/modal/video-share.component.ts index 6bc9b09fa..5109bcd11 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-share.component.ts @@ -72,7 +72,7 @@ export class VideoShareComponent { controls: true } - this.modalService.open(this.modal) + this.modalService.open(this.modal, { centered: true }) } getVideoIframeCode () { diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.ts b/client/src/app/videos/+video-watch/modal/video-support.component.ts index b56a51fbf..0058172f2 100644 --- a/client/src/app/videos/+video-watch/modal/video-support.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-support.component.ts @@ -21,7 +21,7 @@ export class VideoSupportComponent { ) { } show () { - this.modalService.open(this.modal) + this.modalService.open(this.modal, { centered: true }) this.markdownService.enhancedMarkdownToHTML(this.video.support) .then(r => this.videoHTMLSupport = r) diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 995cc6025..e4840dd81 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -252,7 +252,7 @@ table { padding-left: 50px; .title-page { - font-size: 15px; + font-size: 17px; } } } diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 2aca8c380..035270e89 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss @@ -30,8 +30,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; .dropdown-item { padding: 3px 15px; - &:active { - color: #000 !important; + &.active { + color: var(--mainBackgroundColor) !important; + background-color: var(--mainHoverColor); + opacity: .9; } } @@ -48,14 +50,12 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; @media screen and (min-width: 768px) { .modal:before { - display: inline-block; vertical-align: middle; content: " "; height: 100%; } .modal-dialog { - display: inline-block; text-align: left; vertical-align: middle; min-width: 500px; -- 2.25.1