diff --git a/.gitignore b/.gitignore index 1278fe7eab77d49c0340f2ff492c77c8121364cf..383fd21c639c75b1cbbdcc8002bef28be69ec4c4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ package-lock.json src/res/raw src/plugin_examples/*/ .vscode +.idea +deploy/datasets/data/ diff --git a/deploy/datasets/index.js b/deploy/datasets/index.js index 0eb0b18e955f4d6e77c25061caae26a04bde6a79..0e884865bde8479f96771220ec0d6e75d06e9e3c 100644 --- a/deploy/datasets/index.js +++ b/deploy/datasets/index.js @@ -4,6 +4,11 @@ const fs = require('fs') const datasetsRouter = express.Router() const { init, getDatasets, getPreview } = require('./query') +const bodyParser = require('body-parser') +datasetsRouter.use(bodyParser.urlencoded({ extended: false })) +datasetsRouter.use(bodyParser.json()) + + init().catch(e => { console.warn(`dataset init failed`, e) }) @@ -13,6 +18,8 @@ datasetsRouter.use((req, res, next) => { next() }) + + datasetsRouter.use('/spatialSearch', require('./spatialRouter')) datasetsRouter.get('/templateName/:templateName', (req, res, next) => { @@ -95,4 +102,43 @@ datasetsRouter.get('/previewFile', (req, res) => { } }) + + +var JSZip = require("jszip"); + +datasetsRouter.post("/downloadParcellationThemself", (req,res, next) => { + + + //ToDo We can add termsOfUse Text file somewhere - will be better + const termsOfUse = 'Access to the data and metadata provided through HBP Knowledge Graph Data Platform ' + + '("KG") requires that you cite and acknowledge said data and metadata according to the Terms and' + + ' Conditions of the Platform.\r\n## Citation requirements are outlined - https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use#citations' + + '\r\n## Acknowledgement requirements are outlined - https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use#acknowledgements' + + '\r\n## These outlines are based on the authoritative Terms and Conditions are found - https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use' + + '\r\n## If you do not accept the Terms & Conditions you are not permitted to access or use the KG to search for, to submit, to post, or to download any materials found there-in.' + + + var zip = new JSZip(); + + zip.file("Publications.txt", req.body['publicationsText']) + zip.file("Terms of use.txt", termsOfUse) + + + + //ToDo: Need to download files dynamicly. Nii folder should remove + if (req.body['fileName'].includes("JuBrain Cytoarchitectonic Atlas")) { + var nii = zip.folder("nifti") + nii.file('jubrain-max-pmap-v22c_space-mnicolin27.nii', fs.readFileSync(path.join(__dirname, 'nii') + '/' + 'jubrain-max-pmap-v22c_space-mnicolin27.nii')) + } + + zip.generateAsync({type:"base64"}) + .then(function (content) { + // location.href="data:application/zip;base64,"+content; + res.end(content) + }); + + + +}); + module.exports = datasetsRouter \ No newline at end of file diff --git a/deploy/datasets/nii/jubrain-max-pmap-v22c_space-mnicolin27.nii b/deploy/datasets/nii/jubrain-max-pmap-v22c_space-mnicolin27.nii new file mode 100644 index 0000000000000000000000000000000000000000..502dcb1e31b901ea3191c86717795d91880b9e3e Binary files /dev/null and b/deploy/datasets/nii/jubrain-max-pmap-v22c_space-mnicolin27.nii differ diff --git a/deploy/package.json b/deploy/package.json index e76b3d9bbe32d2aae33a3d7ae39d7ae3740f50e1..9c2eee4fd7c868ffb436d016a306805509ba650d 100644 --- a/deploy/package.json +++ b/deploy/package.json @@ -13,8 +13,11 @@ "author": "", "license": "ISC", "dependencies": { + "body-parser": "^1.19.0", "express": "^4.16.4", "express-session": "^1.15.6", + "jszip": "^3.2.1", + "jszip-utils": "0.0.2", "jwt-decode": "^2.2.0", "memorystore": "^1.6.1", "openid-client": "^2.4.5", diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index 619548fb845d8d71addf3b323b7a3e435aab1bbb..4189e85dc1f36c3da550156882948d43ef31e0b0 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -50,7 +50,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { @ViewChild('publications') publications: TemplateRef<any> @ViewChild('sidenav', { read: ElementRef} ) mobileSideNav: ElementRef - /** * required for styling of all child components */ @@ -257,7 +256,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { } downloadPublications() { - const filename = this.selectedTemplate.name + ' - ' + this.selectedParcellation.name + '.txt' + const fileName = this.selectedTemplate.name + ' - ' + this.selectedParcellation.name let publicationsText = '' if (this.tPublication) { @@ -275,7 +274,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { }); } - this.zipFileDownloadService.saveTextAsFile(publicationsText, filename) + this.zipFileDownloadService.downloadZip(publicationsText, fileName) publicationsText = '' } diff --git a/src/atlasViewer/atlasViewer.style.css b/src/atlasViewer/atlasViewer.style.css index e3b805bfa801af142bd6a2eb8c1f3721b11276d5..283df246c121ad228e514660757f1cbff1c8a20b 100644 --- a/src/atlasViewer/atlasViewer.style.css +++ b/src/atlasViewer/atlasViewer.style.css @@ -189,4 +189,8 @@ mat-sidenav { .downloadPublications { align-self: flex-end; outline: none; +} + +.textPartInPublications { + text-align: left; } \ No newline at end of file diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index dddf61d6222aefd4fa8eea60713e0c9f88b69570..139d8ba70e03a8d5d91ba5f8f2216d0f0f4983a6 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -265,14 +265,14 @@ <div *ngIf="tPublication"> <p>{{selectedTemplate['name']}} Publication(s)</p> - <div *ngFor="let tp of tPublication"> + <div *ngFor="let tp of tPublication" class="textPartInPublications"> <a [href]="tp['doi']" target="_blank">{{tp['citation']}}</a> </div> <hr *ngIf="pPublication"> </div> <div *ngIf="pPublication"> <p>{{selectedParcellation['name']}} Publication(s)</p> - <div *ngFor="let pp of pPublication"> + <div *ngFor="let pp of pPublication" class="textPartInPublications"> <a [href]="pp['doi']" target="_blank">{{pp['citation']}}</a> </div> </div> diff --git a/src/components/radiolist/radiolist.component.ts b/src/components/radiolist/radiolist.component.ts index dd0bca365ddef44e390aa7afed23e5bd4e3bffa2..bd5fd328cf7cb19dadead6c973cbf7d297cc8ffa 100644 --- a/src/components/radiolist/radiolist.component.ts +++ b/src/components/radiolist/radiolist.component.ts @@ -68,12 +68,12 @@ export class RadioList{ downloadPublications(item) { - const filename = item['name'] + ' publications.txt' + const filename = item['name'] let publicationsText = item['name'] + ' Publications:\r\n' item['properties']['publications'].forEach((p, i) => { publicationsText += '\t' + (i+1) + '. ' + p['citation'] + ' - ' + p['doi'] + '\r\n' }); - this.zipFileDownloadService.saveTextAsFile(publicationsText, filename) + this.zipFileDownloadService.downloadZip(publicationsText, filename) publicationsText = '' } } \ No newline at end of file diff --git a/src/components/radiolist/radiolist.style.css b/src/components/radiolist/radiolist.style.css index 6973a4c9701b781ede3ca2e099a499ff464c6209..c3fcf157b57217734cf562400ddcc17697a67ba2 100644 --- a/src/components/radiolist/radiolist.style.css +++ b/src/components/radiolist/radiolist.style.css @@ -77,4 +77,8 @@ ul,span.dropdown-item-1 .downloadPublications { align-self: flex-end; outline: none; +} + +.textPartInPublications { + text-align: left; } \ No newline at end of file diff --git a/src/components/radiolist/radiolist.template.html b/src/components/radiolist/radiolist.template.html index 1bf2e9857f410144e7c4714849c9afb55b80fa16..ffa5c4fc1d7ed5c044bbbbc2926d2acc706796b5 100644 --- a/src/components/radiolist/radiolist.template.html +++ b/src/components/radiolist/radiolist.template.html @@ -23,7 +23,7 @@ <div *ngIf="input['properties'] && input['properties']['publications']" class="timerToast"> <button mat-mini-fab color="primary" class="downloadPublications" (click)="downloadPublications(input)">↓</button> <p>{{input['name']}} Publication(s)</p> - <div *ngFor="let p of input['properties']['publications']"> + <div *ngFor="let p of input['properties']['publications']" class="textPartInPublications"> <a [href]="p['doi']" target="_blank">{{p['citation']}}</a> </div> </div> diff --git a/src/main.module.ts b/src/main.module.ts index 169f495052a8fc24d3540d7a173ea15a3e68303b..7523afe930f0ae4e24b4a066b6d2256cef37abe7 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -43,6 +43,7 @@ import 'hammerjs'; import 'hammer-timejs'; import { ZipFileDownloadService } from "./services/zipFileDownload.service"; +import {HttpClientModule} from "@angular/common/http"; @NgModule({ imports : [ @@ -64,7 +65,8 @@ import { ZipFileDownloadService } from "./services/zipFileDownload.service"; dataStore, spatialSearchState, uiState, - }) + }), + HttpClientModule ], declarations : [ AtlasViewer, diff --git a/src/services/zipFileDownload.service.ts b/src/services/zipFileDownload.service.ts index e1524a68460a4e15dce9ad86c80c2f7fdeb688f7..434a11338646477647249970c75463c7b17fd2c2 100644 --- a/src/services/zipFileDownload.service.ts +++ b/src/services/zipFileDownload.service.ts @@ -1,33 +1,58 @@ import { Injectable } from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {AtlasViewerConstantsServices} from "src/atlasViewer/atlasViewer.constantService.service"; @Injectable({ providedIn: 'root' }) - export class ZipFileDownloadService { - saveTextAsFile(data, filename) { - if (!data) { - console.error('Console.save: No data') - return; - } - if (!filename) filename = 'Publication.json' + constructor(private httpClient: HttpClient, private constantService: AtlasViewerConstantsServices) {} - var blob = new Blob([data], { type: 'text/plain' }), - e = document.createEvent('MouseEvents'), - a = document.createElement('a') - // FOR IE: + downloadZip(publicationsText, fileName) { + const correctedName = fileName.replace(/[|&;$%@"<>()+,/]/g, "") + this.httpClient.post(this.constantService.backendUrl + 'datasets/downloadParcellationThemself', { + fileName: correctedName, + publicationsText: publicationsText, + },{responseType: "text"} + ).subscribe(data => { + this.downloadFile(data, correctedName) + }) + } - if (window.navigator && window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveOrOpenBlob(blob, filename); - } - else { - var e = document.createEvent('MouseEvents'), - a = document.createElement('a'); - - a.download = filename; - a.href = window.URL.createObjectURL(blob); - a.dataset.downloadurl = ['text/plain', a.download, a.href].join(':'); - e.initEvent('click', true, false); - a.dispatchEvent(e); + downloadFile(data, fileName) { + + const contentType = 'application/zip'; + const b64Data = data + + + const b64toBlob = (b64Data, contentType='', sliceSize=512) => { + const byteCharacters = atob(b64Data); + const byteArrays = []; + + for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { + const slice = byteCharacters.slice(offset, offset + sliceSize); + + const byteNumbers = new Array(slice.length); + for (let i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i); + } + + const byteArray = new Uint8Array(byteNumbers); + byteArrays.push(byteArray); + } + + const blob = new Blob(byteArrays, {type: contentType}); + return blob; } + + + const blob = b64toBlob(b64Data, contentType); + // const blob = new Blob([data], { type: 'text/csv' }); + const url= window.URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.download = fileName + '.zip'; + anchor.href = url; + anchor.click(); } + + } \ No newline at end of file