Add User Guide Editor (ref #239)

This commit is contained in:
George Kalampokis 2020-02-13 18:15:34 +02:00
parent 85689db9aa
commit 9018795e6e
15 changed files with 246 additions and 13 deletions

View File

@ -1,5 +1,8 @@
package eu.eudat.controllers;
import eu.eudat.models.data.helpers.responses.ResponseItem;
import eu.eudat.models.data.userguide.UserGuide;
import eu.eudat.types.ApiMessageCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
@ -8,9 +11,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -39,10 +40,13 @@ public class UserGuideController {
String fileName = result.get(0);
InputStream is = new FileInputStream(fileName);
String[] filepath = fileName.split("\\.")[0].split("\\\\");
String simplename = filepath[filepath.length - 1];
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(is.available());
responseHeaders.setContentType(MediaType.TEXT_HTML);
responseHeaders.set("Content-Disposition", "attachment;filename=" + fileName);
responseHeaders.set("Content-Disposition", "attachment;filename=" + simplename);
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
@ -53,4 +57,14 @@ public class UserGuideController {
return new ResponseEntity<>(content, responseHeaders, HttpStatus.OK);
}
@RequestMapping(value = "current", method = RequestMethod.POST)
public @ResponseBody
ResponseEntity<ResponseItem<String>> updateGuide(@RequestBody UserGuide guide) throws Exception {
String fileName = this.environment.getProperty("userguide.path") + guide.getName() + ".html";
OutputStream os = new FileOutputStream(fileName);
os.write(guide.getHtml().getBytes());
os.close();
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<String>().status(ApiMessageCode.SUCCESS_MESSAGE).message("Updated").payload("Updated"));
}
}

View File

@ -0,0 +1,23 @@
package eu.eudat.models.data.userguide;
public class UserGuide {
private String name;
private String html;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHtml() {
return html;
}
public void setHtml(String html) {
this.html = html;
}
}

View File

@ -18,7 +18,10 @@
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
"src/favicon.ico"
"src/favicon.ico",
{ "glob": "**/*", "input": "node_modules/tinymce/skins", "output": "/tinymce/skins/" },
{ "glob": "**/*", "input": "node_modules/tinymce/themes", "output": "/tinymce/themes/" },
{ "glob": "**/*", "input": "node_modules/tinymce/plugins", "output": "/tinymce/plugins/" }
],
"styles": [
"src/styles.scss",
@ -27,7 +30,9 @@
"node_modules/cookieconsent/build/cookieconsent.min.css"
],
"scripts": [
"node_modules/cookieconsent/build/cookieconsent.min.js"
"node_modules/cookieconsent/build/cookieconsent.min.js",
"node_modules/tinymce/tinymce.min.js"
]
},
"configurations": {
@ -95,11 +100,15 @@
"styles": [
"src/styles.scss",
"src/assets/scss/material-dashboard.scss",
"src/assets/css/demo.css"
"src/assets/css/demo.css",
"node_modules/tinymce/tinymce.min.js"
],
"assets": [
"src/assets",
"src/favicon.ico"
"src/favicon.ico",
{ "glob": "**/*", "input": "node_modules/tinymce/skins", "output": "/tinymce/skins/" },
{ "glob": "**/*", "input": "node_modules/tinymce/themes", "output": "/tinymce/themes/" },
{ "glob": "**/*", "input": "node_modules/tinymce/plugins", "output": "/tinymce/plugins/" }
]
}
},

View File

@ -22,6 +22,7 @@
"@ngx-translate/core": "^11.0.1",
"@ngx-translate/http-loader": "^4.0.0",
"@swimlane/ngx-datatable": "^16.0.2",
"@tinymce/tinymce-angular": "^3.4.0",
"@w11k/angular-sticky-things": "^1.1.2",
"bootstrap": "^4.3.1",
"cookieconsent": "^3.1.1",
@ -32,6 +33,7 @@
"ngx-cookie-service": "^2.2.0",
"ngx-cookieconsent": "^2.2.3",
"rxjs": "^6.3.2",
"tinymce": "^5.1.6",
"tslib": "^1.10.0",
"web-animations-js": "^2.3.2",
"zone.js": "~0.9.1"

