Add user guide on the sidebar footer (ref #239)

This commit is contained in:
George Kalampokis 2020-02-12 18:24:42 +02:00
parent 68908d3d77
commit 85689db9aa
18 changed files with 300 additions and 3 deletions

View File

@ -0,0 +1,56 @@
package eu.eudat.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@RestController
@CrossOrigin
@RequestMapping(value = {"/api/userguide/"})
public class UserGuideController {
private Environment environment;
@Autowired
public UserGuideController(Environment environment) {
this.environment = environment;
}
@RequestMapping(path = "current", method = RequestMethod.GET )
public ResponseEntity getUserGuide() throws IOException {
Stream<Path> walk = Files.walk(Paths.get(this.environment.getProperty("userguide.path")));
List<String> result = walk.filter(Files::isRegularFile)
.map(Path::toString).collect(Collectors.toList());
String fileName = result.get(0);
InputStream is = new FileInputStream(fileName);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(is.available());
responseHeaders.setContentType(MediaType.TEXT_HTML);
responseHeaders.set("Content-Disposition", "attachment;filename=" + fileName);
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
byte[] content = new byte[is.available()];
is.read(content);
is.close();
return new ResponseEntity<>(content, responseHeaders, HttpStatus.OK);
}
}

View File

@ -68,4 +68,6 @@ zenodo.access_token=
#############CONTACT EMAIL CONFIGURATIONS#########
contact_email.mail=
language.path=/tempLang/i18n/
language.path=tempLang/i18n/
userguide.path=tempGuide/

View File

@ -118,6 +118,14 @@ const appRoutes: Routes = [
title: 'FAQ.TITLE'
}
},
{
path: 'user-guide',
loadChildren: () => import('./ui/user-guide/user-guide.module').then(m => m.UserGuideModule),
data: {
breadcrumb: true,
title: 'GUIDE.TITLE'
}
},
{
path: 'privacy-policy',
loadChildren: () => import('./ui/sidebar/sidebar-footer/privacy/privacy.module').then(m => m.PrivacyModule),

View File

@ -33,6 +33,7 @@ import { BaseHttpService } from './core/services/http/base-http.service';
// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateServerLoader(http);
//GK: In case we want the original translation provider uncomment the line below.
//return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
}

View File

@ -38,6 +38,7 @@ import { ContactSupportService } from './services/contact-support/contact-suppor
import { LanguageService } from './services/language/language.service';
import { AdminAuthGuard } from './admin-auth-guard.service';
import { LockService } from './services/lock/lock.service';
import { UserGuideService } from './services/user-guide/user-guide.service';
//
//
// This is shared module that provides all the services. Its imported only once on the AppModule.
@ -95,7 +96,8 @@ export class CoreServiceModule {
EmailConfirmationService,
ContactSupportService,
LanguageService,
LockService
LockService,
UserGuideService
],
};
}

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { environment } from 'environments/environment';
import { Observable } from 'rxjs';
import { HttpResponse, HttpClient } from '@angular/common/http';
import { BaseHttpService } from '../http/base-http.service';
@Injectable()
export class UserGuideService {
private userGuideUrl = `${environment.Server}userguide`;
constructor(
private translate: TranslateService,
private http: HttpClient,
private baseHttp: BaseHttpService
) {}
public getUserGuide(): Observable<HttpResponse<Blob>> {
return this.http.get(`${this.userGuideUrl}/current`, { responseType: 'blob', observe: 'response', headers: {'Content-type': 'text/html',
'Accept': 'text/html',
'Access-Control-Allow-Origin': environment.App,
'Access-Control-Allow-Credentials': 'true'} });
}
}

View File

@ -7,6 +7,13 @@
<a class="option vl" href="#"><i class="fa fa-book style-icon"></i>Guide</a>
<a class="option" href="#"><i class="fa fa-life-ring style-icon"></i>Help</a>
</div> -->
<div class="row d-flex flex-reverse">
<div class="col-12 text-center">
<p class="option" (click)="openUserGuideDialog()" [ngClass]="{'option-active': this.router.url === '/user-guide'}">
<!-- <i class="fa fa-question-circle pr-2 pt-1"></i> -->
{{'FOOTER.GUIDE' | translate}}</p>
</div>
</div>
<div class="row d-flex flex-reverse">
<div class="col-6 text-center">
<p class="option" (click)="openGlossaryDialog()" [ngClass]="{'option-active': this.router.url === '/glossary'}">

View File

