From ba430d7516bc5b1324b60571ba7594460969b7fb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 18 Dec 2019 15:31:54 +0100 Subject: [PATCH] Lazy load static objects --- .../about-instance.component.ts | 38 ++-- .../contact-admin-modal.component.ts | 8 +- .../edit-custom-config.component.ts | 23 +- .../+admin/moderation/moderation.component.ts | 14 +- .../video-blacklist-list.component.ts | 19 +- .../users/user-edit/user-create.component.ts | 2 + .../app/+admin/users/user-edit/user-edit.ts | 17 +- .../users/user-edit/user-update.component.ts | 2 + .../users/user-list/user-list.component.ts | 10 +- .../my-account-change-email.component.ts | 43 ++-- ...my-account-interface-settings.component.ts | 10 +- ...ount-notification-preferences.component.ts | 10 +- .../my-account-video-settings.component.ts | 6 +- ...-account-video-channel-update.component.ts | 10 +- ...account-video-playlist-create.component.ts | 13 +- ...account-video-playlist-update.component.ts | 14 +- .../app/+my-account/my-account.component.ts | 16 +- .../shared/actor-avatar-info.component.ts | 17 +- .../+register/register-routing.module.ts | 2 +- .../+signup/+register/register.component.ts | 10 +- ...verify-account-ask-send-email.component.ts | 8 +- client/src/app/app.component.ts | 46 ++-- client/src/app/app.module.ts | 6 +- client/src/app/core/plugins/plugin.service.ts | 6 +- .../src/app/core/routing/redirect.service.ts | 12 +- .../routing/server-config-resolver.service.ts | 12 +- client/src/app/core/server/server.service.ts | 205 +++++++++--------- client/src/app/core/theme/theme.service.ts | 15 +- client/src/app/login/login-routing.module.ts | 2 +- client/src/app/login/login.component.ts | 13 +- client/src/app/menu/menu.component.ts | 10 +- .../app/search/search-filters.component.ts | 16 +- .../shared/images/preview-upload.component.ts | 10 +- .../instance-features-table.component.html | 16 +- .../instance-features-table.component.ts | 23 +- .../app/shared/instance/instance.service.ts | 45 ++-- .../user-moderation-dropdown.component.ts | 15 +- .../app/shared/overview/overview.service.ts | 2 +- .../video-caption/video-caption.service.ts | 2 +- .../video-import/video-import.service.ts | 2 +- ...eo-playlist-element-miniature.component.ts | 19 +- .../video-playlist/video-playlist.service.ts | 6 +- .../app/shared/video/abstract-video-list.ts | 10 +- .../shared/video/video-miniature.component.ts | 9 +- client/src/app/shared/video/video.service.ts | 4 +- .../video-caption-add-modal.component.ts | 10 +- .../shared/video-edit.component.html | 2 +- .../shared/video-edit.component.ts | 21 +- .../video-add-components/video-send.ts | 13 +- .../video-upload.component.ts | 5 +- .../videos/+video-edit/video-add.component.ts | 17 +- .../+video-watch/video-watch.component.ts | 16 +- .../video-list/video-most-liked.component.ts | 7 +- .../video-list/video-trending.component.ts | 6 +- scripts/build/index.sh | 2 +- 55 files changed, 543 insertions(+), 354 deletions(-) diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts index 16ccae2e2..87beb13da 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts @@ -5,7 +5,8 @@ import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-a import { InstanceService } from '@app/shared/instance/instance.service' import { MarkdownService } from '@app/shared/renderer' import { forkJoin } from 'rxjs' -import { first } from 'rxjs/operators' +import { map, switchMap } from 'rxjs/operators' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-about-instance', @@ -33,6 +34,8 @@ export class AboutInstanceComponent implements OnInit { languages: string[] = [] categories: string[] = [] + serverConfig: ServerConfig + constructor ( private notifier: Notifier, private serverService: ServerService, @@ -42,25 +45,35 @@ export class AboutInstanceComponent implements OnInit { ) {} get instanceName () { - return this.serverService.getConfig().instance.name + return this.serverConfig.instance.name } get isContactFormEnabled () { - return this.serverService.getConfig().email.enabled && this.serverService.getConfig().contactForm.enabled + return this.serverConfig.email.enabled && this.serverConfig.contactForm.enabled } get isNSFW () { - return this.serverService.getConfig().instance.isNSFW + return this.serverConfig.instance.isNSFW } ngOnInit () { - forkJoin([ - this.instanceService.getAbout(), - this.serverService.localeObservable.pipe(first()), - this.serverService.videoLanguagesLoaded.pipe(first()), - this.serverService.videoCategoriesLoaded.pipe(first()) - ]).subscribe( - async ([ about, translations ]) => { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + + this.instanceService.getAbout() + .pipe( + switchMap(about => { + return forkJoin([ + this.instanceService.buildTranslatedLanguages(about), + this.instanceService.buildTranslatedCategories(about) + ]).pipe(map(([ languages, categories ]) => ({ about, languages, categories }))) + }) + ).subscribe( + async ({ about, languages, categories }) => { + this.languages = languages + this.categories = categories + this.shortDescription = about.instance.shortDescription this.creationReason = about.instance.creationReason @@ -68,9 +81,6 @@ export class AboutInstanceComponent implements OnInit { this.businessModel = about.instance.businessModel this.html = await this.instanceService.buildHtml(about) - - this.languages = this.instanceService.buildTranslatedLanguages(about, translations) - this.categories = this.instanceService.buildTranslatedCategories(about, translations) }, () => this.notifier.error(this.i18n('Cannot get about information from server')) diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts index 878d49b55..2ed41e741 100644 --- a/client/src/app/+about/about-instance/contact-admin-modal.component.ts +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts @@ -6,6 +6,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' import { FormReactive, InstanceValidatorsService } from '@app/shared' import { InstanceService } from '@app/shared/instance/instance.service' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-contact-admin-modal', @@ -18,6 +19,7 @@ export class ContactAdminModalComponent extends FormReactive implements OnInit { error: string private openedModal: NgbModalRef + private serverConfig: ServerConfig constructor ( protected formValidatorService: FormValidatorService, @@ -32,10 +34,14 @@ export class ContactAdminModalComponent extends FormReactive implements OnInit { } get instanceName () { - return this.serverService.getConfig().instance.name + return this.serverConfig.instance.name } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.buildForm({ fromName: this.instanceValidatorsService.FROM_NAME, fromEmail: this.instanceValidatorsService.FROM_EMAIL, diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 1f6751297..25e06d8a1 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts @@ -8,7 +8,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { SelectItem } from 'primeng/api' import { forkJoin } from 'rxjs' -import { first } from 'rxjs/operators' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-edit-custom-config', @@ -24,6 +24,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { languageItems: SelectItem[] = [] categoryItems: SelectItem[] = [] + private serverConfig: ServerConfig + constructor ( protected formValidatorService: FormValidatorService, private customConfigValidatorsService: CustomConfigValidatorsService, @@ -84,7 +86,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { } get availableThemes () { - return this.serverService.getConfig().theme.registered + return this.serverConfig.theme.registered .map(t => t.name) } @@ -93,6 +95,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + const formGroupData: { [key in keyof CustomConfig ]: any } = { instance: { name: this.customConfigValidatorsService.INSTANCE_NAME, @@ -218,16 +224,13 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { forkJoin([ this.configService.getCustomConfig(), - this.serverService.videoLanguagesLoaded.pipe(first()), // First so the observable completes - this.serverService.videoCategoriesLoaded.pipe(first()) + this.serverService.getVideoLanguages(), + this.serverService.getVideoCategories() ]).subscribe( - ([ config ]) => { + ([ config, languages, categories ]) => { this.customConfig = config - const languages = this.serverService.getVideoLanguages() this.languageItems = languages.map(l => ({ label: l.label, value: l.id })) - - const categories = this.serverService.getVideoCategories() this.categoryItems = categories.map(l => ({ label: l.label, value: l.id })) this.updateForm() @@ -249,12 +252,14 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { async formValidated () { this.configService.updateCustomConfig(this.form.value) + .pipe( + ) .subscribe( res => { this.customConfig = res // Reload general configuration - this.serverService.loadConfig() + this.serverService.resetConfig() this.updateForm() diff --git a/client/src/app/+admin/moderation/moderation.component.ts b/client/src/app/+admin/moderation/moderation.component.ts index 47154af3f..7744deb06 100644 --- a/client/src/app/+admin/moderation/moderation.component.ts +++ b/client/src/app/+admin/moderation/moderation.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { UserRight } from '../../../../../shared' import { AuthService, ServerService } from '@app/core' @@ -6,14 +6,18 @@ import { AuthService, ServerService } from '@app/core' templateUrl: './moderation.component.html', styleUrls: [ './moderation.component.scss' ] }) -export class ModerationComponent { - autoBlacklistVideosEnabled: boolean +export class ModerationComponent implements OnInit { + autoBlacklistVideosEnabled = false constructor ( private auth: AuthService, private serverService: ServerService - ) { - this.autoBlacklistVideosEnabled = this.serverService.getConfig().autoBlacklist.videos.ofUsers.enabled + ) { } + + ngOnInit (): void { + this.serverService.getConfig() + .subscribe(config => this.autoBlacklistVideosEnabled = config.autoBlacklist.videos.ofUsers.enabled) + } hasVideoAbusesRight () { diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts index f4bce7c48..5876f658b 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts @@ -33,11 +33,18 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { private i18n: I18n ) { super() + } - // don't filter if auto-blacklist not enabled as this will be only list - if (this.serverService.getConfig().autoBlacklist.videos.ofUsers.enabled) { - this.listBlacklistTypeFilter = VideoBlacklistType.MANUAL - } + ngOnInit () { + this.serverService.getConfig() + .subscribe(config => { + // don't filter if auto-blacklist not enabled as this will be only list + if (config.autoBlacklist.videos.ofUsers.enabled) { + this.listBlacklistTypeFilter = VideoBlacklistType.MANUAL + } + }) + + this.initialize() this.videoBlacklistActions = [ { @@ -47,10 +54,6 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { ] } - ngOnInit () { - this.initialize() - } - getVideoUrl (videoBlacklist: VideoBlacklist) { return Video.buildClientUrl(videoBlacklist.video.uuid) } diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts index 3b57a49c6..e726ec4d7 100644 --- a/client/src/app/+admin/users/user-edit/user-create.component.ts +++ b/client/src/app/+admin/users/user-edit/user-create.component.ts @@ -34,6 +34,8 @@ export class UserCreateComponent extends UserEdit implements OnInit { } ngOnInit () { + super.ngOnInit() + const defaultValues = { role: UserRole.USER.toString(), videoQuota: '-1', diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts index 6625d65d6..02f1dcd42 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.ts +++ b/client/src/app/+admin/users/user-edit/user-edit.ts @@ -1,21 +1,30 @@ import { AuthService, ServerService } from '../../../core' import { FormReactive } from '../../../shared' -import { USER_ROLE_LABELS, UserRole, VideoResolution } from '../../../../../../shared' +import { ServerConfig, USER_ROLE_LABELS, UserRole, VideoResolution } from '../../../../../../shared' import { ConfigService } from '@app/+admin/config/shared/config.service' import { UserAdminFlag } from '@shared/models/users/user-flag.model' +import { OnInit } from '@angular/core' -export abstract class UserEdit extends FormReactive { +export abstract class UserEdit extends FormReactive implements OnInit { videoQuotaOptions: { value: string, label: string }[] = [] videoQuotaDailyOptions: { value: string, label: string }[] = [] username: string userId: number + protected serverConfig: ServerConfig + protected abstract serverService: ServerService protected abstract configService: ConfigService protected abstract auth: AuthService abstract isCreation (): boolean abstract getFormButtonTitle (): string + ngOnInit (): void { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + } + getRoles () { const authUser = this.auth.getUser() @@ -32,12 +41,12 @@ export abstract class UserEdit extends FormReactive { isTranscodingInformationDisplayed () { const formVideoQuota = parseInt(this.form.value['videoQuota'], 10) - return this.serverService.getConfig().transcoding.enabledResolutions.length !== 0 && + return this.serverConfig.transcoding.enabledResolutions.length !== 0 && formVideoQuota > 0 } computeQuotaWithTranscoding () { - const transcodingConfig = this.serverService.getConfig().transcoding + const transcodingConfig = this.serverConfig.transcoding const resolutions = transcodingConfig.enabledResolutions const higherResolution = VideoResolution.H_4K diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts index c7052a925..d1682a99d 100644 --- a/client/src/app/+admin/users/user-edit/user-update.component.ts +++ b/client/src/app/+admin/users/user-edit/user-update.component.ts @@ -43,6 +43,8 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { } ngOnInit () { + super.ngOnInit() + const defaultValues = { videoQuota: '-1', videoQuotaDaily: '-1' } this.buildForm({ email: this.userValidatorsService.USER_EMAIL, diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index ab82713b2..1083ba291 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts @@ -4,7 +4,7 @@ import { SortMeta } from 'primeng/components/common/sortmeta' import { ConfirmService, ServerService } from '../../../core' import { RestPagination, RestTable, UserService } from '../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' -import { User } from '../../../../../../shared' +import { ServerConfig, User } from '../../../../../../shared' import { UserBanModalComponent } from '@app/shared/moderation' import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' @@ -25,6 +25,8 @@ export class UserListComponent extends RestTable implements OnInit { selectedUsers: User[] = [] bulkUserActions: DropdownAction[] = [] + private serverConfig: ServerConfig + constructor ( private notifier: Notifier, private confirmService: ConfirmService, @@ -41,10 +43,14 @@ export class UserListComponent extends RestTable implements OnInit { } get requiresEmailVerification () { - return this.serverService.getConfig().signup.requiresEmailVerification + return this.serverConfig.signup.requiresEmailVerification } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.initialize() this.bulkUserActions = [ diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts index ec7cf935c..9d406805f 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts @@ -6,6 +6,7 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' import { User } from '../../../../../../shared' import { tap } from 'rxjs/operators' +import { forkJoin } from 'rxjs' @Component({ selector: 'my-account-change-email', @@ -45,29 +46,29 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni const password = this.form.value[ 'password' ] const email = this.form.value[ 'new-email' ] - this.userService.changeEmail(password, email) - .pipe( - tap(() => this.authService.refreshUserInformation()) - ) - .subscribe( - () => { - this.form.reset() + forkJoin([ + this.serverService.getConfig(), + this.userService.changeEmail(password, email) + ]).pipe(tap(() => this.authService.refreshUserInformation())) + .subscribe( + ([ config ]) => { + this.form.reset() - if (this.serverService.getConfig().signup.requiresEmailVerification) { - this.success = this.i18n('Please check your emails to verify your new email.') - } else { - this.success = this.i18n('Email updated.') - } - }, - - err => { - if (err.status === 401) { - this.error = this.i18n('You current password is invalid.') - return - } + if (config.signup.requiresEmailVerification) { + this.success = this.i18n('Please check your emails to verify your new email.') + } else { + this.success = this.i18n('Email updated.') + } + }, - this.error = err.message + err => { + if (err.status === 401) { + this.error = this.i18n('You current password is invalid.') + return } - ) + + this.error = err.message + } + ) } } 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 5ec1c9f8f..441f89f10 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,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core' import { Notifier, ServerService } from '@app/core' -import { UserUpdateMe } from '../../../../../../shared' +import { ServerConfig, UserUpdateMe } from '../../../../../../shared' import { AuthService } from '../../../core' import { FormReactive, User, UserService } from '../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' @@ -16,6 +16,8 @@ export class MyAccountInterfaceSettingsComponent extends FormReactive implements @Input() user: User = null @Input() userInformationLoaded: Subject + private serverConfig: ServerConfig + constructor ( protected formValidatorService: FormValidatorService, private authService: AuthService, @@ -28,11 +30,15 @@ export class MyAccountInterfaceSettingsComponent extends FormReactive implements } get availableThemes () { - return this.serverService.getConfig().theme.registered + return this.serverConfig.theme.registered .map(t => t.name) } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.buildForm({ theme: null }) diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts index 76fabb19d..6ba1a1020 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts @@ -21,7 +21,7 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { webNotifications: { [ id in keyof UserNotificationSetting ]: boolean } = {} as any labelNotifications: { [ id in keyof UserNotificationSetting ]: string } = {} as any rightNotifications: { [ id in keyof Partial ]: UserRight } = {} as any - emailEnabled: boolean + emailEnabled = false private savePreferences = debounce(this.savePreferencesImpl.bind(this), 500) @@ -31,7 +31,6 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { private serverService: ServerService, private notifier: Notifier ) { - this.labelNotifications = { newVideoFromSubscription: this.i18n('New video from your subscriptions'), newCommentOnMyVideo: this.i18n('New comment on your video'), @@ -55,11 +54,14 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION } - - this.emailEnabled = this.serverService.getConfig().email.enabled } ngOnInit () { + this.serverService.getConfig() + .subscribe(config => { + this.emailEnabled = config.email.enabled + }) + this.userInformationLoaded.subscribe(() => this.loadNotificationSettings()) } 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 99eee23b8..a66159b3f 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 @@ -41,11 +41,9 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI }) forkJoin([ - this.serverService.videoLanguagesLoaded.pipe(first()), + this.serverService.getVideoLanguages(), this.userInformationLoaded.pipe(first()) - ]).subscribe(() => { - const languages = this.serverService.getVideoLanguages() - + ]).subscribe(([ languages ]) => { this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] this.languageItems = this.languageItems .concat(languages.map(l => ({ label: l.label, value: l.id }))) diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts index 081e956d2..9c948b367 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts @@ -9,6 +9,7 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-account-video-channel-update', @@ -21,6 +22,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE private paramsSub: Subscription private oldSupportField: string + private serverConfig: ServerConfig constructor ( protected formValidatorService: FormValidatorService, @@ -37,6 +39,10 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.buildForm({ 'display-name': this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME, description: this.videoChannelValidatorsService.VIDEO_CHANNEL_DESCRIPTION, @@ -109,11 +115,11 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE } get maxAvatarSize () { - return this.serverService.getConfig().avatar.file.size.max + return this.serverConfig.avatar.file.size.max } get avatarExtensions () { - return this.serverService.getConfig().avatar.file.extensions.join(',') + return this.serverConfig.avatar.file.extensions.join(',') } isCreation () { diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts index 8aed8b513..e47e5f980 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts @@ -47,15 +47,14 @@ export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylis populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) .catch(err => console.error('Cannot populate user video channels.', err)) - this.serverService.videoPlaylistPrivaciesLoaded.subscribe( - () => { - this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies() + this.serverService.getVideoPlaylistPrivacies() + .subscribe(videoPlaylistPrivacies => { + this.videoPlaylistPrivacies = videoPlaylistPrivacies - this.form.patchValue({ - privacy: VideoPlaylistPrivacy.PRIVATE + this.form.patchValue({ + privacy: VideoPlaylistPrivacy.PRIVATE + }) }) - } - ) } formValidated () { diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts index 917ad7258..2f85cdd96 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { AuthService, Notifier, ServerService } from '@app/core' -import { Subscription } from 'rxjs' +import { forkJoin, Subscription } from 'rxjs' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { MyAccountVideoPlaylistEdit } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-edit' @@ -56,13 +56,17 @@ export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylis this.paramsSub = this.route.params .pipe( map(routeParams => routeParams['videoPlaylistId']), - switchMap(videoPlaylistId => this.videoPlaylistService.getVideoPlaylist(videoPlaylistId)), - delayWhen(() => this.serverService.videoPlaylistPrivaciesLoaded) + switchMap(videoPlaylistId => { + return forkJoin([ + this.videoPlaylistService.getVideoPlaylist(videoPlaylistId), + this.serverService.getVideoPlaylistPrivacies() + ]) + }) ) .subscribe( - videoPlaylistToUpdate => { - this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies() + ([ videoPlaylistToUpdate, videoPlaylistPrivacies]) => { this.videoPlaylistToUpdate = videoPlaylistToUpdate + this.videoPlaylistPrivacies = videoPlaylistPrivacies this.hydrateFormFromPlaylist() }, diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts index d98d06f8e..05dcf522d 100644 --- a/client/src/app/+my-account/my-account.component.ts +++ b/client/src/app/+my-account/my-account.component.ts @@ -1,20 +1,28 @@ -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { TopMenuDropdownParam } from '@app/shared/menu/top-menu-dropdown.component' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-my-account', templateUrl: './my-account.component.html', styleUrls: [ './my-account.component.scss' ] }) -export class MyAccountComponent { +export class MyAccountComponent implements OnInit { menuEntries: TopMenuDropdownParam[] = [] + private serverConfig: ServerConfig + constructor ( private serverService: ServerService, private i18n: I18n - ) { + ) { } + + ngOnInit (): void { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) const libraryEntries: TopMenuDropdownParam = { label: this.i18n('My library'), @@ -91,7 +99,7 @@ export class MyAccountComponent { } isVideoImportEnabled () { - const importConfig = this.serverService.getConfig().import.videos + const importConfig = this.serverConfig.import.videos return importConfig.http.enabled || importConfig.torrent.enabled } diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.ts b/client/src/app/+my-account/shared/actor-avatar-info.component.ts index 0289a66c3..101dfa556 100644 --- a/client/src/app/+my-account/shared/actor-avatar-info.component.ts +++ b/client/src/app/+my-account/shared/actor-avatar-info.component.ts @@ -1,26 +1,35 @@ -import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core' +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' import { ServerService } from '../../core/server' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' import { Account } from '@app/shared/account/account.model' import { Notifier } from '@app/core' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-actor-avatar-info', templateUrl: './actor-avatar-info.component.html', styleUrls: [ './actor-avatar-info.component.scss' ] }) -export class ActorAvatarInfoComponent { +export class ActorAvatarInfoComponent implements OnInit { @ViewChild('avatarfileInput', { static: false }) avatarfileInput: ElementRef @Input() actor: VideoChannel | Account @Output() avatarChange = new EventEmitter() + private serverConfig: ServerConfig + constructor ( private serverService: ServerService, private notifier: Notifier ) {} + ngOnInit (): void { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + } + onAvatarChange () { const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] if (avatarfile.size > this.maxAvatarSize) { @@ -35,10 +44,10 @@ export class ActorAvatarInfoComponent { } get maxAvatarSize () { - return this.serverService.getConfig().avatar.file.size.max + return this.serverConfig.avatar.file.size.max } get avatarExtensions () { - return this.serverService.getConfig().avatar.file.extensions.join(',') + return this.serverConfig.avatar.file.extensions.join(',') } } diff --git a/client/src/app/+signup/+register/register-routing.module.ts b/client/src/app/+signup/+register/register-routing.module.ts index e3a5001dc..f47e80755 100644 --- a/client/src/app/+signup/+register/register-routing.module.ts +++ b/client/src/app/+signup/+register/register-routing.module.ts @@ -16,7 +16,7 @@ const registerRoutes: Routes = [ } }, resolve: { - serverConfigLoaded: ServerConfigResolver + serverConfig: ServerConfigResolver } } ] diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts index acec56f04..ae944ec15 100644 --- a/client/src/app/+signup/+register/register.component.ts +++ b/client/src/app/+signup/+register/register.component.ts @@ -4,10 +4,11 @@ import { UserService, UserValidatorsService } from '@app/shared' import { I18n } from '@ngx-translate/i18n-polyfill' import { UserRegister } from '@shared/models/users/user-register.model' import { FormGroup } from '@angular/forms' -import { About } from '@shared/models/server' +import { About, ServerConfig } from '@shared/models/server' import { InstanceService } from '@app/shared/instance/instance.service' import { HooksService } from '@app/core/plugins/hooks.service' import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' +import { ActivatedRoute } from '@angular/router' @Component({ selector: 'my-register', @@ -34,7 +35,10 @@ export class RegisterComponent implements OnInit { formStepUser: FormGroup formStepChannel: FormGroup + private serverConfig: ServerConfig + constructor ( + private route: ActivatedRoute, private authService: AuthService, private userValidatorsService: UserValidatorsService, private notifier: Notifier, @@ -48,10 +52,12 @@ export class RegisterComponent implements OnInit { } get requiresEmailVerification () { - return this.serverService.getConfig().signup.requiresEmailVerification + return this.serverConfig.signup.requiresEmailVerification } ngOnInit (): void { + this.serverConfig = this.route.snapshot.data.serverConfig + this.instanceService.getAbout() .subscribe( async about => { diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts index cfd471fa4..3bd604b66 100644 --- a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts +++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts @@ -5,6 +5,7 @@ import { ServerService } from '@app/core/server' import { FormReactive, UserService } from '@app/shared' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-verify-account-ask-send-email', @@ -13,6 +14,7 @@ import { UserValidatorsService } from '@app/shared/forms/form-validators/user-va }) export class VerifyAccountAskSendEmailComponent extends FormReactive implements OnInit { + private serverConfig: ServerConfig constructor ( protected formValidatorService: FormValidatorService, @@ -27,10 +29,14 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements } get requiresEmailVerification () { - return this.serverService.getConfig().signup.requiresEmailVerification + return this.serverConfig.signup.requiresEmailVerification } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.buildForm({ 'verify-email-email': this.userValidatorsService.USER_EMAIL }) diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 351620c59..883f36514 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -15,7 +15,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' -import { UserRole } from '@shared/models' +import { ServerConfig, UserRole } from '@shared/models' import { User } from '@app/shared' import { InstanceService } from '@app/shared/instance/instance.service' @@ -33,6 +33,8 @@ export class AppComponent implements OnInit { customCSS: SafeHtml + private serverConfig: ServerConfig + constructor ( private i18n: I18n, private viewportScroller: ViewportScroller, @@ -52,7 +54,7 @@ export class AppComponent implements OnInit { ) { } get instanceName () { - return this.serverService.getConfig().instance.name + return this.serverConfig.instance.name } get defaultRoute () { @@ -62,6 +64,10 @@ export class AppComponent implements OnInit { ngOnInit () { document.getElementById('incompatible-browser').className += ' browser-ok' + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.loadPlugins() this.themeService.initialize() @@ -72,14 +78,6 @@ export class AppComponent implements OnInit { this.authService.refreshUserInformation() } - // Load custom data from server - this.serverService.loadConfig() - this.serverService.loadVideoCategories() - this.serverService.loadVideoLanguages() - this.serverService.loadVideoLicences() - this.serverService.loadVideoPrivacies() - this.serverService.loadVideoPlaylistPrivacies() - // Do not display menu on small screens if (this.screenService.isInSmallView()) { this.isMenuDisplayed = false @@ -187,10 +185,8 @@ export class AppComponent implements OnInit { private injectJS () { // Inject JS - this.serverService.configLoaded - .subscribe(() => { - const config = this.serverService.getConfig() - + this.serverService.getConfig() + .subscribe(config => { if (config.instance.customizations.javascript) { try { // tslint:disable:no-eval @@ -204,17 +200,14 @@ export class AppComponent implements OnInit { private injectCSS () { // Inject CSS if modified (admin config settings) - this.serverService.configLoaded - .pipe(skip(1)) // We only want to subscribe to reloads, because the CSS is already injected by the server + this.serverService.configReloaded .subscribe(() => { const headStyle = document.querySelector('style.custom-css-style') if (headStyle) headStyle.parentNode.removeChild(headStyle) - const config = this.serverService.getConfig() - // We test customCSS if the admin removed the css - if (this.customCSS || config.instance.customizations.css) { - const styleTag = '' + if (this.customCSS || this.serverConfig.instance.customizations.css) { + const styleTag = '' this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag) } }) @@ -227,25 +220,22 @@ export class AppComponent implements OnInit { } private async openModalsIfNeeded () { - this.serverService.configLoaded + this.authService.userInformationLoaded .pipe( - first(), - switchMap(() => this.authService.userInformationLoaded), map(() => this.authService.getUser()), filter(user => user.role === UserRole.ADMINISTRATOR) - ).subscribe(user => setTimeout(() => this.openAdminModals(user))) // setTimeout because of ngIf in template + ).subscribe(user => setTimeout(() => this._openAdminModalsIfNeeded(user))) // setTimeout because of ngIf in template } - private async openAdminModals (user: User) { + private async _openAdminModalsIfNeeded (user: User) { if (user.noWelcomeModal !== true) return this.welcomeModal.show() - const config = this.serverService.getConfig() - if (user.noInstanceConfigWarningModal === true || !config.signup.allowed) return + if (user.noInstanceConfigWarningModal === true || !this.serverConfig.signup.allowed) return this.instanceService.getAbout() .subscribe(about => { if ( - config.instance.name.toLowerCase() === 'peertube' || + this.serverConfig.instance.name.toLowerCase() === 'peertube' || !about.instance.terms || !about.instance.administrator || !about.instance.maintenanceLifetime diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 38b7328e2..dda705811 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -25,10 +25,10 @@ export function metaFactory (serverService: ServerService): MetaLoader { return new MetaStaticLoader({ pageTitlePositioning: PageTitlePositioning.PrependPageTitle, pageTitleSeparator: ' - ', - get applicationName () { return serverService.getConfig().instance.name }, + get applicationName () { return serverService.getTmpConfig().instance.name }, defaults: { - get title () { return serverService.getConfig().instance.name }, - get description () { return serverService.getConfig().instance.shortDescription } + get title () { return serverService.getTmpConfig().instance.name }, + get description () { return serverService.getTmpConfig().instance.shortDescription } } }) } diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index e24468da5..da5114048 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts @@ -70,9 +70,9 @@ export class PluginService implements ClientHook { } initializePlugins () { - this.server.configLoaded - .subscribe(() => { - this.plugins = this.server.getConfig().plugin.registered + this.server.getConfig() + .subscribe(config => { + this.plugins = config.plugin.registered this.buildScopeStruct() diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts index 43b89f08d..3982cf36f 100644 --- a/client/src/app/core/routing/redirect.service.ts +++ b/client/src/app/core/routing/redirect.service.ts @@ -16,15 +16,15 @@ export class RedirectService { private serverService: ServerService ) { // The config is first loaded from the cache so try to get the default route - const config = this.serverService.getConfig() - if (config && config.instance && config.instance.defaultClientRoute) { - RedirectService.DEFAULT_ROUTE = config.instance.defaultClientRoute + const tmpConfig = this.serverService.getTmpConfig() + if (tmpConfig && tmpConfig.instance && tmpConfig.instance.defaultClientRoute) { + RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute } // Load default route - this.serverService.configLoaded - .subscribe(() => { - const defaultRouteConfig = this.serverService.getConfig().instance.defaultClientRoute + this.serverService.getConfig() + .subscribe(config => { + const defaultRouteConfig = config.instance.defaultClientRoute if (defaultRouteConfig) { RedirectService.DEFAULT_ROUTE = defaultRouteConfig diff --git a/client/src/app/core/routing/server-config-resolver.service.ts b/client/src/app/core/routing/server-config-resolver.service.ts index ec7d6428f..3b7ed99bf 100644 --- a/client/src/app/core/routing/server-config-resolver.service.ts +++ b/client/src/app/core/routing/server-config-resolver.service.ts @@ -1,17 +1,13 @@ import { Injectable } from '@angular/core' import { Resolve } from '@angular/router' import { ServerService } from '@app/core/server' +import { ServerConfig } from '@shared/models' @Injectable() -export class ServerConfigResolver implements Resolve { - constructor ( - private server: ServerService - ) {} +export class ServerConfigResolver implements Resolve { + constructor (private server: ServerService) {} resolve () { - // FIXME: directly returning this.server.configLoaded does not seem to work - return new Promise(res => { - return this.server.configLoaded.subscribe(() => res(true)) - }) + return this.server.getConfig() } } diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index fdcc51cc5..ec904bf57 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts @@ -1,34 +1,36 @@ -import { map, shareReplay, switchMap, tap } from 'rxjs/operators' +import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators' import { HttpClient } from '@angular/common/http' import { Inject, Injectable, LOCALE_ID } from '@angular/core' import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' -import { Observable, of, ReplaySubject } from 'rxjs' +import { Observable, of, Subject } from 'rxjs' import { getCompleteLocale, ServerConfig } from '../../../../../shared' import { environment } from '../../../environments/environment' -import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos' +import { VideoConstant } from '../../../../../shared/models/videos' import { isDefaultLocale, peertubeTranslate } from '../../../../../shared/models/i18n' import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' import { sortBy } from '@app/shared/misc/utils' -import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' -import { cloneDeep } from 'lodash-es' @Injectable() export class ServerService { - private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server/' private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/' private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' private static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/' private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' - configLoaded = new ReplaySubject(1) - videoPrivaciesLoaded = new ReplaySubject(1) - videoPlaylistPrivaciesLoaded = new ReplaySubject(1) - videoCategoriesLoaded = new ReplaySubject(1) - videoLicencesLoaded = new ReplaySubject(1) - videoLanguagesLoaded = new ReplaySubject(1) - localeObservable: Observable + configReloaded = new Subject() + private localeObservable: Observable + private videoLicensesObservable: Observable[]> + private videoCategoriesObservable: Observable[]> + private videoPrivaciesObservable: Observable[]> + private videoPlaylistPrivaciesObservable: Observable[]> + private videoLanguagesObservable: Observable[]> + private configObservable: Observable + + private configReset = false + + private configLoaded = false private config: ServerConfig = { instance: { name: 'PeerTube', @@ -121,132 +123,141 @@ export class ServerService { enabled: true } } - private videoCategories: Array> = [] - private videoLicences: Array> = [] - private videoLanguages: Array> = [] - private videoPrivacies: Array> = [] - private videoPlaylistPrivacies: Array> = [] constructor ( private http: HttpClient, @Inject(LOCALE_ID) private localeId: string ) { - this.loadServerLocale() this.loadConfigLocally() } - loadConfig () { - this.http.get(ServerService.BASE_CONFIG_URL) - .pipe(tap(this.saveConfigLocally)) - .subscribe(data => { - this.config = data + getServerVersionAndCommit () { + const serverVersion = this.config.serverVersion + const commit = this.config.serverCommit || '' - this.configLoaded.next(true) - }) - } + let result = serverVersion + if (commit) result += '...' + commit - loadVideoCategories () { - return this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'categories', this.videoCategories, this.videoCategoriesLoaded, true) + return result } - loadVideoLicences () { - return this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'licences', this.videoLicences, this.videoLicencesLoaded) + resetConfig () { + this.configLoaded = false + this.configReset = true } - loadVideoLanguages () { - return this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'languages', this.videoLanguages, this.videoLanguagesLoaded, true) - } + getConfig () { + if (this.configLoaded) return of(this.config) - loadVideoPrivacies () { - return this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'privacies', this.videoPrivacies, this.videoPrivaciesLoaded) - } + if (!this.configObservable) { + this.configObservable = this.http.get(ServerService.BASE_CONFIG_URL) + .pipe( + tap(this.saveConfigLocally), + tap(() => this.configLoaded = true), + tap(() => { + if (this.configReset) { + this.configReloaded.next() + this.configReset = false + } + }), + share() + ) + } - loadVideoPlaylistPrivacies () { - return this.loadAttributeEnum( - ServerService.BASE_VIDEO_PLAYLIST_URL, - 'privacies', - this.videoPlaylistPrivacies, - this.videoPlaylistPrivaciesLoaded - ) + return this.configObservable } - getConfig () { - return cloneDeep(this.config) - } - - getServerVersionAndCommit () { - const serverVersion = this.config.serverVersion - const commit = this.config.serverCommit || '' - - let result = `v${serverVersion}` - if (commit) result += '...' + commit - - return result + getTmpConfig () { + return this.config } getVideoCategories () { - return cloneDeep(this.videoCategories) + if (!this.videoCategoriesObservable) { + this.videoCategoriesObservable = this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'categories', true) + } + + return this.videoCategoriesObservable.pipe(first()) } getVideoLicences () { - return cloneDeep(this.videoLicences) + if (!this.videoLicensesObservable) { + this.videoLicensesObservable = this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'licences') + } + + return this.videoLicensesObservable.pipe(first()) } getVideoLanguages () { - return cloneDeep(this.videoLanguages) + if (!this.videoLanguagesObservable) { + this.videoLanguagesObservable = this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'languages', true) + } + + return this.videoLanguagesObservable.pipe(first()) } getVideoPrivacies () { - return cloneDeep(this.videoPrivacies) + if (!this.videoPrivaciesObservable) { + this.videoPrivaciesObservable = this.loadAttributeEnum(ServerService.BASE_VIDEO_URL, 'privacies') + } + + return this.videoPrivaciesObservable.pipe(first()) } getVideoPlaylistPrivacies () { - return cloneDeep(this.videoPlaylistPrivacies) + if (!this.videoPlaylistPrivaciesObservable) { + this.videoPlaylistPrivaciesObservable = this.loadAttributeEnum(ServerService.BASE_VIDEO_PLAYLIST_URL, 'privacies') + } + + return this.videoPlaylistPrivaciesObservable.pipe(first()) + } + + getServerLocale () { + if (!this.localeObservable) { + const completeLocale = isOnDevLocale() ? getDevLocale() : getCompleteLocale(this.localeId) + + // Default locale, nothing to translate + if (isDefaultLocale(completeLocale)) { + this.localeObservable = of({}).pipe(shareReplay()) + } else { + this.localeObservable = this.http + .get(ServerService.BASE_LOCALE_URL + completeLocale + '/server.json') + .pipe(shareReplay()) + } + } + + return this.localeObservable.pipe(first()) } - private loadAttributeEnum ( + private loadAttributeEnum ( baseUrl: string, attributeName: 'categories' | 'licences' | 'languages' | 'privacies', - hashToPopulate: VideoConstant[], - notifier: ReplaySubject, sort = false ) { - this.localeObservable - .pipe( - switchMap(translations => { - return this.http.get<{ [id: string]: string }>(baseUrl + attributeName) - .pipe(map(data => ({ data, translations }))) - }) - ) - .subscribe(({ data, translations }) => { - Object.keys(data) - .forEach(dataKey => { - const label = data[ dataKey ] - - hashToPopulate.push({ - id: attributeName === 'languages' ? dataKey : parseInt(dataKey, 10), - label: peertubeTranslate(label, translations) - }) - }) - - if (sort === true) sortBy(hashToPopulate, 'label') - - notifier.next(true) - }) - } + return this.getServerLocale() + .pipe( + switchMap(translations => { + return this.http.get<{ [ id: string ]: string }>(baseUrl + attributeName) + .pipe(map(data => ({ data, translations }))) + }), + map(({ data, translations }) => { + const hashToPopulate: VideoConstant[] = [] - private loadServerLocale () { - const completeLocale = isOnDevLocale() ? getDevLocale() : getCompleteLocale(this.localeId) + Object.keys(data) + .forEach(dataKey => { + const label = data[ dataKey ] - // Default locale, nothing to translate - if (isDefaultLocale(completeLocale)) { - this.localeObservable = of({}).pipe(shareReplay()) - return - } + hashToPopulate.push({ + id: (attributeName === 'languages' ? dataKey : parseInt(dataKey, 10)) as T, + label: peertubeTranslate(label, translations) + }) + }) + + if (sort === true) sortBy(hashToPopulate, 'label') - this.localeObservable = this.http - .get(ServerService.BASE_LOCALE_URL + completeLocale + '/server.json') - .pipe(shareReplay()) + return hashToPopulate + }), + shareReplay() + ) } private saveConfigLocally (config: ServerConfig) { diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts index 3eebc1acc..2c5873cb3 100644 --- a/client/src/app/core/theme/theme.service.ts +++ b/client/src/app/core/theme/theme.service.ts @@ -3,7 +3,7 @@ import { AuthService } from '@app/core/auth' import { ServerService } from '@app/core/server' import { environment } from '../../../environments/environment' import { PluginService } from '@app/core/plugins/plugin.service' -import { ServerConfigTheme } from '@shared/models' +import { ServerConfig, ServerConfigTheme } from '@shared/models' import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' import { first } from 'rxjs/operators' @@ -20,6 +20,8 @@ export class ThemeService { private themeFromLocalStorage: ServerConfigTheme private themeDOMLinksFromLocalStorage: HTMLLinkElement[] = [] + private serverConfig: ServerConfig + constructor ( private auth: AuthService, private pluginService: PluginService, @@ -30,9 +32,12 @@ export class ThemeService { // Try to load from local storage first, so we don't have to wait network requests this.loadAndSetFromLocalStorage() - this.server.configLoaded - .subscribe(() => { - const themes = this.server.getConfig().theme.registered + this.serverConfig = this.server.getTmpConfig() + this.server.getConfig() + .subscribe(config => { + this.serverConfig = config + + const themes = this.serverConfig.theme.registered this.removeThemeFromLocalStorageIfNeeded(themes) this.injectThemes(themes) @@ -77,7 +82,7 @@ export class ThemeService { if (theme !== 'instance-default') return theme } - return this.server.getConfig().theme.default + return this.serverConfig.theme.default } private loadTheme (name: string) { diff --git a/client/src/app/login/login-routing.module.ts b/client/src/app/login/login-routing.module.ts index 5a41f4e7e..22f59b4d9 100644 --- a/client/src/app/login/login-routing.module.ts +++ b/client/src/app/login/login-routing.module.ts @@ -15,7 +15,7 @@ const loginRoutes: Routes = [ } }, resolve: { - serverConfigLoaded: ServerConfigResolver + serverConfig: ServerConfigResolver } } ] diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index 911b9982f..cf923492a 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts @@ -7,7 +7,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { LoginValidatorsService } from '@app/shared/forms/form-validators/login-validators.service' import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' -import { Router } from '@angular/router' +import { ActivatedRoute, Router } from '@angular/router' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-login', @@ -23,10 +24,12 @@ export class LoginComponent extends FormReactive implements OnInit { forgotPasswordEmail = '' private openedForgotPasswordModal: NgbModalRef + private serverConfig: ServerConfig constructor ( - public router: Router, protected formValidatorService: FormValidatorService, + private router: Router, + private route: ActivatedRoute, private modalService: NgbModal, private loginValidatorsService: LoginValidatorsService, private authService: AuthService, @@ -40,14 +43,16 @@ export class LoginComponent extends FormReactive implements OnInit { } get signupAllowed () { - return this.serverService.getConfig().signup.allowed === true + return this.serverConfig.signup.allowed === true } isEmailDisabled () { - return this.serverService.getConfig().email.enabled === false + return this.serverConfig.email.enabled === false } ngOnInit () { + this.serverConfig = this.route.snapshot.data.serverConfig + this.buildForm({ username: this.loginValidatorsService.LOGIN_USERNAME, password: this.loginValidatorsService.LOGIN_PASSWORD diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index c7c31577a..2d522b521 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts @@ -4,6 +4,7 @@ import { AuthService, AuthStatus, RedirectService, ServerService, ThemeService } import { User } from '../shared/users/user.model' import { LanguageChooserComponent } from '@app/menu/language-chooser.component' import { HotkeysService } from 'angular2-hotkeys' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-menu', @@ -18,6 +19,7 @@ export class MenuComponent implements OnInit { userHasAdminAccess = false helpVisible = false + private serverConfig: ServerConfig private routesPerRight: { [ role in UserRight ]?: string } = { [UserRight.MANAGE_USERS]: '/admin/users', [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', @@ -36,6 +38,10 @@ export class MenuComponent implements OnInit { ) {} ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.isLoggedIn = this.authService.isLoggedIn() if (this.isLoggedIn === true) this.user = this.authService.getUser() this.computeIsUserHasAdminAccess() @@ -64,8 +70,8 @@ export class MenuComponent implements OnInit { } isRegistrationAllowed () { - return this.serverService.getConfig().signup.allowed && - this.serverService.getConfig().signup.allowedForCurrentIP + return this.serverConfig.signup.allowed && + this.serverConfig.signup.allowedForCurrentIP } getFirstAdminRightAvailable () { diff --git a/client/src/app/search/search-filters.component.ts b/client/src/app/search/search-filters.component.ts index 57131fcac..344a260df 100644 --- a/client/src/app/search/search-filters.component.ts +++ b/client/src/app/search/search-filters.component.ts @@ -1,10 +1,10 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core' +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { ValidatorFn } from '@angular/forms' import { VideoValidatorsService } from '@app/shared' import { ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { AdvancedSearch } from '@app/search/advanced-search.model' -import { VideoConstant } from '../../../../shared' +import { ServerConfig, VideoConstant } from '../../../../shared' @Component({ selector: 'my-search-filters', @@ -33,6 +33,8 @@ export class SearchFiltersComponent implements OnInit { originallyPublishedStartYear: string originallyPublishedEndYear: string + private serverConfig: ServerConfig + constructor ( private i18n: I18n, private videoValidatorsService: VideoValidatorsService, @@ -99,9 +101,13 @@ export class SearchFiltersComponent implements OnInit { } ngOnInit () { - this.serverService.videoCategoriesLoaded.subscribe(() => this.videoCategories = this.serverService.getVideoCategories()) - this.serverService.videoLicencesLoaded.subscribe(() => this.videoLicences = this.serverService.getVideoLicences()) - this.serverService.videoLanguagesLoaded.subscribe(() => this.videoLanguages = this.serverService.getVideoLanguages()) + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + + this.serverService.getVideoCategories().subscribe(categories => this.videoCategories = categories) + this.serverService.getVideoLicences().subscribe(licences => this.videoLicences = licences) + this.serverService.getVideoLanguages().subscribe(languages => this.videoLanguages = languages) this.loadFromDurationRange() this.loadFromPublishedRange() diff --git a/client/src/app/shared/images/preview-upload.component.ts b/client/src/app/shared/images/preview-upload.component.ts index 44b78866e..f56f5b1f8 100644 --- a/client/src/app/shared/images/preview-upload.component.ts +++ b/client/src/app/shared/images/preview-upload.component.ts @@ -2,6 +2,7 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' import { ServerService } from '@app/core' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-preview-upload', @@ -24,6 +25,7 @@ export class PreviewUploadComponent implements OnInit, ControlValueAccessor { imageSrc: SafeResourceUrl allowedExtensionsMessage = '' + private serverConfig: ServerConfig private file: File constructor ( @@ -32,14 +34,18 @@ export class PreviewUploadComponent implements OnInit, ControlValueAccessor { ) {} get videoImageExtensions () { - return this.serverService.getConfig().video.image.extensions + return this.serverConfig.video.image.extensions } get maxVideoImageSize () { - return this.serverService.getConfig().video.image.size.max + return this.serverConfig.video.image.size.max } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.allowedExtensionsMessage = this.videoImageExtensions.join(', ') } diff --git a/client/src/app/shared/instance/instance-features-table.component.html b/client/src/app/shared/instance/instance-features-table.component.html index f880a886f..fd8b3354f 100644 --- a/client/src/app/shared/instance/instance-features-table.component.html +++ b/client/src/app/shared/instance/instance-features-table.component.html @@ -1,6 +1,6 @@
- +
@@ -19,7 +19,7 @@ @@ -30,15 +30,15 @@ @@ -69,14 +69,14 @@ @@ -88,7 +88,7 @@
PeerTube version
User registration allowed - +
Transcoding in multiple resolutions - +
Video uploads - Requires manual validation by moderators - Automatically published + Requires manual validation by moderators + Automatically published
HTTP import (YouTube, Vimeo, direct URL...) - +
Torrent import - +
P2P enabled - +
diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/instance/instance-features-table.component.ts index 1661f1efe..8fd15ebad 100644 --- a/client/src/app/shared/instance/instance-features-table.component.ts +++ b/client/src/app/shared/instance/instance-features-table.component.ts @@ -10,7 +10,7 @@ import { ServerConfig } from '@shared/models' }) export class InstanceFeaturesTableComponent implements OnInit { quotaHelpIndication = '' - config: ServerConfig + serverConfig: ServerConfig constructor ( private i18n: I18n, @@ -19,29 +19,34 @@ export class InstanceFeaturesTableComponent implements OnInit { } get initialUserVideoQuota () { - return this.serverService.getConfig().user.videoQuota + return this.serverConfig.user.videoQuota } get dailyUserVideoQuota () { - return Math.min(this.initialUserVideoQuota, this.serverService.getConfig().user.videoQuotaDaily) + return Math.min(this.initialUserVideoQuota, this.serverConfig.user.videoQuotaDaily) } ngOnInit () { - this.serverService.configLoaded - .subscribe(() => { - this.config = this.serverService.getConfig() + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => { + this.serverConfig = config this.buildQuotaHelpIndication() }) } buildNSFWLabel () { - const policy = this.serverService.getConfig().instance.defaultNSFWPolicy + const policy = this.serverConfig.instance.defaultNSFWPolicy if (policy === 'do_not_list') return this.i18n('Hidden') if (policy === 'blur') return this.i18n('Blurred with confirmation request') if (policy === 'display') return this.i18n('Displayed') } + getServerVersionAndCommit () { + return this.serverService.getServerVersionAndCommit() + } + private getApproximateTime (seconds: number) { const hours = Math.floor(seconds / 3600) let pluralSuffix = '' @@ -53,10 +58,6 @@ export class InstanceFeaturesTableComponent implements OnInit { return this.i18n('~ {{minutes}} {minutes, plural, =1 {minute} other {minutes}}', { minutes }) } - getServerVersionAndCommit () { - return this.serverService.getServerVersionAndCommit() - } - private buildQuotaHelpIndication () { if (this.initialUserVideoQuota === -1) return diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/instance/instance.service.ts index 44b413fa4..8b26063fb 100644 --- a/client/src/app/shared/instance/instance.service.ts +++ b/client/src/app/shared/instance/instance.service.ts @@ -1,4 +1,4 @@ -import { catchError } from 'rxjs/operators' +import { catchError, map } from 'rxjs/operators' import { HttpClient } from '@angular/common/http' import { Injectable } from '@angular/core' import { environment } from '../../../environments/environment' @@ -7,6 +7,7 @@ import { About } from '../../../../../shared/models/server' import { MarkdownService } from '@app/shared/renderer' import { peertubeTranslate } from '@shared/models' import { ServerService } from '@app/core' +import { forkJoin } from 'rxjs' @Injectable() export class InstanceService { @@ -57,25 +58,35 @@ export class InstanceService { return html } - buildTranslatedLanguages (about: About, translations: any) { - const languagesArray = this.serverService.getVideoLanguages() + buildTranslatedLanguages (about: About) { + return forkJoin([ + this.serverService.getVideoLanguages(), + this.serverService.getServerLocale() + ]).pipe( + map(([ languagesArray, translations ]) => { + return about.instance.languages + .map(l => { + const languageObj = languagesArray.find(la => la.id === l) - return about.instance.languages - .map(l => { - const languageObj = languagesArray.find(la => la.id === l) - - return peertubeTranslate(languageObj.label, translations) - }) + return peertubeTranslate(languageObj.label, translations) + }) + }) + ) } - buildTranslatedCategories (about: About, translations: any) { - const categoriesArray = this.serverService.getVideoCategories() - - return about.instance.categories - .map(c => { - const categoryObj = categoriesArray.find(ca => ca.id === c) + buildTranslatedCategories (about: About) { + return forkJoin([ + this.serverService.getVideoCategories(), + this.serverService.getServerLocale() + ]).pipe( + map(([ categoriesArray, translations ]) => { + return about.instance.categories + .map(c => { + const categoryObj = categoriesArray.find(ca => ca.id === c) - return peertubeTranslate(categoryObj.label, translations) - }) + return peertubeTranslate(categoryObj.label, translations) + }) + }) + ) } } diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts index e9d4c1437..d82dc3d94 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' @@ -7,12 +7,13 @@ import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' import { User, UserRight } from '../../../../../shared/models/users' import { Account } from '@app/shared/account/account.model' import { BlocklistService } from '@app/shared/blocklist' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-user-moderation-dropdown', templateUrl: './user-moderation-dropdown.component.html' }) -export class UserModerationDropdownComponent implements OnChanges { +export class UserModerationDropdownComponent implements OnInit, OnChanges { @ViewChild('userBanModal', { static: false }) userBanModal: UserBanModalComponent @Input() user: User @@ -26,6 +27,8 @@ export class UserModerationDropdownComponent implements OnChanges { userActions: DropdownAction<{ user: User, account: Account }>[][] = [] + private serverConfig: ServerConfig + constructor ( private authService: AuthService, private notifier: Notifier, @@ -38,7 +41,13 @@ export class UserModerationDropdownComponent implements OnChanges { ) { } get requiresEmailVerification () { - return this.serverService.getConfig().signup.requiresEmailVerification + return this.serverConfig.signup.requiresEmailVerification + } + + ngOnInit (): void { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) } ngOnChanges () { diff --git a/client/src/app/shared/overview/overview.service.ts b/client/src/app/shared/overview/overview.service.ts index bd4068925..79cb781f7 100644 --- a/client/src/app/shared/overview/overview.service.ts +++ b/client/src/app/shared/overview/overview.service.ts @@ -60,7 +60,7 @@ export class OverviewService { .pipe( // Translate categories switchMap(() => { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe( tap(translations => { for (const c of videosOverviewResult.categories) { diff --git a/client/src/app/shared/video-caption/video-caption.service.ts b/client/src/app/shared/video-caption/video-caption.service.ts index 977f6253a..6bfe67435 100644 --- a/client/src/app/shared/video-caption/video-caption.service.ts +++ b/client/src/app/shared/video-caption/video-caption.service.ts @@ -22,7 +22,7 @@ export class VideoCaptionService { return this.authHttp.get>(VideoService.BASE_VIDEO_URL + videoId + '/captions') .pipe( switchMap(captionsResult => { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe(map(translations => ({ captionsResult, translations }))) }), map(({ captionsResult, translations }) => { diff --git a/client/src/app/shared/video-import/video-import.service.ts b/client/src/app/shared/video-import/video-import.service.ts index 7ae13154d..3e3fb7dfb 100644 --- a/client/src/app/shared/video-import/video-import.service.ts +++ b/client/src/app/shared/video-import/video-import.service.ts @@ -91,7 +91,7 @@ export class VideoImportService { } private extractVideoImports (result: ResultList): Observable> { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe( map(translations => { result.data.forEach(d => diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts index a8e5a4885..cd592eab0 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts +++ b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core' +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' import { Video } from '@app/shared/video/video.model' -import { VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models' +import { ServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models' import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' import { ActivatedRoute } from '@angular/router' import { I18n } from '@ngx-translate/i18n-polyfill' @@ -17,7 +17,7 @@ import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist- templateUrl: './video-playlist-element-miniature.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class VideoPlaylistElementMiniatureComponent { +export class VideoPlaylistElementMiniatureComponent implements OnInit { @ViewChild('moreDropdown', { static: false }) moreDropdown: NgbDropdown @Input() playlist: VideoPlaylist @@ -39,6 +39,8 @@ export class VideoPlaylistElementMiniatureComponent { stopTimestamp: number } = {} as any + private serverConfig: ServerConfig + constructor ( private authService: AuthService, private serverService: ServerService, @@ -51,6 +53,15 @@ export class VideoPlaylistElementMiniatureComponent { private cdr: ChangeDetectorRef ) {} + ngOnInit (): void { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => { + this.serverConfig = config + this.cdr.detectChanges() + }) + } + isUnavailable (e: VideoPlaylistElement) { return e.type === VideoPlaylistElementType.UNAVAILABLE } @@ -80,7 +91,7 @@ export class VideoPlaylistElementMiniatureComponent { } isVideoBlur (video: Video) { - return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig()) + return video.isVideoNSFWForUser(this.authService.getUser(), this.serverConfig) } removeFromPlaylist (playlistElement: VideoPlaylistElement) { diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts index 42791af86..2945b4959 100644 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/video-playlist/video-playlist.service.ts @@ -173,7 +173,7 @@ export class VideoPlaylistService { } extractPlaylists (result: ResultList) { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe( map(translations => { const playlistsJSON = result.data @@ -190,12 +190,12 @@ export class VideoPlaylistService { } extractPlaylist (playlist: VideoPlaylistServerModel) { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe(map(translations => new VideoPlaylist(playlist, translations))) } extractVideoPlaylistElements (result: ResultList) { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe( map(translations => { const elementsJson = result.data diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index 2926b179b..faeea27d9 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts @@ -13,7 +13,7 @@ import { Notifier, ServerService } from '@app/core' import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' import { I18n } from '@ngx-translate/i18n-polyfill' import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' -import { ResultList } from '@shared/models' +import { ResultList, ServerConfig } from '@shared/models' enum GroupDate { UNKNOWN = 0, @@ -61,6 +61,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor onDataSubject = new Subject() + protected serverConfig: ServerConfig + protected abstract notifier: Notifier protected abstract authService: AuthService protected abstract route: ActivatedRoute @@ -85,6 +87,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.groupedDateLabels = { [GroupDate.UNKNOWN]: null, [GroupDate.TODAY]: this.i18n('Today'), @@ -251,7 +257,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor } let path = this.router.url - if (!path || path === '/') path = this.serverService.getConfig().instance.defaultClientRoute + if (!path || path === '/') path = this.serverConfig.instance.defaultClientRoute this.router.navigate([ path ], { queryParams, replaceUrl: true, queryParamsHandling: 'merge' }) } diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index d5c7dfd9b..9fffc7ddb 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, LOCALE import { User } from '../users' import { Video } from './video.model' import { ServerService } from '@app/core' -import { VideoPrivacy, VideoState } from '../../../../../shared' +import { ServerConfig, VideoPrivacy, VideoState } from '../../../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component' import { ScreenService } from '@app/shared/misc/screen.service' @@ -55,6 +55,7 @@ export class VideoMiniatureComponent implements OnInit { report: true } showActions = false + serverConfig: ServerConfig private ownerDisplayTypeChosen: 'account' | 'videoChannel' @@ -66,10 +67,14 @@ export class VideoMiniatureComponent implements OnInit { ) { } get isVideoBlur () { - return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) + return this.video.isVideoNSFWForUser(this.user, this.serverConfig) } ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + this.setUpBy() // We rely on mouseenter to lazy load actions diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index b0fa55966..9adf46495 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts @@ -64,7 +64,7 @@ export class VideoService implements VideosProvider { } getVideo (options: { videoId: string }): Observable { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe( switchMap(translations => { return this.authHttp.get(VideoService.BASE_VIDEO_URL + options.videoId) @@ -315,7 +315,7 @@ export class VideoService implements VideosProvider { } extractVideos (result: ResultList) { - return this.serverService.localeObservable + return this.serverService.getServerLocale() .pipe( map(translations => { const videosJson = result.data diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts index 86c6e03e7..1a9bf5171 100644 --- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts @@ -5,7 +5,7 @@ import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validator import { ServerService } from '@app/core' import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' -import { VideoConstant } from '../../../../../../shared' +import { ServerConfig, VideoConstant } from '../../../../../../shared' @Component({ selector: 'my-video-caption-add-modal', @@ -15,6 +15,7 @@ import { VideoConstant } from '../../../../../../shared' export class VideoCaptionAddModalComponent extends FormReactive implements OnInit { @Input() existingCaptions: string[] + @Input() serverConfig: ServerConfig @Output() captionAdded = new EventEmitter() @@ -35,15 +36,16 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni } get videoCaptionExtensions () { - return this.serverService.getConfig().videoCaption.file.extensions + return this.serverConfig.videoCaption.file.extensions } get videoCaptionMaxSize () { - return this.serverService.getConfig().videoCaption.file.size.max + return this.serverConfig.videoCaption.file.size.max } ngOnInit () { - this.videoCaptionLanguages = this.serverService.getVideoLanguages() + this.serverService.getVideoLanguages() + .subscribe(languages => this.videoCaptionLanguages = languages) this.buildForm({ language: this.videoCaptionsValidatorsService.VIDEO_CAPTION_LANGUAGE, diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html index e2a222037..e40649d95 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html @@ -269,5 +269,5 @@
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts index d0d5e2a2b..982e071ad 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts @@ -12,7 +12,7 @@ import { VideoCaptionService } from '@app/shared/video-caption' import { VideoCaptionAddModalComponent } from '@app/videos/+video-edit/shared/video-caption-add-modal.component' import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' import { removeElementFromArray } from '@app/shared/misc/utils' -import { VideoConstant, VideoPrivacy } from '../../../../../../shared' +import { ServerConfig, VideoConstant, VideoPrivacy } from '../../../../../../shared' import { VideoService } from '@app/shared/video/video.service' @Component({ @@ -51,6 +51,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { calendarTimezone: string calendarDateFormat: string + serverConfig: ServerConfig + private schedulerInterval: any private firstPatchDone = false private initialVideoCaptions: string[] = [] @@ -130,12 +132,19 @@ export class VideoEditComponent implements OnInit, OnDestroy { ngOnInit () { this.updateForm() - this.videoCategories = this.serverService.getVideoCategories() - this.videoLicences = this.serverService.getVideoLicences() - this.videoLanguages = this.serverService.getVideoLanguages() + this.serverService.getVideoCategories() + .subscribe(res => this.videoCategories = res) + this.serverService.getVideoLicences() + .subscribe(res => this.videoLicences = res) + this.serverService.getVideoLanguages() + .subscribe(res => this.videoLanguages = res) + + this.serverService.getVideoPrivacies() + .subscribe(privacies => this.videoPrivacies = this.videoService.explainedPrivacyLabels(privacies)) - const privacies = this.serverService.getVideoPrivacies() - this.videoPrivacies = this.videoService.explainedPrivacyLabels(privacies) + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id) diff --git a/client/src/app/videos/+video-edit/video-add-components/video-send.ts b/client/src/app/videos/+video-edit/video-add-components/video-send.ts index 580c123a0..b32f16950 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-send.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-send.ts @@ -3,7 +3,7 @@ import { LoadingBarService } from '@ngx-loading-bar/core' import { AuthService, Notifier, ServerService } from '@app/core' import { catchError, switchMap, tap } from 'rxjs/operators' import { FormReactive } from '@app/shared' -import { VideoConstant, VideoPrivacy } from '../../../../../../shared' +import { ServerConfig, VideoConstant, VideoPrivacy } from '../../../../../../shared' import { VideoService } from '@app/shared/video/video.service' import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' import { VideoCaptionService } from '@app/shared/video-caption' @@ -29,6 +29,7 @@ export abstract class VideoSend extends FormReactive implements OnInit { protected serverService: ServerService protected videoService: VideoService protected videoCaptionService: VideoCaptionService + protected serverConfig: ServerConfig abstract canDeactivate (): CanComponentDeactivateResult @@ -38,10 +39,14 @@ export abstract class VideoSend extends FormReactive implements OnInit { populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) .then(() => this.firstStepChannelId = this.userVideoChannels[ 0 ].id) - this.serverService.videoPrivaciesLoaded + this.serverConfig = this.serverService.getTmpConfig() + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + + this.serverService.getVideoPrivacies() .subscribe( - () => { - this.videoPrivacies = this.serverService.getVideoPrivacies() + privacies => { + this.videoPrivacies = privacies this.firstStepPrivacyId = this.DEFAULT_VIDEO_PRIVACY }) diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts index 23b79edd3..28e10e562 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts @@ -14,6 +14,7 @@ import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard. import { FormValidatorService, UserService } from '@app/shared' import { VideoCaptionService } from '@app/shared/video-caption' import { scrollToTop } from '@app/shared/misc/utils' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-video-upload', @@ -70,7 +71,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy } get videoExtensions () { - return this.serverService.getConfig().video.file.extensions.join(',') + return this.serverConfig.video.file.extensions.join(',') } ngOnInit () { @@ -155,7 +156,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy } const privacy = this.firstStepPrivacyId.toString() - const nsfw = this.serverService.getConfig().instance.isNSFW + const nsfw = this.serverConfig.instance.isNSFW const waitTranscoding = true const commentsEnabled = true const downloadEnabled = true diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 911bc884e..401d8a08f 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts @@ -1,28 +1,37 @@ -import { Component, HostListener, ViewChild } from '@angular/core' +import { Component, HostListener, OnInit, ViewChild } from '@angular/core' import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service' import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component' import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component' import { AuthService, ServerService } from '@app/core' import { VideoImportTorrentComponent } from '@app/videos/+video-edit/video-add-components/video-import-torrent.component' +import { ServerConfig } from '@shared/models' @Component({ selector: 'my-videos-add', templateUrl: './video-add.component.html', styleUrls: [ './video-add.component.scss' ] }) -export class VideoAddComponent implements CanComponentDeactivate { +export class VideoAddComponent implements OnInit, CanComponentDeactivate { @ViewChild('videoUpload', { static: false }) videoUpload: VideoUploadComponent @ViewChild('videoImportUrl', { static: false }) videoImportUrl: VideoImportUrlComponent @ViewChild('videoImportTorrent', { static: false }) videoImportTorrent: VideoImportTorrentComponent secondStepType: 'upload' | 'import-url' | 'import-torrent' videoName: string + serverConfig: ServerConfig constructor ( private auth: AuthService, private serverService: ServerService ) {} + ngOnInit () { + this.serverConfig = this.serverService.getTmpConfig() + + this.serverService.getConfig() + .subscribe(config => this.serverConfig = config) + } + onFirstStepDone (type: 'upload' | 'import-url' | 'import-torrent', videoName: string) { this.secondStepType = type this.videoName = videoName @@ -52,11 +61,11 @@ export class VideoAddComponent implements CanComponentDeactivate { } isVideoImportHttpEnabled () { - return this.serverService.getConfig().import.videos.http.enabled + return this.serverConfig.import.videos.http.enabled } isVideoImportTorrentEnabled () { - return this.serverService.getConfig().import.videos.torrent.enabled + return this.serverConfig.import.videos.torrent.enabled } isInSecondStep () { 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 8cc1e8b58..626d0ca07 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -8,7 +8,7 @@ import { MetaService } from '@ngx-meta/core' import { AuthUser, Notifier, ServerService } from '@app/core' import { forkJoin, Observable, Subscription } from 'rxjs' import { Hotkey, HotkeysService } from 'angular2-hotkeys' -import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' +import { ServerConfig, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' import { AuthService, ConfirmService } from '../../core' import { RestExtractor, VideoBlacklistService } from '../../shared' import { VideoDetails } from '../../shared/video/video-details.model' @@ -84,6 +84,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { private queryParamsSub: Subscription private configSub: Subscription + private serverConfig: ServerConfig + constructor ( private elementRef: ElementRef, private changeDetector: ChangeDetectorRef, @@ -120,11 +122,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } async ngOnInit () { - this.configSub = this.serverService.configLoaded - .subscribe(() => { + this.serverConfig = this.serverService.getTmpConfig() + + this.configSub = this.serverService.getConfig() + .subscribe(config => { + this.serverConfig = config + if ( isWebRTCDisabled() || - this.serverService.getConfig().tracker.enabled === false || + this.serverConfig.tracker.enabled === false || peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' ) { this.hasAlreadyAcceptedPrivacyConcern = true @@ -280,7 +286,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } isVideoBlur (video: Video) { - return video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) + return video.isVideoNSFWForUser(this.user, this.serverConfig) } isAutoPlayEnabled () { 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 aff8413eb..f94a7da04 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 @@ -40,11 +40,8 @@ export class VideoMostLikedComponent extends AbstractVideoList implements OnInit this.generateSyndicationList() - this.serverService.configLoaded.subscribe( - () => { - this.titlePage = this.i18n('Most liked videos') - this.titleTooltip = this.i18n('Videos that have the higher number of likes.') - }) + this.titlePage = this.i18n('Most liked videos') + this.titleTooltip = this.i18n('Videos that have the higher number of likes.') } getVideosObservable (page: number) { 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 19324da63..bc88679fa 100644 --- a/client/src/app/videos/video-list/video-trending.component.ts +++ b/client/src/app/videos/video-list/video-trending.component.ts @@ -40,9 +40,9 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit, this.generateSyndicationList() - this.serverService.configLoaded.subscribe( - () => { - const trendingDays = this.serverService.getConfig().trending.videos.intervalDays + this.serverService.getConfig().subscribe( + config => { + const trendingDays = config.trending.videos.intervalDays if (trendingDays === 1) { this.titlePage = this.i18n('Trending for the last 24 hours') diff --git a/scripts/build/index.sh b/scripts/build/index.sh index 12359e68f..31f2733c3 100755 --- a/scripts/build/index.sh +++ b/scripts/build/index.sh @@ -8,6 +8,6 @@ else clientCommand="npm run build:client" fi -npm run concurrently -- --raw \ +npm run concurrently -- --raw \w "$clientCommand" \ "npm run build:server" -- 2.41.0