View File

@ -180,6 +180,14 @@ const appRoutes: Routes = [
title: 'GENERAL.TITLES.LANGUAGE-EDITOR'
},
},
{
path: 'user-guide-editor',
loadChildren: () => import('./ui/user-guide-editor/user-guide-editor.module').then(m => m.UserGuideEditorModule),
data: {
breadcrumb: true,
title: 'GENERAL.TITLES.GUIDE-EDITOR'
},
},
{
path: 'login/admin',
loadChildren: () => import('./ui/auth/admin-login/admin-login.module').then(m => m.AdminLoginModule),

View File

@ -22,4 +22,8 @@ export class UserGuideService {
'Access-Control-Allow-Credentials': 'true'} });
}
public updateUserGuide(data: any): Observable<String> {
return this.http.post<string>(`${this.userGuideUrl}/current`, data);
}
}

View File

@ -51,7 +51,8 @@ export const ADMIN_ROUTES: RouteInfo[] = [
{ path: '/dmp-profiles', title: 'SIDE-BAR.DMP-TEMPLATES', icon: 'library_books' },
{ path: '/dataset-profiles', title: 'SIDE-BAR.DATASET-TEMPLATES', icon: 'library_books' },
{ path: '/users', title: 'SIDE-BAR.USERS', icon: 'people' },
{ path: '/language-editor', title: 'SIDE-BAR.LANGUAGE-EDITOR', icon: 'language'}
{ path: '/language-editor', title: 'SIDE-BAR.LANGUAGE-EDITOR', icon: 'language'},
{ path: '/user-guide-editor', title: 'SIDE-BAR.GUIDE-EDITOR', icon: 'import_contacts'}
];
// export const HISTORY_ROUTES: RouteInfo[] = [
// { path: '/typography', title: 'SIDE-BAR.HISTORY-VISITED', icon: 'visibility'},

View File

@ -0,0 +1,30 @@
<div class="user-guide-editor">
<form (ngSubmit)="submit()" [formGroup]="formGroup">
<div class="row">
<mat-card class="col-md-8 offset-md-2">
<mat-card-title><div>{{'GUIDE.TITLE' | translate}}</div></mat-card-title>
<mat-card-content>
<editor [init]="{
base_url: '/tinymce',
suffix: '.min',
height: 800,
menubar: true,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen fullpage',
'insertdatetime media table paste code help wordcount importcss'
],
toolbar:
'undo redo | formatselect | bold italic backcolor | \
alignleft aligncenter alignright alignjustify | \
bullist numlist outdent indent | code | preview | removeformat | help'
}" formControlName="html"></editor>
</mat-card-content>
</mat-card>
<button mat-fab class="mat-fab-bottom-right save-btn" type="submit">
<mat-icon class="mat-24" title="save">save</mat-icon>
</button>
</div>
</form>
</div>

View File

@ -0,0 +1,13 @@
.user-guide-editor {
padding-top: 5em;
padding-bottom: 2em;
.save-btn {
padding-top: inherit !important;
top: auto !important;
width: 56px !important;
bottom: 10px;
position: fixed;
right: 10px;
}
}

View File

