diff --git a/e2e/src/iv.e2e-spec.js b/e2e/src/iv.e2e-spec.js new file mode 100644 index 0000000000000000000000000000000000000000..34fdb7c386b2c97e8f1858bd2c7356810bf0d820 --- /dev/null +++ b/e2e/src/iv.e2e-spec.js @@ -0,0 +1,98 @@ +const url = 'http://localhost:8081/' + +const noErrorLog = require('./noErrorLog') +const { getSelectedTemplate, getSelectedParcellation } = require('./ivApi') +const { getSearchParam, wait } = require('./util') + +describe('protractor works', () => { + it('protractor works', () => { + expect(true).toEqual(true) + }) +}) + +describe('Home screen', () => { + beforeEach(() => { + browser.waitForAngularEnabled(false) + browser.get(url) + }) + + it('get title works', () => { + browser.getTitle() + .then(title => { + expect(title).toEqual('Interactive Atlas Viewer') + }) + .catch(error => { + console.error('error', error) + }) + + browser.executeScript('window.interactiveViewer') + .then(result => expect(result).toBeDefined()) + browser.executeScript('window.viewer') + .then(result => expect(result).toBeNull()) + + noErrorLog(browser) + }) +}) + +describe('Query param: ', () => { + + it('correctly defined templateSelected and selectedParcellation works', async () => { + + const searchParam = '?templateSelected=MNI+Colin+27&parcellationSelected=JuBrain+Cytoarchitectonic+Atlas' + browser.get(url + searchParam) + browser.executeScript('window.interactiveViewer').then(result => expect(result).toBeDefined()) + browser.executeScript('window.viewer').then(result => expect(result).toBeDefined()) + + await wait(browser) + + getSelectedTemplate(browser) + .then(template => { + expect(template.name).toBe('MNI Colin 27') + }) + + getSelectedParcellation(browser) + .then(parcellation => { + expect(parcellation.name).toBe('JuBrain Cytoarchitectonic Atlas') + }) + + noErrorLog(browser) + }) + + it('correctly defined templateSelected but incorrectly defined selectedParcellation work', async () => { + const searchParam = '?templateSelected=MNI+Colin+27&parcellationSelected=NoName' + browser.get(url + searchParam) + + await wait(browser) + + getSelectedTemplate(browser) + .then(template => { + expect(template.name).toBe('MNI Colin 27') + }) + .catch(fail) + + Promise.all([ + getSelectedTemplate(browser), + getSelectedParcellation(browser) + ]) + .then(([template, parcellation]) => { + expect(parcellation.name).toBe(template.parcellations[0].name) + }) + .catch(fail) + + noErrorLog(browser) + }) + + it('incorrectly defined templateSelected should clear searchparam', async () => { + const searchParam = '?templateSelected=NoName2&parcellationSelected=NoName' + browser.get(url + searchParam) + + await wait(browser) + + getSearchParam(browser) + .then(searchParam => { + const templateSelected = searchParam.get('templateSelected') + expect(templateSelected).toBeNull() + }) + .catch(fail) + }) +}) \ No newline at end of file diff --git a/e2e/src/ivApi.js b/e2e/src/ivApi.js new file mode 100644 index 0000000000000000000000000000000000000000..825bee506911fff68b5dfd80924b73ed3f83df2f --- /dev/null +++ b/e2e/src/ivApi.js @@ -0,0 +1,11 @@ +exports.getSelectedTemplate = (browser) => new Promise((resolve, reject) => { + browser.executeAsyncScript('let sub = window.interactiveViewer.metadata.selectedTemplateBSubject.subscribe(obj => arguments[arguments.length - 1](obj));sub.unsubscribe()') + .then(resolve) + .catch(reject) +}) + +exports.getSelectedParcellation = (browser) => new Promise((resolve, reject) => { + browser.executeAsyncScript('let sub = window.interactiveViewer.metadata.selectedParcellationBSubject.subscribe(obj => arguments[arguments.length - 1](obj));sub.unsubscribe()') + .then(resolve) + .catch(reject) +}) \ No newline at end of file diff --git a/e2e/src/niftilayer.e2e-spec.js b/e2e/src/niftilayer.e2e-spec.js deleted file mode 100644 index 0fab4ed54b424adc59a5b1830f564500a186ca84..0000000000000000000000000000000000000000 --- a/e2e/src/niftilayer.e2e-spec.js +++ /dev/null @@ -1,25 +0,0 @@ -const url = 'http://localhost:8080/' - -describe('protractor works', () => { - it('getting title works', () => { - browser.get(url) - browser.getTitle().then(function(title){ - expect(title).toBe('Interactive Atlas Viewer') - browser.executeScript('window.interactiveViewer').then(result => expect(result).toBeDefined()) - browser.executeScript('window.viewer').then(result => expect(result).toBeNull()) - }) - }) -}) - -describe('URL works', () => { - - it('templateSelected existing template works', () => { - - const searchParam = '?templateSelected=MNI+Colin+27' - browser.get(url + searchParam) - browser.executeScript('window.interactiveViewer').then(result => expect(result).toBeDefined()) - browser.executeScript('window.viewer').then(result => expect(result).toBeDefined()) - browser.executeAsyncScript('window.interactiveViewer.metadata.selectedTemplateBSubject.subscribe(obj => arguments[arguments.length - 1](obj))') - .then(obj => expect(obj.name).toBe('MNI Colin 27')) - }) -}) \ No newline at end of file diff --git a/e2e/src/noErrorLog.js b/e2e/src/noErrorLog.js new file mode 100644 index 0000000000000000000000000000000000000000..d7bc1ef7dc223d3f66aece5994623fbaea239823 --- /dev/null +++ b/e2e/src/noErrorLog.js @@ -0,0 +1,14 @@ +module.exports = (browser) => { + if (!browser) { + fail('browser not defined') + throw new Error('browser not defined') + } + browser.manage().logs().get('browser') + .then(browserLogs => { + const errorLogs = browserLogs + .filter(log => !(/favicon/.test(log.message))) + .filter(log => log.level.value > 900) + expect(errorLogs.length).toEqual(0) + }) + .catch(fail) +} \ No newline at end of file diff --git a/e2e/src/util.js b/e2e/src/util.js new file mode 100644 index 0000000000000000000000000000000000000000..030f2ccad62151ce4392c95704b211d57fb7af43 --- /dev/null +++ b/e2e/src/util.js @@ -0,0 +1,20 @@ +const {URLSearchParams} = require('url') + +exports.getSearchParam = (browser) => { + const script = ` + const search = window.location.search; + return search + ` + return browser.executeScript(script) + .then(search => new URLSearchParams(search)) +} + +exports.wait = (browser) => new Promise(resolve => { + + /** + * TODO figure out how to wait until the select parcellation is populated + */ + + browser.sleep(1000) + .then(resolve) +}) \ No newline at end of file diff --git a/package.json b/package.json index 1ed24602b17406726b4cf9a24183da38b1d1b097..de42925f1316dc751433170342d6ad34e95096c6 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "dev-server-aot": "PRODUCTION=true GIT_HASH=`git log --pretty=format:'%h' --invert-grep --grep=^.ignore -1` webpack-dev-server --config webpack.aot.js", "dev-server-all-interfaces": "webpack-dev-server --config webpack.dev.js --mode development --hot --host 0.0.0.0", "serve-plugins": "node src/plugin_examples/server.js", - "test": "karma start spec/karma.conf.js" + "test": "karma start spec/karma.conf.js", + "e2e": "protractor e2e/protractor.conf" }, "keywords": [], "author": "", diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index 594b28c22891efa9ebff8edd14730d4b3efb1bd8..cc1e6b0bf5c315b7b927c2caf49ec45cbbc3a2a0 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -55,7 +55,8 @@ export class AtlasViewerConstantsServices{ 'res/json/colin.json', 'res/json/MNI152.json', 'res/json/waxholmRatV2_0.json', - 'res/json/allenMouse.json' + 'res/json/allenMouse.json', + 'res/json/test.json' ] /* to be provided by KG in future */ @@ -189,6 +190,14 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}">${this.repoUrl}</a>` } + incorrectParcellationNameSearchParam(title) { + return `The selected parcellation - ${title} - is not available. The the first parcellation of the template is selected instead.` + } + + incorrectTemplateNameSearchParam(title) { + return `The selected template - ${title} - is not available.` + } + private supportEmailAddress = `x.gui@fz-juelich.de` private repoUrl = `https://github.com/HumanBrainProject/interactive-viewer` diff --git a/src/atlasViewer/atlasViewer.urlService.service.ts b/src/atlasViewer/atlasViewer.urlService.service.ts index 9087930a30aea98f5baf1fc04237c202d8241a73..5aa63426f2d52de25b01b7bcf3d5a2a226d8eec6 100644 --- a/src/atlasViewer/atlasViewer.urlService.service.ts +++ b/src/atlasViewer/atlasViewer.urlService.service.ts @@ -6,6 +6,7 @@ import { filter, map, scan, distinctUntilChanged, skipWhile, take } from "rxjs/o import { getActiveColorMapFragmentMain } from "../ui/nehubaContainer/nehubaContainer.component"; import { PluginServices } from "./atlasViewer.pluginService.service"; import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service"; +import { ToastService } from "src/services/toastService.service"; declare var window @@ -21,7 +22,8 @@ export class AtlasViewerURLService{ constructor( private store : Store<ViewerStateInterface>, private pluginService : PluginServices, - private constantService:AtlasViewerConstantsServices + private constantService:AtlasViewerConstantsServices, + private toastService: ToastService ){ this.pluginState$ = this.store.pipe( @@ -85,28 +87,65 @@ export class AtlasViewerURLService{ /* first, check if any template and parcellations are to be loaded */ const searchedTemplatename = searchparams.get('templateSelected') const searchedParcellationName = searchparams.get('parcellationSelected') + + if (!searchedTemplatename) { + const urlString = window.location.href + /** + * TODO think of better way of doing this + */ + history.replaceState(null, '', urlString.split('?')[0]) + return + } const templateToLoad = fetchedTemplates.find(template=>template.name === searchedTemplatename) - if(!templateToLoad) + if (!templateToLoad) { + this.toastService.showToast( + this.constantService.incorrectTemplateNameSearchParam(searchedTemplatename), + { + timeout: 5000 + } + ) + const urlString = window.location.href + /** + * TODO think of better way of doing this + */ + history.replaceState(null, '', urlString.split('?')[0]) return - const parcellationToLoad = templateToLoad ? - templateToLoad.parcellations.find(parcellation=>parcellation.name === searchedParcellationName) : - templateToLoad.parcellations[0] + } + + /** + * TODO if search param of either template or parcellation is incorrect, wrong things are searched + */ + const parcellationToLoad = templateToLoad.parcellations.find(parcellation=>parcellation.name === searchedParcellationName) + + if (!parcellationToLoad) { + this.toastService.showToast( + this.constantService.incorrectParcellationNameSearchParam(searchedParcellationName), + { + timeout: 5000 + } + ) + } this.store.dispatch({ type : NEWVIEWER, selectTemplate : templateToLoad, - selectParcellation : parcellationToLoad + selectParcellation : parcellationToLoad || templateToLoad.parcellations[0] }) /* selected regions */ - const labelIndexMap = getLabelIndexMap(parcellationToLoad.regions) - const selectedRegions = searchparams.get('regionsSelected') - if(selectedRegions){ - this.store.dispatch({ - type : SELECT_REGIONS, - selectRegions : selectedRegions.split('_').map(labelIndex=>labelIndexMap.get(Number(labelIndex))) - }) + if (parcellationToLoad && parcellationToLoad.regions) { + /** + * either or both parcellationToLoad and .regions maybe empty + */ + const labelIndexMap = getLabelIndexMap(parcellationToLoad.regions) + const selectedRegions = searchparams.get('regionsSelected') + if(selectedRegions){ + this.store.dispatch({ + type : SELECT_REGIONS, + selectRegions : selectedRegions.split('_').map(labelIndex=>labelIndexMap.get(Number(labelIndex))) + }) + } } /* now that the parcellation is loaded, load the navigation state */ diff --git a/src/res/cvtLyon.js b/src/res/cvtLyon.js new file mode 100644 index 0000000000000000000000000000000000000000..073d04bca2c38e6bb48d2f436f1ef00d0da386c4 --- /dev/null +++ b/src/res/cvtLyon.js @@ -0,0 +1,23 @@ +const fs = require('fs') +fs.readFile( + './ext/***REMOVED***.json', + // './ext/***REMOVED***.json', + 'utf-8', (err, data) => { + const json = JSON.parse(data) + const newFile = json.map(item => { + return { + name: item.name.replace('DESj', 'item2'), + // name: item.name.replace('BATg', 'item1'), + templateSpace: item.templateSpace, + geometry: item.geometry + } + }) + + fs.writeFile( + './raw/exportForOliver/item2.json', + // './raw/exportForOliver/item1.json', + JSON.stringify(newFile), 'utf-8', err => { + if (err) throw err + console.log('done') + }) +}) \ No newline at end of file diff --git a/src/res/ext/test.json b/src/res/ext/test.json new file mode 100644 index 0000000000000000000000000000000000000000..9d9e02858246fb04e3872496b5f22426692d2cba --- /dev/null +++ b/src/res/ext/test.json @@ -0,0 +1,14 @@ +{ + "name": "test", + "type": "template", + "species": "Human", + "useTheme": "dark", + "ngId": "greyscale", + "nehubaConfigURL": "res/json/testNehubaConfig.json", + "parcellations": [ + ], + "properties": { + "name": "test", + "description": "test description" + } +} \ No newline at end of file diff --git a/src/res/ext/testNehubaConfig.json b/src/res/ext/testNehubaConfig.json new file mode 100644 index 0000000000000000000000000000000000000000..2a42671d7a955b6300ccd372f1b496d702063cbd --- /dev/null +++ b/src/res/ext/testNehubaConfig.json @@ -0,0 +1,66 @@ +{ + "globals": { + "hideNullImageValues": true, + "useNehubaLayout": true, + "useNehubaMeshLayer": true, + "useCustomSegmentColors": true + }, + "zoomWithoutCtrl": true, + "hideNeuroglancerUI": true, + "rightClickWithCtrl": true, + "rotateAtViewCentre": true, + "zoomAtViewCentre": true, + "enableMeshLoadingControl": true, + "layout": { + "useNehubaPerspective": { + "fixedZoomPerspectiveSlices": { + "sliceViewportWidth": 300, + "sliceViewportHeight": 300, + "sliceZoom": 724698.1843689409, + "sliceViewportSizeMultiplier": 2 + }, + "centerToOrigin": true, + "mesh": { + "removeBasedOnNavigation": true, + "flipRemovedOctant": true, + "surfaceParcellation": true + }, + "removePerspectiveSlicesBackground": { + "mode": "==" + }, + "waitForMesh": true, + "drawSubstrates": { + "color": [ + 0.5, + 0.5, + 1, + 0.2 + ] + }, + "drawZoomLevels": { + "cutOff": 150000 + }, + "restrictZoomLevel": { + "minZoom": 2500000, + "maxZoom": 3500000 + } + } + }, + "dataset": { + "imageBackground": [ + 0, + 0, + 0, + 1 + ], + "initialNgState": { + "showDefaultAnnotations": false, + "layers": { + "greyscale": { + + "source": "dvid://http://dvid-dev.humanbrainproject.org:/5b666a3cc19344f29025c65e829f98f2/grayscale" + } + } + } + } +} \ No newline at end of file diff --git a/src/ui/banner/banner.component.ts b/src/ui/banner/banner.component.ts index 29088073bf540a99ae51db17acc6011576f65127..fc75046e2c26bedc769d8f8624e20543b3bf48f5 100644 --- a/src/ui/banner/banner.component.ts +++ b/src/ui/banner/banner.component.ts @@ -132,6 +132,9 @@ export class AtlasBanner implements OnDestroy, OnInit { } handleParcellationChange(parcellation) { + if (!(parcellation && parcellation.regions)) { + return + } this.selectedParcellation = parcellation this.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions) } diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 560b8c825f999d20cd209fcd00753b94b5df2bd6..f97f445181d8f8f357c72bbcaabf86b5d5310072 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -353,6 +353,9 @@ export class NehubaContainer implements OnInit, OnDestroy{ map(state => { const deepCopiedState = JSON.parse(JSON.stringify(state)) const navigation = deepCopiedState.templateSelected.nehubaConfig.dataset.initialNgState.navigation + if (!navigation) { + return deepCopiedState + } navigation.zoomFactor = calculateSliceZoomFactor(navigation.zoomFactor) deepCopiedState.templateSelected.nehubaConfig.dataset.initialNgState.navigation = navigation return deepCopiedState @@ -646,6 +649,12 @@ export class NehubaContainer implements OnInit, OnDestroy{ } private handleParcellation(parcellation:any){ + /** + * parcellaiton may be undefined + */ + if ( !(parcellation && parcellation.regions)) { + return + } this.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions) this.nehubaViewer.regionsLabelIndexMap = this.regionsLabelIndexMap diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index 3d46a0226dab5d2ea445651da46bb03c4d5ceaec..b20c2edb87a12a326546cb1258408859e0dca424 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -80,9 +80,12 @@ export class NehubaViewerUnit implements OnDestroy{ this.constantService.loadExportNehubaPromise .then(() => { - const { sliceZoom, sliceViewportWidth, sliceViewportHeight } = this.config.layout.useNehubaPerspective.fixedZoomPerspectiveSlices - const dim = Math.min(sliceZoom * sliceViewportWidth, sliceZoom * sliceViewportHeight) - this._dim = [dim, dim, dim] + const fixedZoomPerspectiveSlices = this.config && this.config.layout && this.config.layout.useNehubaPerspective && this.config.layout.useNehubaPerspective.fixedZoomPerspectiveSlices + if (fixedZoomPerspectiveSlices) { + const { sliceZoom, sliceViewportWidth, sliceViewportHeight } = fixedZoomPerspectiveSlices + const dim = Math.min(sliceZoom * sliceViewportWidth, sliceZoom * sliceViewportHeight) + this._dim = [dim, dim, dim] + } this.patchNG() this.loadNehuba() }) @@ -300,6 +303,7 @@ export class NehubaViewerUnit implements OnDestroy{ loadNehuba(){ this.nehubaViewer = window['export_nehuba'].createNehubaViewer(this.config, (err)=>{ /* print in debug mode */ + console.log(err) }) if(this.regionsLabelIndexMap){