Lazy load static objects
authorChocobozzz <me@florianbigard.com>
Wed, 18 Dec 2019 14:31:54 +0000 (15:31 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 18 Dec 2019 14:40:59 +0000 (15:40 +0100)
55 files changed:
client/src/app/+about/about-instance/about-instance.component.ts
client/src/app/+about/about-instance/contact-admin-modal.component.ts
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/+admin/moderation/moderation.component.ts
client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts
client/src/app/+admin/users/user-edit/user-create.component.ts
client/src/app/+admin/users/user-edit/user-edit.ts
client/src/app/+admin/users/user-edit/user-update.component.ts
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
client/src/app/+my-account/my-account-settings/my-account-interface/my-account-interface-settings.component.ts
client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts
client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts
client/src/app/+my-account/my-account.component.ts
client/src/app/+my-account/shared/actor-avatar-info.component.ts
client/src/app/+signup/+register/register-routing.module.ts
client/src/app/+signup/+register/register.component.ts
client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
client/src/app/app.component.ts
client/src/app/app.module.ts
client/src/app/core/plugins/plugin.service.ts
client/src/app/core/routing/redirect.service.ts
client/src/app/core/routing/server-config-resolver.service.ts
client/src/app/core/server/server.service.ts
client/src/app/core/theme/theme.service.ts
client/src/app/login/login-routing.module.ts
client/src/app/login/login.component.ts
client/src/app/menu/menu.component.ts
client/src/app/search/search-filters.component.ts
client/src/app/shared/images/preview-upload.component.ts
client/src/app/shared/instance/instance-features-table.component.html
client/src/app/shared/instance/instance-features-table.component.ts
client/src/app/shared/instance/instance.service.ts
client/src/app/shared/moderation/user-moderation-dropdown.component.ts
client/src/app/shared/overview/overview.service.ts
client/src/app/shared/video-caption/video-caption.service.ts
client/src/app/shared/video-import/video-import.service.ts
client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts
client/src/app/shared/video-playlist/video-playlist.service.ts
client/src/app/shared/video/abstract-video-list.ts
client/src/app/shared/video/video-miniature.component.ts
client/src/app/shared/video/video.service.ts
client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/app/videos/+video-edit/video-add-components/video-send.ts
client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
client/src/app/videos/+video-edit/video-add.component.ts
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/app/videos/video-list/video-most-liked.component.ts
client/src/app/videos/video-list/video-trending.component.ts
scripts/build/index.sh

index 16ccae2e27c6a879905b3e44692863ff6e5abe4c..87beb13dad51743bb92317e9176dc0b3867149cd 100644 (file)
@@ -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'))
index 878d49b55d1eacb03ba400c8b8868723f3224026..2ed41e7419c297a928d8162557588ca4ac1bd9ab 100644 (file)
@@ -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,
index 1f67512974feb81c0c5677a13b42389f983a907b..25e06d8a1dabf58290e82d531b55ece726884021 100644 (file)
@@ -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()
 
index 47154af3ffb6cd53b7ff9aef2547d92ee9002b82..7744deb06ba2d768d6e332dfc7c34fa798627d98 100644 (file)
@@ -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 () {
index f4bce7c48a69de977210e7eb7aa6b2a4720ed88f..5876f658b0856cc0a50a2fe5f90dd4d47e023095 100644 (file)
@@ -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)
   }
index 3b57a49c6df07ecd722e108ed844c9ef9f32506e..e726ec4d7f75232a66b38653676964e268f5f7ef 100644 (file)
@@ -34,6 +34,8 @@ export class UserCreateComponent extends UserEdit implements OnInit {
   }
 
   ngOnInit () {
+    super.ngOnInit()
+
     const defaultValues = {
       role: UserRole.USER.toString(),
       videoQuota: '-1',
index 6625d65d6ee8709b2bde8a0d6b42cad317aabeb6..02f1dcd42cae6d3e0bd8e363dc73a2d13286d016 100644 (file)
@@ -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
index c7052a9256c7431f254fc05ce3c27c0104fe79c9..d1682a99df4e51ee2a059a8ad20418c36594bbc9 100644 (file)
@@ -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,
index ab82713b2bc99357a4a67bfe680ec03dc538b52c..1083ba29112c82d7f7dd69d28ad0368d379effe4 100644 (file)
@@ -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<User[]>[] = []
 
+  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 = [
index ec7cf935c7464d54c773916a0919f0362ac54cd5..9d406805fae8d1ec796bdb1bf20ab8ccf3430693 100644 (file)
@@ -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
+        }
+      )
   }
 }
