From 5e7505a65c4e91927b3dd7b6b1569fc5be05bd2c Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Tue, 31 May 2022 10:57:26 +0200 Subject: [PATCH] feat: allow plugin to add and remove custom layer bugfix: allow layer transparency to be modified via ng custom layer --- deploy/plugins/index.js | 66 ++++++++++--------- src/api/service.ts | 46 ++++++++++++- src/plugin/README.md | 20 ++++++ src/plugin/request.md | 45 +++++++++++++ src/plugin/tsUtil.js | 1 + src/state/atlasSelection/effects.ts | 4 +- .../layerCtrl.service/layerCtrl.service.ts | 25 ++++++- .../nehubaViewer/nehubaViewer.component.ts | 10 ++- 8 files changed, 181 insertions(+), 36 deletions(-) diff --git a/deploy/plugins/index.js b/deploy/plugins/index.js index e7d2aafd6..6089e2fe9 100644 --- a/deploy/plugins/index.js +++ b/deploy/plugins/index.js @@ -34,38 +34,42 @@ const getKey = url => `plugin:manifest-cache:${url}` router.get('/manifests', async (_req, res) => { - const allManifests = await Promise.all([ - ...V2_7_PLUGIN_URLS, - ...V2_7_STAGING_PLUGIN_URLS - ].map(async url => { - const key = getKey(url) - - await lruStore._initPr - const { store } = lruStore + const allManifests = await Promise.all( + [...V2_7_PLUGIN_URLS, ...V2_7_STAGING_PLUGIN_URLS].map(async url => + race( + async () => { + const key = getKey(url) + + await lruStore._initPr + const { store } = lruStore + + try { + const storedManifest = await store.get(key) + if (storedManifest) return JSON.parse(storedManifest) + else throw `not found` + } catch (e) { + const resp = await got(url) + const json = JSON.parse(resp.body) - try { - const storedManifest = await store.get(key) - if (storedManifest) return JSON.parse(storedManifest) - else throw `not found` - } catch (e) { - const resp = await got(url) - const json = JSON.parse(resp.body) - - const { iframeUrl, 'siibra-explorer': flag } = json - if (!flag) return null - if (!iframeUrl) return null - const u = new URL(url) - - let replaceObj = {} - if (!/^https?:\/\//.test(iframeUrl)) { - u.pathname = path.resolve(path.dirname(u.pathname), iframeUrl) - replaceObj['iframeUrl'] = u.toString() - } - const returnObj = {...json, ...replaceObj} - await store.set(key, JSON.stringify(returnObj), { maxAge: 1000 * 60 * 60 }) - return returnObj - } - })) + const { iframeUrl, 'siibra-explorer': flag } = json + if (!flag) return null + if (!iframeUrl) return null + const u = new URL(url) + + let replaceObj = {} + if (!/^https?:\/\//.test(iframeUrl)) { + u.pathname = path.resolve(path.dirname(u.pathname), iframeUrl) + replaceObj['iframeUrl'] = u.toString() + } + const returnObj = {...json, ...replaceObj} + await store.set(key, JSON.stringify(returnObj), { maxAge: 1000 * 60 * 60 }) + return returnObj + } + }, + { timeout: 1000 } + ) + ) + ) res.status(200).json( [...V2_7_DEV_PLUGINS, ...allManifests.filter(v => !!v)] diff --git a/src/api/service.ts b/src/api/service.ts index 41b100f78..67269b960 100644 --- a/src/api/service.ts +++ b/src/api/service.ts @@ -4,7 +4,7 @@ import { Subject } from "rxjs"; import { distinctUntilChanged, filter, map, take } from "rxjs/operators"; import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel, OpenMINDSCoordinatePoint } from "src/atlasComponents/sapi"; import { SxplrCoordinatePointExtension } from "src/atlasComponents/sapi/type"; -import { MainState, atlasSelection, userInteraction, annotation } from "src/state" +import { MainState, atlasSelection, userInteraction, annotation, atlasAppearance } from "src/state" import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; import { CANCELLABLE_DIALOG, CANCELLABLE_DIALOG_OPTS } from "src/util/interfaces"; import { Booth, BoothResponder, createBroadcastingJsonRpcChannel, JRPCRequest, JRPCResp } from "./jsonrpc" @@ -13,6 +13,8 @@ export type NAMESPACE_TYPE = "sxplr" export const namespace: NAMESPACE_TYPE = "sxplr" const nameSpaceRegex = new RegExp(`^${namespace}`) +type AddableLayer = atlasAppearance.NgLayerCustomLayer + type AtId = { "@id": string } @@ -87,6 +89,27 @@ export type ApiBoothEvents = { response: 'OK' } + loadLayers: { + request: { + layers: AddableLayer[] + } + response: 'OK' + } + + updateLayers: { + request: { + layers: AddableLayer[] + } + response: 'OK' + } + + removeLayers: { + request: { + layers: {id: string}[] + } + response: 'OK' + } + exit: { request: { requests: JRPCRequest<keyof ApiBoothEvents, ApiBoothEvents[keyof ApiBoothEvents]['request']>[] @@ -441,6 +464,27 @@ export class ApiService implements BoothResponder<ApiBoothEvents>{ } break } + case 'loadLayers': + case 'updateLayers': { + const { layers } = event.params as ApiBoothEvents['loadLayers']['request'] | ApiBoothEvents['updateLayers']['request'] + for (const layer of layers) { + this.store.dispatch( + atlasAppearance.actions.addCustomLayer({ + customLayer: layer + }) + ) + } + break + } + case 'removeLayers': { + const { layers } = event.params as ApiBoothEvents['removeLayers']['request'] + for (const layer of layers) { + this.store.dispatch( + atlasAppearance.actions.removeCustomLayer(layer) + ) + } + break + } case 'exit': { const { requests } = event.params as ApiBoothEvents['exit']['request'] for (const req of requests) { diff --git a/src/plugin/README.md b/src/plugin/README.md index 260bd7db5..8656ab315 100644 --- a/src/plugin/README.md +++ b/src/plugin/README.md @@ -6,6 +6,26 @@ siibra-explorer provides a plugin system, which allow a third party application ## Quickstart +### manifest + +The plugin need to expose a manifest json file. The manifest file needs to have the following properties: + +```json +{ + "iframeUrl": "<iframeUrl>", + "name": "<name>", + "siibra-explorer": "<siibra-explorer>" +} +``` + +| property | required | desc | +| --- | --- | --- | +| `iframeUrl` | true | points to the html where the iframe is located. If does not start with `https?://`, siibra-explorer will try to resolve it relative to the absolute path of manifest. | +| `name` | true | name of the plugin | +| `siibra-explorer` | true | the version siibra-explorer this plugin is targetting. Should be >= 2.7.0. n.b. currently this entry is partially implemented, and any truthy value is sufficient. + | + + <!-- TBD --> ## Architecture diff --git a/src/plugin/request.md b/src/plugin/request.md index a2575863a..487c1344f 100644 --- a/src/plugin/request.md +++ b/src/plugin/request.md @@ -188,6 +188,51 @@ window.addEventListener('pagehide', () => { ``` +### `sxplr.loadLayers` + +- request + + ```ts + {"layers": AddableLayer[]} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.updateLayers` + +- request + + ```ts + {"layers": AddableLayer[]} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.removeLayers` + +- request + + ```ts + {"layers": {"id": string}} + ``` + +- response + + ```ts + 'OK' + ``` + + ### `sxplr.exit` - request diff --git a/src/plugin/tsUtil.js b/src/plugin/tsUtil.js index b27c5c973..ea0040ad8 100644 --- a/src/plugin/tsUtil.js +++ b/src/plugin/tsUtil.js @@ -14,6 +14,7 @@ function getTypeText(node){ return node.typeName.text } case "ArrayType": { + if (!node.elementType.typeName) return getTypeText(node.elementType) return `${node.elementType.typeName.text}[]` } case "TypeLiteral": { diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts index 0ef6018a2..e134e7d4b 100644 --- a/src/state/atlasSelection/effects.ts +++ b/src/state/atlasSelection/effects.ts @@ -50,8 +50,8 @@ export class Effect { ) }, ({ current, previous }) => { - const prevSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(previous.template["@id"]) - const currSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(current.template["@id"]) + const prevSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(previous?.template?.["@id"]) + const currSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(current?.template?.["@id"]) /** * if either space name is undefined, return default state for navigation */ diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index 7cd16eb37..ba8b1d0ad 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -1,7 +1,7 @@ import { Injectable, OnDestroy } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { combineLatest, merge, Observable, Subject, Subscription } from "rxjs"; -import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, switchMap, withLatestFrom } from "rxjs/operators"; +import { debounceTime, distinctUntilChanged, filter, map, pairwise, shareReplay, startWith, switchMap, withLatestFrom } from "rxjs/operators"; import { IColorMap, INgLayerCtrl, TNgLayerCtrl } from "./layerCtrl.util"; import { SAPIRegion } from "src/atlasComponents/sapi/core"; import { getParcNgId } from "../config.service" @@ -276,6 +276,16 @@ export class NehubaLayerControlService implements OnDestroy{ private ngLayersRegister: atlasAppearance.NgLayerCustomLayer[] = [] + private updateCustomLayerTransparency$ = this.store$.pipe( + select(atlasAppearance.selectors.customLayers), + map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]), + pairwise(), + map(([ oldCustomLayers, newCustomLayers ]) => { + return newCustomLayers.filter(({ id, opacity }) => oldCustomLayers.some(({ id: oldId, opacity: oldOpacity }) => oldId === id && oldOpacity !== opacity)) + }), + filter(arr => arr.length > 0) + ) + private ngLayers$ = this.customLayers$.pipe( map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]), distinctUntilChanged( @@ -324,6 +334,19 @@ export class NehubaLayerControlService implements OnDestroy{ } as TNgLayerCtrl<'remove'> }) ), + this.updateCustomLayerTransparency$.pipe( + map(layers => { + const payload: Record<string, number> = {} + for (const layer of layers) { + const opacity = layer.opacity ?? 0.8 + payload[layer.id] = opacity + } + return { + type: 'setLayerTransparency', + payload + } as TNgLayerCtrl<'setLayerTransparency'> + }) + ), this.manualNgLayersControl$, ).pipe( ) diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index 633778743..cfd902f1a 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -785,7 +785,15 @@ export class NehubaViewerUnit implements OnDestroy { private setLayerTransparency(layerName: string, alpha: number) { const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName) if (!layer) return - layer.layer.displayState.objectAlpha.restoreState(alpha) + + /** + * for segmentation layer + */ + if (layer.layer.displayState) layer.layer.displayState.objectAlpha.restoreState(alpha) + /** + * for image layer + */ + if (layer.layer.opacity) layer.layer.opacity.restoreState(alpha) } public setMeshTransparency(flag: boolean){ -- GitLab