diff options
Diffstat (limited to 'client/src/app/menu')
-rw-r--r-- | client/src/app/menu/avatar-notification.component.html | 2 | ||||
-rw-r--r-- | client/src/app/menu/avatar-notification.component.ts | 1 | ||||
-rw-r--r-- | client/src/app/menu/language-chooser.component.scss | 7 | ||||
-rw-r--r-- | client/src/app/menu/language-chooser.component.ts | 18 | ||||
-rw-r--r-- | client/src/app/menu/menu.component.html | 103 | ||||
-rw-r--r-- | client/src/app/menu/menu.component.scss | 207 | ||||
-rw-r--r-- | client/src/app/menu/menu.component.ts | 100 |
7 files changed, 343 insertions, 95 deletions
diff --git a/client/src/app/menu/avatar-notification.component.html b/client/src/app/menu/avatar-notification.component.html index 7975afba5..df2a102a3 100644 --- a/client/src/app/menu/avatar-notification.component.html +++ b/client/src/app/menu/avatar-notification.component.html | |||
@@ -30,7 +30,7 @@ | |||
30 | </div> | 30 | </div> |
31 | 31 | ||
32 | <my-user-notifications | 32 | <my-user-notifications |
33 | [ignoreLoadingBar]="true" [infiniteScroll]="false" itemsPerPage="10" | 33 | [ignoreLoadingBar]="true" [infiniteScroll]="false" [itemsPerPage]="10" |
34 | [markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()" | 34 | [markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()" |
35 | ></my-user-notifications> | 35 | ></my-user-notifications> |
36 | 36 | ||
diff --git a/client/src/app/menu/avatar-notification.component.ts b/client/src/app/menu/avatar-notification.component.ts index 989a11849..c447f031c 100644 --- a/client/src/app/menu/avatar-notification.component.ts +++ b/client/src/app/menu/avatar-notification.component.ts | |||
@@ -6,7 +6,6 @@ import { Notifier, UserNotificationSocket } from '@app/core' | |||
6 | import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' |
7 | import { NavigationEnd, Router } from '@angular/router' | 7 | import { NavigationEnd, Router } from '@angular/router' |
8 | import { filter } from 'rxjs/operators' | 8 | import { filter } from 'rxjs/operators' |
9 | import { UserNotificationsComponent } from '@app/shared' | ||
10 | 9 | ||
11 | @Component({ | 10 | @Component({ |
12 | selector: 'my-avatar-notification', | 11 | selector: 'my-avatar-notification', |
diff --git a/client/src/app/menu/language-chooser.component.scss b/client/src/app/menu/language-chooser.component.scss index 72deb3952..6226a85cb 100644 --- a/client/src/app/menu/language-chooser.component.scss +++ b/client/src/app/menu/language-chooser.component.scss | |||
@@ -4,6 +4,13 @@ | |||
4 | .help-to-translate { | 4 | .help-to-translate { |
5 | @include peertube-button-link; | 5 | @include peertube-button-link; |
6 | @include orange-button; | 6 | @include orange-button; |
7 | |||
8 | &.focus-visible, | ||
9 | &:focus { | ||
10 | box-shadow: none; | ||
11 | } | ||
12 | |||
13 | border-radius: 0; | ||
7 | } | 14 | } |
8 | 15 | ||
9 | .modal-body { | 16 | .modal-body { |
diff --git a/client/src/app/menu/language-chooser.component.ts b/client/src/app/menu/language-chooser.component.ts index 4a6e4c75a..9bc934ad4 100644 --- a/client/src/app/menu/language-chooser.component.ts +++ b/client/src/app/menu/language-chooser.component.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, ViewChild, Inject, LOCALE_ID } from '@angular/core' |
2 | import { I18N_LOCALES } from '../../../../shared' | 2 | import { I18N_LOCALES } from '../../../../shared' |
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { sortBy } from '@app/shared/misc/utils' | 4 | import { sortBy } from '@app/shared/misc/utils' |
5 | import { getCompleteLocale } from '@shared/models/i18n' | ||
6 | import { isOnDevLocale, getDevLocale } from '@app/shared/i18n/i18n-utils' | ||
5 | 7 | ||
6 | @Component({ | 8 | @Component({ |
7 | selector: 'my-language-chooser', | 9 | selector: 'my-language-chooser', |
@@ -13,7 +15,10 @@ export class LanguageChooserComponent { | |||
13 | 15 | ||
14 | languages: { id: string, label: string }[] = [] | 16 | languages: { id: string, label: string }[] = [] |
15 | 17 | ||
16 | constructor (private modalService: NgbModal) { | 18 | constructor ( |
19 | private modalService: NgbModal, | ||
20 | @Inject(LOCALE_ID) private localeId: string | ||
21 | ) { | ||
17 | const l = Object.keys(I18N_LOCALES) | 22 | const l = Object.keys(I18N_LOCALES) |
18 | .map(k => ({ id: k, label: I18N_LOCALES[k] })) | 23 | .map(k => ({ id: k, label: I18N_LOCALES[k] })) |
19 | 24 | ||
@@ -21,11 +26,18 @@ export class LanguageChooserComponent { | |||
21 | } | 26 | } |
22 | 27 | ||
23 | show () { | 28 | show () { |
24 | this.modalService.open(this.modal) | 29 | this.modalService.open(this.modal, { centered: true }) |
25 | } | 30 | } |
26 | 31 | ||
27 | buildLanguageLink (lang: { id: string }) { | 32 | buildLanguageLink (lang: { id: string }) { |
28 | return window.location.origin + '/' + lang.id | 33 | return window.location.origin + '/' + lang.id |
29 | } | 34 | } |
30 | 35 | ||
36 | getCurrentLanguage () { | ||
37 | const english = 'English' | ||
38 | const locale = isOnDevLocale() ? getDevLocale() : getCompleteLocale(this.localeId) | ||
39 | |||
40 | if (locale) return I18N_LOCALES[locale] || english | ||
41 | return english | ||
42 | } | ||
31 | } | 43 | } |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 675fb597d..1cb51ef55 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -8,34 +8,65 @@ | |||
8 | <a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a> | 8 | <a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a> |
9 | <a *ngIf="!user.account" routerLink="/my-account/settings" class="logged-in-display-name">{{ user.account?.displayName }}</a> | 9 | <a *ngIf="!user.account" routerLink="/my-account/settings" class="logged-in-display-name">{{ user.account?.displayName }}</a> |
10 | 10 | ||
11 | <div ngxClipboard [cbContent]="user.account?.nameWithHost" class="logged-in-username">{{ user.username }}</div> | 11 | <div class="logged-in-username">{{ user.username }}</div> |
12 | </div> | 12 | </div> |
13 | 13 | ||
14 | <div class="logged-in-more" ngbDropdown placement="bottom-right auto"> | 14 | <div class="logged-in-more" ngbDropdown [placement]="placement" container="body" autoClose="outside"> |
15 | <my-global-icon iconName="more-vertical" ngbDropdownToggle role="button"></my-global-icon> | 15 | <my-global-icon iconName="more-vertical" ngbDropdownToggle role="button"></my-global-icon> |
16 | 16 | ||
17 | <div ngbDropdownMenu> | 17 | <div ngbDropdownMenu> |
18 | <a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="dropdown-item"> | 18 | <a *ngIf="user.account" ngbDropdownItem ngbDropdownToggle class="dropdown-item" [routerLink]="[ '/accounts', user.account.nameWithHost ]"> |
19 | <my-global-icon iconName="go"></my-global-icon> <ng-container i18n>Public profile</ng-container> | 19 | <my-global-icon iconName="go"></my-global-icon> <ng-container i18n>Public profile</ng-container> |
20 | </a> | 20 | </a> |
21 | 21 | ||
22 | <div class="dropdown-divider"></div> | 22 | <div class="dropdown-divider"></div> |
23 | 23 | ||
24 | <a routerLink="/my-account" class="dropdown-item"> | 24 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account"> |
25 | <my-global-icon iconName="user"></my-global-icon> <ng-container i18n>Account settings</ng-container> | 25 | <my-global-icon iconName="user"></my-global-icon> <ng-container i18n>Account settings</ng-container> |
26 | </a> | 26 | </a> |
27 | 27 | ||
28 | <a routerLink="/my-account/video-channels" class="dropdown-item"> | 28 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/video-channels"> |
29 | <my-global-icon iconName="folder"></my-global-icon> <ng-container i18n>Channels settings</ng-container> | 29 | <my-global-icon iconName="folder"></my-global-icon> <ng-container i18n>Channels settings</ng-container> |
30 | </a> | 30 | </a> |
31 | 31 | ||
32 | <div class="dropdown-divider"></div> | 32 | <div class="dropdown-divider"></div> |
33 | 33 | ||
34 | <a class="dropdown-item" href="https://joinpeertube.org/help" target="_blank" rel="noopener noreferrer"> | 34 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()"> |
35 | <my-global-icon iconName="help"></my-global-icon> <ng-container i18n>Help</ng-container> | 35 | <my-global-icon iconName="language"></my-global-icon> |
36 | <ng-container i18n>Interface: {{ language }}</ng-container> | ||
37 | <i class="ml-auto glyphicon glyphicon-menu-right"></i> | ||
36 | </a> | 38 | </a> |
37 | 39 | ||
38 | <a (click)="logout($event)" class="dropdown-item" href="#"> | 40 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="video-settings"> |
41 | <my-global-icon iconName="video-lang"></my-global-icon> | ||
42 | <ng-container i18n>Videos: {{ videoLanguages.join(', ') }}</ng-container> | ||
43 | <i class="ml-auto glyphicon glyphicon-menu-right"></i> | ||
44 | </a> | ||
45 | |||
46 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="video-settings"> | ||
47 | <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy === 'display' }" iconName="sensitive"></my-global-icon> | ||
48 | <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy !== 'display' }" iconName="unsensitive"></my-global-icon> | ||
49 | <ng-container i18n>Sensitive: {{ nsfwPolicy }}</ng-container> | ||
50 | <i class="ml-auto glyphicon glyphicon-menu-right"></i> | ||
51 | </a> | ||
52 | |||
53 | <a ngbDropdownItem class="dropdown-item" (click)="toggleUseP2P()"> | ||
54 | <my-global-icon iconName="p2p"></my-global-icon> | ||
55 | <ng-container i18n>Help share videos</ng-container> | ||
56 | <input type="checkbox" [checked]="user.webTorrentEnabled"/><label class="ml-auto" for="switch">Toggle p2p</label> | ||
57 | </a> | ||
58 | |||
59 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account"> | ||
60 | <my-global-icon iconName="more-horizontal"></my-global-icon> <ng-container i18n>More account settings</ng-container> | ||
61 | </a> | ||
62 | |||
63 | <div class="dropdown-divider"></div> | ||
64 | |||
65 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openHotkeysCheatSheet()"> | ||
66 | <i class="icon icon-shortcuts"></i> <ng-container i18n>Keyboard shortcuts</ng-container> | ||
67 | </a> | ||
68 | |||
69 | <a ngbDropdownItem ngbDropdownToggle (click)="logout($event)" class="dropdown-item" href="#"> | ||
39 | <my-global-icon iconName="sign-out"></my-global-icon> <ng-container i18n>Log out</ng-container> | 70 | <my-global-icon iconName="sign-out"></my-global-icon> <ng-container i18n>Log out</ng-container> |
40 | </a> | 71 | </a> |
41 | </div> | 72 | </div> |
@@ -48,7 +79,7 @@ | |||
48 | </div> | 79 | </div> |
49 | 80 | ||
50 | <div *ngIf="isLoggedIn" class="panel-block"> | 81 | <div *ngIf="isLoggedIn" class="panel-block"> |
51 | <div i18n class="block-title">My library</div> | 82 | <div i18n class="block-title">MY LIBRARY</div> |
52 | 83 | ||
53 | <a routerLink="/my-account/videos" routerLinkActive="active"> | 84 | <a routerLink="/my-account/videos" routerLinkActive="active"> |
54 | <my-global-icon iconName="videos"></my-global-icon> | 85 | <my-global-icon iconName="videos"></my-global-icon> |
@@ -73,7 +104,7 @@ | |||
73 | </div> | 104 | </div> |
74 | 105 | ||
75 | <div class="panel-block"> | 106 | <div class="panel-block"> |
76 | <div i18n class="block-title">Videos</div> | 107 | <div i18n class="block-title">VIDEOS</div> |
77 | 108 | ||
78 | <a routerLink="/videos/overview" routerLinkActive="active"> | 109 | <a routerLink="/videos/overview" routerLinkActive="active"> |
79 | <my-global-icon iconName="globe"></my-global-icon> | 110 | <my-global-icon iconName="globe"></my-global-icon> |
@@ -100,32 +131,56 @@ | |||
100 | <ng-container i18n>Local</ng-container> | 131 | <ng-container i18n>Local</ng-container> |
101 | </a> | 132 | </a> |
102 | </div> | 133 | </div> |
134 | </div> | ||
103 | 135 | ||
136 | <div class="footer"> | ||
104 | <div class="panel-block"> | 137 | <div class="panel-block"> |
105 | <div class="block-title" i18n>More</div> | ||
106 | |||
107 | <a *ngIf="userHasAdminAccess" [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active"> | 138 | <a *ngIf="userHasAdminAccess" [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active"> |
108 | <my-global-icon iconName="administration"></my-global-icon> | 139 | <my-global-icon iconName="cog"></my-global-icon> |
109 | <ng-container i18n>Administration</ng-container> | 140 | <ng-container i18n>Administration</ng-container> |
110 | </a> | 141 | </a> |
111 | 142 | <a *ngIf="!isLoggedIn" (click)="openQuickSettings()"> | |
112 | <a routerLink="/about" routerLinkActive="active"> | 143 | <my-global-icon iconName="cog"></my-global-icon> |
113 | <my-global-icon iconName="about"></my-global-icon> | 144 | <ng-container i18n>Settings</ng-container> |
145 | </a> | ||
146 | <a routerLink="/about/instance"> | ||
147 | <my-global-icon iconName="help"></my-global-icon> | ||
114 | <ng-container i18n>About</ng-container> | 148 | <ng-container i18n>About</ng-container> |
115 | </a> | 149 | </a> |
116 | </div> | 150 | </div> |
117 | </div> | ||
118 | 151 | ||
119 | <div class="footer d-flex justify-content-between"> | 152 | <div class="bottom-links"> |
120 | <span class="language"> | ||
121 | <span tabindex="0" role="button" (keyup.enter)="openLanguageChooser()" (click)="openLanguageChooser()" i18n-title title="Change the language" class="icon icon-language"></span> | ||
122 | </span> | ||
123 | 153 | ||
124 | <span class="shortcuts"> | 154 | <div class="footer-links"> |
125 | <span tabindex="0" role="button" (keyup.enter)="openHotkeysCheatSheet()" (click)="openHotkeysCheatSheet()" i18n-title title="Show keyboard shortcuts" class="icon icon-shortcuts"></span> | 155 | <div *ngIf="isLoggedIn === false"> |
126 | </span> | 156 | <span role="button" (click)="openLanguageChooser()" class="c-hand" i18n>Interface: {{ language }}</span> |
157 | </div> | ||
158 | |||
159 | <div> | ||
160 | <a i18n routerLink="/about/instance">Contact</a> | ||
161 | <a i18n href="https://joinpeertube.org/help" i18n-title title="Get help using PeerTube" target="_blank" rel="noopener noreferrer">Help</a> | ||
162 | <a i18n href="https://joinpeertube.org/faq" i18n-title title="Frequently asked questions about PeerTube" target="_blank" rel="noopener noreferrer">FAQ</a> | ||
163 | <a i18n routerLink="/about/instance" fragment="statistics">Stats</a> | ||
164 | <a i18n href="https://docs.joinpeertube.org/api-rest-reference.html" i18n-title title="API documentation" target="_blank" rel="noopener noreferrer">API</a> | ||
165 | <a (click)="openHotkeysCheatSheet()" class="c-hand" i18n>Shortcuts</a> | ||
166 | </div> | ||
167 | </div> | ||
168 | |||
169 | <div class="footer-copyleft"> | ||
170 | <small class="d-inline" i18n-title title="powered by PeerTube - CopyLeft 2015-2020"> | ||
171 | <a href="https://joinpeertube.org" i18n-title title="PeerTube website" target="_blank" rel="noopener noreferrer" i18n> | ||
172 | powered by PeerTube | ||
173 | </a> | ||
174 | |||
175 | <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" i18n-title title="PeerTube license" target="_blank" rel="noopener noreferrer"> | ||
176 | <span aria-label="copyleft" class="d-inline-block" style="transform: rotateY(180deg)">©</span> 2015-2020 | ||
177 | </a> | ||
178 | </small> | ||
179 | </div> | ||
180 | </div> | ||
127 | </div> | 181 | </div> |
128 | </menu> | 182 | </menu> |
129 | </div> | 183 | </div> |
130 | 184 | ||
131 | <my-language-chooser #languageChooserModal></my-language-chooser> | 185 | <my-language-chooser #languageChooserModal></my-language-chooser> |
186 | <my-quick-settings #quickSettingsModal></my-quick-settings> | ||
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index b05173751..5bff0c328 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss | |||
@@ -6,7 +6,8 @@ | |||
6 | height: calc(100vh - #{$header-height}); | 6 | height: calc(100vh - #{$header-height}); |
7 | padding: 0; | 7 | padding: 0; |
8 | width: $menu-width; | 8 | width: $menu-width; |
9 | z-index: 10000; | 9 | z-index: z(menu); |
10 | scrollbar-color: var(--actionButtonColor) var(--menuBackgroundColor); | ||
10 | } | 11 | } |
11 | 12 | ||
12 | menu { | 13 | menu { |
@@ -26,9 +27,13 @@ menu { | |||
26 | overflow-y: auto; | 27 | overflow-y: auto; |
27 | } | 28 | } |
28 | 29 | ||
30 | @media not all and (hover: hover) and (pointer: fine) { | ||
31 | overflow-y: auto; | ||
32 | } | ||
33 | |||
29 | &.logged-in { | 34 | &.logged-in { |
30 | .panel-block { | 35 | .panel-block { |
31 | margin-bottom: 25px; | 36 | margin-bottom: 20px; |
32 | } | 37 | } |
33 | 38 | ||
34 | .block-title { | 39 | .block-title { |
@@ -87,22 +92,6 @@ menu { | |||
87 | @include apply-svg-color(var(--menuForegroundColor)); | 92 | @include apply-svg-color(var(--menuForegroundColor)); |
88 | } | 93 | } |
89 | } | 94 | } |
90 | |||
91 | .dropdown-item { | ||
92 | @include dropdown-with-icon-item; | ||
93 | |||
94 | my-global-icon { | ||
95 | width: 22px; | ||
96 | height: 22px; | ||
97 | |||
98 | &[iconName="sign-out"] { | ||
99 | position: relative; | ||
100 | right: -1px; | ||
101 | height: 21px; | ||
102 | width: 21px; | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | } | 95 | } |
107 | } | 96 | } |
108 | 97 | ||
@@ -142,7 +131,7 @@ menu { | |||
142 | } | 131 | } |
143 | 132 | ||
144 | .panel-block { | 133 | .panel-block { |
145 | margin-bottom: 45px; | 134 | margin-bottom: 15px; |
146 | 135 | ||
147 | a { | 136 | a { |
148 | @include disable-default-a-behaviour; | 137 | @include disable-default-a-behaviour; |
@@ -197,58 +186,160 @@ menu { | |||
197 | } | 186 | } |
198 | 187 | ||
199 | .footer { | 188 | .footer { |
200 | padding-bottom: 15px; | ||
201 | padding-left: $menu-lateral-padding; | ||
202 | padding-right: $menu-lateral-padding; | ||
203 | width: $menu-width; | 189 | width: $menu-width; |
190 | padding-bottom: 15px; | ||
204 | 191 | ||
205 | .language, .shortcuts, .color-palette { | 192 | .bottom-links { |
206 | display: inline-block; | 193 | display: flex; |
207 | color: $menu-bottom-color; | 194 | flex-direction: column; |
208 | cursor: pointer; | 195 | padding: 0 $menu-lateral-padding; |
209 | font-size: 12px; | 196 | } |
210 | font-weight: $font-semibold; | ||
211 | 197 | ||
212 | .icon { | 198 | $footer-links-base-opacity: .8; |
213 | @include disable-outline; | ||
214 | @include icon(28px); | ||
215 | opacity: 0.9; | ||
216 | 199 | ||
217 | &.icon-language { | 200 | .footer-links { |
218 | position: relative; | 201 | &, > div { |
219 | top: -1px; | 202 | display: flex; |
220 | width: 28px; | 203 | flex-wrap: wrap; |
221 | height: 24px; | 204 | } |
222 | 205 | ||
223 | background-image: url('../../assets/images/menu/language.png'); | 206 | a, span[role=button] { |
207 | display: inline-block; | ||
208 | text-decoration: none; | ||
209 | color: var(--mainBackgroundColor); | ||
210 | opacity: $footer-links-base-opacity; | ||
211 | white-space: nowrap; | ||
212 | font-size: 90%; | ||
213 | font-weight: 500; | ||
214 | line-height: 1.4rem; | ||
215 | margin-right: 8px; | ||
216 | |||
217 | &.inline-global-icon { | ||
218 | display: inline-flex; | ||
219 | align-items: center; | ||
220 | white-space: nowrap; | ||
221 | height: 1.4rem; | ||
222 | |||
223 | my-global-icon { | ||
224 | @include apply-svg-color(var(--mainBackgroundColor)); | ||
225 | |||
226 | display: flex; | ||
227 | width: auto; | ||
228 | height: 90%; | ||
229 | margin-right: .2rem; | ||
230 | } | ||
224 | } | 231 | } |
232 | } | ||
233 | } | ||
225 | 234 | ||
226 | &.icon-shortcuts { | 235 | .footer-copyleft small a { |
227 | position: relative; | 236 | @include disable-default-a-behaviour; |
228 | top: -1px; | ||
229 | width: 24px; | ||
230 | height: 24px; | ||
231 | 237 | ||
232 | background-image: url('../../assets/images/menu/keyboard.png'); | 238 | color: var(--mainBackgroundColor); |
233 | filter: invert(100%); | 239 | opacity: $footer-links-base-opacity - .2; |
234 | } | 240 | } |
241 | } | ||
242 | } | ||
235 | 243 | ||
236 | &.icon-moonsun { | 244 | .dropdown-menu { |
237 | margin-left: 10px; | 245 | width: calc(100% + 40px); |
238 | position: relative; | 246 | } |
239 | top: -1px; | ||
240 | width: 24px; | ||
241 | height: 24px; | ||
242 | 247 | ||
243 | background-image: url('../../assets/images/menu/moonsun.svg'); | 248 | .dropdown-item { |
244 | } | 249 | @include dropdown-with-icon-item; |
245 | 250 | ||
246 | &:hover { | 251 | cursor: pointer; |
247 | opacity: 1; | 252 | display: flex; |
248 | } | 253 | align-items: center; |
249 | } | 254 | |
255 | i.glyphicon-menu-right { | ||
256 | opacity: .4; | ||
257 | } | ||
258 | |||
259 | my-global-icon { | ||
260 | &[iconName="cog"], | ||
261 | &[iconName="sign-out"] { | ||
262 | position: relative; | ||
263 | right: -2px; | ||
264 | height: 20px; | ||
265 | width: 20px; | ||
250 | } | 266 | } |
251 | } | 267 | } |
268 | |||
269 | my-global-icon.not-displayed { | ||
270 | display: none; | ||
271 | } | ||
272 | |||
273 | &:hover { | ||
274 | my-global-icon.hover-display-toggle.not-displayed { | ||
275 | display: inherit; | ||
276 | } | ||
277 | my-global-icon.hover-display-toggle { | ||
278 | display: none; | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | |||
283 | .more-settings { | ||
284 | text-transform: uppercase; | ||
285 | font-size: 80%; | ||
286 | color: #6c757d; | ||
287 | } | ||
288 | |||
289 | .icon { | ||
290 | @include disable-outline; | ||
291 | @include icon(22px); | ||
292 | opacity: 0.8; | ||
293 | |||
294 | &.icon-shortcuts { | ||
295 | position: relative; | ||
296 | top: -1px; | ||
297 | margin-right: 10px; | ||
298 | |||
299 | background-image: url('../../assets/images/menu/keyboard.png'); | ||
300 | } | ||
301 | } | ||
302 | |||
303 | input[type=checkbox]{ | ||
304 | position: absolute; | ||
305 | visibility: hidden; | ||
306 | } | ||
307 | |||
308 | label { | ||
309 | cursor: pointer; | ||
310 | text-indent: -9999px; | ||
311 | width: 35px; | ||
312 | height: 20px; | ||
313 | background: #cccccc; | ||
314 | display: block; | ||
315 | border-radius: 100px; | ||
316 | position: relative; | ||
317 | margin: 0; | ||
318 | |||
319 | &:after { | ||
320 | content: ''; | ||
321 | position: absolute; | ||
322 | top: 3px; | ||
323 | left: 3px; | ||
324 | width: 14px; | ||
325 | height: 14px; | ||
326 | background: var(--mainBackgroundColor); | ||
327 | border-radius: 50%; | ||
328 | transition: 0.3s ease-out; | ||
329 | } | ||
330 | |||
331 | &:active:after { | ||
332 | width: 40px; | ||
333 | } | ||
334 | } | ||
335 | |||
336 | input:checked + label { | ||
337 | background: var(--mainColor); | ||
338 | |||
339 | &:after { | ||
340 | left: calc(100% - 3px); | ||
341 | transform: translateX(-100%); | ||
342 | } | ||
252 | } | 343 | } |
253 | 344 | ||
254 | @media screen and (max-width: $mobile-view) { | 345 | @media screen and (max-width: $mobile-view) { |
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index 1d7651e78..015c14bce 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts | |||
@@ -1,10 +1,14 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { UserRight } from '../../../../shared/models/users/user-right.enum' | 2 | import { UserRight } from '../../../../shared/models/users/user-right.enum' |
3 | import { AuthService, AuthStatus, RedirectService, ServerService, ThemeService } from '../core' | 3 | import { AuthService, AuthStatus, RedirectService, ServerService } from '../core' |
4 | import { User } from '../shared/users/user.model' | 4 | import { User } from '@app/shared/users/user.model' |
5 | import { UserService } from '@app/shared/users/user.service' | ||
5 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | 6 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' |
6 | import { HotkeysService } from 'angular2-hotkeys' | 7 | import { HotkeysService } from 'angular2-hotkeys' |
7 | import { ServerConfig } from '@shared/models' | 8 | import { ServerConfig, VideoConstant } from '@shared/models' |
9 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
11 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
8 | 12 | ||
9 | @Component({ | 13 | @Component({ |
10 | selector: 'my-menu', | 14 | selector: 'my-menu', |
@@ -13,12 +17,17 @@ import { ServerConfig } from '@shared/models' | |||
13 | }) | 17 | }) |
14 | export class MenuComponent implements OnInit { | 18 | export class MenuComponent implements OnInit { |
15 | @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent | 19 | @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent |
20 | @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent | ||
16 | 21 | ||
17 | user: User | 22 | user: User |
18 | isLoggedIn: boolean | 23 | isLoggedIn: boolean |
24 | |||
19 | userHasAdminAccess = false | 25 | userHasAdminAccess = false |
20 | helpVisible = false | 26 | helpVisible = false |
21 | 27 | ||
28 | videoLanguages: string[] = [] | ||
29 | |||
30 | private languages: VideoConstant<string>[] = [] | ||
22 | private serverConfig: ServerConfig | 31 | private serverConfig: ServerConfig |
23 | private routesPerRight: { [ role in UserRight ]?: string } = { | 32 | private routesPerRight: { [ role in UserRight ]?: string } = { |
24 | [UserRight.MANAGE_USERS]: '/admin/users', | 33 | [UserRight.MANAGE_USERS]: '/admin/users', |
@@ -31,10 +40,25 @@ export class MenuComponent implements OnInit { | |||
31 | 40 | ||
32 | constructor ( | 41 | constructor ( |
33 | private authService: AuthService, | 42 | private authService: AuthService, |
43 | private userService: UserService, | ||
34 | private serverService: ServerService, | 44 | private serverService: ServerService, |
35 | private redirectService: RedirectService, | 45 | private redirectService: RedirectService, |
36 | private hotkeysService: HotkeysService | 46 | private hotkeysService: HotkeysService, |
37 | ) {} | 47 | private screenService: ScreenService, |
48 | private i18n: I18n | ||
49 | ) { } | ||
50 | |||
51 | get isInMobileView () { | ||
52 | return this.screenService.isInMobileView() | ||
53 | } | ||
54 | |||
55 | get placement () { | ||
56 | if (this.isInMobileView) { | ||
57 | return 'left-top auto' | ||
58 | } else { | ||
59 | return 'right-top auto' | ||
60 | } | ||
61 | } | ||
38 | 62 | ||
39 | ngOnInit () { | 63 | ngOnInit () { |
40 | this.serverConfig = this.serverService.getTmpConfig() | 64 | this.serverConfig = this.serverService.getTmpConfig() |
@@ -63,9 +87,35 @@ export class MenuComponent implements OnInit { | |||
63 | } | 87 | } |
64 | ) | 88 | ) |
65 | 89 | ||
66 | this.hotkeysService.cheatSheetToggle.subscribe(isOpen => { | 90 | this.hotkeysService.cheatSheetToggle |
67 | this.helpVisible = isOpen | 91 | .subscribe(isOpen => this.helpVisible = isOpen) |
68 | }) | 92 | |
93 | this.serverService.getVideoLanguages() | ||
94 | .subscribe(languages => { | ||
95 | this.languages = languages | ||
96 | |||
97 | this.authService.userInformationLoaded | ||
98 | .subscribe(() => this.buildUserLanguages()) | ||
99 | }) | ||
100 | } | ||
101 | |||
102 | get language () { | ||
103 | return this.languageChooserModal.getCurrentLanguage() | ||
104 | } | ||
105 | |||
106 | get nsfwPolicy () { | ||
107 | if (!this.user) return | ||
108 | |||
109 | switch (this.user.nsfwPolicy) { | ||
110 | case 'do_not_list': | ||
111 | return this.i18n('hide') | ||
112 | |||
113 | case 'blur': | ||
114 | return this.i18n('blur') | ||
115 | |||
116 | case 'display': | ||
117 | return this.i18n('display') | ||
118 | } | ||
69 | } | 119 | } |
70 | 120 | ||
71 | isRegistrationAllowed () { | 121 | isRegistrationAllowed () { |
@@ -117,6 +167,40 @@ export class MenuComponent implements OnInit { | |||
117 | this.hotkeysService.cheatSheetToggle.next(!this.helpVisible) | 167 | this.hotkeysService.cheatSheetToggle.next(!this.helpVisible) |
118 | } | 168 | } |
119 | 169 | ||
170 | openQuickSettings () { | ||
171 | this.quickSettingsModal.show() | ||
172 | } | ||
173 | |||
174 | toggleUseP2P () { | ||
175 | if (!this.user) return | ||
176 | this.user.webTorrentEnabled = !this.user.webTorrentEnabled | ||
177 | |||
178 | this.userService.updateMyProfile({ webTorrentEnabled: this.user.webTorrentEnabled }) | ||
179 | .subscribe(() => this.authService.refreshUserInformation()) | ||
180 | } | ||
181 | |||
182 | langForLocale (localeId: string) { | ||
183 | if (localeId === '_unknown') return this.i18n('Unknown') | ||
184 | |||
185 | return this.languages.find(lang => lang.id === localeId).label | ||
186 | } | ||
187 | |||
188 | private buildUserLanguages () { | ||
189 | if (!this.user) { | ||
190 | this.videoLanguages = [] | ||
191 | return | ||
192 | } | ||
193 | |||
194 | if (!this.user.videoLanguages) { | ||
195 | this.videoLanguages = [ this.i18n('any language') ] | ||
196 | return | ||
197 | } | ||
198 | |||
199 | this.videoLanguages = this.user.videoLanguages | ||
200 | .map(locale => this.langForLocale(locale)) | ||
201 | .map(value => value === undefined ? '?' : value) | ||
202 | } | ||
203 | |||
120 | private computeIsUserHasAdminAccess () { | 204 | private computeIsUserHasAdminAccess () { |
121 | const right = this.getFirstAdminRightAvailable() | 205 | const right = this.getFirstAdminRightAvailable() |
122 | 206 | ||