From d3217560a611b94f888ecf3de93b428a7521d4de Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Fri, 28 Feb 2020 13:52:21 +0100 Subject: [PATCH] Add visitor settings, rework logged-in dropdown (#2514) * Add visitor settings, rework logged-in dropdown * Make user dropdown P2P switch functional * Fix lint * Fix unnecessary notification when user logs out * Simplify visitor settings code and remove unnecessary icons * Catch parsing errors and reindent menu styles --- CREDITS.md | 3 +- .../about-instance.component.html | 2 +- .../account-videos.component.ts | 4 + .../my-account-history.component.ts | 2 + ...-account-interface-settings.component.html | 2 +- ...my-account-interface-settings.component.ts | 40 +++- .../my-account-video-settings.component.html | 4 +- .../my-account-video-settings.component.ts | 64 ++++-- .../app/+my-account/my-account.component.html | 2 +- .../src/app/+my-account/my-account.module.ts | 4 - .../video-channel-videos.component.ts | 4 + client/src/app/app.component.html | 5 - client/src/app/app.module.ts | 2 + client/src/app/core/auth/auth-user.model.ts | 15 -- client/src/app/core/theme/theme.service.ts | 31 +-- .../app/menu/language-chooser.component.scss | 2 + .../app/menu/language-chooser.component.ts | 15 +- client/src/app/menu/menu.component.html | 99 +++++++-- client/src/app/menu/menu.component.scss | 200 +++++++++++++----- client/src/app/menu/menu.component.ts | 62 +++++- .../modal/quick-settings-modal.component.html | 19 ++ .../modal/quick-settings-modal.component.scss | 39 ++++ .../modal/quick-settings-modal.component.ts | 62 ++++++ .../shared/images/global-icon.component.ts | 7 +- .../src/app/shared/misc/help.component.scss | 1 + client/src/app/shared/misc/storage.service.ts | 40 ++++ client/src/app/shared/shared.module.ts | 19 +- client/src/app/shared/users/user.model.ts | 29 ++- client/src/app/shared/users/user.service.ts | 77 +++++-- .../app/shared/video/abstract-video-list.ts | 23 +- client/src/app/shared/video/video.service.ts | 23 +- .../video/videos-selection.component.ts | 4 + .../video-watch-playlist.component.ts | 7 +- .../+video-watch/video-watch.component.html | 15 +- .../+video-watch/video-watch.component.scss | 10 +- .../+video-watch/video-watch.component.ts | 19 +- .../videos/+video-watch/video-watch.module.ts | 4 +- .../recommended-videos.component.html | 2 +- .../recommended-videos.component.scss | 22 -- .../recommended-videos.component.ts | 20 +- .../video-list/video-local.component.ts | 4 + .../video-list/video-most-liked.component.ts | 4 + .../video-recently-added.component.ts | 4 + .../video-list/video-trending.component.ts | 4 + .../video-user-subscriptions.component.ts | 4 + .../src/assets/images/global/video-lang.svg | 15 ++ client/src/assets/images/menu/about.svg | 11 - .../src/assets/images/menu/administration.svg | 10 - client/src/assets/images/menu/eye-closed.svg | 17 ++ client/src/assets/images/menu/eye.svg | 15 ++ client/src/assets/images/menu/language.png | Bin 10937 -> 0 bytes client/src/assets/images/menu/language.svg | 10 + client/src/assets/images/menu/moonsun.svg | 1 - client/src/assets/images/menu/p2p.svg | 11 + client/src/sass/application.scss | 1 + client/src/sass/bootstrap.scss | 8 + client/src/sass/include/_mixins.scss | 8 + client/src/sass/include/_variables.scss | 7 +- client/src/sass/primeng-custom.scss | 27 +++ 59 files changed, 911 insertions(+), 254 deletions(-) create mode 100644 client/src/app/modal/quick-settings-modal.component.html create mode 100644 client/src/app/modal/quick-settings-modal.component.scss create mode 100644 client/src/app/modal/quick-settings-modal.component.ts create mode 100644 client/src/app/shared/misc/storage.service.ts create mode 100644 client/src/assets/images/global/video-lang.svg delete mode 100644 client/src/assets/images/menu/about.svg delete mode 100644 client/src/assets/images/menu/administration.svg create mode 100644 client/src/assets/images/menu/eye-closed.svg create mode 100644 client/src/assets/images/menu/eye.svg delete mode 100644 client/src/assets/images/menu/language.png create mode 100644 client/src/assets/images/menu/language.svg delete mode 100644 client/src/assets/images/menu/moonsun.svg create mode 100644 client/src/assets/images/menu/p2p.svg diff --git a/CREDITS.md b/CREDITS.md index 4276668f9..3e69299e9 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -327,4 +327,5 @@ * [Robbie Pearce](https://robbiepearce.com/softies/) * [Fork-Awesome](https://github.com/ForkAwesome/Fork-Awesome) - * playlist add by Material UI + * `playlist add` by Material UI + * `language` by Aaron Jin diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index b712d0044..043f63354 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html @@ -96,7 +96,7 @@
-
+
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' import { Subscription } from 'rxjs' import { ScreenService } from '@app/shared/misc/screen.service' import { Notifier, ServerService } from '@app/core' +import { UserService } from '@app/shared' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-account-videos', @@ -34,9 +36,11 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, protected serverService: ServerService, protected route: ActivatedRoute, protected authService: AuthService, + protected userService: UserService, protected notifier: Notifier, protected confirmService: ConfirmService, protected screenService: ScreenService, + protected storageService: LocalStorageService, private accountService: AccountService, private videoService: VideoService ) { 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' import { UserHistoryService } from '@app/shared/users/user-history.service' import { UserService } from '@app/shared' import { Notifier, ServerService } from '@app/core' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-account-history', @@ -35,6 +36,7 @@ export class MyAccountHistoryComponent extends AbstractVideoList implements OnIn protected userService: UserService, protected notifier: Notifier, protected screenService: ScreenService, + protected storageService: LocalStorageService, private confirmService: ConfirmService, private videoService: VideoService, 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 @@
- + 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 @@ -import { Component, Input, OnInit } from '@angular/core' +import { Component, Input, OnInit, OnDestroy } from '@angular/core' import { Notifier, ServerService } from '@app/core' import { ServerConfig, UserUpdateMe } from '../../../../../../shared' import { AuthService } from '../../../core' -import { FormReactive, User, UserService } from '../../../shared' +import { FormReactive } from '../../../shared/forms/form-reactive' +import { User, UserService } from '../../../shared/users' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' -import { Subject } from 'rxjs' +import { Subject, Subscription } from 'rxjs' @Component({ selector: 'my-account-interface-settings', templateUrl: './my-account-interface-settings.component.html', styleUrls: [ './my-account-interface-settings.component.scss' ] }) -export class MyAccountInterfaceSettingsComponent extends FormReactive implements OnInit { +export class MyAccountInterfaceSettingsComponent extends FormReactive implements OnInit, OnDestroy { @Input() user: User = null + @Input() reactiveUpdate = false + @Input() notifyOnUpdate = true @Input() userInformationLoaded: Subject + formValuesWatcher: Subscription + private serverConfig: ServerConfig constructor ( @@ -48,9 +53,17 @@ export class MyAccountInterfaceSettingsComponent extends FormReactive implements this.form.patchValue({ theme: this.user.theme }) + + if (this.reactiveUpdate) { + this.formValuesWatcher = this.form.valueChanges.subscribe(val => this.updateInterfaceSettings()) + } }) } + ngOnDestroy () { + this.formValuesWatcher?.unsubscribe() + } + updateInterfaceSettings () { const theme = this.form.value['theme'] @@ -58,14 +71,19 @@ export class MyAccountInterfaceSettingsComponent extends FormReactive implements theme } - this.userService.updateMyProfile(details).subscribe( - () => { - this.authService.refreshUserInformation() + if (this.authService.isLoggedIn()) { + this.userService.updateMyProfile(details).subscribe( + () => { + this.authService.refreshUserInformation() - this.notifier.success(this.i18n('Interface settings updated.')) - }, + if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.')) + }, - err => this.notifier.error(err.message) - ) + err => this.notifier.error(err.message) + ) + } else { + this.userService.updateMyAnonymousProfile(details) + if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.')) + } } } 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 @@ + +
- + 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 @@ -import { Component, Input, OnInit } from '@angular/core' +import { Component, Input, OnInit, OnDestroy } from '@angular/core' import { Notifier, ServerService } from '@app/core' -import { UserUpdateMe } from '../../../../../../shared' +import { UserUpdateMe } from '../../../../../../shared/models/users' +import { User, UserService } from '@app/shared/users' import { AuthService } from '../../../core' -import { FormReactive, User, UserService } from '../../../shared' +import { FormReactive } from '@app/shared/forms/form-reactive' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' -import { forkJoin, Subject } from 'rxjs' +import { forkJoin, Subject, Subscription } from 'rxjs' import { SelectItem } from 'primeng/api' import { first } from 'rxjs/operators' +import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' +import { pick } from 'lodash-es' @Component({ selector: 'my-account-video-settings', templateUrl: './my-account-video-settings.component.html', styleUrls: [ './my-account-video-settings.component.scss' ] }) -export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit { +export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit, OnDestroy { @Input() user: User = null + @Input() reactiveUpdate = false + @Input() notifyOnUpdate = true @Input() userInformationLoaded: Subject languageItems: SelectItem[] = [] + defaultNSFWPolicy: NSFWPolicyType + formValuesWatcher: Subscription constructor ( protected formValidatorService: FormValidatorService, @@ -32,6 +39,8 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI } ngOnInit () { + let oldForm: any + this.buildForm({ nsfwPolicy: null, webTorrentEnabled: null, @@ -42,8 +51,9 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI forkJoin([ this.serverService.getVideoLanguages(), + this.serverService.getConfig(), this.userInformationLoaded.pipe(first()) - ]).subscribe(([ languages ]) => { + ]).subscribe(([ languages, config ]) => { this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] this.languageItems = this.languageItems .concat(languages.map(l => ({ label: l.label, value: l.id }))) @@ -52,17 +62,32 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI ? this.user.videoLanguages : this.languageItems.map(l => l.value) + this.defaultNSFWPolicy = config.instance.defaultNSFWPolicy + this.form.patchValue({ - nsfwPolicy: this.user.nsfwPolicy, + nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy, webTorrentEnabled: this.user.webTorrentEnabled, autoPlayVideo: this.user.autoPlayVideo === true, autoPlayNextVideo: this.user.autoPlayNextVideo, videoLanguages }) + + if (this.reactiveUpdate) { + oldForm = { ...this.form.value } + this.formValuesWatcher = this.form.valueChanges.subscribe((formValue: any) => { + const updatedKey = Object.keys(formValue).find(k => formValue[k] !== oldForm[k]) + oldForm = { ...this.form.value } + this.updateDetails([updatedKey]) + }) + } }) } - updateDetails () { + ngOnDestroy () { + this.formValuesWatcher?.unsubscribe() + } + + updateDetails (onlyKeys?: string[]) { const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] const webTorrentEnabled = this.form.value['webTorrentEnabled'] const autoPlayVideo = this.form.value['autoPlayVideo'] @@ -81,7 +106,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI } } - const details: UserUpdateMe = { + let details: UserUpdateMe = { nsfwPolicy, webTorrentEnabled, autoPlayVideo, @@ -89,15 +114,22 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI videoLanguages } - this.userService.updateMyProfile(details).subscribe( - () => { - this.notifier.success(this.i18n('Video settings updated.')) + if (onlyKeys) details = pick(details, onlyKeys) - this.authService.refreshUserInformation() - }, + if (this.authService.isLoggedIn()) { + this.userService.updateMyProfile(details).subscribe( + () => { + this.authService.refreshUserInformation() - err => this.notifier.error(err.message) - ) + if (this.notifyOnUpdate) this.notifier.success(this.i18n('Video settings updated.')) + }, + + err => this.notifier.error(err.message) + ) + } else { + this.userService.updateMyAnonymousProfile(details) + if (this.notifyOnUpdate) this.notifier.success(this.i18n('Display/Video settings updated.')) + } } 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 @@
-
+
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' import { SharedModule } from '../shared' import { MyAccountRoutingModule } from './my-account-routing.module' import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' -import { MyAccountVideoSettingsComponent } from './my-account-settings/my-account-video-settings/my-account-video-settings.component' import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' import { MyAccountComponent } from './my-account.component' import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component' @@ -37,7 +36,6 @@ import { } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' import { DragDropModule } from '@angular/cdk/drag-drop' import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' -import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' @NgModule({ imports: [ @@ -54,10 +52,8 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account MyAccountComponent, MyAccountSettingsComponent, MyAccountChangePasswordComponent, - MyAccountVideoSettingsComponent, MyAccountProfileComponent, MyAccountChangeEmailComponent, - MyAccountInterfaceSettingsComponent, MyAccountVideosComponent, 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' import { Subscription } from 'rxjs' import { ScreenService } from '@app/shared/misc/screen.service' import { Notifier, ServerService } from '@app/core' +import { UserService } from '@app/shared' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-video-channel-videos', @@ -34,9 +36,11 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On protected serverService: ServerService, protected route: ActivatedRoute, protected authService: AuthService, + protected userService: UserService, protected notifier: Notifier, protected confirmService: ConfirmService, protected screenService: ScreenService, + protected storageService: LocalStorageService, private videoChannelService: VideoChannelService, private videoService: VideoService ) { 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 @@
- -
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' import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '@shared/models' import { APP_BASE_HREF } from '@angular/common' +import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' export function metaFactory (serverService: ServerService): MetaLoader { return new MetaStaticLoader({ @@ -39,6 +40,7 @@ export function metaFactory (serverService: ServerService): MetaLoader { MenuComponent, LanguageChooserComponent, + QuickSettingsModalComponent, AvatarNotificationComponent, HeaderComponent, 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 { } export class AuthUser extends User implements ServerMyUserModel { - private static KEYS = { - ID: 'id', - ROLE: 'role', - EMAIL: 'email', - VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', - USERNAME: 'username', - NSFW_POLICY: 'nsfw_policy', - WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', - AUTO_PLAY_VIDEO: 'auto_play_video' - } - tokens: Tokens specialPlaylists: MyUserSpecialPlaylist[] @@ -106,10 +95,6 @@ export class AuthUser extends User implements ServerMyUserModel { peertubeLocalStorage.removeItem(this.KEYS.USERNAME) peertubeLocalStorage.removeItem(this.KEYS.ID) peertubeLocalStorage.removeItem(this.KEYS.ROLE) - peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY) - peertubeLocalStorage.removeItem(this.KEYS.WEBTORRENT_ENABLED) - peertubeLocalStorage.removeItem(this.KEYS.VIDEOS_HISTORY_ENABLED) - peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO) peertubeLocalStorage.removeItem(this.KEYS.EMAIL) Tokens.flush() } 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' import { environment } from '../../../environments/environment' import { PluginService } from '@app/core/plugins/plugin.service' import { ServerConfig, ServerConfigTheme } from '@shared/models' -import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' import { first } from 'rxjs/operators' +import { User } from '@app/shared/users/user.model' +import { UserService } from '@app/shared/users/user.service' +import { LocalStorageService } from '@app/shared/misc/storage.service' +import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' @Injectable() export class ThemeService { - private static KEYS = { - LAST_ACTIVE_THEME: 'last_active_theme' - } - private oldThemeName: string private themes: ServerConfigTheme[] = [] @@ -24,8 +23,10 @@ export class ThemeService { constructor ( private auth: AuthService, + private userService: UserService, private pluginService: PluginService, - private server: ServerService + private server: ServerService, + private localStorageService: LocalStorageService ) {} initialize () { @@ -77,11 +78,11 @@ export class ThemeService { private getCurrentTheme () { if (this.themeFromLocalStorage) return this.themeFromLocalStorage.name - if (this.auth.isLoggedIn()) { - const theme = this.auth.getUser().theme - if (theme !== 'instance-default') return theme - } + const theme = this.auth.isLoggedIn() + ? this.auth.getUser().theme + : this.userService.getAnonymousUser().theme + if (theme !== 'instance-default') return theme return this.serverConfig.theme.default } @@ -111,9 +112,9 @@ export class ThemeService { this.pluginService.reloadLoadedScopes() - peertubeLocalStorage.setItem(ThemeService.KEYS.LAST_ACTIVE_THEME, JSON.stringify(theme)) + this.localStorageService.setItem(User.KEYS.THEME, JSON.stringify(theme), false) } else { - peertubeLocalStorage.removeItem(ThemeService.KEYS.LAST_ACTIVE_THEME) + this.localStorageService.removeItem(User.KEYS.THEME, false) } this.oldThemeName = currentTheme @@ -126,6 +127,10 @@ export class ThemeService { if (!this.auth.isLoggedIn()) { this.updateCurrentTheme() + + this.localStorageService.watch([User.KEYS.THEME]).subscribe( + () => this.updateCurrentTheme() + ) } this.auth.userInformationLoaded @@ -134,7 +139,7 @@ export class ThemeService { } private loadAndSetFromLocalStorage () { - const lastActiveThemeString = peertubeLocalStorage.getItem(ThemeService.KEYS.LAST_ACTIVE_THEME) + const lastActiveThemeString = this.localStorageService.getItem(User.KEYS.THEME) if (!lastActiveThemeString) return 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 @@ .help-to-translate { @include peertube-button-link; @include orange-button; + + border-radius: 0; } .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 @@ -import { Component, ElementRef, ViewChild } from '@angular/core' +import { Component, ElementRef, ViewChild, Inject, LOCALE_ID } from '@angular/core' import { I18N_LOCALES } from '../../../../shared' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { sortBy } from '@app/shared/misc/utils' +import { getCompleteLocale } from '@shared/models/i18n' +import { isOnDevLocale, getDevLocale } from '@app/shared/i18n/i18n-utils' @Component({ selector: 'my-language-chooser', @@ -13,7 +15,10 @@ export class LanguageChooserComponent { languages: { id: string, label: string }[] = [] - constructor (private modalService: NgbModal) { + constructor ( + private modalService: NgbModal, + @Inject(LOCALE_ID) private localeId: string + ) { const l = Object.keys(I18N_LOCALES) .map(k => ({ id: k, label: I18N_LOCALES[k] })) @@ -28,4 +33,10 @@ export class LanguageChooserComponent { return window.location.origin + '/' + lang.id } + getCurrentLanguage () { + const english = 'English' + const locale = isOnDevLocale() ? getDevLocale() : getCompleteLocale(this.localeId) + if (locale) return I18N_LOCALES[locale] || english + return english + } } 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 @@
{{ user.username }}
- + - - + 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 { &.logged-in { .panel-block { - margin-bottom: 25px; + margin-bottom: 20px; } .block-title { @@ -88,22 +88,6 @@ menu { @include apply-svg-color(var(--menuForegroundColor)); } } - - .dropdown-item { - @include dropdown-with-icon-item; - - my-global-icon { - width: 22px; - height: 22px; - - &[iconName="sign-out"] { - position: relative; - right: -1px; - height: 21px; - width: 21px; - } - } - } } } @@ -143,7 +127,7 @@ menu { } .panel-block { - margin-bottom: 45px; + margin-bottom: 15px; a { @include disable-default-a-behaviour; @@ -198,60 +182,162 @@ menu { } .footer { - padding-bottom: 15px; - padding-left: $menu-lateral-padding; - padding-right: $menu-lateral-padding; width: $menu-width; + padding-bottom: 15px; - .language, .shortcuts, .color-palette { - display: inline-block; - color: $menu-bottom-color; - cursor: pointer; - font-size: 12px; - font-weight: $font-semibold; + & > div:not(.panel-block) { + padding-left: $menu-lateral-padding; + padding-right: $menu-lateral-padding; + row-gap: 1em; + } - .icon { - @include disable-outline; - @include icon(28px); - opacity: 0.9; + $footer-links-base-opacity: .8; - &.icon-language { - position: relative; - top: -1px; - width: 28px; - height: 24px; + .footer-links { + display: inline-flex; + flex-wrap: wrap; + + & > a { + @include disable-default-a-behaviour; - background-image: url('../../assets/images/menu/language.png'); + display: inline-block; + text-decoration: none; + color: var(--mainBackgroundColor); + opacity: $footer-links-base-opacity; + white-space: nowrap; + font-size: 90%; + font-weight: 500; + line-height: 1.4rem; + margin-right: 8px; + + &.inline-global-icon { + display: inline-flex; + align-items: center; + white-space: nowrap; + height: 1.4rem; + + my-global-icon { + @include apply-svg-color(var(--mainBackgroundColor)); + + display: flex; + width: auto; + height: 90%; + margin-right: .2rem; + } } + } + } - &.icon-shortcuts { - position: relative; - top: -1px; - width: 24px; - height: 24px; + .footer-copyleft small a { + @include disable-default-a-behaviour; - background-image: url('../../assets/images/menu/keyboard.png'); - filter: invert(100%); - } + color: var(--mainBackgroundColor); + opacity: $footer-links-base-opacity - .2; + } + } +} - &.icon-moonsun { - margin-left: 10px; - position: relative; - top: -1px; - width: 24px; - height: 24px; +.dropdown-menu { + width: calc(100% + 40px); +} - background-image: url('../../assets/images/menu/moonsun.svg'); - } +.dropdown-item { + @include dropdown-with-icon-item; - &:hover { - opacity: 1; - } - } + cursor: pointer; + display: flex; + align-items: center; + + i.glyphicon-menu-right { + opacity: .4; + } + + my-global-icon { + &[iconName="cog"], + &[iconName="sign-out"] { + position: relative; + right: -2px; + height: 20px; + width: 20px; + } + } + + my-global-icon.not-displayed { + display: none; + } + + &:hover { + my-global-icon.hover-display-toggle.not-displayed { + display: inherit; + } + my-global-icon.hover-display-toggle { + display: none; } } } +.more-settings { + text-transform: uppercase; + font-size: 80%; + color: #6c757d; +} + +.icon { + @include disable-outline; + @include icon(22px); + opacity: 0.8; + + &.icon-shortcuts { + position: relative; + top: -1px; + margin-right: 10px; + + background-image: url('../../assets/images/menu/keyboard.png'); + } +} + +input[type=checkbox]{ + position: absolute; + visibility: hidden; +} + +label { + cursor: pointer; + text-indent: -9999px; + width: 35px; + height: 20px; + background: #cccccc; + display: block; + border-radius: 100px; + position: relative; + margin: 0; + + &:after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 14px; + height: 14px; + background: var(--mainBackgroundColor); + border-radius: 50%; + transition: 0.3s ease-out; + } + + &:active:after { + width: 40px; + } +} + +input:checked + label { + background: var(--mainColor); + + &:after { + left: calc(100% - 3px); + transform: translateX(-100%); + } +} + @media screen and (max-width: $mobile-view) { .menu-wrapper { 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 @@ import { Component, OnInit, ViewChild } from '@angular/core' import { UserRight } from '../../../../shared/models/users/user-right.enum' -import { AuthService, AuthStatus, RedirectService, ServerService, ThemeService } from '../core' -import { User } from '../shared/users/user.model' +import { AuthService, AuthStatus, RedirectService, ServerService } from '../core' +import { User } from '@app/shared/users/user.model' +import { UserService } from '@app/shared/users/user.service' import { LanguageChooserComponent } from '@app/menu/language-chooser.component' import { HotkeysService } from 'angular2-hotkeys' -import { ServerConfig } from '@shared/models' +import { ServerConfig, VideoConstant } from '@shared/models' +import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' +import { I18n } from '@ngx-translate/i18n-polyfill' @Component({ selector: 'my-menu', @@ -13,11 +16,14 @@ import { ServerConfig } from '@shared/models' }) export class MenuComponent implements OnInit { @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent + @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent user: User isLoggedIn: boolean + userHasAdminAccess = false helpVisible = false + languages: VideoConstant[] = [] private serverConfig: ServerConfig private routesPerRight: { [ role in UserRight ]?: string } = { @@ -31,9 +37,11 @@ export class MenuComponent implements OnInit { constructor ( private authService: AuthService, + private userService: UserService, private serverService: ServerService, private redirectService: RedirectService, - private hotkeysService: HotkeysService + private hotkeysService: HotkeysService, + private i18n: I18n ) {} ngOnInit () { @@ -63,9 +71,33 @@ export class MenuComponent implements OnInit { } ) - this.hotkeysService.cheatSheetToggle.subscribe(isOpen => { - this.helpVisible = isOpen - }) + this.hotkeysService.cheatSheetToggle.subscribe(isOpen => this.helpVisible = isOpen) + + this.serverService.getVideoLanguages().subscribe(languages => this.languages = languages) + } + + get language () { + return this.languageChooserModal.getCurrentLanguage() + } + + get videoLanguages (): string[] { + if (!this.user) return + if (!this.user.videoLanguages) return [this.i18n('any language')] + return this.user.videoLanguages + .map(locale => this.langForLocale(locale)) + .map(value => value === undefined ? '?' : value) + } + + get nsfwPolicy () { + if (!this.user) return + switch (this.user.nsfwPolicy) { + case 'do_not_list': + return this.i18n('hide') + case 'blur': + return this.i18n('blur') + case 'display': + return this.i18n('display') + } } isRegistrationAllowed () { @@ -117,6 +149,22 @@ export class MenuComponent implements OnInit { this.hotkeysService.cheatSheetToggle.next(!this.helpVisible) } + openQuickSettings () { + this.quickSettingsModal.show() + } + + toggleUseP2P () { + if (!this.user) return + this.user.webTorrentEnabled = !this.user.webTorrentEnabled + this.userService.updateMyProfile({ + webTorrentEnabled: this.user.webTorrentEnabled + }).subscribe(() => this.authService.refreshUserInformation()) + } + + langForLocale(localeId: string) { + return this.languages.find(lang => lang.id = localeId).label + } + private computeIsUserHasAdminAccess () { const right = this.getFirstAdminRightAvailable() 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 @@ + + + + + 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 @@ +@import '_mixins'; + +.modal-button { + @include disable-default-a-behaviour; + transform: translateY(2px); + + button { + @include peertube-button; + @include grey-button; + @include button-with-icon(18px, 4px, -1px); + + my-global-icon { + @include apply-svg-color(#585858); + } + } + + & + .modal-button { + margin-left: 1rem; + } +} + +.icon { + @include disable-outline; + @include icon(22px); + opacity: 0.6; + margin-left: -1px; + + &.icon-shortcuts { + position: relative; + top: -1px; + margin-right: 4px; + + background-image: url('../../assets/images/menu/keyboard.png'); + } +} + +.quick-settings-title { + @include in-content-small-title; +} \ 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 @@ +import { Component, ViewChild, OnInit } from '@angular/core' +import { AuthService, AuthStatus } from '@app/core' +import { FormReactive, FormValidatorService, UserService, User } from '@app/shared' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { ReplaySubject } from 'rxjs' +import { LocalStorageService } from '@app/shared/misc/storage.service' +import { filter } from 'rxjs/operators' + +@Component({ + selector: 'my-quick-settings', + templateUrl: './quick-settings-modal.component.html', + styleUrls: [ './quick-settings-modal.component.scss' ] +}) +export class QuickSettingsModalComponent extends FormReactive implements OnInit { + @ViewChild('modal', { static: true }) modal: NgbModal + + user: User + userInformationLoaded = new ReplaySubject(1) + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private userService: UserService, + private authService: AuthService, + private localStorageService: LocalStorageService + ) { + super() + } + + ngOnInit () { + this.user = this.userService.getAnonymousUser() + this.localStorageService.watch().subscribe( + () => this.user = this.userService.getAnonymousUser() + ) + this.userInformationLoaded.next(true) + + this.authService.loginChangedSource + .pipe(filter(status => status !== AuthStatus.LoggedIn)) + .subscribe( + () => { + this.user = this.userService.getAnonymousUser() + this.userInformationLoaded.next(true) + } + ) + } + + isUserLoggedIn () { + return this.authService.isLoggedIn() + } + + show () { + this.openedModal = this.modalService.open(this.modal, { centered: true }) + } + + hide () { + this.openedModal.close() + this.form.reset() + } +} 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 = { 'playlist-add': require('!!raw-loader?!../../../assets/images/video/playlist-add.svg'), 'play': require('!!raw-loader?!../../../assets/images/global/play.svg'), 'playlists': require('!!raw-loader?!../../../assets/images/global/playlists.svg'), - 'about': require('!!raw-loader?!../../../assets/images/menu/about.svg'), 'globe': require('!!raw-loader?!../../../assets/images/menu/globe.svg'), 'home': require('!!raw-loader?!../../../assets/images/menu/home.svg'), 'recently-added': require('!!raw-loader?!../../../assets/images/menu/recently-added.svg'), 'trending': require('!!raw-loader?!../../../assets/images/menu/trending.svg'), + 'video-lang': require('!!raw-loader?!../../../assets/images/global/video-lang.svg'), 'videos': require('!!raw-loader?!../../../assets/images/global/videos.svg'), 'folder': require('!!raw-loader?!../../../assets/images/global/folder.svg'), - 'administration': require('!!raw-loader?!../../../assets/images/menu/administration.svg'), 'subscriptions': require('!!raw-loader?!../../../assets/images/menu/subscriptions.svg'), + 'language': require('!!raw-loader?!../../../assets/images/menu/language.svg'), + 'unsensitive': require('!!raw-loader?!../../../assets/images/menu/eye.svg'), + 'sensitive': require('!!raw-loader?!../../../assets/images/menu/eye-closed.svg'), + 'p2p': require('!!raw-loader?!../../../assets/images/menu/p2p.svg'), 'users': require('!!raw-loader?!../../../assets/images/global/users.svg'), 'search': require('!!raw-loader?!../../../assets/images/global/search.svg'), '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 @@ ::ng-deep { .help-popover { + z-index: z(help-popover) !important; max-width: 300px; .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 @@ +import { Injectable } from '@angular/core' +import { Observable, Subject } from 'rxjs' +import { + peertubeLocalStorage, + peertubeSessionStorage +} from './peertube-web-storage' +import { filter } from 'rxjs/operators' + +abstract class StorageService { + protected instance: Storage + static storageSub = new Subject() + + watch (keys?: string[]): Observable { + return StorageService.storageSub.asObservable().pipe(filter(val => keys ? keys.includes(val) : true)) + } + + getItem (key: string) { + return this.instance.getItem(key) + } + + setItem (key: string, data: any, notifyOfUpdate = true) { + this.instance.setItem(key, data) + if (notifyOfUpdate) StorageService.storageSub.next(key) + } + + removeItem (key: string, notifyOfUpdate = true) { + this.instance.removeItem(key) + if (notifyOfUpdate) StorageService.storageSub.next(key) + } +} + +@Injectable() +export class LocalStorageService extends StorageService { + protected instance: Storage = peertubeLocalStorage +} + +@Injectable() +export class SessionStorageService extends StorageService { + protected instance: Storage = peertubeSessionStorage +} 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 { import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' import { InputMaskModule } from 'primeng/inputmask' import { ScreenService } from '@app/shared/misc/screen.service' +import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service' import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' import { VideoCaptionService } from '@app/shared/video-caption' import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' @@ -101,6 +102,10 @@ import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.co import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' import { RedundancyService } from '@app/shared/video/redundancy.service' import { ClipboardModule } from '@angular/cdk/clipboard' +import { InputSwitchModule } from 'primeng/inputswitch' + +import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' +import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' @NgModule({ imports: [ @@ -122,7 +127,8 @@ import { ClipboardModule } from '@angular/cdk/clipboard' PrimeSharedModule, InputMaskModule, NgPipesModule, - MultiSelectModule + MultiSelectModule, + InputSwitchModule ], declarations: [ @@ -180,7 +186,10 @@ import { ClipboardModule } from '@angular/cdk/clipboard' DateToggleComponent, GlobalIconComponent, - PreviewUploadComponent + PreviewUploadComponent, + + MyAccountVideoSettingsComponent, + MyAccountInterfaceSettingsComponent ], exports: [ @@ -258,7 +267,10 @@ import { ClipboardModule } from '@angular/cdk/clipboard' FromNowPipe, HighlightPipe, PeerTubeTemplateDirective, - VideoDurationPipe + VideoDurationPipe, + + MyAccountVideoSettingsComponent, + MyAccountInterfaceSettingsComponent ], providers: [ @@ -303,6 +315,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard' I18nPrimengCalendarService, ScreenService, + LocalStorageService, SessionStorageService, UserNotificationService, 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 @@ -import { hasUserRight, User as UserServerModel, UserNotificationSetting, UserRight, UserRole, VideoChannel } from '../../../../../shared' +import { + hasUserRight, + User as UserServerModel, + UserNotificationSetting, + UserRight, + UserRole +} from '../../../../../shared/models/users' +import { VideoChannel } from '../../../../../shared/models/videos' import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' import { Account } from '@app/shared/account/account.model' import { Avatar } from '../../../../../shared/models/avatars/avatar.model' import { UserAdminFlag } from '@shared/models/users/user-flag.model' export class User implements UserServerModel { + static KEYS = { + ID: 'id', + ROLE: 'role', + EMAIL: 'email', + VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', + USERNAME: 'username', + NSFW_POLICY: 'nsfw_policy', + WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', + AUTO_PLAY_VIDEO: 'auto_play_video', + SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video', + AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist', + THEME: 'last_active_theme', + VIDEO_LANGUAGES: 'video_languages' + } + id: number username: string email: string @@ -60,8 +82,11 @@ export class User implements UserServerModel { this.nsfwPolicy = hash.nsfwPolicy this.webTorrentEnabled = hash.webTorrentEnabled - this.videosHistoryEnabled = hash.videosHistoryEnabled this.autoPlayVideo = hash.autoPlayVideo + this.autoPlayNextVideo = hash.autoPlayNextVideo + this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist + this.videosHistoryEnabled = hash.videosHistoryEnabled + this.videoLanguages = hash.videoLanguages this.theme = hash.theme 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 @@ -import { from, Observable, of } from 'rxjs' -import { catchError, concatMap, map, share, shareReplay, tap, toArray } from 'rxjs/operators' +import { from, Observable } from 'rxjs' +import { catchError, concatMap, map, shareReplay, toArray } from 'rxjs/operators' import { HttpClient, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' -import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' +import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' import { environment } from '../../../environments/environment' import { RestExtractor, RestPagination, RestService } from '../rest' import { Avatar } from '../../../../../shared/models/avatars/avatar.model' @@ -10,6 +10,10 @@ import { SortMeta } from 'primeng/api' import { BytesPipe } from 'ngx-pipes' import { I18n } from '@ngx-translate/i18n-polyfill' import { UserRegister } from '@shared/models/users/user-register.model' +import { User } from './user.model' +import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' +import { has } from 'lodash-es' +import { LocalStorageService, SessionStorageService } from '../misc/storage.service' @Injectable() export class UserService { @@ -17,12 +21,14 @@ export class UserService { private bytesPipe = new BytesPipe() - private userCache: { [ id: number ]: Observable } = {} + private userCache: { [ id: number ]: Observable } = {} constructor ( private authHttp: HttpClient, private restExtractor: RestExtractor, private restService: RestService, + private localStorageService: LocalStorageService, + private sessionStorageService: SessionStorageService, private i18n: I18n ) { } @@ -64,6 +70,30 @@ export class UserService { ) } + updateMyAnonymousProfile (profile: UserUpdateMe) { + const supportedKeys = { + // local storage keys + nsfwPolicy: (val: NSFWPolicyType) => this.localStorageService.setItem(User.KEYS.NSFW_POLICY, val), + webTorrentEnabled: (val: boolean) => this.localStorageService.setItem(User.KEYS.WEBTORRENT_ENABLED, String(val)), + autoPlayVideo: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO, String(val)), + autoPlayNextVideoPlaylist: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST, String(val)), + theme: (val: string) => this.localStorageService.setItem(User.KEYS.THEME, val), + videoLanguages: (val: string[]) => this.localStorageService.setItem(User.KEYS.VIDEO_LANGUAGES, JSON.stringify(val)), + + // session storage keys + autoPlayNextVideo: (val: boolean) => + this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, String(val)) + } + + for (const key of Object.keys(profile)) { + try { + if (has(supportedKeys, key)) supportedKeys[key](profile[key]) + } catch (err) { + console.error(`Cannot set item ${key} in localStorage. Likely due to a value impossible to stringify.`, err) + } + } + } + deleteMe () { const url = UserService.BASE_USERS_URL + 'me' @@ -187,7 +217,7 @@ export class UserService { ) } - updateUsers (users: User[], userUpdate: UserUpdate) { + updateUsers (users: UserServerModel[], userUpdate: UserUpdate) { return from(users) .pipe( concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)), @@ -205,17 +235,40 @@ export class UserService { } getUser (userId: number) { - return this.authHttp.get(UserService.BASE_USERS_URL + userId) + return this.authHttp.get(UserService.BASE_USERS_URL + userId) .pipe(catchError(err => this.restExtractor.handleError(err))) } - getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable> { + getAnonymousUser () { + let videoLanguages + try { + videoLanguages = JSON.parse(this.localStorageService.getItem(User.KEYS.VIDEO_LANGUAGES)) + } catch (err) { + videoLanguages = null + console.error('Cannot parse desired video languages from localStorage.', err) + } + + return new User({ + // local storage keys + nsfwPolicy: this.localStorageService.getItem(User.KEYS.NSFW_POLICY) as NSFWPolicyType, + webTorrentEnabled: this.localStorageService.getItem(User.KEYS.WEBTORRENT_ENABLED) !== 'false', + autoPlayVideo: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO) === 'true', + autoPlayNextVideoPlaylist: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST) === 'true', + theme: this.localStorageService.getItem(User.KEYS.THEME) || 'default', + videoLanguages, + + // session storage keys + autoPlayNextVideo: this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' + }) + } + + getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable> { let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) if (search) params = params.append('search', search) - return this.authHttp.get>(UserService.BASE_USERS_URL, { params }) + return this.authHttp.get>(UserService.BASE_USERS_URL, { params }) .pipe( map(res => this.restExtractor.convertResultListDateToHuman(res)), map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))), @@ -223,7 +276,7 @@ export class UserService { ) } - removeUser (usersArg: User | User[]) { + removeUser (usersArg: UserServerModel | UserServerModel[]) { const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] return from(users) @@ -234,7 +287,7 @@ export class UserService { ) } - banUsers (usersArg: User | User[], reason?: string) { + banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) { const body = reason ? { reason } : {} const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] @@ -246,7 +299,7 @@ export class UserService { ) } - unbanUsers (usersArg: User | User[]) { + unbanUsers (usersArg: UserServerModel | UserServerModel[]) { const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] return from(users) @@ -257,7 +310,7 @@ export class UserService { ) } - private formatUser (user: User) { + private formatUser (user: UserServerModel) { let videoQuota if (user.videoQuota === -1) { 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 @@ -import { debounceTime, first, tap } from 'rxjs/operators' +import { debounceTime, first, tap, throttleTime } from 'rxjs/operators' import { OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs' @@ -15,6 +15,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' import { ServerConfig } from '@shared/models' import { GlobalIconName } from '@app/shared/images/global-icon.component' +import { UserService, User } from '../users' +import { LocalStorageService } from '../misc/storage.service' enum GroupDate { UNKNOWN = 0, @@ -72,9 +74,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor protected abstract notifier: Notifier protected abstract authService: AuthService + protected abstract userService: UserService protected abstract route: ActivatedRoute protected abstract serverService: ServerService protected abstract screenService: ScreenService + protected abstract storageService: LocalStorageService protected abstract router: Router protected abstract i18n: I18n abstract titlePage: string @@ -124,6 +128,16 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor if (this.loadOnInit === true) { loadUserObservable.subscribe(() => this.loadMoreVideos()) } + + this.storageService.watch([ + User.KEYS.NSFW_POLICY, + User.KEYS.VIDEO_LANGUAGES + ]).pipe(throttleTime(200)).subscribe( + () => { + this.loadUserVideoLanguagesIfNeeded() + if (this.hasDoneFirstQuery) this.reloadVideos() + } + ) } ngOnDestroy () { @@ -279,7 +293,12 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor } private loadUserVideoLanguagesIfNeeded () { - if (!this.authService.isLoggedIn() || !this.useUserVideoLanguagePreferences) { + if (!this.useUserVideoLanguagePreferences) { + return of(true) + } + + if (!this.authService.isLoggedIn()) { + this.languageOneOf = this.userService.getAnonymousUser().videoLanguages return of(true) } 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' import { Account } from '@app/shared/account/account.model' import { AccountService } from '@app/shared/account/account.service' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' -import { ServerService } from '@app/core' +import { ServerService, AuthService } from '@app/core' import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' import { I18n } from '@ngx-translate/i18n-polyfill' +import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' export interface VideosProvider { getVideos (parameters: { @@ -49,6 +50,8 @@ export class VideoService implements VideosProvider { constructor ( private authHttp: HttpClient, + private authService: AuthService, + private userService: UserService, private restExtractor: RestExtractor, private restService: RestService, private serverService: ServerService, @@ -199,9 +202,10 @@ export class VideoService implements VideosProvider { filter?: VideoFilter, categoryOneOf?: number, languageOneOf?: string[], - skipCount?: boolean + skipCount?: boolean, + nsfw?: boolean }): Observable> { - const { videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount } = parameters + const { videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount, nsfw } = parameters const pagination = this.restService.componentPaginationToRestPagination(videoPagination) @@ -212,6 +216,15 @@ export class VideoService implements VideosProvider { if (categoryOneOf) params = params.set('categoryOneOf', categoryOneOf + '') if (skipCount) params = params.set('skipCount', skipCount + '') + if (nsfw) { + params = params.set('nsfw', nsfw + '') + } else { + const nsfwPolicy = this.authService.isLoggedIn() + ? this.authService.getUser().nsfwPolicy + : this.userService.getAnonymousUser().nsfwPolicy + if (this.nsfwPolicyToFilter(nsfwPolicy)) params.set('nsfw', 'false') + } + if (languageOneOf) { for (const l of languageOneOf) { params = params.append('languageOneOf[]', l) @@ -368,4 +381,8 @@ export class VideoService implements VideosProvider { catchError(err => this.restExtractor.handleError(err)) ) } + + private nsfwPolicyToFilter (policy: NSFWPolicyType) { + return policy === 'do_not_list' + } } 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' import { ComponentPagination } from '@app/shared/rest/component-pagination.model' import { I18n } from '@ngx-translate/i18n-polyfill' import { ResultList } from '@shared/models' +import { UserService } from '../users' +import { LocalStorageService } from '../misc/storage.service' export type SelectionType = { [ id: number ]: boolean } @@ -51,7 +53,9 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni protected route: ActivatedRoute, protected notifier: Notifier, protected authService: AuthService, + protected userService: UserService, protected screenService: ScreenService, + protected storageService: LocalStorageService, protected serverService: ServerService ) { 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. import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' import { I18n } from '@ngx-translate/i18n-polyfill' +import { SessionStorageService, LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-video-watch-playlist', @@ -42,16 +43,18 @@ export class VideoWatchPlaylistComponent { private notifier: Notifier, private i18n: I18n, private videoPlaylist: VideoPlaylistService, + private localStorageService: LocalStorageService, + private sessionStorageService: SessionStorageService, private router: Router ) { // defaults to true this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() ? this.auth.getUser().autoPlayNextVideoPlaylist - : peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' + : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' this.setAutoPlayNextVideoPlaylistSwitchText() // defaults to false - this.loopPlaylist = peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' + this.loopPlaylist = this.sessionStorageService.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' this.setLoopPlaylistSwitchText() } 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 @@
- Friendly Reminder: - - 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. - + + Help your peers + and activate the sharing system to improve the experience for everyone. + More information
-
- OK +
+ No thanks +
+
+ Activate
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 { // If the view is not expanded, take into account the menu .privacy-concerns { + z-index: z(dropdown) + 1; width: calc(100% - #{$menu-width}); } @@ -488,11 +489,11 @@ my-video-comments { } } - .privacy-concerns-okay { - background-color: var(--mainColor); + .privacy-concerns-button { padding: 5px 8px 5px 7px; margin-left: auto; border-radius: 3px; + white-space: nowrap; cursor: pointer; transition: background-color 0.3s; font-weight: $font-semibold; @@ -501,6 +502,11 @@ my-video-comments { background-color: #000; } } + + .privacy-concerns-okay { + background-color: var(--mainColor); + margin-left: 10px; + } } @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' import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { RedirectService } from '@app/core/routing/redirect.service' -import { peertubeLocalStorage, peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' +import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' import { MetaService } from '@ngx-meta/core' import { AuthUser, Notifier, ServerService } from '@app/core' @@ -10,7 +10,7 @@ import { forkJoin, Observable, Subscription } from 'rxjs' import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' import { AuthService, ConfirmService } from '../../core' -import { RestExtractor } from '../../shared' +import { RestExtractor, UserService } from '../../shared' import { VideoDetails } from '../../shared/video/video-details.model' import { VideoService } from '../../shared/video/video.service' import { VideoShareComponent } from './modal/video-share.component' @@ -35,7 +35,6 @@ import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watc import { getStoredP2PEnabled, getStoredTheater } from '../../../assets/player/peertube-player-local-storage' import { HooksService } from '@app/core/plugins/hooks.service' import { PlatformLocation } from '@angular/common' -import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component' import { scrollToTop, isXPercentInViewport } from '@app/shared/misc/utils' @Component({ @@ -95,6 +94,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { private confirmService: ConfirmService, private metaService: MetaService, private authService: AuthService, + private userService: UserService, private serverService: ServerService, private restExtractor: RestExtractor, private notifier: Notifier, @@ -118,6 +118,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { return this.authService.getUser() } + get anonymousUser () { + return this.userService.getAnonymousUser() + } + async ngOnInit () { this.serverConfig = this.serverService.getTmpConfig() @@ -266,6 +270,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.redirectService.redirectToHomepage() } + declinedPrivacyConcern () { + peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'false') + this.hasAlreadyAcceptedPrivacyConcern = false + } + acceptedPrivacyConcern () { peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true') this.hasAlreadyAcceptedPrivacyConcern = true @@ -290,7 +299,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { isAutoPlayEnabled () { return ( (this.user && this.user.autoPlayNextVideo) || - peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' + this.anonymousUser.autoPlayNextVideo ) } @@ -302,7 +311,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { isPlaylistAutoPlayEnabled () { return ( (this.user && this.user.autoPlayNextVideoPlaylist) || - peertubeSessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true' + this.anonymousUser.autoPlayNextVideoPlaylist ) } 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' import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' import { QRCodeModule } from 'angularx-qrcode' -import { InputSwitchModule } from 'primeng/inputswitch' import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestamp-route-transformer.directive' @NgModule({ @@ -21,8 +20,7 @@ import { TimestampRouteTransformerDirective } from '@app/shared/angular/timestam SharedModule, NgbTooltipModule, QRCodeModule, - RecommendationsModule, - InputSwitchModule + RecommendationsModule ], 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 @@ [ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto" > AUTOPLAY - + 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 @@ font-weight: 600; } } - -/* p-inputSwitch styles to reduce the switch size */ - -::ng-deep { - p-inputswitch { - height: 20px; - } - - .ui-inputswitch { - width: 2.5em !important; - height: 1.45em !important; - - .ui-inputswitch-slider::before { - height: 1em !important; - width: 1em !important; - } - } - - .ui-inputswitch-checked .ui-inputswitch-slider::before { - transform: translateX(1em) !important; - } -} 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- import { User } from '@app/shared' import { AuthService, Notifier } from '@app/core' import { UserService } from '@app/shared/users/user.service' -import { peertubeSessionStorage } from '@app/shared/misc/peertube-web-storage' import { I18n } from '@ngx-translate/i18n-polyfill' +import { SessionStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-recommended-videos', @@ -16,8 +16,6 @@ import { I18n } from '@ngx-translate/i18n-polyfill' styleUrls: [ './recommended-videos.component.scss' ] }) export class RecommendedVideosComponent implements OnChanges { - static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video' - @Input() inputRecommendation: RecommendationInfo @Input() user: User @Input() playlist: VideoPlaylist @@ -34,15 +32,21 @@ export class RecommendedVideosComponent implements OnChanges { private authService: AuthService, private notifier: Notifier, private i18n: I18n, - private store: RecommendedVideosStore + private store: RecommendedVideosStore, + private sessionStorageService: SessionStorageService ) { this.videos$ = this.store.recommendations$ this.hasVideos$ = this.store.hasRecommendations$ this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) - this.autoPlayNextVideo = this.authService.isLoggedIn() - ? this.authService.getUser().autoPlayNextVideo - : peertubeSessionStorage.getItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false + if (this.authService.isLoggedIn()) { + this.autoPlayNextVideo = this.authService.getUser().autoPlayNextVideo + } else { + this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' || false + this.sessionStorageService.watch([User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO]).subscribe( + () => this.autoPlayNextVideo = this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' + ) + } this.autoPlayNextVideoTooltip = this.i18n('When active, the next video is automatically played after the current one.') } @@ -58,7 +62,7 @@ export class RecommendedVideosComponent implements OnChanges { } switchAutoPlayNextVideo () { - peertubeSessionStorage.setItem(RecommendedVideosComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) + this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) if (this.authService.isLoggedIn()) { 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' import { UserRight } from '../../../../../shared/models/users' import { Notifier, ServerService } from '@app/core' import { HooksService } from '@app/core/plugins/hooks.service' +import { UserService } from '@app/shared' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-videos-local', @@ -31,7 +33,9 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On protected route: ActivatedRoute, protected notifier: Notifier, protected authService: AuthService, + protected userService: UserService, protected screenService: ScreenService, + protected storageService: LocalStorageService, private videoService: VideoService, private hooks: HooksService ) { 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' import { ScreenService } from '@app/shared/misc/screen.service' import { Notifier, ServerService } from '@app/core' import { HooksService } from '@app/core/plugins/hooks.service' +import { UserService } from '@app/shared' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-videos-most-liked', @@ -28,7 +30,9 @@ export class VideoMostLikedComponent extends AbstractVideoList implements OnInit protected route: ActivatedRoute, protected notifier: Notifier, protected authService: AuthService, + protected userService: UserService, protected screenService: ScreenService, + protected storageService: LocalStorageService, private videoService: VideoService, private hooks: HooksService ) { 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' import { ScreenService } from '@app/shared/misc/screen.service' import { Notifier, ServerService } from '@app/core' import { HooksService } from '@app/core/plugins/hooks.service' +import { UserService } from '@app/shared' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-videos-recently-added', @@ -29,7 +31,9 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On protected router: Router, protected notifier: Notifier, protected authService: AuthService, + protected userService: UserService, protected screenService: ScreenService, + protected storageService: LocalStorageService, private videoService: VideoService, private hooks: HooksService ) { 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' import { ScreenService } from '@app/shared/misc/screen.service' import { Notifier, ServerService } from '@app/core' import { HooksService } from '@app/core/plugins/hooks.service' +import { UserService } from '@app/shared' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-videos-trending', @@ -28,7 +30,9 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit, protected route: ActivatedRoute, protected notifier: Notifier, protected authService: AuthService, + protected userService: UserService, protected screenService: ScreenService, + protected storageService: LocalStorageService, private videoService: VideoService, private hooks: HooksService ) { 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' import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' import { Notifier, ServerService } from '@app/core' import { HooksService } from '@app/core/plugins/hooks.service' +import { UserService } from '@app/shared' +import { LocalStorageService } from '@app/shared/misc/storage.service' @Component({ selector: 'my-videos-user-subscriptions', @@ -29,7 +31,9 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement protected route: ActivatedRoute, protected notifier: Notifier, protected authService: AuthService, + protected userService: UserService, protected screenService: ScreenService, + protected storageService: LocalStorageService, private videoService: VideoService, private hooks: HooksService ) { diff --git a/client/src/assets/images/global/video-lang.svg b/client/src/assets/images/global/video-lang.svg new file mode 100644 index 000000000..8d7b6a016 --- /dev/null +++ b/client/src/assets/images/global/video-lang.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/client/src/assets/images/menu/about.svg b/client/src/assets/images/menu/about.svg deleted file mode 100644 index bea602aac..000000000 --- a/client/src/assets/images/menu/about.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/client/src/assets/images/menu/administration.svg b/client/src/assets/images/menu/administration.svg deleted file mode 100644 index 0dceda082..000000000 --- a/client/src/assets/images/menu/administration.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/client/src/assets/images/menu/eye-closed.svg b/client/src/assets/images/menu/eye-closed.svg new file mode 100644 index 000000000..5c441e719 --- /dev/null +++ b/client/src/assets/images/menu/eye-closed.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/images/menu/eye.svg b/client/src/assets/images/menu/eye.svg new file mode 100644 index 000000000..d1c3941f1 --- /dev/null +++ b/client/src/assets/images/menu/eye.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/images/menu/language.png b/client/src/assets/images/menu/language.png deleted file mode 100644 index 60e6fec00fd20a3beae24b37729d8f16d1d7d476..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10937 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 + + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/images/menu/moonsun.svg b/client/src/assets/images/menu/moonsun.svg deleted file mode 100644 index fe2a96396..000000000 --- a/client/src/assets/images/menu/moonsun.svg +++ /dev/null @@ -1 +0,0 @@ -Artboard 633 \ No newline at end of file diff --git a/client/src/assets/images/menu/p2p.svg b/client/src/assets/images/menu/p2p.svg new file mode 100644 index 000000000..744643010 --- /dev/null +++ b/client/src/assets/images/menu/p2p.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 560414e90..046368c8b 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -24,6 +24,7 @@ body { // now beware node-sass requires interpolation // for css custom properties #{$var} --mainColor: #{$orange-color}; + --mainColorLighter: #{$orange-color-lighter}; --mainHoverColor: #{$orange-hover-color}; --mainBackgroundColor: #{$bg-color}; --mainForegroundColor: #{$fg-color}; diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 6cced995e..bb7b21274 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss @@ -13,6 +13,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; flex: auto; } +.c-hand { + cursor: pointer; +} + @keyframes spin { from { transform: scale(1) rotate(0deg); @@ -41,6 +45,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; background-color: var(--mainHoverColor); opacity: .9; } + + &::after { + display: none; + } } button { diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 4766e4490..bafc82d7d 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss @@ -72,6 +72,14 @@ } } +@mixin fill-svg-color ($color) { + ::ng-deep svg { + path { + fill: $color; + } + } +} + @mixin button-focus-visible-shadow($color) { &.focus-visible { box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 4px $color; diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index 4ef8e17b9..91229cee0 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss @@ -14,6 +14,7 @@ $grey-foreground-color: #585858; $grey-foreground-hover-color: #303030; $orange-color: #F1680D; +$orange-color-lighter: rgb(233, 159, 110); $orange-hover-color: #F97D46; $cyan-color: hsl(187, 77%, 34%); @@ -74,6 +75,7 @@ $activated-action-button-color: black; // to be warned of non-existing variables $variables: ( --mainColor: var(--mainColor), + --mainColorLighter: var(--mainColorLighter), --mainHoverColor: var(--mainHoverColor), --mainBackgroundColor: var(--mainBackgroundColor), --mainForegroundColor: var(--mainForegroundColor), @@ -112,8 +114,9 @@ $zindex: ( tooltip : 14000, loadbar : 15000, modal : 16000, - notification : 17000, - hotkeys : 18000 + help-popover : 17000, + notification : 18000, + hotkeys : 19000 ); @function z($label) { diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index 4d2d6cb67..e2c453228 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss @@ -274,6 +274,15 @@ p-multiselect { // left: -2px !important; //} } + + .ui-multiselect-panel .ui-multiselect-items .ui-multiselect-item.ui-state-highlight { + background-color: var(--mainColorLighter); + } + + .ui-inputtext:enabled:focus:not(.ui-state-error) { + border-color: var(--mainColorLighter) !important; + box-shadow: none; + } } // PrimeNG calendar tweaks @@ -379,6 +388,24 @@ p-inputswitch { .ui-inputswitch-checked .ui-inputswitch-slider { background-color: var(--mainColor) !important; } + + &.small { + height: 20px; + + .ui-inputswitch { + width: 2.5em !important; + height: 1.45em !important; + + .ui-inputswitch-slider::before { + height: 1em !important; + width: 1em !important; + } + } + + .ui-inputswitch-checked .ui-inputswitch-slider::before { + transform: translateX(1em) !important; + } + } } p-toast { -- 2.41.0