diff --git a/common/constants.js b/common/constants.js index 24a68a668d0a8981610489b36cb8496cd04d9704..1f596e7b311febefc0a44576e6cd74f589fc4ce9 100644 --- a/common/constants.js +++ b/common/constants.js @@ -5,6 +5,8 @@ TOGGLE_EXPLORE_PANEL: `Toggle explore panel`, MODALITY_FILTER: `Toggle dataset modality filter`, LIST_OF_DATASETS: `List of datasets`, + DOWNLOAD_PREVIEW: `Download`, + DOWNLOAD_PREVIEW_CSV: `Download CSV`, // overlay specific CONTEXT_MENU: `Viewer context menu`, diff --git a/docs/advanced/keyboard.md b/docs/advanced/keyboard.md index 04e5aacf0d396e6f619e722a4ec63f18a594ab22..d331cb87b092483f4d9fb98e1d8ffa4bd1ebfe4b 100644 --- a/docs/advanced/keyboard.md +++ b/docs/advanced/keyboard.md @@ -6,4 +6,5 @@ Please note that the keyboard shortcuts may alter the behaviour irreversibly. |---|---| |[0-9]|Toggle layer visibility| |[h] [?]|Show help| -|[o]|Toggle orthographic/perspective _3d view_ | \ No newline at end of file +|[o]|Toggle orthographic/perspective _3d view_ | +|[a]|Toggle axis visibility | diff --git a/docs/releases/v2.2.2.md b/docs/releases/v2.2.2.md index b848770a812d3152d8f1eae3adb79da1dd80e644..b2220aaad227bc7ecfdb3a8d4d157df7e741c8aa 100644 --- a/docs/releases/v2.2.2.md +++ b/docs/releases/v2.2.2.md @@ -4,3 +4,4 @@ - Fixed PMap color map reset colormap (#523) - Modal filters are now sorted alphabetically (#524) +- Restored ability to download csv and image from dataset preview (#522) diff --git a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js index 62ae98d68df6c51fcb5f98a29f2bd7f00767d55d..9eeb93161fa2747655248613201019fd1f54bf83 100644 --- a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js +++ b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js @@ -1,6 +1,6 @@ const { AtlasPage } = require('../util') const { ARIA_LABELS } = require('../../../common/constants') -const { TOGGLE_EXPLORE_PANEL, MODALITY_FILTER } = ARIA_LABELS +const { TOGGLE_EXPLORE_PANEL, MODALITY_FILTER, DOWNLOAD_PREVIEW, DOWNLOAD_PREVIEW_CSV } = ARIA_LABELS const templates = [ 'MNI Colin 27', @@ -103,6 +103,14 @@ describe('> receptor dataset previews', () => { await iavPage.waitFor(true, true) const modalHasCanvas = await iavPage.modalHasChild('canvas') expect(modalHasCanvas).toEqual(true) + + await iavPage.wait(500) + + const modalHasDownloadBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW}"]`) + const modalHasDownloadCSVBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW_CSV}"]`) + + expect(modalHasDownloadBtn).toEqual(true) + expect(modalHasDownloadCSVBtn).toEqual(true) }) it('> can display profile', async () => { @@ -113,6 +121,14 @@ describe('> receptor dataset previews', () => { await iavPage.waitFor(true, true) const modalHasCanvas = await iavPage.modalHasChild('canvas') expect(modalHasCanvas).toEqual(true) + + await iavPage.wait(500) + + const modalHasDownloadBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW}"]`) + const modalHasDownloadCSVBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW_CSV}"]`) + + expect(modalHasDownloadBtn).toEqual(true) + expect(modalHasDownloadCSVBtn).toEqual(true) }) }) it('> can display image', async () => { @@ -122,6 +138,14 @@ describe('> receptor dataset previews', () => { await iavPage.waitFor(true, true) const modalHasImage = await iavPage.modalHasChild('div[data-img-src]') expect(modalHasImage).toEqual(true) + + await iavPage.wait(500) + + const modalHasDownloadBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW}"]`) + const modalHasDownloadCSVBtn = await iavPage.modalHasChild(`[aria-label="${DOWNLOAD_PREVIEW_CSV}"]`) + + expect(modalHasDownloadBtn).toEqual(true) + expect(modalHasDownloadCSVBtn).toEqual(false) }) }) diff --git a/src/index.html b/src/index.html index 498849604f08e081d10541a3ecf8fb1d3ae344be..65ddf0e75d50a6ec7823dfc908b7e2ebc21b0d7b 100644 --- a/src/index.html +++ b/src/index.html @@ -11,7 +11,7 @@ <link rel="stylesheet" href="theme.css"> <link rel="stylesheet" href="version.css"> - <script src="https://unpkg.com/kg-dataset-previewer@0.0.17/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer> + <script src="https://unpkg.com/kg-dataset-previewer@0.0.18/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer> </script> <title>Interactive Atlas Viewer</title> diff --git a/src/res/ext/bigbrain.json b/src/res/ext/bigbrain.json index 7ac96b80533d17e1a521a62a9b0465c599c03dfe..b49c34f64a957f1ba114c407a6dd13f26ad7e014 100644 --- a/src/res/ext/bigbrain.json +++ b/src/res/ext/bigbrain.json @@ -8,7 +8,7 @@ "originDatasets": [ { "kgSchema": "minds/core/dataset/v1.0.0", - "kgId": "e32f9053-38c9-4911-b868-845c56828f4d" + "kgId": "d07f9305-1e75-4548-a348-b155fb323d31" } ], "nehubaConfigURL": "nehubaConfig/bigbrainNehubaConfig", diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/ui/databrowserModule/databrowser.useEffect.ts index 74eb19f0218e1df91ddd40d81f50b28a4b868d12..01f7db4680d5f90c788843d052e6b4888c9c7391 100644 --- a/src/ui/databrowserModule/databrowser.useEffect.ts +++ b/src/ui/databrowserModule/databrowser.useEffect.ts @@ -172,7 +172,7 @@ export class DataBrowserUseEffect implements OnDestroy { return forkJoin( from(this.kgSingleDatasetService.getInfoFromKg({ kgSchema: re[0], kgId: re[1] })), - this.http.get(`${DS_PREVIEW_URL}/${re[1]}/${filename}`) + this.http.get(`${DS_PREVIEW_URL}/${re[1]}/${encodeURIComponent(filename)}`) ).pipe( map(([ dataset, file ]) => { return { diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/ui/databrowserModule/kgSingleDatasetService.service.ts index f5abf717d1d08201c99bba3c63fa17c79fe89a28..e0f656389d95effdbe6a85de5248ef7730d089c3 100644 --- a/src/ui/databrowserModule/kgSingleDatasetService.service.ts +++ b/src/ui/databrowserModule/kgSingleDatasetService.service.ts @@ -35,15 +35,6 @@ export class KgSingleDatasetService implements OnDestroy { } } - // TODO deprecate, in favour of web component - public datasetHasPreview({ name }: { name: string } = { name: null }) { - if (!name) { throw new Error('kgSingleDatasetService#datasetHashPreview name must be defined') } - const _url = new URL(`${BACKENDURL.replace(/\/$/, '')}/datasets/hasPreview`) - const searchParam = _url.searchParams - searchParam.set('datasetName', name) - return this.http.get(_url.toString()) - } - public getInfoFromKg({ kgId, kgSchema = 'minds/core/dataset/v1.0.0' }: Partial<KgQueryInterface>) { const _url = new URL(`${BACKENDURL.replace(/\/$/, '')}/datasets/kgInfo`) const searchParam = _url.searchParams diff --git a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts b/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts index 48094f998f402e8d4230e16da6b7ab3740ad6b2d..ce664fd3d22ac13f336eed2355f53f3b602d39e2 100644 --- a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts +++ b/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.component.ts @@ -1,7 +1,33 @@ -import { Component, Input, Inject } from "@angular/core"; +import { Component, Input, Inject, ViewChild, ElementRef } from "@angular/core"; import { MAT_DIALOG_DATA } from "@angular/material/dialog"; import { AtlasViewerConstantsServices } from "../../singleDataset/singleDataset.base"; -import { Observable } from "rxjs"; +import { Observable, fromEvent, Subscription, from, of, throwError } from "rxjs"; +import { switchMapTo, catchError, take, concatMap, map, retryWhen, delay } from "rxjs/operators"; +import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; +import { ARIA_LABELS } from 'common/constants' + +const { + DOWNLOAD_PREVIEW, + DOWNLOAD_PREVIEW_CSV +} = ARIA_LABELS + +const fromPromiseRetry = ({ retries = 10, timeout = 100 } = {}) => { + const retryCounter = 0 + return (fn: () => Promise<any>) => new Observable(obs => { + fn() + .then(val => obs.next(val)) + .catch(e => obs.error(e)) + .finally(() => obs.complete()) + }).pipe( + + retryWhen(err => { + if (retryCounter >= retries) return throwError(err) + return err.pipe( + delay(timeout) + ) + }) + ) +} @Component({ templateUrl: './previewCW.template.html', @@ -12,6 +38,14 @@ import { Observable } from "rxjs"; export class PreviewComponentWrapper{ + public DOWNLOAD_PREVIEW_ARIA_LABEL = DOWNLOAD_PREVIEW + public DOWNLOAD_PREVIEW_CSV_ARIA_LABEL = DOWNLOAD_PREVIEW_CSV + + private subscriptions: Subscription[] = [] + + @ViewChild('dataPreviewerStencilCmp', { read: ElementRef, static: true }) + private dataPreviewerStencilCmp: ElementRef<any> + public darktheme$: Observable<boolean> @Input() @@ -28,7 +62,8 @@ export class PreviewComponentWrapper{ constructor( @Inject(MAT_DIALOG_DATA) data: any, - private constantService: AtlasViewerConstantsServices + private constantService: AtlasViewerConstantsServices, + private sanitizer: DomSanitizer ){ this.darktheme$ = this.constantService.darktheme$ @@ -40,4 +75,43 @@ export class PreviewComponentWrapper{ this.datasetName = datasetName } } + + public downloadHref: SafeResourceUrl + public downloadCsvHref: SafeResourceUrl + + ngAfterViewInit(){ + this.dataPreviewerStencilCmp.nativeElement.getDownloadPreviewHref() + + const hydrateHrefSubscription = fromEvent(this.dataPreviewerStencilCmp.nativeElement, 'renderEvent').pipe( + switchMapTo( + fromPromiseRetry()(() => this.dataPreviewerStencilCmp.nativeElement.getDownloadPreviewHref()).pipe( + concatMap((downloadHref: string) => { + return from(this.dataPreviewerStencilCmp.nativeElement.getDownloadCsvHref()).pipe( + catchError(err => of(null)), + map(csvHref => { + return { + downloadHref, + csvHref + } + }) + ) + }) + ) + ), + take(1) + ).subscribe(({ downloadHref, csvHref }) => { + if (csvHref) this.downloadCsvHref = this.sanitizer.bypassSecurityTrustResourceUrl(csvHref) + this.downloadHref = this.sanitizer.bypassSecurityTrustResourceUrl(downloadHref) + }) + + this.subscriptions.push( + hydrateHrefSubscription + ) + } + + ngOnDestroy(){ + while(this.subscriptions.length > 0) { + this.subscriptions.pop().unsubscribe() + } + } } \ No newline at end of file diff --git a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html b/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html index bb6a1112dcc8bc26f0f4dd41a2a020e2974b773c..8a62ab8c9865c17bdba5204ddd0241c11a602dad 100644 --- a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html +++ b/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html @@ -11,8 +11,8 @@ </div> <!-- main content --> - <div class="flex-grow-1 flex-shrink-1"> - <span class="d-block"> + <div class="flex-grow-1 flex-shrink-1 w-0 d-flex align-items-center"> + <span class="text-truncate"> {{ filename }} </span> <small class="text-muted d-block"> @@ -34,6 +34,29 @@ [darkmode]="darktheme$ | async" [filename]="filename" [kgId]="kgId" - [backendUrl]="backendUrl"> + [backendUrl]="backendUrl" + #dataPreviewerStencilCmp> </kg-dataset-previewer> + +<div class="flex-shrink-0 flex-grow-0 d-inline-flex"> + <a *ngIf="downloadHref" + [attr.href]="downloadHref" + [attr.download]="filename" + [attr.aria-label]="DOWNLOAD_PREVIEW_ARIA_LABEL" + target="_blank" + mat-icon-button + color="primary"> + <i class="fas fa-download"></i> + </a> + + <a *ngIf="downloadCsvHref" + [attr.href]="downloadCsvHref" + [attr.download]="filename" + [attr.aria-label]="DOWNLOAD_PREVIEW_CSV_ARIA_LABEL" + target="_blank" + mat-icon-button + color="primary"> + <i class="fas fa-file-csv"></i> + </a> +</div>