index 5ec1c9f8fc31b95ee6d372d283363569a792d2d5..441f89f10729f27653738e23cc42e79831dea4de 100644 (file)
@@ -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<any>
 
+  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
     })
index 76fabb19d905aab08835395878c4ae25c9abe0cb..6ba1a10204d6a83b0b07f21e2f196bef427db6e6 100644 (file)
@@ -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<UserNotificationSetting> ]: 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())
   }
 
index 99eee23b8d6fe53bbcf6b4e938af5c477d27d680..a66159b3f73429e08f0d991f4ed1fa0b2da0297b 100644 (file)
@@ -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 })))
index 081e956d27e6d1fefc8ca8af72d6dae6de7b4928..9c948b367be93ff684f13f938c1b66c1fc1382e6 100644 (file)
@@ -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 () {
index 8aed8b5135cc7f0d329e0542f8106674a0ed1565..e47e5f9807820c9d453e349926523cfb7b90cb1e 100644 (file)
@@ -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 () {
index 917ad725844cfcd45f72ea0bf29b89d1775074a2..2f85cdd963f62f17fc832ee40d8e67fe4777d308 100644 (file)
@@ -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()
                            },
index d98d06f8e7f619c25a2ee8c95b73a17dfd7e5dbc..05dcf522d4c13bdf6b8ba1be1fe161c6f0cf7492 100644 (file)
@@ -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
   }
index 0289a66c3b8fee90bfa69dbc474ea4df45ce5969..101dfa5569e4c255f4097c40cad8d538c322ea19 100644 (file)
@@ -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<HTMLInputElement>
 
   @Input() actor: VideoChannel | Account
 
   @Output() avatarChange = new EventEmitter<FormData>()
 
+  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(',')
   }
 }
index e3a5001dc5d945f236cb88651c167e34e48f529a..f47e80755275b1a99ece1603ecdab8deba9eb742 100644 (file)
@@ -16,7 +16,7 @@ const registerRoutes: Routes = [
       }
     },
     resolve: {
-      serverConfigLoaded: ServerConfigResolver
+      serverConfig: ServerConfigResolver
     }
   }
 ]
index acec56f04293439a3255f9f88ec63800689ba51a..ae944ec15b57594f4ca605a7e06b3ce6c9414d6c 100644 (file)
@@ -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 => {
index cfd471fa400eaeb210e5930475f9708643246759..3bd604b662ca7fdcec0e2071803bd33b676d3668 100644 (file)
@@ -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
     })
index 351620c59da1ab216ac5e8e9b7fa29544b89bf67..883f365142295152e73841ac25b5e077e6f38a59 100644 (file)
@@ -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 = '<style>' + config.instance.customizations.css + '</style>'
+          if (this.customCSS || this.serverConfig.instance.customizations.css) {
+            const styleTag = '<style>' + this.serverConfig.instance.customizations.css + '</style>'
             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
index 38b7328e21951f913d9d897b925e3a117f7b78cb..dda705811fb8bc8b8867081ad37b6f94139a0dd9 100644 (file)
@@ -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 }
     }
   })
 }
index e24468da553f12c653ad6b2a6fa9270df8934355..da5114048a994a896c790688ce88a6860dbad27d 100644 (file)
@@ -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()
 
