Skip to content
Snippets Groups Projects
Commit cb05e6a8 authored by Xiao Gui's avatar Xiao Gui
Browse files

refactor: remove hardcoded json files

refactor: remove unused components
parent f77b5575
No related branches found
No related tags found
No related merge requests found
Showing
with 1 addition and 1110 deletions
...@@ -61,10 +61,6 @@ COPY --from=builder /iv/deploy . ...@@ -61,10 +61,6 @@ COPY --from=builder /iv/deploy .
# Copy built interactive viewer # Copy built interactive viewer
COPY --from=compressor /iv ./public COPY --from=compressor /iv ./public
# Copy the resources files needed to respond to queries
# is this even necessary any more?
COPY --from=compressor /iv/res/json ./res
RUN chown -R node:node /iv-app RUN chown -R node:node /iv-app
USER node USER node
......
const CACHED_DATASET_URL = process.env.CACHED_DATASET_URL
exports.handler = (ev, ctx, cb) => {
const {
path,
httpMethod,
headers,
queryStringParameters,
body,
isBase64Encoded,
} = ev
const re = /datasets\/\/?templateNameParcellationName\/(.+)\/(.*?)$/.exec(path)
if (!re) {
return cb(null, { status: 401 })
}
const [ _, templateName, parcellationName ] = re
if (CACHED_DATASET_URL) {
cb(null, {
statusCode: 302,
headers: {
'Location': CACHED_DATASET_URL
}
})
} else {
return cb(null, {
statusCode: 200,
body: '[]',
headers: {
'content-type': 'application/json'
}
})
}
}
const fs = require('fs')
const { reconfigureUrl } = require('./reconfigPrecomputedServer')
exports.handler = (ev, ctx, cb) => {
const {
path,
httpMethod,
headers,
queryStringParameters,
body,
isBase64Encoded,
} = ev
const re = /nehubaConfig\/(.+)$/.exec(path)
if (!re) {
return cb(null, {
status: 401,
body: `config name is required`
})
}
const configName = re[1]
fs.readFile(`./json/${configName}.json`, 'utf-8', (err, data) => {
if (err) {
return cb(null, {
status: 404,
body: `config ${configName} does not exist, ${err.toString()}`
})
}
return cb(null, {
status: 200,
body: reconfigureUrl(data),
headers: {
'Content-type': 'application/json'
}
})
})
}
\ No newline at end of file
exports.handler = (ev, ctx, cb) => {
cb(null, {
status: 200,
body: '[]',
headers: {
'Content-type': 'application/json'
}
})
}
const fs = require('fs')
exports.handler = (ev, ctx, cb) => {
const {
path,
httpMethod,
headers,
queryStringParameters,
body,
isBase64Encoded,
} = ev
const templates = [
// 'infant',
// 'templates/bigbrain',
'templates/colin',
'templates/MNI152',
// 'templates/waxholmRatV2_0',
// 'templates/allenMouse'
]
const resp = templates
const re = /templates\/(.+)$/.exec(path)
if (re) {
const templateName = re[1]
fs.readFile(`./json/${templateName}.json`, 'utf-8', (err, data) => {
if (err) {
return cb(null, {
status: 500,
body: err.toString()
})
}
return cb(null, {
status: 200,
body: data,
headers: {
'Content-type': 'application/json'
}
})
})
} else {
cb(null, {
status: 200,
body: JSON.stringify(resp),
headers: {
'Content-type': 'application/json'
}
})
}
}
\ No newline at end of file
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core";
import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; import { AngularMaterialModule } from "src/sharedModules";
import { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component"; import { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component";
import {HasConnectivity} from "src/atlasComponents/connectivity/hasConnectivity.directive"; import {HasConnectivity} from "src/atlasComponents/connectivity/hasConnectivity.directive";
import {KgDatasetModule} from "src/atlasComponents/regionalFeatures/bsFeatures/kgDataset"; import {KgDatasetModule} from "src/atlasComponents/regionalFeatures/bsFeatures/kgDataset";
......
import { Component, Input, OnChanges, Pipe, PipeTransform, ChangeDetectionStrategy } from "@angular/core";
import { BACKENDURL } from 'src/util/constants'
import { IDataEntry } from "src/services/stateStore.service";
import { getKgSchemaIdFromFullId } from "../util/getKgSchemaIdFromFullId.pipe";
import { ARIA_LABELS } from 'common/constants'
const ARIA_LABEL_HAS_DOWNLOAD = ARIA_LABELS.BULK_DOWNLOAD
const ARIA_LABEL_HAS_NO_DOWNLOAD = ARIA_LABELS.NO_BULK_DOWNLOAD
@Component({
selector: 'iav-datamodule-bulkdownload-cmp',
templateUrl: './bulkDownloadBtn.template.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BulkDownloadBtn implements OnChanges{
@Input()
kgSchema = 'minds/core/dataset/v1.0.0'
@Input()
kgIds: string[] = []
public postUrl: string
public stringifiedKgIds: string = `[]`
public ariaLabel = ARIA_LABEL_HAS_DOWNLOAD
constructor(
){
const _url = new URL(`${BACKENDURL.replace(/\/$/, '')}/datasets/bulkDownloadKgFiles`)
this.postUrl = _url.toString()
}
ngOnChanges(){
this.stringifiedKgIds = JSON.stringify(this.kgIds)
this.ariaLabel = this.kgIds.length === 0
? ARIA_LABEL_HAS_NO_DOWNLOAD
: ARIA_LABEL_HAS_DOWNLOAD
}
}
@Pipe({
name: 'iavDatamoduleTransformDsToIdPipe',
pure: true
})
export class TransformDatasetToIdPipe implements PipeTransform{
public transform(datasets: Partial<IDataEntry>[]): string[]{
return datasets.map(({ fullId }) => {
const re = getKgSchemaIdFromFullId(fullId)
if (re) return re[1]
else return null
})
}
}
<form [action]="postUrl"
method="POST"
target="_blank"
#bulkDlForm>
<input
hidden
[value]="stringifiedKgIds"
type="text"
name="kgIds"
id="kgIds"
readonly="readonly">
<button mat-icon-button
[attr.aria-label]="ariaLabel"
[ngClass]="{'text-muted': kgIds.length === 0}"
[matTooltip]="ariaLabel"
(click)="kgIds.length === 0 ? null : bulkDlForm.submit()">
<i class="fas fa-download"></i>
</button>
</form>
\ No newline at end of file
export { IContributor } from './util'
export { ContributorModule } from './module'
\ No newline at end of file
import { Pipe, PipeTransform } from "@angular/core";
import { IContributor } from "./util";
@Pipe({
name: 'getContributorKgLink'
})
export class GetContributorKgLink implements PipeTransform{
public transform(contributor: IContributor): string{
const id = contributor['identifier']
return `https://kg.ebrains.eu/search/instances/Contributor/${id}`
}
}
import { NgModule } from "@angular/core";
import { GetContributorKgLink } from "./kgLink.pipe";
@NgModule({
declarations: [
GetContributorKgLink
],
exports: [
GetContributorKgLink
]
})
export class ContributorModule{}
\ No newline at end of file
/**
* as defined by interactiveViewerKgQuery-v1_0
*/
export interface IContributor{
['@id']: string
['shortName']: string
['name']: string
['schema.org/shortName']: string
['identifier']: string
}
\ No newline at end of file
import { CommonModule } from "@angular/common";
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, Optional } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { ComponentsModule } from "src/components/components.module";
import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module'
import { UtilModule } from "src/util";
import { KgSingleDatasetService } from "./kgSingleDatasetService.service"
import { AggregateArrayIntoRootPipe } from "./util/aggregateArrayIntoRoot.pipe";
import { CopyPropertyPipe } from "./util/copyProperty.pipe";
import { DatasetIsFavedPipe } from "./util/datasetIsFaved.pipe";
import { FilterDataEntriesbyMethods } from "./util/filterDataEntriesByMethods.pipe";
import { FilterDataEntriesByRegion } from "./util/filterDataEntriesByRegion.pipe";
import { PathToNestedChildren } from "./util/pathToNestedChildren.pipe";
import { RegionBackgroundToRgbPipe } from "./util/regionBackgroundToRgb.pipe";
import { ScrollingModule } from "@angular/cdk/scrolling";
import { PreviewFileIconPipe } from "./preview/previewFileIcon.pipe";
import { PreviewFileTypePipe } from "./preview/previewFileType.pipe";
import { SingleDatasetListView } from "./singleDataset/listView/singleDatasetListView.component";
import { GetKgSchemaIdFromFullIdPipe, getKgSchemaIdFromFullId } from "./util/getKgSchemaIdFromFullId.pipe";
import { PreviewFileVisibleInSelectedReferenceTemplatePipe } from "./util/previewFileDisabledByReferenceSpace.pipe";
import { DatasetPreviewList, UnavailableTooltip } from "./preview/datasetPreviews/datasetPreviewsList/datasetPreviewList.component";
import { PreviewComponentWrapper } from "./preview/previewComponentWrapper/previewCW.component";
import { BulkDownloadBtn, TransformDatasetToIdPipe } from "./bulkDownload/bulkDownloadBtn.component";
import { PreviewDatasetFile, IAV_DATASET_PREVIEW_DATASET_FN, IAV_DATASET_PREVIEW_ACTIVE, TypePreviewDispalyed } from "./preview/previewDatasetFile.directive";
import {
DatasetPreview
} from 'src/services/state/dataStore.store'
import {
OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN,
} from './constants'
import { ShownPreviewsDirective } from "./preview/shownPreviews.directive";
import { LayerBrowserModule } from "../../ui/layerbrowser";
import { ContributorModule } from "./contributor";
import { DatabrowserService } from "./databrowser.service";
import { RegionalFeaturesModule } from "../regionalFeatures";
import { SingleDatasetDirective } from "./singleDataset/singleDataset.directive";
import { KgDatasetModule } from "../regionalFeatures/bsFeatures/kgDataset";
const previewEmitFactory = ( overrideFn: (file: any, dataset: any) => void) => {
if (overrideFn) return overrideFn
return () => console.error(`previewEmitFactory not overriden`)
}
/**
* TODO deprecate
*/
@NgModule({
imports: [
CommonModule,
ComponentsModule,
ScrollingModule,
FormsModule,
UtilModule,
AngularMaterialModule,
LayerBrowserModule,
ContributorModule,
RegionalFeaturesModule,
KgDatasetModule,
],
declarations: [
SingleDatasetDirective,
SingleDatasetListView,
DatasetPreviewList,
PreviewComponentWrapper,
BulkDownloadBtn,
/**
* Directives
*/
PreviewDatasetFile,
ShownPreviewsDirective,
/**
* pipes
*/
PathToNestedChildren,
CopyPropertyPipe,
FilterDataEntriesbyMethods,
FilterDataEntriesByRegion,
AggregateArrayIntoRootPipe,
DatasetIsFavedPipe,
RegionBackgroundToRgbPipe,
GetKgSchemaIdFromFullIdPipe,
PreviewFileIconPipe,
PreviewFileTypePipe,
PreviewFileVisibleInSelectedReferenceTemplatePipe,
UnavailableTooltip,
TransformDatasetToIdPipe,
PreviewFileTypePipe,
],
exports: [
KgDatasetModule,
SingleDatasetDirective,
SingleDatasetListView,
FilterDataEntriesbyMethods,
GetKgSchemaIdFromFullIdPipe,
BulkDownloadBtn,
TransformDatasetToIdPipe,
PreviewDatasetFile,
PreviewFileTypePipe,
ShownPreviewsDirective,
],
entryComponents: [
PreviewComponentWrapper
],
providers: [
KgSingleDatasetService,
DatabrowserService,
{
provide: IAV_DATASET_PREVIEW_DATASET_FN,
useFactory: previewEmitFactory,
deps: [ [new Optional(), OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN] ]
}
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
/**
* shouldn't need bootstrap, so no need for browser module
*/
})
export class DatabrowserModule {
}
export { DatasetPreview, IAV_DATASET_PREVIEW_ACTIVE, TypePreviewDispalyed }
export { getKgSchemaIdFromFullId }
describe('> databrowser.service.ts', () => {
describe('> DatabrowserService', () => {
describe('> getDatasetsByRegion', () => {
it('memoize the fn call so http requests are only sent onces')
})
})
})
\ No newline at end of file
import { HttpClient } from "@angular/common/http";
import {ComponentRef, Injectable, OnDestroy} from "@angular/core";
import { select, Store } from "@ngrx/store";
import { BehaviorSubject, forkJoin, from, fromEvent, Observable, of, Subscription } from "rxjs";
import { catchError, debounceTime, distinctUntilChanged, filter, map, shareReplay, switchMap, withLatestFrom } from "rxjs/operators";
import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
// TODO remove dependency on widget unit module
import { WidgetUnit } from "src/widget";
import { LoggingService } from "src/logging";
import { SHOW_KG_TOS } from "src/services/state/uiState.store.helper";
import { NO_METHODS } from "./util/filterDataEntriesByMethods.pipe";
import { FilterDataEntriesByRegion } from "./util/filterDataEntriesByRegion.pipe";
import { datastateActionToggleFav, datastateActionUnfavDataset, datastateActionFavDataset, datastateActionFetchedDataentries } from "src/services/state/dataState/actions";
import { getStringIdsFromRegion, getRegionHemisphere, getIdFromFullId } from 'common/util'
import { viewerStateSelectedTemplateSelector, viewerStateSelectorNavigation } from "src/services/state/viewerState/selectors";
import { BACKENDURL, getFetchOption, getHttpHeader } from "src/util/constants";
import { IKgDataEntry } from ".";
const SPATIAL_WIDTH = 600
const noMethodDisplayName = 'No methods described'
/**
* param for .toFixed method
* 6: nm
* 3: um
* 0: mm
*/
const SPATIAL_SEARCH_PRECISION = 6
/**
* in ms
*/
const SPATIAL_SEARCH_DEBOUNCE = 500
export function temporaryFilterDataentryName(name: string): string {
return /autoradiography/.test(name)
? 'autoradiography'
: NO_METHODS === name
? noMethodDisplayName
: name
}
function generateToken() {
return Date.now().toString()
}
@Injectable({
providedIn: 'root',
})
export class DatabrowserService implements OnDestroy {
public kgTos$: Observable<any>
public favedDataentries$: Observable<Partial<IKgDataEntry>[]>
public darktheme: boolean = false
public instantiatedWidgetUnits: WidgetUnit[] = []
public getDataByRegion: (arg: {regions: any[] }) => Observable<IKgDataEntry[]> = ({ regions }) =>
forkJoin(regions.map(this.getDatasetsByRegion.bind(this))).pipe(
map(
(arrOfArr: IKgDataEntry[][]) => arrOfArr.reduce(
(acc, curr) => {
/**
* In the event of multi region selection
* It is entirely possibly that different regions can fetch the same dataset
* If that's the case, filter by fullId attribute
*/
const existSet = new Set(acc.map(v => v['fullId']))
const filteredCurr = curr.filter(v => !existSet.has(v['fullId']))
return acc.concat(filteredCurr)
},
[]
)
)
)
private filterDEByRegion: FilterDataEntriesByRegion = new FilterDataEntriesByRegion()
private dataentries: IKgDataEntry[] = []
private subscriptions: Subscription[] = []
public manualFetchDataset$: BehaviorSubject<null> = new BehaviorSubject(null)
private spatialDatasets$: Observable<any>
public viewportBoundingBox$: Observable<[Point, Point]>
private templateSelected: any
constructor(
private workerService: AtlasWorkerService,
private store: Store<any>,
private http: HttpClient,
private log: LoggingService,
) {
this.kgTos$ = this.http.get(`${BACKENDURL}datasets/tos`, {
responseType: 'text',
}).pipe(
catchError((err, _obs) => {
this.log.warn(`fetching kgTos error`, err)
return of(null)
}),
shareReplay(1),
)
this.favedDataentries$ = this.store.pipe(
select('dataStore'),
select('favDataEntries'),
shareReplay(1),
)
this.subscriptions.push(
store.pipe(
select('fetchedDataEntries'),
).subscribe(de => {
this.dataentries = de
}),
)
this.subscriptions.push(
this.store.pipe(
select(viewerStateSelectedTemplateSelector)
).subscribe(tmpl => {
this.templateSelected = tmpl
})
)
this.viewportBoundingBox$ = this.store.pipe(
select(viewerStateSelectorNavigation),
distinctUntilChanged(),
debounceTime(SPATIAL_SEARCH_DEBOUNCE),
filter(v => !!v && !!v.position && !!v.zoom),
map(({ position, zoom }) => {
// in mm
const center = position.map(n => n / 1e6)
const searchWidth = SPATIAL_WIDTH / 4 * zoom / 1e6
const pt1 = center.map(v => (v - searchWidth)) as [number, number, number]
const pt2 = center.map(v => (v + searchWidth)) as [number, number, number]
return [pt1, pt2] as [Point, Point]
}),
)
this.spatialDatasets$ = this.viewportBoundingBox$.pipe(
withLatestFrom(this.store.pipe(
select(viewerStateSelectedTemplateSelector),
distinctUntilChanged(),
filter(v => !!v),
)),
switchMap(([ bbox, templateSelected ]) => {
const _bbox = bbox.map(pt => pt.map(v => v.toFixed(SPATIAL_SEARCH_PRECISION)))
/**
* templateSelected and templateSelected.name must be defined for spatial search
*/
if (!templateSelected || !templateSelected.name) { return from(Promise.reject('templateSelected must not be empty')) }
const encodedTemplateName = encodeURIComponent(templateSelected.name)
// spatial dataset has halted for the time being
return of(null)
// return this.http.get(`${BACKENDURL}datasets/spatialSearch/templateName/${encodedTemplateName}/bbox/${_bbox[0].join('_')}__${_bbox[1].join("_")}`).pipe(
// catchError((err) => (this.log.log(err), of([]))),
// )
}),
)
}
public ngOnDestroy() {
this.subscriptions.forEach(s => s.unsubscribe())
}
public toggleFav(dataentry: Partial<IKgDataEntry>) {
this.store.dispatch(
datastateActionToggleFav({
payload: {
fullId: dataentry.fullId || null
}
})
)
}
public saveToFav(dataentry: Partial<IKgDataEntry>) {
this.store.dispatch(
datastateActionFavDataset({
payload: {
fullId: dataentry?.fullId || null
}
})
)
}
public removeFromFav(dataentry: Partial<IKgDataEntry>) {
this.store.dispatch(
datastateActionUnfavDataset({
payload: {
fullId: dataentry.fullId || null
}
})
)
}
// TODO deprecate
public fetchPreviewData(datasetName: string) {
const encodedDatasetName = encodeURIComponent(datasetName)
return new Promise((resolve, reject) => {
fetch(`${BACKENDURL}datasets/preview/${encodedDatasetName}`, getFetchOption())
.then(res => res.json())
.then(resolve)
.catch(reject)
})
}
private dispatchData(arr: IKgDataEntry[]) {
this.store.dispatch(
datastateActionFetchedDataentries({
fetchedDataEntries: arr
})
)
}
public fetchedFlag: boolean = false
public fetchError: string
public fetchingFlag: boolean = false
private mostRecentFetchToken: any
private memoizedDatasetByRegion = new Map<string, Observable<IKgDataEntry[]>>()
private getDatasetsByRegion(region: { fullId: any }){
const hemisphereObj = (() => {
const hemisphere = getRegionHemisphere(region)
return hemisphere ? { hemisphere } : {}
})()
const refSpaceObj = this.templateSelected && this.templateSelected.fullId
? { referenceSpaceId: getIdFromFullId(this.templateSelected.fullId) }
: {}
const fullIds = getStringIdsFromRegion(region) as string[]
for (const fullId of fullIds) {
if (!this.memoizedDatasetByRegion.has(fullId)) {
const obs$ = this.http.get<IKgDataEntry[]>(
`${BACKENDURL}datasets/byRegion/${encodeURIComponent(fullId)}`,
{
params: {
...hemisphereObj,
...refSpaceObj
},
headers: getHttpHeader(),
responseType: 'json'
}
).pipe(
shareReplay(1),
)
this.memoizedDatasetByRegion.set(fullId, obs$)
}
}
return forkJoin(
fullIds.map(fullId => this.memoizedDatasetByRegion.get(fullId))
).pipe(
map(array => array.reduce((acc, currArr) => {
return acc.concat(
currArr.filter(ds => !new Set(acc.map(ds => ds['fullId'])).has(ds['fullId']))
)
}, []))
)
}
private lowLevelQuery(templateName: string, parcellationName: string): Promise<IKgDataEntry[]> {
const encodedTemplateName = encodeURIComponent(templateName)
const encodedParcellationName = encodeURIComponent(parcellationName)
return this.http.get(
`${BACKENDURL}datasets//templateNameParcellationName/${encodedTemplateName}/${encodedParcellationName}`,
{
headers: getHttpHeader(),
responseType: 'json'
}
).pipe(
map((arr: any[]) => {
const map = arr.reduce((acc, item) => {
const newMap = new Map(acc)
return newMap.set(item.name, item)
}, new Map())
return Array.from(map.values() as IKgDataEntry[])
})
).toPromise()
}
private fetchData(templateName: string, parcellationName: string) {
this.dispatchData([])
const requestToken = generateToken()
this.mostRecentFetchToken = requestToken
this.fetchingFlag = true
this.lowLevelQuery(templateName, parcellationName)
.then(array => {
if (this.mostRecentFetchToken === requestToken) {
this.dispatchData(array)
this.mostRecentFetchToken = null
this.fetchedFlag = true
this.fetchingFlag = false
this.fetchError = null
}
})
.catch(e => {
if (this.mostRecentFetchToken === requestToken) {
this.fetchingFlag = false
this.mostRecentFetchToken = null
this.fetchError = 'Fetching dataset error.'
this.log.warn('Error fetching dataset', e)
/**
* TODO
* retry?
*/
}
})
}
public getModalityFromDE = getModalityFromDE
}
export function reduceDataentry(accumulator: Array<{name: string, occurance: number}>, dataentry: IKgDataEntry) {
const methods = (dataentry.methods || [])
.reduce((acc, item) => acc.concat(
item.length > 0
? item
: NO_METHODS ), [])
.map(temporaryFilterDataentryName)
const newDE = Array.from(new Set(methods))
.filter(m => !accumulator.some(a => a.name === m))
return newDE.map(name => {
return {
name,
occurance: 1,
}
}).concat(accumulator.map(({name, occurance, ...rest}) => {
return {
...rest,
name,
occurance: methods.some(m => m === name)
? occurance + 1
: occurance,
}
}))
}
export function getModalityFromDE(dataentries: IKgDataEntry[]): CountedDataModality[] {
return dataentries.reduce((acc, de) => reduceDataentry(acc, de), [])
}
export function getIdFromDataEntry(dataentry: IKgDataEntry) {
const { id, fullId } = dataentry
const regex = /\/([a-zA-Z0-9-]*?)$/.exec(fullId)
return (regex && regex[1]) || id
}
export interface CountedDataModality {
name: string
occurance: number
visible: boolean
}
type Point = [number, number, number]
const defaultState = {
fetchedDataEntries: [],
favDataEntries: [],
fetchedSpatialData: [],
}
import { DataBrowserUseEffect } from './databrowser.useEffect'
import { TestBed } from '@angular/core/testing'
import { Observable } from 'rxjs'
import { provideMockActions } from '@ngrx/effects/testing'
import { provideMockStore } from '@ngrx/store/testing'
import { hot } from 'jasmine-marbles'
let actions$: Observable<any>
describe('> databrowser.useEffect.ts', () => {
describe('> DataBrowserUseEffect', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideMockStore({
initialState: {
dataStore: defaultState
}
}),
provideMockActions(() => actions$),
DataBrowserUseEffect,
]
})
})
it('> should instantiate properly', () => {
const useEffect = TestBed.inject(DataBrowserUseEffect)
expect(useEffect.favDataEntries$).toBeObservable(
hot('a', {
a: []
})
)
})
})
})
\ No newline at end of file
import { Injectable } from "@angular/core";
import { Actions, Effect, ofType } from "@ngrx/effects";
import { select, Store } from "@ngrx/store";
import { Observable, Subscription } from "rxjs";
import { filter, map, withLatestFrom } from "rxjs/operators";
import { LOCAL_STORAGE_CONST } from "src/util/constants";
import { getKgSchemaIdFromFullId } from "./util/getKgSchemaIdFromFullId.pipe";
import { datastateActionToggleFav, datastateActionUpdateFavDataset, datastateActionUnfavDataset, datastateActionFavDataset } from "src/services/state/dataState/actions";
@Injectable({
providedIn: 'root',
})
export class DataBrowserUseEffect {
private subscriptions: Subscription[] = []
constructor(
private store$: Store<any>,
private actions$: Actions<any>,
) {
this.favDataEntries$ = this.store$.pipe(
select('dataStore'),
select('favDataEntries'),
)
this.toggleDataset$ = this.actions$.pipe(
ofType(datastateActionToggleFav.type),
withLatestFrom(this.favDataEntries$),
map(([action, prevFavDataEntries]) => {
const { payload = {} } = action as any
const { fullId } = payload
const re1 = getKgSchemaIdFromFullId(fullId)
if (!re1) {
return datastateActionUpdateFavDataset({
favDataEntries: prevFavDataEntries
})
}
const favIdx = prevFavDataEntries.findIndex(ds => {
const re2 = getKgSchemaIdFromFullId(ds.fullId)
if (!re2) return false
return re2[1] === re1[1]
})
return datastateActionUpdateFavDataset({
favDataEntries: favIdx >= 0
? prevFavDataEntries.filter((_, idx) => idx !== favIdx)
: prevFavDataEntries.concat(payload),
})
}),
)
this.unfavDataset$ = this.actions$.pipe(
ofType(datastateActionUnfavDataset.type),
withLatestFrom(this.favDataEntries$),
map(([action, prevFavDataEntries]) => {
const { payload = {} } = action as any
const { fullId } = payload
const re1 = getKgSchemaIdFromFullId(fullId)
return datastateActionUpdateFavDataset({
favDataEntries: prevFavDataEntries.filter(ds => {
const re2 = getKgSchemaIdFromFullId(ds.fullId)
if (!re2) return false
if (!re1) return true
return re2[1] !== re1[1]
})
})
}),
)
this.favDataset$ = this.actions$.pipe(
ofType(datastateActionFavDataset.type),
withLatestFrom(this.favDataEntries$),
map(([ action, prevFavDataEntries ]) => {
const { payload } = action as any
/**
* check duplicate
*/
const { fullId } = payload
const re1 = getKgSchemaIdFromFullId(fullId)
if (!re1) {
return datastateActionUpdateFavDataset({
favDataEntries: prevFavDataEntries
})
}
const isDuplicate = prevFavDataEntries.some(favDe => {
const re2 = getKgSchemaIdFromFullId(favDe.fullId)
if (!re2) return false
return re1[1] === re2[1]
})
const favDataEntries = isDuplicate
? prevFavDataEntries
: prevFavDataEntries.concat(payload)
return datastateActionUpdateFavDataset({
favDataEntries
})
}),
)
this.subscriptions.push(
this.favDataEntries$.pipe(
filter(v => !!v),
).subscribe(favDataEntries => {
/**
* only store the minimal data in localstorage/db, hydrate when needed
* for now, only save id
*
* do not save anything else on localstorage. This could potentially be leaking sensitive information
*/
const serialisedFavDataentries = favDataEntries.map(({ fullId }) => {
return { fullId }
})
window.localStorage.setItem(LOCAL_STORAGE_CONST.FAV_DATASET, JSON.stringify(serialisedFavDataentries))
}),
)
}
public ngOnDestroy() {
while (this.subscriptions.length > 0) {
this.subscriptions.pop().unsubscribe()
}
}
private savedFav$: Observable<Array<{id: string, name: string}> | null>
public favDataEntries$: Observable<Partial<any>[]>
@Effect()
public favDataset$: Observable<any>
@Effect()
public unfavDataset$: Observable<any>
@Effect()
public toggleDataset$: Observable<any>
}
export {
DatabrowserModule,
IAV_DATASET_PREVIEW_ACTIVE,
TypePreviewDispalyed,
} from './databrowser.module'
export { DataBrowserFeatureStore, DATESTORE_FEATURE_KEY } from './store.module'
export { DatabrowserService } from './databrowser.service'
export {
DATASTORE_DEFAULT_STATE,
OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN,
EnumPreviewFileTypes,
determinePreviewFileType,
IKgActivity,
IKgDataEntry,
IKgParcellationRegion,
IKgPublication,
IKgReferenceSpace,
DatasetPreview,
PreviewComponentWrapper,
getKgSchemaIdFromFullId,
PreviewDatasetFile,
GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME,
} from './pure'
import { HttpClient } from "@angular/common/http";
import { Injectable, OnDestroy, TemplateRef } from "@angular/core";
import { select, Store } from "@ngrx/store";
import { Observable, Subscription } from "rxjs";
import { filter, shareReplay } from "rxjs/operators";
import { BACKENDURL } from "src/util/constants";
import { uiStateShowBottomSheet } from "src/services/state/uiState.store.helper";
import { ngViewerActionRemoveNgLayer } from "src/services/state/ngViewerState/actions";
@Injectable({ providedIn: 'root' })
export class KgSingleDatasetService implements OnDestroy {
private subscriptions: Subscription[] = []
public ngLayers: Set<string> = new Set()
constructor(
private store$: Store<any>,
private http: HttpClient,
) {
this.subscriptions.push(
this.store$.pipe(
select('ngViewerState'),
filter(v => !!v),
).subscribe(layersInterface => {
this.ngLayers = new Set(layersInterface.layers.map(l => l.source.replace(/^nifti:\/\//, '')))
}),
)
}
public ngOnDestroy() {
while (this.subscriptions.length > 0) {
this.subscriptions.pop().unsubscribe()
}
}
private memoizedDatasetFromKg: Map<string, Observable<any>> = new Map()
public getInfoFromKg({ kgId, kgSchema = 'minds/core/dataset/v1.0.0' }: Partial<KgQueryInterface>) {
const key = `${kgSchema}/${kgId}`
if (this.memoizedDatasetFromKg.has(key)) return this.memoizedDatasetFromKg.get(key)
const _url = new URL(`${BACKENDURL.replace(/\/$/, '')}/datasets/kgInfo`)
const searchParam = _url.searchParams
searchParam.set('kgSchema', kgSchema)
searchParam.set('kgId', kgId)
const query$ = this.http.get<any>(_url.toString(), { responseType: 'json' }).pipe(
shareReplay(1)
)
this.memoizedDatasetFromKg.set(key, query$)
return query$
}
public getDownloadZipFromKgHref({ kgSchema = 'minds/core/dataset/v1.0.0', kgId }) {
const _url = new URL(`${BACKENDURL.replace(/\/$/, '')}/datasets/downloadKgFiles`)
const searchParam = _url.searchParams
searchParam.set('kgSchema', kgSchema)
searchParam.set('kgId', kgId)
return _url.toString()
}
public showPreviewList(template: TemplateRef<any>) {
this.store$.dispatch(
uiStateShowBottomSheet({
bottomSheetTemplate: template,
config: {
ariaLabel: `List of preview files`
}
})
)
}
public removeNgLayer({ url }) {
this.store$.dispatch(
ngViewerActionRemoveNgLayer({
layer: { name: url }
})
)
}
}
interface KgQueryInterface {
kgSchema: string
kgId: string
}
import { Component, Input, ChangeDetectorRef, Output, EventEmitter, Pipe, PipeTransform } from "@angular/core";
import { ViewerPreviewFile } from "src/services/state/dataStore.store";
import { Store, select } from "@ngrx/store";
import { IavRootStoreInterface } from "src/services/stateStore.service";
import { Observable } from "rxjs";
import { DS_PREVIEW_URL } from 'src/util/constants'
@Component({
selector: 'dataset-preview-list',
templateUrl: './datasetPreviewList.template.html'
})
export class DatasetPreviewList{
public datasetPreviewList: any[] = []
public loadingDatasetPreviewList: boolean = false
public selectedTemplateSpace$: Observable<any>
constructor(
private cdr: ChangeDetectorRef,
store$: Store<IavRootStoreInterface>
){
this.selectedTemplateSpace$ = store$.pipe(
select('viewerState'),
select('templateSelected')
)
}
@Input()
kgId: string
public DS_PREVIEW_URL = DS_PREVIEW_URL
handleKgDsPrvUpdated(event: CustomEvent){
const { detail } = event
const { datasetFiles, loadingFlag } = detail
this.loadingDatasetPreviewList = loadingFlag
this.datasetPreviewList = datasetFiles
this.cdr.markForCheck()
}
}
@Pipe({
name: 'unavailableTooltip'
})
export class UnavailableTooltip implements PipeTransform{
public transform(file: ViewerPreviewFile): string{
if (file.referenceSpaces.length === 0) return `This preview is not available to be viewed in any reference space.`
else return `This preview is available in the following reference space: ${file.referenceSpaces.map(({ name }) => name).join(', ')}`
}
}
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment