import { CommonModule } from '@angular/common' import { HttpErrorResponse } from '@angular/common/http' import { Component, OnInit, OnDestroy, inject } from '@angular/core' import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms' import { ActivatedRoute, RouterLink } from '@angular/router' import { CanComponentDeactivate, Notifier, ServerService } from '@app/core' import { genericUploadErrorHandler } from '@app/helpers' import { URL_VALIDATOR } from '@app/shared/form-validators/common-validators' import { ADMIN_EMAIL_VALIDATOR, INSTANCE_NAME_VALIDATOR, INSTANCE_SHORT_DESCRIPTION_VALIDATOR } from '@app/shared/form-validators/custom-config-validators' import { BuildFormArgumentTyped, FormDefaultTyped, FormReactiveErrorsTyped, FormReactiveMessagesTyped } from '@app/shared/form-validators/form-validator.model' import { CustomMarkupService } from '@app/shared/shared-custom-markup/custom-markup.service' import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service' import { SelectRadioComponent } from '@app/shared/shared-forms/select/select-radio.component' import { InstanceService } from '@app/shared/shared-main/instance/instance.service' import { maxBy } from '@peertube/peertube-core-utils' import { ActorImage, CustomConfig, HTMLServerConfig, NSFWPolicyType, VideoConstant } from '@peertube/peertube-models' import { SelectOptionsItem } from 'src/types/select-options-item.model' import { ActorAvatarEditComponent } from '../../../shared/shared-actor-image-edit/actor-avatar-edit.component' import { ActorBannerEditComponent } from '../../../shared/shared-actor-image-edit/actor-banner-edit.component' import { AdminConfigService } from '../../../shared/shared-admin/admin-config.service' import { CustomMarkupHelpComponent } from '../../../shared/shared-custom-markup/custom-markup-help.component' import { MarkdownTextareaComponent } from '../../../shared/shared-forms/markdown-textarea.component' import { PeertubeCheckboxComponent } from '../../../shared/shared-forms/peertube-checkbox.component' import { SelectCheckboxComponent } from '../../../shared/shared-forms/select/select-checkbox.component' import { HelpComponent } from '../../../shared/shared-main/buttons/help.component' import { PeerTubeTemplateDirective } from '../../../shared/shared-main/common/peertube-template.directive' import { AdminSaveBarComponent } from '../shared/admin-save-bar.component' import { Subscription } from 'rxjs' type Form = { admin: FormGroup<{ email: FormControl }> contactForm: FormGroup<{ enabled: FormControl }> instance: FormGroup<{ name: FormControl shortDescription: FormControl description: FormControl categories: FormControl languages: FormControl serverCountry: FormControl support: FormGroup<{ text: FormControl }> social: FormGroup<{ externalLink: FormControl mastodonLink: FormControl blueskyLink: FormControl }> isNSFW: FormControl defaultNSFWPolicy: FormControl terms: FormControl codeOfConduct: FormControl moderationInformation: FormControl administrator: FormControl creationReason: FormControl maintenanceLifetime: FormControl businessModel: FormControl hardwareInformation: FormControl }> } @Component({ selector: 'my-admin-config-information', templateUrl: './admin-config-information.component.html', styleUrls: [ './admin-config-common.scss' ], imports: [ FormsModule, ReactiveFormsModule, ActorAvatarEditComponent, ActorBannerEditComponent, SelectRadioComponent, CommonModule, CustomMarkupHelpComponent, MarkdownTextareaComponent, SelectCheckboxComponent, RouterLink, PeertubeCheckboxComponent, PeerTubeTemplateDirective, HelpComponent, AdminSaveBarComponent ] }) export class AdminConfigInformationComponent implements OnInit, OnDestroy, CanComponentDeactivate { private customMarkup = inject(CustomMarkupService) private notifier = inject(Notifier) private instanceService = inject(InstanceService) private server = inject(ServerService) private route = inject(ActivatedRoute) private formReactiveService = inject(FormReactiveService) private adminConfigService = inject(AdminConfigService) form: FormGroup
formErrors: FormReactiveErrorsTyped = {} validationMessages: FormReactiveMessagesTyped = {} languageItems: SelectOptionsItem[] = [] categoryItems: SelectOptionsItem[] = [] instanceBannerUrl: string instanceAvatars: ActorImage[] = [] nsfwItems: SelectOptionsItem[] = [ { id: 'do_not_list', label: $localize`Hide` }, { id: 'warn', label: $localize`Warn` }, { id: 'blur', label: $localize`Blur` }, { id: 'display', label: $localize`Display` } ] private serverConfig: HTMLServerConfig private customConfig: CustomConfig private customConfigSub: Subscription get instanceName () { return this.server.getHTMLConfig().instance.name } ngOnInit () { this.customConfig = this.route.parent.snapshot.data['customConfig'] const data = this.route.snapshot.data as { languages: VideoConstant[] categories: VideoConstant[] } this.languageItems = data.languages.map(l => ({ label: l.label, id: l.id })) this.categoryItems = data.categories.map(l => ({ label: l.label, id: l.id })) this.serverConfig = this.server.getHTMLConfig() this.updateActorImages() this.buildForm() this.customConfigSub = this.adminConfigService.getCustomConfigReloadedObs() .subscribe(customConfig => { this.customConfig = customConfig this.form.patchValue(this.customConfig) }) } ngOnDestroy () { if (this.customConfigSub) this.customConfigSub.unsubscribe() } private buildForm () { const obj: BuildFormArgumentTyped = { admin: { email: ADMIN_EMAIL_VALIDATOR }, contactForm: { enabled: null }, instance: { name: INSTANCE_NAME_VALIDATOR, shortDescription: INSTANCE_SHORT_DESCRIPTION_VALIDATOR, description: null, isNSFW: null, defaultNSFWPolicy: null, terms: null, codeOfConduct: null, creationReason: null, moderationInformation: null, administrator: null, maintenanceLifetime: null, businessModel: null, hardwareInformation: null, categories: null, languages: null, serverCountry: null, support: { text: null }, social: { externalLink: URL_VALIDATOR, mastodonLink: URL_VALIDATOR, blueskyLink: URL_VALIDATOR } } } const defaultValues: FormDefaultTyped = this.customConfig const { form, formErrors, validationMessages } = this.formReactiveService.buildForm(obj, defaultValues) this.form = form this.formErrors = formErrors this.validationMessages = validationMessages } canDeactivate () { return { canDeactivate: !this.form.dirty } } getCustomMarkdownRenderer () { return this.customMarkup.getCustomMarkdownRenderer() } onBannerChange (formData: FormData) { this.instanceService.updateInstanceBanner(formData) .subscribe({ next: () => { this.notifier.success($localize`Banner changed.`) this.resetActorImages() }, error: (err: HttpErrorResponse) => genericUploadErrorHandler({ err, name: $localize`banner`, notifier: this.notifier }) }) } onBannerDelete () { this.instanceService.deleteInstanceBanner() .subscribe({ next: () => { this.notifier.success($localize`Banner deleted.`) this.resetActorImages() }, error: err => this.notifier.error(err.message) }) } onAvatarChange (formData: FormData) { this.instanceService.updateInstanceAvatar(formData) .subscribe({ next: () => { this.notifier.success($localize`Avatar changed.`) this.resetActorImages() }, error: (err: HttpErrorResponse) => genericUploadErrorHandler({ err, name: $localize`avatar`, notifier: this.notifier }) }) } onAvatarDelete () { this.instanceService.deleteInstanceAvatar() .subscribe({ next: () => { this.notifier.success($localize`Avatar deleted.`) this.resetActorImages() }, error: err => this.notifier.error(err.message) }) } private updateActorImages () { this.instanceBannerUrl = maxBy(this.serverConfig.instance.banners, 'width')?.path this.instanceAvatars = this.serverConfig.instance.avatars } private resetActorImages () { this.server.resetConfig() .subscribe(config => { this.serverConfig = config this.updateActorImages() }) } save () { this.adminConfigService.saveAndUpdateCurrent({ currentConfig: this.customConfig, form: this.form, formConfig: this.form.value, success: $localize`Information updated.` }) } }