diff options
Diffstat (limited to 'client/src/app')
44 files changed, 792 insertions, 229 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index b712d0044..043f63354 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -96,7 +96,7 @@ | |||
96 | </div> | 96 | </div> |
97 | 97 | ||
98 | <div class="col"> | 98 | <div class="col"> |
99 | <div i18n class="middle-title"> | 99 | <div id="statistics" i18n class="middle-title"> |
100 | STATISTICS | 100 | STATISTICS |
101 | </div> | 101 | </div> |
102 | <my-instance-statistics></my-instance-statistics> | 102 | <my-instance-statistics></my-instance-statistics> |
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index ac4477c18..41b27b541 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts | |||
@@ -12,6 +12,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
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 | ) { |