}
show () {
- this.openedModal = this.modalService.open(this.modal, { keyboard: false })
+ this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false })
}
hide () {
</div>
</div>
- <div class="links">
- <a i18n routerLink="video-channels" routerLinkActive="active" class="title-page">Video channels</a>
+ <div class="links w-100">
+ <ng-template #linkTemplate let-item="item">
+ <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
+ </ng-template>
- <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
-
- <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a>
+ <list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow>
</div>
</div>
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',
account: Account
accountUser: User
videoChannels: VideoChannel[] = []
+ links: ListOverflowItem[] = []
isAccountManageable = false
accountFollowerTitle = ''
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 () {
<div class="row">
<div class="sub-menu">
- <a i18n *ngIf="hasUsersRight()" routerLink="/admin/users" routerLinkActive="active" class="title-page">
- Users
- </a>
+ <ng-template #linkTemplate let-item="item">
+ <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
+ </ng-template>
- <a i18n *ngIf="hasServerFollowRight()" routerLink="/admin/follows" routerLinkActive="active" class="title-page">
- Follows & redundancies
- </a>
-
- <a i18n *ngIf="hasVideoAbusesRight() || hasVideoBlacklistRight()" routerLink="/admin/moderation" routerLinkActive="active" class="title-page">
- Moderation
- </a>
-
- <a i18n *ngIf="hasConfigRight()" routerLink="/admin/config" routerLinkActive="active" class="title-page">
- Configuration
- </a>
-
- <a i18n *ngIf="hasPluginsRight()" routerLink="/admin/plugins" routerLinkActive="active" class="title-page">
- Plugins/Themes
- </a>
-
- <a i18n *ngIf="hasJobsRight() || hasLogsRight() || hasDebugRight()" routerLink="/admin/system" routerLinkActive="active" class="title-page">
- System
- </a>
+ <list-overflow [items]="items" [itemTemplate]="linkTemplate"></list-overflow>
</div>
<div class="margin-content">
-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)
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
show (videoChangeOwnership: VideoChangeOwnership) {
this.videoChangeOwnership = videoChangeOwnership
this.modalService
- .open(this.modal)
+ .open(this.modal, { centered: true })
.result
.then(() => this.acceptOwnership())
.catch(() => this.videoChangeOwnership = undefined)
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
</div>
</div>
- <div class="links">
- <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
- <a i18n routerLink="video-playlists" routerLinkActive="active" class="title-page">Video playlists</a>
- <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a>
+ <div class="links w-100">
+ <ng-template #linkTemplate let-item="item">
+ <a [routerLink]="item.routerLink" routerLinkActive="active" class="title-page">{{ item.label }}</a>
+ </ng-template>
+
+ <list-overflow [items]="links" [itemTemplate]="linkTemplate"></list-overflow>
</div>
</div>
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',
videoChannel: VideoChannel
hotkeys: Hotkey[]
+ links: ListOverflowItem[] = []
isChannelManageable = false
private routeSub: Subscription
}, 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 () {
}
show () {
- this.modalService.open(this.modal)
+ this.modalService.open(this.modal, { centered: true })
}
buildLanguageLink (lang: { id: string }) {
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()
) { }
show () {
- this.modalService.open(this.modal,{
+ this.modalService.open(this.modal, {
+ centered: true,
backdrop: 'static',
keyboard: false,
size: 'lg'
--- /dev/null
+<div #itemsParent class="d-flex align-items-center text-nowrap w-100 list-overflow-parent">
+ <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id">
+ <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container>
+ </span>
+
+ <ng-container *ngIf="isMenuDisplayed()">
+ <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()">
+ <span class="glyphicon glyphicon-chevron-down"></span>
+ </button>
+
+ <div *ngIf="!isInMobileView" class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)">
+ <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ routeActive: active }"
+ ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button"
+ >
+ <span class="glyphicon glyphicon-chevron-down"></span>
+ </button>
+
+ <div ngbDropdownMenu>
+ <a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length"
+ [routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item">
+ {{ item.label }}
+ </a>
+ </div>
+ </div>
+ </ng-container>
+</div >
+
+<ng-template #modal let-close="close" let-dismiss="dismiss">
+ <div class="modal-body">
+ <a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length"
+ [routerLink]="item.routerLink" routerLinkActive="active" (click)="dismissOtherModals()">
+ {{ item.label }}
+ </a>
+ </div>
+</ng-template>
--- /dev/null
+@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;
+ }
+ }
+}
--- /dev/null
+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<T extends ListOverflowItem> {
+ @ViewChild('modal', { static: true }) modal: ElementRef
+ @ViewChild('itemsParent', { static: true }) parent: ElementRef<HTMLDivElement>
+ @ViewChildren('itemsRendered') itemsRendered: QueryList<ElementRef>
+ @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
+ }
+}
openModal (user: User | User[]) {
this.usersToBan = user
- this.openedModal = this.modalService.open(this.modal)
+ this.openedModal = this.modalService.open(this.modal, { centered: true })
}
hide () {
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'
InfiniteScrollerDirective,
TextareaAutoResizeDirective,
HelpComponent,
+ ListOverflowComponent,
ReactiveFileComponent,
PeertubeCheckboxComponent,
InfiniteScrollerDirective,
TextareaAutoResizeDirective,
HelpComponent,
+ ListOverflowComponent,
InputReadonlyCopyComponent,
ReactiveFileComponent,
}
show () {
- this.openedModal = this.modalService.open(this.modal, { keyboard: false })
+ this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false })
}
hide () {
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
}
show () {
- this.openedModal = this.modalService.open(this.modal, { keyboard: false })
+ this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false })
}
hide () {
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 () {
controls: true
}
- this.modalService.open(this.modal)
+ this.modalService.open(this.modal, { centered: true })
}
getVideoIframeCode () {
) { }
show () {
- this.modalService.open(this.modal)
+ this.modalService.open(this.modal, { centered: true })
this.markdownService.enhancedMarkdownToHTML(this.video.support)
.then(r => this.videoHTMLSupport = r)
padding-left: 50px;
.title-page {
- font-size: 15px;
+ font-size: 17px;
}
}
}
.dropdown-item {
padding: 3px 15px;
- &:active {
- color: #000 !important;
+ &.active {
+ color: var(--mainBackgroundColor) !important;
+ background-color: var(--mainHoverColor);
+ opacity: .9;
}
}
@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;