diff --git a/frontend/src/app/components/publication-edition/publication-edition.component.ts b/frontend/src/app/components/publication-edition/publication-edition.component.ts index 1aa2b8b..b52fa05 100644 --- a/frontend/src/app/components/publication-edition/publication-edition.component.ts +++ b/frontend/src/app/components/publication-edition/publication-edition.component.ts @@ -1,6 +1,6 @@ import { CommonModule, Location } from "@angular/common"; import { Component, EventEmitter, inject, Input, OnChanges, OnDestroy, Output } from "@angular/core"; -import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { FormGroup, ReactiveFormsModule } from "@angular/forms"; import { MatDialogModule } from "@angular/material/dialog"; import { MatIconModule } from "@angular/material/icon"; import { MatInputModule } from "@angular/material/input"; @@ -48,18 +48,13 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy { publicationInEdition!: Publication; private readonly categoryService = inject(CategoryService); - private readonly formBuilder = inject(FormBuilder); private readonly location = inject(Location); private readonly publicationEditionService = inject(PublicationEditionService); private subscriptions: Subscription[] = []; - publicationEditionForm: FormGroup = this.formBuilder.group({ - title: new FormControl('', [Validators.required]), - description: new FormControl('', [Validators.required]), - text: new FormControl('', [Validators.required]), - illustrationId: new FormControl('', [Validators.required]), - categoryId: new FormControl('', [Validators.required]) - }); + get publicationEditionForm(): FormGroup { + return this.publicationEditionService.publicationEditionForm; + } get isLoading$(): Observable { return this.publicationEditionService.isLoading$; @@ -92,52 +87,14 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy { } return 0; } - + ngOnChanges(): void { this.ngOnDestroy(); - this.publicationInEdition = this.publication; - this.publicationEditionService.init(this.publicationInEdition); - - ['title', 'description', 'text'].forEach(fieldName => { - const fieldSubscription = this.publicationEditionForm.controls[fieldName].valueChanges - .pipe( - map(value => value?.length ? value as string : '') - ) - .subscribe(fieldValue => { - switch (fieldName) { - case 'title': - this.publicationEditionService.editTitle(fieldValue); - break; - case 'description': - this.publicationEditionService.editDescription(fieldValue); - break; - case 'text': - this.publicationEditionService.editText(fieldValue); - break; - default: - break; - } - }); - this.subscriptions.push(fieldSubscription); - }); - - const categoryIdChangeSubscription = this.publicationEditionForm.controls['categoryId'].valueChanges - .subscribe(newCategoryId => { - this.publicationEditionService.editCategoryId(newCategoryId); - }); - this.subscriptions.push(categoryIdChangeSubscription); - - const publicationSubscription = this.publicationEditionService.state$.subscribe(state => { - console.log(state.publication.parsedText.substring(0, 15)); - this.publicationInEdition = state.publication; - this.publicationEditionForm.controls['title'].setValue(this.publicationInEdition.title, { emitEvent: false }); - this.publicationEditionForm.controls['description'].setValue(this.publicationInEdition.description, { emitEvent: false }); - this.publicationEditionForm.controls['text'].setValue(this.publicationInEdition.text, { emitEvent: false }); - this.publicationEditionForm.controls['illustrationId'].setValue(this.publicationInEdition.illustrationId, { emitEvent: false }); - this.publicationEditionForm.controls['categoryId'].setValue(this.publicationInEdition.categoryId, { emitEvent: false }); - }); - this.subscriptions.push(publicationSubscription); + if (!this.publicationInEdition || this.publicationInEdition !== this.publication) { + this.publicationInEdition = this.publication; + this.publicationEditionService.init(this.publicationInEdition); + } } ngOnDestroy(): void { @@ -164,10 +121,6 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy { this.publicationEditionService.displayCodeBlockDialog(); } - save(): void { - this.publicationSave.emit(this.publicationInEdition); - } - displayPictureSectionDialog(): void { this.publicationEditionService.displayPictureSectionDialog(); } @@ -183,6 +136,10 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy { } } + save(): void { + this.publicationSave.emit(this.publicationEditionService.editedPublication); + } + onTabChange(tabSelectedIndex: number): void { if (tabSelectedIndex === 1) { this.publicationEditionService.loadPreview(); diff --git a/frontend/src/app/components/publication-edition/publication-edition.service.ts b/frontend/src/app/components/publication-edition/publication-edition.service.ts index ef60523..36ccd58 100644 --- a/frontend/src/app/components/publication-edition/publication-edition.service.ts +++ b/frontend/src/app/components/publication-edition/publication-edition.service.ts @@ -3,13 +3,14 @@ import { inject, Injectable, OnDestroy } from "@angular/core"; import { MatDialog } from "@angular/material/dialog"; import { MatSnackBar } from "@angular/material/snack-bar"; import { ActivatedRoute } from "@angular/router"; -import { BehaviorSubject, Observable, Subscription } from "rxjs"; +import { BehaviorSubject, debounceTime, distinctUntilChanged, Observable, Subscription } from "rxjs"; import { Publication } from "../../core/rest-services/publications/model/publication"; import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service"; import { copy } from "../../core/utils/ObjectUtils"; import { CodeBlockDialog } from "./code-block-dialog/code-block-dialog.component"; import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component"; import { PreviewContentRequest } from "../../core/rest-services/publications/model/preview"; +import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms"; declare let Prism: any; @@ -57,10 +58,11 @@ const DEFAULT_STATE: PublicationEditionState = { @Injectable() export class PublicationEditionService implements OnDestroy { private readonly activatedRoute = inject(ActivatedRoute); - private readonly publicationRestService = inject(PublicationRestService); - private readonly location = inject(Location); - private readonly snackBar = inject(MatSnackBar); private readonly dialog = inject(MatDialog); + private readonly formBuilder = inject(FormBuilder); + private readonly location = inject(Location); + private readonly publicationRestService = inject(PublicationRestService); + private readonly snackBar = inject(MatSnackBar); private isLoadingSubject = new BehaviorSubject(false); private stateSubject = new BehaviorSubject(copy(DEFAULT_STATE)); @@ -68,6 +70,14 @@ export class PublicationEditionService implements OnDestroy { private isSavingSubject = new BehaviorSubject(false); private isPreviewingSubject = new BehaviorSubject(false); + publicationEditionForm: FormGroup = this.formBuilder.group({ + title: new FormControl('', [Validators.required]), + description: new FormControl('', [Validators.required]), + text: new FormControl('', [Validators.required]), + illustrationId: new FormControl('', [Validators.required]), + categoryId: new FormControl('', [Validators.required]) + }); + ngOnDestroy(): void { this.subscriptions.forEach(subscription => subscription.unsubscribe()); } @@ -80,6 +90,17 @@ export class PublicationEditionService implements OnDestroy { this.stateSubject.next(state); } + private _updateForm(): void { + const state = this._state; + const publication = state.publication; + + this.publicationEditionForm.controls['title'].setValue(publication.title); + this.publicationEditionForm.controls['description'].setValue(publication.description); + this.publicationEditionForm.controls['text'].setValue(publication.text); + this.publicationEditionForm.controls['illustrationId'].setValue(publication.illustrationId); + this.publicationEditionForm.controls['categoryId'].setValue(publication.categoryId); + } + get isLoading$(): Observable { return this.isLoadingSubject.asObservable(); } @@ -96,6 +117,10 @@ export class PublicationEditionService implements OnDestroy { return this.stateSubject.asObservable(); } + get editedPublication(): Publication { + return this._state.publication; + } + loadPublication(): void { this.isLoadingSubject.next(true); @@ -106,52 +131,47 @@ export class PublicationEditionService implements OnDestroy { this.location.back(); } else { this.publicationRestService.getById(publicationId) - .then(publication => { - const state = this._state; - state.publication = publication; - this.stateSubject.next(state); - }) - .catch(error => { - const errorMessage = $localize`A technical error occurred while loading publication data.`; - this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000}); - console.error(errorMessage, error) - }) - .finally(() => this.isLoadingSubject.next(false)); + .then(publication => { + const state = this._state; + state.publication = publication; + this.stateSubject.next(state); + }) + .catch(error => { + const errorMessage = $localize`A technical error occurred while loading publication data.`; + this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000}); + console.error(errorMessage, error) + }) + .finally(() => this.isLoadingSubject.next(false)); } - }); + }); } init(publication: Publication): void { const state = this._state; state.publication = publication; this.stateSubject.next(state); + this._updateForm(); + + const formValueChangesSubscription = this.publicationEditionForm.valueChanges + .pipe( + debounceTime(200), + distinctUntilChanged() + ) + .subscribe(formValue => { + const state = this._state; + const publication = state.publication; + + publication.title = formValue.title; + publication.description = formValue.description; + publication.categoryId = formValue.categoryId; + publication.text = formValue.text; + + this._save(state); + }) + this.subscriptions.push(formValueChangesSubscription); } - editTitle(title: string): void { - const state = this._state; - state.publication.title = title; - this._save(state); - } - - editDescription(description: string): void { - const state = this._state; - state.publication.description = description; - this._save(state); - } - - editCategoryId(categoryId: string): void { - const state = this._state; - state.publication.categoryId = categoryId; - this._save(state); - } - - editText(text: string): void { - const state = this._state; - state.publication.text = text; - this._save(state); - } - - editIllustrationId(pictureId: string): void { + private editIllustrationId(pictureId: string): void { const state = this._state; state.publication.illustrationId = pictureId this._save(state); @@ -204,6 +224,7 @@ export class PublicationEditionService implements OnDestroy { publication.text = textWithTags; this._save(state); + this._updateForm(); } else { console.error(`Bad value for parameter of function 'insertTitle': '${titleNumber}'.`); } @@ -233,6 +254,7 @@ export class PublicationEditionService implements OnDestroy { publication.text = textWithTags; this._save(state); + this._updateForm(); } insertLink(): void { @@ -248,9 +270,10 @@ export class PublicationEditionService implements OnDestroy { publication.text = textWithTags; this._save(state); + this._updateForm(); } - insertCodeBlock(programmingLanguage: string, codeBlock: string): void { + private insertCodeBlock(programmingLanguage: string, codeBlock: string): void { const state = this._state; const publication = state.publication; @@ -263,6 +286,7 @@ export class PublicationEditionService implements OnDestroy { publication.text = textWithTags; this._save(state); + this._updateForm(); } loadPreview(): void {