diff --git a/package.json b/package.json index 491667075a4a27dfec14a0a4693c05ac17bab5d1..a2ed139c5d747a35ef3c5ed5d8db25138d6831cb 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "license": "ISC", "devDependencies": { "@angular/animations": "^7.2.15", + "@angular/cdk": "^7.3.7", "@angular/common": "^7.2.15", "@angular/compiler": "^7.2.15", "@angular/compiler-cli": "^7.2.15", @@ -31,8 +32,10 @@ "@angular/forms": "^7.2.15", "@angular/http": "^7.2.15", "@angular/language-service": "^7.2.15", + "@angular/material": "^7.3.7", "@angular/platform-browser": "^7.2.15", "@angular/platform-browser-dynamic": "^7.2.15", + "@angular/router": "^7.2.15", "@ngrx/effects": "^7.4.0", "@ngrx/store": "^6.0.1", "@ngtools/webpack": "^6.0.5", @@ -47,6 +50,7 @@ "core-js": "^3.0.1", "css-loader": "^3.2.0", "file-loader": "^1.1.11", + "hammerjs": "^2.0.8", "html-webpack-plugin": "^3.2.0", "jasmine": "^3.1.0", "jasmine-core": "^3.4.0", @@ -78,11 +82,7 @@ "webpack-closure-compiler": "^2.1.6", "webpack-dev-server": "^3.1.4", "webpack-merge": "^4.1.2", - "@angular/cdk": "^7.3.7", - "@angular/material": "^7.3.7", - "@angular/router": "^7.2.15", "zone.js": "^0.9.1" }, - "dependencies": { - } + "dependencies": {} } diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts index 66d7a3ff527cd84d8c11af520c3bd82cdf3741e3..84f5e30d687124fd8ebc5b457c70929385de1a62 100644 --- a/src/atlasViewer/atlasViewer.pluginService.service.ts +++ b/src/atlasViewer/atlasViewer.pluginService.service.ts @@ -1,14 +1,14 @@ import { Injectable, ViewContainerRef, ComponentFactoryResolver, ComponentFactory } from "@angular/core"; +import { PluginInitManifestInterface, PLUGIN_STATE_ACTION_TYPES } from "src/services/state/pluginState.store"; import { HttpClient } from '@angular/common/http' -import { PluginInitManifestInterface, ACTION_TYPES } from "src/services/state/pluginState.store"; import { isDefined } from 'src/services/stateStore.service' import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service"; import { PluginUnit } from "./pluginUnit/pluginUnit.component"; import { WidgetServices } from "./widgetUnit/widgetService.service"; import '../res/css/plugin_styles.css' -import { interval, BehaviorSubject, Observable, merge, of } from "rxjs"; -import { take, takeUntil, map, shareReplay } from "rxjs/operators"; +import { BehaviorSubject, Observable, merge, of } from "rxjs"; +import { map, shareReplay } from "rxjs/operators"; import { Store } from "@ngrx/store"; import { WidgetUnit } from "./widgetUnit/widgetUnit.component"; import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service"; @@ -93,7 +93,7 @@ export class PluginServices{ this.widgetService.minimisedWindow$ ).pipe( map(set => { - const returnSet = new Set() + const returnSet = new Set<string>() for (let [pluginName, wu] of this.mapPluginNameToWidgetUnit) { if (set.has(wu)) { returnSet.add(pluginName) @@ -217,7 +217,7 @@ export class PluginServices{ : null handler.setInitManifestUrl = (url) => this.store.dispatch({ - type : ACTION_TYPES.SET_INIT_PLUGIN, + type : PLUGIN_STATE_ACTION_TYPES.SET_INIT_PLUGIN, manifest : { name : plugin.name, initManifestUrl : url diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index 1a4876f532732f6415988deb76849f926ab3377a..f8d3522472ea1c736ea56850db0091c8b536d69a 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -78,10 +78,6 @@ <help-component> </help-component> </mat-tab> - <mat-tab label="Settings"> - <config-component> - </config-component> - </mat-tab> <mat-tab label="Privacy Policy"> <!-- TODO make tab container scrollable --> <cookie-agreement> diff --git a/src/main.module.ts b/src/main.module.ts index e9de0e721ce376f8f19bf89659b86d51f1ccb957..b557d69b45e03f6990b3ea808e6470ba210462e1 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -47,6 +47,8 @@ import { ViewerStateControllerUseEffect } from "./ui/viewerStateController/viewe import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component"; import { ViewerStateUseEffect } from "./services/state/viewerState.store"; +import 'hammerjs' + @NgModule({ imports : [ FormsModule, diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index 65124d1b299482a6debd5e37450293da4391fe49..d5b2c296496bf6c94cd3127c620dcc1812176f36 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -286,6 +286,15 @@ markdown-dom pre code white-space: initial!important; } +.w-5em +{ + width: 5em!important; +} +.w-10em +{ + width: 10em!important; +} + .mw-400px { max-width: 400px!important; @@ -346,9 +355,26 @@ markdown-dom pre code pointer-events: none; } -.h-100 +.h-5em { - height:100%; + height: 5em!important; +} + +.h-7em +{ + height:7em!important; +} +.h-10em +{ + height:10em!important; +} +.h-15em +{ + height:15em!important; +} +.h-20em +{ + height:20em!important; } .overflow-x-hidden diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts index d1dd8b27a79daa1b2a73ad56df959cac71f87636..289489d7e549bd4266aac0a57ffd67c877aa2804 100644 --- a/src/services/state/ngViewerState.store.ts +++ b/src/services/state/ngViewerState.store.ts @@ -1,32 +1,58 @@ import { Action } from '@ngrx/store' +export const FOUR_PANEL = 'FOUR_PANEL' +export const V_ONE_THREE = 'V_ONE_THREE' +export const H_ONE_THREE = 'H_ONE_THREE' +export const SINGLE_PANEL = 'SINGLE_PANEL' + export interface NgViewerStateInterface{ layers : NgLayerInterface[] forceShowSegment : boolean | null nehubaReady: boolean + panelMode: string + panelOrder: string } export interface NgViewerAction extends Action{ layer : NgLayerInterface forceShowSegment : boolean nehubaReady: boolean + payload: any } -const defaultState:NgViewerStateInterface = {layers:[], forceShowSegment:null, nehubaReady: false} +const defaultState:NgViewerStateInterface = { + layers:[], + forceShowSegment:null, + nehubaReady: false, + panelMode: FOUR_PANEL, + panelOrder: `0123` +} export function ngViewerState(prevState:NgViewerStateInterface = defaultState, action:NgViewerAction):NgViewerStateInterface{ switch(action.type){ + case ACTION_TYPES.SET_PANEL_ORDER: { + const { payload } = action + const { panelOrder } = payload + return { + ...prevState, + panelOrder + } + } + case ACTION_TYPES.SWITCH_PANEL_MODE: { + const { payload } = action + const { panelMode } = payload + if (SUPPORTED_PANEL_MODES.indexOf(panelMode) < 0) return prevState + return { + ...prevState, + panelMode + } + } case ADD_NG_LAYER: - return Object.assign({}, prevState, { + return { + ...prevState, + /* this configration hides the layer if a non mixable layer already present */ - layers : action.layer.constructor === Array - ? prevState.layers.concat(action.layer) - : prevState.layers.concat( - Object.assign({}, action.layer, - action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 - ? {visible: false} - : {})) - + /* this configuration does not the addition of multiple non mixable layers */ // layers : action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 // ? prevState.layers @@ -34,39 +60,47 @@ export function ngViewerState(prevState:NgViewerStateInterface = defaultState, a /* this configuration allows the addition of multiple non mixables */ // layers : prevState.layers.map(l => mapLayer(l, action.layer)).concat(action.layer) - }) + layers : action.layer.constructor === Array + ? prevState.layers.concat(action.layer) + : prevState.layers.concat({ + ...action.layer, + ...( action.layer.mixability === 'nonmixable' && prevState.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 + ? {visible: false} + : {}) + }) + } case REMOVE_NG_LAYER: - return Object.assign({}, prevState, { + return { + ...prevState, layers : prevState.layers.filter(l => l.name !== action.layer.name) - } as NgViewerStateInterface) + } case SHOW_NG_LAYER: - return Object.assign({}, prevState, { + return { + ...prevState, layers : prevState.layers.map(l => l.name === action.layer.name - ? Object.assign({}, l, { - visible : true - } as NgLayerInterface) + ? { ...l, visible: true } : l) - }) + } case HIDE_NG_LAYER: - return Object.assign({}, prevState, { + return { + ...prevState, + layers : prevState.layers.map(l => l.name === action.layer.name - ? Object.assign({}, l, { - visible : false - } as NgLayerInterface) + ? { ...l, visible: false } : l) - }) + } case FORCE_SHOW_SEGMENT: - return Object.assign({}, prevState, { + return { + ...prevState, forceShowSegment : action.forceShowSegment - }) as NgViewerStateInterface + } case NEHUBA_READY: const { nehubaReady } = action return { ...prevState, nehubaReady } - default: - return prevState + default: return prevState } } @@ -84,4 +118,19 @@ interface NgLayerInterface{ visible : boolean shader? : string transform? : any -} \ No newline at end of file +} + +const ACTION_TYPES = { + SWITCH_PANEL_MODE: 'SWITCH_PANEL_MODE', + SET_PANEL_ORDER: 'SET_PANEL_ORDER' +} + +export const SUPPORTED_PANEL_MODES = [ + FOUR_PANEL, + H_ONE_THREE, + V_ONE_THREE, + SINGLE_PANEL, +] + + +export const NG_VIEWER_ACTION_TYPES = ACTION_TYPES \ No newline at end of file diff --git a/src/services/state/pluginState.store.ts b/src/services/state/pluginState.store.ts index 93d6b1e9678128b624fbe146c26afb08f494306b..e9a80cc8d3b180b847ec8e6b86294093e8a7f903 100644 --- a/src/services/state/pluginState.store.ts +++ b/src/services/state/pluginState.store.ts @@ -12,10 +12,12 @@ export interface PluginInitManifestActionInterface extends Action{ } } -export const ACTION_TYPES = { +const ACTION_TYPES = { SET_INIT_PLUGIN: `SET_INIT_PLUGIN` } +export const PLUGIN_STATE_ACTION_TYPES = ACTION_TYPES + export function pluginState(prevState:PluginInitManifestInterface = {initManifests : new Map()}, action:PluginInitManifestActionInterface):PluginInitManifestInterface{ switch(action.type){ case ACTION_TYPES.SET_INIT_PLUGIN: diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts index 12a3a700a6f84bdf71fa77699c0be0b772756385..6f6f0b4f4a06b7110d9481dc9324b3947bb2106d 100644 --- a/src/ui/config/config.component.ts +++ b/src/ui/config/config.component.ts @@ -1,12 +1,14 @@ import { Component, OnInit, OnDestroy } from '@angular/core' import { Store, select } from '@ngrx/store'; import { ViewerConfiguration, ACTION_TYPES } from 'src/services/state/viewerConfig.store' -import { Observable, Subject, Subscription } from 'rxjs'; -import { map, distinctUntilChanged, debounceTime } from 'rxjs/operators'; -import { MatSlideToggleChange } from '@angular/material'; +import { Observable, Subscription } from 'rxjs'; +import { map, distinctUntilChanged, startWith, shareReplay } from 'rxjs/operators'; +import { MatSlideToggleChange, MatSliderChange } from '@angular/material'; +import { NG_VIEWER_ACTION_TYPES, SUPPORTED_PANEL_MODES } from 'src/services/state/ngViewerState.store'; const GPU_TOOLTIP = `GPU TOOLTIP` const ANIMATION_TOOLTIP = `ANIMATION_TOOLTIP` +const ROOT_TEXT_ORDER = ['Coronal', 'Sagittal', 'Axial', '3D'] @Component({ selector: 'config-component', @@ -20,6 +22,7 @@ export class ConfigComponent implements OnInit, OnDestroy{ public GPU_TOOLTIP = GPU_TOOLTIP public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP + public supportedPanelModes = SUPPORTED_PANEL_MODES /** * in MB @@ -27,12 +30,17 @@ export class ConfigComponent implements OnInit, OnDestroy{ public gpuLimit$: Observable<number> public animationFlag$: Observable<boolean> - public keydown$: Subject<Event> = new Subject() private subscriptions: Subscription[] = [] public gpuMin : number = 100 public gpuMax : number = 1000 + + public panelMode$: Observable<string> + private panelOrder: string + private panelOrder$: Observable<string> + public panelTexts$: Observable<[string, string, string, string]> + constructor(private store: Store<ViewerConfiguration>) { this.gpuLimit$ = this.store.pipe( select('viewerConfigState'), @@ -45,24 +53,27 @@ export class ConfigComponent implements OnInit, OnDestroy{ select('viewerConfigState'), map((config:ViewerConfiguration) => config.animation), ) + + this.panelMode$ = this.store.pipe( + select('ngViewerState'), + select('panelMode'), + startWith(SUPPORTED_PANEL_MODES[0]) + ) + + this.panelOrder$ = this.store.pipe( + select('ngViewerState'), + select('panelOrder') + ) + + this.panelTexts$ = this.panelOrder$.pipe( + map(string => string.split('').map(s => Number(s))), + map(arr => arr.map(idx => ROOT_TEXT_ORDER[idx]) as [string, string, string, string]) + ) } ngOnInit(){ this.subscriptions.push( - this.keydown$.pipe( - debounceTime(250) - ).subscribe(ev => { - /** - * maybe greak in FF. ev.srcElement is IE non standard property - */ - const val = (<HTMLInputElement>ev.srcElement).value - const numVal = val && Number(val) - if (isNaN(numVal) || numVal < this.gpuMin || numVal > this.gpuMax ) - return - this.setGpuPreset({ - value: numVal - }) - }) + this.panelOrder$.subscribe(panelOrder => this.panelOrder = panelOrder) ) } @@ -70,14 +81,6 @@ export class ConfigComponent implements OnInit, OnDestroy{ this.subscriptions.forEach(s => s.unsubscribe()) } - public wheelEvent(ev:WheelEvent) { - const delta = ev.deltaY * -1e5 - this.store.dispatch({ - type: ACTION_TYPES.CHANGE_GPU_LIMIT, - payload: { delta } - }) - } - public toggleAnimationFlag(ev: MatSlideToggleChange ){ const { checked } = ev this.store.dispatch({ @@ -88,12 +91,53 @@ export class ConfigComponent implements OnInit, OnDestroy{ }) } - public setGpuPreset({value}: {value: number}) { + public handleMatSliderChange(ev:MatSliderChange){ this.store.dispatch({ type: ACTION_TYPES.UPDATE_CONFIG, config: { - gpuLimit: value * 1e6 + gpuLimit: ev.value * 1e6 } }) } + usePanelMode(panelMode: string){ + this.store.dispatch({ + type: NG_VIEWER_ACTION_TYPES.SWITCH_PANEL_MODE, + payload: { panelMode } + }) + } + + handleDrop(event:DragEvent){ + const droppedAttri = (event.target as HTMLElement).getAttribute('panel-order') + const draggedAttri = event.dataTransfer.getData('text/plain') + if (droppedAttri === draggedAttri) return + const idx1 = Number(droppedAttri) + const idx2 = Number(draggedAttri) + const arr = this.panelOrder.split(''); + + [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]] + this.store.dispatch({ + type: NG_VIEWER_ACTION_TYPES.SET_PANEL_ORDER, + payload: { panelOrder: arr.join('') } + }) + } + handleDragOver(event:DragEvent){ + event.preventDefault() + const target = (event.target as HTMLElement) + target.classList.add('onDragOver') + } + handleDragLeave(event:DragEvent){ + (event.target as HTMLElement).classList.remove('onDragOver') + } + handleDragStart(event:DragEvent){ + const target = (event.target as HTMLElement) + const attri = target.getAttribute('panel-order') + event.dataTransfer.setData('text/plain', attri) + + } + handleDragend(event:DragEvent){ + const target = (event.target as HTMLElement) + target.classList.remove('onDragOver') + } + + public stepSize: number = 10 } \ No newline at end of file diff --git a/src/ui/config/config.style.css b/src/ui/config/config.style.css index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..907391412ce3868cca03f2b38599f2f551238085 100644 --- a/src/ui/config/config.style.css +++ b/src/ui/config/config.style.css @@ -0,0 +1,14 @@ +.config-transition +{ + transition: background-color ease-in-out 200ms; +} + +.config-transition:hover +{ + background-color: rgba(128,128,128,0.1); +} + +.onDragOver +{ + background-color: rgba(128,128,128,0.2); +} \ No newline at end of file diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html index dca3b164d0fbe6bd4235e50e856073835acb13d0..20927120dd97ec7f77e783ae3fcabfbcdc6243b6 100644 --- a/src/ui/config/config.template.html +++ b/src/ui/config/config.template.html @@ -1,42 +1,190 @@ -<div class="input-group mb-2"> - <span class="input-group-prepend"> - <span class="input-group-text"> - GPU Limit - <small tooltipClass="z-1060" [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small> - </span> - </span> - <input - (wheel)="wheelEvent($event)" - type="number" - [min]="100" - [max]="1000" - [step]="0.1" - class="form-control" - (input)="keydown$.next($event)" - [value]="gpuLimit$ | async "> - - <div class="input-group-append"> - - <div (click)="setGpuPreset({ value: 100 })" class="btn btn-outline-secondary"> - 100 +<mat-tab-group> + + <!-- viewer preference --> + <mat-tab label="Viewer Preference"> + + <div class="m-2"> + <div class="mat-h2"> + Rearrange Viewports + </div> + <div class="mat-h4 text-muted"> + Click and drag to rearrange viewport positions + </div> + <current-layout class="d-flex w-20em h-15em p-2"> + <div + matRipple + (dragstart)="handleDragStart($event)" + (dragover)="handleDragOver($event)" + (dragleave)="handleDragLeave($event)" + (dragend)="handleDragend($event)" + (drop)="handleDrop($event)" + class="w-100 h-100 config-transition" + cell-i> + <div + [attr.panel-order]="0" + class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + draggable="true"> + {{ (panelTexts$ | async)[0] }} + </div> + </div> + <div + matRipple + (dragstart)="handleDragStart($event)" + (dragover)="handleDragOver($event)" + (dragleave)="handleDragLeave($event)" + (dragend)="handleDragend($event)" + (drop)="handleDrop($event)" + class="w-100 h-100 config-transition" + cell-ii> + <div + [attr.panel-order]="1" + class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + draggable="true"> + {{ (panelTexts$ | async)[1] }} + </div> + </div> + <div + matRipple + (dragstart)="handleDragStart($event)" + (dragover)="handleDragOver($event)" + (dragleave)="handleDragLeave($event)" + (dragend)="handleDragend($event)" + (drop)="handleDrop($event)" + class="w-100 h-100 config-transition" + cell-iii> + <div + [attr.panel-order]="2" + class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + draggable="true"> + {{ (panelTexts$ | async)[2] }} + </div> + </div> + <div + matRipple + (dragstart)="handleDragStart($event)" + (dragover)="handleDragOver($event)" + (dragleave)="handleDragLeave($event)" + (dragend)="handleDragend($event)" + (drop)="handleDrop($event)" + class="w-100 h-100 config-transition" + cell-iv> + <div + [attr.panel-order]="3" + class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + draggable="true"> + {{ (panelTexts$ | async)[3] }} + </div> + </div> + </current-layout> + + <div class="mat-body text-muted font-italic"> + Plane designation refers to default orientation (without oblique rotation). + </div> + </div> + + <!-- scroll window --> + + <div class="m-2"> + <div class="mat-h2"> + Select a viewports configuration + </div> + </div> + + <div class="d-flex flex-row flex-nowrap p-2"> + + <!-- Four Panel Card --> + <button + class="m-2 p-2" + mat-flat-button + (click)="usePanelMode(supportedPanelModes[0])" + [color]="(panelMode$ | async) === supportedPanelModes[0] ? 'primary' : null"> + <layout-four-panel class="d-block w-10em h-7em"> + <div class="border w-100 h-100" cell-i></div> + <div class="border w-100 h-100" cell-ii></div> + <div class="border w-100 h-100" cell-iii></div> + <div class="border w-100 h-100" cell-iv></div> + </layout-four-panel> + </button> + + <!-- horizontal 1 3 card --> + <button + class="m-2 p-2" + mat-flat-button + (click)="usePanelMode(supportedPanelModes[1])" + [color]="(panelMode$ | async) === supportedPanelModes[1] ? 'primary' : null"> + <layout-horizontal-one-three class="d-block w-10em h-7em"> + <div class="border w-100 h-100" cell-i></div> + <div class="border w-100 h-100" cell-ii></div> + <div class="border w-100 h-100" cell-iii></div> + <div class="border w-100 h-100" cell-iv></div> + </layout-horizontal-one-three> + </button> + + <!-- vertical 1 3 card --> + <button + class="m-2 p-2" + mat-flat-button + (click)="usePanelMode(supportedPanelModes[2])" + [color]="(panelMode$ | async) === supportedPanelModes[2] ? 'primary' : null"> + <layout-vertical-one-three class="d-block w-10em h-7em"> + <div class="border w-100 h-100" cell-i></div> + <div class="border w-100 h-100" cell-ii></div> + <div class="border w-100 h-100" cell-iii></div> + <div class="border w-100 h-100" cell-iv></div> + </layout-vertical-one-three> + </button> + + <!-- single --> + <button + class="m-2 p-2" + mat-flat-button + (click)="usePanelMode(supportedPanelModes[3])" + [color]="(panelMode$ | async) === supportedPanelModes[3] ? 'primary' : null"> + <layout-single-panel class="d-block w-10em h-7em"> + <div class="border w-100 h-100" cell-i></div> + <div class="border w-100 h-100" cell-ii></div> + <div class="border w-100 h-100" cell-iii></div> + <div class="border w-100 h-100" cell-iv></div> + </layout-single-panel> + </button> </div> - <div (click)="setGpuPreset({ value: 500 })" class="btn btn-outline-secondary"> - 500 + </mat-tab> + + <!-- hard ware --> + <mat-tab label="Hardware"> + <div class="d-flex align-items-center"> + <mat-slide-toggle + [checked]="animationFlag$ | async" + (change)="toggleAnimationFlag($event)"> + Enable Animation + </mat-slide-toggle> + <small [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small> </div> - <div (click)="setGpuPreset({ value: 1000 })" class="btn btn-outline-secondary"> - 1000 + <div class="d-flex flex-row align-items-center justify-content start"> + <label + class="m-0 d-inline-block flex-grow-0 flex-shrink-0" + for="gpuLimitSlider"> + GPU Limit + <small [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small> + </label> + <mat-slider + class="flex-grow-1 flex-shrink-1 ml-2 mr-2" + id="gpuLimitSlider" + name="gpuLimitSlider" + thumbLabel="true" + min="100" + max="1000" + [step]="stepSize" + (change)="handleMatSliderChange($event)" + [value]="gpuLimit$ | async"> + </mat-slider> + <span class="d-inline-block flex-grow-0 flex-shrink-0 w-10em"> + {{ gpuLimit$ | async }} MB + </span> </div> - <span class="input-group-text"> - MB - </span> - </div> -</div> - -<div class="d-flex align-items-center"> - <mat-slide-toggle - [checked]="animationFlag$ | async" - (change)="toggleAnimationFlag($event)"> - Enable Animation - </mat-slide-toggle> - <small [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small> -</div> \ No newline at end of file + + + </mat-tab> + +</mat-tab-group> + diff --git a/src/ui/config/currentLayout/currentLayout.component.ts b/src/ui/config/currentLayout/currentLayout.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..df40022f719efb0d9cce84fa1a8bcdfbe6512b06 --- /dev/null +++ b/src/ui/config/currentLayout/currentLayout.component.ts @@ -0,0 +1,27 @@ +import { Component } from "@angular/core"; +import { Store, select } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { SUPPORTED_PANEL_MODES } from "src/services/state/ngViewerState.store"; +import { startWith } from "rxjs/operators"; + +@Component({ + selector: 'current-layout', + templateUrl: './currentLayout.template.html', + styleUrls: [ + './currentLayout.style.css' + ] +}) + +export class CurrentLayout{ + + public supportedPanelModes = SUPPORTED_PANEL_MODES + public panelMode$: Observable<string> + + constructor(private store$: Store<any>){ + this.panelMode$ = this.store$.pipe( + select('ngViewerState'), + select('panelMode'), + startWith(SUPPORTED_PANEL_MODES[0]) + ) + } +} \ No newline at end of file diff --git a/src/ui/config/currentLayout/currentLayout.style.css b/src/ui/config/currentLayout/currentLayout.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/ui/config/currentLayout/currentLayout.template.html b/src/ui/config/currentLayout/currentLayout.template.html new file mode 100644 index 0000000000000000000000000000000000000000..f3d04c104239b9e81f3ff0552c892e86c444ba0d --- /dev/null +++ b/src/ui/config/currentLayout/currentLayout.template.html @@ -0,0 +1,82 @@ +<div [ngSwitch]="panelMode$ | async" class="w-100 h-100 d-flex flex-row"> + <layout-four-panel + *ngSwitchCase="supportedPanelModes[0]" + class="d-block w-100 h-100"> + <div class="w-100 h-100" cell-i> + <ng-content *ngTemplateOutlet="celli"></ng-content> + </div> + <div class="w-100 h-100" cell-ii> + <ng-content *ngTemplateOutlet="cellii"></ng-content> + </div> + <div class="w-100 h-100" cell-iii> + <ng-content *ngTemplateOutlet="celliii"></ng-content> + </div> + <div class="w-100 h-100" cell-iv> + <ng-content *ngTemplateOutlet="celliv"></ng-content> + </div> + </layout-four-panel> + <layout-horizontal-one-three + *ngSwitchCase="supportedPanelModes[1]" + class="d-block w-100 h-100"> + <div class="w-100 h-100" cell-i> + <ng-content *ngTemplateOutlet="celli"></ng-content> + </div> + <div class="w-100 h-100" cell-ii> + <ng-content *ngTemplateOutlet="cellii"></ng-content> + </div> + <div class="w-100 h-100" cell-iii> + <ng-content *ngTemplateOutlet="celliii"></ng-content> + </div> + <div class="w-100 h-100" cell-iv> + <ng-content *ngTemplateOutlet="celliv"></ng-content> + </div> + </layout-horizontal-one-three> + <layout-vertical-one-three + *ngSwitchCase="supportedPanelModes[2]" + class="d-block w-100 h-100"> + <div class="w-100 h-100" cell-i> + <ng-content *ngTemplateOutlet="celli"></ng-content> + </div> + <div class="w-100 h-100" cell-ii> + <ng-content *ngTemplateOutlet="cellii"></ng-content> + </div> + <div class="w-100 h-100" cell-iii> + <ng-content *ngTemplateOutlet="celliii"></ng-content> + </div> + <div class="w-100 h-100" cell-iv> + <ng-content *ngTemplateOutlet="celliv"></ng-content> + </div> + </layout-vertical-one-three> + <layout-single-panel + *ngSwitchCase="supportedPanelModes[3]" + class="d-block w-100 h-100"> + <div class="w-100 h-100" cell-i> + <ng-content *ngTemplateOutlet="celli"></ng-content> + </div> + <div class="w-100 h-100" cell-ii> + <ng-content *ngTemplateOutlet="cellii"></ng-content> + </div> + <div class="w-100 h-100" cell-iii> + <ng-content *ngTemplateOutlet="celliii"></ng-content> + </div> + <div class="w-100 h-100" cell-iv> + <ng-content *ngTemplateOutlet="celliv"></ng-content> + </div> + </layout-single-panel> + <div *ngSwitchDefault> + A panel mode which I have never seen before ... + </div> +</div> + +<ng-template #celli> + <ng-content select="[cell-i]"></ng-content> +</ng-template> +<ng-template #cellii> + <ng-content select="[cell-ii]"></ng-content> +</ng-template> +<ng-template #celliii> + <ng-content select="[cell-iii]"></ng-content> +</ng-template> +<ng-template #celliv> + <ng-content select="[cell-iv]"></ng-content> +</ng-template> \ No newline at end of file diff --git a/src/ui/config/layouts/fourPanel/fourPanel.component.ts b/src/ui/config/layouts/fourPanel/fourPanel.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7a22a241e08a3862385b7e7704bf20f0adfded7 --- /dev/null +++ b/src/ui/config/layouts/fourPanel/fourPanel.component.ts @@ -0,0 +1,13 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: 'layout-four-panel', + templateUrl: './fourPanel.template.html', + styleUrls: [ + './fourPanel.style.css' + ] +}) + +export class FourPanelLayout{ + +} \ No newline at end of file diff --git a/src/ui/config/layouts/fourPanel/fourPanel.style.css b/src/ui/config/layouts/fourPanel/fourPanel.style.css new file mode 100644 index 0000000000000000000000000000000000000000..03169dfb4b9564f62c86ca9250303c5337927861 --- /dev/null +++ b/src/ui/config/layouts/fourPanel/fourPanel.style.css @@ -0,0 +1,4 @@ +.four-panel-cell +{ + flex: 0 0 50%; +} \ No newline at end of file diff --git a/src/ui/config/layouts/fourPanel/fourPanel.template.html b/src/ui/config/layouts/fourPanel/fourPanel.template.html new file mode 100644 index 0000000000000000000000000000000000000000..ddb10f1f6a34adda68f578625dd843baf536dfa0 --- /dev/null +++ b/src/ui/config/layouts/fourPanel/fourPanel.template.html @@ -0,0 +1,18 @@ +<div class="w-100 h-100 d-flex flex-column justify-content-center align-items-stretch"> + <div class="d-flex flex-row flex-grow-1 flex-shrink-1"> + <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center"> + <ng-content select="[cell-i]"></ng-content> + </div> + <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center"> + <ng-content select="[cell-ii]"></ng-content> + </div> + </div> + <div class="d-flex flex-row flex-grow-1 flex-shrink-1"> + <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center"> + <ng-content select="[cell-iii]"></ng-content> + </div> + <div class="d-flex flex-row four-panel-cell align-items-center justify-content-center"> + <ng-content select="[cell-iv]"></ng-content> + </div> + </div> +</div> diff --git a/src/ui/config/layouts/h13/h13.component.ts b/src/ui/config/layouts/h13/h13.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..eccf98f96c6e49993db2a2ac609bfbc05e9a6bc7 --- /dev/null +++ b/src/ui/config/layouts/h13/h13.component.ts @@ -0,0 +1,13 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: 'layout-horizontal-one-three', + templateUrl: './h13.template.html', + styleUrls: [ + './h13.style.css' + ] +}) + +export class HorizontalOneThree{ + +} \ No newline at end of file diff --git a/src/ui/config/layouts/h13/h13.style.css b/src/ui/config/layouts/h13/h13.style.css new file mode 100644 index 0000000000000000000000000000000000000000..be83c538297487ff9c330ba551ac85e2badfd309 --- /dev/null +++ b/src/ui/config/layouts/h13/h13.style.css @@ -0,0 +1,12 @@ +.major-column +{ + flex: 0 0 67%; +} +.minor-column +{ + flex: 0 0 33%; +} +.layout-31-cell +{ + flex: 0 0 33.33%; +} \ No newline at end of file diff --git a/src/ui/config/layouts/h13/h13.template.html b/src/ui/config/layouts/h13/h13.template.html new file mode 100644 index 0000000000000000000000000000000000000000..d389ce304f6671f2610920c5c6c3d7780b63ac1d --- /dev/null +++ b/src/ui/config/layouts/h13/h13.template.html @@ -0,0 +1,18 @@ +<div class="w-100 h-100 d-flex flex-row justify-content-center align-items-stretch"> + <div class="d-flex flex-column major-column"> + <div class="overflow-hidden flex-grow-1 d-flex align-items-center justify-content-center"> + <ng-content select="[cell-i]"></ng-content> + </div> + </div> + <div class="d-flex flex-column minor-column"> + <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-ii]"></ng-content> + </div> + <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-iii]"></ng-content> + </div> + <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-iv]"></ng-content> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/ui/config/layouts/single/single.component.ts b/src/ui/config/layouts/single/single.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..25101d27a9462b37e4072a912bd281126fdb7500 --- /dev/null +++ b/src/ui/config/layouts/single/single.component.ts @@ -0,0 +1,13 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: 'layout-single-panel', + templateUrl: './single.template.html', + styleUrls: [ + './single.style.css' + ] +}) + +export class SinglePanel{ + +} \ No newline at end of file diff --git a/src/ui/config/layouts/single/single.style.css b/src/ui/config/layouts/single/single.style.css new file mode 100644 index 0000000000000000000000000000000000000000..19615b37060256eef0194131b901b11e89b01b49 --- /dev/null +++ b/src/ui/config/layouts/single/single.style.css @@ -0,0 +1,12 @@ +.major-column +{ + flex: 0 0 100%; +} +.minor-column +{ + flex: 0 0 0%; +} +.layout-31-cell +{ + flex: 0 0 33%; +} \ No newline at end of file diff --git a/src/ui/config/layouts/single/single.template.html b/src/ui/config/layouts/single/single.template.html new file mode 100644 index 0000000000000000000000000000000000000000..561a6363b701518ec5d27b1f0d78507e02afe30a --- /dev/null +++ b/src/ui/config/layouts/single/single.template.html @@ -0,0 +1,18 @@ +<div class="w-100 h-100 d-flex flex-row justify-content-center align-items-stretch"> + <div class="d-flex flex-column major-column"> + <div class="overflow-hidden flex-grow-1 d-flex align-items-center justify-content-center"> + <ng-content select="[cell-i]"></ng-content> + </div> + </div> + <div class="d-flex flex-column minor-column"> + <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-ii]"></ng-content> + </div> + <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-iii]"></ng-content> + </div> + <div class="overflow-hidden layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-iv]"></ng-content> + </div> + </div> + </div> \ No newline at end of file diff --git a/src/ui/config/layouts/v13/v13.component.ts b/src/ui/config/layouts/v13/v13.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c650ac701de3fff14118a9487403410ae89e72ad --- /dev/null +++ b/src/ui/config/layouts/v13/v13.component.ts @@ -0,0 +1,13 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: 'layout-vertical-one-three', + templateUrl: './v13.template.html', + styleUrls: [ + './v13.style.css' + ] +}) + +export class VerticalOneThree{ + +} \ No newline at end of file diff --git a/src/ui/config/layouts/v13/v13.style.css b/src/ui/config/layouts/v13/v13.style.css new file mode 100644 index 0000000000000000000000000000000000000000..be83c538297487ff9c330ba551ac85e2badfd309 --- /dev/null +++ b/src/ui/config/layouts/v13/v13.style.css @@ -0,0 +1,12 @@ +.major-column +{ + flex: 0 0 67%; +} +.minor-column +{ + flex: 0 0 33%; +} +.layout-31-cell +{ + flex: 0 0 33.33%; +} \ No newline at end of file diff --git a/src/ui/config/layouts/v13/v13.template.html b/src/ui/config/layouts/v13/v13.template.html new file mode 100644 index 0000000000000000000000000000000000000000..148f70cb596d2df0183f25d012bb9d948b610f60 --- /dev/null +++ b/src/ui/config/layouts/v13/v13.template.html @@ -0,0 +1,18 @@ +<div class="w-100 h-100 d-flex flex-column justify-content-center align-items-stretch"> + <div class="d-flex flex-column major-column"> + <div class="flex-grow-1 d-flex align-items-center justify-content-center"> + <ng-content select="[cell-i]"></ng-content> + </div> + </div> + <div class="d-flex flex-row minor-column"> + <div class="layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-ii]"></ng-content> + </div> + <div class="layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-iii]"></ng-content> + </div> + <div class="layout-31-cell d-flex align-items-center justify-content-center"> + <ng-content select="[cell-iv]"></ng-content> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 66dd73300d5b6bface0a36c5c844c2b9f56d0aa9..3112fa91a944a0766c871af90034af33c989a715 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -3,14 +3,15 @@ import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"; import { Store, select } from "@ngrx/store"; import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId, DataEntry } from "../../services/stateStore.service"; import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs"; -import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, throttleTime, bufferTime, startWith } from "rxjs/operators"; +import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay, throttleTime, bufferTime, startWith } from "rxjs/operators"; import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service"; import { timedValues } from "../../util/generator"; import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service"; import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; import { pipeFromArray } from "rxjs/internal/util/pipe"; -import { NEHUBA_READY } from "src/services/state/ngViewerState.store"; +import { NEHUBA_READY, H_ONE_THREE, V_ONE_THREE, FOUR_PANEL, SINGLE_PANEL } from "src/services/state/ngViewerState.store"; import { MOUSE_OVER_SEGMENTS } from "src/services/state/uiState.store"; +import { getHorizontalOneThree, getVerticalOneThree, getFourPanel, getSinglePanel } from "./util"; import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store"; import { MatBottomSheet, MatButton } from "@angular/material"; import { DATASETS_ACTIONS_TYPES } from "src/services/state/dataStore.store"; @@ -89,10 +90,6 @@ const scanFn : (acc:[boolean, boolean, boolean], curr: CustomEvent) => [boolean, export class NehubaContainer implements OnInit, OnDestroy{ @ViewChild('container',{read:ViewContainerRef}) container : ViewContainerRef - @ViewChild('[pos00]',{read:ElementRef}) topleft : ElementRef - @ViewChild('[pos01]',{read:ElementRef}) topright : ElementRef - @ViewChild('[pos10]',{read:ElementRef}) bottomleft : ElementRef - @ViewChild('[pos11]',{read:ElementRef}) bottomright : ElementRef private nehubaViewerFactory : ComponentFactory<NehubaViewerUnit> @@ -145,6 +142,9 @@ export class NehubaContainer implements OnInit, OnDestroy{ public nanometersToOffsetPixelsFn : Function[] = [] private viewerConfig : Partial<ViewerConfiguration> = {} + private viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null] + public panelMode$: Observable<string> + private redrawLayout$: Observable<[string, string]> public favDataEntries$: Observable<DataEntry[]> constructor( @@ -173,6 +173,25 @@ export class NehubaContainer implements OnInit, OnDestroy{ filter(() => isDefined(this.nehubaViewer) && isDefined(this.nehubaViewer.nehubaViewer)) ) + this.redrawLayout$ = this.store.pipe( + select('ngViewerState'), + select('nehubaReady'), + distinctUntilChanged(), + filter(v => !!v), + switchMapTo(combineLatest( + this.store.pipe( + select('ngViewerState'), + select('panelMode'), + distinctUntilChanged() + ), + this.store.pipe( + select('ngViewerState'), + select('panelOrder'), + distinctUntilChanged() + ) + )) + ) + this.nehubaViewerFactory = this.csf.resolveComponentFactory(NehubaViewerUnit) this.newViewer$ = this.store.pipe( @@ -340,24 +359,10 @@ export class NehubaContainer implements OnInit, OnDestroy{ ).pipe( map(results => results[1] === null ? results[0] : '') ) - - /* each time a new viewer is initialised, take the first event to get the translation function */ - this.newViewer$.pipe( - // switchMap(() => fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent') - // .pipe( - // ...takeOnePipe - // ) - // ) - - switchMap(() => pipeFromArray([...takeOnePipe])(fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent'))) - - - ).subscribe((events)=>{ - [0,1,2].forEach(idx=>this.nanometersToOffsetPixelsFn[idx] = (events[idx] as any).detail.nanometersToOffsetPixels) - }) this.sliceViewLoadingMain$ = fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent').pipe( scan(scanFn, [null, null, null]), + shareReplay(1) ) this.sliceViewLoading0$ = this.sliceViewLoadingMain$ @@ -408,14 +413,90 @@ export class NehubaContainer implements OnInit, OnDestroy{ ? state.layers.findIndex(l => l.mixability === 'nonmixable') >= 0 : false) ) + + this.panelMode$ = this.store.pipe( + select('ngViewerState'), + select('panelMode'), + distinctUntilChanged(), + ) } get isMobile(){ return this.constantService.mobile } + private removeExistingPanels() { + const element = this.nehubaViewer.nehubaViewer.ngviewer.layout.container.componentValue.element as HTMLElement + while (element.childElementCount > 0) { + element.removeChild(element.firstElementChild) + } + return element + } + ngOnInit(){ + /* each time a new viewer is initialised, take the first event to get the translation function */ + this.subscriptions.push( + this.newViewer$.pipe( + switchMap(() => pipeFromArray([...takeOnePipe])(fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent'))) + ).subscribe((events)=>{ + for (const idx in [0,1,2]) { + const ev = events[idx] as CustomEvent + this.viewPanels[idx] = ev.target as HTMLElement + this.nanometersToOffsetPixelsFn[idx] = ev.detail.nanometersToOffsetPixels + } + }) + ) + + this.subscriptions.push( + this.newViewer$.pipe( + switchMapTo(fromEvent(this.elementRef.nativeElement, 'perpspectiveRenderEvent').pipe( + take(1) + )), + ).subscribe(ev => this.viewPanels[3] = ((ev as CustomEvent).target) as HTMLElement) + ) + + this.subscriptions.push( + this.redrawLayout$.subscribe(([mode, panelOrder]) => { + const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.viewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] + /** + * TODO be smarter with event stream + */ + if (!this.nehubaViewer) return + + switch (mode) { + case H_ONE_THREE:{ + const element = this.removeExistingPanels() + const newEl = getHorizontalOneThree(viewPanels) + element.appendChild(newEl) + break; + } + case V_ONE_THREE:{ + const element = this.removeExistingPanels() + const newEl = getVerticalOneThree(viewPanels) + element.appendChild(newEl) + break; + } + case FOUR_PANEL: { + const element = this.removeExistingPanels() + const newEl = getFourPanel(viewPanels) + element.appendChild(newEl) + break; + } + case SINGLE_PANEL: { + const element = this.removeExistingPanels() + const newEl = getSinglePanel(viewPanels) + element.appendChild(newEl) + break; + } + default: + } + for (const panel of viewPanels){ + (panel as HTMLElement).classList.add('neuroglancer-panel') + } + }) + ) + this.subscriptions.push( this.viewerPerformanceConfig$.subscribe(config => { this.nehubaViewer.applyPerformanceConfig(config) @@ -1202,7 +1283,6 @@ export const takeOnePipe = [ * 4 ??? */ const key = identifySrcElement(target) - const _ = {} _[key] = event return Object.assign({},acc,_) diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css index 34ff8d48462b2598d695c404f6500f84a75535a1..57a6ad380921978025fdb9692967d716743d5eab 100644 --- a/src/ui/nehubaContainer/nehubaContainer.style.css +++ b/src/ui/nehubaContainer/nehubaContainer.style.css @@ -15,6 +15,11 @@ input[navigateInput] box-shadow: inset 0px 2px 2px 2px rgba(0,0,0,0.05); } +current-layout +{ + top: 0; + left: 0; +} div[landmarkMasterContainer] { @@ -66,7 +71,7 @@ hr } -div[landmarkMasterContainer] > div > [landmarkContainer] > div.loadingIndicator +div.loadingIndicator { left: auto; top: auto; @@ -75,6 +80,7 @@ div[landmarkMasterContainer] > div > [landmarkContainer] > div.loadingIndicator margin-right: 0.2em; margin-bottom: 0.2em; width: 100%; + position:absolute; height:2em; display: flex; flex-direction: row-reverse; diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index 96aae5f78873ebdda17a9887d51265f87d25ddd0..6ba9a1443ce3e189057eac4e8c347a48fd0a6b52 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -4,70 +4,23 @@ <ui-splashscreen (contextmenu)="$event.stopPropagation();" *ngIf="!viewerLoaded"> </ui-splashscreen> -<div landmarkMasterContainer> +<!-- spatial landmarks overlay --> +<!-- loading indicator --> - <div> - <layout-floating-container pos00 landmarkContainer> - <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" - [highlight]="spatialData.highlight ? spatialData.highlight : false" - [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" - [positionX]="getPositionX(0,spatialData)" [positionY]="getPositionY(0,spatialData)" - [positionZ]="getPositionZ(0,spatialData)"> - </nehuba-2dlandmark-unit> - - <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator"> - <div class="spinnerAnimationCircle"> - - </div> - </div> - </layout-floating-container> +<current-layout class="position-absolute w-100 h-100 d-block pe-none"> + <div class="w-100 h-100 position-relative" cell-i> + <ng-content *ngTemplateOutlet="overlayi"></ng-content> </div> - <div> - <layout-floating-container pos01 landmarkContainer> - <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" - [highlight]="spatialData.highlight ? spatialData.highlight : false" - [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" - [positionX]="getPositionX(1,spatialData)" [positionY]="getPositionY(1,spatialData)" - [positionZ]="getPositionZ(1,spatialData)"> - </nehuba-2dlandmark-unit> - - <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator"> - <div class="spinnerAnimationCircle"> - - </div> - </div> - </layout-floating-container> + <div class="w-100 h-100 position-relative" cell-ii> + <ng-content *ngTemplateOutlet="overlayii"></ng-content> </div> - <div> - <layout-floating-container pos10 landmarkContainer> - <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" - (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" - [highlight]="spatialData.highlight ? spatialData.highlight : false" - [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" - [positionX]="getPositionX(2,spatialData)" [positionY]="getPositionY(2,spatialData)" - [positionZ]="getPositionZ(2,spatialData)"> - </nehuba-2dlandmark-unit> - - <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator"> - <div class="spinnerAnimationCircle"> - - </div> - </div> - </layout-floating-container> + <div class="w-100 h-100 position-relative" cell-iii> + <ng-content *ngTemplateOutlet="overlayiii"></ng-content> </div> - <div> - <layout-floating-container pos11 landmarkContainer> - <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator"> - <div class="spinnerAnimationCircle"></div> - <div perspectiveLoadingText> - {{ perspectiveViewLoading$ | async }} - </div> - </div> - </layout-floating-container> + <div class="w-100 h-100 position-relative" cell-iv> + <ng-content *ngTemplateOutlet="overlayiv"></ng-content> </div> -</div> +</current-layout> <layout-floating-container *ngIf="viewerLoaded && !isMobile"> @@ -87,8 +40,11 @@ </div> <!-- StatusCard container--> - <ui-status-card [selectedTemplate]="selectedTemplate" [isMobile]="isMobile" - [onHoverSegmentName]="onHoverSegmentName$ | async" [nehubaViewer]="nehubaViewer"> + <ui-status-card + [selectedTemplate]="selectedTemplate" + [isMobile]="isMobile" + [onHoverSegmentName]="onHoverSegmentName$ | async" + [nehubaViewer]="nehubaViewer"> </ui-status-card> </layout-floating-container> @@ -96,7 +52,10 @@ </div> -<mobile-overlay *ngIf="isMobile && viewerLoaded" [tunableProperties]="tunableMobileProperties" +<!-- mobile nub, allowing for ooblique slicing in mobile --> +<mobile-overlay + *ngIf="isMobile && viewerLoaded" + [tunableProperties]="tunableMobileProperties" (deltaValue)="handleMobileOverlayEvent($event)"> <div class="base" delta> <div mobileObliqueGuide class="p-2 mb-4 shadow"> @@ -104,7 +63,9 @@ </div> </div> <div class="base" guide> - <div mobileObliqueGuide class="p-2 mb-4 shadow"> + <div + mobileObliqueGuide + class="p-2 mb-4 shadow"> <div> <i class="fas fa-arrows-alt-v"></i> oblique mode </div> @@ -113,11 +74,84 @@ </div> </div> </div> - <div (contextmenu)="$event.stopPropagation(); $event.preventDefaul();" mobileObliqueCtrl initiator> - <i class="fas fa-globe"></i> + <div + (contextmenu)="$event.stopPropagation(); $event.preventDefaul();" + [ngStyle]="panelMode$ | async | mobileControlNubStylePipe" + mobileObliqueCtrl + initiator> + <button mat-mini-fab color="primary"> + <i class="fas fa-globe"></i> + </button> </div> </mobile-overlay> +<!-- overlay templates --> +<!-- inserted using ngTemplateOutlet --> +<ng-template #overlayi> + <layout-floating-container pos00 landmarkContainer> + <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" + (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" + [highlight]="spatialData.highlight ? spatialData.highlight : false" + [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" + [positionX]="getPositionX(0,spatialData)" [positionY]="getPositionY(0,spatialData)" + [positionZ]="getPositionZ(0,spatialData)"> + </nehuba-2dlandmark-unit> + + <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"> + + </div> + </div> + </layout-floating-container> +</ng-template> + +<ng-template #overlayii> + <layout-floating-container pos01 landmarkContainer> + <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" + (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" + [highlight]="spatialData.highlight ? spatialData.highlight : false" + [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" + [positionX]="getPositionX(1,spatialData)" [positionY]="getPositionY(1,spatialData)" + [positionZ]="getPositionZ(1,spatialData)"> + </nehuba-2dlandmark-unit> + + <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"> + + </div> + </div> + </layout-floating-container> +</ng-template> + +<ng-template #overlayiii> + <layout-floating-container pos10 landmarkContainer> + <nehuba-2dlandmark-unit *ngFor="let spatialData of (selectedPtLandmarks$ | async)" + (mouseenter)="handleMouseEnterLandmark(spatialData)" (mouseleave)="handleMouseLeaveLandmark(spatialData)" + [highlight]="spatialData.highlight ? spatialData.highlight : false" + [fasClass]="spatialData.type === 'userLandmark' ? 'fa-chevron-down' : 'fa-map-marker'" + [positionX]="getPositionX(2,spatialData)" [positionY]="getPositionY(2,spatialData)" + [positionZ]="getPositionZ(2,spatialData)"> + </nehuba-2dlandmark-unit> + + <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"> + + </div> + </div> + </layout-floating-container> +</ng-template> + +<ng-template #overlayiv> + <layout-floating-container pos11 landmarkContainer> + <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator"> + <div class="spinnerAnimationCircle"></div> + <div perspectiveLoadingText> + {{ perspectiveViewLoading$ | async }} + </div> + </div> + </layout-floating-container> +</ng-template> + <ng-template #savedDatasets> <mat-list rol="list"> <h3 mat-subheader>Favourite Datasets</h3> diff --git a/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts b/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..4566c470506588e39edc79f005843a24be80c748 --- /dev/null +++ b/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts @@ -0,0 +1,30 @@ +import { PipeTransform, Pipe } from "@angular/core"; +import { FOUR_PANEL, H_ONE_THREE, V_ONE_THREE, SINGLE_PANEL } from "src/services/state/ngViewerState.store"; + +@Pipe({ + name: 'mobileControlNubStylePipe' +}) + +export class MobileControlNubStylePipe implements PipeTransform{ + public transform(panelMode: string): any{ + switch (panelMode) { + case SINGLE_PANEL: + return { + top: '80%', + left: '95%' + } + case V_ONE_THREE: + case H_ONE_THREE: + return { + top: '66.66%', + left: '66.66%' + } + case FOUR_PANEL: + default: + return { + top: '50%', + left: '50%' + } + } + } +} \ No newline at end of file diff --git a/src/ui/nehubaContainer/util.ts b/src/ui/nehubaContainer/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..edd3a98f8fea9b48e40fe7cd16702f469a95ca93 --- /dev/null +++ b/src/ui/nehubaContainer/util.ts @@ -0,0 +1,74 @@ +const flexContCmnCls = ['w-100', 'h-100', 'd-flex', 'justify-content-center', 'align-items-stretch'] + +const makeRow = (...els:HTMLElement[]) => { + const container = document.createElement('div') + container.classList.add(...flexContCmnCls, 'flex-row') + for (const el of els){ + container.appendChild(el) + } + return container +} + +const makeCol = (...els:HTMLElement[]) => { + const container = document.createElement('div') + container.classList.add(...flexContCmnCls, 'flex-column') + for (const el of els){ + container.appendChild(el) + } + return container +} + +export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { + for (let panel of panels){ + panel.className = '' + } + const majorContainer = makeCol(panels[0]) + const minorContainer = makeCol(panels[1], panels[2], panels[3]) + + majorContainer.style.flexBasis = '67%' + minorContainer.style.flexBasis = '33%' + + return makeRow(majorContainer, minorContainer) +} + +export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { + for (let panel of panels){ + panel.className = '' + } + const majorContainer = makeRow(panels[0]) + const minorContainer = makeRow(panels[1], panels[2], panels[3]) + + majorContainer.style.flexBasis = '67%' + minorContainer.style.flexBasis = '33%' + + return makeCol(majorContainer, minorContainer) +} + + +export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { + for (let panel of panels){ + panel.className = '' + } + const majorContainer = makeRow(panels[0], panels[1]) + const minorContainer = makeRow(panels[2], panels[3]) + + majorContainer.style.flexBasis = '50%' + minorContainer.style.flexBasis = '50%' + + return makeCol(majorContainer, minorContainer) +} + +export const getSinglePanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { + for (let panel of panels){ + panel.className = '' + } + const majorContainer = makeRow(panels[0]) + const minorContainer = makeRow(panels[1], panels[2], panels[3]) + + majorContainer.style.flexBasis = '100%' + minorContainer.style.flexBasis = '0%' + + minorContainer.className = '' + minorContainer.style.height = '0px' + return makeCol(majorContainer, minorContainer) +} \ No newline at end of file diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts index 225b90104e30d3c0b8495d723826295757198066..9967e80d64279a7ef9e9b760a1c784593926c3b3 100644 --- a/src/ui/sharedModules/angularMaterial.module.ts +++ b/src/ui/sharedModules/angularMaterial.module.ts @@ -15,6 +15,7 @@ import { MatBottomSheetModule, MatListModule, MatSlideToggleModule, + MatRippleModule } from '@angular/material'; import { NgModule } from '@angular/core'; @@ -41,6 +42,7 @@ import { NgModule } from '@angular/core'; MatBottomSheetModule, MatListModule, MatSlideToggleModule, + MatRippleModule ], exports: [ MatButtonModule, @@ -59,6 +61,7 @@ import { NgModule } from '@angular/core'; MatBottomSheetModule, MatListModule, MatSlideToggleModule, + MatRippleModule ], }) export class AngularMaterialModule { } \ No newline at end of file diff --git a/src/ui/signinBanner/signinBanner.components.ts b/src/ui/signinBanner/signinBanner.components.ts index f559248fe9f5b47382e39b0cb7f3d52da6e7b705..abd1eb3397f1c1a3e7ada69d3ead390f29bfbed7 100644 --- a/src/ui/signinBanner/signinBanner.components.ts +++ b/src/ui/signinBanner/signinBanner.components.ts @@ -1,9 +1,8 @@ -import {Component, ChangeDetectionStrategy, OnDestroy, OnInit, Input, ViewChild, TemplateRef } from "@angular/core"; +import {Component, ChangeDetectionStrategy, Input, TemplateRef } from "@angular/core"; import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; import { AuthService, User } from "src/services/auth.service"; -import { Store} from "@ngrx/store"; -import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; -import { safeFilter, isDefined, NEWVIEWER, SELECT_REGIONS, SELECT_PARCELLATION, CHANGE_NAVIGATION } from "src/services/stateStore.service"; +import { MatDialog } from "@angular/material"; + @Component({ selector: 'signin-banner', @@ -24,15 +23,30 @@ export class SigninBanner{ constructor( private constantService: AtlasViewerConstantsServices, private authService: AuthService, - private store: Store<ViewerConfiguration> + private dialog: MatDialog ){ this.isMobile = this.constantService.mobile } + /** + * move the templates to signin banner when pluginprettify is merged + */ showHelp() { this.constantService.showHelpSubject$.next() } + /** + * move the templates to signin banner when pluginprettify is merged + */ + showSetting(settingTemplate:TemplateRef<any>){ + this.dialog.open(settingTemplate, { + autoFocus: false + }) + } + + /** + * move the templates to signin banner when pluginprettify is merged + */ showSignin() { this.constantService.showSigninSubject$.next(this.user) } diff --git a/src/ui/signinBanner/signinBanner.template.html b/src/ui/signinBanner/signinBanner.template.html index 5636dc1707484127091201d0d09e7bae552e1152..1da0fb01da873392fbe79901047520aaff3bf699 100644 --- a/src/ui/signinBanner/signinBanner.template.html +++ b/src/ui/signinBanner/signinBanner.template.html @@ -14,6 +14,19 @@ </button> </div> + <!-- setting btn --> + <div class="btnWrapper"> + <button + #settingBtn + matTooltip="Settings" + matTooltipPosition="below" + (click)="showSetting(settingTemplate)" + mat-icon-button + color="basic"> + <i class="fas fa-cog"></i> + </button> + </div> + <!-- signin --> <div class="btnWrapper"> <button diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index d4a8a3efd7d2bfb3f8c7ca06947ff12d068b62ac..ae129d6220403450c0ebc00cff530c85e1cf842c 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -47,8 +47,12 @@ import { KGToS } from "./kgtos/kgtos.component"; import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module' import { TemplateParcellationsDecorationPipe } from "src/util/pipes/templateParcellationDecoration.pipe"; import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe"; -import { MatRippleModule } from "@angular/material"; -import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe" +import { FourPanelLayout } from "./config/layouts/fourPanel/fourPanel.component"; +import { HorizontalOneThree } from "./config/layouts/h13/h13.component"; +import { VerticalOneThree } from "./config/layouts/v13/v13.component"; +import { SinglePanel } from "./config/layouts/single/single.component"; +import { CurrentLayout } from "./config/currentLayout/currentLayout.component"; +import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControlNubStyle.pipe"; import { ScrollingModule } from "@angular/cdk/scrolling" import { HttpClientModule } from "@angular/common/http"; import { GetFilenamePipe } from "src/util/pipes/getFilename.pipe"; @@ -72,12 +76,6 @@ import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; ScrollingModule, AngularMaterialModule, - /** - * TODO - * move to angular material module - */ - MatRippleModule, - PopoverModule.forRoot(), TooltipModule.forRoot() ], @@ -103,6 +101,11 @@ import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; StatusCardComponent, CookieAgreement, KGToS, + FourPanelLayout, + HorizontalOneThree, + VerticalOneThree, + SinglePanel, + CurrentLayout, ViewerStateController, RegionTextSearchAutocomplete, @@ -119,6 +122,7 @@ import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe"; FilterNameBySearch, TemplateParcellationsDecorationPipe, AppendtooltipTextPipe, + MobileControlNubStylePipe, GetTemplateImageSrcPipe, ImgSrcSetPipe, PluginBtnFabColorPipe,