From d3217560a611b94f888ecf3de93b428a7521d4de Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Fri, 28 Feb 2020 13:52:21 +0100 Subject: 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/about-instance.component.html | 2 +- .../account-videos/account-videos.component.ts | 4 + .../my-account-history.component.ts | 2 + .../my-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 +++++-- .../src/app/+my-account/my-account.component.html | 2 +- client/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 ++-- .../src/app/menu/language-chooser.component.scss | 2 + client/src/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 ++++++- .../app/modal/quick-settings-modal.component.html | 19 ++ .../app/modal/quick-settings-modal.component.scss | 39 ++++ .../app/modal/quick-settings-modal.component.ts | 62 +++++++ .../src/app/shared/images/global-icon.component.ts | 7 +- client/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 ++++++-- client/src/app/shared/video/abstract-video-list.ts | 23 ++- client/src/app/shared/video/video.service.ts | 23 ++- .../app/shared/video/videos-selection.component.ts | 4 + .../+video-watch/video-watch-playlist.component.ts | 7 +- .../videos/+video-watch/video-watch.component.html | 15 +- .../videos/+video-watch/video-watch.component.scss | 10 +- .../videos/+video-watch/video-watch.component.ts | 19 +- .../app/videos/+video-watch/video-watch.module.ts | 4 +- .../recommended-videos.component.html | 2 +- .../recommended-videos.component.scss | 22 --- .../recommended-videos.component.ts | 20 ++- .../app/videos/video-list/video-local.component.ts | 4 + .../video-list/video-most-liked.component.ts | 4 + .../video-list/video-recently-added.component.ts | 4 + .../videos/video-list/video-trending.component.ts | 4 + .../video-user-subscriptions.component.ts | 4 + client/src/assets/images/global/video-lang.svg | 15 ++ client/src/assets/images/menu/about.svg | 11 -- client/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 60e6fec00..000000000 Binary files a/client/src/assets/images/menu/language.png and /dev/null differ diff --git a/client/src/assets/images/menu/language.svg b/client/src/assets/images/menu/language.svg new file mode 100644 index 000000000..0ac754c87 --- /dev/null +++ b/client/src/assets/images/menu/language.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ 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 { -- cgit v1.2.3