change supportive material editor model

This commit is contained in:
amentis 2023-11-28 19:06:15 +02:00
parent 319e1f0dca
commit 3d5c718d52
8 changed files with 322 additions and 150 deletions

View File

@ -26,6 +26,8 @@ public class SupportiveMaterialPersist {
@NotEmpty(message = "{validation.empty}")
private String payload;
private String hash;
public UUID getId() {
return id;
}
@ -57,4 +59,12 @@ public class SupportiveMaterialPersist {
public void setPayload(String payload) {
this.payload = payload;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
}

View File

@ -1,20 +1,14 @@
import { IsActive } from "@app/core/common/enum/is-active.enum";
import { SupportiveMaterialFieldType } from "@app/core/common/enum/supportive-material-field-type";
import { Guid } from "@common/types/guid";
import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model";
export interface SupportiveMaterial{
id: Guid;
export interface SupportiveMaterial extends BaseEntity{
type: SupportiveMaterialFieldType;
languageCode: string;
payload: string;
createdAt: Date;
updatedAt: Date;
isActive: IsActive;
}
export interface SupportiveMaterialPersist{
id: Guid;
export interface SupportiveMaterialPersist extends BaseEntityPersist{
type: SupportiveMaterialFieldType;
languageCode: string;
payload: string;

View File

@ -24,6 +24,7 @@ import { ReferenceTypeExternalApiHTTPMethodType } from '@app/core/common/enum/re
import { UserDescriptionTemplateRole } from '@app/core/common/enum/user-description-template-role';
import { ReferenceType } from '@app/core/common/enum/reference-type';
import { ReferenceSourceType } from '@app/core/common/enum/reference-source-type';
import { SupportiveMaterialFieldType } from '@app/core/common/enum/supportive-material-field-type';
@Injectable()
export class EnumUtils {
@ -340,4 +341,14 @@ export class EnumUtils {
}
}
toSupportiveMaterialTypeString(status: SupportiveMaterialFieldType): string {
switch (status) {
case SupportiveMaterialFieldType.Faq: return this.language.instant('FOOTER.FAQ');
case SupportiveMaterialFieldType.About: return this.language.instant('FOOTER.ABOUT');
case SupportiveMaterialFieldType.Glossary: return this.language.instant('FOOTER.GLOSSARY');
case SupportiveMaterialFieldType.TermsOfService: return this.language.instant('FOOTER.TERMS-OF-SERVICE');
case SupportiveMaterialFieldType.UserGuide: return this.language.instant('FOOTER.GUIDE');
}
}
}

View File

@ -1,57 +1,60 @@
<div class="supportive-material-editor">
<form (ngSubmit)="submit()" [formGroup]="formGroup">
<form *ngIf="formGroup" (ngSubmit)="formSubmit()">
<div>
<mat-card class="col-md-8 offset-md-2">
<div>
<div class ="material">
<mat-form-field>
<mat-label>{{'SUPPORTIVE-MATERIAL-EDITOR.FIELDS.MATERIAL-TYPE' | translate}}</mat-label>
<mat-select [value] ="selectedMaterial" (selectionChange)="selectedMaterialChanged($event.value)">
<mat-option *ngFor="let vis of visiblesMaterialsTypes" [value]="vis">
{{vis.name | translate}}
<mat-select (selectionChange)="selectedMaterialChanged($event.value)" name="type" [formControl]="formGroup.get('type')" required>
<mat-option *ngFor="let type of supportiveMaterialTypeEnum" [value]="type">
{{enumUtils.toSupportiveMaterialTypeString(type)}}
</mat-option>
</mat-select>
</mat-select>
<mat-error *ngIf="formGroup.get('type').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class ="lang">
<mat-form-field>
<mat-label>{{'SUPPORTIVE-MATERIAL-EDITOR.FIELDS.LANGUAGE' | translate}}</mat-label>
<mat-select [value] ="selectedLang" (selectionChange)="selectedLangChanged($event.value)">
<mat-option *ngFor="let vis of visiblesLangTypes" [value]="vis">
<mat-select (selectionChange)="selectedLangChanged($event.value)" name= "languageCode" [formControl]="formGroup.get('languageCode')">
<mat-option *ngFor="let vis of visiblesLangTypes" [value]="vis.type">
{{vis.name | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="formGroup.get('languageCode').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
<!-- <mat-card-title><div>{{selectedMaterial.name | translate}}</div></mat-card-title> -->
<mat-card-content>
<mat-card-content *ngIf="formGroup.get('type').value && formGroup.get('languageCode').value">
<editor [init]="{
base_url: '/tinymce',
suffix: '.min',
height: 800,
menubar: true,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks fullscreen fullpage',
'insertdatetime media table paste code help wordcount importcss ',
'codesample toc visualchars'
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks fullscreen fullpage',
'insertdatetime media table paste code help wordcount importcss ',
'codesample toc visualchars'
],
extended_valid_elements: '*[*]',
forced_root_block: '',
valid_children: '+body[script],ol[li|div|p|a|ol|table],h2[span],h3[span]',
save_enablewhendirty: false,
toolbar:
'undo redo | formatselect | bold italic backcolor | \
alignleft aligncenter alignright alignjustify | \
bullist numlist outdent indent | code codesample | searchreplace | preview | removeformat | help'
}" formControlName="html"></editor>
'undo redo | formatselect | bold italic backcolor | \
alignleft aligncenter alignright alignjustify | \
bullist numlist outdent indent | code codesample | searchreplace | preview | removeformat | help'
}" [formControl]="formGroup.get('payload')"></editor>
</mat-card-content>
</mat-card>
<button mat-fab class="mat-fab-bottom-right save-btn" matTooltip="{{'SUPPORTIVE-MATERIAL-EDITOR.ACTIONS.SAVE' | translate}}" type="submit">
<mat-icon class="mat-24">save</mat-icon>
</button>
</div>
</form>
</div>

View File

@ -1,26 +1,39 @@
import { Component, OnInit } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder } from '@angular/forms';
import { BaseComponent } from '@common/base/base.component';
import { takeUntil } from 'rxjs/operators';
import { map, takeUntil } from 'rxjs/operators';
import { UiNotificationService, SnackBarNotificationLevel } from '@app/core/services/notification/ui-notification-service';
import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { isNullOrUndefined } from '@app/utilities/enhancers/utils';
import { environment } from 'environments/environment';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { AuthService } from '@app/core/services/auth/auth.service';
import { LanguageService } from '@app/core/services/language/language.service';
import { MatomoService } from '@app/core/services/matomo/matomo-service';
import { HttpClient } from '@angular/common/http';
import { SupportiveMaterialService } from '@app/core/services/supportive-material/supportive-material.service';
import { SupportiveMaterialFieldType } from '@app/core/common/enum/supportive-material-field-type';
import { SupportiveMaterialPersist } from '@app/core/model/supportive-material/supportive-material';
import { SupportiveMaterial, SupportiveMaterialPersist } from '@app/core/model/supportive-material/supportive-material';
import { Guid } from '@common/types/guid';
import { BaseEditor } from '@common/base/base-editor';
import { SupportiveMaterialEditorModel } from './supportive-material-editor.model';
import { MatDialog } from '@angular/material/dialog';
import { FormService } from '@common/forms/form-service';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { DatePipe } from '@angular/common';
import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import { LanguageService } from '@app/core/services/language/language.service';
import { SupportiveMaterialEditorService } from './supportive-material-editor.service';
import { SupportiveMaterialEditorResolver } from './supportive-material-editor.resolver';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { LanguageV2Service } from '@app/core/services/language/language-v2.service';
import { nameof } from 'ts-simple-nameof';
import { Language } from '@app/core/model/language/language';
interface VisibleMaterialType{
name: string;
type: SupportiveMaterialFieldType;
}
interface VisibleLangType{
name: string;
@ -31,31 +44,45 @@ interface VisibleLangType{
@Component({
selector: 'app-supportive-material-editor',
templateUrl: './supportive-material-editor.component.html',
styleUrls: ['./supportive-material-editor.component.scss']
styleUrls: ['./supportive-material-editor.component.scss'],
providers: [SupportiveMaterialEditorService]
})
export class SupportiveMaterialEditorComponent extends BaseComponent implements OnInit {
public formGroup: UntypedFormGroup;
private formBuilder: UntypedFormBuilder;
export class SupportiveMaterialEditorComponent extends BaseEditor<SupportiveMaterialEditorModel, SupportiveMaterial> implements OnInit {
isNew = true;
isDeleted = false;
formGroup: UntypedFormGroup = null;
showInactiveDetails = false;
constructor(private supportiveMaterialService: SupportiveMaterialService,
private uiNotificationService: UiNotificationService,
private translate: TranslateService,
private router: Router,
private configurationService: ConfigurationService,
private languageService: LanguageService,
private httpClient: HttpClient,
public supportiveMaterialTypeEnum = this.enumUtils.getEnumValues(SupportiveMaterialFieldType);
constructor(
// BaseFormEditor injected dependencies
protected dialog: MatDialog,
protected language: TranslateService,
protected formService: FormService,
protected router: Router,
protected uiNotificationService: UiNotificationService,
protected httpErrorHandlingService: HttpErrorHandlingService,
protected filterService: FilterService,
protected datePipe: DatePipe,
protected route: ActivatedRoute,
protected queryParamsService: QueryParamsService,
// Rest dependencies. Inject any other needed deps here:
public authService: AuthService,
public enumUtils: EnumUtils,
private supportiveMaterialService: SupportiveMaterialService,
private languageService: LanguageService,
private languageV2Service: LanguageV2Service,
private logger: LoggingService,
private supportiveMaterialEditorService: SupportiveMaterialEditorService,
private fileUtils: FileUtils,
private matomoService: MatomoService
) { super(); }
) {
super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, datePipe, route, queryParamsService);
}
visiblesMaterialsTypes: VisibleMaterialType[] = [
{name: "FOOTER.FAQ", type: SupportiveMaterialFieldType.Faq},
{name: "FOOTER.ABOUT", type: SupportiveMaterialFieldType.About},
{name: "FOOTER.GLOSSARY", type: SupportiveMaterialFieldType.Glossary},
{name: "FOOTER.TERMS-OF-SERVICE", type: SupportiveMaterialFieldType.TermsOfService},
{name: "FOOTER.GUIDE", type: SupportiveMaterialFieldType.UserGuide}
]
visiblesLangTypes: VisibleLangType[] = [
{name: "GENERAL.LANGUAGES.ENGLISH", type: "en"},
{name: "GENERAL.LANGUAGES.GREEK", type: "gr"},
@ -70,103 +97,117 @@ export class SupportiveMaterialEditorComponent extends BaseComponent implements
]
selectedMaterial: VisibleMaterialType;
selectedLang: VisibleLangType;
materialId: Guid = null;
ngOnInit() {
this.selectedMaterial = this.visiblesMaterialsTypes.find(x => x.type == SupportiveMaterialFieldType.Faq);
this.selectedLang = this.visiblesLangTypes.find(x => x.type == this.languageService.getCurrentLanguage());
this.getSupportiveMaterialData();
}
public selectedMaterialChanged(item: VisibleMaterialType){
this.selectedMaterial = item;
this.getSupportiveMaterialData();
}
public selectedLangChanged(item: VisibleLangType){
this.selectedLang = item;
this.getSupportiveMaterialData();
}
private getSupportiveMaterialData(){
this.matomoService.trackPageView(`Admin: ${this.selectedMaterial.name} Editor`);
this.formBuilder = new UntypedFormBuilder();
this.formGroup = this.formBuilder.group({
name: [''],
html: ['']
});
// this.supportiveMaterialService.getPublic(this.selectedLang.type, this.selectedMaterial.type).pipe(takeUntil(this._destroyed)).subscribe(data => {
// const contentDispositionHeader = data.headers.get('Content-Disposition');
// const filename = contentDispositionHeader.split(';')[1].trim().split('=')[1].replace(/"/g, '');
// this.formGroup.get('name').patchValue(filename);
// this.loadFile(data.body);
// });
const lookup = SupportiveMaterialService.DefaultSupportiveMaterialLookup();
lookup.types = [this.selectedMaterial.type];
lookup.languageCodes = [this.selectedLang.type];
this.supportiveMaterialService.query(lookup).pipe(takeUntil(this._destroyed)).subscribe(data => {
if(data.count == 1){
this.materialId = data.items[0].id;
this.formGroup.get('html').patchValue(data.items[0].payload);
}
});
}
private parseText(source: string): string {
source = source.replace(/src="images/g, `src="${this.configurationService.guideAssets}`);
source = source.replace(/\r\n +>/g, '>\n');
const brokenElements = Array.from(new Set(source.match(/&lt;\/\w+\d* &gt;/g)));
if (!isNullOrUndefined(brokenElements)) {
brokenElements.forEach((brokenElement) => {
const tempvalue = brokenElement.match(/\/\w+\d*/)[0];
source = source.replace(new RegExp(brokenElement, 'g'), `<${tempvalue}>\n`);
});
getItem(itemId: Guid, successFunction: (item: SupportiveMaterial) => void) {
this.supportiveMaterialService.getSingle(itemId, SupportiveMaterialEditorResolver.lookupFields())
.pipe(map(data => data as SupportiveMaterial), takeUntil(this._destroyed))
.subscribe(
data => successFunction(data),
error => this.onCallbackError(error)
);
}
return source;
}
// loadFile(data: Blob) {
// const reader = new FileReader();
// reader.addEventListener('load', () => {
// let result = this.parseText(reader.result as string);
// //result = result.replace(/class="href" path="/g, 'href="#');
// this.formGroup.get('html').patchValue(result);
// }, false);
// reader.readAsText(data);
// }
submit() {
let result = this.parseText(this.formGroup.get('html').value);
this.formGroup.get('html').patchValue(result);
const item = {id: this.materialId,
type: this.selectedMaterial.type,
languageCode: this.selectedLang.type,
payload: this.formGroup.value['html']} as SupportiveMaterialPersist;
this.supportiveMaterialService.persist(item).pipe(takeUntil(this._destroyed))
.subscribe(
complete => {
this.onCallbackSuccess();
},
error => {
this.onCallbackError(error);
prepareForm(data: SupportiveMaterial) {
try {
this.editorModel = data ? new SupportiveMaterialEditorModel().fromModel(data) : new SupportiveMaterialEditorModel();
this.isDeleted = data ? data.isActive === IsActive.Inactive : false;
this.buildForm();
} catch (error) {
this.logger.error('Could not parse Supportive Material item: ' + data + error);
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
}
);
}
onCallbackSuccess(): void {
this.uiNotificationService.snackBarNotification( this.translate.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
this.router.navigate(['/reload']).then(() => this.router.navigate(['/supportive-material']));
}
onCallbackError(error: any) {
this.uiNotificationService.snackBarNotification( error, SnackBarNotificationLevel.Error);
//this.validateAllFormFields(this.formGroup);
}
}
buildForm() {
this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.authService.hasPermission(AppPermission.EditLanguage));
this.supportiveMaterialEditorService.setValidationErrorModel(this.editorModel.validationErrorModel);
}
refreshData(): void {
this.getItem(this.editorModel.id, (data: SupportiveMaterial) => this.prepareForm(data));
}
refreshOnNavigateToData(id?: Guid): void {
this.formGroup.markAsPristine();
let route = [];
if (id === null) {
route.push('../..');
} else if (this.isNew) {
route.push('../' + id);
} else {
route.push('..');
}
this.router.navigate(route, { queryParams: { 'lookup': this.queryParamsService.serializeLookup(this.lookupParams), 'lv': ++this.lv }, replaceUrl: true, relativeTo: this.route });
}
persistEntity(onSuccess?: (response) => void): void {
const formData = this.formService.getValue(this.formGroup.value) as SupportiveMaterialPersist;
this.supportiveMaterialService.persist(formData)
.pipe(takeUntil(this._destroyed)).subscribe(
complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete),
error => this.onCallbackError(error)
);
}
formSubmit(): void {
this.formService.touchAllFormFields(this.formGroup);
if (!this.isFormValid()) {
return;
}
this.persistEntity();
}
public delete() {
const value = this.formGroup.value;
if (value.id) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
maxWidth: '300px',
data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
this.supportiveMaterialService.delete(value.id).pipe(takeUntil(this._destroyed))
.subscribe(
complete => this.onCallbackSuccess(),
error => this.onCallbackError(error)
);
}
});
}
}
public selectedMaterialChanged(){
this.getSupportiveMaterialData();
}
public selectedLangChanged(){
this.getSupportiveMaterialData();
}
private getSupportiveMaterialData(){
if(this.formGroup.get('type').value >= 0 && this.formGroup.get('languageCode').value){
const lookup = SupportiveMaterialService.DefaultSupportiveMaterialLookup();
lookup.types = [this.formGroup.get('type').value];
lookup.languageCodes = [this.formGroup.get('languageCode').value];
this.supportiveMaterialService.query(lookup).pipe(takeUntil(this._destroyed)).subscribe(data => {
if(data.count == 1){
this.formGroup.get('id').patchValue(data.items[0].id);
this.formGroup.get('payload').patchValue(data.items[0].payload);
}else{
this.formGroup.get('id').patchValue(null);
this.formGroup.get('payload').patchValue('');
}
});
}
}
}

View File

@ -0,0 +1,55 @@
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { SupportiveMaterialFieldType } from "@app/core/common/enum/supportive-material-field-type";
import { SupportiveMaterial, SupportiveMaterialPersist } from "@app/core/model/supportive-material/supportive-material";
import { BaseEditorModel } from "@common/base/base-form-editor-model";
import { BackendErrorValidator } from "@common/forms/validation/custom-validator";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
import { Validation, ValidationContext } from "@common/forms/validation/validation-context";
export class SupportiveMaterialEditorModel extends BaseEditorModel implements SupportiveMaterialPersist {
type: SupportiveMaterialFieldType;
languageCode: string;
payload: string;
permissions: string[];
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor() { super(); }
public fromModel(item: SupportiveMaterial): SupportiveMaterialEditorModel {
if (item) {
super.fromModel(item);
this.type = item.type;
this.languageCode = item.languageCode;
this.payload = item.payload;
}
return this;
}
buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup {
if (context == null) { context = this.createValidationContext(); }
return this.formBuilder.group({
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
type: [{ value: this.type, disabled: disabled }, context.getValidation('type').validators],
languageCode: [{ value: this.languageCode, disabled: disabled }, context.getValidation('languageCode').validators],
payload: [{ value: this.payload, disabled: disabled }, context.getValidation('payload').validators],
hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators]
});
}
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] });
baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'type')] });
baseValidationArray.push({ key: 'languageCode', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'languageCode')] });
baseValidationArray.push({ key: 'payload', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'payload')] });
baseValidationArray.push({ key: 'hash', validators: [] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}

View File

@ -0,0 +1,43 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { SupportiveMaterial } from '@app/core/model/supportive-material/supportive-material';
import { SupportiveMaterialService } from '@app/core/services/supportive-material/supportive-material.service';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { BaseEditorResolver } from '@common/base/base-editor.resolver';
import { Guid } from '@common/types/guid';
import { takeUntil, tap } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
@Injectable()
export class SupportiveMaterialEditorResolver extends BaseEditorResolver {
constructor(private supportiveMaterialService: SupportiveMaterialService, private breadcrumbService: BreadcrumbService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<SupportiveMaterial>(x => x.id),
nameof<SupportiveMaterial>(x => x.type),
nameof<SupportiveMaterial>(x => x.languageCode),
nameof<SupportiveMaterial>(x => x.payload),
nameof<SupportiveMaterial>(x => x.createdAt),
nameof<SupportiveMaterial>(x => x.updatedAt),
nameof<SupportiveMaterial>(x => x.hash),
nameof<SupportiveMaterial>(x => x.isActive)
]
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const fields = [
...SupportiveMaterialEditorResolver.lookupFields()
];
const id = route.paramMap.get('id');
if (id != null) {
return this.supportiveMaterialService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.languageCode)), takeUntil(this._destroyed));
}
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
@Injectable()
export class SupportiveMaterialEditorService {
private validationErrorModel: ValidationErrorModel;
public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void {
this.validationErrorModel = validationErrorModel;
}
public getValidationErrorModel(): ValidationErrorModel {
return this.validationErrorModel;
}
}