diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index 065bff9f6f631476d94acf85743f37c787c21160..7d93f74715df0a22257a3638b3ad232c02c39a8a 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -636,6 +636,16 @@ mat-icon[fontset="far"] min-height: 4rem; } +.w-30vw +{ + width: 30vw!important; +} + +.h-30vw +{ + height: 30vw!important; +} + /* overwrite bootstrap default focus styling */ *:focus { diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/ui/databrowserModule/databrowser.module.ts index 93b0103ea4026b8b3ddecdb2c9681fdfaae31113..b952b012d14d2e1d8fa3709e4c4f8f037edda245 100644 --- a/src/ui/databrowserModule/databrowser.module.ts +++ b/src/ui/databrowserModule/databrowser.module.ts @@ -32,6 +32,8 @@ import { GetKgSchemaIdFromFullIdPipe } from "./util/getKgSchemaIdFromFullId.pipe import { PreviewFileIconPipe } from "./preview/previewFileIcon.pipe"; import { PreviewFileTypePipe } from "./preview/previewFileType.pipe"; import { SingleDatasetListView } from "./singleDataset/listView/singleDatasetListView.component"; +import { CanvastoBlobPipe } from "./fileviewer/util/canvasToBlob.pipe"; +import { BlobToUrlDirective } from "./fileviewer/util/blobToUrl.direcive"; @NgModule({ imports:[ @@ -56,6 +58,11 @@ import { SingleDatasetListView } from "./singleDataset/listView/singleDatasetLis SingleDatasetView, SingleDatasetListView, + /** + * directives + */ + BlobToUrlDirective, + /** * pipes */ @@ -69,7 +76,8 @@ import { SingleDatasetListView } from "./singleDataset/listView/singleDatasetLis RegionBackgroundToRgbPipe, GetKgSchemaIdFromFullIdPipe, PreviewFileIconPipe, - PreviewFileTypePipe + PreviewFileTypePipe, + CanvastoBlobPipe ], exports:[ DataBrowser, @@ -79,6 +87,7 @@ import { SingleDatasetListView } from "./singleDataset/listView/singleDatasetLis ModalityPicker, FilterDataEntriesbyMethods, FileViewer, + GetKgSchemaIdFromFullIdPipe ], entryComponents:[ DataBrowser, diff --git a/src/ui/databrowserModule/fileviewer/fileviewer.component.ts b/src/ui/databrowserModule/fileviewer/fileviewer.component.ts index 95e4b9fcce7256a4d5ded0d174da8d1e00fbb522..edaeafad745cddce950087e0fa8e052fb6dc3d45 100644 --- a/src/ui/databrowserModule/fileviewer/fileviewer.component.ts +++ b/src/ui/databrowserModule/fileviewer/fileviewer.component.ts @@ -1,8 +1,5 @@ -import { Component, Input, OnChanges, OnDestroy, ViewChild, ElementRef, OnInit, Output, EventEmitter, Inject, Optional } from '@angular/core' +import { Component, Input, ViewChild, ElementRef, Inject, Optional, OnChanges } from '@angular/core' -import { DomSanitizer } from '@angular/platform-browser'; -import { interval,from } from 'rxjs'; -import { switchMap,take,retry } from 'rxjs/operators' import { ViewerPreviewFile } from 'src/services/state/dataStore.store'; import { MAT_DIALOG_DATA } from '@angular/material'; @@ -15,7 +12,7 @@ import { MAT_DIALOG_DATA } from '@angular/material'; ] }) -export class FileViewer implements OnChanges,OnDestroy,OnInit{ +export class FileViewer implements OnChanges{ /** * fetched directly from KG */ @@ -24,79 +21,14 @@ export class FileViewer implements OnChanges,OnDestroy,OnInit{ @ViewChild('childChart') childChart : ChartComponentInterface constructor( - @Optional() @Inject(MAT_DIALOG_DATA) data, - private sanitizer:DomSanitizer + @Optional() @Inject(MAT_DIALOG_DATA) data ){ if (data) this.previewFile = data.previewFile } - private _downloadUrl : string - private _pngDownloadUrl : string - - ngOnDestroy(){ - this.revokeUrls() - } - - ngOnInit(){ - this.createUrls() - } + public downloadUrl: string ngOnChanges(){ - this.revokeUrls() - this.createUrls() - } - - get downloadUrl(){ - return this.previewFile.url - } - - /* TODO require better way to check if a chart exists */ - private createUrls(){ - - const timer$ = interval(50) - const timerSet$ = timer$.pipe( - switchMap(()=>from(new Promise((rs,rj)=>{ - if(!this.childChart) - return rj('chart not defined after 500ms') - this.childChart.canvas.nativeElement.toBlob((blob)=>{ - blob ? rs(blob) : rj('blob is undefined') - - },'image/png') - }))), - retry(10), - take(1) - ) - - timerSet$.subscribe((blob)=>{ - this._pngDownloadUrl = URL.createObjectURL(blob) - },(err)=>console.warn('warning',err)) - - - if(!this.previewFile.url && this.previewFile.data){ - const stringJson = JSON.stringify(this.previewFile.data) - const newBlob = new Blob([stringJson],{type:'application/octet-stream'}) - this._downloadUrl = URL.createObjectURL(newBlob) - } - } - - private revokeUrls(){ - if(this._downloadUrl){ - URL.revokeObjectURL(this._downloadUrl) - this._downloadUrl = null - } - if(this._pngDownloadUrl){ - URL.revokeObjectURL(this._pngDownloadUrl) - this._pngDownloadUrl = null - } - } - - get downloadName(){ - return this.previewFile.name - } - - get downloadPng(){ - return this._pngDownloadUrl ? - this.sanitizer.bypassSecurityTrustResourceUrl(this._pngDownloadUrl) : - null + this.downloadUrl = this.previewFile.url } } diff --git a/src/ui/databrowserModule/fileviewer/fileviewer.style.css b/src/ui/databrowserModule/fileviewer/fileviewer.style.css index 51438cd57dbb6705bbc929dba85668fb406c6837..e850661e5c5879af558758be2476da087685349a 100644 --- a/src/ui/databrowserModule/fileviewer/fileviewer.style.css +++ b/src/ui/databrowserModule/fileviewer/fileviewer.style.css @@ -32,8 +32,3 @@ div[mimetypeTextContainer] { margin:1em; } - -.downloadButton { - color: #4595ec; - font-size: 20px; -} diff --git a/src/ui/databrowserModule/fileviewer/fileviewer.template.html b/src/ui/databrowserModule/fileviewer/fileviewer.template.html index 8403b4c24df0ac392e4879457eeab7db175c049d..35cc94aaee688c5c42cd6ca889ac6e92fcfc7cb5 100644 --- a/src/ui/databrowserModule/fileviewer/fileviewer.template.html +++ b/src/ui/databrowserModule/fileviewer/fileviewer.template.html @@ -16,80 +16,94 @@ <!-- image --> <div *ngSwitchCase="'image/jpeg'" > <!-- TODO figure out a more elegant way of dealing with draggable img --> - <img draggable = "false" [src] = "previewFile.url" /> + <img draggable="false" [src]="previewFile.url" /> </div> <!-- data container --> - <div [ngSwitch]="previewFile.data.chartType" *ngSwitchCase="'application/json'"> + <div [ngSwitch]="previewFile.data.chartType" + *ngSwitchCase="'application/json'"> <radar-chart #childChart - [colors] = "previewFile.data.colors" - [options] = "previewFile.data.chartOptions" + [colors]="previewFile.data.colors" + [options]="previewFile.data.chartOptions" [labels]="previewFile.data.labels" [radarDatasets]="previewFile.data.datasets" *ngSwitchCase="'radar'"> </radar-chart> <line-chart - #childChart - [colors] = "previewFile.data.colors" - [options] = "previewFile.data.chartOptions" - [lineDatasets] = "previewFile.data.datasets" - *ngSwitchCase = "'line'"> + #childChart="iavLineChart" + + [colors]="previewFile.data.colors" + [options]="previewFile.data.chartOptions" + [lineDatasets]="previewFile.data.datasets" + *ngSwitchCase="'line'"> </line-chart> <div *ngSwitchDefault> The json file is not a chart. I mean, we could dump the json, but would it really help? </div> </div> - <div *ngSwitchCase = "'application/hibop'"> + + <div *ngSwitchCase="'application/hibop'"> <div mimetypeTextContainer> You will need to install the HiBoP software on your computer, click the 'Download File' button and open the .hibop file. </div> </div> - <div *ngSwitchCase = "'application/nifti'"> + + <div *ngSwitchCase="'application/nifti'"> <dedicated-viewer - [previewFile] = "previewFile"> + [previewFile]="previewFile"> </dedicated-viewer> </div> - <div *ngSwitchCase = "'application/nehuba-layer'"> + + <div *ngSwitchCase="'application/nehuba-layer'"> APPLICATION NEHUBA LAYER - NOT YET IMPLEMENTED </div> + <div *ngSwitchDefault> <div mimetypeTextContainer> The selected file with the mimetype {{ previewFile.mimetype }} could not be displayed. </div> </div> + </div> <div class="mt-1 w-100 d-flex justify-content-end pl-1 pr-1 pt-2 pb-2"> - <button - (click)="childChart.downloadChartAsPng()" - class="outline-none mr-1 ml-1 btn btn-link p-0" - container="body" - *ngIf="childChart && childChart.shapedLineChartDatasets" - matTooltip="Download line graph as csv"> - <i class="fas fa-file-csv downloadButton"></i> - </button> - - <a class="outline-none mr-1 ml-1" - *ngIf="downloadUrl" - [href]="downloadUrl" - target="_blank" - download - matTooltip="Download File" - color="#4595ec"> - <i class="far fa-arrow-alt-circle-down downloadButton"></i> + + <a mat-icon-button + matTooltip="Download line graph as csv" + [hidden]="!(childChart && childChart.csvDataUrl)" + + target="_blank" + [download]="childChart && childChart.csvTitle" + [href]="childChart && childChart.csvDataUrl"> + + <i class="fas fa-file-csv"></i> + </a> + + <a mat-icon-button + *ngIf="downloadUrl" + [href]="downloadUrl" + target="_blank" + download + matTooltip="Download File"> + <i class="fas fa-download"></i> </a> - <a class="outline-none mr-1 ml-1" - *ngIf="downloadPng" - container="body" - [href]="downloadPng" - target="_blank" - download - matTooltip="Download chart as an image" - color="#4595ec"> - <i class="far fa-arrow-alt-circle-down downloadButton"></i> + + <a mat-icon-button + [hidden]="!iavBlobToUrlDirective.url" + + [iav-blob-to-url]="(childChart && childChart.canvas && childChart.canvas.nativeElement) | canvasToBlobPipe | async" + #iavBlobToUrlDirective="iavBlobToUrl" + + target="_blank" + [href]="iavBlobToUrlDirective.url" + [download]="childChart && childChart.imageTitle" + + matTooltip="Download chart as an image"> + <i class="fas fa-download "></i> </a> + </div> diff --git a/src/ui/databrowserModule/fileviewer/line/line.chart.component.ts b/src/ui/databrowserModule/fileviewer/line/line.chart.component.ts index add3df4af823f2f11e17b8f03068e65543643564..564b19f6811ca7a0a599a598f27bbee2c22281be 100644 --- a/src/ui/databrowserModule/fileviewer/line/line.chart.component.ts +++ b/src/ui/databrowserModule/fileviewer/line/line.chart.component.ts @@ -1,7 +1,7 @@ -import {Component, Input, OnChanges, ElementRef, ViewChild, Output, EventEmitter} from '@angular/core' -import { DatasetInterface, ChartColor, ScaleOptionInterface, LegendInterface, TitleInterfacce, applyOption } from '../chart.interface' +import { Component, Input, OnChanges, ElementRef, ViewChild } from '@angular/core' +import { DatasetInterface, ChartColor, ScaleOptionInterface, LegendInterface, TitleInterfacce, applyOption } from '../chart.interface' -import { ChartOptions, LinearTickOptions,ChartDataSets, ChartPoint } from 'chart.js'; +import { ChartOptions, LinearTickOptions,ChartDataSets } from 'chart.js'; import { Color } from 'ng2-charts'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; @Component({ @@ -10,33 +10,28 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; styleUrls : [ `./line.chart.style.css` ], + exportAs: 'iavLineChart' }) export class LineChart implements OnChanges{ - @ViewChild('canvas') canvas : ElementRef - @ViewChild('DownloadLineChartLink', {read: ElementRef}) downloadLineChartLink : ElementRef - - public downloadChartAsPng() { - if (this.downloadLineChartLink && this.downloadLineChartLink.nativeElement && this.downloadLineChartLink.nativeElement.click()) - this.downloadLineChartLink.nativeElement.click() - } + @ViewChild('canvas') canvas: ElementRef /** * labels of each of the columns, spider web edges */ - @Input() labels : string[] + @Input() labels: string[] /** * shown on the legend, different lines */ - @Input() lineDatasets : LineDatasetInputInterface[] = [] + @Input() lineDatasets: LineDatasetInputInterface[] = [] /** * colors of the datasetes */ - @Input() colors : ChartColor[] = [] + @Input() colors: ChartColor[] = [] - @Input() options : any + @Input() options: any mousescroll(_ev:MouseWheelEvent){ @@ -46,69 +41,70 @@ export class LineChart implements OnChanges{ } - maxY : number + maxY: number - xAxesTicks : LinearTickOptions = { - stepSize : 20, - fontColor : 'white' + xAxesTicks: LinearTickOptions = { + stepSize: 20, + fontColor: 'white' } - chartOption : LineChartOption = { - scales : { - xAxes : [{ - type : 'linear', - gridLines : { - color : 'rgba(128,128,128,0.5)' + chartOption: Partial<LineChartOption> = { + responsive: true, + scales: { + xAxes: [{ + type: 'linear', + gridLines: { + color: 'rgba(128,128,128,0.5)' }, - ticks : this.xAxesTicks, - scaleLabel : { - display : true, - labelString : 'X Axes label', - fontColor : 'rgba(200,200,200,1.0)' + ticks: this.xAxesTicks, + scaleLabel: { + display: true, + labelString: 'X Axes label', + fontColor: 'rgba(200,200,200,1.0)' } }], - yAxes : [{ - gridLines : { - color : 'rgba(128,128,128,0.5)' + yAxes: [{ + gridLines: { + color: 'rgba(128,128,128,0.5)' }, - ticks : { - fontColor : 'white', + ticks: { + fontColor: 'white', }, - scaleLabel : { - display : true, - labelString : 'Y Axes label', - fontColor : 'rgba(200,200,200,1.0)' + scaleLabel: { + display: true, + labelString: 'Y Axes label', + fontColor: 'rgba(200,200,200,1.0)' } }], }, - legend : { - display : true + legend: { + display: true }, - title : { - display : true, - text : 'Title', - fontColor : 'rgba(255,255,255,1.0)' + title: { + display: true, + text: 'Title', + fontColor: 'rgba(255,255,255,1.0)' }, - color : [{ - backgroundColor : `rgba(255,255,255,0.2)` + color: [{ + backgroundColor: `rgba(255,255,255,0.2)` }], animation :undefined, - elements : + elements: { - point : { - radius : 0, - hoverRadius : 8, - hitRadius : 4 + point: { + radius: 0, + hoverRadius: 8, + hitRadius: 4 } } } - chartDataset : DatasetInterface = { - labels : [], - datasets : [] + chartDataset: DatasetInterface = { + labels: [], + datasets: [] } - shapedLineChartDatasets : ChartDataSets[] + shapedLineChartDatasets: ChartDataSets[] constructor(private sanitizer:DomSanitizer){ @@ -116,11 +112,11 @@ export class LineChart implements OnChanges{ ngOnChanges(){ this.shapedLineChartDatasets = this.lineDatasets.map(lineDataset=>({ - data : lineDataset.data.map((v,idx)=>({ - x : idx, - y : v + data: lineDataset.data.map((v,idx)=>({ + x: idx, + y: v })), - fill : 'origin' + fill: 'origin' })) this.maxY = this.chartDataset.datasets.reduce((max,dataset)=>{ @@ -132,6 +128,8 @@ export class LineChart implements OnChanges{ },0) applyOption(this.chartOption,this.options) + + this.generateDataUrl() } getDataPointString(input:any):string{ @@ -140,19 +138,30 @@ export class LineChart implements OnChanges{ : this.getDataPointString(input.y) } - get cvsData():string{ + public csvDataUrl: SafeUrl + public csvTitle: string + public imageTitle: string + + private generateDataUrl(){ const row0 = [this.chartOption.scales.xAxes[0].scaleLabel.labelString].concat(this.chartOption.scales.yAxes[0].scaleLabel.labelString).join(',') const maxRows = this.lineDatasets.reduce((acc, lds) => Math.max(acc, lds.data.length), 0) const rows = Array.from(Array(maxRows)).map((_,idx) => [idx.toString()].concat(this.shapedLineChartDatasets.map(v => v.data[idx] ? this.getDataPointString(v.data[idx]) : '').join(','))).join('\n') - return `${row0}\n${rows}` - } + const csvData = `${row0}\n${rows}` + + this.csvDataUrl = this.sanitizer.bypassSecurityTrustUrl(`data:text/csv;charset=utf-8,${csvData}`) + this.csvTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.csv` - get csvDataUrl():SafeUrl{ - return this.sanitizer.bypassSecurityTrustUrl(`data:text/csv;charset=utf-8,${this.cvsData}`) + this.imageTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.png` } - get csvTitle(){ - return `${this.graphTitleAsString.replace(/\s/g, '')}.csv` + private getGraphTitleAsString():string{ + try{ + return this.chartOption.title.text.constructor === Array + ? (this.chartOption.title.text as string[]).join(' ') + : this.chartOption.title.text as string + }catch(e){ + return `Untitled` + } } get graphTitleAsString():string{ @@ -168,20 +177,20 @@ export class LineChart implements OnChanges{ export interface LineDatasetInputInterface{ - label? : string - data : number[] + label?: string + data: number[] } export interface LinearChartOptionInterface{ - scales? : { - xAxes? : ScaleOptionInterface[] - yAxes? : ScaleOptionInterface[] + scales?: { + xAxes?: ScaleOptionInterface[] + yAxes?: ScaleOptionInterface[] } - legend? : LegendInterface - title? : TitleInterfacce - color? : Color[] + legend?: LegendInterface + title?: TitleInterfacce + color?: Color[] } interface LineChartOption extends ChartOptions{ - color? : Color[] + color?: Color[] } diff --git a/src/ui/databrowserModule/fileviewer/line/line.chart.template.html b/src/ui/databrowserModule/fileviewer/line/line.chart.template.html index 760ae982d3d3379e70b5b50cd9089da8d66bd7d1..fb69619ff98aebbddc81b1535ee3b0eb20b5662e 100644 --- a/src/ui/databrowserModule/fileviewer/line/line.chart.template.html +++ b/src/ui/databrowserModule/fileviewer/line/line.chart.template.html @@ -1,31 +1,14 @@ -<canvas - *ngIf="shapedLineChartDatasets" - (mousewheel)="mousescroll($event)" - width='100%' - height='100%' - baseChart - chartType="line" - [options]="chartOption" - [colors]="colors" - [datasets]="shapedLineChartDatasets" - [labels]="chartDataset.labels" - #canvas> -</canvas> -<div class="w-100 d-flex justify-content-end"> - <!-- TODO ngIF expression changed after checked --> - <a hidden - #DownloadLineChartLink - class="outline-none" - [download]="csvTitle" - [href]="csvDataUrl" - container="body" - *ngIf="shapedLineChartDatasets" - matTooltip="Download as csv"> - <i class="fas fa-file-csv"></i> - <!-- <i class="fas fa-file-csv"></i><span class="ml-2">Download line graph as csv</span>--> - </a> - <span - *ngIf="!shapedLineChartDatasets"> - datasets are required to display linear graph -</span> -</div> +<div *ngIf="shapedLineChartDatasets" + class="position-relative col-12"> + <canvas baseChart + (mousewheel)="mousescroll($event)" + height="500" + width="500" + chartType="line" + [options]="chartOption" + [colors]="colors" + [datasets]="shapedLineChartDatasets" + [labels]="chartDataset.labels" + #canvas> + </canvas> +</div> \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/radar/radar.chart.component.ts b/src/ui/databrowserModule/fileviewer/radar/radar.chart.component.ts index 591a0cc9739812b24e605bb045b47b50dc3bd2cb..2eaad28d926c74e30efbebf433508de54c8b646b 100644 --- a/src/ui/databrowserModule/fileviewer/radar/radar.chart.component.ts +++ b/src/ui/databrowserModule/fileviewer/radar/radar.chart.component.ts @@ -1,7 +1,10 @@ -import { Component, Input, OnChanges, ViewChild, ElementRef, AfterViewInit, AfterViewChecked } from '@angular/core' +import { Component, Input, OnChanges, ViewChild, ElementRef } from '@angular/core' import { DatasetInterface, ChartColor, ScaleOptionInterface, TitleInterfacce, LegendInterface, applyOption } from '../chart.interface'; import { Color } from 'ng2-charts'; +import { SafeUrl, DomSanitizer } from '@angular/platform-browser'; +import { RadialChartOptions } from 'chart.js' + @Component({ selector : `radar-chart`, templateUrl : './radar.chart.template.html', @@ -54,7 +57,8 @@ export class RadarChart implements OnChanges{ maxY : number - chartOption : RadarChartOptionInterface = { + chartOption : Partial<RadialChartOptions> = { + responsive: true, scale : { gridLines : { color : 'rgba(128,128,128,0.5)' @@ -81,10 +85,7 @@ export class RadarChart implements OnChanges{ display : true, fontColor : 'rgba(255,255,255,1.0)' }, - color : [{ - backgroundColor : `rgba(255,255,255,0.2)` - }], - animation : false + animation: null } chartDataset : DatasetInterface = { @@ -92,6 +93,9 @@ export class RadarChart implements OnChanges{ datasets : [] } + constructor(private sanitizer: DomSanitizer){ + + } ngOnChanges(){ this.chartDataset = { @@ -109,6 +113,32 @@ export class RadarChart implements OnChanges{ },0) applyOption(this.chartOption,this.options) + + this.generateDataUrl() + } + + public csvDataUrl: SafeUrl + public csvTitle: string + public imageTitle: string + + private generateDataUrl(){ + const row0 = ['Receptors', ...this.chartDataset.datasets.map(ds => ds.label || 'no label')].join(',') + const otherRows = (this.chartDataset.labels as string[]) + .map((label, index) => [ label, ...this.chartDataset.datasets.map(ds => ds.data[index]) ].join(',')).join('\n') + const csvData = `${row0}\n${otherRows}` + + this.csvDataUrl = this.sanitizer.bypassSecurityTrustUrl(`data:text/csv;charset=utf-8,${csvData}`) + this.csvTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.csv` + + this.imageTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.png` + } + + private getGraphTitleAsString():string{ + try{ + return this.chartOption.title.text as string + }catch(e){ + return `Untitled` + } } } diff --git a/src/ui/databrowserModule/fileviewer/radar/radar.chart.template.html b/src/ui/databrowserModule/fileviewer/radar/radar.chart.template.html index b45ae24429a8121d3ec8444921d6ea1bff60a38c..1782e4b0ca7bf202c97b13e502b2c46cbb038b11 100644 --- a/src/ui/databrowserModule/fileviewer/radar/radar.chart.template.html +++ b/src/ui/databrowserModule/fileviewer/radar/radar.chart.template.html @@ -1,17 +1,22 @@ -<canvas - #canvas - (mousewheel)="mousescroll($event)" - width = '100%' - height = '100%' - *ngIf = "chartDataset.datasets.length > 0 && chartDataset.labels.length > 0" - baseChart - chartType="radar" - [options]="chartOption" - [colors]="colors" - [datasets]="chartDataset.datasets" - [labels]="chartDataset.labels"> -</canvas> +<div *ngIf="chartDataset.datasets.length > 0 && chartDataset.labels.length > 0" + class="position-relative col-12"> + + <!-- baseChart directive is needed by ng2-chart --> + <canvas baseChart + (mousewheel)="mousescroll($event)" + class="h-100 w-100" + height="500" + width="500" + chartType="radar" + [options]="chartOption" + [colors]="colors" + [datasets]="chartDataset.datasets" + [labels]="chartDataset.labels" + #canvas> + </canvas> +</div> + <span - *ngIf = "chartDataset.datasets.length == 0 || chartDataset.labels.length == 0"> - datasets and labels are required to display radar +*ngIf="chartDataset.datasets.length === 0 || chartDataset.labels.length === 0"> +datasets and labels are required to display radar </span> \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/util/blobToUrl.direcive.ts b/src/ui/databrowserModule/fileviewer/util/blobToUrl.direcive.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c2f7ca20b62821ac0c44fbb5f0ff71e05e38476 --- /dev/null +++ b/src/ui/databrowserModule/fileviewer/util/blobToUrl.direcive.ts @@ -0,0 +1,40 @@ +import { Directive, Input, OnChanges, OnDestroy } from "@angular/core"; +import { SafeUrl, DomSanitizer } from "@angular/platform-browser"; + +/** + * URL.createObjectURL does not get GC'ed. + * so their initialisation and destroy need to be handled by angular life cycle + * thus, directive is needed, rather than a pipe + */ + +@Directive({ + selector: '[iav-blob-to-url]', + exportAs: 'iavBlobToUrl' +}) + +export class BlobToUrlDirective implements OnChanges, OnDestroy{ + + public url:SafeUrl + + private _url: string + + constructor(private sanitizer: DomSanitizer){ + + } + + @Input('iav-blob-to-url') + blob: Blob + + ngOnChanges(){ + this.ngOnDestroy() + if (this.blob) { + this._url = window.URL.createObjectURL(this.blob) + this.url = this.sanitizer.bypassSecurityTrustUrl(this._url) + } + } + + ngOnDestroy(){ + this.url = null + if (this._url) window.URL.revokeObjectURL(this._url) + } +} \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/util/canvasToBlob.pipe.ts b/src/ui/databrowserModule/fileviewer/util/canvasToBlob.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..f61a6350c85d7ae68763c40cd52c8aa5ed7a97b8 --- /dev/null +++ b/src/ui/databrowserModule/fileviewer/util/canvasToBlob.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from '@angular/core' + +@Pipe({ + name: 'canvasToBlobPipe' +}) + +export class CanvastoBlobPipe implements PipeTransform{ + public transform(canvas:HTMLCanvasElement): Promise<Blob>{ + if (!canvas) return Promise.resolve(null) + return new Promise(resolve => { + canvas.toBlob(blob => resolve(blob), 'image/png') + }) + } +} \ No newline at end of file diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/ui/databrowserModule/kgSingleDatasetService.service.ts index 418704c4d12a965a51e84071870a7c0a35a35fd5..b8fd02a17c0f5d374ea2451b1b7e170edf159452 100644 --- a/src/ui/databrowserModule/kgSingleDatasetService.service.ts +++ b/src/ui/databrowserModule/kgSingleDatasetService.service.ts @@ -98,7 +98,8 @@ export class KgSingleDatasetService implements OnDestroy{ this.dialog.open(FileViewer, { data: { previewFile: file - } + }, + autoFocus: false }) } diff --git a/src/ui/databrowserModule/preview/preview.component.ts b/src/ui/databrowserModule/preview/preview.component.ts index 4748a11b5216dd053cd34e9a926d350654571414..bb2e06485eee70e053130a03922f4ad82a2690f6 100644 --- a/src/ui/databrowserModule/preview/preview.component.ts +++ b/src/ui/databrowserModule/preview/preview.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; +import { Component, Input, OnInit, Output, EventEmitter, ChangeDetectorRef, ChangeDetectionStrategy } from "@angular/core"; import { DatabrowserService } from "../databrowser.service"; import { ViewerPreviewFile } from "src/services/state/dataStore.store"; @@ -13,7 +13,8 @@ const getRenderNodeFn = ({name : activeFileName = ''} = {}) => ({name = '', path templateUrl: './previewList.template.html', styleUrls: [ './preview.style.css' - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class PreviewComponent implements OnInit{ @@ -27,7 +28,8 @@ export class PreviewComponent implements OnInit{ private error: string constructor( - private dbrService:DatabrowserService + private dbrService:DatabrowserService, + private cdr: ChangeDetectorRef ){ this.renderNode = getRenderNodeFn() } @@ -43,6 +45,8 @@ export class PreviewComponent implements OnInit{ this.activeFile = ev.inputItem this.renderNode = getRenderNodeFn(this.activeFile) } + + this.cdr.markForCheck() } public renderNode: (obj:any) => string @@ -52,15 +56,17 @@ export class PreviewComponent implements OnInit{ this.dbrService.fetchPreviewData(this.datasetName) .then(json => { this.previewFiles = json as ViewerPreviewFile[] - if (this.previewFiles.length > 0) + if (this.previewFiles.length > 0) { this.activeFile = this.previewFiles[0] this.renderNode = getRenderNodeFn(this.activeFile) + } }) .catch(e => { this.error = JSON.stringify(e) }) .finally(() => { this.fetchCompleteFlag = true + this.cdr.markForCheck() }) } } diff --git a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html b/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html index 81d4bf4ccc4d9b1364d82dd5e5354be189a7dd96..929de1f2d7c5da1fd85bd6a7ad787b6132cd9a7a 100644 --- a/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html +++ b/src/ui/databrowserModule/singleDataset/listView/singleDatasetListView.template.html @@ -54,7 +54,7 @@ <!-- preview --> <button mat-menu-item - *ngIf="true" + *ngIf="preview" class="no-focus" (click)="showPreviewList(previewFilesListTemplate)"> <mat-icon fontSet="far" fontIcon="fa-eye"></mat-icon> diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index 414c0bf23b77661cb23c9a808bd609dadb36449a..32cf2f9b7c437e9e9317e7cc05d214e2eebd6f06 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -196,37 +196,21 @@ <!-- render all fav dataset as mat list --> <!-- TODO maybe use virtual scroll here? --> + <mat-list-item mat-ripple class="align-items-center" *ngFor="let ds of (favDataEntries$ | async)" role="listitem"> - <span class="flex-grow-1 flex-shrink-1"> - {{ ds.name }} - </span> - - <!-- download --> - <button - #downloadBtn="matButton" - (click)="downloadDs($event, ds, downloadBtn)" - matTooltip="Download Dataset" - matTooltipPosition="after" - color="primary" - class="flex-grow-0 flex-shrink-0" - mat-icon-button> - <i class="fas fa-download"></i> - </button> - - <!-- remove from fav --> - <button - (click)="removeFav($event, ds)" - matTooltip="Remove Pin" - matTooltipPosition="after" - color="warn" - class="flex-grow-0 flex-shrink-0" - mat-icon-button> - <i class="fas fa-trash"></i> - </button> + + <single-dataset-list-view + class="d-block pt-1 pb-1 w-100" + [kgSchema]="(ds.fullId | getKgSchemaIdFromFullIdPipe)[0]" + [kgId]="(ds.fullId | getKgSchemaIdFromFullIdPipe)[1]" + [dataset]="ds"> + + </single-dataset-list-view> + </mat-list-item> </mat-list> </ng-template>