const checkbox = await getPluginCheckbox()
await checkbox.click()
- await browserSleep(5000)
-
await expectSubmitState({ disabled: true })
const error = await $('.form-error*=Should be enabled')
+
expect(await error.isDisplayed()).toBeTruthy()
})
font-size: 13px;
font-weight: $font-semibold;
}
+
+.alert {
+ margin-top: 15px;
+}
import { map } from 'rxjs/operators'
import { SelectChannelItem } from 'src/types/select-options-item.model'
import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
-import { AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, Validators } from '@angular/forms'
+import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms'
import { HooksService, PluginService, ServerService } from '@app/core'
import { removeElementFromArray } from '@app/helpers'
import { BuildFormValidator } from '@app/shared/form-validators'
for (const setting of this.pluginFields) {
await this.pluginService.translateSetting(setting.pluginInfo.plugin.npmName, setting.commonOptions)
- const validator = (control: AbstractControl): ValidationErrors | null => {
+ const validator = async (control: AbstractControl) => {
if (!setting.commonOptions.error) return null
- const error = setting.commonOptions.error({ formValues: this.form.value, value: control.value })
+ const error = await setting.commonOptions.error({ formValues: this.form.value, value: control.value })
return error?.error ? { [setting.commonOptions.name]: error.text } : null
}
const name = setting.commonOptions.name
pluginObj[name] = {
- VALIDATORS: [ validator ],
+ ASYNC_VALIDATORS: [ validator ],
+ VALIDATORS: [],
MESSAGES: {}
}
this.cd.detectChanges()
this.pluginFieldsAdded.emit()
+
+ // Plugins may need other control values to calculate potential errors
+ this.form.valueChanges.subscribe(() => this.formValidatorService.updateTreeValidity(this.pluginDataFormGroup))
}
private trackPrivacyChange () {
})
}
- updateSecondStep () {
- if (this.checkForm() === false) {
- return
- }
+ async updateSecondStep () {
+ if (!await this.isFormValid()) return
const video = new VideoEdit()
video.patch(this.form.value)
})
}
- updateSecondStep () {
- if (this.checkForm() === false) {
- return
- }
+ async updateSecondStep () {
+ if (!await this.isFormValid()) return
this.video.patch(this.form.value)
})
}
- updateSecondStep () {
- if (this.checkForm() === false) {
- return
- }
+ async updateSecondStep () {
+ if (!await this.isFormValid()) return
this.video.patch(this.form.value)
})
}
- checkForm () {
- this.forceCheck()
-
- return this.form.valid
- }
-
protected updateVideoAndCaptions (video: VideoEdit) {
this.loadingBar.useRef().start()
})
)
}
+
+ protected async isFormValid () {
+ await this.waitPendingCheck()
+ this.forceCheck()
+
+ return this.form.valid
+ }
}
}
isPublishingButtonDisabled () {
- return !this.checkForm() ||
+ return !this.form.valid ||
this.isUpdatingVideo === true ||
this.videoUploaded !== true ||
!this.videoUploadedIds.id
return $localize`Upload ${videofile.name}`
}
- updateSecondStep () {
- if (this.isPublishingButtonDisabled()) {
- return
- }
+ async updateSecondStep () {
+ if (!await this.isFormValid()) return
+ if (this.isPublishingButtonDisabled()) return
const video = new VideoEdit()
video.patch(this.form.value)
return { canDeactivate: this.formChanged === false, text }
}
- checkForm () {
- this.forceCheck()
-
- return this.form.valid
- }
-
isWaitTranscodingEnabled () {
if (this.videoDetails.getFiles().length > 1) { // Already transcoded
return false
return true
}
- update () {
- if (this.checkForm() === false || this.isUpdatingVideo === true) {
+ async update () {
+ await this.waitPendingCheck()
+ this.forceCheck()
+
+ if (!this.form.valid || this.isUpdatingVideo === true) {
return
}
}
onValidKey () {
- this.check()
+ this.forceCheck()
if (!this.form.valid) return
this.formValidated()
-import { ValidatorFn } from '@angular/forms'
+import { AsyncValidatorFn, ValidatorFn } from '@angular/forms'
export type BuildFormValidator = {
VALIDATORS: ValidatorFn[]
+ ASYNC_VALIDATORS?: AsyncValidatorFn[]
+
MESSAGES: { [ name: string ]: string }
}
+
import { FormGroup } from '@angular/forms'
+import { wait } from '@root-helpers/utils'
import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model'
import { FormValidatorService } from './form-validator.service'
this.formErrors = formErrors
this.validationMessages = validationMessages
- this.form.valueChanges.subscribe(() => this.onValueChanged(this.form, this.formErrors, this.validationMessages, false))
+ this.form.statusChanges.subscribe(async status => {
+ // FIXME: remove when https://github.com/angular/angular/issues/41519 is fixed
+ await this.waitPendingCheck()
+
+ this.onStatusChanged(this.form, this.formErrors, this.validationMessages)
+ })
}
- protected forceCheck () {
- return this.onValueChanged(this.form, this.formErrors, this.validationMessages, true)
+ protected async waitPendingCheck () {
+ if (this.form.status !== 'PENDING') return
+
+ // FIXME: the following line does not work: https://github.com/angular/angular/issues/41519
+ // return firstValueFrom(this.form.statusChanges.pipe(filter(status => status !== 'PENDING')))
+ // So we have to fallback to active wait :/
+
+ do {
+ await wait(10)
+ } while (this.form.status === 'PENDING')
}
- protected check () {
- return this.onValueChanged(this.form, this.formErrors, this.validationMessages, false)
+ protected forceCheck () {
+ this.onStatusChanged(this.form, this.formErrors, this.validationMessages, false)
}
- private onValueChanged (
+ private onStatusChanged (
form: FormGroup,
formErrors: FormReactiveErrors,
validationMessages: FormReactiveValidationMessages,
- forceCheck = false
+ onlyDirty = true
) {
for (const field of Object.keys(formErrors)) {
if (formErrors[field] && typeof formErrors[field] === 'object') {
- this.onValueChanged(
+ this.onStatusChanged(
form.controls[field] as FormGroup,
formErrors[field] as FormReactiveErrors,
- validationMessages[field] as FormReactiveValidationMessages,
- forceCheck
+ validationMessages[field] as FormReactiveValidationMessages
)
continue
}
if (control.dirty) this.formChanged = true
- if (forceCheck) control.updateValueAndValidity({ emitEvent: false })
- if (!control || !control.dirty || !control.enabled || control.valid) continue
+ if (!control || (onlyDirty && !control.dirty) || !control.enabled || !control.errors) continue
const staticMessages = validationMessages[field]
for (const key of Object.keys(control.errors)) {
// Try to find error message in static validation messages first
// Then check if the validator returns a string that is the error
- if (typeof formErrorValue === 'boolean') formErrors[field] += staticMessages[key] + ' '
+ if (staticMessages[key]) formErrors[field] += staticMessages[key] + ' '
else if (typeof formErrorValue === 'string') formErrors[field] += control.errors[key]
else throw new Error('Form error value of ' + field + ' is invalid')
}
}
}
-
}
import { Injectable } from '@angular/core'
-import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms'
+import { AsyncValidatorFn, FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms'
import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model'
import { FormReactiveErrors, FormReactiveValidationMessages } from './form-reactive'
form.addControl(
name,
- new FormControl(defaultValue, field?.VALIDATORS as ValidatorFn[])
+ new FormControl(defaultValue, field?.VALIDATORS as ValidatorFn[], field?.ASYNC_VALIDATORS as AsyncValidatorFn[])
)
}
}
+ updateTreeValidity (group: FormGroup | FormArray): void {
+ for (const key of Object.keys(group.controls)) {
+ const abstractControl = group.controls[key] as FormControl
+
+ if (abstractControl instanceof FormGroup || abstractControl instanceof FormArray) {
+ this.updateTreeValidity(abstractControl)
+ } else {
+ abstractControl.updateValueAndValidity({ emitEvent: false })
+ }
+ }
+ }
+
private isRecursiveField (field: any) {
return field && typeof field === 'object' && !field.MESSAGES && !field.VALIDATORS
}
}
onValidKey () {
- this.check()
+ this.forceCheck()
if (!this.form.valid) return
this.formValidated()
// Return undefined | null if there is no error or return a string with the detailed error
// Not supported by plugin setting registration
- error?: (options: any) => { error: boolean, text?: string }
+ error?: (options: any) => Promise<{ error: boolean, text?: string }>
}
export interface RegisterClientVideoFieldOptions {