aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.html2
-rw-r--r--client/src/app/+accounts/account-videos/account-videos.component.ts4
-rw-r--r--client/src/app/+my-account/my-account-history/my-account-history.component.ts2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts40
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html4
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts64
-rw-r--r--client/src/app/+my-account/my-account.component.html2
-rw-r--r--client/src/app/+my-account/my-account.module.ts4
-rw-r--r--client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts4
-rw-r--r--client/src/app/app.component.html5
-rw-r--r--client/src/app/app.module.ts2
-rw-r--r--client/src/app/core/auth/auth-user.model.ts15
-rw-r--r--client/src/app/core/theme/theme.service.ts31
-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
-rw-r--r--client/src/app/modal/quick-settings-modal.component.html19
-rw-r--r--client/src/app/modal/quick-settings-modal.component.scss39
-rw-r--r--client/src/app/modal/quick-settings-modal.component.ts62
-rw-r--r--client/src/app/shared/images/global-icon.component.ts7
-rw-r--r--client/src/app/shared/misc/help.component.scss1
-rw-r--r--client/src/app/shared/misc/storage.service.ts40
-rw-r--r--client/src/app/shared/shared.module.ts19
-rw-r--r--client/src/app/shared/users/user.model.ts29
-rw-r--r--client/src/app/shared/users/user.service.ts77
-rw-r--r--client/src/app/shared/video/abstract-video-list.ts23
-rw-r--r--client/src/app/shared/video/video.service.ts23
-rw-r--r--client/src/app/shared/video/videos-selection.component.ts4
-rw-r--r--client/src/app/videos/+video-watch/video-watch-playlist.component.ts7
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html15
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss10
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts19
-rw-r--r--client/src/app/videos/+video-watch/video-watch.module.ts4
-rw-r--r--client/src/app/videos/recommendations/recommended-videos.component.html2
-rw-r--r--client/src/app/videos/recommendations/recommended-videos.component.scss22
-rw-r--r--client/src/app/videos/recommendations/recommended-videos.component.ts20
-rw-r--r--client/src/app/videos/video-list/video-local.component.ts4
-rw-r--r--client/src/app/videos/video-list/video-most-liked.component.ts4
-rw-r--r--client/src/app/videos/video-list/video-recently-added.component.ts4
-rw-r--r--client/src/app/videos/video-list/video-trending.component.ts4
-rw-r--r--client/src/app/videos/video-list/video-user-subscriptions.component.ts4
-rw-r--r--client/src/assets/images/global/video-lang.svg15
-rw-r--r--client/src/assets/images/menu/about.svg11
-rw-r--r--client/src/assets/images/menu/administration.svg10
-rw-r--r--client/src/assets/images/menu/eye-closed.svg17
-rw-r--r--client/src/assets/images/menu/eye.svg15
-rw-r--r--client/src/assets/images/menu/language.pngbin10937 -> 0 bytes
-rw-r--r--client/src/assets/images/menu/language.svg10
-rw-r--r--client/src/assets/images/menu/moonsun.svg1
-rw-r--r--client/src/assets/images/menu/p2p.svg11
-rw-r--r--client/src/sass/application.scss1
-rw-r--r--client/src/sass/bootstrap.scss8
-rw-r--r--client/src/sass/include/_mixins.scss8
-rw-r--r--client/src/sass/include/_variables.scss7
-rw-r--r--client/src/sass/primeng-custom.scss27
58 files changed, 909 insertions, 253 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html
index b712d0044..043f63354 100644
--- a/client/src/app/+about/about-instance/about-instance.component.html
+++ b/client/src/app/+about/about-instance/about-instance.component.html
@@ -96,7 +96,7 @@
96 </div> 96 </div>
97 97
98 <div class="col"> 98 <div class="col">
99 <div i18n class="middle-title"> 99 <div id="statistics" i18n class="middle-title">
100 STATISTICS 100 STATISTICS
101 </div> 101 </div>
102 <my-instance-statistics></my-instance-statistics> 102 <my-instance-statistics></my-instance-statistics>
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts
index ac4477c18..41b27b541 100644
--- a/client/src/app/+accounts/account-videos/account-videos.component.ts
+++ b/client/src/app/+accounts/account-videos/account-videos.component.ts
@@ -12,6 +12,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
12import { Subscription } from 'rxjs' 12import { Subscription } from 'rxjs'
13import { ScreenService } from '@app/shared/misc/screen.service' 13import { ScreenService } from '@app/shared/misc/screen.service'
14import { Notifier, ServerService } from '@app/core' 14import { Notifier, ServerService } from '@app/core'
15import { UserService } from '@app/shared'
16import { LocalStorageService } from '@app/shared/misc/storage.service'
15 17
16@Component({ 18@Component({
17 selector: 'my-account-videos', 19 selector: 'my-account-videos',
@@ -34,9 +36,11 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
34 protected serverService: ServerService, 36 protected serverService: ServerService,
35 protected route: ActivatedRoute, 37 protected route: ActivatedRoute,
36 protected authService: AuthService, 38 protected authService: AuthService,
39 protected userService: UserService,
37 protected notifier: Notifier, 40 protected notifier: Notifier,
38 protected confirmService: ConfirmService, 41 protected confirmService: ConfirmService,
39 protected screenService: ScreenService, 42 protected screenService: ScreenService,
43 protected storageService: LocalStorageService,
40 private accountService: AccountService, 44 private accountService: AccountService,
41 private videoService: VideoService 45 private videoService: VideoService
42 ) { 46 ) {
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.ts b/client/src/app/+my-account/my-account-history/my-account-history.component.ts
index 13607119e..5f0ccee50 100644
--- a/client/src/app/+my-account/my-account-history/my-account-history.component.ts
+++ b/client/src/app/+my-account/my-account-history/my-account-history.component.ts
@@ -11,6 +11,7 @@ import { ScreenService } from '@app/shared/misc/screen.service'
11import { UserHistoryService } from '@app/shared/users/user-history.service' 11import { UserHistoryService } from '@app/shared/users/user-history.service'
12import { UserService } from '@app/shared' 12import { UserService } from '@app/shared'
13import { Notifier, ServerService } from '@app/core' 13import { Notifier, ServerService } from '@app/core'
14import { LocalStorageService } from '@app/shared/misc/storage.service'
14 15
15@Component({ 16@Component({
16 selector: 'my-account-history', 17 selector: 'my-account-history',
@@ -35,6 +36,7 @@ export class MyAccountHistoryComponent extends AbstractVideoList implements OnIn
35 protected userService: UserService, 36 protected userService: UserService,
36 protected notifier: Notifier, 37 protected notifier: Notifier,
37 protected screenService: ScreenService, 38 protected screenService: ScreenService,
39 protected storageService: LocalStorageService,
38 private confirmService: ConfirmService, 40 private confirmService: ConfirmService,
39 private videoService: VideoService, 41 private videoService: VideoService,
40 private userHistoryService: UserHistoryService 42 private userHistoryService: UserHistoryService
diff --git a/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html
index f034c6bb3..6f48d8f7d 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.html
@@ -12,5 +12,5 @@
12 </div> 12 </div>
13 </div> 13 </div>
14 14
15 <input type="submit" i18n-value value="Save" [disabled]="!form.valid"> 15 <input *ngIf="!reactiveUpdate" type="submit" class="mt-0" i18n-value value="Save" [disabled]="!form.valid">
16</form> 16</form>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts
index 441f89f10..b6c17c0e3 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts
@@ -1,21 +1,26 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit, OnDestroy } from '@angular/core'
2import { Notifier, ServerService } from '@app/core' 2import { Notifier, ServerService } from '@app/core'
3import { ServerConfig, UserUpdateMe } from '../../../../../../shared' 3import { ServerConfig, UserUpdateMe } from '../../../../../../shared'
4import { AuthService } from '../../../core' 4import { AuthService } from '../../../core'
5import { FormReactive, User, UserService } from '../../../shared' 5import { FormReactive } from '../../../shared/forms/form-reactive'
6import { User, UserService } from '../../../shared/users'
6import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
7import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 8import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
8import { Subject } from 'rxjs' 9import { Subject, Subscription } from 'rxjs'
9 10
10@Component({ 11@Component({
11 selector: 'my-account-interface-settings', 12 selector: 'my-account-interface-settings',
12 templateUrl: './my-account-interface-settings.component.html', 13 templateUrl: './my-account-interface-settings.component.html',
13 styleUrls: [ './my-account-interface-settings.component.scss' ] 14 styleUrls: [ './my-account-interface-settings.component.scss' ]
14}) 15})
15export class MyAccountInterfaceSettingsComponent extends FormReactive implements OnInit { 16export class MyAccountInterfaceSettingsComponent extends FormReactive implements OnInit, OnDestroy {
16 @Input() user: User = null 17 @Input() user: User = null
18 @Input() reactiveUpdate = false
19 @Input() notifyOnUpdate = true
17 @Input() userInformationLoaded: Subject<any> 20 @Input() userInformationLoaded: Subject<any>
18 21
22 formValuesWatcher: Subscription
23
19 private serverConfig: ServerConfig 24 private serverConfig: ServerConfig
20 25
21 constructor ( 26 constructor (
@@ -48,9 +53,17 @@ export class MyAccountInterfaceSettingsComponent extends FormReactive implements
48 this.form.patchValue({ 53 this.form.patchValue({
49 theme: this.user.theme 54 theme: this.user.theme
50 }) 55 })
56
57 if (this.reactiveUpdate) {
58 this.formValuesWatcher = this.form.valueChanges.subscribe(val => this.updateInterfaceSettings())
59 }
51 }) 60 })
52 } 61 }
53 62
63 ngOnDestroy () {
64 this.formValuesWatcher?.unsubscribe()
65 }
66
54 updateInterfaceSettings () { 67 updateInterfaceSettings () {
55 const theme = this.form.value['theme'] 68 const theme = this.form.value['theme']
56 69
@@ -58,14 +71,19 @@ export class MyAccountInterfaceSettingsComponent extends FormReactive implements
58 theme 71 theme
59 } 72 }
60 73
61 this.userService.updateMyProfile(details).subscribe( 74 if (this.authService.isLoggedIn()) {
62 () => { 75 this.userService.updateMyProfile(details).subscribe(
63 this.authService.refreshUserInformation() 76 () => {
77 this.authService.refreshUserInformation()
64 78
65 this.notifier.success(this.i18n('Interface settings updated.')) 79 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.'))
66 }, 80 },
67 81
68 err => this.notifier.error(err.message) 82 err => this.notifier.error(err.message)
69 ) 83 )
84 } else {
85 this.userService.updateMyAnonymousProfile(details)
86 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.'))
87 }
70 } 88 }
71} 89}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
index 51a672734..f17829127 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
@@ -35,6 +35,8 @@
35 </div> 35 </div>
36 </div> 36 </div>
37 37
38 <ng-content select="inner-title"></ng-content>
39
38 <div class="form-group"> 40 <div class="form-group">
39 <my-peertube-checkbox 41 <my-peertube-checkbox
40 inputName="webTorrentEnabled" formControlName="webTorrentEnabled" 42 inputName="webTorrentEnabled" formControlName="webTorrentEnabled"
@@ -56,5 +58,5 @@
56 ></my-peertube-checkbox> 58 ></my-peertube-checkbox>
57 </div> 59 </div>
58 60
59 <input type="submit" i18n-value value="Save" [disabled]="!form.valid"> 61 <input *ngIf="!reactiveUpdate" type="submit" i18n-value value="Save" [disabled]="!form.valid">
60</form> 62</form>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
index a66159b3f..0aaa54cd7 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
@@ -1,24 +1,31 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit, OnDestroy } from '@angular/core'
2import { Notifier, ServerService } from '@app/core' 2import { Notifier, ServerService } from '@app/core'
3import { UserUpdateMe } from '../../../../../../shared' 3import { UserUpdateMe } from '../../../../../../shared/models/users'
4import { User, UserService } from '@app/shared/users'
4import { AuthService } from '../../../core' 5import { AuthService } from '../../../core'
5import { FormReactive, User, UserService } from '../../../shared' 6import { FormReactive } from '@app/shared/forms/form-reactive'
6import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
7import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 8import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
8import { forkJoin, Subject } from 'rxjs' 9import { forkJoin, Subject, Subscription } from 'rxjs'
9import { SelectItem } from 'primeng/api' 10import { SelectItem } from 'primeng/api'
10import { first } from 'rxjs/operators' 11import { first } from 'rxjs/operators'
12import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
13import { pick } from 'lodash-es'
11 14
12@Component({ 15@Component({
13 selector: 'my-account-video-settings', 16 selector: 'my-account-video-settings',
14 templateUrl: './my-account-video-settings.component.html', 17 templateUrl: './my-account-video-settings.component.html',
15 styleUrls: [ './my-account-video-settings.component.scss' ] 18 styleUrls: [ './my-account-video-settings.component.scss' ]
16}) 19})
17export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit { 20export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit, OnDestroy {
18 @Input() user: User = null 21 @Input() user: User = null
22 @Input() reactiveUpdate = false
23 @Input() notifyOnUpdate = true
19 @Input() userInformationLoaded: Subject<any> 24 @Input() userInformationLoaded: Subject<any>
20 25
21 languageItems: SelectItem[] = [] 26 languageItems: SelectItem[] = []
27 defaultNSFWPolicy: NSFWPolicyType
28 formValuesWatcher: Subscription
22 29
23 constructor ( 30 constructor (
24 protected formValidatorService: FormValidatorService, 31 protected formValidatorService: FormValidatorService,
@@ -32,6 +39,8 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
32 } 39 }
33 40
34 ngOnInit () { 41 ngOnInit () {
42 let oldForm: any
43
35 this.buildForm({ 44 this.buildForm({
36 nsfwPolicy: null, 45 nsfwPolicy: null,
37 webTorrentEnabled: null, 46 webTorrentEnabled: null,
@@ -42,8 +51,9 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
42 51
43 forkJoin([ 52 forkJoin([
44 this.serverService.getVideoLanguages(), 53 this.serverService.getVideoLanguages(),
54 this.serverService.getConfig(),
45 this.userInformationLoaded.pipe(first()) 55 this.userInformationLoaded.pipe(first())
46 ]).subscribe(([ languages ]) => { 56 ]).subscribe(([ languages, config ]) => {
47 this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] 57 this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ]
48 this.languageItems = this.languageItems 58 this.languageItems = this.languageItems
49 .concat(languages.map(l => ({ label: l.label, value: l.id }))) 59 .concat(languages.map(l => ({ label: l.label, value: l.id })))
@@ -52,17 +62,32 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
52 ? this.user.videoLanguages 62 ? this.user.videoLanguages
53 : this.languageItems.map(l => l.value) 63 : this.languageItems.map(l => l.value)
54 64
65 this.defaultNSFWPolicy = config.instance.defaultNSFWPolicy
66
55 this.form.patchValue({ 67 this.form.patchValue({
56 nsfwPolicy: this.user.nsfwPolicy, 68 nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy,
57 webTorrentEnabled: this.user.webTorrentEnabled, 69 webTorrentEnabled: this.user.webTorrentEnabled,
58 autoPlayVideo: this.user.autoPlayVideo === true, 70 autoPlayVideo: this.user.autoPlayVideo === true,
59 autoPlayNextVideo: this.user.autoPlayNextVideo, 71 autoPlayNextVideo: this.user.autoPlayNextVideo,
60 videoLanguages 72 videoLanguages
61 }) 73 })
74
75 if (this.reactiveUpdate) {
76 oldForm = { ...this.form.value }
77 this.formValuesWatcher = this.form.valueChanges.subscribe((formValue: any) => {
78 const updatedKey = Object.keys(formValue).find(k => formValue[k] !== oldForm[k])
79 oldForm = { ...this.form.value }
80 this.updateDetails([updatedKey])
81 })
82 }
62 }) 83 })
63 } 84 }
64 85
65 updateDetails () { 86 ngOnDestroy () {
87 this.formValuesWatcher?.unsubscribe()
88 }
89
90 updateDetails (onlyKeys?: string[]) {
66 const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] 91 const nsfwPolicy = this.form.value[ 'nsfwPolicy' ]
67 const webTorrentEnabled = this.form.value['webTorrentEnabled'] 92 const webTorrentEnabled = this.form.value['webTorrentEnabled']
68 const autoPlayVideo = this.form.value['autoPlayVideo'] 93 const autoPlayVideo = this.form.value['autoPlayVideo']
@@ -81,7 +106,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
81 } 106 }
82 } 107 }
83 108
84 const details: UserUpdateMe = { 109 let details: UserUpdateMe = {
85 nsfwPolicy, 110 nsfwPolicy,
86 webTorrentEnabled, 111 webTorrentEnabled,
87 autoPlayVideo, 112 autoPlayVideo,
@@ -89,15 +114,22 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
89 videoLanguages 114 videoLanguages
90 } 115 }
91 116
92 this.userService.updateMyProfile(details).subscribe( 117 if (onlyKeys) details = pick(details, onlyKeys)
93 () => {
94 this.notifier.success(this.i18n('Video settings updated.'))
95 118
96 this.authService.refreshUserInformation() 119 if (this.authService.isLoggedIn()) {
97 }, 120 this.userService.updateMyProfile(details).subscribe(
121 () => {
122 this.authService.refreshUserInformation()
98 123
99 err => this.notifier.error(err.message) 124 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Video settings updated.'))
100 ) 125 },
126
127 err => this.notifier.error(err.message)
128 )
129 } else {
130 this.userService.updateMyAnonymousProfile(details)
131 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Display/Video settings updated.'))
132 }
101 } 133 }
102 134
103 getDefaultVideoLanguageLabel () { 135 getDefaultVideoLanguageLabel () {
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html
index 3999252be..d885eb243 100644
--- a/client/src/app/+my-account/my-account.component.html
+++ b/client/src/app/+my-account/my-account.component.html
@@ -1,7 +1,7 @@
1<div class="row"> 1<div class="row">
2 <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown> 2 <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown>
3 3
4 <div class="margin-content"> 4 <div class="margin-content pb-5">
5 <router-outlet></router-outlet> 5 <router-outlet></router-outlet>
6 </div> 6 </div>
7</div> 7</div>
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 6cf1499d3..db8ffac16 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -5,7 +5,6 @@ import { InputSwitchModule } from 'primeng/inputswitch'
5import { SharedModule } from '../shared' 5import { SharedModule } from '../shared'
6import { MyAccountRoutingModule } from './my-account-routing.module' 6import { MyAccountRoutingModule } from './my-account-routing.module'
7import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' 7import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component'
8import { MyAccountVideoSettingsComponent } from './my-account-settings/my-account-video-settings/my-account-video-settings.component'
9import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' 8import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
10import { MyAccountComponent } from './my-account.component' 9import { MyAccountComponent } from './my-account.component'
11import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component' 10import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
@@ -37,7 +36,6 @@ import {
37} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' 36} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
38import { DragDropModule } from '@angular/cdk/drag-drop' 37import { DragDropModule } from '@angular/cdk/drag-drop'
39import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' 38import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email'
40import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
41 39
42@NgModule({ 40@NgModule({
43 imports: [ 41 imports: [
@@ -54,10 +52,8 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account
54 MyAccountComponent, 52 MyAccountComponent,
55 MyAccountSettingsComponent, 53 MyAccountSettingsComponent,
56 MyAccountChangePasswordComponent, 54 MyAccountChangePasswordComponent,
57 MyAccountVideoSettingsComponent,
58 MyAccountProfileComponent, 55 MyAccountProfileComponent,
59 MyAccountChangeEmailComponent, 56 MyAccountChangeEmailComponent,
60 MyAccountInterfaceSettingsComponent,
61 57
62 MyAccountVideosComponent, 58 MyAccountVideosComponent,
63 59
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
index f32a892a4..9eaa3ba32 100644
--- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
+++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
@@ -12,6 +12,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
12import { Subscription } from 'rxjs' 12import { Subscription } from 'rxjs'
13import { ScreenService } from '@app/shared/misc/screen.service' 13import { ScreenService } from '@app/shared/misc/screen.service'
14import { Notifier, ServerService } from '@app/core' 14import { Notifier, ServerService } from '@app/core'
15import { UserService } from '@app/shared'
16import { LocalStorageService } from '@app/shared/misc/storage.service'
15 17
16@Component({ 18@Component({
17 selector: 'my-video-channel-videos', 19 selector: 'my-video-channel-videos',
@@ -34,9 +36,11 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
34 protected serverService: ServerService, 36 protected serverService: ServerService,
35 protected route: ActivatedRoute, 37 protected route: ActivatedRoute,
36 protected authService: AuthService, 38 protected authService: AuthService,
39 protected userService: UserService,
37 protected notifier: Notifier, 40 protected notifier: Notifier,
38 protected confirmService: ConfirmService, 41 protected confirmService: ConfirmService,
39 protected screenService: ScreenService, 42 protected screenService: ScreenService,
43 protected storageService: LocalStorageService,
40 private videoChannelService: VideoChannelService, 44 private videoChannelService: VideoChannelService,
41 private videoService: VideoService 45 private videoService: VideoService
42 ) { 46 ) {
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index 7743124d4..54b320f79 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -27,11 +27,6 @@
27 <div class="main-row"> 27 <div class="main-row">
28 <router-outlet></router-outlet> 28 <router-outlet></router-outlet>
29 </div> 29 </div>
30
31 <footer class="row">
32 <a href="https://joinpeertube.org" title="PeerTube website" target="_blank" rel="noopener noreferrer" i18n-title>Powered by PeerTube</a>&nbsp;-&nbsp;
33 <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" title="PeerTube license" target="_blank" rel="noopener noreferrer" i18n-title>CopyLeft 2015-2020</a>
34 </footer>
35 </div> 30 </div>
36 </div> 31 </div>
37</div> 32</div>
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 9e220a383..55e929e78 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -19,6 +19,7 @@ import { WelcomeModalComponent } from '@app/modal/welcome-modal.component'
19import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' 19import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
20import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '@shared/models' 20import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '@shared/models'
21import { APP_BASE_HREF } from '@angular/common' 21import { APP_BASE_HREF } from '@angular/common'
22import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component'
22 23
23export function metaFactory (serverService: ServerService): MetaLoader { 24export function metaFactory (serverService: ServerService): MetaLoader {
24 return new MetaStaticLoader({ 25 return new MetaStaticLoader({
@@ -39,6 +40,7 @@ export function metaFactory (serverService: ServerService): MetaLoader {
39 40
40 MenuComponent, 41 MenuComponent,
41 LanguageChooserComponent, 42 LanguageChooserComponent,
43 QuickSettingsModalComponent,
42 AvatarNotificationComponent, 44 AvatarNotificationComponent,
43 HeaderComponent, 45 HeaderComponent,
44 SearchTypeaheadComponent, 46 SearchTypeaheadComponent,
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts
index 1447daead..4ad904beb 100644
--- a/client/src/app/core/auth/auth-user.model.ts
+++ b/client/src/app/core/auth/auth-user.model.ts
@@ -67,17 +67,6 @@ class Tokens {
67} 67}
68 68
69export class AuthUser extends User implements ServerMyUserModel { 69export class AuthUser extends User implements ServerMyUserModel {
70 private static KEYS = {
71 ID: 'id',
72 ROLE: 'role',
73 EMAIL: 'email',
74 VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
75 USERNAME: 'username',
76 NSFW_POLICY: 'nsfw_policy',
77 WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
78 AUTO_PLAY_VIDEO: 'auto_play_video'
79 }
80
81 tokens: Tokens 70 tokens: Tokens
82 specialPlaylists: MyUserSpecialPlaylist[] 71 specialPlaylists: MyUserSpecialPlaylist[]
83 72
@@ -106,10 +95,6 @@ export class AuthUser extends User implements ServerMyUserModel {
106 peertubeLocalStorage.removeItem(this.KEYS.USERNAME) 95 peertubeLocalStorage.removeItem(this.KEYS.USERNAME)
107 peertubeLocalStorage.removeItem(this.KEYS.ID) 96 peertubeLocalStorage.removeItem(this.KEYS.ID)
108 peertubeLocalStorage.removeItem(this.KEYS.ROLE) 97 peertubeLocalStorage.removeItem(this.KEYS.ROLE)
109 peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY)
110 peertubeLocalStorage.removeItem(this.KEYS.WEBTORRENT_ENABLED)
111 peertubeLocalStorage.removeItem(this.KEYS.VIDEOS_HISTORY_ENABLED)
112 peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO)
113 peertubeLocalStorage.removeItem(this.KEYS.EMAIL) 98 peertubeLocalStorage.removeItem(this.KEYS.EMAIL)
114 Tokens.flush() 99 Tokens.flush()
115 } 100 }
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts
index 2c5873cb3..3c066ca74 100644
--- a/client/src/app/core/theme/theme.service.ts
+++ b/client/src/app/core/theme/theme.service.ts
@@ -4,16 +4,15 @@ import { ServerService } from '@app/core/server'
4import { environment } from '../../../environments/environment' 4import { environment } from '../../../environments/environment'
5import { PluginService } from '@app/core/plugins/plugin.service' 5import { PluginService } from '@app/core/plugins/plugin.service'
6import { ServerConfig, ServerConfigTheme } from '@shared/models' 6import { ServerConfig, ServerConfigTheme } from '@shared/models'
7import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
8import { first } from 'rxjs/operators' 7import { first } from 'rxjs/operators'
8import { User } from '@app/shared/users/user.model'
9import { UserService } from '@app/shared/users/user.service'
10import { LocalStorageService } from '@app/shared/misc/storage.service'
11import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
9 12
10@Injectable() 13@Injectable()
11export class ThemeService { 14export class ThemeService {
12 15
13 private static KEYS = {
14 LAST_ACTIVE_THEME: 'last_active_theme'
15 }
16
17 private oldThemeName: string 16 private oldThemeName: string
18 private themes: ServerConfigTheme[] = [] 17 private themes: ServerConfigTheme[] = []
19 18
@@ -24,8 +23,10 @@ export class ThemeService {
24 23
25 constructor ( 24 constructor (
26 private auth: AuthService, 25 private auth: AuthService,
26 private userService: UserService,
27 private pluginService: PluginService, 27 private pluginService: PluginService,
28 private server: ServerService 28 private server: ServerService,
29 private localStorageService: LocalStorageService
29 ) {} 30 ) {}
30 31
31 initialize () { 32 initialize () {
@@ -77,11 +78,11 @@ export class ThemeService {
77 private getCurrentTheme () { 78 private getCurrentTheme () {
78 if (this.themeFromLocalStorage) return this.themeFromLocalStorage.name 79 if (this.themeFromLocalStorage) return this.themeFromLocalStorage.name
79 80
80 if (this.auth.isLoggedIn()) { 81 const theme = this.auth.isLoggedIn()
81 const theme = this.auth.getUser().theme 82 ? this.auth.getUser().theme
82 if (theme !== 'instance-default') return theme 83 : this.userService.getAnonymousUser().theme
83 }
84 84
85 if (theme !== 'instance-default') return theme
85 return this.serverConfig.theme.default 86 return this.serverConfig.theme.default
86 } 87 }
87 88
@@ -111,9 +112,9 @@ export class ThemeService {
111 112
112 this.pluginService.reloadLoadedScopes() 113 this.pluginService.reloadLoadedScopes()
113 114
114 peertubeLocalStorage.setItem(ThemeService.KEYS.LAST_ACTIVE_THEME, JSON.stringify(theme)) 115 this.localStorageService.setItem(User.KEYS.THEME, JSON.stringify(theme), false)
115 } else { 116 } else {
116 peertubeLocalStorage.removeItem(ThemeService.KEYS.LAST_ACTIVE_THEME) 117 this.localStorageService.removeItem(User.KEYS.THEME, false)
117 } 118 }
118 119
119 this.oldThemeName = currentTheme 120 this.oldThemeName = currentTheme
@@ -126,6 +127,10 @@ export class ThemeService {
126 127
127 if (!this.auth.isLoggedIn()) { 128 if (!this.auth.isLoggedIn()) {
128 this.updateCurrentTheme() 129 this.updateCurrentTheme()
130
131 this.localStorageService.watch([User.KEYS.THEME]).subscribe(
132 () => this.updateCurrentTheme()
133 )
129 } 134 }
130 135
131 this.auth.userInformationLoaded 136 this.auth.userInformationLoaded
@@ -134,7 +139,7 @@ export class ThemeService {
134 } 139 }
135 140
136 private loadAndSetFromLocalStorage () { 141 private loadAndSetFromLocalStorage () {
137 const lastActiveThemeString = peertubeLocalStorage.getItem(ThemeService.KEYS.LAST_ACTIVE_THEME) 142 const lastActiveThemeString = this.localStorageService.getItem(User.KEYS.THEME)
138 if (!lastActiveThemeString) return 143 if (!lastActiveThemeString) return
139 144
140 try { 145 try {
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
diff --git a/client/src/app/modal/quick-settings-modal.component.html b/client/src/app/modal/quick-settings-modal.component.html
new file mode 100644
index 000000000..8ee83e7dc
--- /dev/null
+++ b/client/src/app/modal/quick-settings-modal.component.html
@@ -0,0 +1,19 @@
1<ng-template #modal let-hide="close">
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Settings</h4>
4 </div>
5
6 <div class="modal-body">
7 <div i18n class="mb-4 quick-settings-title">Display settings</div>
8
9 <my-account-video-settings *ngIf="!isUserLoggedIn()" [user]="user" [userInformationLoaded]="userInformationLoaded" [reactiveUpdate]="true" [notifyOnUpdate]="true">
10 <ng-container ngProjectAs="inner-title">
11 <div i18n class="mb-4 mt-4 quick-settings-title">Video settings</div>
12 </ng-container>
13 </my-account-video-settings>
14
15 <div i18n class="mb-4 mt-4 quick-settings-title">Interface settings</div>
16
17 <my-account-interface-settings *ngIf="!isUserLoggedIn()" [user]="user" [userInformationLoaded]="userInformationLoaded" [reactiveUpdate]="true" [notifyOnUpdate]="true"></my-account-interface-settings>
18 </div>
19</ng-template>
diff --git a/client/src/app/modal/quick-settings-modal.component.scss b/client/src/app/modal/quick-settings-modal.component.scss
new file mode 100644
index 000000000..ef21542f3
--- /dev/null
+++ b/client/src/app/modal/quick-settings-modal.component.scss
@@ -0,0 +1,39 @@
1@import '_mixins';
2
3.modal-button {
4 @include disable-default-a-behaviour;
5 transform: translateY(2px);
6
7 button {
8 @include peertube-button;
9 @include grey-button;
10 @include button-with-icon(18px, 4px, -1px);
11
12 my-global-icon {
13 @include apply-svg-color(#585858);
14 }
15 }
16
17 & + .modal-button {
18 margin-left: 1rem;
19 }
20}
21
22.icon {
23 @include disable-outline;
24 @include icon(22px);
25 opacity: 0.6;
26 margin-left: -1px;
27
28 &.icon-shortcuts {
29 position: relative;
30 top: -1px;
31 margin-right: 4px;
32
33 background-image: url('../../assets/images/menu/keyboard.png');
34 }
35}
36
37.quick-settings-title {
38 @include in-content-small-title;
39} \ No newline at end of file
diff --git a/client/src/app/modal/quick-settings-modal.component.ts b/client/src/app/modal/quick-settings-modal.component.ts
new file mode 100644
index 000000000..41d6c9f47
--- /dev/null
+++ b/client/src/app/modal/quick-settings-modal.component.ts
@@ -0,0 +1,62 @@
1import { Component, ViewChild, OnInit } from '@angular/core'
2import { AuthService, AuthStatus } from '@app/core'
3import { FormReactive, FormValidatorService, UserService, User } from '@app/shared'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { ReplaySubject } from 'rxjs'
7import { LocalStorageService } from '@app/shared/misc/storage.service'
8import { filter } from 'rxjs/operators'
9
10@Component({
11 selector: 'my-quick-settings',
12 templateUrl: './quick-settings-modal.component.html',
13 styleUrls: [ './quick-settings-modal.component.scss' ]
14})
15export class QuickSettingsModalComponent extends FormReactive implements OnInit {
16 @ViewChild('modal', { static: true }) modal: NgbModal
17
18 user: User
19 userInformationLoaded = new ReplaySubject<boolean>(1)
20
21 private openedModal: NgbModalRef
22
23 constructor (
24 protected formValidatorService: FormValidatorService,
25 private modalService: NgbModal,
26 private userService: UserService,
27 private authService: AuthService,
28 private localStorageService: LocalStorageService
29 ) {
30 super()
31 }
32
33 ngOnInit () {
34 this.user = this.userService.getAnonymousUser()
35 this.localStorageService.watch().subscribe(
36 () => this.user = this.userService.getAnonymousUser()
37 )
38 this.userInformationLoaded.next(true)
39
40 this.authService.loginChangedSource
41 .pipe(filter(status => status !== AuthStatus.LoggedIn))
42 .subscribe(
43 () => {
44 this.user = this.userService.getAnonymousUser()
45 this.userInformationLoaded.next(true)
46 }
47 )
48 }
49
50 isUserLoggedIn () {
51 return this.authService.isLoggedIn()
52 }
53
54 show () {
55 this.openedModal = this.modalService.open(this.modal, { centered: true })
56 }
57
58 hide () {
59 this.openedModal.close()
60 this.form.reset()
61 }
62}
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts
index b6e641228..e83daf077 100644
--- a/client/src/app/shared/images/global-icon.component.ts
+++ b/client/src/app/shared/images/global-icon.component.ts
@@ -39,15 +39,18 @@ const icons = {
39 'playlist-add': require('!!raw-loader?!../../../assets/images/video/playlist-add.svg'), 39 'playlist-add': require('!!raw-loader?!../../../assets/images/video/playlist-add.svg'),
40 'play': require('!!raw-loader?!../../../assets/images/global/play.svg'), 40 'play': require('!!raw-loader?!../../../assets/images/global/play.svg'),
41 'playlists': require('!!raw-loader?!../../../assets/images/global/playlists.svg'), 41 'playlists': require('!!raw-loader?!../../../assets/images/global/playlists.svg'),
42 'about': require('!!raw-loader?!../../../assets/images/menu/about.svg'),
43 'globe': require('!!raw-loader?!../../../assets/images/menu/globe.svg'), 42 'globe': require('!!raw-loader?!../../../assets/images/menu/globe.svg'),
44 'home': require('!!raw-loader?!../../../assets/images/menu/home.svg'), 43 'home': require('!!raw-loader?!../../../assets/images/menu/home.svg'),
45 'recently-added': require('!!raw-loader?!../../../assets/images/menu/recently-added.svg'), 44 'recently-added': require('!!raw-loader?!../../../assets/images/menu/recently-added.svg'),
46 'trending': require('!!raw-loader?!../../../assets/images/menu/trending.svg'), 45 'trending': require('!!raw-loader?!../../../assets/images/menu/trending.svg'),
46 'video-lang': require('!!raw-loader?!../../../assets/images/global/video-lang.svg'),
47 'videos': require('!!raw-loader?!../../../assets/images/global/videos.svg'), 47 'videos': require('!!raw-loader?!../../../assets/images/global/videos.svg'),
48 'folder': require('!!raw-loader?!../../../assets/images/global/folder.svg'), 48 'folder': require('!!raw-loader?!../../../assets/images/global/folder.svg'),
49 'administration': require('!!raw-loader?!../../../assets/images/menu/administration.svg'),
50 'subscriptions': require('!!raw-loader?!../../../assets/images/menu/subscriptions.svg'), 49 'subscriptions': require('!!raw-loader?!../../../assets/images/menu/subscriptions.svg'),
50 'language': require('!!raw-loader?!../../../assets/images/menu/language.svg'),
51 'unsensitive': require('!!raw-loader?!../../../assets/images/menu/eye.svg'),
52 'sensitive': require('!!raw-loader?!../../../assets/images/menu/eye-closed.svg'),
53 'p2p': require('!!raw-loader?!../../../assets/images/menu/p2p.svg'),
51 'users': require('!!raw-loader?!../../../assets/images/global/users.svg'), 54 'users': require('!!raw-loader?!../../../assets/images/global/users.svg'),
52 'search': require('!!raw-loader?!../../../assets/images/global/search.svg'), 55 'search': require('!!raw-loader?!../../../assets/images/global/search.svg'),
53 'refresh': require('!!raw-loader?!../../../assets/images/global/refresh.svg') 56 'refresh': require('!!raw-loader?!../../../assets/images/global/refresh.svg')
diff --git a/client/src/app/shared/misc/help.component.scss b/client/src/app/shared/misc/help.component.scss
index f55a516e4..f00df88b5 100644
--- a/client/src/app/shared/misc/help.component.scss
+++ b/client/src/app/shared/misc/help.component.scss
@@ -17,6 +17,7 @@
17 17
18::ng-deep { 18::ng-deep {
19 .help-popover { 19 .help-popover {
20 z-index: z(help-popover) !important;
20 max-width: 300px; 21 max-width: 300px;
21 22
22 .popover-body { 23 .popover-body {
diff --git a/client/src/app/shared/misc/storage.service.ts b/client/src/app/shared/misc/storage.service.ts
new file mode 100644
index 000000000..0d4a8ab53
--- /dev/null
+++ b/client/src/app/shared/misc/storage.service.ts
@@ -0,0 +1,40 @@
1import { Injectable } from '@angular/core'
2import { Observable, Subject } from 'rxjs'
3import {
4 peertubeLocalStorage,
5 peertubeSessionStorage
6} from './peertube-web-storage'
7import { filter } from 'rxjs/operators'
8
9abstract class StorageService {
10 protected instance: Storage
11 static storageSub = new Subject<string>()
12
13 watch (keys?: string[]): Observable<string> {
14 return StorageService.storageSub.asObservable().pipe(filter(val => keys ? keys.includes(val) : true))
15 }
16
17 getItem (key: string) {
18 return this.instance.getItem(key)
19 }
20
21 setItem (key: string, data: any, notifyOfUpdate = true) {
22 this.instance.setItem(key, data)
23 if (notifyOfUpdate) StorageService.storageSub.next(key)
24 }
25
26 removeItem (key: string, notifyOfUpdate = true) {
27 this.instance.removeItem(key)
28 if (notifyOfUpdate) StorageService.storageSub.next(key)
29 }
30}
31
32@Injectable()
33export class LocalStorageService extends StorageService {
34 protected instance: Storage = peertubeLocalStorage
35}
36
37@Injectable()
38export class SessionStorageService extends StorageService {
39 protected instance: Storage = peertubeSessionStorage
40}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 30b3ba0c1..75aa30dab 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -47,6 +47,7 @@ import {
47import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' 47import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar'
48import { InputMaskModule } from 'primeng/inputmask' 48import { InputMaskModule } from 'primeng/inputmask'
49import { ScreenService } from '@app/shared/misc/screen.service' 49import { ScreenService } from '@app/shared/misc/screen.service'
50import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service'
50import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' 51import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service'
51import { VideoCaptionService } from '@app/shared/video-caption' 52import { VideoCaptionService } from '@app/shared/video-caption'
52import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' 53import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
@@ -101,6 +102,10 @@ import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.co
101import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' 102import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component'
102import { RedundancyService } from '@app/shared/video/redundancy.service' 103import { RedundancyService } from '@app/shared/video/redundancy.service'
103import { ClipboardModule } from '@angular/cdk/clipboard' 104import { ClipboardModule } from '@angular/cdk/clipboard'
105import { InputSwitchModule } from 'primeng/inputswitch'
106
107import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
108import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
104 109
105@NgModule({ 110@NgModule({
106 imports: [ 111 imports: [
@@ -122,7 +127,8 @@ import { ClipboardModule } from '@angular/cdk/clipboard'
122 PrimeSharedModule, 127 PrimeSharedModule,
123 InputMaskModule, 128 InputMaskModule,
124 NgPipesModule, 129 NgPipesModule,
125 MultiSelectModule 130 MultiSelectModule,
131 InputSwitchModule
126 ], 132 ],
127 133
128 declarations: [ 134 declarations: [
@@ -180,7 +186,10 @@ import { ClipboardModule } from '@angular/cdk/clipboard'
180 DateToggleComponent, 186 DateToggleComponent,
181 187
182 GlobalIconComponent, 188 GlobalIconComponent,
183 PreviewUploadComponent 189 PreviewUploadComponent,
190
191 MyAccountVideoSettingsComponent,
192 MyAccountInterfaceSettingsComponent
184 ], 193 ],
185 194
186 exports: [ 195 exports: [
@@ -258,7 +267,10 @@ import { ClipboardModule } from '@angular/cdk/clipboard'
258 FromNowPipe, 267 FromNowPipe,
259 HighlightPipe, 268 HighlightPipe,
260 PeerTubeTemplateDirective, 269 PeerTubeTemplateDirective,
261 VideoDurationPipe 270 VideoDurationPipe,
271
272 MyAccountVideoSettingsComponent,
273 MyAccountInterfaceSettingsComponent
262 ], 274 ],
263 275
264 providers: [ 276 providers: [
@@ -303,6 +315,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard'
303 315
304 I18nPrimengCalendarService, 316 I18nPrimengCalendarService,
305 ScreenService, 317 ScreenService,
318 LocalStorageService, SessionStorageService,
306 319
307 UserNotificationService, 320 UserNotificationService,
308 321
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 7707d7dda..a37cae749 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -1,10 +1,32 @@
1import { hasUserRight, User as UserServerModel, UserNotificationSetting, UserRight, UserRole, VideoChannel } from '../../../../../shared' 1import {
2 hasUserRight,
3 User as UserServerModel,
4 UserNotificationSetting,
5 UserRight,
6 UserRole
7} from '../../../../../shared/models/users'
8import { VideoChannel } from '../../../../../shared/models/videos'
2import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' 9import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
3import { Account } from '@app/shared/account/account.model' 10import { Account } from '@app/shared/account/account.model'
4import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 11import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
5import { UserAdminFlag } from '@shared/models/users/user-flag.model' 12import { UserAdminFlag } from '@shared/models/users/user-flag.model'
6 13
7export class User implements UserServerModel { 14export class User implements UserServerModel {
15 static KEYS = {
16 ID: 'id',
17 ROLE: 'role',
18 EMAIL: 'email',
19 VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
20 USERNAME: 'username',
21 NSFW_POLICY: 'nsfw_policy',
22 WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
23 AUTO_PLAY_VIDEO: 'auto_play_video',
24 SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video',
25 AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist',
26 THEME: 'last_active_theme',
27 VIDEO_LANGUAGES: 'video_languages'
28 }
29
8 id: number 30 id: number
9 username: string 31 username: string
10 email: string 32 email: string
@@ -60,8 +82,11 @@ export class User implements UserServerModel {
60 82
61 this.nsfwPolicy = hash.nsfwPolicy 83 this.nsfwPolicy = hash.nsfwPolicy
62 this.webTorrentEnabled = hash.webTorrentEnabled 84 this.webTorrentEnabled = hash.webTorrentEnabled
63 this.videosHistoryEnabled = hash.videosHistoryEnabled
64 this.autoPlayVideo = hash.autoPlayVideo 85 this.autoPlayVideo = hash.autoPlayVideo
86 this.autoPlayNextVideo = hash.autoPlayNextVideo
87 this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist
88 this.videosHistoryEnabled = hash.videosHistoryEnabled
89 this.videoLanguages = hash.videoLanguages
65 90
66 this.theme = hash.theme 91 this.theme = hash.theme
67 92
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index e24d91df3..a79343646 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -1,8 +1,8 @@
1import { from, Observable, of } from 'rxjs' 1import { from, Observable } from 'rxjs'
2import { catchError, concatMap, map, share, shareReplay, tap, toArray } from 'rxjs/operators' 2import { catchError, concatMap, map, shareReplay, toArray } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' 5import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared'
6import { environment } from '../../../environments/environment' 6import { environment } from '../../../environments/environment'
7import { RestExtractor, RestPagination, RestService } from '../rest' 7import { RestExtractor, RestPagination, RestService } from '../rest'
8import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 8import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
@@ -10,6 +10,10 @@ import { SortMeta } from 'primeng/api'
10import { BytesPipe } from 'ngx-pipes' 10import { BytesPipe } from 'ngx-pipes'
11import { I18n } from '@ngx-translate/i18n-polyfill' 11import { I18n } from '@ngx-translate/i18n-polyfill'
12import { UserRegister } from '@shared/models/users/user-register.model' 12import { UserRegister } from '@shared/models/users/user-register.model'
13import { User } from './user.model'
14import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
15import { has } from 'lodash-es'
16import { LocalStorageService, SessionStorageService } from '../misc/storage.service'
13 17
14@Injectable() 18@Injectable()
15export class UserService { 19export class UserService {
@@ -17,12 +21,14 @@ export class UserService {
17 21
18 private bytesPipe = new BytesPipe() 22 private bytesPipe = new BytesPipe()
19 23
20 private userCache: { [ id: number ]: Observable<User> } = {} 24 private userCache: { [ id: number ]: Observable<UserServerModel> } = {}
21 25
22 constructor ( 26 constructor (
23 private authHttp: HttpClient, 27 private authHttp: HttpClient,
24 private restExtractor: RestExtractor, 28 private restExtractor: RestExtractor,
25 private restService: RestService, 29 private restService: RestService,
30 private localStorageService: LocalStorageService,
31 private sessionStorageService: SessionStorageService,
26 private i18n: I18n 32 private i18n: I18n
27 ) { } 33 ) { }
28 34
@@ -64,6 +70,30 @@ export class UserService {
64 ) 70 )
65 } 71 }
66 72
73 updateMyAnonymousProfile (profile: UserUpdateMe) {
74 const supportedKeys = {
75 // local storage keys
76 nsfwPolicy: (val: NSFWPolicyType) => this.localStorageService.setItem(User.KEYS.NSFW_POLICY, val),
77 webTorrentEnabled: (val: boolean) => this.localStorageService.setItem(User.KEYS.WEBTORRENT_ENABLED, String(val)),
78 autoPlayVideo: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO, String(val)),
79 autoPlayNextVideoPlaylist: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST, String(val)),
80 theme: (val: string) => this.localStorageService.setItem(User.KEYS.THEME, val),
81 videoLanguages: (val: string[]) => this.localStorageService.setItem(User.KEYS.VIDEO_LANGUAGES, JSON.stringify(val)),
82
83 // session storage keys
84 autoPlayNextVideo: (val: boolean) =>
85 this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, String(val))
86 }
87
88 for (const key of Object.keys(profile)) {
89 try {
90 if (has(supportedKeys, key)) supportedKeys[key](profile[key])
91 } catch (err) {
92 console.error(`Cannot set item ${key} in localStorage. Likely due to a value impossible to stringify.`, err)
93 }
94 }
95 }
96
67 deleteMe () { 97 deleteMe () {
68 const url = UserService.BASE_USERS_URL + 'me' 98 const url = UserService.BASE_USERS_URL + 'me'
69 99
@@ -187,7 +217,7 @@ export class UserService {
187 ) 217 )
188 } 218 }
189 219
190 updateUsers (users: User[], userUpdate: UserUpdate) { 220 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
191 return from(users) 221 return from(users)
192 .pipe( 222 .pipe(
193 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)), 223 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
@@ -205,17 +235,40 @@ export class UserService {
205 } 235 }
206 236
207 getUser (userId: number) { 237 getUser (userId: number) {
208 return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId) 238 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId)
209 .pipe(catchError(err => this.restExtractor.handleError(err))) 239 .pipe(catchError(err => this.restExtractor.handleError(err)))
210 } 240 }
211 241
212 getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<User>> { 242 getAnonymousUser () {
243 let videoLanguages
244 try {
245 videoLanguages = JSON.parse(this.localStorageService.getItem(User.KEYS.VIDEO_LANGUAGES))
246 } catch (err) {
247 videoLanguages = null
248 console.error('Cannot parse desired video languages from localStorage.', err)
249 }
250
251 return new User({
252 // local storage keys
253 nsfwPolicy: this.localStorageService.getItem(User.KEYS.NSFW_POLICY) as NSFWPolicyType,
254 webTorrentEnabled: this.localStorageService.getItem(User.KEYS.WEBTORRENT_ENABLED) !== 'false',
255 autoPlayVideo: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO) === 'true',
256 autoPlayNextVideoPlaylist: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST) === 'true',
257 theme: this.localStorageService.getItem(User.KEYS.THEME) || 'default',
258 videoLanguages,
259
260 // session storage keys
261 autoPlayNextVideo: this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
262 })
263 }
264
265 getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<UserServerModel>> {
213 let params = new HttpParams() 266 let params = new HttpParams()
214 params = this.restService.addRestGetParams(params, pagination, sort) 267 params = this.restService.addRestGetParams(params, pagination, sort)
215 268
216 if (search) params = params.append('search', search) 269 if (search) params = params.append('search', search)
217 270
218 return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params }) 271 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
219 .pipe( 272 .pipe(
220 map(res => this.restExtractor.convertResultListDateToHuman(res)), 273 map(res => this.restExtractor.convertResultListDateToHuman(res)),
221 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))), 274 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
@@ -223,7 +276,7 @@ export class UserService {
223 ) 276 )
224 } 277 }
225 278
226 removeUser (usersArg: User | User[]) { 279 removeUser (usersArg: UserServerModel | UserServerModel[]) {
227 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] 280 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
228 281
229 return from(users) 282 return from(users)
@@ -234,7 +287,7 @@ export class UserService {
234 ) 287 )
235 } 288 }
236 289
237 banUsers (usersArg: User | User[], reason?: string) { 290 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
238 const body = reason ? { reason } : {} 291 const body = reason ? { reason } : {}
239 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] 292 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
240 293
@@ -246,7 +299,7 @@ export class UserService {
246 ) 299 )
247 } 300 }
248 301
249 unbanUsers (usersArg: User | User[]) { 302 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
250 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] 303 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
251 304
252 return from(users) 305 return from(users)
@@ -257,7 +310,7 @@ export class UserService {
257 ) 310 )
258 } 311 }
259 312
260 private formatUser (user: User) { 313 private formatUser (user: UserServerModel) {
261 let videoQuota 314 let videoQuota
262 if (user.videoQuota === -1) { 315 if (user.videoQuota === -1) {
263 videoQuota = this.i18n('Unlimited') 316 videoQuota = this.i18n('Unlimited')
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 2f5f82aa3..b146d7014 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -1,4 +1,4 @@
1import { debounceTime, first, tap } from 'rxjs/operators' 1import { debounceTime, first, tap, throttleTime } from 'rxjs/operators'
2import { OnDestroy, OnInit } from '@angular/core' 2import { OnDestroy, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs' 4import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs'
@@ -15,6 +15,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
15import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' 15import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date'
16import { ServerConfig } from '@shared/models' 16import { ServerConfig } from '@shared/models'
17import { GlobalIconName } from '@app/shared/images/global-icon.component' 17import { GlobalIconName } from '@app/shared/images/global-icon.component'
18import { UserService, User } from '../users'
19import { LocalStorageService } from '../misc/storage.service'
18 20
19enum GroupDate { 21enum GroupDate {
20 UNKNOWN = 0, 22 UNKNOWN = 0,
@@ -72,9 +74,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
72 74
73 protected abstract notifier: Notifier 75 protected abstract notifier: Notifier
74 protected abstract authService: AuthService 76 protected abstract authService: AuthService
77 protected abstract userService: UserService
75 protected abstract route: ActivatedRoute 78 protected abstract route: ActivatedRoute
76 protected abstract serverService: ServerService 79 protected abstract serverService: ServerService
77 protected abstract screenService: ScreenService 80 protected abstract screenService: ScreenService
81 protected abstract storageService: LocalStorageService
78 protected abstract router: Router 82 protected abstract router: Router
79 protected abstract i18n: I18n 83 protected abstract i18n: I18n
80 abstract titlePage: string 84 abstract titlePage: string
@@ -124,6 +128,16 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
124 if (this.loadOnInit === true) { 128 if (this.loadOnInit === true) {
125 loadUserObservable.subscribe(() => this.loadMoreVideos()) 129 loadUserObservable.subscribe(() => this.loadMoreVideos())
126 } 130 }
131
132 this.storageService.watch([
133 User.KEYS.NSFW_POLICY,
134 User.KEYS.VIDEO_LANGUAGES
135 ]).pipe(throttleTime(200)).subscribe(
136 () => {
137 this.loadUserVideoLanguagesIfNeeded()
138 if (this.hasDoneFirstQuery) this.reloadVideos()
139 }
140 )
127 } 141 }
128 142
129 ngOnDestroy () { 143 ngOnDestroy () {
@@ -279,7 +293,12 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
279 } 293 }
280 294
281 private loadUserVideoLanguagesIfNeeded () { 295 private loadUserVideoLanguagesIfNeeded () {
282 if (!this.authService.isLoggedIn() || !this.useUserVideoLanguagePreferences) { 296 if (!this.useUserVideoLanguagePreferences) {
297 return of(true)
298 }
299
300 if (!this.authService.isLoggedIn()) {
301 this.languageOneOf = this.userService.getAnonymousUser().videoLanguages
283 return of(true) 302 return of(true)
284 } 303 }
285 304
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 996202154..a51b9cab9 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -27,10 +27,11 @@ import { objectToFormData } from '@app/shared/misc/utils'
27import { Account } from '@app/shared/account/account.model' 27import { Account } from '@app/shared/account/account.model'
28import { AccountService } from '@app/shared/account/account.service' 28import { AccountService } from '@app/shared/account/account.service'
29import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' 29import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
30import { ServerService } from '@app/core' 30import { ServerService, AuthService } from '@app/core'
31import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' 31import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
32import { VideoChannel } from '@app/shared/video-channel/video-channel.model' 32import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
33import { I18n } from '@ngx-translate/i18n-polyfill' 33import { I18n } from '@ngx-translate/i18n-polyfill'
34import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
34 35
35export interface VideosProvider { 36export interface VideosProvider {
36 getVideos (parameters: { 37 getVideos (parameters: {
@@ -49,6 +50,8 @@ export class VideoService implements VideosProvider {
49 50
50 constructor ( 51 constructor (
51 private authHttp: HttpClient, 52 private authHttp: HttpClient,
53 private authService: AuthService,
54 private userService: UserService,
52 private restExtractor: RestExtractor, 55 private restExtractor: RestExtractor,
53 private restService: RestService, 56 private restService: RestService,
54 private serverService: ServerService, 57 private serverService: ServerService,
@@ -199,9 +202,10 @@ export class VideoService implements VideosProvider {
199 filter?: VideoFilter, 202 filter?: VideoFilter,
200 categoryOneOf?: number, 203 categoryOneOf?: number,
201 languageOneOf?: string[], 204 languageOneOf?: string[],
202 skipCount?: boolean 205 skipCount?: boolean,
206 nsfw?: boolean
203 }): Observable<ResultList<Video>> { 207 }): Observable<ResultList<Video>> {
204 const { videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount } = parameters 208 const { videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount, nsfw } = parameters
205 209
206 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 210 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
207 211
@@ -212,6 +216,15 @@ export class VideoService implements VideosProvider {
212 if (categoryOneOf) params = params.set('categoryOneOf', categoryOneOf + '') 216 if (categoryOneOf) params = params.set('categoryOneOf', categoryOneOf + '')
213 if (skipCount) params = params.set('skipCount', skipCount + '') 217 if (skipCount) params = params.set('skipCount', skipCount + '')
214 218
219 if (nsfw) {
220 params = params.set('nsfw', nsfw + '')
221 } else {
222 const nsfwPolicy = this.authService.isLoggedIn()
223 ? this.authService.getUser().nsfwPolicy
224 : this.userService.getAnonymousUser().nsfwPolicy
225 if (this.nsfwPolicyToFilter(nsfwPolicy)) params.set('nsfw', 'false')
226 }
227
215 if (languageOneOf) { 228 if (languageOneOf) {
216 for (const l of languageOneOf) { 229 for (const l of languageOneOf) {
217 params = params.append('languageOneOf[]', l) 230 params = params.append('languageOneOf[]', l)
@@ -368,4 +381,8 @@ export class VideoService implements VideosProvider {
368 catchError(err => this.restExtractor.handleError(err)) 381 catchError(err => this.restExtractor.handleError(err))
369 ) 382 )
370 } 383 }
384
385 private nsfwPolicyToFilter (policy: NSFWPolicyType) {
386 return policy === 'do_not_list'
387 }
371} 388}
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts
index 064420056..17e5beb24 100644
--- a/client/src/app/shared/video/videos-selection.component.ts
+++ b/client/src/app/shared/video/videos-selection.component.ts
@@ -22,6 +22,8 @@ import { VideoSortField } from '@app/shared/video/sort-field.type'
22import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 22import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
23import { I18n } from '@ngx-translate/i18n-polyfill' 23import { I18n } from '@ngx-translate/i18n-polyfill'
24import { ResultList } from '@shared/models' 24import { ResultList } from '@shared/models'
25import { UserService } from '../users'
26import { LocalStorageService } from '../misc/storage.service'
25 27
26export type SelectionType = { [ id: number ]: boolean } 28export type SelectionType = { [ id: number ]: boolean }
27 29
@@ -51,7 +53,9 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
51 protected route: ActivatedRoute, 53 protected route: ActivatedRoute,
52 protected notifier: Notifier, 54 protected notifier: Notifier,
53 protected authService: AuthService, 55 protected authService: AuthService,
56 protected userService: UserService,
54 protected screenService: ScreenService, 57 protected screenService: ScreenService,
58 protected storageService: LocalStorageService,
55 protected serverService: ServerService 59 protected serverService: ServerService
56 ) { 60 ) {
57 super() 61 super()
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
index c5ed36000..827c34d41 100644
--- a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
@@ -9,6 +9,7 @@ import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.
9import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' 9import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
10import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' 10import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage'
11import { I18n } from '@ngx-translate/i18n-polyfill' 11import { I18n } from '@ngx-translate/i18n-polyfill'
12import { SessionStorageService, LocalStorageService } from '@app/shared/misc/storage.service'
12 13
13@Component({ 14@Component({
14 selector: 'my-video-watch-playlist', 15 selector: 'my-video-watch-playlist',
@@ -42,16 +43,18 @@ export class VideoWatchPlaylistComponent {
42 private notifier: Notifier, 43 private notifier: Notifier,
43 private i18n: I18n, 44 private i18n: I18n,
44 private videoPlaylist: VideoPlaylistService, 45 private videoPlaylist: VideoPlaylistService,
46 private localStorageService: LocalStorageService,
47 private sessionStorageService: SessionStorageService,
45 private router: Router 48 private router: Router
46 ) { 49 ) {
47 // defaults to true 50 // defaults to true
48 this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() 51 this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn()
49 ? this.auth.getUser().autoPlayNextVideoPlaylist 52 ? this.auth.getUser().autoPlayNextVideoPlaylist
50 : peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' 53 : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
51 this.setAutoPlayNextVideoPlaylistSwitchText() 54 this.setAutoPlayNextVideoPlaylistSwitchText()
52 55
53 // defaults to false 56 // defaults to false
54 this.loopPlaylist = peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' 57 this.loopPlaylist = this.sessionStorageService.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
55 this.setLoopPlaylistSwitchText() 58 this.setLoopPlaylistSwitchText()
56 } 59 }
57 60
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index cfa0432ad..585acd7a8 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -257,15 +257,18 @@
257 257
258 <div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> 258 <div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false">
259 <div class="privacy-concerns-text"> 259 <div class="privacy-concerns-text">
260 <strong i18n>Friendly Reminder: </strong> 260 <span i18n class="mr-2">
261 <ng-container i18n> 261 <strong>Help your peers</strong>
262 the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers. 262 and activate the sharing system to improve the experience for everyone.
263 </ng-container> 263 </span>
264 <a i18n i18n-title title="Get more information" target="_blank" rel="noopener noreferrer" href="/about/peertube">More information</a> 264 <a i18n i18n-title title="Get more information" target="_blank" rel="noopener noreferrer" href="/about/peertube">More information</a>
265 </div> 265 </div>
266 266
267 <div i18n class="privacy-concerns-okay" (click)="acceptedPrivacyConcern()"> 267 <div i18n class="privacy-concerns-button" (click)="declinedPrivacyConcern()">
268 OK 268 No thanks
269 </div>
270 <div i18n class="privacy-concerns-button privacy-concerns-okay" (click)="acceptedPrivacyConcern()">
271 Activate
269 </div> 272 </div>
270 </div> 273 </div>
271</div> 274</div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index ae79c2ff6..10e129ac5 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -443,6 +443,7 @@ my-video-comments {
443 443
444// If the view is not expanded, take into account the menu 444// If the view is not expanded, take into account the menu
445.privacy-concerns { 445.privacy-concerns {
446 z-index: z(dropdown) + 1;
446 width: calc(100% - #{$menu-width}); 447 width: calc(100% - #{$menu-width});
447} 448}
448 449
@@ -488,11 +489,11 @@ my-video-comments {
488 } 489 }
489 } 490 }
490 491
491 .privacy-concerns-okay { 492 .privacy-concerns-button {
492 background-color: var(--mainColor);
493 padding: 5px 8px 5px 7px; 493 padding: 5px 8px 5px 7px;
494 margin-left: auto; 494 margin-left: auto;
495 border-radius: 3px; 495 border-radius: 3px;
496 white-space: nowrap;
496 cursor: pointer; 497 cursor: pointer;
497 transition: background-color 0.3s; 498 transition: background-color 0.3s;
498 font-weight: $font-semibold; 499 font-weight: $font-semibold;
@@ -501,6 +502,11 @@ my-video-comments {
501 background-color: #000; 502 background-color: #000;
502 } 503 }
503 } 504 }
505
506 .privacy-concerns-okay {
507 background-color: var(--mainColor);
508 margin-left: 10px;
509 }
504} 510}
505 511
506@media screen and (max-width: 1600px) { 512@media screen and (max-width: 1600px) {
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index ee3deb5e9..9ba14316c 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -2,7 +2,7 @@ import { catchError } from 'rxjs/operators'
2import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' 2import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { RedirectService } from '@app/core/routing/redirect.service' 4import { RedirectService } from '@app/core/routing/redirect.service'
5import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' 5import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
6import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' 6import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
7import { MetaService } from '@ngx-meta/core' 7import { MetaService } from '@ngx-meta/core'
8import { AuthUser, Notifier, ServerService } from '@app/core' 8import { AuthUser, Notifier, ServerService } from '@app/core'
@@ -10,7 +10,7 @@ import { forkJoin, Observable, Subscription } from 'rxjs'
10import { Hotkey, HotkeysService } from 'angular2-hotkeys' 10import { Hotkey, HotkeysService } from 'angular2-hotkeys'
11import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' 11import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared'
12import { AuthService, ConfirmService } from '../../core' 12import { AuthService, ConfirmService } from '../../core'
13import { RestExtractor } from '../../shared' 13import { RestExtractor, UserService } from '../../shared'
14import { VideoDetails } from '../../shared/video/video-details.model' 14import { VideoDetails } from '../../shared/video/video-details.model'
15import { VideoService } from '../../shared/video/video.service' 15import { VideoService } from '../../shared/video/video.service'
16import { VideoShareComponent } from './modal/video-share.component' 16import { VideoShareComponent } from './modal/video-share.component'
@@ -35,7 +35,6 @@ import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watc
35import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' 35import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage'
36import { HooksService } from '@app/core/plugins/hooks.service' 36import { HooksService } from '@app/core/plugins/hooks.service'
37import { PlatformLocation } from '@angular/common' 37import { PlatformLocation } from '@angular/common'
38import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component'
39import { scrollToTop, isXPercentInViewport } from '@app/shared/misc/utils' 38import { scrollToTop, isXPercentInViewport } from '@app/shared/misc/utils'
40 39
41@Component({ 40@Component({
@@ -95,6 +94,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
95 private confirmService: ConfirmService, 94 private confirmService: ConfirmService,
96 private metaService: MetaService, 95 private metaService: MetaService,
97 private authService: AuthService, 96 private authService: AuthService,
97 private userService: UserService,
98 private serverService: ServerService, 98 private serverService: ServerService,
99 private restExtractor: RestExtractor, 99 private restExtractor: RestExtractor,
100 private notifier: Notifier, 100 private notifier: Notifier,
@@ -118,6 +118,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
118 return this.authService.getUser() 118 return this.authService.getUser()
119 } 119 }
120 120
121 get anonymousUser () {
122 return this.userService.getAnonymousUser()
123 }
124
121 async ngOnInit () { 125 async ngOnInit () {
122 this.serverConfig = this.serverService.getTmpConfig() 126 this.serverConfig = this.serverService.getTmpConfig()
123 127
@@ -266,6 +270,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
266 this.redirectService.redirectToHomepage() 270 this.redirectService.redirectToHomepage()
267 } 271 }
268 272
273 declinedPrivacyConcern () {
274 peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'false')
275 this.hasAlreadyAcceptedPrivacyConcern = false
276 }
277
269 acceptedPrivacyConcern () { 278 acceptedPrivacyConcern () {
270 peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true') 279 peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true')
271 this.hasAlreadyAcceptedPrivacyConcern = true 280 this.hasAlreadyAcceptedPrivacyConcern = true
@@ -290,7 +299,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
290 isAutoPlayEnabled () { 299 isAutoPlayEnabled () {
291 return ( 300 return (
292 (this.user && this.user.autoPlayNextVideo) || 301 (this.user && this.user.autoPlayNextVideo) ||
293 peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' 302 this.anonymousUser.autoPlayNextVideo
294 ) 303 )
295 } 304 }
296 305
@@ -302,7 +311,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
302 isPlaylistAutoPlayEnabled () { 311 isPlaylistAutoPlayEnabled () {
303 return ( 312 return (
304 (this.user && this.user.autoPlayNextVideoPlaylist) || 313 (this.user && this.user.autoPlayNextVideoPlaylist) ||
305 peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' 314 this.anonymousUser.autoPlayNextVideoPlaylist
306 ) 315 )
307 } 316 }
308 317
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts
index 5fa50ecbb..9b445269d 100644
--- a/client/src/app/videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/videos/+video-watch/video-watch.module.ts
@@ -12,7 +12,6 @@ import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
12import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' 12import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module'
13import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' 13import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
14import { QRCodeModule } from 'angularx-qrcode' 14import { QRCodeModule } from 'angularx-qrcode'
15import { InputSwitchModule } from 'primeng/inputswitch'
16import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestamp-route-transformer.directive' 15import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestamp-route-transformer.directive'
17 16
18@NgModule({ 17@NgModule({
@@ -21,8 +20,7 @@ import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestam
21 SharedModule, 20 SharedModule,
22 NgbTooltipModule, 21 NgbTooltipModule,
23 QRCodeModule, 22 QRCodeModule,
24 RecommendationsModule, 23 RecommendationsModule
25 InputSwitchModule
26 ], 24 ],
27 25
28 declarations: [ 26 declarations: [
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.html b/client/src/app/videos/recommendations/recommended-videos.component.html
index 4548c7d80..74f9ed2a5 100644
--- a/client/src/app/videos/recommendations/recommended-videos.component.html
+++ b/client/src/app/videos/recommendations/recommended-videos.component.html
@@ -8,7 +8,7 @@
8 [ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto" 8 [ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto"
9 > 9 >
10 <span i18n>AUTOPLAY</span> 10 <span i18n>AUTOPLAY</span>
11 <p-inputSwitch [(ngModel)]="autoPlayNextVideo" (ngModelChange)="switchAutoPlayNextVideo()"></p-inputSwitch> 11 <p-inputSwitch class="small" [(ngModel)]="autoPlayNextVideo" (ngModelChange)="switchAutoPlayNextVideo()"></p-inputSwitch>
12 </div> 12 </div>
13 </div> 13 </div>
14 14
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.scss b/client/src/app/videos/recommendations/recommended-videos.component.scss
index 1ab0c47ff..cde62f87f 100644
--- a/client/src/app/videos/recommendations/recommended-videos.component.scss
+++ b/client/src/app/videos/recommendations/recommended-videos.component.scss
@@ -25,25 +25,3 @@
25 font-weight: 600; 25 font-weight: 600;
26 } 26 }
27} 27}
28
29/* p-inputSwitch styles to reduce the switch size */
30
31::ng-deep {
32 p-inputswitch {
33 height: 20px;
34 }
35
36 .ui-inputswitch {
37 width: 2.5em !important;
38 height: 1.45em !important;
39
40 .ui-inputswitch-slider::before {
41 height: 1em !important;
42 width: 1em !important;
43 }
44 }
45
46 .ui-inputswitch-checked .ui-inputswitch-slider::before {
47 transform: translateX(1em) !important;
48 }
49}
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.ts b/client/src/app/videos/recommendations/recommended-videos.component.ts
index ada6d3433..d4b4c929b 100644
--- a/client/src/app/videos/recommendations/recommended-videos.component.ts
+++ b/client/src/app/videos/recommendations/recommended-videos.component.ts
@@ -7,8 +7,8 @@ import { RecommendedVideosStore } from '@app/videos/recommendations/recommended-
7import { User } from '@app/shared' 7import { User } from '@app/shared'
8import { AuthService, Notifier } from '@app/core' 8import { AuthService, Notifier } from '@app/core'
9import { UserService } from '@app/shared/users/user.service' 9import { UserService } from '@app/shared/users/user.service'
10import { peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage'
11import { I18n } from '@ngx-translate/i18n-polyfill' 10import { I18n } from '@ngx-translate/i18n-polyfill'
11import { SessionStorageService } from '@app/shared/misc/storage.service'
12 12
13@Component({ 13@Component({
14 selector: 'my-recommended-videos', 14 selector: 'my-recommended-videos',
@@ -16,8 +16,6 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
16 styleUrls: [ './recommended-videos.component.scss' ] 16 styleUrls: [ './recommended-videos.component.scss' ]
17}) 17})
18export class RecommendedVideosComponent implements OnChanges { 18export class RecommendedVideosComponent implements OnChanges {
19 static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
20
21 @Input() inputRecommendation: RecommendationInfo 19 @Input() inputRecommendation: RecommendationInfo
22 @Input() user: User 20 @Input() user: User
23 @Input() playlist: VideoPlaylist 21 @Input() playlist: VideoPlaylist
@@ -34,15 +32,21 @@ export class RecommendedVideosComponent implements OnChanges {
34 private authService: AuthService, 32 private authService: AuthService,
35 private notifier: Notifier, 33 private notifier: Notifier,
36 private i18n: I18n, 34 private i18n: I18n,
37 private store: RecommendedVideosStore 35 private store: RecommendedVideosStore,
36 private sessionStorageService: SessionStorageService
38 ) { 37 ) {
39 this.videos$ = this.store.recommendations$ 38 this.videos$ = this.store.recommendations$
40 this.hasVideos$ = this.store.hasRecommendations$ 39 this.hasVideos$ = this.store.hasRecommendations$
41 this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) 40 this.videos$.subscribe(videos => this.gotRecommendations.emit(videos))
42 41
43 this.autoPlayNextVideo = this.authService.isLoggedIn() 42 if (this.authService.isLoggedIn()) {
44 ? this.authService.getUser().autoPlayNextVideo 43 this.autoPlayNextVideo = this.authService.getUser().autoPlayNextVideo
45 : peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false 44 } else {
45 this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false
46 this.sessionStorageService.watch([User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO]).subscribe(
47 () => this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
48 )
49 }
46 50
47 this.autoPlayNextVideoTooltip = this.i18n('When active, the next video is automatically played after the current one.') 51 this.autoPlayNextVideoTooltip = this.i18n('When active, the next video is automatically played after the current one.')
48 } 52 }
@@ -58,7 +62,7 @@ export class RecommendedVideosComponent implements OnChanges {
58 } 62 }
59 63
60 switchAutoPlayNextVideo () { 64 switchAutoPlayNextVideo () {
61 peertubeSessionStorage.setItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) 65 this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString())
62 66
63 if (this.authService.isLoggedIn()) { 67 if (this.authService.isLoggedIn()) {
64 const details = { 68 const details = {
diff --git a/client/src/app/videos/video-list/video-local.component.ts b/client/src/app/videos/video-list/video-local.component.ts
index 59f65f95c..757b0e498 100644
--- a/client/src/app/videos/video-list/video-local.component.ts
+++ b/client/src/app/videos/video-list/video-local.component.ts
@@ -11,6 +11,8 @@ import { ScreenService } from '@app/shared/misc/screen.service'
11import { UserRight } from '../../../../../shared/models/users' 11import { UserRight } from '../../../../../shared/models/users'
12import { Notifier, ServerService } from '@app/core' 12import { Notifier, ServerService } from '@app/core'
13import { HooksService } from '@app/core/plugins/hooks.service' 13import { HooksService } from '@app/core/plugins/hooks.service'
14import { UserService } from '@app/shared'
15import { LocalStorageService } from '@app/shared/misc/storage.service'
14 16
15@Component({ 17@Component({
16 selector: 'my-videos-local', 18 selector: 'my-videos-local',
@@ -31,7 +33,9 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
31 protected route: ActivatedRoute, 33 protected route: ActivatedRoute,
32 protected notifier: Notifier, 34 protected notifier: Notifier,
33 protected authService: AuthService, 35 protected authService: AuthService,
36 protected userService: UserService,
34 protected screenService: ScreenService, 37 protected screenService: ScreenService,
38 protected storageService: LocalStorageService,
35 private videoService: VideoService, 39 private videoService: VideoService,
36 private hooks: HooksService 40 private hooks: HooksService
37 ) { 41 ) {
diff --git a/client/src/app/videos/video-list/video-most-liked.component.ts b/client/src/app/videos/video-list/video-most-liked.component.ts
index 6ff7a1e0e..b69fad05f 100644
--- a/client/src/app/videos/video-list/video-most-liked.component.ts
+++ b/client/src/app/videos/video-list/video-most-liked.component.ts
@@ -9,6 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
9import { ScreenService } from '@app/shared/misc/screen.service' 9import { ScreenService } from '@app/shared/misc/screen.service'
10import { Notifier, ServerService } from '@app/core' 10import { Notifier, ServerService } from '@app/core'
11import { HooksService } from '@app/core/plugins/hooks.service' 11import { HooksService } from '@app/core/plugins/hooks.service'
12import { UserService } from '@app/shared'
13import { LocalStorageService } from '@app/shared/misc/storage.service'
12 14
13@Component({ 15@Component({
14 selector: 'my-videos-most-liked', 16 selector: 'my-videos-most-liked',
@@ -28,7 +30,9 @@ export class VideoMostLikedComponent extends AbstractVideoList implements OnInit
28 protected route: ActivatedRoute, 30 protected route: ActivatedRoute,
29 protected notifier: Notifier, 31 protected notifier: Notifier,
30 protected authService: AuthService, 32 protected authService: AuthService,
33 protected userService: UserService,
31 protected screenService: ScreenService, 34 protected screenService: ScreenService,
35 protected storageService: LocalStorageService,
32 private videoService: VideoService, 36 private videoService: VideoService,
33 private hooks: HooksService 37 private hooks: HooksService
34 ) { 38 ) {
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts
index 7568f4536..c1ddd4fd4 100644
--- a/client/src/app/videos/video-list/video-recently-added.component.ts
+++ b/client/src/app/videos/video-list/video-recently-added.component.ts
@@ -9,6 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
9import { ScreenService } from '@app/shared/misc/screen.service' 9import { ScreenService } from '@app/shared/misc/screen.service'
10import { Notifier, ServerService } from '@app/core' 10import { Notifier, ServerService } from '@app/core'
11import { HooksService } from '@app/core/plugins/hooks.service' 11import { HooksService } from '@app/core/plugins/hooks.service'
12import { UserService } from '@app/shared'
13import { LocalStorageService } from '@app/shared/misc/storage.service'
12 14
13@Component({ 15@Component({
14 selector: 'my-videos-recently-added', 16 selector: 'my-videos-recently-added',
@@ -29,7 +31,9 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
29 protected router: Router, 31 protected router: Router,
30 protected notifier: Notifier, 32 protected notifier: Notifier,
31 protected authService: AuthService, 33 protected authService: AuthService,
34 protected userService: UserService,
32 protected screenService: ScreenService, 35 protected screenService: ScreenService,
36 protected storageService: LocalStorageService,
33 private videoService: VideoService, 37 private videoService: VideoService,
34 private hooks: HooksService 38 private hooks: HooksService
35 ) { 39 ) {
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts
index e29830b5b..fbe052277 100644
--- a/client/src/app/videos/video-list/video-trending.component.ts
+++ b/client/src/app/videos/video-list/video-trending.component.ts
@@ -9,6 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
9import { ScreenService } from '@app/shared/misc/screen.service' 9import { ScreenService } from '@app/shared/misc/screen.service'
10import { Notifier, ServerService } from '@app/core' 10import { Notifier, ServerService } from '@app/core'
11import { HooksService } from '@app/core/plugins/hooks.service' 11import { HooksService } from '@app/core/plugins/hooks.service'
12import { UserService } from '@app/shared'
13import { LocalStorageService } from '@app/shared/misc/storage.service'
12 14
13@Component({ 15@Component({
14 selector: 'my-videos-trending', 16 selector: 'my-videos-trending',
@@ -28,7 +30,9 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
28 protected route: ActivatedRoute, 30 protected route: ActivatedRoute,
29 protected notifier: Notifier, 31 protected notifier: Notifier,
30 protected authService: AuthService, 32 protected authService: AuthService,
33 protected userService: UserService,
31 protected screenService: ScreenService, 34 protected screenService: ScreenService,
35 protected storageService: LocalStorageService,
32 private videoService: VideoService, 36 private videoService: VideoService,
33 private hooks: HooksService 37 private hooks: HooksService
34 ) { 38 ) {
diff --git a/client/src/app/videos/video-list/video-user-subscriptions.component.ts b/client/src/app/videos/video-list/video-user-subscriptions.component.ts
index cf0b15054..036fd8dcb 100644
--- a/client/src/app/videos/video-list/video-user-subscriptions.component.ts
+++ b/client/src/app/videos/video-list/video-user-subscriptions.component.ts
@@ -10,6 +10,8 @@ import { ScreenService } from '@app/shared/misc/screen.service'
10import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' 10import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
11import { Notifier, ServerService } from '@app/core' 11import { Notifier, ServerService } from '@app/core'
12import { HooksService } from '@app/core/plugins/hooks.service' 12import { HooksService } from '@app/core/plugins/hooks.service'
13import { UserService } from '@app/shared'
14import { LocalStorageService } from '@app/shared/misc/storage.service'
13 15
14@Component({ 16@Component({
15 selector: 'my-videos-user-subscriptions', 17 selector: 'my-videos-user-subscriptions',
@@ -29,7 +31,9 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
29 protected route: ActivatedRoute, 31 protected route: ActivatedRoute,
30 protected notifier: Notifier, 32 protected notifier: Notifier,
31 protected authService: AuthService, 33 protected authService: AuthService,
34 protected userService: UserService,
32 protected screenService: ScreenService, 35 protected screenService: ScreenService,
36 protected storageService: LocalStorageService,
33 private videoService: VideoService, 37 private videoService: VideoService,
34 private hooks: HooksService 38 private hooks: HooksService
35 ) { 39 ) {
diff --git a/client/src/assets/images/global/video-lang.svg b/client/src/assets/images/global/video-lang.svg
new file mode 100644
index 000000000..8d7b6a016
--- /dev/null
+++ b/client/src/assets/images/global/video-lang.svg
@@ -0,0 +1,15 @@
1<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" style="transform:scale(1.1);">
2 <g class="layer" style="transform: scale(.9);">
3 <g fill="none" fill-rule="evenodd">
4 <g transform="translate(-884.000000, -863.000000)">
5 <g transform="translate(884.000000, 863.000000)">
6 <path d="m22.78031,7.45167c0,0 -0.21495,-1.56763 -0.87461,-2.25797c-0.83658,-0.90605 -1.7743,-0.91055 -2.20433,-0.96357c-3.07858,-0.23013 -7.69661,-0.23013 -7.69661,-0.23013l-0.00956,0c0,0 -4.61792,0 -7.69661,0.23013c-0.43005,0.05302 -1.36743,0.05752 -2.20431,0.96357c-0.65962,0.69034 -0.87427,2.25797 -0.87427,2.25797c0,0 -0.22001,1.84092 -0.22001,3.68182l0,1.72585c0,1.84087 0.22001,3.68177 0.22001,3.68177c0,0 0.21465,1.56766 0.87427,2.25799c0.83688,0.90608 1.93618,0.87741 2.42581,0.97238c1.76002,0.17451 7.47991,0.22852 7.47991,0.22852c0,0 4.62279,-0.00719 7.70137,-0.2373c0.43003,-0.05305 1.36775,-0.05752 2.20433,-0.9636c0.65966,-0.69033 0.87461,-2.25799 0.87461,-2.25799c0,0 0.21969,-1.8409 0.21969,-3.68177l0,-1.72585c0,-1.8409 -0.21969,-3.68182 -0.21969,-3.68182l0,0z" fill="#ffffff" stroke="#000000" stroke-width="2"/>
7 </g>
8 </g>
9 </g>
10 <g>
11 <path d="m9.639451,16.289861a0.758829,0.758829 0 0 1 -0.537251,-1.296079l3.226539,-3.226539l-2.689289,0a0.758829,0.758829 0 0 1 0,-1.517657l4.521101,0a0.758829,0.758829 0 0 1 0.537251,1.296079l-4.522619,4.521101a0.758829,0.758829 0 0 1 -0.535733,0.223096z" fill="#000000" stroke="#000000" stroke-width="0"/>
12 <path d="m13.029897,9.507451l-2.208191,0a0.758829,0.758829 0 1 1 0,-1.517657l2.21578,0a0.758829,0.758829 0 0 1 0,1.517657l-0.007588,0z" fill="#000000" stroke="#000000" stroke-width="0"/>
13 </g>
14 </g>
15</svg>
diff --git a/client/src/assets/images/menu/about.svg b/client/src/assets/images/menu/about.svg
deleted file mode 100644
index bea602aac..000000000
--- a/client/src/assets/images/menu/about.svg
+++ /dev/null
@@ -1,11 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
4 <g id="Artboard-4" transform="translate(-400.000000, -247.000000)">
5 <g id="69" transform="translate(400.000000, 247.000000)">
6 <circle id="Oval-7" stroke="#000000" stroke-width="2" cx="12" cy="12" r="10"></circle>
7 <path d="M12.016,14.544 C12.384,14.544 12.64,14.256 12.704,13.904 L12.768,13.168 C14.544,12.864 16,11.952 16,9.936 L16,9.904 C16,7.904 14.48,6.656 12.24,6.656 C10.768,6.656 9.696,7.184 8.848,7.984 C8.624,8.176 8.528,8.432 8.528,8.672 C8.528,9.152 8.928,9.552 9.424,9.552 C9.648,9.552 9.856,9.456 10.016,9.328 C10.656,8.752 11.344,8.448 12.192,8.448 C13.344,8.448 14.032,9.072 14.032,9.968 L14.032,10 C14.032,11.008 13.2,11.584 11.696,11.728 C11.264,11.776 11.008,12.096 11.072,12.528 L11.232,13.904 C11.28,14.272 11.552,14.544 11.92,14.544 L12.016,14.544 Z M10.784,16.816 L10.784,16.976 C10.784,17.6 11.264,18.08 11.92,18.08 C12.576,18.08 13.056,17.6 13.056,16.976 L13.056,16.816 C13.056,16.192 12.576,15.712 11.92,15.712 C11.264,15.712 10.784,16.192 10.784,16.816 Z" id="?" fill="#000000"></path>
8 </g>
9 </g>
10 </g>
11</svg>
diff --git a/client/src/assets/images/menu/administration.svg b/client/src/assets/images/menu/administration.svg
deleted file mode 100644
index 0dceda082..000000000
--- a/client/src/assets/images/menu/administration.svg
+++ /dev/null
@@ -1,10 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
4 <g id="Artboard-4" transform="translate(-444.000000, -247.000000)" fill="#000000">
5 <g id="70" transform="translate(444.000000, 247.000000)">
6 <path d="M8.82929429,17 L20.0066023,17 C20.5552407,17 21,17.4438648 21,18 C21,18.5522847 20.5550537,19 20.0066023,19 L8.82929429,19 C8.41745788,20.1651924 7.30621883,21 6,21 C4.34314575,21 3,19.6568542 3,18 C3,16.3431458 4.34314575,15 6,15 C7.30621883,15 8.41745788,15.8348076 8.82929429,17 Z M9.17070571,13 L3.99339768,13 C3.44475929,13 3,12.5561352 3,12 C3,11.4477153 3.44494629,11 3.99339768,11 L9.17070571,11 C9.58254212,9.83480763 10.6937812,9 12,9 C13.3062188,9 14.4174579,9.83480763 14.8292943,11 L20.0066023,11 C20.5552407,11 21,11.4438648 21,12 C21,12.5522847 20.5550537,13 20.0066023,13 L14.8292943,13 C14.4174579,14.1651924 13.3062188,15 12,15 C10.6937812,15 9.58254212,14.1651924 9.17070571,13 Z M15.1659641,6.98648118 C15.1124525,6.99537358 15.05751,7 15.0014977,7 L3.99850233,7 C3.44704472,7 3,6.55613518 3,6 C3,5.44771525 3.44748943,5 3.99850233,5 L15.0014977,5 C15.0575314,5 15.1124871,5.00458274 15.1660053,5.01340035 C15.5740343,3.84121344 16.6887792,3 18,3 C19.6568542,3 21,4.34314575 21,6 C21,7.65685425 19.6568542,9 18,9 C16.688735,9 15.5739592,8.15872988 15.1659641,6.98648118 Z M18,7 C18.5522847,7 19,6.55228475 19,6 C19,5.44771525 18.5522847,5 18,5 C17.4477153,5 17,5.44771525 17,6 C17,6.55228475 17.4477153,7 18,7 Z M12,13 C12.5522847,13 13,12.5522847 13,12 C13,11.4477153 12.5522847,11 12,11 C11.4477153,11 11,11.4477153 11,12 C11,12.5522847 11.4477153,13 12,13 Z M6,19 C6.55228475,19 7,18.5522847 7,18 C7,17.4477153 6.55228475,17 6,17 C5.44771525,17 5,17.4477153 5,18 C5,18.5522847 5.44771525,19 6,19 Z" id="Combined-Shape"></path>
7 </g>
8 </g>
9 </g>
10</svg>
diff --git a/client/src/assets/images/menu/eye-closed.svg b/client/src/assets/images/menu/eye-closed.svg
new file mode 100644
index 000000000..5c441e719
--- /dev/null
+++ b/client/src/assets/images/menu/eye-closed.svg
@@ -0,0 +1,17 @@
1<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2 <defs/>
3 <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
4 <g transform="translate(-796.000000, -1046.000000)" stroke="#000000" stroke-width="2">
5 <g transform="translate(48.000000, 1046.000000)">
6 <g transform="translate(760.000000, 12.000000) scale(1, -1) translate(-760.000000, -12.000000) translate(748.000000, 0.000000)">
7 <path d="M2,14 C2,14 5,7 12,7 C19,7 22,14 22,14" id="Path-80" stroke-linejoin="round"/>
8 <path d="M12,7 L12,5"/>
9 <path d="M18,8.5 L19,7"/>
10 <path d="M21,12 L22.5,11"/>
11 <path d="M1.5,12 L3,11" transform="translate(2.250000, 11.500000) scale(1, -1) translate(-2.250000, -11.500000) "/>
12 <path d="M5,8.5 L6,7" transform="translate(5.500000, 7.750000) scale(-1, 1) translate(-5.500000, -7.750000) "/>
13 </g>
14 </g>
15 </g>
16 </g>
17</svg> \ No newline at end of file
diff --git a/client/src/assets/images/menu/eye.svg b/client/src/assets/images/menu/eye.svg
new file mode 100644
index 000000000..d1c3941f1
--- /dev/null
+++ b/client/src/assets/images/menu/eye.svg
@@ -0,0 +1,15 @@
1<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2 <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
3 <g transform="translate(-268.000000, -203.000000)" stroke="#000000" stroke-width="2">
4 <g transform="translate(268.000000, 203.000000)">
5 <path d="M2,12 C2,12 5,5 12,5 C19,5 22,12 22,12 C22,12 19,19 12,19 C5,19 2,12 2,12 Z" stroke-linejoin="round"/>
6 <circle id="Oval-50" cx="12" cy="12" r="3"/>
7 <path d="M12,5 L12,3" stroke-linecap="round"/>
8 <path d="M18,6.5 L19,5" stroke-linecap="round"/>
9 <path d="M21,10 L22.5,9" stroke-linecap="round"/>
10 <path d="M1.5,10 L3,9" stroke-linecap="round" transform="translate(2.250000, 9.500000) scale(1, -1) translate(-2.250000, -9.500000) "/>
11 <path d="M5,6.5 L6,5" stroke-linecap="round" transform="translate(5.500000, 5.750000) scale(-1, 1) translate(-5.500000, -5.750000) "/>
12 </g>
13 </g>
14 </g>
15</svg> \ No newline at end of file
diff --git a/client/src/assets/images/menu/language.png b/client/src/assets/images/menu/language.png
deleted file mode 100644
index 60e6fec00..000000000
--- a/client/src/assets/images/menu/language.png
+++ /dev/null
Binary files differ
diff --git a/client/src/assets/images/menu/language.svg b/client/src/assets/images/menu/language.svg
new file mode 100644
index 000000000..0ac754c87
--- /dev/null
+++ b/client/src/assets/images/menu/language.svg
@@ -0,0 +1,10 @@
1<!-- by Aaron Jin - free for commercial use -->
2<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" style="transform:scale(1.2)">
3 <path stroke="#000000" fill="#000000" stroke-width="3" d="M92.63,155H42.09a17.8,17.8,0,0,1-17.78-17.78V29.31a5,5,0,0,1,5-5h88.32a5,5,0,0,1,4.9,6L97.53,151A5,5,0,0,1,92.63,155ZM34.31,34.31V137.22A7.79,7.79,0,0,0,42.09,145H88.56L111.49,34.31Z"/>
4 <path stroke="#000000" fill="#000000" stroke-width="3" d="M170.69,175.69H75a5,5,0,0,1-4.9-6L74.39,149a5,5,0,0,1,9.8,2l-3,14.67h84.55V62.78A7.79,7.79,0,0,0,157.91,55H113.35a5,5,0,0,1,0-10h44.56a17.8,17.8,0,0,1,17.78,17.78V170.69A5,5,0,0,1,170.69,175.69Z"/>
5 <path stroke="#000000" fill="#000000" stroke-width="3" d="M50,92h0a5,5,0,0,1-5-5l0-24.49a17.49,17.49,0,0,1,35,0V87a5,5,0,0,1-10,0V62.49a7.49,7.49,0,0,0-15,0L55,87A5,5,0,0,1,50,92Z"/>
6 <path stroke="#000000" fill="#000000" stroke-width="3" d="M75,76H50a5,5,0,0,1,0-10H75a5,5,0,0,1,0,10Z"/>
7 <path stroke="#000000" fill="#000000" stroke-width="3" d="M120.21,155a5,5,0,0,1-3.54-8.54l21.26-21.26H120.21a5,5,0,0,1,0-10H150a5,5,0,0,1,3.54,8.54l-29.8,29.79A5,5,0,0,1,120.21,155Z"/>
8 <path stroke="#000000" fill="#000000" stroke-width="3" d="M150,155a5,5,0,0,1-3.54-1.47l-14.89-14.89a5,5,0,0,1,7.07-7.07l14.9,14.89A5,5,0,0,1,150,155Z"/>
9 <path stroke="#000000" fill="#000000" stroke-width="3" d="M142.55,110.31H128a5,5,0,1,1,0-10h14.6a5,5,0,0,1,0,10Z"/>
10</svg> \ No newline at end of file
diff --git a/client/src/assets/images/menu/moonsun.svg b/client/src/assets/images/menu/moonsun.svg
deleted file mode 100644
index fe2a96396..000000000
--- a/client/src/assets/images/menu/moonsun.svg
+++ /dev/null
@@ -1 +0,0 @@
1<svg height="300px" width="300px" fill="#fff" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 100 100" x="0px" y="0px"><title>Artboard 633</title><circle cx="50" cy="6" r="4"/><circle cx="50" cy="94" r="4"/><circle cx="6" cy="50" r="4"/><circle cx="94" cy="50" r="4"/><circle cx="18" cy="18" r="4"/><circle cx="82" cy="82" r="4"/><circle cx="18" cy="82" r="4"/><circle cx="82" cy="18" r="4"/><path d="M82,50A32,32,0,1,0,50,82,32,32,0,0,0,82,50ZM50,26a23.67,23.67,0,0,1,5.87.76c4.36,9.93.57,19-4.66,24.29s-14.4,9.24-24.45,4.83A23.75,23.75,0,0,1,26,50,24,24,0,0,1,50,26Zm0,48a23.94,23.94,0,0,1-18.26-8.47,29.38,29.38,0,0,0,3.74.26,30.07,30.07,0,0,0,21.41-9.11,29.82,29.82,0,0,0,8.61-25A24,24,0,0,1,50,74Z"/></svg> \ No newline at end of file
diff --git a/client/src/assets/images/menu/p2p.svg b/client/src/assets/images/menu/p2p.svg
new file mode 100644
index 000000000..744643010
--- /dev/null
+++ b/client/src/assets/images/menu/p2p.svg
@@ -0,0 +1,11 @@
1<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2 <defs/>
3 <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
4 <g transform="translate(-752.000000, -423.000000)" fill="#000000">
5 <g transform="translate(752.000000, 423.000000)">
6 <path d="M19.1632285,17.9958742 C20.7455119,17.9132011 22,16.5984601 22,14.9914698 L22,7.0085302 C22,5.35043647 20.6598453,4 19.0049107,4 L4.99508929,4 C3.33899222,4 2,5.34829734 2,7.0085302 L2,14.9914698 C2,16.5963573 3.25552676,17.9130154 4.83678095,17.9958629 L6.5,16 L4.99508929,16 C4.4481604,16 4,15.5484013 4,14.9914698 L4,7.0085302 C4,6.4497782 4.44667411,6 4.99508929,6 L19.0049107,6 C19.5518396,6 20,6.45159872 20,7.0085302 L20,14.9914698 C20,15.5502218 19.5533259,16 19.0049107,16 L17.5,16 L19.1632285,17.9958742 Z" fill-rule="nonzero"/>
7 <polygon stroke="#000000" stroke-width="2" stroke-linejoin="round" points="12 14 17 20 7 20"/>
8 </g>
9 </g>
10 </g>
11</svg> \ No newline at end of file
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 560414e90..046368c8b 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -24,6 +24,7 @@ body {
24 // now beware node-sass requires interpolation 24 // now beware node-sass requires interpolation
25 // for css custom properties #{$var} 25 // for css custom properties #{$var}
26 --mainColor: #{$orange-color}; 26 --mainColor: #{$orange-color};
27 --mainColorLighter: #{$orange-color-lighter};
27 --mainHoverColor: #{$orange-hover-color}; 28 --mainHoverColor: #{$orange-hover-color};
28 --mainBackgroundColor: #{$bg-color}; 29 --mainBackgroundColor: #{$bg-color};
29 --mainForegroundColor: #{$fg-color}; 30 --mainForegroundColor: #{$fg-color};
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
index 6cced995e..bb7b21274 100644
--- a/client/src/sass/bootstrap.scss
+++ b/client/src/sass/bootstrap.scss
@@ -13,6 +13,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
13 flex: auto; 13 flex: auto;
14} 14}
15 15
16.c-hand {
17 cursor: pointer;
18}
19
16@keyframes spin { 20@keyframes spin {
17 from { 21 from {
18 transform: scale(1) rotate(0deg); 22 transform: scale(1) rotate(0deg);
@@ -41,6 +45,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
41 background-color: var(--mainHoverColor); 45 background-color: var(--mainHoverColor);
42 opacity: .9; 46 opacity: .9;
43 } 47 }
48
49 &::after {
50 display: none;
51 }
44 } 52 }
45 53
46 button { 54 button {
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index 4766e4490..bafc82d7d 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -72,6 +72,14 @@
72 } 72 }
73} 73}
74 74
75@mixin fill-svg-color ($color) {
76 ::ng-deep svg {
77 path {
78 fill: $color;
79 }
80 }
81}
82
75@mixin button-focus-visible-shadow($color) { 83@mixin button-focus-visible-shadow($color) {
76 &.focus-visible { 84 &.focus-visible {
77 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 4px $color; 85 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 4px $color;
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index 4ef8e17b9..91229cee0 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -14,6 +14,7 @@ $grey-foreground-color: #585858;
14$grey-foreground-hover-color: #303030; 14$grey-foreground-hover-color: #303030;
15 15
16$orange-color: #F1680D; 16$orange-color: #F1680D;
17$orange-color-lighter: rgb(233, 159, 110);
17$orange-hover-color: #F97D46; 18$orange-hover-color: #F97D46;
18 19
19$cyan-color: hsl(187, 77%, 34%); 20$cyan-color: hsl(187, 77%, 34%);
@@ -74,6 +75,7 @@ $activated-action-button-color: black;
74// to be warned of non-existing variables 75// to be warned of non-existing variables
75$variables: ( 76$variables: (
76 --mainColor: var(--mainColor), 77 --mainColor: var(--mainColor),
78 --mainColorLighter: var(--mainColorLighter),
77 --mainHoverColor: var(--mainHoverColor), 79 --mainHoverColor: var(--mainHoverColor),
78 --mainBackgroundColor: var(--mainBackgroundColor), 80 --mainBackgroundColor: var(--mainBackgroundColor),
79 --mainForegroundColor: var(--mainForegroundColor), 81 --mainForegroundColor: var(--mainForegroundColor),
@@ -112,8 +114,9 @@ $zindex: (
112 tooltip : 14000, 114 tooltip : 14000,
113 loadbar : 15000, 115 loadbar : 15000,
114 modal : 16000, 116 modal : 16000,
115 notification : 17000, 117 help-popover : 17000,
116 hotkeys : 18000 118 notification : 18000,
119 hotkeys : 19000
117); 120);
118 121
119@function z($label) { 122@function z($label) {
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss
index 4d2d6cb67..e2c453228 100644
--- a/client/src/sass/primeng-custom.scss
+++ b/client/src/sass/primeng-custom.scss
@@ -274,6 +274,15 @@ p-multiselect {
274 // left: -2px !important; 274 // left: -2px !important;
275 //} 275 //}
276 } 276 }
277
278 .ui-multiselect-panel .ui-multiselect-items .ui-multiselect-item.ui-state-highlight {
279 background-color: var(--mainColorLighter);
280 }
281
282 .ui-inputtext:enabled:focus:not(.ui-state-error) {
283 border-color: var(--mainColorLighter) !important;
284 box-shadow: none;
285 }
277} 286}
278 287
279// PrimeNG calendar tweaks 288// PrimeNG calendar tweaks
@@ -379,6 +388,24 @@ p-inputswitch {
379 .ui-inputswitch-checked .ui-inputswitch-slider { 388 .ui-inputswitch-checked .ui-inputswitch-slider {
380 background-color: var(--mainColor) !important; 389 background-color: var(--mainColor) !important;
381 } 390 }
391
392 &.small {
393 height: 20px;
394
395 .ui-inputswitch {
396 width: 2.5em !important;
397 height: 1.45em !important;
398
399 .ui-inputswitch-slider::before {
400 height: 1em !important;
401 width: 1em !important;
402 }
403 }
404
405 .ui-inputswitch-checked .ui-inputswitch-slider::before {
406 transform: translateX(1em) !important;
407 }
408 }
382} 409}
383 410
384p-toast { 411p-toast {