@ -0,0 +1,91 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { BaseComponent } from '@common/base/base.component';
import { UserGuideService } from '@app/core/services/user-guide/user-guide.service';
import { 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 { isNullOrUndefined } from 'util';
import { environment } from 'environments/environment';
@Component({
selector: 'app-user-guide-editor',
templateUrl: './user-guide-editor.component.html',
styleUrls: ['./user-guide-editor.component.scss']
})
export class UserGuideEditorComponent extends BaseComponent implements OnInit {
public formGroup: FormGroup;
private formBuilder: FormBuilder;
constructor(private userGuideService: UserGuideService,
private uiNotificationService: UiNotificationService,
private translate: TranslateService,
private router: Router,
) { super(); }
ngOnInit() {
this.formBuilder = new FormBuilder();
this.formGroup = this.formBuilder.group({
name: [''],
html: ['']
});
this.userGuideService.getUserGuide().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);
});
}
private parseText(source: string): string {
source = source.replace(/src="images/g, `src="${environment.guideAssets}`);
source = source.replace(/\r\n +>/g, '>\r\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}>\r\n`);
});
}
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);
result = result.replace(/href="#/g, 'class="href" path="');
this.formGroup.get('html').patchValue(result);
this.userGuideService.updateUserGuide(this.formGroup.value).pipe(takeUntil(this._destroyed))
.subscribe(
complete => {
this.onCallbackSuccess(complete);
},
error => {
this.onCallbackError(error);
}
);
}
onCallbackSuccess(id?: String): void {
this.uiNotificationService.snackBarNotification( this.translate.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
this.router.navigate(['/reload']).then(() => this.router.navigate(['/user-guide-editor']));
}
onCallbackError(error: any) {
this.uiNotificationService.snackBarNotification( error, SnackBarNotificationLevel.Error);
//this.validateAllFormFields(this.formGroup);
}
}

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { EditorModule } from '@tinymce/tinymce-angular';
import { UserGuideEditorRoutingModule } from './user-guide-editor.routing';
import { UserGuideEditorComponent } from './user-guide-editor.component';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
@NgModule({
declarations: [UserGuideEditorComponent],
imports: [
CommonUiModule,
CommonFormsModule,
UserGuideEditorRoutingModule,
EditorModule
]
})
export class UserGuideEditorModule { }

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UserGuideEditorComponent } from './user-guide-editor.component';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
const routes: Routes = [
{ path: '', component: UserGuideEditorComponent, canActivate: [AdminAuthGuard] },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UserGuideEditorRoutingModule { }

View File

@ -120,7 +120,8 @@
"DATASET-UPDATE": "Update Dataset Description",
"DATASET-PROFILES-NEW-VERSION": "New Version of Dataset Description Template",
"DATASET-PROFILES-CLONE": "New Clone of Dataset Description Template",
"LANGUAGE-EDITOR": "Language Editor"
"LANGUAGE-EDITOR": "Language Editor",
"GUIDE-EDITOR": "User Guide Editor"
},
"FILE-TYPES": {
"PDF": "PDF",
@ -215,7 +216,8 @@
"DATASET-TEMPLATES": "Dataset Description Templates",
"DMP-TEMPLATES": "DMP Templates",
"USERS": "Users",
"LANGUAGE-EDITOR":"Language Editor"
"LANGUAGE-EDITOR":"Language Editor",
"GUIDE-EDITOR": "User Guide Editor"
},
"DATASET-PROFILE-EDITOR": {
"TITLE": {

View File

@ -120,7 +120,8 @@
"DATASET-UPDATE": "Update Dataset Description",
"DATASET-PROFILES-NEW-VERSION": "New Version of Dataset Description Template",
"DATASET-PROFILES-CLONE": "New Clone of Dataset Description Template",
"LANGUAGE-EDITOR": "Language Editor"
"LANGUAGE-EDITOR": "Language Editor",
"GUIDE-EDITOR": "User Guide Editor"
},
"FILE-TYPES": {
"PDF": "PDF",
@ -215,7 +216,8 @@
"DATASET-TEMPLATES": "Dataset Description Templates",
"DMP-TEMPLATES": "DMP Templates",
"USERS": "Users",
"LANGUAGE-EDITOR":"Language Editor"
"LANGUAGE-EDITOR":"Language Editor",
"GUIDE-EDITOR": "User Guide Editor"
},
"DATASET-PROFILE-EDITOR": {
"TITLE": {

View File

@ -44,4 +44,5 @@ export const environment = {
logLevels: ["debug", "info", "warning", "error"]
},
lockInterval: 60000,
guideAssets: "assets/images/guide"
};