index 43b89f08dc8e194f1ca7e4759d5c6d13fb46b1bf..3982cf36f31a6e1eea1559f2bea65f541a599046 100644 (file)
@@ -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
index ec7d6428f55ff2949a60ca53bceedfa58e7bb795..3b7ed99bfda89924522985211cc64eb88f11cb7d 100644 (file)
@@ -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<boolean> {
-  constructor (
-    private server: ServerService
-  ) {}
+export class ServerConfigResolver implements Resolve<ServerConfig> {
+  constructor (private server: ServerService) {}
 
   resolve () {
-    // FIXME: directly returning this.server.configLoaded does not seem to work
-    return new Promise<boolean>(res => {
-      return this.server.configLoaded.subscribe(() => res(true))
-    })
+    return this.server.getConfig()
   }
 }
index fdcc51cc5b28832a7266ea2de4f922788eebb4b2..ec904bf57b96dcee88c99e6c2ac70137ed9c7675 100644 (file)
@@ -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<boolean>(1)
-  videoPrivaciesLoaded = new ReplaySubject<boolean>(1)
-  videoPlaylistPrivaciesLoaded = new ReplaySubject<boolean>(1)
-  videoCategoriesLoaded = new ReplaySubject<boolean>(1)
-  videoLicencesLoaded = new ReplaySubject<boolean>(1)
-  videoLanguagesLoaded = new ReplaySubject<boolean>(1)
-  localeObservable: Observable<any>
+  configReloaded = new Subject<void>()
 
+  private localeObservable: Observable<any>
+  private videoLicensesObservable: Observable<VideoConstant<number>[]>
+  private videoCategoriesObservable: Observable<VideoConstant<number>[]>
+  private videoPrivaciesObservable: Observable<VideoConstant<number>[]>
+  private videoPlaylistPrivaciesObservable: Observable<VideoConstant<number>[]>
+  private videoLanguagesObservable: Observable<VideoConstant<string>[]>
+  private configObservable: Observable<ServerConfig>
+
+  private configReset = false
+
+  private configLoaded = false
   private config: ServerConfig = {
     instance: {
       name: 'PeerTube',
@@ -121,132 +123,141 @@ export class ServerService {
       enabled: true
     }
   }
-  private videoCategories: Array<VideoConstant<number>> = []
-  private videoLicences: Array<VideoConstant<number>> = []
-  private videoLanguages: Array<VideoConstant<string>> = []
-  private videoPrivacies: Array<VideoConstant<VideoPrivacy>> = []
-  private videoPlaylistPrivacies: Array<VideoConstant<VideoPlaylistPrivacy>> = []
 
   constructor (
     private http: HttpClient,
     @Inject(LOCALE_ID) private localeId: string
   ) {
-    this.loadServerLocale()
     this.loadConfigLocally()
   }
 
-  loadConfig () {
-    this.http.get<ServerConfig>(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<ServerConfig>(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<number>(ServerService.BASE_VIDEO_URL, 'categories', true)
+    }
+
+    return this.videoCategoriesObservable.pipe(first())
   }
 
   getVideoLicences () {
-    return cloneDeep(this.videoLicences)
+    if (!this.videoLicensesObservable) {
+      this.videoLicensesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'licences')
+    }
+
+    return this.videoLicensesObservable.pipe(first())
   }
 
   getVideoLanguages () {
-    return cloneDeep(this.videoLanguages)
+    if (!this.videoLanguagesObservable) {
+      this.videoLanguagesObservable = this.loadAttributeEnum<string>(ServerService.BASE_VIDEO_URL, 'languages', true)
+    }
+
+    return this.videoLanguagesObservable.pipe(first())
   }
 
   getVideoPrivacies () {
-    return cloneDeep(this.videoPrivacies)
+    if (!this.videoPrivaciesObservable) {
+      this.videoPrivaciesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'privacies')
+    }
+
+    return this.videoPrivaciesObservable.pipe(first())
   }
 
   getVideoPlaylistPrivacies () {
-    return cloneDeep(this.videoPlaylistPrivacies)
+    if (!this.videoPlaylistPrivaciesObservable) {
+      this.videoPlaylistPrivaciesObservable = this.loadAttributeEnum<number>(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 <T extends string | number> (
     baseUrl: string,
     attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
-    hashToPopulate: VideoConstant<string | number>[],
-    notifier: ReplaySubject<boolean>,
     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<T>[] = []
 
-  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) {
index 3eebc1acc8e7142e44b8d3d14ea6a7be5af1cf23..2c5873cb31bb7da654d89f20d583185536aeaaaa 100644 (file)
@@ -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) {
index 5a41f4e7eeb460a11da3568916b3fa1e2ffaf3fd..22f59b4d913bc253010028def8ba00eea2e3939d 100644 (file)
@@ -15,7 +15,7 @@ const loginRoutes: Routes = [
       }
     },
     resolve: {
-      serverConfigLoaded: ServerConfigResolver
+      serverConfig: ServerConfigResolver
     }
   }
 ]
index 911b9982fc542f1908d5c65aea331deb1e2645f0..cf923492a9a9bb2913e07cfdedd223eb78597ab9 100644 (file)
@@ -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
index c7c31577ae66f66f40efaf119367b8cb93eacd55..2d522b5217da58aa8a098a5e3850ee2eedfb08fb 100644 (file)
@@ -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 () {
index 57131fcac5d1b383d5a53e3e43f418eff7863709..344a260dfdd1bae8ecc193f2cd1c0689e1eea134 100644 (file)
@@ -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()
index 44b78866e0e330fe89aff8e0c282422a9dcb07f9..f56f5b1f81db215839faf11cb3151a8bea6a9bb1 100644 (file)
@@ -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(', ')
   }
 
index f880a886ffe3e13d37bf851217309d908dd71a71..fd8b3354f1043ce9abc42c1c6fcf02a92f26460f 100644 (file)
@@ -1,6 +1,6 @@
 <div class="feature-table">
 
-  <table class="table" *ngIf="config">
+  <table class="table" *ngIf="serverConfig">
     <tr>
       <td i18n class="label">PeerTube version</td>
 
@@ -19,7 +19,7 @@
     <tr>
       <td i18n class="label">User registration allowed</td>
       <td>
-        <my-feature-boolean [value]="config.signup.allowed"></my-feature-boolean>
+        <my-feature-boolean [value]="serverConfig.signup.allowed"></my-feature-boolean>
       </td>
     </tr>
 
     <tr>
       <td i18n class="sub-label">Transcoding in multiple resolutions</td>
       <td>
-        <my-feature-boolean [value]="config.transcoding.enabledResolutions.length !== 0"></my-feature-boolean>
+        <my-feature-boolean [value]="serverConfig.transcoding.enabledResolutions.length !== 0"></my-feature-boolean>
       </td>
     </tr>
 
     <tr>
       <td i18n class="sub-label">Video uploads</td>
       <td>
-        <span *ngIf="config.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span>
-        <span *ngIf="!config.autoBlacklist.videos.ofUsers.enabled">Automatically published</span>
+        <span *ngIf="serverConfig.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span>
+        <span *ngIf="!serverConfig.autoBlacklist.videos.ofUsers.enabled">Automatically published</span>
       </td>
     </tr>
 
     <tr>
       <td i18n class="sub-label">HTTP import (YouTube, Vimeo, direct URL...)</td>
       <td>
-        <my-feature-boolean [value]="config.import.videos.http.enabled"></my-feature-boolean>
+        <my-feature-boolean [value]="serverConfig.import.videos.http.enabled"></my-feature-boolean>
       </td>
     </tr>
 
     <tr>
       <td i18n class="sub-label">Torrent import</td>
       <td>
-        <my-feature-boolean [value]="config.import.videos.torrent.enabled"></my-feature-boolean>
+        <my-feature-boolean [value]="serverConfig.import.videos.torrent.enabled"></my-feature-boolean>
       </td>
     </tr>
 
@@ -88,7 +88,7 @@
     <tr>
       <td i18n class="sub-label">P2P enabled</td>
       <td>
-        <my-feature-boolean [value]="config.tracker.enabled"></my-feature-boolean>
+        <my-feature-boolean [value]="serverConfig.tracker.enabled"></my-feature-boolean>
       </td>
     </tr>
   </table>
index 1661f1efeba0ca7a61e49941e4c21b1cc6844075..8fd15ebadad2bed015c4727630cee165bd75f3eb 100644 (file)
@@ -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
 
index 44b413fa4263f0980de3d9ecc52d609b254d3656..8b26063fbc336b91073109f2b519a87e80dce71b 100644 (file)
@@ -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)
+                    })
+      })
+    )
   }
 }
