Add publication edition form.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
application:
|
application:
|
||||||
pictures:
|
pictures:
|
||||||
path: /Users/florian_thierry/Documents/Developpement/codiki-hexa/backend/pictures-folder/
|
path: /home/florian/Developpement/codiki-hexagonal/backend/pictures-folder/
|
||||||
temp-path : /Users/florian_thierry/Documents/Developpement/codiki-hexa/backend/pictures-folder/temp/
|
temp-path : /home/florian/Developpement/codiki-hexagonal/backend/pictures-folder/temp/
|
||||||
|
|
||||||
server:
|
server:
|
||||||
port: 8987
|
port: 8987
|
||||||
|
|||||||
@@ -99,5 +99,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5680
frontend/package-lock.json
generated
5680
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,10 @@ export const routes: Routes = [
|
|||||||
path: 'publications/:publicationId',
|
path: 'publications/:publicationId',
|
||||||
loadComponent: () => import('./pages/publication/publication.component').then(module => module.PublicationComponent)
|
loadComponent: () => import('./pages/publication/publication.component').then(module => module.PublicationComponent)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'publications/:publicationId/edit',
|
||||||
|
loadComponent: () => import('./pages/publication-edition/publication-edition.component').then(module => module.PublicationEditionComponent)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent)
|
loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent)
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ng-container *ngIf="isAuthenticated; else anonymousRightMenu">
|
<ng-container *ngIf="isAuthenticated; else anonymousRightMenu">
|
||||||
<a [routerLink]="['/disconnect']" matRipple>Disconnect</a>
|
<a [routerLink]="['/disconnect']" class="button" matRipple>Disconnect</a>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #anonymousRightMenu>
|
<ng-template #anonymousRightMenu>
|
||||||
<a [routerLink]="['/login']" matRipple>Login</a>
|
<a [routerLink]="['/login']" class="button" matRipple>Login</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<app-side-menu #sideMenu></app-side-menu>
|
<app-side-menu #sideMenu></app-side-menu>
|
||||||
@@ -39,20 +39,6 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
button {
|
|
||||||
padding: .8em 1.2em;
|
|
||||||
border-radius: 10em;
|
|
||||||
border: none;
|
|
||||||
background-color: #3f51b5;
|
|
||||||
color: white;
|
|
||||||
transition: background-color .2s ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #5b6ed8;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #3f51b5;
|
color: #3f51b5;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
@if ((isLoading$ | async) == true) {
|
||||||
|
<mat-spinner></mat-spinner>
|
||||||
|
}
|
||||||
|
@else {
|
||||||
|
<!-- <ng-template #afterLoadingPart> -->
|
||||||
|
@if (publication) {
|
||||||
|
<form [formGroup]="publicationEditionForm" (submit)="save()" ngNativeValidate>
|
||||||
|
<header>
|
||||||
|
<h1>Modification de l'article {{ publication.title }}</h1>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<mat-tab-group dynamicHeight>
|
||||||
|
<mat-tab label="Edition">
|
||||||
|
<div class="form-content">
|
||||||
|
<mat-form-field class="example-form-field">
|
||||||
|
<mat-label>Title</mat-label>
|
||||||
|
<input matInput type="text" formControlName="title" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="example-form-field">
|
||||||
|
<mat-label>Picture</mat-label>
|
||||||
|
<input matInput type="text" formControlName="illustrationId" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="example-form-field">
|
||||||
|
<mat-label>Description</mat-label>
|
||||||
|
<input matInput type="text" formControlName="description" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="example-form-field">
|
||||||
|
<mat-label>Content</mat-label>
|
||||||
|
<textarea matInput formControlName="text"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
|
||||||
|
<mat-tab label="Previewing">
|
||||||
|
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<button type="submit">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button type="button" class="secondary" (click)="goPreviousLocation()">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
@else {
|
||||||
|
<div class="loading-failed">
|
||||||
|
<h1>Publication failed to load...</h1>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- <ng-template #loadingFailedMessage>
|
||||||
|
|
||||||
|
</ng-template> -->
|
||||||
|
<!-- </ng-template> -->
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin: 1em;
|
||||||
|
max-width: 80em;
|
||||||
|
width: 90%;
|
||||||
|
border-radius: .5em;
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 2em;
|
||||||
|
background-color: #3f51b5;
|
||||||
|
color: white;
|
||||||
|
border-radius: .5em .5em 0 0;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
padding: 2em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
button, a.button {
|
||||||
|
padding: .8em 1.2em;
|
||||||
|
border-radius: 10em;
|
||||||
|
border: none;
|
||||||
|
background-color: #3f51b5;
|
||||||
|
color: white;
|
||||||
|
transition: background-color .2s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #5b6ed8;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
color: #3f51b5;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f2f4ff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-content {
|
||||||
|
padding: 2em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { CommonModule, Location } from '@angular/common';
|
||||||
|
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||||
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
|
import { debounceTime, map, Observable, Subscription } from 'rxjs';
|
||||||
|
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||||
|
import { PublicationEditionService } from './publication-edition.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-publication-edition',
|
||||||
|
standalone: true,
|
||||||
|
imports: [ReactiveFormsModule, MatInputModule, MatProgressSpinner, MatTabsModule, CommonModule],
|
||||||
|
templateUrl: './publication-edition.component.html',
|
||||||
|
styleUrl: './publication-edition.component.scss',
|
||||||
|
providers: [PublicationEditionService]
|
||||||
|
})
|
||||||
|
export class PublicationEditionComponent implements OnInit, OnDestroy {
|
||||||
|
private formBuilder = inject(FormBuilder);
|
||||||
|
private location = inject(Location);
|
||||||
|
private publicationEditionService = inject(PublicationEditionService);
|
||||||
|
|
||||||
|
private subscriptions: Subscription[] = [];
|
||||||
|
publication!: Publication;
|
||||||
|
publicationEditionForm: FormGroup = this.formBuilder.group({
|
||||||
|
title: new FormControl<string | undefined>('', [Validators.required]),
|
||||||
|
description: new FormControl<string | undefined>('', [Validators.required]),
|
||||||
|
text: new FormControl<string | undefined>('', [Validators.required]),
|
||||||
|
illustrationId: new FormControl<string | undefined>('', [Validators.required]),
|
||||||
|
categoryId: new FormControl<string | undefined>('', [Validators.required])
|
||||||
|
});
|
||||||
|
|
||||||
|
get isLoading$(): Observable<boolean> {
|
||||||
|
return this.publicationEditionService.isLoading$;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
['title', 'description', 'text'].forEach(fieldName => {
|
||||||
|
const fieldSubscription = this.publicationEditionForm.controls[fieldName].valueChanges
|
||||||
|
.pipe(
|
||||||
|
debounceTime(300),
|
||||||
|
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.publicationEditionService
|
||||||
|
});
|
||||||
|
this.subscriptions.push(fieldSubscription);
|
||||||
|
});
|
||||||
|
|
||||||
|
const publicationSubscription = this.publicationEditionService.publication$.subscribe(publication => {
|
||||||
|
this.publication = publication;
|
||||||
|
this.publicationEditionForm.controls['title'].setValue(publication.title, { emitEvent: false });
|
||||||
|
this.publicationEditionForm.controls['description'].setValue(publication.description, { emitEvent: false });
|
||||||
|
this.publicationEditionForm.controls['text'].setValue(publication.text, { emitEvent: false });
|
||||||
|
this.publicationEditionForm.controls['illustrationId'].setValue(publication.illustrationId, { emitEvent: false });
|
||||||
|
this.publicationEditionForm.controls['categoryId'].setValue(publication.categoryId, { emitEvent: false });
|
||||||
|
});
|
||||||
|
this.subscriptions.push(publicationSubscription);
|
||||||
|
|
||||||
|
this.publicationEditionService.loadPublication();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subscriptions.forEach(subscription => subscription?.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
goPreviousLocation(): void {
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { Location } from "@angular/common";
|
||||||
|
import { inject, Injectable } from "@angular/core";
|
||||||
|
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { BehaviorSubject, Observable } 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';
|
||||||
|
|
||||||
|
const DEFAULT_PUBLICATION: Publication = {
|
||||||
|
id: '',
|
||||||
|
key: '',
|
||||||
|
title: '',
|
||||||
|
text: '',
|
||||||
|
parsedText: '',
|
||||||
|
description: '',
|
||||||
|
creationDate: new Date(),
|
||||||
|
illustrationId: '',
|
||||||
|
categoryId: '',
|
||||||
|
author: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
image: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PublicationEditionService {
|
||||||
|
private activatedRoute = inject(ActivatedRoute);
|
||||||
|
private publicationRestService = inject(PublicationRestService);
|
||||||
|
private location = inject(Location);
|
||||||
|
private snackBar = inject(MatSnackBar);
|
||||||
|
|
||||||
|
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||||
|
private publicationSubject = new BehaviorSubject<Publication>(copy(DEFAULT_PUBLICATION));
|
||||||
|
|
||||||
|
private get _publication(): Publication {
|
||||||
|
return this.publicationSubject.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _save(publication: Publication): void {
|
||||||
|
this.publicationSubject.next(publication);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLoading$(): Observable<boolean> {
|
||||||
|
return this.isLoadingSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
get publication$(): Observable<Publication> {
|
||||||
|
return this.publicationSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPublication(): void {
|
||||||
|
this.isLoadingSubject.next(true);
|
||||||
|
|
||||||
|
this.activatedRoute.paramMap.subscribe(params => {
|
||||||
|
const publicationId = params.get('publicationId');
|
||||||
|
if (publicationId == undefined) {
|
||||||
|
this.snackBar.open('A technical error occurred while loading publication data.', 'Close', {duration: 5000});
|
||||||
|
this.location.back();
|
||||||
|
} else {
|
||||||
|
this.publicationRestService.getById(publicationId)
|
||||||
|
.then(publication => {
|
||||||
|
this.publicationSubject.next(publication);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
const errorMessage = 'A technical error occurred while loading publication data.';
|
||||||
|
this.snackBar.open(errorMessage, 'Close', {duration: 5000});
|
||||||
|
console.error(errorMessage, error)
|
||||||
|
})
|
||||||
|
.finally(() => this.isLoadingSubject.next(false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editTitle(title: string): void {
|
||||||
|
const publication = this._publication;
|
||||||
|
publication.title = title;
|
||||||
|
this._save(publication);
|
||||||
|
}
|
||||||
|
|
||||||
|
editDescription(description: string): void {
|
||||||
|
const publication = this._publication;
|
||||||
|
publication.description = description;
|
||||||
|
this._save(publication);
|
||||||
|
}
|
||||||
|
|
||||||
|
editText(text: string): void {
|
||||||
|
const publication = this._publication;
|
||||||
|
publication.text = text;
|
||||||
|
this._save(publication);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user