diff --git a/docs/releases/v2.14.4.md b/docs/releases/v2.14.4.md index 09ba75e4890ffe86a72f422ef1290d0da532b3b2..c6ba74a276d284dd2f32a7b2d115ebe2f04c0566 100644 --- a/docs/releases/v2.14.4.md +++ b/docs/releases/v2.14.4.md @@ -4,6 +4,7 @@ - Sagittal view of perspective PiP changes hemisphere when user navigates to the otehr hemisphere - Adds an additional degree adjustment to perspective PiP +- Encode maximised panel state ## Behind the Scenes diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts index 3e1b4b110af41530ae2a154a49503e17f71038ba..63e326041b9f6402a0798e24b3f22a246f84745f 100644 --- a/src/routerModule/routeStateTransform.service.ts +++ b/src/routerModule/routeStateTransform.service.ts @@ -4,13 +4,20 @@ import { map } from "rxjs/operators"; import { SAPI } from "src/atlasComponents/sapi"; import { translateV3Entities } from "src/atlasComponents/sapi/translateV3"; import { SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes" -import { atlasSelection, defaultState, MainState, plugins, userInteraction } from "src/state"; +import { atlasAppearance, atlasSelection, defaultState, MainState, plugins, userInteraction, userInterface } from "src/state"; import { decodeToNumber, encodeNumber, encodeURIFull, separator } from "./cipher"; import { TUrlAtlas, TUrlPathObj, TUrlStandaloneVolume } from "./type"; import { decodePath, encodeId, decodeId, encodePath } from "./util"; -import { QuickHash } from "src/util/fn"; +import { QuickHash, decodeBool, encodeBool, isNullish } from "src/util/fn"; import { NEHUBA_CONFIG_SERVICE_TOKEN, NehubaConfigSvc } from "src/viewerModule/nehuba/config.service"; +type ViewerConfigState = { + panelMode: userInterface.PanelMode + panelOrder: string + octantRemoval: boolean + showDelineation: boolean +} + @Injectable() export class RouteStateTransformSvc { @@ -181,7 +188,22 @@ export class RouteStateTransformSvc { console.error(`cannot parse navigation state`, e) } } + const viewerConfigState = returnObj['vs'] && returnObj['vs'][0] + if (viewerConfigState) { + + const { panelMode, panelOrder } = !!viewerConfigState + ? this.decodeMiscState(viewerConfigState) + : { panelMode: "FOUR_PANEL" as const, panelOrder: "0123" } + + returnState['[state.ui]'].panelMode = panelMode + returnState['[state.ui]'].panelOrder = panelOrder + // TODO restoring octant removal and hidedelination is still quite buggy + // enable in future update + // returnState["[state.atlasAppearance]"].showDelineation = showDelineation + // returnState["[state.atlasAppearance]"].octantRemoval = octantRemoval + + } // pluginState should always be defined, regardless if standalone volume or not const pluginStates = fullPath.queryParams['pl'] if (pluginStates) { @@ -245,6 +267,74 @@ export class RouteStateTransformSvc { return returnState } + public stateVersion = 'v1' + + encodeMiscState(config: ViewerConfigState): string { + const { panelMode, panelOrder, octantRemoval, showDelineation } = config + if (isNullish(panelOrder) && isNullish(panelMode)) { + return null + } + let panelModeVal = 1 + if (panelMode === "PIP_PANEL") { + panelModeVal = 2 + } + let panelOrderVal = 4 + if (("0123".split("")).includes(panelOrder[0])){ + panelOrderVal = parseInt(panelOrder[0]) + } + const encodedBools = encodeBool(octantRemoval, showDelineation) + const array = new Uint8Array([ + panelModeVal, + panelOrderVal, + encodedBools + ]) + const encodedVal = window.btoa(new Uint8Array(array.buffer).reduce((data, v) => data + String.fromCharCode(v), '')) + return `${this.stateVersion}-${encodedVal}` + } + + decodeMiscState(val: string){ + if (!val.startsWith(`${this.stateVersion}-`)) { + throw new Error(`version must start with "${this.stateVersion}-"`) + } + const trimStart = new RegExp(`^${this.stateVersion}-`) + const encodedVal = val.replace(trimStart, "") + const array = Uint8Array.from(window.atob(encodedVal), v => v.charCodeAt(0)) + + let returnVal: Partial<ViewerConfigState> = {} + + const panelModeVal = array[0] + if (panelModeVal === 1) { + returnVal.panelMode = "FOUR_PANEL" + } else if (panelModeVal) { + returnVal.panelMode = "PIP_PANEL" + } else { + console.warn(`panelmode set to ${panelModeVal}, which is unknown, set to default (4 panel)`) + returnVal.panelMode = "FOUR_PANEL" + } + + let panelOrderVal = array[1] + let panelOrder = "" + while (panelOrder.length < 4) { + panelOrder += `${panelOrderVal % 4}` + panelOrderVal ++ + } + if ( + panelOrder.split("").some( + v => !("0123".split("")).includes(v) + ) + ) { + console.warn(`panelOrder=${panelOrder} contains strings that is not in 0123, set to default (0123)`) + panelOrder = "0123" + } + returnVal.panelOrder = panelOrder + + const [ octantRemoval, showDelineation ] = decodeBool(array[2]) + returnVal.octantRemoval = octantRemoval + returnVal.showDelineation = showDelineation + + return returnVal + } + async cvtStateToRoute(_state: MainState) { /** @@ -262,6 +352,11 @@ export class RouteStateTransformSvc { const navigation = atlasSelection.selectors.navigation(state) const selectedFeature = userInteraction.selectors.selectedFeature(state) + const panelMode = userInterface.selectors.panelMode(state) + const panelOrder = userInterface.selectors.panelOrder(state) + const octantRemoval = atlasAppearance.selectors.octantRemoval(state) + const showDelineation = !(atlasAppearance.selectors.showDelineation(state)) + const searchParam = new URLSearchParams() let cNavString: string @@ -301,7 +396,8 @@ export class RouteStateTransformSvc { .replace(/:/g, '~') ) ) - })() + })(), + vs: this.encodeMiscState({ octantRemoval, panelMode, panelOrder, showDelineation }) } /** diff --git a/src/routerModule/type.ts b/src/routerModule/type.ts index 284b5e7a9441fd52be9281ad3dec5c749e390993..da828b1e6a77b25e9bfecdd8c2aee45e15bfce7a 100644 --- a/src/routerModule/type.ts +++ b/src/routerModule/type.ts @@ -16,6 +16,7 @@ export type TUrlPlugin<T> = { export type TUrlNav<T> = { ['@']: T // navstring + vs?: T } export type TUrlViewFeat<T> = { diff --git a/src/state/userInterface/store.ts b/src/state/userInterface/store.ts index cc854e820d61e94cbee9b5b95f988dbe9db0ec58..321e92c8806bd08930a59ef4a8c6a2e6cfac36f6 100644 --- a/src/state/userInterface/store.ts +++ b/src/state/userInterface/store.ts @@ -5,15 +5,11 @@ import { PanelMode } from "./const" export type UiStore = { panelMode: PanelMode panelOrder: string // permutation of 0123 - octantRemoval: boolean - showDelineation: boolean } export const defaultState: UiStore = { - panelMode: 'FOUR_PANEL', - panelOrder: '0123', - octantRemoval: false, - showDelineation: true, + panelMode: null, + panelOrder: null, } export const reducer = createReducer( diff --git a/src/util/fn.ts b/src/util/fn.ts index 5a041b905ba4e7c1b0298c4a23e23de48993a65f..86b7ecf6236cfb0acf7f68d23e42e08f9dd587e3 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -463,7 +463,15 @@ type TGetShaderCfg = { opacity: number } -function encodeBool(...flags: boolean[]) { +export function decodeBool(num: number) { + const rBool: boolean[] = [] + for (let i = 0; i < 8; i ++) { + rBool.push( ((num >> i) & 1) === 1 ) + } + return rBool +} + +export function encodeBool(...flags: boolean[]) { if (flags.length > 8) { throw new Error(`encodeBool can only handle upto 8 bools`) } diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts index 118031cc0806bb692dead3bff0f7811adae32759..4abf5d72a4a3cbb34a0e52e024729a7f6845b19b 100644 --- a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts +++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts @@ -1,13 +1,14 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, fromEvent, interval, merge, Observable, of, Subject, Subscription } from "rxjs"; +import { combineLatest, fromEvent, merge, Observable, of, Subject, Subscription } from "rxjs"; import { userInterface } from "src/state"; import { NehubaViewerUnit } from "../../nehubaViewer/nehubaViewer.component"; import { NEHUBA_INSTANCE_INJTKN, takeOnePipe, getFourPanel, getHorizontalOneThree, getSinglePanel, getPipPanel, getVerticalOneThree } from "../../util"; import { QUICKTOUR_DESC, QUICKTOUR_DESC_MD, ARIA_LABELS, IDS } from 'common/constants' import { IQuickTourData } from "src/ui/quickTour/constrants"; -import { debounce, debounceTime, distinctUntilChanged, filter, map, mapTo, switchMap, take } from "rxjs/operators"; +import { debounceTime, distinctUntilChanged, map, mapTo, switchMap, take } from "rxjs/operators"; import {panelOrder} from "src/state/userInterface/selectors"; +import { switchMapWaitFor } from "src/util/fn"; @Component({ selector: `nehuba-layout-overlay`, @@ -104,17 +105,20 @@ export class NehubaLayoutOverlay implements OnDestroy{ } public panelMode$ = this.store$.pipe( - select(userInterface.selectors.panelMode) + select(userInterface.selectors.panelMode), + map(v => v || "FOUR_PANEL") ) public panelOrder$ = this.store$.pipe( select(userInterface.selectors.panelOrder), + map(v => v || "0123") ) public volumeChunkLoading$: Subject<boolean> = new Subject() public showPipPerspectiveView$ = this.store$.pipe( select(panelOrder), + map(v => v || "0123"), distinctUntilChanged(), map(po => po[0] !== '3') ) @@ -231,13 +235,12 @@ export class NehubaLayoutOverlay implements OnDestroy{ this.panelMode$, this.panelOrder$, ]).pipe( - debounce(() => - nehubaUnit?.nehubaViewer?.ngviewer - ? of(true) - : interval(16).pipe( - filter(() => nehubaUnit?.nehubaViewer?.ngviewer), - take(1) - ) + switchMap( + switchMapWaitFor({ + leading: true, + interval: 16, + condition: () => "0123".split("").every(v => !!this.nehubaViewPanels[Number(v)]) + }) ) ).subscribe(([mode, panelOrder]) => { @@ -246,13 +249,6 @@ export class NehubaLayoutOverlay implements OnDestroy{ const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.nehubaViewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] - /** - * TODO smarter with event stream - */ - if (!viewPanels.every(v => !!v)) { - return - } - switch (this.currentPanelMode) { case "H_ONE_THREE": { const element = removeExistingPanels() diff --git a/src/viewerModule/nehuba/viewerCtrl/effects.ts b/src/viewerModule/nehuba/viewerCtrl/effects.ts deleted file mode 100644 index 5eeaafed8eaf61c7d03a5ddee0cb55759b39a8e6..0000000000000000000000000000000000000000 --- a/src/viewerModule/nehuba/viewerCtrl/effects.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from "@angular/core"; -import { createEffect } from "@ngrx/effects"; -import { Store } from "@ngrx/store"; -import { of } from "rxjs"; -import { switchMap } from "rxjs/operators"; -import { atlasSelection, userInterface } from "src/state"; - -@Injectable() -export class ViewerCtrlEffects { - onTemplateChangeResetLayout$ = createEffect(() => this.store$.pipe( - atlasSelection.fromRootStore.distinctATP(), - switchMap(() => of( - userInterface.actions.setPanelMode({ - panelMode: "FOUR_PANEL" - }), - userInterface.actions.setPanelOrder({ - order: '0123' - }), - )) - )) - - constructor(private store$: Store){} -} diff --git a/src/viewerModule/nehuba/viewerCtrl/module.ts b/src/viewerModule/nehuba/viewerCtrl/module.ts index 082107630ef288fa064e13bb3ec779094ee2abe3..f4af0bbd3fcfdf847bb31a34fa27a66469b80938 100644 --- a/src/viewerModule/nehuba/viewerCtrl/module.ts +++ b/src/viewerModule/nehuba/viewerCtrl/module.ts @@ -8,8 +8,6 @@ import { ViewerCtrlCmp } from "./viewerCtrlCmp/viewerCtrlCmp.component"; import { PerspectiveViewSlider } from "./perspectiveViewSlider/perspectiveViewSlider.component"; import { SnapPerspectiveOrientationCmp } from "src/viewerModule/nehuba/viewerCtrl/snapPerspectiveOrientation/snapPerspectiveOrientation.component"; import { WindowResizeModule } from "src/util/windowResize"; -import { EffectsModule } from "@ngrx/effects"; -import { ViewerCtrlEffects } from "./effects" @NgModule({ imports: [ @@ -20,9 +18,6 @@ import { ViewerCtrlEffects } from "./effects" ReactiveFormsModule, ComponentsModule, WindowResizeModule, - EffectsModule.forFeature([ - ViewerCtrlEffects - ]) ], declarations: [ ViewerCtrlCmp, diff --git a/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts b/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts index b73e082c07a55af21c7f148352956946e4157355..890e01b2eed7242f6249dfc512591ae9c1e6d865 100644 --- a/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts +++ b/src/viewerModule/nehuba/viewerCtrl/perspectiveViewSlider/perspectiveViewSlider.component.ts @@ -15,7 +15,7 @@ import { atlasSelection } from "src/state"; import { floatEquality } from "common/util" import { CURRENT_TEMPLATE_DIM_INFO, TemplateInfo } from "../../layerCtrl.service/layerCtrl.util"; import { DestroyDirective } from "src/util/directives/destroy.directive"; -import { isNullish, isWheelEvent } from "src/util/fn" +import { isNullish, isWheelEvent, switchMapWaitFor } from "src/util/fn" const MAX_DIM = 200 @@ -242,6 +242,11 @@ export class PerspectiveViewSlider { switchMap(templateSize => { return this.rangeControlSetting$.pipe( switchMap(orientation => this.navPosition$.pipe( + switchMap( + switchMapWaitFor({ + condition: nav => !!nav && !!nav.real + }) + ), take(1), map(nav => { if (!nav || !orientation || !templateSize) return null