index e9d4c1437ee79ade5140a84a9a5870c0842a2aaa..d82dc3d9491a9c01fe28848e9f600b9e69730ac9 100644 (file)
@@ -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 () {
index bd4068925486b2d6bfa40fccd38090a34b954b04..79cb781f7c30d2f07b50134d9e3346d96d19b8f9 100644 (file)
@@ -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) {
index 977f6253ab4e4f4ff78a4456ca41bf408ead5cef..6bfe6743573453af7ff73cce3f9340e858b56054 100644 (file)
@@ -22,7 +22,7 @@ export class VideoCaptionService {
     return this.authHttp.get<ResultList<VideoCaption>>(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 }) => {
index 7ae13154dee7839b185e5ea3ce391959fa95c626..3e3fb7dfb42aeb6a60287e773da51d0f79b2058d 100644 (file)
@@ -91,7 +91,7 @@ export class VideoImportService {
   }
 
   private extractVideoImports (result: ResultList<VideoImport>): Observable<ResultList<VideoImport>> {
-    return this.serverService.localeObservable
+    return this.serverService.getServerLocale()
                .pipe(
                  map(translations => {
                    result.data.forEach(d =>
index a8e5a4885859e6611540119b0f8eb8299c90bc4c..cd592eab082aea423324c8b781300ae8beb46889 100644 (file)
@@ -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) {
index 42791af866fd282c6ea25f3c2594461d88d9bd7a..2945b495996d164b2e4847e26650d632bfb4ac30 100644 (file)
@@ -173,7 +173,7 @@ export class VideoPlaylistService {
   }
 
   extractPlaylists (result: ResultList<VideoPlaylistServerModel>) {
-    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<ServerVideoPlaylistElement>) {
-    return this.serverService.localeObservable
+    return this.serverService.getServerLocale()
                .pipe(
                  map(translations => {
                    const elementsJson = result.data
index 2926b179b566a2ee2d909705bb20a6e4975960a3..faeea27d9fc77f598e37db7e8f50f5137bc21a61 100644 (file)
@@ -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<any[]>()
 
+  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' })
   }
index d5c7dfd9b580cef5b3515f7dbfb4df4dedc9555e..9fffc7ddb5bdd75fcd109acb808e5a6bf32f38c7 100644 (file)
@@ -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
index b0fa559664632fb647fbdfeae653e94b2c3215e6..9adf46495c427c89cf3f897ef935ceb0aa3f7e65 100644 (file)
@@ -64,7 +64,7 @@ export class VideoService implements VideosProvider {
   }
 
   getVideo (options: { videoId: string }): Observable<VideoDetails> {
-    return this.serverService.localeObservable
+    return this.serverService.getServerLocale()
                .pipe(
                  switchMap(translations => {
                    return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + options.videoId)
@@ -315,7 +315,7 @@ export class VideoService implements VideosProvider {
   }
 
   extractVideos (result: ResultList<VideoServerModel>) {
-    return this.serverService.localeObservable
+    return this.serverService.getServerLocale()
                .pipe(
                  map(translations => {
                    const videosJson = result.data
index 86c6e03e75be6f16856ebdba28d659ae9f4e18a8..1a9bf5171cab9a23381bb3edf254f5e33ac08a0f 100644 (file)
@@ -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<VideoCaptionEdit>()
 
@@ -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,
index e2a22203797515494ff76061e5aa36cccdb75719..e40649d9597b01b403513a33d0f4b59ed6b5d554 100644 (file)
 </div>
 
 <my-video-caption-add-modal
-  #videoCaptionAddModal [existingCaptions]="existingCaptions" (captionAdded)="onCaptionAdded($event)"
+  #videoCaptionAddModal [existingCaptions]="existingCaptions" [serverConfig]="serverConfig" (captionAdded)="onCaptionAdded($event)"
 ></my-video-caption-add-modal>
index d0d5e2a2b0d3083fe6e8062f5631296328b10132..982e071ad535a5bd2bb21ef0c8fb3ab438a08f80 100644 (file)
@@ -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)
 
index 580c123a029a59cc10a857545a441b9c60cdc28c..b32f16950908a8f05f72557b38336ef0f3bd7622 100644 (file)
@@ -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
           })
index 23b79edd35f5f9dffa5e9939aaa65e41b47b910f..28e10e5628c271d7567696b76c87dffcab94bd69 100644 (file)
@@ -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
index 911bc884e6a4ea5da3e9d9fce6200d45f4164a49..401d8a08f3966bda34be3c735eb18ed87e1ec0fb 100644 (file)
@@ -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 () {
index 8cc1e8b58b0a2c750caa49a4e25ab1f880e2e37d..626d0ca07e115b3ac8bcdefb70cd633fc07d038a 100644 (file)
@@ -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 () {
index aff8413eb13b2305bc5b5305dee9c396af372a7c..f94a7da0425aec714e5cb595efe1d6b9791285d9 100644 (file)
@@ -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) {
index 19324da633ac0f30fa568e53824b4cd264a3b517..bc88679faacccedf050f54c00be75a856e54d1f4 100644 (file)
@@ -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')
index 12359e68fb007e65ecf627f54a02d54667e349b2..31f2733c32a8432de512f9a369e925dfa197e974 100755 (executable)
@@ -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"