diff --git a/Dockerfile b/Dockerfile
index f13fff01bd1405023b5479bf74c9d55dde5774f6..084dc81ae5daf8572492d3a27fac22d0125c1649 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,15 +3,9 @@ FROM node:12 as builder
ARG BACKEND_URL
ENV BACKEND_URL=${BACKEND_URL}
-ARG USE_LOGO
-ENV USE_LOGO=${USE_LOGO:-hbp}
-
ARG DATASET_PREVIEW_URL
ENV DATASET_PREVIEW_URL=${DATASET_PREVIEW_URL:-https://hbp-kg-dataset-previewer.apps.hbp.eu/datasetPreview}
-ARG USE_LOGO
-ENV USE_LOGO=${USE_LOGO:-hbp}
-
ARG STRICT_LOCAL
ENV STRICT_LOCAL=${STRICT_LOCAL:-false}
diff --git a/README.md b/README.md
index a6c762757b71bd98e94d102e2a3a255ea9339340..a241ff830b3e97ea93e3d04c419a11b9a453ce4c 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,6 @@ As interactive atlas viewer uses [webpack define plugin](https://webpack.js.org/
| `DATASET_PREVIEW_URL` | dataset preview url used by component <https://github.com/fzj-inm1-bda/kg-dataset-previewer>. Useful for diagnosing issues with dataset previews.| https://hbp-kg-dataset-previewer.apps.hbp.eu/datasetPreview | http://localhost:1234/datasetPreview |
| `MATOMO_URL` | base url for matomo analytics | `null` | https://example.com/matomo/ |
| `MATOMO_ID` | application id for matomo analytics | `null` | 6 |
-| `USE_LOGO` | possible values are `hbp`, `ebrains`, `fzj` | `hbp` | `ebrains` |
| `STRICT_LOCAL` | hides **Explore** and **Download** buttons. Useful for offline demo's | `false` | `true` |
| `KIOSK_MODE` | after 5 minutes of inactivity, shows overlay inviting users to interact | `false` | `true` |
| `BUILD_TEXT` | overlay text at bottom right of the viewer. set to `''` to hide. | |
@@ -52,7 +51,7 @@ It is recommended to manage your environments with `.env` file.
| `LOCAL_CDN` | rewrite cdns to local server. useful for offlnie demo | | `http://localhost:7080/` |
| `PLUGIN_URLS` | semi colon separated urls to be returned when user queries plugins | `''`
| `STAGING_PLUGIN_URLS` | semi colon separated urls to be returned when user queries plugins | `''`
-
+| `USE_LOGO` | possible values are `hbp`, `ebrains`, `fzj` | `hbp` | `ebrains` |
##### ebrains user authentication
diff --git a/deploy/app.js b/deploy/app.js
index 917bd1fdfb43e74cc68bc0348206f50ea672f6a8..643cb485fe28a8528cc17543ad67628005f4d89b 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -151,6 +151,9 @@ app.get('/', cookieParser(), (req, res) => {
}
})
+
+app.use('/logo', require('./logo'))
+
/**
* User route, for user profile/management
*/
diff --git a/deploy/atlas/index.js b/deploy/atlas/index.js
index adbb330a3505cf4aee93dc8b6abc3edbc5a43884..35f4c488f81d730399395718cb2178f0ed84bb6d 100644
--- a/deploy/atlas/index.js
+++ b/deploy/atlas/index.js
@@ -1,19 +1,25 @@
const router = require('express').Router()
const url = require('url')
+const fs = require('fs')
+const path = require('path')
const { detEncoding } = require('nomiseco')
const { getAllAtlases, getAtlasById, isReady } = require('./query')
+const HOSTNAME = process.env.HOSTNAME || 'http://localhost:3000'
const { getTemplate } = require('../templates/query')
const { getHandleErrorFn } = require('../util/streamHandleError')
+const getPreviewFn = ({ res, lastpart }) => HOSTNAME.replace(/\/$/, '') + '/' +
+ (res.locals.routePathname
+ ? url.resolve(`${res.locals.routePathname}/`, lastpart)
+ : lastpart)
+
router.get('/', async (req, res) => {
const allAtlases = await getAllAtlases()
const resolvedAtlases = allAtlases.map(v => {
return {
'@id': v,
- url: res.locals.routePathname
- ? url.resolve(`${res.locals.routePathname}/`, encodeURIComponent(v))
- : encodeURIComponent(v)
+ url: getPreviewFn({ res, lastpart: encodeURIComponent(v) })
}
})
res.status(200).json(resolvedAtlases)
@@ -27,8 +33,46 @@ router.get('/ready', (req, res) => {
}
})
+const previewImageFIleNameMap = new Map([
+ ['minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588', 'bugbrain.png'],
+ ['minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2', 'icbm2009c.png'],
+ ['minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992', 'colin27.png'],
+ ['minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579', 'cytoarchitectonic-maps.png'],
+ ['juelich/iav/atlas/v1.0.0/3', 'cortical-layers.png'],
+ ['juelich/iav/atlas/v1.0.0/4', 'grey-white-matter.png'],
+ ['juelich/iav/atlas/v1.0.0/5', 'firbe-long.png'],
+ ['juelich/iav/atlas/v1.0.0/6', 'firbe-short.png'],
+ ['minds/core/parcellationatlas/v1.0.0/d80fbab2-ce7f-4901-a3a2-3c8ef8a3b721', 'difumo-64.png'],
+ ['minds/core/parcellationatlas/v1.0.0/73f41e04-b7ee-4301-a828-4b298ad05ab8', 'difumo-128.png'],
+ ['minds/core/parcellationatlas/v1.0.0/141d510f-0342-4f94-ace7-c97d5f160235', 'difumo-256.png'],
+ ['minds/core/parcellationatlas/v1.0.0/63b5794f-79a4-4464-8dc1-b32e170f3d16', 'difumo-512.png'],
+ ['minds/core/parcellationatlas/v1.0.0/12fca5c5-b02c-46ce-ab9f-f12babf4c7e1', 'difumo-1024.png'],
+ ['minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9', 'allen-mouse.png'],
+ ['minds/core/parcellationatlas/v1.0.0/05655b58-3b6f-49db-b285-64b5a0276f83', 'allen-mouse-2017.png'],
+ ['minds/core/parcellationatlas/v1.0.0/39a1384b-8413-4d27-af8d-22432225401f', 'allen-mouse-2015.png'],
+ ['minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8', 'waxholm.png'],
+ ['minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe', 'waxholm-v3.png'],
+ ['minds/core/parcellationatlas/v1.0.0/2449a7f0-6dd0-4b5a-8f1e-aec0db03679d', 'waxholm-v2.png'],
+ ['minds/core/parcellationatlas/v1.0.0/11017b35-7056-4593-baad-3934d211daba', 'waxholm-v1.png'],
+])
+
router.get('/preview', (req, res) => {
- res.status(501).end()
+ const { id } = req.query
+ const filename = previewImageFIleNameMap.get(id)
+ if (!filename) return res.status(404).end()
+
+ let filepath
+ if (process.env.NODE_ENV === 'production') {
+ filepath = path.join(__dirname, '../res/image', filename)
+ } else {
+ filepath = path.join(__dirname, '../../src/res/images/atlas-selection', filename)
+ }
+ try {
+ res.setHeader('Content-Type', 'image/png')
+ fs.createReadStream(filepath).pipe(res).on('error', getHandleErrorFn(req, res))
+ } catch (e) {
+ res.status(500).send(e)
+ }
})
router.get('/:atlasId', async (req, res) => {
@@ -41,17 +85,13 @@ router.get('/:atlasId', async (req, res) => {
return res.status(200).json({
...rest,
- previewUrl: res.locals.routePathname
- ? url.resolve(`${res.locals.routePathname}/`, lastPathPreview)
- : lastPathPreview,
+ previewUrl: getPreviewFn({ res, lastpart: lastPathPreview }),
parcellations: parcellations.map(p => {
const spSearchParam = new url.URLSearchParams()
spSearchParam.set('id', p['@id'])
const parcellationPreview = `preview?${spSearchParam.toString()}`
return {
- previewUrl: res.locals.routePathname
- ? url.resolve(`${res.locals.routePathname}/`, parcellationPreview)
- : parcellationPreview,
+ previewUrl: getPreviewFn({ res, lastpart: parcellationPreview }),
...p
}
}),
@@ -63,15 +103,10 @@ router.get('/:atlasId', async (req, res) => {
const lastPathPreview = `preview?${searchParam.toString()}`
return {
...tmpl,
- previewUrl: res.locals.routePathname
- ? url.resolve(`${res.locals.routePathname}/`, lastPathPreview)
- : lastPathPreview,
- url: res.locals.routePathname
- ? url.resolve(`${res.locals.routePathname}/`, lastTemplatePath)
- : lastTemplatePath
+ previewUrl: getPreviewFn({ res, lastpart: lastPathPreview }),
+ url: getPreviewFn({ res, lastpart: lastTemplatePath })
}
})
-
})
})
diff --git a/src/res/images/HBP_Primary_RGB_BlackText.png b/deploy/logo/assets/HBP_Primary_RGB_BlackText.png
similarity index 100%
rename from src/res/images/HBP_Primary_RGB_BlackText.png
rename to deploy/logo/assets/HBP_Primary_RGB_BlackText.png
diff --git a/src/res/images/HBP_Primary_RGB_WhiteText.png b/deploy/logo/assets/HBP_Primary_RGB_WhiteText.png
similarity index 100%
rename from src/res/images/HBP_Primary_RGB_WhiteText.png
rename to deploy/logo/assets/HBP_Primary_RGB_WhiteText.png
diff --git a/src/res/images/ebrains-logo-dark.svg b/deploy/logo/assets/ebrains-logo-dark.svg
similarity index 100%
rename from src/res/images/ebrains-logo-dark.svg
rename to deploy/logo/assets/ebrains-logo-dark.svg
diff --git a/src/res/images/ebrains-logo-light.svg b/deploy/logo/assets/ebrains-logo-light.svg
similarity index 100%
rename from src/res/images/ebrains-logo-light.svg
rename to deploy/logo/assets/ebrains-logo-light.svg
diff --git a/src/res/images/fzj_black_transparent_svg.svg b/deploy/logo/assets/fzj_black_transparent_svg.svg
similarity index 100%
rename from src/res/images/fzj_black_transparent_svg.svg
rename to deploy/logo/assets/fzj_black_transparent_svg.svg
diff --git a/src/res/images/fzj_white_transparent_svg.svg b/deploy/logo/assets/fzj_white_transparent_svg.svg
similarity index 100%
rename from src/res/images/fzj_white_transparent_svg.svg
rename to deploy/logo/assets/fzj_white_transparent_svg.svg
diff --git a/deploy/logo/index.js b/deploy/logo/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..d4750d225f9d0a09510db3fa40175544670c2969
--- /dev/null
+++ b/deploy/logo/index.js
@@ -0,0 +1,40 @@
+const fs = require('fs')
+const path = require('path')
+const { getHandleErrorFn } = require('../util/streamHandleError')
+const router = require('express').Router()
+
+const map = new Map([
+ ['hbp', {
+ mimetype: 'image/png',
+ light: 'HBP_Primary_RGB_BlackText.png',
+ dark: 'HBP_Primary_RGB_WhiteText.png'
+ }],
+ ['ebrains', {
+ mimetype: 'image/svg+xml',
+ light: 'ebrains-logo-dark.svg',
+ dark: 'ebrains-logo-light.svg'
+ }],
+ ['fzj', {
+ mimetype: 'image/svg+xml',
+ light: 'fzj_black_transparent_svg.svg',
+ dark: 'fzj_white_transparent_svg.svg'
+ }]
+])
+
+router.get('/', (req, res) => {
+
+ const USE_LOGO = process.env.USE_LOGO || 'hbp'
+ const { mimetype, light, dark } = map.get(USE_LOGO) || map.get('hbp')
+ const darktheme = !!req.query.darktheme
+ try {
+ res.setHeader('Content-type', mimetype)
+ fs.createReadStream(
+ path.join(__dirname, `assets/${darktheme ? dark : light}`)
+ ).pipe(res).on('error', getHandleErrorFn(req, res))
+ } catch (e) {
+ console.error(`Fetching logo error ${e.toString()}`)
+ res.status(500).end()
+ }
+})
+
+module.exports = router
\ No newline at end of file
diff --git a/src/atlasViewer/atlasViewer.history.service.ts b/src/atlasViewer/atlasViewer.history.service.ts
index aa381ffec51b286f094424e96f8a1e0c688271ec..746ddb4fc01b79d0c1c8d909b4aac9223c87d3a6 100644
--- a/src/atlasViewer/atlasViewer.history.service.ts
+++ b/src/atlasViewer/atlasViewer.history.service.ts
@@ -48,6 +48,7 @@ export class AtlasViewerHistoryUseEffect implements OnDestroy {
return {
type: GENERAL_ACTION_TYPES.APPLY_STATE,
state: {
+ ...storeState,
...defaultRootState,
viewerState: {
...defaultRootState.viewerState,
diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html
index 5836c9005d93e9991105e54ee3780ae38f17a6a7..422971347ad0284d49a033bde86c6f14ad483cc4 100644
--- a/src/atlasViewer/atlasViewer.template.html
+++ b/src/atlasViewer/atlasViewer.template.html
@@ -58,7 +58,7 @@
[currentOnHover]="iavMouseHoverEl.currentOnHoverObs$ | async"
iav-captureClickListenerDirective
[iav-captureClickListenerDirective-captureDocument]="true"
- (iav-captureClickListenerDirective-onClick)="mouseClickDocument($event)">
+ (iav-captureClickListenerDirective-onUnmovedClick)="mouseClickDocument($event)">
</ui-nehuba-container>
<div class="z-index-10 position-absolute pe-none w-100 h-100">
@@ -100,7 +100,7 @@
</div>
- <div class="fixed-bottom pe-none d-flex justify-content-end m-4">
+ <div class="fixed-bottom muted-7 pe-none mb-8 d-flex justify-content-end m-4">
<ng-container *ngTemplateOutlet="logoTmpl">
</ng-container>
</div>
diff --git a/src/atlasViewer/atlasViewer.urlUtil.ts b/src/atlasViewer/atlasViewer.urlUtil.ts
index 2fee4d50c186852f1466b67c70f753824409f061..a46f5fd3f4675a48e6ccbf3ea90cee0f4314c759 100644
--- a/src/atlasViewer/atlasViewer.urlUtil.ts
+++ b/src/atlasViewer/atlasViewer.urlUtil.ts
@@ -322,6 +322,5 @@ export const cvtSearchParamToState = (searchparams: URLSearchParams, state: IavR
}
})()
- console.log(returnState)
return returnState
}
diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css
index 864e2b1901019acb1bfdc5fd8a8363a357fbaec8..aaa460d345db20ed131767266762207ef448816b 100644
--- a/src/res/css/extra_styles.css
+++ b/src/res/css/extra_styles.css
@@ -576,6 +576,11 @@ mat-icon[fontset="far"]
max-width: none!important;
}
+.mb-8
+{
+ margin-bottom:4rem!important;
+}
+
.min-h-2
{
min-height: 1rem;
@@ -705,3 +710,13 @@ kg-dataset-previewer > img
{
top: unset!important;
}
+
+.fixed-top
+{
+ bottom: unset!important;
+}
+
+.layerGroupMenu > .mat-menu-content
+{
+ width: 100%;
+}
diff --git a/src/res/ext/atlas/atlas_multiLevelHuman.json b/src/res/ext/atlas/atlas_multiLevelHuman.json
index e463edcd352076d27f570854d51da7c4ecb0a09b..3364a77d26f18650406a1405a44114118583e2cf 100644
--- a/src/res/ext/atlas/atlas_multiLevelHuman.json
+++ b/src/res/ext/atlas/atlas_multiLevelHuman.json
@@ -5,7 +5,7 @@
{
"@id": "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588",
"name": "Big Brain (Histology)",
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579",
"name": "Jülich Cytoarchitechtonic Brain Atlas (human)"
@@ -23,7 +23,7 @@
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric",
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579",
"name": "Jülich Cytoarchitechtonic Brain Atlas (human)"
@@ -34,6 +34,7 @@
},
{
"ngId": "fibre bundle short",
+ "name": "Fibre Bundle Atlas - Short Bundle",
"@id": "juelich/iav/atlas/v1.0.0/6"
},
{
@@ -61,7 +62,7 @@
{
"@id": "minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992",
"name": "MNI Colin 27",
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579",
"name": "Jülich Cytoarchitechtonic Brain Atlas (human)"
@@ -74,7 +75,7 @@
"@id": "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579",
"name": "Jülich Cytoarchitechtonic Brain Atlas (human)",
"baseLayer": true,
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588",
"name": "Big Brain (Histology)"
@@ -92,7 +93,7 @@
{
"name": "BigBrain Cortical Layers Segmentation",
"@id": "juelich/iav/atlas/v1.0.0/3",
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588",
"name": "Big Brain (Histology)"
@@ -102,7 +103,7 @@
{
"name": "Grey/White matter",
"@id": "juelich/iav/atlas/v1.0.0/4",
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588",
"name": "Big Brain (Histology)"
@@ -112,7 +113,7 @@
{
"name": "Fibre Bundle Atlas - Long Bundle",
"@id": "juelich/iav/atlas/v1.0.0/5",
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric"
@@ -121,8 +122,9 @@
},
{
"ngId": "fibre bundle short",
+ "name": "Fibre Bundle Atlas - Short Bundle",
"@id": "juelich/iav/atlas/v1.0.0/6",
- "avilableIn": [
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric"
@@ -132,7 +134,8 @@
{
"name": "DiFuMo Atlas (64 dimensions)",
"@id": "minds/core/parcellationatlas/v1.0.0/d80fbab2-ce7f-4901-a3a2-3c8ef8a3b721",
- "avilableIn": [
+ "groupName": "DiFuMo Atlas",
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric"
@@ -142,7 +145,8 @@
{
"name": "DiFuMo Atlas (128 dimensions)",
"@id": "minds/core/parcellationatlas/v1.0.0/73f41e04-b7ee-4301-a828-4b298ad05ab8",
- "avilableIn": [
+ "groupName": "DiFuMo Atlas",
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric"
@@ -152,7 +156,8 @@
{
"name": "DiFuMo Atlas (256 dimensions)",
"@id": "minds/core/parcellationatlas/v1.0.0/141d510f-0342-4f94-ace7-c97d5f160235",
- "avilableIn": [
+ "groupName": "DiFuMo Atlas",
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric"
@@ -162,7 +167,8 @@
{
"name": "DiFuMo Atlas (512 dimensions)",
"@id": "minds/core/parcellationatlas/v1.0.0/63b5794f-79a4-4464-8dc1-b32e170f3d16",
- "avilableIn": [
+ "groupName": "DiFuMo Atlas",
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric"
@@ -172,7 +178,8 @@
{
"name": "DiFuMo Atlas (1024 dimensions)",
"@id": "minds/core/parcellationatlas/v1.0.0/12fca5c5-b02c-46ce-ab9f-f12babf4c7e1",
- "avilableIn": [
+ "groupName": "DiFuMo Atlas",
+ "availableIn": [
{
"@id": "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2",
"name": "MNI 152 ICBM 2009c Nonlinear Asymmetric"
diff --git a/src/res/ext/colin.json b/src/res/ext/colin.json
index 1ea080d5fcefe131189e1bff2351e3dcb30ce3a2..b9c110ebc1df05b0e118855b21f5cbefabe4b836 100644
--- a/src/res/ext/colin.json
+++ b/src/res/ext/colin.json
@@ -10,6 +10,7 @@
"parcellations": [
{
"fullId": "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579",
+ "@id": "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579",
"name": "JuBrain Cytoarchitectonic Atlas",
"ngId": "jubrain colin v18 left",
"auxillaryMeshIndices": [
diff --git a/src/res/images/atlas-selection/allen-mouse-2015.png b/src/res/images/atlas-selection/allen-mouse-2015.png
new file mode 100644
index 0000000000000000000000000000000000000000..36b493023e63d3b33c2527191165174713ff7701
Binary files /dev/null and b/src/res/images/atlas-selection/allen-mouse-2015.png differ
diff --git a/src/res/images/atlas-selection/allen-mouse-2017.png b/src/res/images/atlas-selection/allen-mouse-2017.png
new file mode 100644
index 0000000000000000000000000000000000000000..4a8b4cad740bf6e0a5b7ee3f27661fe4941f13e3
Binary files /dev/null and b/src/res/images/atlas-selection/allen-mouse-2017.png differ
diff --git a/src/res/images/atlas-selection/allen-mouse.png b/src/res/images/atlas-selection/allen-mouse.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d38cfce2b17f7f973d1fab5469dfba4f1b63537
Binary files /dev/null and b/src/res/images/atlas-selection/allen-mouse.png differ
diff --git a/src/res/images/atlas-selection/bugbrain.png b/src/res/images/atlas-selection/bugbrain.png
new file mode 100644
index 0000000000000000000000000000000000000000..416dbc79b489db22ed82868237e5a68c40ecf23a
Binary files /dev/null and b/src/res/images/atlas-selection/bugbrain.png differ
diff --git a/src/res/images/atlas-selection/colin27.png b/src/res/images/atlas-selection/colin27.png
new file mode 100644
index 0000000000000000000000000000000000000000..d49773ece71854f93d8dcaed9ca3a710e11decb0
Binary files /dev/null and b/src/res/images/atlas-selection/colin27.png differ
diff --git a/src/res/images/atlas-selection/cortical-layers.png b/src/res/images/atlas-selection/cortical-layers.png
new file mode 100644
index 0000000000000000000000000000000000000000..38a9e902a8691dbb3e2d9732e42a12cda8cae2a2
Binary files /dev/null and b/src/res/images/atlas-selection/cortical-layers.png differ
diff --git a/src/res/images/atlas-selection/cytoarchitectonic-maps.png b/src/res/images/atlas-selection/cytoarchitectonic-maps.png
new file mode 100644
index 0000000000000000000000000000000000000000..c9645ea83f8299e1ce64e185df17d75de1032da5
Binary files /dev/null and b/src/res/images/atlas-selection/cytoarchitectonic-maps.png differ
diff --git a/src/res/images/atlas-selection/difumo-1024.png b/src/res/images/atlas-selection/difumo-1024.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ab4bf4ac91ac9be0eb6c96af1c45ab17cc9bddd
Binary files /dev/null and b/src/res/images/atlas-selection/difumo-1024.png differ
diff --git a/src/res/images/atlas-selection/difumo-128.png b/src/res/images/atlas-selection/difumo-128.png
new file mode 100644
index 0000000000000000000000000000000000000000..47de2e12571b69f192f9117f41b45e1200c7f7e2
Binary files /dev/null and b/src/res/images/atlas-selection/difumo-128.png differ
diff --git a/src/res/images/atlas-selection/difumo-256.png b/src/res/images/atlas-selection/difumo-256.png
new file mode 100644
index 0000000000000000000000000000000000000000..08e508a6faa65bcbbc9799ff37025915992b4199
Binary files /dev/null and b/src/res/images/atlas-selection/difumo-256.png differ
diff --git a/src/res/images/atlas-selection/difumo-512.png b/src/res/images/atlas-selection/difumo-512.png
new file mode 100644
index 0000000000000000000000000000000000000000..b1c064664a6836b2d41c60a4407def4bfbc2e4dc
Binary files /dev/null and b/src/res/images/atlas-selection/difumo-512.png differ
diff --git a/src/res/images/atlas-selection/difumo-64.png b/src/res/images/atlas-selection/difumo-64.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf14836310113c9cb2688cb5f298a2aa87c010e3
Binary files /dev/null and b/src/res/images/atlas-selection/difumo-64.png differ
diff --git a/src/res/images/atlas-selection/firbe-long.png b/src/res/images/atlas-selection/firbe-long.png
new file mode 100644
index 0000000000000000000000000000000000000000..040d96a8dc7c575e1076072e800fedf08a413cb1
Binary files /dev/null and b/src/res/images/atlas-selection/firbe-long.png differ
diff --git a/src/res/images/atlas-selection/firbe-short.png b/src/res/images/atlas-selection/firbe-short.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c52194bc5fa263114e6d4be9a2e6422ff5fa79e
Binary files /dev/null and b/src/res/images/atlas-selection/firbe-short.png differ
diff --git a/src/res/images/atlas-selection/grey-white-matter.png b/src/res/images/atlas-selection/grey-white-matter.png
new file mode 100644
index 0000000000000000000000000000000000000000..352a7de6500d4d82a2952f911815726d7bae474b
Binary files /dev/null and b/src/res/images/atlas-selection/grey-white-matter.png differ
diff --git a/src/res/images/atlas-selection/icbm2009c.png b/src/res/images/atlas-selection/icbm2009c.png
new file mode 100644
index 0000000000000000000000000000000000000000..1dfc2c47e84d60c7a5b4d69eba182cf003f1bf62
Binary files /dev/null and b/src/res/images/atlas-selection/icbm2009c.png differ
diff --git a/src/res/images/atlas-selection/waxholm-v1.png b/src/res/images/atlas-selection/waxholm-v1.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e24e005e975938cbe6f3f4c658b6a306d9cce8e
Binary files /dev/null and b/src/res/images/atlas-selection/waxholm-v1.png differ
diff --git a/src/res/images/atlas-selection/waxholm-v2.png b/src/res/images/atlas-selection/waxholm-v2.png
new file mode 100644
index 0000000000000000000000000000000000000000..e18fe6d5876c02b5ab34297c048219d6f622f0f8
Binary files /dev/null and b/src/res/images/atlas-selection/waxholm-v2.png differ
diff --git a/src/res/images/atlas-selection/waxholm-v3.png b/src/res/images/atlas-selection/waxholm-v3.png
new file mode 100644
index 0000000000000000000000000000000000000000..1b072a165fded37525ab849afe69ea61c3503f3b
Binary files /dev/null and b/src/res/images/atlas-selection/waxholm-v3.png differ
diff --git a/src/res/images/atlas-selection/waxholm.png b/src/res/images/atlas-selection/waxholm.png
new file mode 100644
index 0000000000000000000000000000000000000000..fea10fee5272e99bc6cfd969a42307c38fa8b6d7
Binary files /dev/null and b/src/res/images/atlas-selection/waxholm.png differ
diff --git a/src/res/images/big-brain.png b/src/res/images/big-brain.png
new file mode 100644
index 0000000000000000000000000000000000000000..12e467e2211a635880a01e2fdb42c251b94320a5
Binary files /dev/null and b/src/res/images/big-brain.png differ
diff --git a/src/services/state/viewerState.store.helper.ts b/src/services/state/viewerState.store.helper.ts
index 28894daf0ce8bb35c92e69bcd86b386f7327aa65..241d9b8d3437a0190553652e4d33636bd9d59b50 100644
--- a/src/services/state/viewerState.store.helper.ts
+++ b/src/services/state/viewerState.store.helper.ts
@@ -37,6 +37,16 @@ export const viewerStateSelectAtlas = createAction(
props<{ atlas: { ['@id']: string } }>()
)
+export const viewerStateSelectParcellationWithId = createAction(
+ `[viewerState] selectParcellationWithId`,
+ props<{ payload: { ['@id']: string } }>()
+)
+
+export const viewerStateSelectTemplateWithId = createAction(
+ `[viewerState] selectTemplateWithId`,
+ props<{ payload: { ['@id']: string } }>()
+)
+
export const viewerStateToggleAdditionalLayer = createAction(
`[viewerState] toggleAdditionalLayer`,
props<{ atlas: { ['@id']: string } }>()
diff --git a/src/ui/atlasLayerWidget/alwContainer/alwContainer.component.ts b/src/ui/atlasLayerWidget/alwContainer/alwContainer.component.ts
index b206c4a894aeab90e7e93a51dd2f5f4e7e2703f8..69319b14ba00962bbce8cfc52ad4eb2254771230 100644
--- a/src/ui/atlasLayerWidget/alwContainer/alwContainer.component.ts
+++ b/src/ui/atlasLayerWidget/alwContainer/alwContainer.component.ts
@@ -37,6 +37,7 @@ export class AtlasLayerContainer {
this.overlayingParcellationLayers$ = this.store$.pipe(
select(viewerStateGetOverlayingAdditionalParcellations),
+ filter(v => !!v),
withLatestFrom(this.templateSelected$),
map(([ additionalP, templateSelected ]) => {
return additionalP
diff --git a/src/ui/atlasLayerWidget/alwContainer/alwContainer.template.html b/src/ui/atlasLayerWidget/alwContainer/alwContainer.template.html
index 6f592f1f1fe8f2359542b218136383ee18a7e6ba..b5d363f0c43ca270766734830f96845a3e8868c8 100644
--- a/src/ui/atlasLayerWidget/alwContainer/alwContainer.template.html
+++ b/src/ui/atlasLayerWidget/alwContainer/alwContainer.template.html
@@ -87,28 +87,34 @@
</mat-chip>
</mat-chip-list>
- <button class="pe-all"
- [color]="visibleTab === 'hierarchy' ? 'primary' : ''"
- (click)="handleClickWidget('hierarchy')"
- mat-icon-button>
- <i class="fas fa-sitemap"></i>
- </button>
- <button class="pe-all"
- [color]="visibleTab === 'connectivity' ? 'primary' : ''"
- (click)="handleClickWidget('connectivity')"
- mat-icon-button>
- <i class="fas fa-braille"></i>
- </button>
- <button class="pe-all"
- mat-icon-button
- [matBadge]="visibleTab === 'dataset' ? null : availableDatasets"
- matBadgeColor="accent"
- [color]="visibleTab === 'dataset' ? 'primary' : ''"
- (click)="handleClickWidget('dataset')">
- <i class="fas fa-database"></i>
- </button>
+ <ng-container *ngTemplateOutlet="btnTmpl; context: { tab: 'hierarchy', iconClass: 'fas fa-sitemap' }">
+ </ng-container>
+
+ <ng-container *ngTemplateOutlet="btnTmpl; context: { tab: 'connectivity', iconClass: 'fas fa-braille' }">
+ </ng-container>
+
+ <ng-container *ngTemplateOutlet="btnTmpl; context: { tab: 'dataset', iconClass: 'fas fa-database', badge: visibleTab === 'dataset' ? null : availableDatasets }">
+ </ng-container>
</div>
+<ng-template
+ #btnTmpl
+ let-matBtnDisabled="matBtnDisabled"
+ let-tab="tab"
+ let-badge="badge"
+ let-iconClass="iconClass">
+ <iav-dynamic-mat-button
+ class="pe-all"
+ [matBadge]="badge"
+ matBadgeColor="accent"
+ (click)="handleClickWidget(tab)"
+ [iav-dynamic-mat-button-disabled]="matBtnDisabled"
+ iav-dynamic-mat-button-color="primary"
+ [iav-dynamic-mat-button-style]="tab === visibleTab ? 'mat-mini-fab' : 'mat-icon-button'">
+ <i [class]="iconClass"></i>
+ </iav-dynamic-mat-button>
+</ng-template>
+
<ng-template #closeBtn>
<button mat-icon-button
(click)="visibleTab = null">
diff --git a/src/ui/logoContainer/logoContainer.component.ts b/src/ui/logoContainer/logoContainer.component.ts
index f0b4569eb12a91c6c17f877c021f31bccc80433a..6af1710795ec7efa9d2a9b8347e38b0e4c3269f2 100644
--- a/src/ui/logoContainer/logoContainer.component.ts
+++ b/src/ui/logoContainer/logoContainer.component.ts
@@ -1,4 +1,7 @@
import { Component } from "@angular/core";
+import { BACKENDURL } from "src/util/constants";
+import { PureContantService } from "src/util";
+import { Subscription } from "rxjs";
@Component({
selector : 'logo-container',
@@ -10,13 +13,22 @@ import { Component } from "@angular/core";
export class LogoContainer {
// only used to define size
- public imgSrc = USE_LOGO === 'hbp'
- ? 'res/image/HBP_Primary_RGB_WhiteText.png'
- : USE_LOGO === 'ebrains'
- ? `res/image/ebrains-logo-light.svg`
- : USE_LOGO === 'fzj'
- ? 'res/image/fzj_black_transparent_svg.svg'
- : null
-
- public useLogo = USE_LOGO
+ public imgSrc = `${BACKENDURL}logo`
+
+ public containerStyle = {
+ backgroundImage: `url('${BACKENDURL}logo')`
+ }
+
+ private subscriptions: Subscription[] = []
+ constructor(
+ pureConstantService: PureContantService
+ ){
+ this.subscriptions.push(
+ pureConstantService.darktheme$.subscribe(flag => {
+ this.containerStyle = {
+ backgroundImage: `url('${BACKENDURL}logo${!!flag ? '?darktheme=true' : ''}')`
+ }
+ })
+ )
+ }
}
diff --git a/src/ui/logoContainer/logoContainer.style.css b/src/ui/logoContainer/logoContainer.style.css
index 5c170a688c2d4e815ffbb5ee4b39564dee9928bc..7c4c04468d1e04b5095f8096bca89a34b0ed3056 100644
--- a/src/ui/logoContainer/logoContainer.style.css
+++ b/src/ui/logoContainer/logoContainer.style.css
@@ -1,33 +1,3 @@
-:host-context([darktheme="true"]) [uselogo="ebrains"][hbpLogoContainer]
-{
- background-image:url('res/image/ebrains-logo-light.svg')
-}
-
-:host-context([darktheme="false"]) [uselogo="ebrains"][hbpLogoContainer]
-{
- background-image:url('res/image/ebrains-logo-dark.svg')
-}
-
-:host-context([darktheme="true"]) [uselogo="hbp"][hbpLogoContainer]
-{
- background-image:url('res/image/HBP_Primary_RGB_WhiteText.png')
-}
-
-:host-context([darktheme="false"]) [uselogo="hbp"][hbpLogoContainer]
-{
- background-image:url('res/image/HBP_Primary_RGB_BlackText.png')
-}
-
-:host-context([darktheme="true"]) [uselogo="fzj"][hbpLogoContainer]
-{
- background-image:url('res/image/fzj_white_transparent_svg.svg')
-}
-
-:host-context([darktheme="false"]) [uselogo="fzj"][hbpLogoContainer]
-{
- background-image:url('res/image/fzj_black_transparent_svg.svg')
-}
-
[hbpLogoContainer]
{
display:inline-block;
diff --git a/src/ui/logoContainer/logoContainer.template.html b/src/ui/logoContainer/logoContainer.template.html
index c4a53a91cd74411c941a717c8a7f1d6189ce146d..647ad7b86efe97d7edfdf11c293b32ce42b11049 100644
--- a/src/ui/logoContainer/logoContainer.template.html
+++ b/src/ui/logoContainer/logoContainer.template.html
@@ -1,3 +1,3 @@
-<span [attr.uselogo]="useLogo" hbpLogoContainer>
+<span [ngStyle]="containerStyle" hbpLogoContainer>
<img [src]="imgSrc" />
</span>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index 84f4101e1801729d3becaac50ee004c69197b40b..af96ee110dbff8e7447eed7e7290a97e87b098a6 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -100,7 +100,7 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy {
public sliceViewLoadingMain$: Observable<[boolean, boolean, boolean]>
public perspectiveViewLoading$: Observable<string|null>
- private templateSelected$: Observable<any>
+ public templateSelected$: Observable<any>
private newViewer$: Observable<any>
private selectedParcellation$: Observable<any>
private selectedRegions$: Observable<any[]>
diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css
index 29a4d75a728298876505e6ae577aae12ac505ee7..dbb79cdf0a18e1c6d4d788e15662b3c15c35ca69 100644
--- a/src/ui/nehubaContainer/nehubaContainer.style.css
+++ b/src/ui/nehubaContainer/nehubaContainer.style.css
@@ -7,11 +7,7 @@
atlas-layer-container
{
- position: absolute;
- bottom: 0;
- lefT: 0;
-
- margin-bottom: 5em;
+ margin-bottom: 1em;
margin-left: 1em;
width: 30em;
height: 50em;
@@ -138,13 +134,18 @@ div#scratch-pad
right: 0;
}
-.status-card-container
+.viewer-config-container
{
position:absolute;
left:1em;
bottom:1em;
- width : 20em;
- pointer-events: all;
+}
+
+.viewer-status-container
+{
+ position: absolute;
+ right: 1em;
+ bottom: 1em;
}
/* if not mobile, then show on hover */
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index d29ca41407106f0540dae057a927007f3999cdcf..259da4bbc77c9141c8376e010d2e1207140f3bd0 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -25,16 +25,29 @@
<layout-floating-container [zIndex]="5">
- <!-- StatusCard container-->
- <div *ngIf="viewerLoaded" class="status-card-container muted-7">
- <ui-status-card
- [selectedTemplateName]="selectedTemplate && selectedTemplate.name"
- [nehubaViewer]="nehubaViewer">
- </ui-status-card>
+ <div *ngIf="templateSelected$ | async"
+ class="viewer-config-container d-flex align-items-end pe-none">
+ <!-- Viewer Selector Container-->
+ <viewer-selector
+ #viewerSelector
+ class="pe-all"
+ (iav-outsideClick)="viewerSelector.selectorExpanded = false">
+ </viewer-selector>
+
+ <atlas-layer-container class="pe-none overflow-visible"></atlas-layer-container>
</div>
- <atlas-layer-container class="overflow-visible"></atlas-layer-container>
+ <!-- StatusCard container-->
+ <div class="viewer-status-container pe-all">
+ <div class="muted-7" *ngIf="viewerLoaded">
+ <ui-status-card
+ [selectedTemplateName]="selectedTemplate && selectedTemplate.name"
+ [nehubaViewer]="nehubaViewer">
+ </ui-status-card>
+ </div>
+
+ </div>
</layout-floating-container>
<div id="scratch-pad">
diff --git a/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts b/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
index 7c0b1491d784a8aa110b7f7279c3800c364c4df6..23bfe7a32fdca74ac5df2f39fd165d8f21921f15 100644
--- a/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
+++ b/src/ui/nehubaContainer/splashScreen/splashScreen.component.ts
@@ -60,7 +60,8 @@ export class SplashScreen implements AfterViewInit {
this.loadedAtlases$ = this.store.pipe(
select(state => state[viewerStateHelperStoreName]),
- select(state => state.fetchedAtlases)
+ select(state => state.fetchedAtlases),
+ filter(v => !!v)
)
this.stillLoadingAtlases$ = this.loadedAtlases$.pipe(
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 1533e6bb20d9b4a726196c9bed08243c1fb821e5..67db58fb2c840ebfb543c11436446a1d4c85356d 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -91,6 +91,7 @@ import { APPEND_SCRIPT_TOKEN, appendScriptFactory } from "src/util/constants";
import { DOCUMENT } from "@angular/common";
import { AtlasDropdownSelector } from './atlasDropdown/atlasDropdown.component'
import { AtlasLayerContainer } from "./atlasLayerWidget/alwContainer/alwContainer.component";
+import {ViewerSelectorComponent} from "src/ui/viewerSelector/viewerSelector.component";
@NgModule({
imports : [
@@ -127,7 +128,9 @@ import { AtlasLayerContainer } from "./atlasLayerWidget/alwContainer/alwContaine
ConfigComponent,
SigninBanner,
AtlasDropdownSelector,
-
+ ViewerSelectorComponent,
+ AtlasDropdownSelector,
+
StatusCardComponent,
CookieAgreement,
KGToS,
diff --git a/src/ui/viewerSelector/viewerSelector.component.ts b/src/ui/viewerSelector/viewerSelector.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9ed9ff95e476deea2531d16b0c3fcfa1483ba683
--- /dev/null
+++ b/src/ui/viewerSelector/viewerSelector.component.ts
@@ -0,0 +1,121 @@
+import { Component, ElementRef, OnInit, ViewChild, ViewChildren, QueryList } from "@angular/core";
+import { select, Store } from "@ngrx/store";
+import { safeFilter } from "src/services/stateStore.service";
+import { distinctUntilChanged, map, withLatestFrom, shareReplay } from "rxjs/operators";
+import { Observable, Subscription } from "rxjs";
+import { viewerStateGetSelectedAtlas, viewerStateSelectParcellationWithId, viewerStateSelectTemplateWithId } from "src/services/state/viewerState.store.helper";
+import { MatMenuTrigger } from "@angular/material/menu";
+
+@Component({
+ selector: 'viewer-selector',
+ templateUrl: './viewerSelector.template.html',
+ styleUrls: ['./viewerSelector.style.css'],
+})
+export class ViewerSelectorComponent implements OnInit {
+
+ @ViewChildren(MatMenuTrigger) matMenuTriggers: QueryList<MatMenuTrigger>
+ public atlas: any
+
+ public groupedLayers = []
+
+ public selectedTemplateSpaceIndex: number = 0
+ public selectedLayers = []
+
+ public selectedTemplate$: Observable<any>
+ private selectedParcellation$: Observable<any>
+ public selectedAtlas$: Observable<any>
+ private subscriptions: Subscription[] = []
+
+ public layerGroupMenuItems: any[]
+
+ public selectorExpanded: boolean = false
+
+ constructor(private store$: Store<any>) {
+ this.selectedAtlas$ = this.store$.pipe(
+ select(viewerStateGetSelectedAtlas),
+ distinctUntilChanged(),
+ shareReplay(1)
+ )
+ this.selectedTemplate$ = this.store$.pipe(
+ select('viewerState'),
+ safeFilter('templateSelected'),
+ map(state => state.templateSelected),
+ withLatestFrom(this.selectedAtlas$),
+ map(([templateSelected, templateFromAtlas]) => {
+ return {
+ ...templateFromAtlas,
+ ...templateSelected
+ }
+ })
+ )
+ this.selectedParcellation$ = this.store$.pipe(
+ select('viewerState'),
+ safeFilter('parcellationSelected'),
+ map(state => state.parcellationSelected),
+ )
+ }
+
+ ngOnInit(): void {
+ this.subscriptions.push(
+ this.selectedTemplate$.subscribe(st => {
+ this.selectedTemplateSpaceIndex = this.atlas && this.atlas.templateSpaces.findIndex(ts => ts['@id'] === st['@id'])
+ })
+ )
+ this.subscriptions.push(
+ this.selectedParcellation$.subscribe(ps => {
+ this.selectedLayers = this.atlas && [this.atlas.parcellations.find(l => l['@id'] === ps['@id'])['@id']]
+ })
+ )
+ this.subscriptions.push(
+ this.selectedAtlas$.subscribe(sa => {
+ if (sa && sa !== 'undefined') this.atlas = sa
+
+ const groupableParcellations = this.atlas?.parcellations.filter(p => p.groupName && p.groupName.length)
+
+ const groupsDict = groupableParcellations.reduce((r, a) => {
+ r[a.groupName] = r[a.groupName] || []
+ r[a.groupName].push(a)
+ return r
+ }, {})
+ this.groupedLayers = Object.entries(groupsDict)
+ })
+ )
+ }
+
+ selectTemplateWithName(template) {
+ this.store$.dispatch(
+ viewerStateSelectTemplateWithId({ payload: template })
+ )
+ }
+
+ selectParcellationWithName(layer) {
+ this.store$.dispatch(
+ viewerStateSelectParcellationWithId({ payload: layer })
+ )
+ }
+
+ templateIncludesLayer(layer, template) {
+ return layer.availableIn.map(a => a['@id']).includes(template['@id'])
+ }
+
+ templateIncludesGroup(group, template) {
+ return group[1].every(v => v.availableIn.map(t => t['@id']).includes(template['@id']))
+ }
+
+ selectedOneOfTheLayers(layers) {
+ const includes = layers.map(l=>l['@id']).some(r=> this.selectedLayers.includes(r))
+ return includes
+ }
+
+ selectedLayersIncludes(id) {
+ return this.selectedLayers.includes(id)
+ }
+
+ notGroupedParcellations(parcellations) {
+ return parcellations.filter(p => !p.groupName)
+ }
+
+ collapseExpandedGroup(){
+ this.matMenuTriggers.forEach(t => t.menuOpen && t.closeMenu())
+ }
+}
diff --git a/src/ui/viewerSelector/viewerSelector.style.css b/src/ui/viewerSelector/viewerSelector.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..2533ae94d54cde966344d5ef06602ff859c459a4
--- /dev/null
+++ b/src/ui/viewerSelector/viewerSelector.style.css
@@ -0,0 +1,114 @@
+.singleLayerImageContainer img {
+ flex-shrink: 0;
+ /*min-width: 100%;*/
+ /*min-height: 100%;*/
+ width: 60px;
+ height: 60px;
+ border-radius: 10px;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+}
+
+.singleLayerImageContainer {
+ max-width: 80px;
+ width: 80px;
+ height: 90px;
+}
+
+.selectedTemplateDefaultContainer {
+ width: 100px;
+ height: 100px;
+ border-radius: 10px;
+}
+.selectedTemplateDefaultContainer img {
+ border-radius: 10px;
+ min-width: 100%;
+ min-height: 100%;
+ width: 100%;
+}
+
+.selectedLayerBorder {
+ border: 2px solid #FED363;
+}
+
+.cursorPointer {
+ cursor: pointer !important;
+}
+
+
+.scale-up-bl {
+ -webkit-animation: scale-up-bl .2s cubic-bezier(.39, .575, .565, 1.000) both;
+ animation: scale-up-bl .2s cubic-bezier(.39, .575, .565, 1.000) both;
+}
+
+@-webkit-keyframes scale-up-bl {
+ 0% {
+ -webkit-transform: scale(.5);
+ transform: scale(.5);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+ 100% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+}
+
+@keyframes scale-up-bl {
+ 0% {
+ -webkit-transform: scale(.5);
+ transform: scale(.5);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+ 100% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+}
+
+
+.scale-down-bl {
+ -webkit-animation: scale-down-bl .4s cubic-bezier(.25, .46, .45, .94) both;
+ animation: scale-down-bl .4s cubic-bezier(.25, .46, .45, .94) both;
+}
+
+@-webkit-keyframes scale-down-bl {
+ 0% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+ 100% {
+ -webkit-transform: scale(.5);
+ transform: scale(.5);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+}
+
+@keyframes scale-down-bl {
+ 0% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+ 100% {
+ -webkit-transform: scale(.5);
+ transform: scale(.5);
+ -webkit-transform-origin: 0 100%;
+ transform-origin: 0 100%;
+ }
+}
+
+.folder-container
+{
+ margin-right:0.5rem;
+ margin-bottom:-0.5rem;
+}
\ No newline at end of file
diff --git a/src/ui/viewerSelector/viewerSelector.template.html b/src/ui/viewerSelector/viewerSelector.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..4dce8803f5b685e957118a57a443bdfc8b8c05a2
--- /dev/null
+++ b/src/ui/viewerSelector/viewerSelector.template.html
@@ -0,0 +1,96 @@
+<div>
+ <div [hidden]="selectorExpanded"
+ class="muted-7 m-2 selectedLayerBorder cursorPointer selectedTemplateDefaultContainer"
+ (click)="selectorExpanded = true">
+ <img [src]="(selectedTemplate$ | async)?.previewUrl" draggable="false"/>
+ </div>
+
+
+ <small *ngIf="selectorExpanded" class="position-relative">
+ <mat-card class="scale-up-bl">
+ <div class="position-absolute cursorPointer" style="top: 10px; right: 10px;" (click)="selectorExpanded = false">
+ <i class="fas fa-times-circle"></i>
+ </div>
+ <mat-card-subtitle class="mb-1">Templates</mat-card-subtitle>
+
+
+ <div class="d-flex flex-wrap w-100">
+ <div class="d-flex flex-column justify-content-start w-100 mb-1 mt-1 overflow-hidden cursorPointer singleLayerImageContainer"
+ *ngFor="let template of (selectedAtlas$ | async)?.templateSpaces; let i = index"
+ [matTooltip]="template.name"
+ matTooltipPosition="above"
+ (click)="selectTemplateWithName(template)">
+ <img class="layer-image align-self-center"
+ [ngClass]="selectedTemplateSpaceIndex === i? 'selectedLayerBorder': null "
+ [src]="template.previewUrl"
+ draggable="false"/> <!--[ngStyle]="{opacity: hover? '1' : '0.2'}" (mouseenter)="hover = true" (mouseleave)="hover = false"-->
+ <small class="ml-1 mr-1 mt-2 text-truncate">{{template.name}}</small>
+ </div>
+ </div>
+
+ <mat-card-subtitle class="mb-1 mt-2">Layers</mat-card-subtitle>
+
+
+
+
+ <div class="d-flex flex-wrap w-100">
+ <div class="d-flex flex-column justify-content-start w-100 mb-1 mt-1 overflow-hidden cursorPointer singleLayerImageContainer"
+ *ngFor="let layer of notGroupedParcellations((selectedAtlas$ | async)?.parcellations); let i = index;"
+ [matTooltip]="layer.name"
+ matTooltipPosition="above"
+ (click)="templateIncludesLayer(layer, atlas?.templateSpaces[selectedTemplateSpaceIndex]) && selectParcellationWithName(layer)"
+ [ngStyle]="{opacity: templateIncludesLayer(layer, atlas?.templateSpaces[selectedTemplateSpaceIndex])? '1' : '0.2'}">
+ <img class="layer-image align-self-center"
+ [ngClass]="selectedLayersIncludes(layer['@id'])? 'selectedLayerBorder' : null"
+ [src]="layer.previewUrl"
+ draggable="false"/>
+ <small class="ml-1 mr-1 mt-2 mb-2 text-truncate">{{layer.name}}</small>
+ </div>
+
+ <div class="d-flex flex-column justify-content-start w-100 mb-1 mt-1 overflow-hidden cursorPointer singleLayerImageContainer"
+ [matMenuTriggerFor]="layerGroupMenu"
+ iav-stop="click"
+ *ngFor="let group of groupedLayers; let i = index;"
+ [matTooltip]="group[0]"
+ matTooltipPosition="above"
+ (click)="layerGroupMenuItems = group[1]"
+ [ngStyle]="{opacity: templateIncludesGroup(group, atlas?.templateSpaces[selectedTemplateSpaceIndex])? '1' : '0.2'}"
+ #groupedMenuTrigger="matMenuTrigger">
+ <img class="layer-image align-self-center"
+ [ngClass]="selectedOneOfTheLayers(group[1])? 'selectedLayerBorder' : null"
+ [src]="group[1][0].previewUrl"
+ draggable="false"/>
+ <div class="d-flex flex-row-reverse align-items-end w-100 h-0">
+ <i class="fas fa-folder folder-container fa-2x"></i>
+ </div>
+ <small class="ml-1 mr-1 mt-2 mb-2 text-truncate">{{group[0]}}</small>
+ </div>
+
+ <mat-menu
+ #layerGroupMenu="matMenu"
+ class="layerGroupMenu"
+ hasBackdrop="false">
+
+ <ng-template matMenuContent>
+ <div iav-stop="click"
+ class="d-flex flex-column align-items-center"
+ (iav-outsideClick)="collapseExpandedGroup()">
+ <div class="d-flex flex-column justify-content-center w-100 mb-1 mt-1 overflow-hidden cursorPointer singleLayerImageContainer"
+ *ngFor="let layer of layerGroupMenuItems; let i = index;"
+ [matTooltip]="layer.name"
+ matTooltipPosition="above"
+ (click)="templateIncludesLayer(layer, atlas?.templateSpaces[selectedTemplateSpaceIndex]) && selectParcellationWithName(layer)"
+ [ngStyle]="{opacity: templateIncludesLayer(layer, atlas?.templateSpaces[selectedTemplateSpaceIndex])? '1' : '0.2'}">
+ <img class="layer-image align-self-center"
+ [ngClass]="selectedLayersIncludes(layer['@id'])? 'selectedLayerBorder' : null"
+ [src]="layer.previewUrl"
+ draggable="false"/>
+ <small class="iv-custom-comp text ml-1 mr-1 mt-2 mb-2 text-truncate">{{layer.name}}</small>
+ </div>
+ </div>
+ </ng-template>
+ </mat-menu>
+ </div>
+ </mat-card>
+ </small>
+</div>
diff --git a/src/ui/viewerStateController/viewerState.base.ts b/src/ui/viewerStateController/viewerState.base.ts
index fdbabfcf73a3f66e5b8338dbbcc826c77370a9e9..fb196c12b991ebbc4b19eae7bcf1ddc4fdcf76d3 100644
--- a/src/ui/viewerStateController/viewerState.base.ts
+++ b/src/ui/viewerStateController/viewerState.base.ts
@@ -17,10 +17,6 @@ const ACTION_TYPES = {
NAVIGATETO_REGION: 'NAVIGATETO_REGION',
}
-const compareWith = (o, n) => !o || !n
- ? false
- : o.name === n.name
-
export class ViewerStateBase implements OnInit {
@ViewChild('savedRegionBottomSheetTemplate', {read: TemplateRef}) public savedRegionBottomSheetTemplate: TemplateRef<any>
@@ -40,8 +36,6 @@ export class ViewerStateBase implements OnInit {
public savedRegionsSelections$: Observable<any[]>
- public compareWith = compareWith
-
private savedRegionBottomSheetRef: MatBottomSheetRef
constructor(
@@ -209,3 +203,4 @@ export class ViewerStateBase implements OnInit {
}
export const VIEWERSTATE_CONTROLLER_ACTION_TYPES = ACTION_TYPES
+
diff --git a/src/ui/viewerStateController/viewerState.useEffect.spec.ts b/src/ui/viewerStateController/viewerState.useEffect.spec.ts
index 6a98174940a4c1993c1be1d072897237aa01ecf7..dd055354b4e04fa8d05b06321f4f41c7dc9ffaee 100644
--- a/src/ui/viewerStateController/viewerState.useEffect.spec.ts
+++ b/src/ui/viewerStateController/viewerState.useEffect.spec.ts
@@ -88,42 +88,42 @@ describe('viewerState.useEffect.ts', () => {
describe('selectTemplateWithName$', () => {
it('if coordXform returns error', () => {
-
+ expect(false).toBe(true)
const viewerStateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
- expect(
- viewerStateCtrlEffect.selectTemplateWithName$
- ).toBeObservable(
- hot(
- 'a',
- {
- a: {
- type: NEWVIEWER,
- selectTemplate: reconstitutedColin,
- selectParcellation: reconstitutedColin.parcellations[0]
- }
- }
- )
- )
+ // expect(
+ // viewerStateCtrlEffect.selectTemplateWithName$
+ // ).toBeObservable(
+ // hot(
+ // 'a',
+ // {
+ // a: {
+ // type: NEWVIEWER,
+ // selectTemplate: reconstitutedColin,
+ // selectParcellation: reconstitutedColin.parcellations[0]
+ // }
+ // }
+ // )
+ // )
})
it('calls with correct param', () => {
-
+ expect(false).toBe(true)
// necessary for observable to fire
const viewerStateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect)
- expect(
- viewerStateCtrlEffect.selectTemplateWithName$
- ).toBeObservable(
- hot(
- 'a',
- {
- a: {
- type: NEWVIEWER,
- selectTemplate: reconstitutedColin,
- selectParcellation: reconstitutedColin.parcellations[0]
- }
- }
- )
- )
+ // expect(
+ // viewerStateCtrlEffect.selectTemplateWithName$
+ // ).toBeObservable(
+ // hot(
+ // 'a',
+ // {
+ // a: {
+ // type: NEWVIEWER,
+ // selectTemplate: reconstitutedColin,
+ // selectParcellation: reconstitutedColin.parcellations[0]
+ // }
+ // }
+ // )
+ // )
expect(spy).toHaveBeenCalledWith(
bigbrainJson.name,
reconstitutedColin.name,
@@ -145,21 +145,21 @@ describe('viewerState.useEffect.ts', () => {
}
updatedColinNavigation.zoomFactor = zoom
updatedColinNavigation.pose.orientation = orientation
-
- expect(
- viewerStateCtrlEffect.selectTemplateWithName$
- ).toBeObservable(
- hot(
- 'a',
- {
- a: {
- type: NEWVIEWER,
- selectTemplate: updatedColin,
- selectParcellation: updatedColin.parcellations[0]
- }
- }
- )
- )
+ expect(false).toBe(true)
+ // expect(
+ // viewerStateCtrlEffect.selectTemplateWithName$
+ // ).toBeObservable(
+ // hot(
+ // 'a',
+ // {
+ // a: {
+ // type: NEWVIEWER,
+ // selectTemplate: updatedColin,
+ // selectParcellation: updatedColin.parcellations[0]
+ // }
+ // }
+ // )
+ // )
})
})
})
diff --git a/src/ui/viewerStateController/viewerState.useEffect.ts b/src/ui/viewerStateController/viewerState.useEffect.ts
index 741b8d6166ed82d21aa47a2465d038129542ca14..6f4a57af9db2ba60f3d5d853f2e3527530a5d925 100644
--- a/src/ui/viewerStateController/viewerState.useEffect.ts
+++ b/src/ui/viewerStateController/viewerState.useEffect.ts
@@ -1,7 +1,7 @@
import { Injectable, OnDestroy, OnInit } from "@angular/core";
import { Actions, Effect, ofType } from "@ngrx/effects";
import { Action, select, Store } from "@ngrx/store";
-import { Observable, Subscription, of } from "rxjs";
+import { Observable, Subscription, of, merge } from "rxjs";
import {distinctUntilChanged, filter, map, shareReplay, withLatestFrom, switchMap, mapTo } from "rxjs/operators";
import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
import { CHANGE_NAVIGATION, FETCHED_TEMPLATE, GENERAL_ACTION_TYPES, IavRootStoreInterface, isDefined, NEWVIEWER, SELECT_PARCELLATION, SELECT_REGIONS } from "src/services/stateStore.service";
@@ -10,7 +10,7 @@ import { regionFlattener } from "src/util/regionFlattener";
import { VIEWERSTATE_CONTROLLER_ACTION_TYPES } from "./viewerState.base";
import {TemplateCoordinatesTransformation} from "src/services/templateCoordinatesTransformation.service";
import { CLEAR_STANDALONE_VOLUMES } from "src/services/state/viewerState.store";
-import { viewerStateToggleRegionSelect } from "src/services/state/viewerState.store.helper";
+import { viewerStateToggleRegionSelect, viewerStateSelectParcellationWithId, viewerStateSelectTemplateWithId } from "src/services/state/viewerState.store.helper";
@Injectable({
providedIn: 'root',
@@ -33,10 +33,10 @@ export class ViewerStateControllerUseEffect implements OnInit, OnDestroy {
)
@Effect()
- public selectTemplateWithName$: Observable<any>
+ public selectParcellation$: Observable<any>
@Effect()
- public selectParcellationWithName$: Observable<any>
+ public selectTemplate$: Observable<any>
/**
* Determines how single click on region hierarchy will affect view
@@ -77,36 +77,47 @@ export class ViewerStateControllerUseEffect implements OnInit, OnDestroy {
distinctUntilChanged(),
)
- this.onTemplateSelectClearStandAloneVolumes$ = this.actions$.pipe(
- ofType(VIEWERSTATE_CONTROLLER_ACTION_TYPES.SELECT_TEMPLATE_WITH_NAME),
- mapTo({
- type: CLEAR_STANDALONE_VOLUMES
- })
+ this.onTemplateSelectClearStandAloneVolumes$ = viewerState$.pipe(
+ select('templateSelected'),
+ distinctUntilChanged(),
+ mapTo({ type: CLEAR_STANDALONE_VOLUMES })
)
- this.selectParcellationWithName$ = this.actions$.pipe(
- ofType(VIEWERSTATE_CONTROLLER_ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME),
- map(action => {
- const { payload = {} } = action as ViewerStateAction
- const { name } = payload
- return name
- }),
- filter(name => !!name),
- withLatestFrom(viewerState$.pipe(
- select('parcellationSelected'),
- )),
- filter(([name, parcellationSelected]) => {
- if (parcellationSelected && parcellationSelected.name === name) { return false }
- return true
- }),
- map(([name]) => name),
+ /**
+ * merge all sources of selecting parcellation into parcellation id
+ */
+ this.selectParcellation$ = merge(
+
+ this.actions$.pipe(
+ ofType(viewerStateSelectParcellationWithId.type),
+ map(({ payload }) => payload['@id'])
+ ),
+
+ /**
+ * deprecated method...
+ * finding id from name
+ */
+ this.actions$.pipe(
+ ofType(VIEWERSTATE_CONTROLLER_ACTION_TYPES.SELECT_PARCELLATION_WITH_NAME),
+ withLatestFrom(viewerState$.pipe(
+ select('templateSelected')
+ )),
+ map(([ action, templateSelected ]) => {
+ const parcellationName = (action as any).payload.name
+ const foundParcellation = templateSelected.parcellations.find(p => p.name === parcellationName)
+ return foundParcellation && foundParcellation['@id']
+ }),
+ filter(v => !!v)
+ )
+ ).pipe(
+ distinctUntilChanged(),
withLatestFrom(viewerState$.pipe(
select('templateSelected'),
)),
- map(([name, templateSelected]) => {
+ map(([id, templateSelected]) => {
const { parcellations: availableParcellations } = templateSelected
- const newParcellation = availableParcellations.find(t => t.name === name)
+ const newParcellation = availableParcellations.find(t => t['@id'] === id)
if (!newParcellation) {
return {
type: GENERAL_ACTION_TYPES.ERROR,
@@ -119,56 +130,76 @@ export class ViewerStateControllerUseEffect implements OnInit, OnDestroy {
type: SELECT_PARCELLATION,
selectParcellation: newParcellation,
}
- }),
+ })
)
- this.selectTemplateWithName$ = this.actions$.pipe(
- ofType(VIEWERSTATE_CONTROLLER_ACTION_TYPES.SELECT_TEMPLATE_WITH_NAME),
- map(action => {
- const { payload = {} } = action as ViewerStateAction
- const { name } = payload
- return name
- }),
- filter(name => !!name),
+ /**
+ * merge all sources into single stream consisting of template id's
+ */
+ this.selectTemplate$ = merge(
+ this.actions$.pipe(
+ ofType(viewerStateSelectTemplateWithId.type),
+ map(({ payload }) => payload['@id'])
+ ),
+ this.actions$.pipe(
+ ofType(VIEWERSTATE_CONTROLLER_ACTION_TYPES.SELECT_TEMPLATE_WITH_NAME),
+ withLatestFrom(viewerState$.pipe(
+ select('fetchedTemplates')
+ )),
+ map(([ action, fetchedTemplates ]) => {
+ const templateName = (action as any).payload.name
+ const foundTemplate = fetchedTemplates.find(t => t.name === templateName)
+ return foundTemplate && foundTemplate['@id']
+ }),
+ filter(v => !!v)
+ )
+ ).pipe(
+
withLatestFrom(
viewerState$
),
- switchMap(([newTemplateName, { templateSelected, fetchedTemplates, navigation }]) => {
+ switchMap(([newTemplateId, { templateSelected, fetchedTemplates, navigation, parcellationSelected }]) => {
if (!templateSelected) {
return of({
- newTemplateName,
+ newTemplateId,
templateSelected: templateSelected,
fetchedTemplates,
translatedCoordinate: null,
- navigation
+ navigation,
+ parcellationSelected
})
}
const position = (navigation && navigation.position) || [0, 0, 0]
- if (newTemplateName === templateSelected.name) return of(null)
+ if (newTemplateId === templateSelected['@id']) return of(null)
+
+ const newTemplateName = fetchedTemplates.find(t => t['@id'] === newTemplateId).name
+
return this.coordinatesTransformation.getPointCoordinatesForTemplate(templateSelected.name, newTemplateName, position).pipe(
map(({ status, statusText, result }) => {
if (status === 'error') {
return {
- newTemplateName,
+ newTemplateId,
templateSelected: templateSelected,
fetchedTemplates,
translatedCoordinate: null,
- navigation
+ navigation,
+ parcellationSelected
}
}
return {
- newTemplateName,
+ newTemplateId,
templateSelected: templateSelected,
fetchedTemplates,
translatedCoordinate: result,
- navigation
+ navigation,
+ parcellationSelected
}
})
)
}),
filter(v => !!v),
- map(({ newTemplateName, templateSelected, fetchedTemplates, translatedCoordinate, navigation }) => {
- const newTemplateTobeSelected = fetchedTemplates.find(t => t.name === newTemplateName)
+ map(({ newTemplateId, templateSelected, fetchedTemplates, translatedCoordinate, navigation, parcellationSelected }) => {
+ const newTemplateTobeSelected = fetchedTemplates.find(t => t['@id'] === newTemplateId)
if (!newTemplateTobeSelected) {
return {
type: GENERAL_ACTION_TYPES.ERROR,
@@ -177,12 +208,16 @@ export class ViewerStateControllerUseEffect implements OnInit, OnDestroy {
},
}
}
+
+ const selectParcellationWithTemplate = newTemplateTobeSelected.parcellations.map(p => p['@id']).includes(parcellationSelected['@id']) ?
+ newTemplateTobeSelected.parcellations[newTemplateTobeSelected.parcellations.findIndex(p => p['@id'] === parcellationSelected['@id'])]
+ : newTemplateTobeSelected.parcellations[0]
if (!translatedCoordinate) {
return {
type: NEWVIEWER,
selectTemplate: newTemplateTobeSelected,
- selectParcellation: newTemplateTobeSelected.parcellations[0],
+ selectParcellation: selectParcellationWithTemplate,
}
}
const deepCopiedState = JSON.parse(JSON.stringify(newTemplateTobeSelected))
diff --git a/src/util/directives/captureClickListener.directive.ts b/src/util/directives/captureClickListener.directive.ts
index c432afde0a1b8aac18960e1e4a05b3cc509269ed..b8e04e77f862b0841fb23cac3b801b494a729809 100644
--- a/src/util/directives/captureClickListener.directive.ts
+++ b/src/util/directives/captureClickListener.directive.ts
@@ -1,6 +1,6 @@
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output, Input, Inject } from "@angular/core";
import { fromEvent, Subscription } from "rxjs";
-import { switchMapTo, takeUntil, filter } from "rxjs/operators";
+import { switchMapTo, takeUntil } from "rxjs/operators";
import { DOCUMENT } from "@angular/common";
@Directive({
@@ -12,7 +12,7 @@ export class CaptureClickListenerDirective implements OnInit, OnDestroy {
@Input('iav-captureClickListenerDirective-captureDocument') captureDocument: boolean = false
private subscriptions: Subscription[] = []
- @Output('iav-captureClickListenerDirective-onClick') public mapClicked: EventEmitter<any> = new EventEmitter()
+ @Output('iav-captureClickListenerDirective-onUnmovedClick') public mapClicked: EventEmitter<any> = new EventEmitter()
@Output('iav-captureClickListenerDirective-onMousedown') public mouseDownEmitter: EventEmitter<any> = new EventEmitter()
constructor(
@@ -27,9 +27,9 @@ export class CaptureClickListenerDirective implements OnInit, OnDestroy {
}
public ngOnInit() {
- const mouseDownObs$ = fromEvent(this.element, 'mousedown', { capture: true })
- const mouseMoveObs$ = fromEvent(this.element, 'mousemove', { capture: true })
- const mouseUpObs$ = fromEvent(this.element, 'mouseup', { capture: true })
+ const mouseDownObs$ = fromEvent(this.element, 'mousedown', { capture: this.captureDocument })
+ const mouseMoveObs$ = fromEvent(this.element, 'mousemove', { capture: this.captureDocument })
+ const mouseUpObs$ = fromEvent(this.element, 'mouseup', { capture: this.captureDocument })
this.subscriptions.push(
mouseDownObs$.subscribe(event => {
diff --git a/src/util/directives/clickOutside.directive.ts b/src/util/directives/clickOutside.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1a845ee56b1cdeef625305d1fa2ba6a04f2dd8ac
--- /dev/null
+++ b/src/util/directives/clickOutside.directive.ts
@@ -0,0 +1,20 @@
+import { Directive, ElementRef, HostListener, Output, EventEmitter } from '@angular/core';
+
+@Directive({
+ selector: '[iav-outsideClick]'
+})
+export class ClickOutsideDirective {
+
+ constructor(private elementRef: ElementRef) { }
+
+ @Output('iav-outsideClick')
+ clickOutside: EventEmitter<any> = new EventEmitter()
+
+ @HostListener('document:click', ['$event.target'])
+ onMouseClick(targetElement) {
+ const clickedInside = this.elementRef.nativeElement.contains(targetElement)
+ if (!clickedInside) {
+ this.clickOutside.emit()
+ }
+ }
+}
diff --git a/src/util/pipes/filterNull.pipe.ts b/src/util/pipes/filterNull.pipe.ts
index 4ee371f340ff9aa07192fdc7f56d8fb92db19bb0..4bbde2567d3f76f550382425264374134571ba0e 100644
--- a/src/util/pipes/filterNull.pipe.ts
+++ b/src/util/pipes/filterNull.pipe.ts
@@ -5,7 +5,7 @@ import { Pipe, PipeTransform } from "@angular/core";
})
export class FilterNullPipe implements PipeTransform {
- public transform(arr: any[]) {
- return (arr && arr.filter(obj => obj !== null)) || []
+ public transform(arr: any[], fn?: (item: any) => boolean) {
+ return (arr && arr.filter(obj => fn ? fn(obj) : obj !== null)) || []
}
}
diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts
index 0e0f2a21b4abf73f4faabcd3598c32bdffdb8712..25668f675b7822edc9ea061ee3ed1b9e9d29b3e5 100644
--- a/src/util/pureConstant.service.ts
+++ b/src/util/pureConstant.service.ts
@@ -15,6 +15,7 @@ export class PureContantService implements OnDestroy{
public useTouchUI$: Observable<boolean>
public fetchedAtlases$: Observable<any>
+ public darktheme$: Observable<boolean>
public totalAtlasesLength: number
@@ -25,8 +26,12 @@ export class PureContantService implements OnDestroy{
constructor(
private store: Store<any>,
- private http: HttpClient
+ private http: HttpClient,
){
+ this.darktheme$ = this.store.pipe(
+ select(state => state?.viewerState?.templateSelected?.useTheme === 'dark')
+ )
+
this.useTouchUI$ = this.store.pipe(
select(this.useTouchUiSelector),
shareReplay(1)
@@ -37,7 +42,11 @@ export class PureContantService implements OnDestroy{
filter(v => !!v),
tap((arr: any[]) => this.totalAtlasesLength = arr.length),
switchMap(atlases => merge(
- ...atlases.map(({ url }) => this.http.get(`${BACKENDURL.replace(/\/$/, '')}/${url}`, { responseType: 'json' }))
+ ...atlases.map(({ url }) => this.http.get(
+ /^http/.test(url)
+ ? url
+ : `${BACKENDURL.replace(/\/$/, '')}/${url}`,
+ { responseType: 'json' }))
)),
scan((acc, curr) => acc.concat(curr), []),
shareReplay(1)
diff --git a/src/util/util.module.ts b/src/util/util.module.ts
index 3649ee9aac8708c8201e2e10a3a996f173c4f2c5..74bea90caf7c10bd18596c596344e03b4f94a490 100644
--- a/src/util/util.module.ts
+++ b/src/util/util.module.ts
@@ -14,6 +14,7 @@ import { SwitchDirective } from "./directives/switch.directive";
import { MediaQueryDirective } from './directives/mediaQuery.directive'
import { LayoutModule } from "@angular/cdk/layout";
import { MapToPropertyPipe } from "./pipes/mapToProperty.pipe";
+import {ClickOutsideDirective} from "src/util/directives/clickOutside.directive";
@NgModule({
imports:[
@@ -32,7 +33,8 @@ import { MapToPropertyPipe } from "./pipes/mapToProperty.pipe";
NmToMm,
SwitchDirective,
MediaQueryDirective,
- MapToPropertyPipe
+ MapToPropertyPipe,
+ ClickOutsideDirective
],
exports: [
FilterNullPipe,
@@ -47,7 +49,8 @@ import { MapToPropertyPipe } from "./pipes/mapToProperty.pipe";
NmToMm,
SwitchDirective,
MediaQueryDirective,
- MapToPropertyPipe
+ MapToPropertyPipe,
+ ClickOutsideDirective
]
})
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 843526a32106b0c44067b2f21c9701f00c99a29c..295f6b6846c87d1b3e7be1b46a8de83e8782459b 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -13,7 +13,6 @@ declare module '*.md'
declare var VERSION : string
declare var PRODUCTION: boolean
declare var BACKEND_URL: string
-declare var USE_LOGO: string
declare var DATASET_PREVIEW_URL: string
declare var MATOMO_URL: string
declare var MATOMO_ID: string
diff --git a/webpack.staticassets.js b/webpack.staticassets.js
index c06e675fa6504c9e72adf7e3ecdb9b7169575a7d..d88a1ff8b7d973dc8f78b5af0f738dc56f1ba60e 100644
--- a/webpack.staticassets.js
+++ b/webpack.staticassets.js
@@ -70,7 +70,6 @@ module.exports = {
SPATIAL_TRANSFORM_BACKEND: JSON.stringify(process.env.SPATIAL_TRANSFORM_BACKEND || 'https://hbp-spatial-backend.apps.hbp.eu'),
MATOMO_URL: JSON.stringify(process.env.MATOMO_URL || null),
MATOMO_ID: JSON.stringify(process.env.MATOMO_ID || null),
- USE_LOGO: JSON.stringify(process.env.USE_LOGO || 'hbp' || 'ebrains' || 'fzj'),
// strick local hides "explore" and "download" btns, which requires internet
STRICT_LOCAL: process.env.STRICT_LOCAL === 'true' ? 'true' : 'false',