diff options
59 files changed, 911 insertions, 254 deletions
diff --git a/CREDITS.md b/CREDITS.md index 4276668f9..3e69299e9 100644 --- a/CREDITS.md +++ b/CREDITS.md | |||
@@ -327,4 +327,5 @@ | |||
327 | 327 | ||
328 | * [Robbie Pearce](https://robbiepearce.com/softies/) | 328 | * [Robbie Pearce](https://robbiepearce.com/softies/) |
329 | * [Fork-Awesome](https://github.com/ForkAwesome/Fork-Awesome) | 329 | * [Fork-Awesome](https://github.com/ForkAwesome/Fork-Awesome) |
330 | * playlist add by Material UI | 330 | * `playlist add` by Material UI |
331 | * `language` by Aaron Jin | ||
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' | |||
12 | import { Subscription } from 'rxjs' | 12 | import { Subscription } from 'rxjs' |
13 | import { ScreenService } from '@app/shared/misc/screen.service' | 13 | import { ScreenService } from '@app/shared/misc/screen.service' |
14 | import { Notifier, ServerService } from '@app/core' | 14 | import { Notifier, ServerService } from '@app/core' |
15 | import { UserService } from '@app/shared' | ||
16 | import { 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' | |||
11 | import { UserHistoryService } from '@app/shared/users/user-history.service' | 11 | import { UserHistoryService } from '@app/shared/users/user-history.service' |
12 | import { UserService } from '@app/shared' | 12 | import { UserService } from '@app/shared' |
13 | import { Notifier, ServerService } from '@app/core' | 13 | import { Notifier, ServerService } from '@app/core' |
14 | import { 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 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit, OnDestroy } from '@angular/core' |
2 | import { Notifier, ServerService } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { ServerConfig, UserUpdateMe } from '../../../../../../shared' | 3 | import { ServerConfig, UserUpdateMe } from '../../../../../../shared' |
4 | import { AuthService } from '../../../core' | 4 | import { AuthService } from '../../../core' |
5 | import { FormReactive, User, UserService } from '../../../shared' | 5 | import { FormReactive } from '../../../shared/forms/form-reactive' |
6 | import { User, UserService } from '../../../shared/users' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | import { Subject } from 'rxjs' | 9 | import { 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 | }) |
15 | export class MyAccountInterfaceSettingsComponent extends FormReactive implements OnInit { | 16 | export 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 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit, OnDestroy } from '@angular/core' |
2 | import { Notifier, ServerService } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { UserUpdateMe } from '../../../../../../shared' | 3 | import { UserUpdateMe } from '../../../../../../shared/models/users' |
4 | import { User, UserService } from '@app/shared/users' | ||
4 | import { AuthService } from '../../../core' | 5 | import { AuthService } from '../../../core' |
5 | import { FormReactive, User, UserService } from '../../../shared' | 6 | import { FormReactive } from '@app/shared/forms/form-reactive' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | import { forkJoin, Subject } from 'rxjs' | 9 | import { forkJoin, Subject, Subscription } from 'rxjs' |
9 | import { SelectItem } from 'primeng/api' | 10 | import { SelectItem } from 'primeng/api' |
10 | import { first } from 'rxjs/operators' | 11 | import { first } from 'rxjs/operators' |
12 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | ||
13 | import { 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 | }) |
17 | export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit { | 20 | export 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' | |||
5 | import { SharedModule } from '../shared' | 5 | import { SharedModule } from '../shared' |
6 | import { MyAccountRoutingModule } from './my-account-routing.module' | 6 | import { MyAccountRoutingModule } from './my-account-routing.module' |
7 | import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' | 7 | import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' |
8 | import { MyAccountVideoSettingsComponent } from './my-account-settings/my-account-video-settings/my-account-video-settings.component' | ||
9 | import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' | 8 | import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' |
10 | import { MyAccountComponent } from './my-account.component' | 9 | import { MyAccountComponent } from './my-account.component' |
11 | import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component' | 10 | import { 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' |
38 | import { DragDropModule } from '@angular/cdk/drag-drop' | 37 | import { DragDropModule } from '@angular/cdk/drag-drop' |
39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' | 38 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' |
40 | import { 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' | |||
12 | import { Subscription } from 'rxjs' | 12 | import { Subscription } from 'rxjs' |
13 | import { ScreenService } from '@app/shared/misc/screen.service' | 13 | import { ScreenService } from '@app/shared/misc/screen.service' |
14 | import { Notifier, ServerService } from '@app/core' | 14 | import { Notifier, ServerService } from '@app/core' |
15 | import { UserService } from '@app/shared' | ||
16 | import { 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> - | ||
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' | |||
19 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | 19 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' |
20 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '@shared/models' | 20 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '@shared/models' |
21 | import { APP_BASE_HREF } from '@angular/common' | 21 | import { APP_BASE_HREF } from '@angular/common' |
22 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' | ||
22 | 23 | ||
23 | export function metaFactory (serverService: ServerService): MetaLoader { | 24 | export 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 | ||
69 | export class AuthUser extends User implements ServerMyUserModel { | 69 | export 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' | |||
4 | import { environment } from '../../../environments/environment' | 4 | import { environment } from '../../../environments/environment' |
5 | import { PluginService } from '@app/core/plugins/plugin.service' | 5 | import { PluginService } from '@app/core/plugins/plugin.service' |
6 | import { ServerConfig, ServerConfigTheme } from '@shared/models' | 6 | import { ServerConfig, ServerConfigTheme } from '@shared/models' |
7 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' | ||
8 | import { first } from 'rxjs/operators' | 7 | import { first } from 'rxjs/operators' |
8 | import { User } from '@app/shared/users/user.model' | ||
9 | import { UserService } from '@app/shared/users/user.service' | ||
10 | import { LocalStorageService } from '@app/shared/misc/storage.service' | ||
11 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' | ||
9 | 12 | ||
10 | @Injectable() | 13 | @Injectable() |
11 | export class ThemeService { | 14 | export 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 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, ViewChild, Inject, LOCALE_ID } from '@angular/core' |
2 | import { I18N_LOCALES } from '../../../../shared' | 2 | import { I18N_LOCALES } from '../../../../shared' |
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { sortBy } from '@app/shared/misc/utils' | 4 | import { sortBy } from '@app/shared/misc/utils' |
5 | import { getCompleteLocale } from '@shared/models/i18n' | ||
6 | import { isOnDevLocale, getDevLocale } from '@app/shared/i18n/i18n-utils' | ||
5 | 7 | ||
6 | @Component({ | 8 | @Component({ |
7 | selector: 'my-language-chooser', | 9 | selector: 'my-language-chooser', |
@@ -13,7 +15,10 @@ export class LanguageChooserComponent { | |||
13 | 15 | ||
14 | languages: { id: string, label: string }[] = [] | 16 | languages: { id: string, label: string }[] = [] |
15 | 17 | ||
16 | constructor (private modalService: NgbModal) { | 18 | constructor ( |
19 | private modalService: NgbModal, | ||
20 | @Inject(LOCALE_ID) private localeId: string | ||
21 | ) { | ||
17 | const l = Object.keys(I18N_LOCALES) | 22 | const l = Object.keys(I18N_LOCALES) |
18 | .map(k => ({ id: k, label: I18N_LOCALES[k] })) | 23 | .map(k => ({ id: k, label: I18N_LOCALES[k] })) |
19 | 24 | ||
@@ -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)">©</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 | |||
299 | input[type=checkbox]{ | ||
300 | position: absolute; | ||
301 | visibility: hidden; | ||
302 | } | ||
303 | |||
304 | label { | ||
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 | |||
332 | input: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 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { UserRight } from '../../../../shared/models/users/user-right.enum' | 2 | import { UserRight } from '../../../../shared/models/users/user-right.enum' |
3 | import { AuthService, AuthStatus, RedirectService, ServerService, ThemeService } from '../core' | 3 | import { AuthService, AuthStatus, RedirectService, ServerService } from '../core' |
4 | import { User } from '../shared/users/user.model' | 4 | import { User } from '@app/shared/users/user.model' |
5 | import { UserService } from '@app/shared/users/user.service' | ||
5 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | 6 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' |
6 | import { HotkeysService } from 'angular2-hotkeys' | 7 | import { HotkeysService } from 'angular2-hotkeys' |
7 | import { ServerConfig } from '@shared/models' | 8 | import { ServerConfig, VideoConstant } from '@shared/models' |
9 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
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 | }) |
14 | export class MenuComponent implements OnInit { | 17 | export 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 @@ | |||
1 | import { Component, ViewChild, OnInit } from '@angular/core' | ||
2 | import { AuthService, AuthStatus } from '@app/core' | ||
3 | import { FormReactive, FormValidatorService, UserService, User } from '@app/shared' | ||
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | ||
6 | import { ReplaySubject } from 'rxjs' | ||
7 | import { LocalStorageService } from '@app/shared/misc/storage.service' | ||
8 | import { 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 | }) | ||
15 | export 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 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Observable, Subject } from 'rxjs' | ||
3 | import { | ||
4 | peertubeLocalStorage, | ||
5 | peertubeSessionStorage | ||
6 | } from './peertube-web-storage' | ||
7 | import { filter } from 'rxjs/operators' | ||
8 | |||
9 | abstract 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() | ||
33 | export class LocalStorageService extends StorageService { | ||
34 | protected instance: Storage = peertubeLocalStorage | ||
35 | } | ||
36 | |||
37 | @Injectable() | ||
38 | export 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 { | |||
47 | import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' | 47 | import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' |
48 | import { InputMaskModule } from 'primeng/inputmask' | 48 | import { InputMaskModule } from 'primeng/inputmask' |
49 | import { ScreenService } from '@app/shared/misc/screen.service' | 49 | import { ScreenService } from '@app/shared/misc/screen.service' |
50 | import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service' | ||
50 | import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' | 51 | import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' |
51 | import { VideoCaptionService } from '@app/shared/video-caption' | 52 | import { VideoCaptionService } from '@app/shared/video-caption' |
52 | import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' | 53 | import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' |
@@ -101,6 +102,10 @@ import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.co | |||
101 | import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' | 102 | import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' |
102 | import { RedundancyService } from '@app/shared/video/redundancy.service' | 103 | import { RedundancyService } from '@app/shared/video/redundancy.service' |
103 | import { ClipboardModule } from '@angular/cdk/clipboard' | 104 | import { ClipboardModule } from '@angular/cdk/clipboard' |
105 | import { InputSwitchModule } from 'primeng/inputswitch' | ||
106 | |||
107 | import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' | ||
108 | import { 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 @@ | |||
1 | import { hasUserRight, User as UserServerModel, UserNotificationSetting, UserRight, UserRole, VideoChannel } from '../../../../../shared' | 1 | import { |
2 | hasUserRight, | ||
3 | User as UserServerModel, | ||
4 | UserNotificationSetting, | ||
5 | UserRight, | ||
6 | UserRole | ||
7 | } from '../../../../../shared/models/users' | ||
8 | import { VideoChannel } from '../../../../../shared/models/videos' | ||
2 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' | 9 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' |
3 | import { Account } from '@app/shared/account/account.model' | 10 | import { Account } from '@app/shared/account/account.model' |
4 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | 11 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' |
5 | import { UserAdminFlag } from '@shared/models/users/user-flag.model' | 12 | import { UserAdminFlag } from '@shared/models/users/user-flag.model' |
6 | 13 | ||
7 | export class User implements UserServerModel { | 14 | export 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 @@ | |||
1 | import { from, Observable, of } from 'rxjs' | 1 | import { from, Observable } from 'rxjs' |
2 | import { catchError, concatMap, map, share, shareReplay, tap, toArray } from 'rxjs/operators' | 2 | import { catchError, concatMap, map, shareReplay, toArray } from 'rxjs/operators' |
3 | import { HttpClient, HttpParams } from '@angular/common/http' | 3 | import { HttpClient, HttpParams } from '@angular/common/http' |
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' | 5 | import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' |
6 | import { environment } from '../../../environments/environment' | 6 | import { environment } from '../../../environments/environment' |
7 | import { RestExtractor, RestPagination, RestService } from '../rest' | 7 | import { RestExtractor, RestPagination, RestService } from '../rest' |
8 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | 8 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' |
@@ -10,6 +10,10 @@ import { SortMeta } from 'primeng/api' | |||
10 | import { BytesPipe } from 'ngx-pipes' | 10 | import { BytesPipe } from 'ngx-pipes' |
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 11 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { UserRegister } from '@shared/models/users/user-register.model' | 12 | import { UserRegister } from '@shared/models/users/user-register.model' |
13 | import { User } from './user.model' | ||
14 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | ||
15 | import { has } from 'lodash-es' | ||
16 | import { LocalStorageService, SessionStorageService } from '../misc/storage.service' | ||
13 | 17 | ||
14 | @Injectable() | 18 | @Injectable() |
15 | export class UserService { | 19 | export 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 @@ | |||
1 | import { debounceTime, first, tap } from 'rxjs/operators' | 1 | import { debounceTime, first, tap, throttleTime } from 'rxjs/operators' |
2 | import { OnDestroy, OnInit } from '@angular/core' | 2 | import { OnDestroy, OnInit } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs' | 4 | import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs' |
@@ -15,6 +15,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
15 | import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' | 15 | import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' |
16 | import { ServerConfig } from '@shared/models' | 16 | import { ServerConfig } from '@shared/models' |
17 | import { GlobalIconName } from '@app/shared/images/global-icon.component' | 17 | import { GlobalIconName } from '@app/shared/images/global-icon.component' |
18 | import { UserService, User } from '../users' | ||
19 | import { LocalStorageService } from '../misc/storage.service' | ||
18 | 20 | ||
19 | enum GroupDate { | 21 | enum 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' | |||
27 | import { Account } from '@app/shared/account/account.model' | 27 | import { Account } from '@app/shared/account/account.model' |
28 | import { AccountService } from '@app/shared/account/account.service' | 28 | import { AccountService } from '@app/shared/account/account.service' |
29 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 29 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
30 | import { ServerService } from '@app/core' | 30 | import { ServerService, AuthService } from '@app/core' |
31 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' | 31 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' |
32 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 32 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
33 | import { I18n } from '@ngx-translate/i18n-polyfill' | 33 | import { I18n } from '@ngx-translate/i18n-polyfill' |
34 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | ||
34 | 35 | ||
35 | export interface VideosProvider { | 36 | export 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' | |||
22 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | 22 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' |
23 | import { I18n } from '@ngx-translate/i18n-polyfill' | 23 | import { I18n } from '@ngx-translate/i18n-polyfill' |
24 | import { ResultList } from '@shared/models' | 24 | import { ResultList } from '@shared/models' |
25 | import { UserService } from '../users' | ||
26 | import { LocalStorageService } from '../misc/storage.service' | ||
25 | 27 | ||
26 | export type SelectionType = { [ id: number ]: boolean } | 28 | export 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. | |||
9 | import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' | 9 | import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' |
10 | import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' | 10 | import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' |
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 11 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { 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' | |||
2 | import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' | 2 | import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { RedirectService } from '@app/core/routing/redirect.service' | 4 | import { RedirectService } from '@app/core/routing/redirect.service' |
5 | import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' | 5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' |
6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
7 | import { MetaService } from '@ngx-meta/core' | 7 | import { MetaService } from '@ngx-meta/core' |
8 | import { AuthUser, Notifier, ServerService } from '@app/core' | 8 | import { AuthUser, Notifier, ServerService } from '@app/core' |
@@ -10,7 +10,7 @@ import { forkJoin, Observable, Subscription } from 'rxjs' | |||
10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
11 | import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' | 11 | import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' |
12 | import { AuthService, ConfirmService } from '../../core' | 12 | import { AuthService, ConfirmService } from '../../core' |
13 | import { RestExtractor } from '../../shared' | 13 | import { RestExtractor, UserService } from '../../shared' |
14 | import { VideoDetails } from '../../shared/video/video-details.model' | 14 | import { VideoDetails } from '../../shared/video/video-details.model' |
15 | import { VideoService } from '../../shared/video/video.service' | 15 | import { VideoService } from '../../shared/video/video.service' |
16 | import { VideoShareComponent } from './modal/video-share.component' | 16 | import { VideoShareComponent } from './modal/video-share.component' |
@@ -35,7 +35,6 @@ import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watc | |||
35 | import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' | 35 | import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' |
36 | import { HooksService } from '@app/core/plugins/hooks.service' | 36 | import { HooksService } from '@app/core/plugins/hooks.service' |
37 | import { PlatformLocation } from '@angular/common' | 37 | import { PlatformLocation } from '@angular/common' |
38 | import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component' | ||
39 | import { scrollToTop, isXPercentInViewport } from '@app/shared/misc/utils' | 38 | import { 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' | |||
12 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' | 12 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' |
13 | import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' | 13 | import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' |
14 | import { QRCodeModule } from 'angularx-qrcode' | 14 | import { QRCodeModule } from 'angularx-qrcode' |
15 | import { InputSwitchModule } from 'primeng/inputswitch' | ||
16 | import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestamp-route-transformer.directive' | 15 | import { 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- | |||
7 | import { User } from '@app/shared' | 7 | import { User } from '@app/shared' |
8 | import { AuthService, Notifier } from '@app/core' | 8 | import { AuthService, Notifier } from '@app/core' |
9 | import { UserService } from '@app/shared/users/user.service' | 9 | import { UserService } from '@app/shared/users/user.service' |
10 | import { peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' | ||
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { 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 | }) |
18 | export class RecommendedVideosComponent implements OnChanges { | 18 | export 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' | |||
11 | import { UserRight } from '../../../../../shared/models/users' | 11 | import { UserRight } from '../../../../../shared/models/users' |
12 | import { Notifier, ServerService } from '@app/core' | 12 | import { Notifier, ServerService } from '@app/core' |
13 | import { HooksService } from '@app/core/plugins/hooks.service' | 13 | import { HooksService } from '@app/core/plugins/hooks.service' |
14 | import { UserService } from '@app/shared' | ||
15 | import { 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' | |||
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | 11 | import { HooksService } from '@app/core/plugins/hooks.service' |
12 | import { UserService } from '@app/shared' | ||
13 | import { 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' | |||
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | 11 | import { HooksService } from '@app/core/plugins/hooks.service' |
12 | import { UserService } from '@app/shared' | ||
13 | import { 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' | |||
9 | import { ScreenService } from '@app/shared/misc/screen.service' | 9 | import { ScreenService } from '@app/shared/misc/screen.service' |
10 | import { Notifier, ServerService } from '@app/core' | 10 | import { Notifier, ServerService } from '@app/core' |
11 | import { HooksService } from '@app/core/plugins/hooks.service' | 11 | import { HooksService } from '@app/core/plugins/hooks.service' |
12 | import { UserService } from '@app/shared' | ||
13 | import { 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' | |||
10 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | 10 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' |
11 | import { Notifier, ServerService } from '@app/core' | 11 | import { Notifier, ServerService } from '@app/core' |
12 | import { HooksService } from '@app/core/plugins/hooks.service' | 12 | import { HooksService } from '@app/core/plugins/hooks.service' |
13 | import { UserService } from '@app/shared' | ||
14 | import { 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 | ||
384 | p-toast { | 411 | p-toast { |