diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index 15ec031e726cfedd3c804778a102f68d662725c0..c81da047c363f4ab4258bf4337a65e3c14ad3213 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -341,6 +341,11 @@ markdown-dom pre code width: 20em!important; } +.w-40em +{ + width: 40em!important; +} + .mh-20em { max-height: 20em; @@ -438,6 +443,17 @@ markdown-dom pre code { width: 50vw!important; } + +.left-0 +{ + left: 0!important; +} + +.top-0 +{ + top: 0!important; +} + /* TODO fix hack */ /* ngx boostrap default z index for modal-container is 1050, which is higher than material tooltip 1000 */ /* when migration away from ngx bootstrap is complete, remove these classes */ diff --git a/src/res/ext/waxholmRatV2_0.json b/src/res/ext/waxholmRatV2_0.json index df036fb15d79cd07639803e9520a10372131b849..b9a472d3d02920f0345146f0410a87ab6d603cfe 100644 --- a/src/res/ext/waxholmRatV2_0.json +++ b/src/res/ext/waxholmRatV2_0.json @@ -3,6 +3,7 @@ "type": "template", "species": "Rat", "useTheme": "dark", + "ngId": "template", "nehubaConfigURL": "nehubaConfig/waxholmRatV2_0NehubaConfig", "parcellations": [ { diff --git a/src/res/ext/waxholmRatV2_0NehubaConfig.json b/src/res/ext/waxholmRatV2_0NehubaConfig.json index 1872f9229c075f353b1092aeef3dabfa67200d4a..b7252093b559f9b76892dc82693b75b114abdc6b 100644 --- a/src/res/ext/waxholmRatV2_0NehubaConfig.json +++ b/src/res/ext/waxholmRatV2_0NehubaConfig.json @@ -1 +1,173 @@ -{"globals":{"hideNullImageValues":true,"useNehubaLayout":true,"useNehubaMeshLayer":true,"useCustomSegmentColors":true},"rotateAtViewCentre":true,"zoomAtViewCentre":true,"zoomWithoutCtrl":true,"hideNeuroglancerUI":true,"rightClickWithCtrl":true,"enableMeshLoadingControl":true,"disableFixedPointObliqueRotation":true,"layout":{"useNehubaPerspective":{"fixedZoomPerspectiveSlices":{"sliceViewportWidth":1000,"sliceViewportHeight":1000,"sliceZoom":51263,"sliceViewportSizeMultiplier":2},"centerToOrigin":true,"mesh":{"removeBasedOnNavigation":true,"flipRemovedOctant":true},"removePerspectiveSlicesBackground":{"mode":"=="},"drawSubstrates":{"color":[0.5,0.5,1,0.2]},"drawZoomLevels":{"cutOff":15000}}},"dataset":{"imageBackground":[0,0,0,1],"initialNgState":{"showDefaultAnnotations":false,"layers":{"template":{"type":"image","source":"precomputed://https://neuroglancer.humanbrainproject.org/precomputed/WHS_SD_rat/templates/v1.01/t2star_masked","transform":[[1,0,0,-9550781],[0,1,0,-24355468],[0,0,1,-9707031],[0,0,0,1]]},"templateUnMasked":{"type":"image","visible":false,"source":"precomputed://https://neuroglancer.humanbrainproject.org/precomputed/WHS_SD_rat/templates/v1.01/t2star","transform":[[1,0,0,-9550781],[0,1,0,-24355468],[0,0,1,-9707031],[0,0,0,1]]},"whole":{"type":"segmentation","source":"precomputed://https://neuroglancer.humanbrainproject.org/precomputed/WHS_SD_rat/parcellations/v2","selectedAlpha":0.35,"transform":[[1,0,0,-9550781],[0,1,0,-24355468],[0,0,1,-9707031],[0,0,0,1]]}},"navigation":{"pose":{"position":{"voxelSize":[39062.5,39062.5,39062.5],"voxelCoordinates":[-1.5317133665084839,-146.45941162109375,20.52083969116211]}},"zoomFactor":59702.66654390316},"perspectiveOrientation":[-0.6278747916221619,-0.43671634793281555,0.4761661887168884,0.4339560568332672],"perspectiveZoom":379767.21993194974}}} \ No newline at end of file +{ + "globals": { + "hideNullImageValues": true, + "useNehubaLayout": true, + "useNehubaMeshLayer": true, + "useCustomSegmentColors": true + }, + "rotateAtViewCentre": true, + "zoomAtViewCentre": true, + "zoomWithoutCtrl": true, + "hideNeuroglancerUI": true, + "rightClickWithCtrl": true, + "enableMeshLoadingControl": true, + "disableFixedPointObliqueRotation": true, + "layout": { + "useNehubaPerspective": { + "fixedZoomPerspectiveSlices": { + "sliceViewportWidth": 1000, + "sliceViewportHeight": 1000, + "sliceZoom": 51263, + "sliceViewportSizeMultiplier": 2 + }, + "centerToOrigin": true, + "mesh": { + "removeBasedOnNavigation": true, + "flipRemovedOctant": true + }, + "removePerspectiveSlicesBackground": { + "mode": "==" + }, + "drawSubstrates": { + "color": [ + 0.5, + 0.5, + 1, + 0.2 + ] + }, + "drawZoomLevels": { + "cutOff": 15000 + } + } + }, + "dataset": { + "imageBackground": [ + 0, + 0, + 0, + 1 + ], + "initialNgState": { + "showDefaultAnnotations": false, + "layers": { + "template": { + "type": "image", + "source": "precomputed://https://neuroglancer.humanbrainproject.org/precomputed/WHS_SD_rat/templates/v1.01/t2star_masked", + "transform": [ + [ + 1, + 0, + 0, + -9550781 + ], + [ + 0, + 1, + 0, + -24355468 + ], + [ + 0, + 0, + 1, + -9707031 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + }, + "templateUnMasked": { + "type": "image", + "visible": false, + "source": "precomputed://https://neuroglancer.humanbrainproject.org/precomputed/WHS_SD_rat/templates/v1.01/t2star", + "transform": [ + [ + 1, + 0, + 0, + -9550781 + ], + [ + 0, + 1, + 0, + -24355468 + ], + [ + 0, + 0, + 1, + -9707031 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + }, + "whole": { + "type": "segmentation", + "source": "precomputed://https://neuroglancer.humanbrainproject.org/precomputed/WHS_SD_rat/parcellations/v2", + "selectedAlpha": 0.35, + "transform": [ + [ + 1, + 0, + 0, + -9550781 + ], + [ + 0, + 1, + 0, + -24355468 + ], + [ + 0, + 0, + 1, + -9707031 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + } + }, + "navigation": { + "pose": { + "position": { + "voxelSize": [ + 39062.5, + 39062.5, + 39062.5 + ], + "voxelCoordinates": [ + -1.5317133665084839, + -146.45941162109375, + 20.52083969116211 + ] + } + }, + "zoomFactor": 59702.66654390316 + }, + "perspectiveOrientation": [ + -0.6278747916221619, + -0.43671634793281555, + 0.4761661887168884, + 0.4339560568332672 + ], + "perspectiveZoom": 379767.21993194974 + } + } +} \ No newline at end of file diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.component.ts b/src/ui/databrowserModule/singleDataset/singleDataset.component.ts index a62636ede17fed686626debeea225bfa54790bdd..a64124ee2bfaac3a695afc51df203aef7f0ec4c7 100644 --- a/src/ui/databrowserModule/singleDataset/singleDataset.component.ts +++ b/src/ui/databrowserModule/singleDataset/singleDataset.component.ts @@ -2,6 +2,7 @@ import { Component, Input, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } import { KgSingleDatasetService } from "../kgSingleDatasetService.service"; import { Publication, File } from 'src/services/state/dataStore.store' import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; +import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.pipe"; @Component({ selector: 'single-dataset-view', @@ -24,6 +25,8 @@ export class SingleDatasetView implements OnInit { @Input() kgSchema?: string @Input() kgId?: string + private humanReadableFileSizePipe: HumanReadableFileSizePipe = new HumanReadableFileSizePipe() + /** * sic! */ @@ -95,6 +98,10 @@ export class SingleDatasetView implements OnInit { : null } + get tooltipText(){ + return `${this.numOfFiles} files ~ ${this.humanReadableFileSizePipe.transform(this.totalFileByteSize)}` + } + get showFooter(){ return (this.appendedKgReferences && this.appendedKgReferences.length > 0) || (this.publications && this.publications.length > 0) diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.template.html b/src/ui/databrowserModule/singleDataset/singleDataset.template.html index 5cf855846d9765f0b290745161896274129c3ec9..26170511e89401cafd0209a2e41305ce03d6410c 100644 --- a/src/ui/databrowserModule/singleDataset/singleDataset.template.html +++ b/src/ui/databrowserModule/singleDataset/singleDataset.template.html @@ -1,59 +1,58 @@ -<div class="card"> - <div class="card-body"> - - <!-- title --> - <h5 class="card-title"> +<mat-card> + + <!-- title --> + <mat-card-header> + <mat-card-title> {{ name }} - </h5> - - <!-- description --> - <p class="card-text"> - {{ description }} - </p> + </mat-card-title> + </mat-card-header> + + <!-- description --> + <mat-card-content> + <p>{{ description }}</p> + </mat-card-content> - <hr *ngIf="showFooter"> - <!-- publications --> - <div class="d-flex flex-column"> + <!-- publications --> + <mat-card-content> + <a *ngFor="let publication of publications" + [href]="publication.doi | doiParserPipe" + mat-button + target="_blank"> + {{ publication.cite }} + </a> + </mat-card-content> - <a - *ngFor="let publication of publications" - [href]="publication.doi | doiParserPipe" - target="_blank"> - {{ publication.cite }} - </a> - </div> - <!-- footer --> - <div class="d-flex justify-content-end"> + <!-- footer --> + <mat-card-actions> - <!-- explore --> - <a - *ngFor="let kgRef of appendedKgReferences" - class="m-2" + <!-- explore --> + <a *ngFor="let kgRef of appendedKgReferences" + class="m-2" + [href]="kgRef" + target="_blank"> + <button mat-raised-button - color="primary" - [href]="kgRef" - target="_blank"> - <span> - Explore - </span> + color="primary"> + Explore <i class="fas fa-external-link-alt"></i> - </a> + </button> + </a> - <!-- download --> - <a - (click)="downloadZipFromKg()" - [disabled]="downloadInProgress" - class="m-2" - *ngIf="files.length > 0" - mat-raised-button - color="primary"> - <span> - Download Zip - </span> - <span>({{ numOfFiles }} files ~ {{ totalFileByteSize / 1e6 | number: '.1-2' }} MB)</span> - <i class="fas" [ngClass]="!downloadInProgress? 'fa-download' :'fa-spinner fa-pulse'"></i> - </a> - </div> - </div> -</div> \ No newline at end of file + + <!-- download --> + <button + (click)="downloadZipFromKg()" + [disabled]="downloadInProgress" + [matTooltip]="tooltipText" + class="m-2" + *ngIf="files.length > 0" + mat-button + color="primary"> + <span> + Download as Zip + </span> + <i class="ml-1 fas" [ngClass]="!downloadInProgress? 'fa-download' :'fa-spinner fa-pulse'"></i> + </button> + </mat-card-actions> +</mat-card> \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 049cf8b121318723b2718d938ab3899280e292a7..d3dbca8365f4ab1f42dc317af481bf6c023abd91 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -464,6 +464,11 @@ export class NehubaContainer implements OnInit, OnDestroy{ */ if (!this.nehubaViewer) return + /** + * TODO smarter with event stream + */ + if (!viewPanels.every(v => !!v)) return + switch (mode) { case H_ONE_THREE:{ const element = this.removeExistingPanels() diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index dfabd435baa3dc82afb393e177b572525d3a434d..30b1d9d6c253cbb46f599001dd911e7ebdfac2b3 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -686,6 +686,10 @@ export class NehubaViewerUnit implements OnDestroy{ * TODO * ugh, ugly code. cleanify */ + /** + * TODO + * sometimes, ngId still happends to be undefined + */ newMap.forEach((segs, ngId) => { this.nehubaViewer.hideSegment(0, { name: ngId diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 78463d9efc5eac6af2a03001284b9f455397409a..0e7780ef14ab30909d8e66dca36c3d0d9302230b 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -43,7 +43,6 @@ import { StatusCardComponent } from "./nehubaContainer/statusCard/statusCard.com import { CookieAgreement } from "./cookieAgreement/cookieAgreement.component"; import { KGToS } from "./kgtos/kgtos.component"; import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module' -import { TemplateParcellationsDecorationPipe } from "src/util/pipes/templateParcellationDecoration.pipe"; import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe"; import { FourPanelLayout } from "./config/layouts/fourPanel/fourPanel.component"; import { HorizontalOneThree } from "./config/layouts/h13/h13.component"; @@ -60,6 +59,7 @@ import { BinSavedRegionsSelectionPipe, SavedRegionsSelectionBtnDisabledPipe } fr import { RegionTextSearchAutocomplete } from "./viewerStateController/regionSearch/regionSearch.component"; import { PluginBtnFabColorPipe } from "src/util/pipes/pluginBtnFabColor.pipe"; import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; +import { TemplateParcellationHasMoreInfo } from "src/util/pipes/templateParcellationHasMoreInfo.pipe"; @NgModule({ @@ -115,7 +115,6 @@ import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; SpatialLandmarksToDataBrowserItemPipe, FilterNullPipe, FilterNameBySearch, - TemplateParcellationsDecorationPipe, AppendtooltipTextPipe, MobileControlNubStylePipe, GetTemplateImageSrcPipe, @@ -127,6 +126,7 @@ import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; GetFileExtension, BinSavedRegionsSelectionPipe, SavedRegionsSelectionBtnDisabledPipe, + TemplateParcellationHasMoreInfo, /* directive */ DownloadDirective, diff --git a/src/ui/viewerStateController/viewerState.component.ts b/src/ui/viewerStateController/viewerState.component.ts index 726c1e895802beea84b8fe44fa72ca0685ae159e..c344d3315092e64dfd8f2b88b947ba02978f5f0e 100644 --- a/src/ui/viewerStateController/viewerState.component.ts +++ b/src/ui/viewerStateController/viewerState.component.ts @@ -1,13 +1,11 @@ import { Component, ViewChild, TemplateRef, OnInit } from "@angular/core"; import { Store, select } from "@ngrx/store"; -import { Observable, Subject, combineLatest, Subscription } from "rxjs"; -import { distinctUntilChanged, shareReplay, bufferTime, filter, map, withLatestFrom, delay, take, tap } from "rxjs/operators"; +import { Observable, Subscription } from "rxjs"; +import { distinctUntilChanged, shareReplay, filter } from "rxjs/operators"; import { SELECT_REGIONS, USER_CONFIG_ACTION_TYPES } from "src/services/stateStore.service"; import { DESELECT_REGIONS, CHANGE_NAVIGATION } from "src/services/state/viewerState.store"; import { ToastService } from "src/services/toastService.service"; -import { getSchemaIdFromName } from "src/util/pipes/templateParcellationDecoration.pipe"; -import { MatDialog, MatSelectChange, MatBottomSheet, MatBottomSheetRef } from "@angular/material"; -import { ExtraButton } from "src/components/radiolist/radiolist.component"; +import { MatSelectChange, MatBottomSheet, MatBottomSheetRef } from "@angular/material"; import { DialogService } from "src/services/dialogService.service"; import { RegionSelection } from "src/services/state/userConfigState.store"; @@ -25,7 +23,6 @@ const compareWith = (o, n) => !o || !n export class ViewerStateController implements OnInit{ - @ViewChild('publicationTemplate', {read:TemplateRef}) publicationTemplate: TemplateRef<any> @ViewChild('savedRegionBottomSheetTemplate', {read:TemplateRef}) savedRegionBottomSheetTemplate: TemplateRef<any> public focused: boolean = false @@ -41,8 +38,6 @@ export class ViewerStateController implements OnInit{ public savedRegionsSelections$: Observable<any[]> - public focusedDatasets$: Observable<any[]> - private userFocusedDataset$: Subject<any> = new Subject() private dismissToastHandler: () => void public compareWith = compareWith @@ -92,33 +87,6 @@ export class ViewerStateController implements OnInit{ select('parcellations') ) - this.focusedDatasets$ = this.userFocusedDataset$.pipe( - filter(v => !!v), - withLatestFrom( - combineLatest(this.templateSelected$, this.parcellationSelected$) - ), - ).pipe( - map(([userFocusedDataset, [selectedTemplate, selectedParcellation]]) => { - const { type, ...rest } = userFocusedDataset - if (type === 'template') return { ...selectedTemplate, ...rest} - if (type === 'parcellation') return { ...selectedParcellation, ...rest } - return { ...rest } - }), - bufferTime(100), - filter(arr => arr.length > 0), - /** - * merge properties field with the root level - * with the prop in properties taking priority - */ - map(arr => arr.map(item => { - const { properties } = item - return { - ...item, - ...properties - } - })), - shareReplay(1) - ) } ngOnInit(){ @@ -127,30 +95,6 @@ export class ViewerStateController implements OnInit{ filter(srs => srs.length === 0) ).subscribe(() => this.savedRegionBottomSheetRef && this.savedRegionBottomSheetRef.dismiss()) ) - this.subscriptions.push( - this.focusedDatasets$.subscribe(() => this.dismissToastHandler && this.dismissToastHandler()) - ) - this.subscriptions.push( - this.focusedDatasets$.pipe( - /** - * creates the illusion that the toast complete disappears before reappearing - */ - delay(100) - ).subscribe(() => this.dismissToastHandler = this.toastService.showToast(this.publicationTemplate, { - dismissable: true, - timeout:7000 - })) - ) - } - - handleActiveDisplayBtnClicked(event: MouseEvent, type: 'parcellation' | 'template', extraBtn: ExtraButton, inputItem:any = {}){ - const { name } = extraBtn - const { kgSchema, kgId } = getSchemaIdFromName(name) - this.userFocusedDataset$.next({ - ...inputItem, - kgSchema, - kgId - }) } handleTemplateChange(event:MatSelectChange){ diff --git a/src/ui/viewerStateController/viewerState.template.html b/src/ui/viewerStateController/viewerState.template.html index 9e1718231d4037b57fefcd2155df8bc51e12e362..9e5ea72d5a5d488b2f3350a77ee2800f75a30a96 100644 --- a/src/ui/viewerStateController/viewerState.template.html +++ b/src/ui/viewerStateController/viewerState.template.html @@ -16,15 +16,46 @@ </mat-option> </mat-select> </mat-form-field> + <ng-container *ngIf="templateSelected$ | async as templateSelected"> - <ng-container *ngIf="(templateSelected | templateParcellationsDecorationPipe)?.extraButtons as extraButtons"> - <button - *ngFor="let extraBtn of extraButtons" - (click)="handleActiveDisplayBtnClicked($event, 'template', extraBtn, templateSelected)" - mat-icon-button> - <i [class]="extraBtn.faIcon"></i> - </button> - </ng-container> + <!-- show on hover component --> + <sleight-of-hand + class="d-inline-block" + *ngIf="templateSelected | templateParcellationHasMoreInfoPipe as moreInfo"> + + <!-- shown when off --> + <div sleight-of-hand-front> + <button + mat-icon-button> + <i class="fas fa-info"></i> + </button> + </div> + + <!-- shown on hover --> + <div class="d-flex flex-row align-items-start" sleight-of-hand-back> + <button class="flex-grow-0 flex-shrink-0" mat-icon-button> + <i class="fas fa-info"></i> + </button> + + <div class="position-relative"> + <button class="position-relative invisible pe-none" mat-icon-button> + <i class="fas fa-info"></i> + </button> + + <single-dataset-view + *ngFor="let originDataset of moreInfo.originDatasets" + class="position-absolute left-0 top-0 w-40em" + [name]="moreInfo.name" + [description]="moreInfo.description" + [publications]="moreInfo.publications" + [kgSchema]="originDataset && originDataset.kgSchema" + [kgId]="originDataset && originDataset.kgId"> + </single-dataset-view> + </div> + + </div> + </sleight-of-hand> + </ng-container> <!-- parcellation selection --> @@ -45,14 +76,44 @@ </mat-form-field> <ng-container *ngIf="parcellationSelected$ | async as parcellationSelected"> - <ng-container *ngIf="(parcellationSelected | templateParcellationsDecorationPipe)?.extraButtons as extraButtons"> - <button - *ngFor="let extraBtn of extraButtons" - (click)="handleActiveDisplayBtnClicked($event, 'parcellation', extraBtn, parcellationSelected)" - mat-icon-button> - <i [class]="extraBtn.faIcon"></i> - </button> - </ng-container> + <!-- show on hover component --> + <sleight-of-hand + class="d-inline-block" + *ngIf="parcellationSelected | templateParcellationHasMoreInfoPipe as moreInfo"> + + <!-- shown when off --> + <div sleight-of-hand-front> + <button + mat-icon-button> + <i class="fas fa-info"></i> + </button> + </div> + + <!-- shown on hover --> + <div class="d-flex flex-row align-items-start" sleight-of-hand-back> + <button class="flex-grow-0 flex-shrink-0" mat-icon-button> + <i class="fas fa-info"></i> + </button> + + <div class="position-relative"> + <button class="position-relative invisible pe-none" mat-icon-button> + <i class="fas fa-info"></i> + </button> + + <single-dataset-view + *ngFor="let originDataset of moreInfo.originDatasets" + class="position-absolute left-0 top-0 w-40em" + [name]="moreInfo.name" + [description]="moreInfo.description" + [publications]="moreInfo.publications" + [kgSchema]="originDataset && originDataset.kgSchema" + [kgId]="originDataset && originDataset.kgId"> + </single-dataset-view> + </div> + + </div> + </sleight-of-hand> + </ng-container> <!-- divider --> @@ -138,18 +199,6 @@ </div> </mat-card> -<ng-template #publicationTemplate> - <single-dataset-view - *ngFor="let focusedDataset of (focusedDatasets$ | async)" - [name]="focusedDataset.name" - [description]="focusedDataset.description" - [publications]="focusedDataset.publications" - [kgSchema]="focusedDataset.kgSchema" - [kgId]="focusedDataset.kgId"> - - </single-dataset-view> -</ng-template> - <!-- bottom sheet for saved regions --> <ng-template #savedRegionBottomSheetTemplate> <mat-action-list> diff --git a/src/util/pipes/humanReadableFileSize.pipe.spec.ts b/src/util/pipes/humanReadableFileSize.pipe.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..802ce27a7586de3008a5f9031a5792df2b86b167 --- /dev/null +++ b/src/util/pipes/humanReadableFileSize.pipe.spec.ts @@ -0,0 +1,44 @@ +import { HumanReadableFileSizePipe } from './humanReadableFileSize.pipe' +import {} from 'jasmine' + + +describe('humanReadableFileSize.pipe.ts', () => { + describe('HumanReadableFileSizePipe', () => { + it('steps properly when nubmers ets large', () => { + const pipe = new HumanReadableFileSizePipe() + const num = 12 + + expect(pipe.transform(num, 0)).toBe(`12 byte(s)`) + expect(pipe.transform(num * 1e3, 0)).toBe(`12 KB`) + expect(pipe.transform(num * 1e6, 0)).toBe(`12 MB`) + expect(pipe.transform(num * 1e9, 0)).toBe(`12 GB`) + expect(pipe.transform(num * 1e12, 0)).toBe(`12 TB`) + + expect(pipe.transform(num * 1e15, 0)).toBe(`12000 TB`) + }) + + it('pads the correct zeros', () => { + const pipe = new HumanReadableFileSizePipe() + const num = 3.14159 + + expect(pipe.transform(num, 0)).toBe(`3 byte(s)`) + expect(pipe.transform(num, 1)).toBe(`3.1 byte(s)`) + expect(pipe.transform(num, 2)).toBe(`3.14 byte(s)`) + expect(pipe.transform(num, 3)).toBe(`3.142 byte(s)`) + expect(pipe.transform(num, 4)).toBe(`3.1416 byte(s)`) + expect(pipe.transform(num, 5)).toBe(`3.14159 byte(s)`) + expect(pipe.transform(num, 6)).toBe(`3.141590 byte(s)`) + expect(pipe.transform(num, 7)).toBe(`3.1415900 byte(s)`) + }) + + it('parses string as well as number', () => { + // TODO finish tests + }) + + it('throws when a non number is passed to either argument', () => { + // TODO finish tests + }) + + + }) +}) \ No newline at end of file diff --git a/src/util/pipes/humanReadableFileSize.pipe.ts b/src/util/pipes/humanReadableFileSize.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..77b3579b44ff0cfbce9b781e076fa1bef6b8587f --- /dev/null +++ b/src/util/pipes/humanReadableFileSize.pipe.ts @@ -0,0 +1,28 @@ +import { PipeTransform, Pipe } from "@angular/core"; + +export const steps = [ + 'byte(s)', + 'KB', + 'MB', + 'GB', + 'TB' +] + +@Pipe({ + name: 'humanReadableFileSizePipe' +}) + +export class HumanReadableFileSizePipe implements PipeTransform{ + public transform(input: string | Number, precision: number = 2) { + let _input = Number(input) + if (!_input) throw new Error(`HumanReadableFileSizePipe needs a string or a number that can be parsed to number`) + let _precision = Number(precision) + if (_precision === NaN) throw new Error(`precision must be a number`) + let counter = 0 + while (_input > 1000 && counter < 4) { + _input = _input / 1000 + counter += 1 + } + return `${_input.toFixed(precision)} ${steps[counter]}` + } +} \ No newline at end of file diff --git a/src/util/pipes/templateParcellationDecoration.pipe.ts b/src/util/pipes/templateParcellationDecoration.pipe.ts deleted file mode 100644 index 6e64082b3a9cebf1df7681b5b3d7491bb3f453a4..0000000000000000000000000000000000000000 --- a/src/util/pipes/templateParcellationDecoration.pipe.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -const notNullNotEmptyString = (string) => !!string && string !== '' -const notEmptyArray = (arr) => !!arr && arr instanceof Array && arr.length > 0 - -/** - * extraButtons are needed to render (i) btn in dropdown menu - * this pipe should append the extraButtons property according to: - * - originalDatasets is defined - * - description is defined on either root level or properties level - */ - -@Pipe({ - name: 'templateParcellationsDecorationPipe' -}) - -export class TemplateParcellationsDecorationPipe implements PipeTransform{ - private decorateSingle(p:any){ - - const { description, properties = {}, publications } = p - const { description:pDescriptions, publications: pPublications } = properties - const defaultOriginaldataset = notNullNotEmptyString(description) - || notNullNotEmptyString(pDescriptions) - || notEmptyArray(publications) - || notEmptyArray(pPublications) - ? [{}] - : [] - - const { originDatasets = defaultOriginaldataset } = p - return { - ...p, - extraButtons: originDatasets - .map(({ kgSchema, kgId }) => { - return { - name: getNameFromSchemaId({ kgSchema, kgId }), - faIcon: 'fas fa-info' - } - }) - } - } - private decorateArray (array:any[]) { - return array.map(this.decorateSingle) - } - public transform(item:any){ - if (!item) return item - if(item instanceof Array) return this.decorateArray(item) - else return this.decorateSingle(item) - } -} - -export const getNameFromSchemaId = ({ kgSchema=null, kgId=null } = {}) => JSON.stringify({kgSchema, kgId}) -export const getSchemaIdFromName = (string = '{}') => { - const {kgSchema=null, kgId=null} = JSON.parse(string) - return { kgSchema, kgId } -} \ No newline at end of file diff --git a/src/util/pipes/templateParcellationHasMoreInfo.pipe.ts b/src/util/pipes/templateParcellationHasMoreInfo.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..aab97596acd5dd8fa6d335e45486756134a001c8 --- /dev/null +++ b/src/util/pipes/templateParcellationHasMoreInfo.pipe.ts @@ -0,0 +1,47 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { Publication } from "src/services/stateStore.service"; + +interface KgSchemaId { + kgSchema: string + kgId: string +} + +interface MoreInfo { + name: string + description: string + publications: Publication[] + originDatasets: KgSchemaId[] + mindsId: KgSchemaId +} + +const notNullNotEmptyString = (string) => !!string && string !== '' +const notEmptyArray = (arr) => !!arr && arr instanceof Array && arr.length > 0 + +@Pipe({ + name: 'templateParcellationHasMoreInfoPipe' +}) + +export class TemplateParcellationHasMoreInfo implements PipeTransform{ + public transform(obj: any):MoreInfo{ + + const { description, properties = {}, publications, name, originDatasets, mindsId } = obj + const { description:pDescriptions, publications: pPublications, name: pName, mindsId: pMindsId } = properties + + const hasMoreInfo = notNullNotEmptyString(description) + || notNullNotEmptyString(pDescriptions) + || notEmptyArray(publications) + || notEmptyArray(pPublications) + + return hasMoreInfo + ? { + name: pName || name, + description: pDescriptions || description, + publications: pPublications || publications, + originDatasets: notEmptyArray(originDatasets) + ? originDatasets + : [{ kgSchema: null, kgId: null }], + mindsId: pMindsId || mindsId + } + : null + } +} \ No newline at end of file