@ -14,6 +14,7 @@ import { ValidationErrorModel } from '@common/forms/validation/error-model/valid
import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators';
import { AuthService } from "@app/core/services/auth/auth.service";
import { UserGuideDialogComponent } from '@app/ui/user-guide/dialog/user-guide-dialog.component';
@Component({
selector: 'app-sidebar-footer',
@ -97,6 +98,20 @@ export class SidebarFooterComponent extends BaseComponent implements OnInit {
}
}
openUserGuideDialog() {
if (this.dialog.openDialogs.length > 0) {
this.dialog.closeAll();
}
else {
const dialogRef = this.dialog.open(UserGuideDialogComponent, {
disableClose: true,
data: {
isDialog: true
}
});
}
}
onCallbackSuccess(): void {
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-EMAIL-SEND'), SnackBarNotificationLevel.Success);
}

View File

@ -7,6 +7,7 @@ import { FaqModule } from '../faq/faq.module';
import { GlossaryModule } from '../glossary/glossary.module';
import { SidebarFooterComponent } from './sidebar-footer/sidebar-footer.component';
import { SidebarComponent } from './sidebar.component';
import { UserGuideModule } from '../user-guide/user-guide.module';
@NgModule({
imports: [
@ -15,7 +16,8 @@ import { SidebarComponent } from './sidebar.component';
GlossaryModule,
FaqModule,
RouterModule,
ContactModule
ContactModule,
UserGuideModule
],
declarations: [
SidebarComponent,

View File

@ -0,0 +1,16 @@
<div class="faq">
<div class="row d-flex flex-row">
<div mat-dialog-title class="col-auto">
{{'GUIDE.TITLE' | translate}}
</div>
<div class="col-auto ml-auto close-btn" (click)="close()">
<mat-icon>close</mat-icon>
</div>
</div>
<div mat-dialog-content class="row">
<app-user-guide-content></app-user-guide-content>
</div>
<div mat-dialog-actions class="row">
<div class="ml-auto col-auto"><button mat-button mat-dialog-close mat-raised-button color="primary" (click)="cancel()">{{'FAQ.CLOSE' | translate}}</button></div>
</div>
</div>

View File

@ -0,0 +1,17 @@
.form {
min-width: 150px;
max-width: 500px;
width: 100%;
}
.full-width {
width: 100%;
}
.close-btn {
cursor: pointer;
}
.faq {
margin-bottom: 1.125rem;
}

View File

@ -0,0 +1,31 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-user-guide-dialog',
templateUrl: './user-guide-dialog.component.html',
styleUrls: ['./user-guide-dialog.component.scss']
})
export class UserGuideDialogComponent {
public isDialog: boolean = false;
constructor(
public dialogRef: MatDialogRef<UserGuideDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.isDialog = data.isDialog;
}
cancel() {
this.dialogRef.close();
}
send() {
this.dialogRef.close(this.data);
}
close() {
this.dialogRef.close(false);
}
}

View File

@ -0,0 +1 @@
<div id='userguide' [innerHTML]="guideHTML"></div>

View File

@ -0,0 +1,7 @@
:host ::ng-deep .href{
color: lightblue !important;
}
:host :hover ::ng-deep .href {
cursor: pointer !important;
}

View File

@ -0,0 +1,58 @@
import { Component, OnInit, AfterViewChecked } from '@angular/core';
import { UserGuideService } from '@app/core/services/user-guide/user-guide.service';
import { BaseComponent } from '@common/base/base.component';
import { takeUntil } from 'rxjs/internal/operators/takeUntil';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-user-guide-content',
templateUrl: './user-guide-content.component.html',
styleUrls: ['./user-guide-content.component.scss']
})
export class UserGuideContentComponent extends BaseComponent implements OnInit, AfterViewChecked {
guideHTML: any;
constructor(
private userGuideService: UserGuideService,
private sanitizer: DomSanitizer
) { super(); }
ngOnInit() {
this.userGuideService.getUserGuide()
.pipe(takeUntil(this._destroyed))
.subscribe(response => {
const blob = new Blob([response.body], { type: 'text/html' });
this.readBlob(blob);
});
}
ngAfterViewChecked(): void {
this.parse();
}
readBlob(blob: Blob) {
const fr = new FileReader();
fr.onload = ev => {
this.guideHTML = this.sanitizer.bypassSecurityTrustHtml(fr.result as string);
this.parse();
};
fr.readAsText(blob);
}
scroll(ev: Event) {
document.getElementById((ev.srcElement as any).getAttribute('path')).scrollIntoView({ behavior: "smooth", block: "start" });
}
private parse() {
const specialElements: HTMLCollection = document.getElementsByClassName('href');
for (let i = 0; i < specialElements.length; i++) {
const element = specialElements.item(i);
element.addEventListener('click',(ev) => this.scroll(ev));
}
}
}

View File

@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { UserGuideRoutingModule } from './user-guide.routing';
import { UserGuideContentComponent } from './user-guide-content/user-guide-content.component';
import { UserGuideDialogComponent } from './dialog/user-guide-dialog.component';
import { CommonUiModule } from '@common/ui/common-ui.module';
@NgModule({
declarations: [
UserGuideContentComponent,
UserGuideDialogComponent
],
imports: [
CommonUiModule,
UserGuideRoutingModule
],
entryComponents: [
UserGuideContentComponent,
UserGuideDialogComponent
],
exports: [
UserGuideDialogComponent
]
})
export class UserGuideModule { }

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UserGuideContentComponent } from './user-guide-content/user-guide-content.component';
import { UserGuideDialogComponent } from './dialog/user-guide-dialog.component';
const routes: Routes = [
{
path: '',
component: UserGuideDialogComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UserGuideRoutingModule { }

View File

@ -997,6 +997,7 @@
"FOOTER": {
"CONTACT-SUPPORT": "Contact Support",
"FAQ": "FAQ",
"GUIDE": "User Guide",
"GLOSSARY": "Glossary",
"TERMS-OF-SERVICE": "Terms Of Service",
"PRIVACY-POLICY": "Privacy Policy"
@ -1011,6 +1012,10 @@
"TITLE-DASHED": "-FAQ-",
"CLOSE": "Close"
},
"GUIDE": {
"TITLE": "-User Guide-",
"MAIN-CONTENT": ""
},
"PRIVACY-POLICY": {
"TITLE": "-Privacy Policy-",
"MAIN-CONTENT": ""