From c50471fe417a5f79e85c89d99deee0dfd245162e Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Thu, 28 May 2020 18:14:47 +0200 Subject: [PATCH] chore: restore download of dataset preview chore: added toggle axis in user doc chore: updated big brain origin dataset bugfix: encode uri for dataset preview --- common/constants.js | 2 + docs/advanced/keyboard.md | 3 +- docs/releases/v2.2.2.md | 1 + .../browsingForDatasets.prod.e2e-spec.js | 26 +++++- src/index.html | 2 +- src/res/ext/bigbrain.json | 2 +- .../databrowser.useEffect.ts | 2 +- .../kgSingleDatasetService.service.ts | 9 --- .../previewCW.component.ts | 80 ++++++++++++++++++- .../previewCW.template.html | 29 ++++++- 10 files changed, 136 insertions(+), 20 deletions(-) diff --git a/common/constants.js b/common/constants.js index 24a68a668..1f596e7b3 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 04e5aacf0..d331cb87b 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 b848770a8..b2220aaad 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 62ae98d68..9eeb93161 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 498849604..65ddf0e75 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 7ac96b805..b49c34f64 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 74eb19f02..01f7db468 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 f5abf717d..e0f656389 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 48094f998..ce664fd3d 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 bb6a1112d..8a62ab8c9 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> -- GitLab