import { Component , OnDestroy , ChangeDetectionStrategy , ViewChild , OnInit , ChangeDetectorRef } from '@angular/core' ; import { FormBuilder , FormArray , FormGroup , FormControl , Validators } from '@angular/forms' ; import { Store , select } from '@ngrx/store' ; import { Utils } from 'shared-library/core/services' ; import { AppState , appState } from '../../../store' ; import { MatSnackBar , MatDialog } from '@angular/material' ; import { Router } from '@angular/router' ; import { QuestionActions } from 'shared-library/core/store/actions/question.actions' ; import { QuestionAddUpdate } from './question-add-update' ; import { Question , Answer } from 'shared-library/shared/model' ; import { debounceTime , map , concatMap , mergeMap , take } from 'rxjs/operators' ; import { AutoUnsubscribe } from 'shared-library/shared/decorators' ; import { QuestionService } from 'shared-library/core/services' ; import { ImageCropperComponent } from 'ngx-img-cropper' ; import { QuillImageUpload } from 'ng-quill-tex/lib/models/quill-image-upload' ; import { PreviewQuestionDialogComponent } from './preview-question-dialog/preview-question-dialog.component' ; import { CropImageDialogComponent } from 'shared-library/shared/components' ; import { of } from 'rxjs' ; @ Component ( { templateUrl : './question-add-update.component.html' , styleUrls : [ './question-add-update.component.scss' ] , changeDetection : ChangeDetectionStrategy . OnPush } ) @ AutoUnsubscribe ( { 'arrayName' : 'subscriptions' } ) export class QuestionAddUpdateComponent extends QuestionAddUpdate implements OnInit , OnDestroy { @ ViewChild ( 'cropper' , { static : false } ) cropper : ImageCropperComponent ; dialogRef ; get tagsArray ( ) : FormArray { return this . questionForm . get ( 'tagsArray' ) as FormArray ; } htmlText : any ; jsonObject : any ; quillImageUrl : string ; quillConfig = { toolbar : { container : [ ] , handlers : { 'mathEditor' : ( ) => { } } } , mathEditor : { } , blotFormatter : { } , syntax : true } ; constructor ( public fb , public store , public utils , public router , public snackBar , public questionAction , public questionService , public dialog , private cd ) { super ( fb , store , utils , questionAction ) ; this . question = new Question ( ) ; this . subscriptions . push ( this . store . select ( appState . coreState ) . pipe ( select ( s => s . applicationSettings ) ) . subscribe ( appSettings => { if ( appSettings && appSettings [ 0 ] && appSettings [ 0 ] . quill_options ) { this . applicationSettings = appSettings [ 0 ] ; this . quillConfig . toolbar . container . push ( this . applicationSettings . quill_options . options ) ; this . quillConfig . toolbar . container . push ( this . applicationSettings . quill_options . list ) ; this . createForm ( this . question ) ; this . saveDraft ( ) ; this . quillConfig . mathEditor = { mathOptions : this . applicationSettings } ; } } ) ) ; if ( this . questionForm ) { this . subscriptions . push ( this . questionForm . get ( 'isRichEditor' ) . valueChanges . subscribe ( isRichEditor => { setTimeout ( ( ) => { this . setValidators ( isRichEditor ) ; } , 0 ) ; } ) ) ; const questionControl = this . questionForm . get ( 'questionText' ) ; this . subscriptions . push ( questionControl . valueChanges . pipe ( debounceTime ( 500 ) ) . subscribe ( v => this . computeAutoTags ( ) ) ) ; this . subscriptions . push ( this . answers . valueChanges . pipe ( debounceTime ( 500 ) ) . subscribe ( v => this . computeAutoTags ( ) ) ) ; this . filteredTags$ = this . questionForm . get ( 'tags' ) . valueChanges . pipe ( map ( val => val . length > 0 ? this . filter ( val ) : [ ] ) ) ; } this . subscriptions . push ( store . select ( appState . coreState ) . pipe ( select ( s => s . questionSaveStatus ) ) . subscribe ( ( status ) => { if ( status === 'SUCCESS' ) { this . store . dispatch ( this . questionAction . resetQuestionSuccess ( ) ) ; this . snackBar . open ( 'Question saved!' , '' , { duration : 2000 } ) ; this . router . navigate ( [ '/user/my/questions' ] ) ; } } ) ) ; } setValidators ( isRichEditor ) { if ( isRichEditor ) { this . questionForm . get ( 'maxTime' ) . setValidators ( Validators . compose ( [ Validators . required ] ) ) ; this . questionForm . get ( 'questionText' ) . setValidators ( Validators . compose ( [ Validators . required ] ) ) ; } else { this . questionForm . get ( 'maxTime' ) . setValidators ( [ ] ) ; this . questionForm . get ( 'questionText' ) . setValidators ( Validators . compose ( [ Validators . required , Validators . maxLength ( this . applicationSettings . question_max_length ) ] ) ) ; } this . questionForm . get ( 'maxTime' ) . updateValueAndValidity ( ) ; this . questionForm . get ( 'questionText' ) . updateValueAndValidity ( ) ; } fileUploaded ( quillImageUpload ) { if ( ! quillImageUpload . isMobile ) { const file = quillImageUpload . file ; this . dialogRef = this . dialog . open ( CropImageDialogComponent , { disableClose : false , data : { file : file , applicationSettings : this . applicationSettings } } ) ; this . subscriptions . push ( this . dialogRef . afterClosed ( ) . pipe ( mergeMap ( result => { const fileName = `questions/${ new Date ( ) . getTime ( ) }-${ file . name }` ; return this . questionService . saveQuestionImage ( result [ 'image' ] , fileName ) ; } ) , mergeMap ( image => { return of ( this . utils . getQuestionUrl ( `${ image [ 'name' ] }` ) + `?d=${ new Date ( ) . getTime ( ) }` ) ; } ) ) . subscribe ( imageUrl => { quillImageUpload . setImage ( imageUrl ) ; this . cd . markForCheck ( ) ; } ) ) ; } } ngOnInit ( ) { } createForm ( question ) { const answersFA = super . createDefaultForm ( question ) ; let fcs = question . tags . map ( tag => { const fc = new FormControl ( tag ) ; return fc ; } ) ; if ( fcs . length === 0 ) { fcs = [ new FormControl ( '' ) ] ; } const tagsFA = new FormArray ( fcs ) ; this . questionForm = this . fb . group ( { id : '' , is_draft : false , category : [ ( question . categories . length > 0 ? question . categories [ 0 ] : '' ) , Validators . required ] , questionText : [ '' , Validators . compose ( [ Validators . required , Validators . maxLength ( this . applicationSettings . question_max_length ) ] ) ] , tags : '' , tagsArray : tagsFA , answers : answersFA , ordered : [ question . ordered ] , explanation : [ question . explanation ] , isRichEditor : [ false ] , maxTime : [ ] } , { validator : questionFormValidator } ) ; } addTag ( ) { const tag = this . questionForm . get ( 'tags' ) . value ; if ( tag ) { super . addTag ( tag ) ; this . questionForm . get ( 'tags' ) . setValue ( '' ) ; } this . setTagsArray ( ) ; } removeEnteredTag ( tag ) { super . removeEnteredTag ( tag ) ; this . setTagsArray ( ) ; } computeAutoTags ( ) { super . computeAutoTags ( ) ; this . setTagsArray ( ) ; } setTagsArray ( ) { this . tagsArray . controls = [ ] ; [ ... this . autoTags , ... this . enteredTags ] . forEach ( tag => this . tagsArray . push ( new FormControl ( tag ) ) ) ; } submit ( ) { const question = this . onSubmit ( ) ; if ( question . isRichEditor ) { question . questionText = this . quillObject . questionText ; question . questionObject = this . quillObject . jsonObject ; } if ( question ) { this . saveQuestion ( question ) ; } } ngOnDestroy ( ) { } onAnswerChanged ( event , answerIndex ) { const answers = ( < FormArray > this . questionForm . get ( 'answers' ) ) ; answers . controls [ answerIndex ] [ 'controls' ] . answerObject . patchValue ( event . delta ) ; answers . controls [ answerIndex ] [ 'controls' ] . answerText . patchValue ( event . html ) ; } preview ( ) { this . switchQuestionQuillObject ( ) ; const dialogRef = this . dialog . open ( PreviewQuestionDialogComponent , { disableClose : false , data : { question : this . question } } ) ; this . subscriptions . push ( dialogRef . afterClosed ( ) . subscribe ( result => { } ) ) ; } switchQuestionQuillObject ( ) { this . question = this . onSubmit ( ) ; if ( this . question && this . question . isRichEditor ) { this . question . questionText = this . quillObject . questionText ; this . question . questionObject = this . quillObject . jsonObject ; } } } function questionFormValidator ( fg ) { const answers = fg . get ( 'answers' ) . value ; if ( answers . filter ( answer => answer . correct ) . length !== 1 ) { return { 'correctAnswerCountInvalid' : true } ; } const tags = fg . get ( 'tagsArray' ) . value ; if ( tags . length < 3 ) { return { 'tagCountInvalid' : true } ; } return null ; }