diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/UserGuideController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/UserGuideController.java new file mode 100644 index 000000000..e6a5b7c50 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/UserGuideController.java @@ -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 walk = Files.walk(Paths.get(this.environment.getProperty("userguide.path"))); + List 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); + + } +} diff --git a/dmp-backend/web/src/main/resources/application-devel.properties b/dmp-backend/web/src/main/resources/application-devel.properties index 581a79c7e..3df62afa4 100644 --- a/dmp-backend/web/src/main/resources/application-devel.properties +++ b/dmp-backend/web/src/main/resources/application-devel.properties @@ -68,4 +68,6 @@ zenodo.access_token= #############CONTACT EMAIL CONFIGURATIONS######### contact_email.mail= -language.path=/tempLang/i18n/ \ No newline at end of file +language.path=tempLang/i18n/ + +userguide.path=tempGuide/ \ No newline at end of file diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index f3e654c82..85b0655aa 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -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), diff --git a/dmp-frontend/src/app/app.module.ts b/dmp-frontend/src/app/app.module.ts index a480eeb31..44eb869ba 100644 --- a/dmp-frontend/src/app/app.module.ts +++ b/dmp-frontend/src/app/app.module.ts @@ -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'); } diff --git a/dmp-frontend/src/app/core/core-service.module.ts b/dmp-frontend/src/app/core/core-service.module.ts index 411ed3044..0fbfbf240 100644 --- a/dmp-frontend/src/app/core/core-service.module.ts +++ b/dmp-frontend/src/app/core/core-service.module.ts @@ -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 ], }; } diff --git a/dmp-frontend/src/app/core/services/user-guide/user-guide.service.ts b/dmp-frontend/src/app/core/services/user-guide/user-guide.service.ts new file mode 100644 index 000000000..7eaddb288 --- /dev/null +++ b/dmp-frontend/src/app/core/services/user-guide/user-guide.service.ts @@ -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> { + 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'} }); + } + +} diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.html b/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.html index 4dd12fe7f..5c35b14c0 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.html +++ b/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.html @@ -7,6 +7,13 @@ Guide Help --> +
+
+

+ + {{'FOOTER.GUIDE' | translate}}

+
+

diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.ts b/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.ts index dda6326be..495defadf 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar-footer/sidebar-footer.component.ts @@ -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); } diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar.module.ts b/dmp-frontend/src/app/ui/sidebar/sidebar.module.ts index 9a05ddb09..f3821a24d 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar.module.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar.module.ts @@ -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, diff --git a/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.html b/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.html new file mode 100644 index 000000000..1d6a8e806 --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.html @@ -0,0 +1,16 @@ +

+
+
+ {{'GUIDE.TITLE' | translate}} +
+
+ close +
+
+
+ +
+
+
+
+
diff --git a/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.scss b/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.scss new file mode 100644 index 000000000..cf0d57845 --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.scss @@ -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; +} diff --git a/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.ts b/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.ts new file mode 100644 index 000000000..15abff28c --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/dialog/user-guide-dialog.component.ts @@ -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, + @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); + } +} diff --git a/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.html b/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.html new file mode 100644 index 000000000..1e66fb9c8 --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.html @@ -0,0 +1 @@ +
diff --git a/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.scss b/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.scss new file mode 100644 index 000000000..f7248a481 --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.scss @@ -0,0 +1,7 @@ +:host ::ng-deep .href{ + color: lightblue !important; +} + +:host :hover ::ng-deep .href { + cursor: pointer !important; +} diff --git a/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.ts b/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.ts new file mode 100644 index 000000000..64388eb4b --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/user-guide-content/user-guide-content.component.ts @@ -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)); + } + } + +} diff --git a/dmp-frontend/src/app/ui/user-guide/user-guide.module.ts b/dmp-frontend/src/app/ui/user-guide/user-guide.module.ts new file mode 100644 index 000000000..ec7507784 --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/user-guide.module.ts @@ -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 { } diff --git a/dmp-frontend/src/app/ui/user-guide/user-guide.routing.ts b/dmp-frontend/src/app/ui/user-guide/user-guide.routing.ts new file mode 100644 index 000000000..b2a0a48d8 --- /dev/null +++ b/dmp-frontend/src/app/ui/user-guide/user-guide.routing.ts @@ -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 { } diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index b8813ea68..9d3e44658 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -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": ""