aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/menu
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/menu')
-rw-r--r--client/src/app/menu/language-chooser.component.scss2
-rw-r--r--client/src/app/menu/language-chooser.component.ts15
-rw-r--r--client/src/app/menu/menu.component.html99
-rw-r--r--client/src/app/menu/menu.component.scss200
-rw-r--r--client/src/app/menu/menu.component.ts62
5 files changed, 290 insertions, 88 deletions
diff --git a/client/src/app/menu/language-chooser.component.scss b/client/src/app/menu/language-chooser.component.scss
index 72deb3952..50d19fd1f 100644
--- a/client/src/app/menu/language-chooser.component.scss
+++ b/client/src/app/menu/language-chooser.component.scss
@@ -4,6 +4,8 @@
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 border-radius: 0;
7} 9}
8 10
9.modal-body { 11.modal-body {
diff --git a/client/src/app/menu/language-chooser.component.ts b/client/src/app/menu/language-chooser.component.ts
index 43f622dfb..fb74cdf19 100644
--- a/client/src/app/menu/language-chooser.component.ts
+++ b/client/src/app/menu/language-chooser.component.ts
@@ -1,7 +1,9 @@
1import { Component, ElementRef, ViewChild } from '@angular/core' 1import { Component, ElementRef, ViewChild, Inject, LOCALE_ID } from '@angular/core'
2import { I18N_LOCALES } from '../../../../shared' 2import { I18N_LOCALES } from '../../../../shared'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { sortBy } from '@app/shared/misc/utils' 4import { sortBy } from '@app/shared/misc/utils'
5import { getCompleteLocale } from '@shared/models/i18n'
6import { 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
@@ -28,4 +33,10 @@ export class LanguageChooserComponent {
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 if (locale) return I18N_LOCALES[locale] || english
40 return english
41 }
31} 42}
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index 790a8af00..399350616 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -11,31 +11,62 @@
11 <div 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" container="body"> 14 <div class="logged-in-more" ngbDropdown placement="right-top auto" 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">
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">
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>
@@ -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
119 <div class="footer d-flex justify-content-between">
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 151
124 <span class="shortcuts"> 152 <div class="d-flex flex-column">
125 <span tabindex="0" role="button" (keyup.enter)="openHotkeysCheatSheet()" (click)="openHotkeysCheatSheet()" i18n-title title="Show keyboard shortcuts" class="icon icon-shortcuts"></span> 153 <div class="footer-links">
126 </span> 154 <a i18n routerLink="/about/instance">Contact</a>
155 <a i18n routerLink="/about/instance">Terms of Service</a>
156 <a i18n routerLink="/about/instance" fragment="statistics">Stats</a>
157 <a (click)="openLanguageChooser()" class="c-hand">
158 <span i18n>Interface: {{ language }}</span>
159 </a>
160 </div>
161 <div class="footer-links">
162 <a i18n href="https://joinpeertube.org/#you-are-a-video-maker" i18n-title title="Creator guide" target="_blank" rel="noopener noreferrer">Creators</a>
163 <a i18n href="https://docs.joinpeertube.org/#/contribute-getting-started" i18n-title title="PeerTube license" target="_blank" rel="noopener noreferrer">Contributors</a>
164 <a i18n routerLink="/about/peertube" i18n-title title="More information about privacy within PeerTube">Privacy</a>
165 <a i18n href="https://joinpeertube.org/faq" i18n-title title="Frequently asked questions about PeerTube" target="_blank" rel="noopener noreferrer">FAQ</a>
166 <a i18n href="https://docs.joinpeertube.org/api-rest-reference.html" i18n-title title="API documentation" target="_blank" rel="noopener noreferrer">API</a>
167 <a i18n href="https://joinpeertube.org/help" i18n-title title="Get help using PeerTube" target="_blank" rel="noopener noreferrer">Help</a>
168 <a (click)="openHotkeysCheatSheet()" class="c-hand" i18n>Shortcuts</a>
169 </div>
170 <div class="footer-copyleft">
171 <small class="d-inline" i18n-title title="powered by PeerTube - CopyLeft 2015-2020">
172 <a href="https://joinpeertube.org" i18n-title title="PeerTube website" target="_blank" rel="noopener noreferrer" i18n>
173 powered by PeerTube
174 </a>
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)">&copy;</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 40c9a2b25..a4b1ec000 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -29,7 +29,7 @@ menu {
29 29
30 &.logged-in { 30 &.logged-in {
31 .panel-block { 31 .panel-block {
32 margin-bottom: 25px; 32 margin-bottom: 20px;
33 } 33 }
34 34
35 .block-title { 35 .block-title {
@@ -88,22 +88,6 @@ menu {
88 @include apply-svg-color(var(--menuForegroundColor)); 88 @include apply-svg-color(var(--menuForegroundColor));
89 } 89 }
90 } 90 }
91
92 .dropdown-item {
93 @include dropdown-with-icon-item;
94
95 my-global-icon {
96 width: 22px;
97 height: 22px;
98
99 &[iconName="sign-out"] {
100 position: relative;
101 right: -1px;
102 height: 21px;
103 width: 21px;
104 }
105 }
106 }
107 } 91 }
108 } 92 }
109 93
@@ -143,7 +127,7 @@ menu {
143 } 127 }
144 128
145 .panel-block { 129 .panel-block {
146 margin-bottom: 45px; 130 margin-bottom: 15px;
147 131
148 a { 132 a {
149 @include disable-default-a-behaviour; 133 @include disable-default-a-behaviour;
@@ -198,60 +182,162 @@ menu {
198 } 182 }
199 183
200 .footer { 184 .footer {
201 padding-bottom: 15px;
202 padding-left: $menu-lateral-padding;
203 padding-right: $menu-lateral-padding;
204 width: $menu-width; 185 width: $menu-width;
186 padding-bottom: 15px;
205 187
206 .language, .shortcuts, .color-palette { 188 & > div:not(.panel-block) {
207 display: inline-block; 189 padding-left: $menu-lateral-padding;
208 color: $menu-bottom-color; 190 padding-right: $menu-lateral-padding;
209 cursor: pointer; 191 row-gap: 1em;
210 font-size: 12px; 192 }
211 font-weight: $font-semibold;
212 193
213 .icon { 194 $footer-links-base-opacity: .8;
214 @include disable-outline;
215 @include icon(28px);
216 opacity: 0.9;
217 195
218 &.icon-language { 196 .footer-links {
219 position: relative; 197 display: inline-flex;
220 top: -1px; 198 flex-wrap: wrap;
221 width: 28px; 199
222 height: 24px; 200 & > a {
201 @include disable-default-a-behaviour;
223 202
224 background-image: url('../../assets/images/menu/language.png'); 203 display: inline-block;
204 text-decoration: none;
205 color: var(--mainBackgroundColor);
206 opacity: $footer-links-base-opacity;
207 white-space: nowrap;
208 font-size: 90%;
209 font-weight: 500;
210 line-height: 1.4rem;
211 margin-right: 8px;
212
213 &.inline-global-icon {
214 display: inline-flex;
215 align-items: center;
216 white-space: nowrap;
217 height: 1.4rem;
218
219 my-global-icon {
220 @include apply-svg-color(var(--mainBackgroundColor));
221
222 display: flex;
223 width: auto;
224 height: 90%;
225 margin-right: .2rem;
226 }
225 } 227 }
228 }
229 }
226 230
227 &.icon-shortcuts { 231 .footer-copyleft small a {
228 position: relative; 232 @include disable-default-a-behaviour;
229 top: -1px;
230 width: 24px;
231 height: 24px;
232 233
233 background-image: url('../../assets/images/menu/keyboard.png'); 234 color: var(--mainBackgroundColor);
234 filter: invert(100%); 235 opacity: $footer-links-base-opacity - .2;
235 } 236 }
237 }
238}
236 239
237 &.icon-moonsun { 240.dropdown-menu {
238 margin-left: 10px; 241 width: calc(100% + 40px);
239 position: relative; 242}
240 top: -1px;
241 width: 24px;
242 height: 24px;
243 243
244 background-image: url('../../assets/images/menu/moonsun.svg'); 244.dropdown-item {
245 } 245 @include dropdown-with-icon-item;
246 246
247 &:hover { 247 cursor: pointer;
248 opacity: 1; 248 display: flex;
249 } 249 align-items: center;
250 } 250
251 i.glyphicon-menu-right {
252 opacity: .4;
253 }
254
255 my-global-icon {
256 &[iconName="cog"],
257 &[iconName="sign-out"] {
258 position: relative;
259 right: -2px;
260 height: 20px;
261 width: 20px;
262 }
263 }
264
265 my-global-icon.not-displayed {
266 display: none;
267 }
268
269 &:hover {
270 my-global-icon.hover-display-toggle.not-displayed {
271 display: inherit;
272 }
273 my-global-icon.hover-display-toggle {
274 display: none;
251 } 275 }
252 } 276 }
253} 277}
254 278
279.more-settings {
280 text-transform: uppercase;
281 font-size: 80%;
282 color: #6c757d;
283}
284
285.icon {
286 @include disable-outline;
287 @include icon(22px);
288 opacity: 0.8;
289
290 &.icon-shortcuts {
291 position: relative;
292 top: -1px;
293 margin-right: 10px;
294
295 background-image: url('../../assets/images/menu/keyboard.png');
296 }
297}
298
299input[type=checkbox]{
300 position: absolute;
301 visibility: hidden;
302}
303
304label {
305 cursor: pointer;
306 text-indent: -9999px;
307 width: 35px;
308 height: 20px;
309 background: #cccccc;
310 display: block;
311 border-radius: 100px;
312 position: relative;
313 margin: 0;
314
315 &:after {
316 content: '';
317 position: absolute;
318 top: 3px;
319 left: 3px;
320 width: 14px;
321 height: 14px;
322 background: var(--mainBackgroundColor);
323 border-radius: 50%;
324 transition: 0.3s ease-out;
325 }
326
327 &:active:after {
328 width: 40px;
329 }
330}
331
332input:checked + label {
333 background: var(--mainColor);
334
335 &:after {
336 left: calc(100% - 3px);
337 transform: translateX(-100%);
338 }
339}
340
255@media screen and (max-width: $mobile-view) { 341@media screen and (max-width: $mobile-view) {
256 .menu-wrapper { 342 .menu-wrapper {
257 width: 100% !important; 343 width: 100% !important;
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
index 1d7651e78..5f3dfc52a 100644
--- a/client/src/app/menu/menu.component.ts
+++ b/client/src/app/menu/menu.component.ts
@@ -1,10 +1,13 @@
1import { Component, OnInit, ViewChild } from '@angular/core' 1import { Component, OnInit, ViewChild } from '@angular/core'
2import { UserRight } from '../../../../shared/models/users/user-right.enum' 2import { UserRight } from '../../../../shared/models/users/user-right.enum'
3import { AuthService, AuthStatus, RedirectService, ServerService, ThemeService } from '../core' 3import { AuthService, AuthStatus, RedirectService, ServerService } from '../core'
4import { User } from '../shared/users/user.model' 4import { User } from '@app/shared/users/user.model'
5import { UserService } from '@app/shared/users/user.service'
5import { LanguageChooserComponent } from '@app/menu/language-chooser.component' 6import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
6import { HotkeysService } from 'angular2-hotkeys' 7import { HotkeysService } from 'angular2-hotkeys'
7import { ServerConfig } from '@shared/models' 8import { ServerConfig, VideoConstant } from '@shared/models'
9import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component'
10import { I18n } from '@ngx-translate/i18n-polyfill'
8 11
9@Component({ 12@Component({
10 selector: 'my-menu', 13 selector: 'my-menu',
@@ -13,11 +16,14 @@ import { ServerConfig } from '@shared/models'
13}) 16})
14export class MenuComponent implements OnInit { 17export class MenuComponent implements OnInit {
15 @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent 18 @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent
19 @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent
16 20
17 user: User 21 user: User
18 isLoggedIn: boolean 22 isLoggedIn: boolean
23
19 userHasAdminAccess = false 24 userHasAdminAccess = false
20 helpVisible = false 25 helpVisible = false
26 languages: VideoConstant<string>[] = []
21 27
22 private serverConfig: ServerConfig 28 private serverConfig: ServerConfig
23 private routesPerRight: { [ role in UserRight ]?: string } = { 29 private routesPerRight: { [ role in UserRight ]?: string } = {
@@ -31,9 +37,11 @@ export class MenuComponent implements OnInit {
31 37
32 constructor ( 38 constructor (
33 private authService: AuthService, 39 private authService: AuthService,
40 private userService: UserService,
34 private serverService: ServerService, 41 private serverService: ServerService,
35 private redirectService: RedirectService, 42 private redirectService: RedirectService,
36 private hotkeysService: HotkeysService 43 private hotkeysService: HotkeysService,
44 private i18n: I18n
37 ) {} 45 ) {}
38 46
39 ngOnInit () { 47 ngOnInit () {
@@ -63,9 +71,33 @@ export class MenuComponent implements OnInit {
63 } 71 }
64 ) 72 )
65 73
66 this.hotkeysService.cheatSheetToggle.subscribe(isOpen => { 74 this.hotkeysService.cheatSheetToggle.subscribe(isOpen => this.helpVisible = isOpen)
67 this.helpVisible = isOpen 75
68 }) 76 this.serverService.getVideoLanguages().subscribe(languages => this.languages = languages)
77 }
78
79 get language () {
80 return this.languageChooserModal.getCurrentLanguage()
81 }
82
83 get videoLanguages (): string[] {
84 if (!this.user) return
85 if (!this.user.videoLanguages) return [this.i18n('any language')]
86 return this.user.videoLanguages
87 .map(locale => this.langForLocale(locale))
88 .map(value => value === undefined ? '?' : value)
89 }
90
91 get nsfwPolicy () {
92 if (!this.user) return
93 switch (this.user.nsfwPolicy) {
94 case 'do_not_list':
95 return this.i18n('hide')
96 case 'blur':
97 return this.i18n('blur')
98 case 'display':
99 return this.i18n('display')
100 }
69 } 101 }
70 102
71 isRegistrationAllowed () { 103 isRegistrationAllowed () {
@@ -117,6 +149,22 @@ export class MenuComponent implements OnInit {
117 this.hotkeysService.cheatSheetToggle.next(!this.helpVisible) 149 this.hotkeysService.cheatSheetToggle.next(!this.helpVisible)
118 } 150 }
119 151
152 openQuickSettings () {
153 this.quickSettingsModal.show()
154 }
155
156 toggleUseP2P () {
157 if (!this.user) return
158 this.user.webTorrentEnabled = !this.user.webTorrentEnabled
159 this.userService.updateMyProfile({
160 webTorrentEnabled: this.user.webTorrentEnabled
161 }).subscribe(() => this.authService.refreshUserInformation())
162 }
163
164 langForLocale(localeId: string) {
165 return this.languages.find(lang => lang.id = localeId).label
166 }
167
120 private computeIsUserHasAdminAccess () { 168 private computeIsUserHasAdminAccess () {
121 const right = this.getFirstAdminRightAvailable() 169 const right = this.getFirstAdminRightAvailable()
122 170