aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/shared-icons/global-icon.component.ts1
-rw-r--r--client/src/app/shared/shared-main/misc/index.ts1
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html50
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss56
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts131
-rw-r--r--client/src/app/shared/shared-main/shared-main.module.ts10
6 files changed, 243 insertions, 6 deletions
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 = {
34 'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default, 34 'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default,
35 'inbox-full': require('!!raw-loader?!../../../assets/images/feather/inbox-full.svg').default, 35 'inbox-full': require('!!raw-loader?!../../../assets/images/feather/inbox-full.svg').default,
36 'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default, 36 'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default,
37 'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default,
37 'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default, 38 'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default,
38 'ownership-change': require('!!raw-loader?!../../../assets/images/feather/share.svg').default, 39 'ownership-change': require('!!raw-loader?!../../../assets/images/feather/share.svg').default,
39 'share': require('!!raw-loader?!../../../assets/images/feather/share-2.svg').default, 40 '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 @@
1export * from './help.component' 1export * from './help.component'
2export * from './list-overflow.component' 2export * from './list-overflow.component'
3export * 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 @@
1<div class="sub-menu" [ngClass]="{ 'no-scroll': isModalOpened }">
2 <ng-container *ngFor="let menuEntry of menuEntries; index as id">
3
4 <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings">{{ menuEntry.label }}</a>
5
6 <div *ngIf="!menuEntry.routerLink" ngbDropdown class="parent-entry"
7 #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)">
8 <span
9 *ngIf="isInSmallView"
10 [ngClass]="{ active: !!suffixLabels[menuEntry.label] }"
11 (click)="openModal(id)" role="button" class="title-page title-page-settings">
12 <ng-container i18n>{{ menuEntry.label }}</ng-container>
13 <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
14 </span>
15
16 <span
17 *ngIf="!isInSmallView"
18 (mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor
19 (click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page title-page-settings"
20 >
21 <ng-container i18n>{{ menuEntry.label }}</ng-container>
22 <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
23 </span>
24
25 <div ngbDropdownMenu>
26 <a *ngFor="let menuChild of menuEntry.children" class="dropdown-item" [ngClass]="{ icon: hasIcons }" [routerLink]="menuChild.routerLink">
27 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
28
29 {{ menuChild.label }}
30 </a>
31 </div>
32 </div>
33 </ng-container>
34</div>
35
36<ng-template #modal let-close="close" let-dismiss="dismiss">
37 <div class="modal-body">
38 <ng-container *ngFor="let menuEntry of menuEntries; index as id">
39 <div [ngClass]="{ hidden: id !== currentMenuEntryIndex }">
40 <a *ngFor="let menuChild of menuEntry.children"
41 [ngClass]="{ icon: hasIcons }"
42 [routerLink]="menuChild.routerLink" routerLinkActive="active" (click)="dismissOtherModals()">
43 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
44
45 {{ menuChild.label }}
46 </a>
47 </div>
48 </ng-container>
49 </div>
50</ng-template>
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 @@
1@import '_variables';
2@import '_mixins';
3
4.parent-entry {
5 span[role=button] {
6 cursor: pointer;
7 }
8
9 a {
10 display: block;
11 }
12}
13
14::ng-deep .dropdown-toggle::after {
15 position: relative;
16 top: 2px;
17}
18
19::ng-deep .dropdown-menu {
20 margin-top: 0 !important;
21}
22
23.icon {
24 @include dropdown-with-icon-item;
25
26 top: -1px;
27}
28
29.sub-menu.no-scroll {
30 overflow-x: hidden;
31}
32
33.modal-body {
34 .hidden {
35 display: none;
36 }
37
38 a {
39 @include disable-default-a-behaviour;
40
41 color: currentColor;
42 box-sizing: border-box;
43 display: block;
44 font-size: 1.2rem;
45 padding: 9px 12px;
46 text-align: initial;
47 text-transform: unset;
48 width: 100%;
49
50 &.active {
51 color: pvar(--mainBackgroundColor) !important;
52 background-color: pvar(--mainHoverColor);
53 opacity: .9;
54 }
55 }
56}
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 @@
1import { Subscription } from 'rxjs'
2import { filter, take } from 'rxjs/operators'
3import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'
4import { NavigationEnd, Router } from '@angular/router'
5import { MenuService, ScreenService } from '@app/core'
6import { GlobalIconName } from '@app/shared/shared-icons'
7import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
8
9export type TopMenuDropdownParam = {
10 label: string
11 routerLink?: string
12
13 children?: {
14 label: string
15 routerLink: string
16
17 iconName?: GlobalIconName
18 }[]
19}
20
21@Component({
22 selector: 'my-top-menu-dropdown',
23 templateUrl: './top-menu-dropdown.component.html',
24 styleUrls: [ './top-menu-dropdown.component.scss' ]
25})
26export class TopMenuDropdownComponent implements OnInit, OnDestroy {
27 @Input() menuEntries: TopMenuDropdownParam[] = []
28
29 @ViewChild('modal', { static: true }) modal: NgbModal
30
31 suffixLabels: { [ parentLabel: string ]: string }
32 hasIcons = false
33 isModalOpened = false
34 currentMenuEntryIndex: number
35
36 private openedOnHover = false
37 private routeSub: Subscription
38
39 constructor (
40 private router: Router,
41 private modalService: NgbModal,
42 private screen: ScreenService,
43 private menuService: MenuService
44 ) { }
45
46 get isInSmallView () {
47 let marginLeft = 0
48 if (this.menuService.isMenuDisplayed) {
49 marginLeft = this.menuService.menuWidth
50 }
51
52 return this.screen.isInSmallView(marginLeft)
53 }
54
55 ngOnInit () {
56 this.updateChildLabels(window.location.pathname)
57
58 this.routeSub = this.router.events
59 .pipe(filter(event => event instanceof NavigationEnd))
60 .subscribe(() => this.updateChildLabels(window.location.pathname))
61
62 this.hasIcons = this.menuEntries.some(
63 e => e.children && e.children.some(c => !!c.iconName)
64 )
65 }
66
67 ngOnDestroy () {
68 if (this.routeSub) this.routeSub.unsubscribe()
69 }
70
71 openDropdownOnHover (dropdown: NgbDropdown) {
72 this.openedOnHover = true
73 dropdown.open()
74
75 // Menu was closed
76 dropdown.openChange
77 .pipe(take(1))
78 .subscribe(() => this.openedOnHover = false)
79 }
80
81 dropdownAnchorClicked (dropdown: NgbDropdown) {
82 if (this.openedOnHover) {
83 this.openedOnHover = false
84 return
85 }
86
87 return dropdown.toggle()
88 }
89
90 closeDropdownIfHovered (dropdown: NgbDropdown) {
91 if (this.openedOnHover === false) return
92
93 dropdown.close()
94 this.openedOnHover = false
95 }
96
97 openModal (index: number) {
98 this.currentMenuEntryIndex = index
99 this.isModalOpened = true
100
101 this.modalService.open(this.modal, {
102 centered: true,
103 beforeDismiss: async () => {
104 this.onModalDismiss()
105 return true
106 }
107 })
108 }
109
110 onModalDismiss () {
111 this.isModalOpened = false
112 }
113
114 dismissOtherModals () {
115 this.modalService.dismissAll()
116 }
117
118 private updateChildLabels (path: string) {
119 this.suffixLabels = {}
120
121 for (const entry of this.menuEntries) {
122 if (!entry.children) continue
123
124 for (const child of entry.children) {
125 if (path.startsWith(child.routerLink)) {
126 this.suffixLabels[entry.label] = child.label
127 }
128 }
129 }
130 }
131}
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
25import { DateToggleComponent } from './date' 25import { DateToggleComponent } from './date'
26import { FeedComponent } from './feeds' 26import { FeedComponent } from './feeds'
27import { LoaderComponent, SmallLoaderComponent } from './loaders' 27import { LoaderComponent, SmallLoaderComponent } from './loaders'
28import { HelpComponent, ListOverflowComponent } from './misc' 28import { HelpComponent, ListOverflowComponent, TopMenuDropdownComponent } from './misc'
29import { UserHistoryService, UserNotificationsComponent, UserNotificationService } from './users' 29import { UserHistoryService, UserNotificationsComponent, UserNotificationService } from './users'
30import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' 30import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
31import { VideoCaptionService } from './video-caption' 31import { VideoCaptionService } from './video-caption'
@@ -81,10 +81,9 @@ import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
81 81
82 HelpComponent, 82 HelpComponent,
83 ListOverflowComponent, 83 ListOverflowComponent,
84 TopMenuDropdownComponent,
84 85
85 UserNotificationsComponent, 86 UserNotificationsComponent,
86
87 FeedComponent
88 ], 87 ],
89 88
90 exports: [ 89 exports: [
@@ -131,10 +130,9 @@ import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
131 130
132 HelpComponent, 131 HelpComponent,
133 ListOverflowComponent, 132 ListOverflowComponent,
133 TopMenuDropdownComponent,
134 134
135 UserNotificationsComponent, 135 UserNotificationsComponent
136
137 FeedComponent
138 ], 136 ],
139 137
140 providers: [ 138 providers: [