diff --git a/docs/releases/v2.14.0.md b/docs/releases/v2.14.0.md index e683b6817dc225c3d38e0bb1038c777a79d797c6..83c1b578309aa024c556ec0920977031dcaf306c 100644 --- a/docs/releases/v2.14.0.md +++ b/docs/releases/v2.14.0.md @@ -10,10 +10,12 @@ - experimental support for `deepzoom://` source format - quick search now show branches in addition to leaves - added context in region hierarchy view +- added coordinate entry dialog ## Bugfix - fixed keyframe mode not activating on second attempt +- on selecting a new point while point assignment is minimized, will maximize the point assignment panel ## Behind the scenes diff --git a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html index 2691032bb97317125bc1179257a61f61f2f27cb5..dc06da4db017ebb5ffd92ad66fb17378f8309405 100644 --- a/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html +++ b/src/atlasComponents/sapiViews/volumes/point-assignment/point-assignment.component.html @@ -1,3 +1,12 @@ +<ng-template [ngIf]="point$ | async | sandsToNum" let-coordinates> + <button mat-icon-button class="sxplr-m-2" + (click)="navigateToPoint(coordinates.coords)" + [matTooltip]="'Navigate To ' + (coordinates.coords | numbers : 2 | addUnitAndJoin : '')"> + <i class="fas fa-map-marker-alt"></i> + </button> +</ng-template> + + <div class="sxplr-m-2" *ngIf="busy$ | async as busyMessage"> <spinner-cmp class="sxplr-d-inline-block"></spinner-cmp> <span> @@ -13,14 +22,6 @@ <ng-template [ngIf]="df$ | async" let-df> - <ng-template [ngIf]="point$ | async | sandsToNum" let-coordinates> - <button mat-icon-button class="sxplr-m-2" - (click)="navigateToPoint(coordinates.coords)" - [matTooltip]="'Navigate To ' + (coordinates.coords | numbers : 2 | addUnitAndJoin : '')"> - <i class="fas fa-map-marker-alt"></i> - </button> - </ng-template> - <button mat-raised-button class="sxplr-m-2" (click)="openDialog(datatableTmpl)"> @@ -36,11 +37,14 @@ matSortDirection="desc"> <ng-container matColumnDef="region"> <th mat-header-cell *matHeaderCellDef mat-sort-header> - region + <span class="sxplr-m-1"> + region + </span> </th> <td mat-cell *matCellDef="let element"> <!-- {{ element | json }} --> - <button mat-button (click)="selectRegion(element['region'], $event)"> + <button mat-button (click)="selectRegion(element['region'], $event)" + class="ws-no-wrap"> {{ element['region'].name }} </button> </td> @@ -73,7 +77,8 @@ region </th> <td mat-cell *matCellDef="let element"> - <button mat-button (click)="selectRegion(element['region'], $event)"> + <button mat-button (click)="selectRegion(element['region'], $event)" + class="ws-no-wrap"> {{ element['region'].name }} </button> </td> diff --git a/src/components/tab/tab.components.ts b/src/components/tab/tab.components.ts new file mode 100644 index 0000000000000000000000000000000000000000..655208dcf79030e793920d0b2fa5db160f020c71 --- /dev/null +++ b/src/components/tab/tab.components.ts @@ -0,0 +1,39 @@ +import { CommonModule } from "@angular/common"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { AngularMaterialModule } from "src/sharedModules"; + +@Component({ + selector: 'sxplr-tab', + standalone: true, + imports: [ + CommonModule, + AngularMaterialModule, + ], + templateUrl: "./tab.template.html", + styleUrls: [ + "./tab.style.scss" + ] +}) + +export class TabComponent{ + @Input("sxplr-tab-icon") + icon: string = "fas fa-file" + + @Input("sxplr-tab-badge") + badge: number + + @Input("sxplr-tab-badge-color") + badgeColor: "primary" | "warn" = "primary" + + @Input("sxplr-tab-color") + color: "primary" | "warn" | "danger" + + @Input("sxplr-tab-override-color") + overrideColor: string + + @Input('sxplr-tab-override-class') + overrideCls: string + + @Output('sxplr-tab-click') + click = new EventEmitter<MouseEvent>() +} diff --git a/src/components/tab/tab.style.scss b/src/components/tab/tab.style.scss new file mode 100644 index 0000000000000000000000000000000000000000..57dc6a4331909688c9ee18c9012715eb166a8b5d --- /dev/null +++ b/src/components/tab/tab.style.scss @@ -0,0 +1,5 @@ +i +{ + margin-left:0.5rem; + margin-right:-0.5rem; +} diff --git a/src/components/tab/tab.template.html b/src/components/tab/tab.template.html new file mode 100644 index 0000000000000000000000000000000000000000..8b25457076c97feeff3e73b97b4dcae502d0de85 --- /dev/null +++ b/src/components/tab/tab.template.html @@ -0,0 +1,9 @@ +<button mat-raised-button + [matBadge]="badge" + [matBadgeColor]="badgeColor" + [color]="color" + [style.backgroundColor]="overrideColor" + [class]="overrideCls" + (click)="click.next($event)"> + <i [class]="icon"></i> +</button> diff --git a/src/extra_styles.css b/src/extra_styles.css index 795f053415d65c0e6ff85df9bf6030c14adcad3f..44453c5327e96f98b63b57d52476bf45a3d3e87b 100644 --- a/src/extra_styles.css +++ b/src/extra_styles.css @@ -383,16 +383,6 @@ markdown-dom p pointer-events: all; } -.t-a-ease-200 -{ - transition: all ease 200ms; -} - -.t-a-ease-500 -{ - transition: all ease 500ms; -} - .h-0 { height: 0px; @@ -775,11 +765,6 @@ body::after padding-top: 16px!important; } -.fixed-bottom -{ - top: unset!important; -} - .sidenav-cover-header-container { padding: 16px; diff --git a/src/util/pipes/getProperty.pipe.ts b/src/util/pipes/getProperty.pipe.ts index 2132c83f2de36cf5b09b257b2fcd22f887be100c..b11e762e3e56ade9cac8405d6e8a59cf668f1f87 100644 --- a/src/util/pipes/getProperty.pipe.ts +++ b/src/util/pipes/getProperty.pipe.ts @@ -5,8 +5,8 @@ import { Pipe, PipeTransform } from "@angular/core"; pure: true }) -export class GetPropertyPipe implements PipeTransform{ - public transform(input: any, property: any = '@id') { +export class GetPropertyPipe<R extends Record<string|number, unknown>> implements PipeTransform{ + public transform(input: R, property: keyof R) { return input && input[property] } } diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index 01c177127a43b261585b42d7ad491bbbb89a0723..e2b3b4589e09d1342be273a0f2c1a5cb4044c204 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -38,6 +38,7 @@ import { BottomMenuModule } from "src/ui/bottomMenu"; import { CURRENT_TEMPLATE_DIM_INFO, TemplateInfo, Z_TRAVERSAL_MULTIPLIER } from "./nehuba/layerCtrl.service/layerCtrl.util"; import { Store } from "@ngrx/store"; import { atlasSelection, userPreference } from "src/state"; +import { TabComponent } from "src/components/tab/tab.components"; @NgModule({ imports: [ @@ -65,6 +66,7 @@ import { atlasSelection, userPreference } from "src/state"; SmartChipModule, ReactiveFormsModule, BottomMenuModule, + TabComponent, ...(environment.ENABLE_LEAP_MOTION ? [LeapModule] : []) ], declarations: [ diff --git a/src/viewerModule/nehuba/module.ts b/src/viewerModule/nehuba/module.ts index 682fae0bb9cf878ebd3ff18007a1ad26eb8cbfb0..485c478b8078df06a5d660dd1838f6aac68c32f8 100644 --- a/src/viewerModule/nehuba/module.ts +++ b/src/viewerModule/nehuba/module.ts @@ -28,6 +28,7 @@ import { NgAnnotationService } from "./annotation/service"; import { NgAnnotationEffects } from "./annotation/effects"; import { NehubaViewerContainer } from "./nehubaViewerInterface/nehubaViewerContainer.component"; import { NehubaUserLayerModule } from "./userLayers"; +import { DialogModule } from "src/ui/dialogInfo"; @NgModule({ imports: [ @@ -58,6 +59,7 @@ import { NehubaUserLayerModule } from "./userLayers"; ]), QuickTourModule, NehubaLayoutOverlayModule, + DialogModule, ], declarations: [ NehubaViewerContainerDirective, diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.ts index 599720814d262e8ac924146ee52c993f5561fcff..da0efe192ead8500dec3e88916e901707b0f9f06 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.ts @@ -1,20 +1,18 @@ import { Component, - OnInit, - OnChanges, TemplateRef, HostBinding, - Optional, Inject, + inject, } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { LoggingService } from "src/logging"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; -import { Observable, Subscription, of, combineLatest } from "rxjs"; -import { map, filter, startWith, throttleTime } from "rxjs/operators"; +import { Observable, combineLatest } from "rxjs"; +import { map, filter, startWith, throttleTime, takeUntil, switchMap, shareReplay, debounceTime } from "rxjs/operators"; import { Clipboard, MatBottomSheet, MatDialog, MatSnackBar } from "src/sharedModules/angularMaterial.exports" import { ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' -import { UntypedFormControl } from "@angular/forms"; +import { FormControl, FormGroup, UntypedFormControl } from "@angular/forms"; import { NEHUBA_INSTANCE_INJTKN } from '../util' import { IQuickTourData } from "src/ui/quickTour/constrants"; @@ -22,35 +20,100 @@ import { actions } from "src/state/atlasSelection"; import { atlasSelection } from "src/state"; import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { NEHUBA_CONFIG_SERVICE_TOKEN, NehubaConfigSvc } from "../config.service"; +import { DestroyDirective } from "src/util/directives/destroy.directive"; +import { getUuid } from "src/util/fn"; @Component({ selector : 'iav-cmp-viewer-nehuba-status', templateUrl : './statusCard.template.html', styleUrls : ['./statusCard.style.css'], + hostDirectives: [ + DestroyDirective, + ], }) -export class StatusCardComponent implements OnInit, OnChanges{ +export class StatusCardComponent { - private _nehubaViewer: NehubaViewerUnit; + readonly #destroy$ = inject(DestroyDirective).destroyed$ - get nehubaViewer(): NehubaViewerUnit{ - return this._nehubaViewer - } - set nehubaViewer(v: NehubaViewerUnit) { - this._nehubaViewer = v - this.ngOnChanges() - } + public nehubaViewer: NehubaViewerUnit @HostBinding('attr.aria-label') public arialabel = ARIA_LABELS.STATUS_PANEL public showFull = false + public dialogForm = new FormGroup({ + x: new FormControl<string>(null), + y: new FormControl<string>(null), + z: new FormControl<string>(null), + }) + private selectedTemplate: SxplrTemplate - private currentNavigation: any - private subscriptions: Subscription[] = [] + private currentNavigation: { + position: number[]; + orientation: number[]; + zoom: number; + perspectiveOrientation: number[]; + perspectiveZoom: number; +} - public navVal$: Observable<string> - public mouseVal$: Observable<string> + public readonly navVal$ = this.nehubaViewer$.pipe( + filter(v => !!v), + switchMap(nehubaViewer => + combineLatest([ + this.statusPanelRealSpace$, + nehubaViewer.viewerPosInReal$.pipe( + filter(v => !!v) + ), + nehubaViewer.viewerPosInVoxel$.pipe( + filter(v => !!v) + ), + ]).pipe( + map(([realFlag, real, voxel]) => realFlag + ? real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ') + : voxel.map(v => v.toFixed(3)).join(', ') ), + startWith(`nehubaViewer initialising`), + throttleTime(16), + ) + ), + shareReplay(1), + ) + public readonly mouseVal$ = this.nehubaViewer$.pipe( + filter(v => !!v), + switchMap(nehubaViewer => + combineLatest([ + this.statusPanelRealSpace$, + nehubaViewer.mousePosInReal$.pipe( + filter(v => !!v), + ), + nehubaViewer.mousePosInVoxel$.pipe( + filter(v => !!v) + ), + ]).pipe( + map(([realFlag, real, voxel]) => realFlag + ? real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ') + : voxel.map(v => v.toFixed(3)).join(', ')), + startWith(``), + ) + ) + ) + public readonly dialogInputState$ = this.dialogForm.valueChanges.pipe( + map(({ x, y, z }) => { + const allEntries = [x, y, z].map(v => this.#parseString(v)) + return { + validated: allEntries.every(entry => + ( + entry.length === 1 + && !Number.isNaN(entry[0]) + ) + ), + valueMm: allEntries.map(entry => entry[0]), + valueNm: allEntries.map(entry => entry[0]).map(v => v*1e6), + string: allEntries.map(entry => `${entry[0]}mm`).join(", "), + } + }), + shareReplay(1) + ) public quickTourData: IQuickTourData = { description: QUICKTOUR_DESC.STATUS_CARD, @@ -69,82 +132,71 @@ export class StatusCardComponent implements OnInit, OnChanges{ private clipboard: Clipboard, private snackbar: MatSnackBar, @Inject(NEHUBA_CONFIG_SERVICE_TOKEN) private nehubaConfigSvc: NehubaConfigSvc, - @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>, + @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaViewer$: Observable<NehubaViewerUnit>, ) { - - if (nehubaViewer$) { - this.subscriptions.push( - nehubaViewer$.subscribe( - viewer => this.nehubaViewer = viewer - ) - ) - } else { - this.log.warn(`NEHUBA_INSTANCE_INJTKN not injected!`) - } - } - - ngOnInit(): void { - this.subscriptions.push( - this.statusPanelFormCtrl.valueChanges.subscribe(val => { - this.statusPanelRealSpace = val - }) + nehubaViewer$.pipe( + takeUntil(this.#destroy$) + ).subscribe( + viewer => this.nehubaViewer = viewer ) + this.statusPanelFormCtrl.valueChanges.pipe( + takeUntil(this.#destroy$) + ).subscribe(val => { + this.statusPanelRealSpace = val + }) + this.store$.pipe( + select(atlasSelection.selectors.navigation) + ).pipe( + takeUntil(this.#destroy$) + ).subscribe(nav => this.currentNavigation = nav) - this.subscriptions.push( - this.store$.pipe( - select(atlasSelection.selectors.selectedTemplate) - ).subscribe(n => this.selectedTemplate = n) - ) + this.nehubaViewer$.pipe( + filter(nv => !!nv), + switchMap(nv => nv.viewerPosInReal$.pipe( + filter(pos => !!pos), + debounceTime(120), + )), + takeUntil(this.#destroy$) + ).subscribe(val => { + const [x, y, z] = val.map(v => (v/1e6).toFixed(3)) + this.dialogForm.setValue({ x, y, z }) + }) - this.subscriptions.push( - this.store$.pipe( - select(atlasSelection.selectors.navigation) - ).subscribe(nav => this.currentNavigation = nav) - ) - } + this.store$.pipe( + select(atlasSelection.selectors.selectedTemplate) + ).pipe( + takeUntil(this.#destroy$) + ).subscribe(n => this.selectedTemplate = n) - ngOnChanges(): void { - if (this.nehubaViewer?.viewerPosInReal$ && this.nehubaViewer?.viewerPosInVoxel$) { - this.navVal$ = combineLatest([ - this.statusPanelRealSpace$, - this.nehubaViewer.viewerPosInReal$.pipe( - filter(v => !!v) - ), - this.nehubaViewer.viewerPosInVoxel$.pipe( - filter(v => !!v) - ) - ]).pipe( - map(([realFlag, real, voxel]) => realFlag - ? real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ') - : voxel.map(v => v.toFixed(3)).join(', ') ), - startWith(`nehubaViewer initialising`) - ) - } else { - this.navVal$ = of(`neubaViewer is undefined`) - } + this.dialogInputState$.pipe( + takeUntil(this.#destroy$) + ).subscribe() - if ( this.nehubaViewer?.mousePosInReal$ && this.nehubaViewer?.mousePosInVoxel$ ) { + this.dialogForm.valueChanges.pipe( + map(({ x, y, z }) => [x, y, z].map(v => this.#parseString(v))), + map(allEntries => allEntries.find(val => val.length === 3)), + filter(fullEntry => !!fullEntry && fullEntry.every(entry => !Number.isNaN(entry))), + takeUntil(this.#destroy$) + ).subscribe(fullEntry => { + this.dialogForm.setValue({ + x: `${fullEntry[0]}`, + y: `${fullEntry[1]}`, + z: `${fullEntry[2]}`, + }) + }) - this.mouseVal$ = combineLatest([ - this.statusPanelRealSpace$, - this.nehubaViewer.mousePosInReal$.pipe( - filter(v => !!v), - throttleTime(16) - ), - this.nehubaViewer.mousePosInVoxel$.pipe( - filter(v => !!v), - throttleTime(16) - ) - ]).pipe( - map(([realFlag, real, voxel]) => realFlag - ? real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ') - : voxel.map(v => v.toFixed(3)).join(', ')), - startWith(``) - ) - } else { - this.mouseVal$ = of(`neubaViewer is undefined`) - } + } + #parseString(input: string): number[]{ + return input + .split(/[\s|,]+/) + .map(v => { + if (/mm$/.test(v)) { + return v.replace(/mm$/, "") + } + return v + }) + .map(Number) } public statusPanelFormCtrl = new UntypedFormControl(true, []) @@ -165,6 +217,41 @@ export class StatusCardComponent implements OnInit, OnChanges{ } } + public selectPoint(pos: number[]) { + this.store$.dispatch( + atlasSelection.actions.selectPoint({ + point: { + "@type": "https://openminds.ebrains.eu/sands/CoordinatePoint", + "@id": getUuid(), + coordinateSpace: { + "@id": this.selectedTemplate.id + }, + coordinates: pos.map(v => ({ + "@id": getUuid(), + "@type": "https://openminds.ebrains.eu/core/QuantitativeValue", + unit: { + "@id": "id.link/mm" + }, + value: v * 1e6, + uncertainty: [0, 0] + })) + } + }) + ) + } + + public navigateTo(pos: number[], positionReal=true) { + this.store$.dispatch( + atlasSelection.actions.navigateTo({ + navigation: { + position: pos + }, + physical: positionReal, + animation: true + }) + ) + } + showBottomSheet(tmpl: TemplateRef<any>): void{ this.bottomSheet.open(tmpl) } diff --git a/src/viewerModule/nehuba/statusCard/statusCard.style.css b/src/viewerModule/nehuba/statusCard/statusCard.style.css index a731c42cd30010291fafbd631db8deb9de5257ee..62feb3a8f17faecbd6dff3d1d0782afdfc0002e6 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.style.css +++ b/src/viewerModule/nehuba/statusCard/statusCard.style.css @@ -1,19 +1,3 @@ -div[linksContainer] -{ - display:inline-block; - margin-left:1em; -} - -div[textContainer] -{ - padding-left:1em; -} - -small[onHoverSegment] -{ - margin-left:2em; -} - .expandedContainer { width: 26rem; diff --git a/src/viewerModule/nehuba/statusCard/statusCard.template.html b/src/viewerModule/nehuba/statusCard/statusCard.template.html index 4d6ca08573c522ac291a9d0cb219449a8d65cf95..274b4473881cbbba4c266fb506e6947597aa80b7 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.template.html +++ b/src/viewerModule/nehuba/statusCard/statusCard.template.html @@ -45,22 +45,6 @@ </button> </div> - <!-- space --> - <div class="d-flex"> - <span class="d-flex align-items-center"> - Voxel space - </span> - - <mat-slide-toggle - [formControl]="statusPanelFormCtrl" - class="sxplr-pl-2 sxplr-pr-2"> - </mat-slide-toggle> - - <span class="d-flex align-items center"> - Physical space - </span> - </div> - <!-- coord --> <div class="d-flex"> @@ -121,15 +105,11 @@ {{ navVal$ | async }} </span> - <ng-template [ngIf]="navVal$ | async" let-navVal> - - <button mat-icon-button - [attr.aria-label]="COPY_NAVIGATION_STRING" - (click)="copyString(navVal)"> - <i class="fas fa-copy"></i> - </button> - - </ng-template> + <button mat-icon-button + [sxplr-dialog-size]="null" + [sxplr-dialog]="pointTmpl"> + <i class="fas fa-pen"></i> + </button> <button mat-icon-button [attr.aria-label]="SHOW_FULL_STATUS_PANEL_ARIA_LABEL" @@ -138,3 +118,49 @@ </button> </div> </ng-template> + +<ng-template #pointTmpl> + <h1 mat-dialog-title> + Navigation Coordinate + </h1> + <div mat-dialog-content> + <form [formGroup]="dialogForm"> + <ng-template ngFor [ngForOf]="['x', 'y', 'z']" let-pos> + + <mat-form-field> + <mat-label>{{ pos }} (mm)</mat-label> + <input type="text" matInput [formControlName]="pos"> + </mat-form-field> + </ng-template> + </form> + </div> + + <div mat-dialog-actions align="end"> + + <ng-template [ngIf]="dialogInputState$ | async" let-state> + + <button mat-raised-button color="primary" + (click)="selectPoint(state.valueMm)" + [disabled]="!state.validated"> + select point + </button> + + <button mat-button color="primary" + (click)="navigateTo(state.valueNm)" + [disabled]="!state.validated"> + navigate to point + </button> + + <button mat-button color="primary" + [attr.aria-label]="COPY_NAVIGATION_STRING" + (click)="copyString(state.string)" + [disabled]="!state.validated"> + copy point + </button> + + </ng-template> + <button mat-button mat-dialog-close> + cancel + </button> + </div> +</ng-template> \ No newline at end of file diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index eb91a6b8b31ce641966d34374d6dbc28282cce31..4b3b162667a8ab30883abf5e86cb35ac1ed79249 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -1,9 +1,9 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild, inject } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, Observable, of, Subscription } from "rxjs"; -import { debounceTime, map, shareReplay, switchMap } from "rxjs/operators"; +import { BehaviorSubject, combineLatest, Observable, of, Subscription } from "rxjs"; +import { debounceTime, distinctUntilChanged, map, shareReplay, switchMap, takeUntil } from "rxjs/operators"; import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' -import { animate, state, style, transition, trigger } from "@angular/animations"; +import { AnimationEvent, animate, state, style, transition, trigger } from "@angular/animations"; import { IQuickTourData } from "src/ui/quickTour"; import { EnumViewerEvt, TContextArg, TSupportedViewers, TViewerEvent } from "../viewer.interface"; import { ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; @@ -15,6 +15,8 @@ import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { EntryComponent } from "src/features/entry/entry.component"; import { TFace, TSandsPoint, getCoord } from "src/util/types"; import { wait } from "src/util/fn"; +import { DestroyDirective } from "src/util/directives/destroy.directive"; +import { MatDrawer } from "@angular/material/sidenav"; interface HasName { name: string @@ -44,29 +46,20 @@ interface HasName { animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') ]) ]), - trigger('openCloseAnchor', [ - state('open', style({ - transform: 'translateY(0)' - })), - state('closed', style({ - transform: 'translateY(100vh)' - })), - transition('open => closed', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]), - transition('closed => open', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]) - ]), ], providers: [ DialogService ], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + hostDirectives: [ + DestroyDirective + ] }) export class ViewerCmp implements OnDestroy { + public readonly destroy$ = inject(DestroyDirective).destroyed$ + @ViewChild('voiFeatureEntryCmp', { read: EntryComponent }) voiCmp: EntryComponent @@ -149,7 +142,9 @@ export class ViewerCmp implements OnDestroy { ]).pipe( switchMap(([tmpl, parc]) => tmpl && parc ? this.sapi.getLabelledMap(parc, tmpl) : of(null)) ) - + + #fullNavBarSwitch$ = new BehaviorSubject<boolean>(false) + #halfNavBarSwitch$ = new BehaviorSubject<boolean>(true) #view0$ = combineLatest([ this.#selectedRegions$, @@ -167,9 +162,11 @@ export class ViewerCmp implements OnDestroy { #view1$ = combineLatest([ this.#currentMap$, this.allAvailableRegions$, + this.#fullNavBarSwitch$, + this.#halfNavBarSwitch$, ]).pipe( - map(( [ currentMap, allAvailableRegions ] ) => ({ - currentMap, allAvailableRegions + map(( [ currentMap, allAvailableRegions, fullSidenavExpanded, halfSidenavExpanded ] ) => ({ + currentMap, allAvailableRegions, fullSidenavExpanded, halfSidenavExpanded })) ) @@ -178,7 +175,7 @@ export class ViewerCmp implements OnDestroy { this.#view1$, ]).pipe( map(([v0, v1]) => ({ ...v0, ...v1 })), - map(({ selectedRegions, viewerMode, selectedFeature, selectedPoint, selectedTemplate, selectedParcellation, currentMap, allAvailableRegions }) => { + map(({ selectedRegions, viewerMode, selectedFeature, selectedPoint, selectedTemplate, selectedParcellation, currentMap, allAvailableRegions, fullSidenavExpanded, halfSidenavExpanded }) => { let spatialObjectTitle: string let spatialObjectSubtitle: string if (selectedPoint) { @@ -221,7 +218,9 @@ export class ViewerCmp implements OnDestroy { * and the full left side bar should not be expandable * if it is already expanded, it should collapse */ - onlyShowMiniTray: selectedRegions.length === 0 && !viewerMode && !selectedFeature && !selectedPoint + onlyShowMiniTray: selectedRegions.length === 0 && !viewerMode && !selectedFeature && !selectedPoint, + fullSidenavExpanded, + halfSidenavExpanded, } }), shareReplay(1), @@ -237,7 +236,6 @@ export class ViewerCmp implements OnDestroy { private viewerStatusRegionCtxMenu: TemplateRef<any> private templateSelected: SxplrTemplate - #parcellationSelected: SxplrParcellation constructor( private store$: Store<any>, @@ -247,11 +245,59 @@ export class ViewerCmp implements OnDestroy { private sapi: SAPI, ){ + this.view$.pipe( + takeUntil(this.destroy$), + map(({ selectedFeature, selectedPoint, selectedRegions, viewerMode }) => ({ + selectedFeature, + selectedPoint, + selectedRegions, + viewerMode, + })), + distinctUntilChanged((o, n) => { + if (o.viewerMode !== n.viewerMode) { + return false + } + if (o.selectedFeature?.id !== n.selectedFeature?.id) { + return false + } + if (o.selectedPoint?.["@type"] !== n.selectedPoint?.["@type"]) { + return false + } + if ( + n.selectedPoint?.["@type"] === "https://openminds.ebrains.eu/sands/CoordinatePoint" + && o.selectedPoint?.["@type"] === "https://openminds.ebrains.eu/sands/CoordinatePoint" + ) { + const newCoords = n.selectedPoint.coordinates.map(v => v.value) + const oldCoords = o.selectedPoint.coordinates.map(v => v.value) + if ([0, 1, 2].some(idx => newCoords[idx] !== oldCoords[idx])) { + return false + } + } + if (o.selectedRegions.length !== n.selectedRegions.length) { + return false + } + const oldRegNames = o.selectedRegions.map(r => r.name) + const newRegName = n.selectedRegions.map(r => r.name) + if (oldRegNames.some(name => !newRegName.includes(name))) { + return false + } + return true + }), + debounceTime(16), + map(({ selectedFeature, selectedRegions, selectedPoint, viewerMode }) => { + return !!viewerMode + || !!selectedFeature + || selectedRegions.length > 0 + || !!selectedPoint + }) + ).subscribe(flag => { + this.#fullNavBarSwitch$.next(flag) + }) + this.subscriptions.push( this.templateSelected$.subscribe( t => this.templateSelected = t ), - this.#parcellationSelected$.subscribe(parc => this.#parcellationSelected = parc), combineLatest([ this.templateSelected$, this.parcellationSelected$, @@ -530,6 +576,25 @@ export class ViewerCmp implements OnDestroy { ) } + controlFullNav(flag: boolean){ + this.#fullNavBarSwitch$.next(flag) + } + + controlHalfNav(flag: boolean) { + this.#halfNavBarSwitch$.next(flag) + if (flag && this.#fullyRestoreToken) { + this.#fullNavBarSwitch$.next(flag) + this.#fullyRestoreToken = false + } + } + + #fullyRestoreToken = false + fullyClose(){ + this.#fullyRestoreToken = true + this.controlFullNav(false) + this.controlHalfNav(false) + } + nameEql(a: HasName, b: HasName){ return a.name === b.name } diff --git a/src/viewerModule/viewerCmp/viewerCmp.style.css b/src/viewerModule/viewerCmp/viewerCmp.style.css index 13f9f9906cce4a7d44d78ea843912dd5083fb4ab..d72febea43178d6447b9c113f66ccb4ca905e2e3 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.style.css +++ b/src/viewerModule/viewerCmp/viewerCmp.style.css @@ -10,24 +10,6 @@ top: 0.5rem; } -.tab-toggle-container -{ - margin-top: 1.5rem; -} - -.tab-toggle-container > * -{ - display: block; -} - -.tab-toggle -{ - margin-left: -1rem; - padding-right: 0.75rem; - margin-right: 1rem; - text-align: right; -} - .accordion-icon { width:1.5rem; @@ -90,10 +72,25 @@ mat-drawer pointer-events: none; } -logo-container +.logo-container { - height: 2rem; + position: fixed; + left: 50%; + bottom: 8rem; + width: 0; + height: 0; + pointer-events: none; opacity: 0.2; + display: flex; + align-items: center; + justify-content: center; + overflow: visible; +} +.logo-container > logo-container +{ + width: 2rem; + height: 2rem; + display: block; } mat-list.contextual-block @@ -158,11 +155,6 @@ sxplr-sapiviews-core-region-region-list-item align-items: center; } -.context-menu-container -{ - margin: 0 -16px; -} - .centered { display: flex; @@ -181,3 +173,25 @@ sxplr-sapiviews-core-region-region-list-item display: flex; align-items: center; } + +mat-card-content.context-menu-container +{ + display: flex; + flex-direction: column; + padding: 0; +} + +sxplr-tab +{ + pointer-events: all; + margin-left: -1rem; + margin-top: 2rem; + margin-right: 1rem; + display: inline-block; +} + +.special-mode-topleft-wrapper +{ + display: inline-flex; + flex-direction: column; +} \ No newline at end of file diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 118e5c2892d7498ddb47bb3d5d740666aee18160..a2fa3860b81f087d43109dc92fba2ef1cb696c5e 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -4,10 +4,12 @@ <div class="floating-ui"> - <div *ngIf="(media.mediaBreakPoint$ | async) < 2" - class="fixed-bottom sxplr-pe-none sxplr-mb-10 d-flex justify-content-center"> - <logo-container></logo-container> - </div> + <ng-template [ngIf]="(media.mediaBreakPoint$ | async) < 2"> + <div class="logo-container"> + <logo-container></logo-container> + </div> + </ng-template> + <div *ngIf="(media.mediaBreakPoint$ | async) < 2" floatingMouseContextualContainerDirective @@ -29,126 +31,109 @@ <!-- master draw container --> <ng-template [ngIf]="view$ | async" let-view> -<mat-drawer-container - *ngIf="viewerLoaded" - iav-switch - [iav-switch-state]="!(view.onlyShowMiniTray)" - #showFullSidenavSwitch="iavSwitch" - class="position-absolute w-100 h-100 mat-drawer-content-overflow-visible invisible" - [hasBackdrop]="false"> - - <!-- master drawer --> - <mat-drawer - mode="side" - #drawer="matDrawer" - [opened]="!(view.onlyShowMiniTray)" - [@openClose]="showFullSidenavSwitch && (showFullSidenavSwitch.switchState$ | async) ? 'open' : 'closed'" - (@openClose.start)="$event.toState === 'open' && drawer.open()" - (@openClose.done)="$event.toState === 'closed' && drawer.close()" - [autoFocus]="false" - [disableClose]="true" - class="sxplr-custom-cmp darker-bg sxplr-p-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2 z-index-10"> - - <!-- entry template --> - <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="regularTmpl"> - <ng-template [ngTemplateOutlet]="alternateModeDrawerTmpl" - [ngTemplateOutletContext]="{ - mode: mode - }"></ng-template> - </ng-template> + <mat-drawer-container + *ngIf="viewerLoaded" + class="position-absolute w-100 h-100 mat-drawer-content-overflow-visible invisible" + [hasBackdrop]="false"> + + <!-- master drawer --> + <mat-drawer + mode="side" + #drawer="matDrawer" + [@openClose]="view.fullSidenavExpanded ? 'open' : 'closed'" + (@openClose.start)="$event.toState === 'open' && drawer.open()" + (@openClose.done)="$event.toState === 'closed' && drawer.close()" + [autoFocus]="false" + [disableClose]="true" + class="sxplr-custom-cmp darker-bg sxplr-p-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2 z-index-10"> + + <!-- entry template --> + <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="regularTmpl"> + <ng-template [ngTemplateOutlet]="alternateModeDrawerTmpl" + [ngTemplateOutletContext]="{ + mode: mode + }"></ng-template> + </ng-template> - <!-- regular mode --> - <ng-template #regularTmpl> - <ng-template - [ngTemplateOutlet]="regularModeDrawerTmpl" - [ngTemplateOutletContext]="{ - drawer: drawer, - showFullSidenavSwitch: showFullSidenavSwitch - }"> + <!-- regular mode --> + <ng-template #regularTmpl> + <ng-template [ngTemplateOutlet]="regularModeDrawerTmpl"> + </ng-template> </ng-template> - </ng-template> - </mat-drawer> + </mat-drawer> - <!-- master content --> - <mat-drawer-content class="visible sxplr-pe-none position-relative"> - <iav-layout-fourcorners> + <!-- master content --> + <mat-drawer-content class="visible sxplr-pe-none position-relative"> + <iav-layout-fourcorners> - <!-- top left --> - <div iavLayoutFourCornersTopLeft class="ws-no-wrap align-items-start d-inline-flex"> + <!-- top left --> + <div iavLayoutFourCornersTopLeft class="ws-no-wrap align-items-start d-inline-flex"> - <!-- special mode --> - <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="defaultTopLeftTmpl"> - <ng-template [ngTemplateOutlet]="specialModeTopLeftTmpl" - [ngTemplateOutletContext]="{ - mode: mode, - toggleMatDrawer: drawer.toggle.bind(drawer) - }"> + <!-- special mode --> + <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="defaultTopLeftTmpl"> + <ng-template [ngTemplateOutlet]="specialModeTopLeftTmpl" + [ngTemplateOutletContext]="{ mode: mode }"> + </ng-template> </ng-template> - </ng-template> - <!-- default mode top left tmpl --> - <ng-template #defaultTopLeftTmpl> - <ng-template [ngTemplateOutlet]="defaultMainContentTopLeft" - [ngTemplateOutletContext]="{ - isOpen: drawer.opened, - drawer: drawer, - showFullSidenavSwitch: showFullSidenavSwitch - }"> + <!-- default mode top left tmpl --> + <ng-template #defaultTopLeftTmpl> + <ng-template [ngTemplateOutlet]="defaultMainContentTopLeft" + [ngTemplateOutletContext]="{ + isOpen: drawer.opened, + drawer: drawer, + view: view + }"> + </ng-template> </ng-template> - </ng-template> - </div> + </div> - <!-- top right --> - <div iavLayoutFourCornersTopRight class="ws-no-wrap"> + <!-- top right --> + <div iavLayoutFourCornersTopRight class="ws-no-wrap"> - <!-- exit special mode --> - <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="defaultTopRightTmpl"> - <ng-template [ngTemplateOutlet]="specialTopRightTmpl" - [ngTemplateOutletContext]="{ - mode: mode - }"> + <!-- exit special mode --> + <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="defaultTopRightTmpl"> + <ng-template [ngTemplateOutlet]="specialTopRightTmpl" + [ngTemplateOutletContext]="{ + mode: mode + }"> + </ng-template> </ng-template> - </ng-template> - - <!-- default mode top right tmpl --> - <ng-template #defaultTopRightTmpl> - <ng-template [ngTemplateOutlet]="minDefaultMainContentTopRight"> + + <!-- default mode top right tmpl --> + <ng-template #defaultTopRightTmpl> + <ng-template [ngTemplateOutlet]="minDefaultMainContentTopRight"> + </ng-template> </ng-template> - </ng-template> - </div> - + </div> - <!-- bottom left --> - <div iavLayoutFourCornersBottomLeft class="ws-no-wrap d-inline-flex w-100vw sxplr-pe-none align-items-center mb-4"> - <!-- special bottom left --> - <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="localBottomLeftTmpl"></ng-template> - - <!-- default mode bottom left tmpl --> - <ng-template #localBottomLeftTmpl> + <!-- bottom left --> + <div iavLayoutFourCornersBottomLeft class="ws-no-wrap d-inline-flex w-100vw sxplr-pe-none align-items-center mb-4"> - <!-- not the most elegant, but it's a hard problem to solve --> - <!-- on the one hand, showFullSidenavSwitch can be of two states --> - <!-- and drawer.opened can be of two states --> - <ng-template [ngTemplateOutlet]="bottomLeftTmpl" - [ngTemplateOutletContext]="{ - showFullSideNav: (showFullSidenavSwitch.switchState$ | async) - ? drawer.open.bind(drawer) - : showFullSidenavSwitch.open.bind(showFullSidenavSwitch) - }"> + <!-- special bottom left --> + <ng-template [ngIf]="view.viewerMode" let-mode [ngIfElse]="localBottomLeftTmpl"></ng-template> + + <!-- default mode bottom left tmpl --> + <ng-template #localBottomLeftTmpl> + + <!-- not the most elegant, but it's a hard problem to solve --> + <!-- on the one hand, showFullSidenavSwitch can be of two states --> + <!-- and drawer.opened can be of two states --> + <ng-template [ngTemplateOutlet]="bottomLeftTmpl"> + </ng-template> </ng-template> - </ng-template> - - </div> + + </div> - <!-- buttom right --> - <div iavLayoutFourCornersBottomRight> - <div class="leap-control-wrapper"> - <div leap-control-view-ref></div> + <!-- buttom right --> + <div iavLayoutFourCornersBottomRight> + <div class="leap-control-wrapper"> + <div leap-control-view-ref></div> + </div> </div> - </div> - </iav-layout-fourcorners> - </mat-drawer-content> + </iav-layout-fourcorners> + </mat-drawer-content> </mat-drawer-container> </ng-template> @@ -166,9 +151,7 @@ <!-- regular mode drawer tmpl --> -<ng-template #regularModeDrawerTmpl - let-drawer="drawer" - let-showFullSidenavSwitch="showFullSidenavSwitch"> +<ng-template #regularModeDrawerTmpl> <!-- selectedFeature || selectedRegion --> <ng-template [ngIf]="view$ | async" let-view> @@ -198,10 +181,7 @@ <ng-template [ngIf]="!view.selectedFeature && !view.selectedPoint"> <ng-template [ngTemplateOutlet]="sidenavRegionTmpl" - [ngTemplateOutletContext]="{ - view: view, - showFullSidenavSwitch: showFullSidenavSwitch - }"> + [ngTemplateOutletContext]="{ view: view }"> </ng-template> </ng-template> @@ -210,25 +190,20 @@ <!-- minimal default drawer content --> -<ng-template #minSearchTray - let-showFullSidenav="showFullSidenav" - let-drawer="drawer"> +<ng-template #minSearchTray> + <ng-template [ngIf]="view$ | async" let-view> - <div class="mt-2 d-inline-block vw-col-10 vw-col-sm-10 vw-col-md-5 vw-col-lg-4 vw-col-xl-3 vw-col-xxl-2" - iav-switch - [iav-switch-state]="true" - #minTrayVisSwitch="iavSwitch" - [ngClass]="{ - 'vw-col-10-nm vw-col-sm-10-nm vw-col-md-5-nm vw-col-lg-4-nm vw-col-xl-3-nm vw-col-xxl-2-nm': !(minTrayVisSwitch.switchState$ | async), - 'transition-margin-left': !drawer.opened - }"> + <div class="mt-2 d-inline-block vw-col-10 vw-col-sm-10 vw-col-md-5 vw-col-lg-4 vw-col-xl-3 vw-col-xxl-2 z-index-1" + [ngClass]="{ + 'vw-col-10-nm vw-col-sm-10-nm vw-col-md-5-nm vw-col-lg-4-nm vw-col-xl-3-nm vw-col-xxl-2-nm': !view.halfSidenavExpanded, + 'transition-margin-left': !view.fullSidenavExpanded + }"> - <!-- collapsed side bar view --> - <div class="h-0 w-100 region-text-search-autocomplete-position"> - <ng-container *ngTemplateOutlet="autocompleteTmpl; context: { showTour: true }"> - </ng-container> - - <ng-template [ngIf]="view$ | async" let-view> + <!-- collapsed side bar view --> + <div class="h-0 w-100 region-text-search-autocomplete-position"> + <ng-container *ngTemplateOutlet="autocompleteTmpl; context: { showTour: true }"> + </ng-container> + <!-- if no selected regions, show spatial search --> <div *ngIf="(view.selectedRegions || []).length === 0" class="sxplr-mt-1 w-100"> <ng-template @@ -238,12 +213,10 @@ }"> </ng-template> </div> - </ng-template> - </div> + </div> - <!-- such a gross implementation --> - <!-- TODO fix this --> - <ng-template [ngIf]="view$ | async" let-view> + <!-- such a gross implementation --> + <!-- TODO fix this --> <div class="min-tray-explr-btn" sxplr-sapiviews-core-region @@ -258,7 +231,7 @@ <button mat-raised-button *ngIf="!(view$ | async | getProperty : 'onlyShowMiniTray')" [attr.aria-label]="ARIA_LABELS.EXPAND" - (click)="showFullSidenav()" + (click)="controlFullNav(!view.fullSidenavExpanded)" class="sxplr-mt-9 sxplr-pe-all w-100" [ngClass]="{ 'darktheme': sapiRegion.regionDarkmode, @@ -266,51 +239,45 @@ }" [style.backgroundColor]="sapiRegion.regionRgbString"> <span class="text sxplr-custom-cmp"> - Explore Foo + Explore </span> </button> </div> - </ng-template> - - </div> - - <!-- tab to minimize mini tray --> - - <div class="tab-toggle-container" [ngClass]="(minTrayVisSwitch.switchState$ | async) ? '' : 'd-none'"> - - <ng-template [ngTemplateOutlet]="tabTmpl_defaultTmpl" [ngTemplateOutletContext]="{ - fontIcon: 'fas fa-chevron-left', - matColor: null, - click: minTrayVisSwitch.toggle.bind(minTrayVisSwitch) - }"> - </ng-template> - </div> - - <div class="tab-toggle-container" [ngClass]="(minTrayVisSwitch.switchState$ | async) ? 'd-none' : ''"> + + </div> - <ng-template [ngIf]="voiFeatureEntryCmp && (voiFeatureEntryCmp.totals$ | async)" - [ngIfElse]="noBadgeTmpl" - let-totals> + <!-- tab to minimize mini tray --> - <ng-template [ngTemplateOutlet]="tabTmpl_defaultTmpl" [ngTemplateOutletContext]="{ - fontIcon: 'fas fa-search', - matColor: 'primary', - click: minTrayVisSwitch.toggle.bind(minTrayVisSwitch), - badge: totals - }"> + <div [ngClass]="view.halfSidenavExpanded ? '' : 'd-none'"> + <sxplr-tab + sxplr-tab-icon="fas fa-chevron-left" + (sxplr-tab-click)="controlHalfNav(!view.halfSidenavExpanded)"> + </sxplr-tab> + </div> + + <div [ngClass]="view.halfSidenavExpanded ? 'd-none' : ''"> + + <ng-template [ngIf]="voiFeatureEntryCmp && (voiFeatureEntryCmp.totals$ | async)" + [ngIfElse]="noBadgeTmpl" + let-totals> + <sxplr-tab + sxplr-tab-icon="fas fa-search" + sxplr-tab-color="primary" + (sxplr-tab-click)="controlHalfNav(!view.halfSidenavExpanded)" + [sxplr-tab-badge]="totals"> + </sxplr-tab> </ng-template> - </ng-template> - <ng-template #noBadgeTmpl> + <ng-template #noBadgeTmpl> - <ng-template [ngTemplateOutlet]="tabTmpl_defaultTmpl" [ngTemplateOutletContext]="{ - fontIcon: 'fas fa-search', - matColor: 'primary', - click: minTrayVisSwitch.toggle.bind(minTrayVisSwitch) - }"> + <sxplr-tab + sxplr-tab-icon="fas fa-search" + sxplr-tab-color="primary" + (sxplr-tab-click)="controlHalfNav(!view.halfSidenavExpanded)"> + </sxplr-tab> </ng-template> - </ng-template> - </div> + </div> + </ng-template> </ng-template> @@ -318,37 +285,27 @@ <!-- top left --> <!-- default top left --> <ng-template #defaultMainContentTopLeft - let-isOpen="isOpen" let-drawer="drawer" - let-showFullSidenavSwitch="showFullSidenavSwitch"> + let-view="view"> <!-- min search tray --> - <ng-template [ngIf]="!(showFullSidenavSwitch.switchState$ | async)"> - <ng-template - [ngTemplateOutlet]="minSearchTray" - [ngTemplateOutletContext]="{ - showFullSidenav: showFullSidenavSwitch.open.bind(showFullSidenavSwitch), - drawer: drawer - }"> + <ng-template [ngIf]="!(view.fullSidenavExpanded)"> + <ng-template [ngTemplateOutlet]="minSearchTray"> </ng-template> </ng-template> <!-- pullable tab top left corner --> - <ng-template [ngIf]="view$ | async" let-view> - - <div *ngIf="showFullSidenavSwitch.switchState$ | async" - class="v-align-top pe-all tab-toggle-container d-inline-block" - (click)="drawer.toggle()" + <ng-template [ngIf]="view.fullSidenavExpanded"> + <sxplr-tab + (sxplr-tab-click)="fullyClose()" + sxplr-tab-icon="fas fa-brain" + quick-tour [quick-tour-description]="quickTourRegionSearch.description" [quick-tour-order]="quickTourRegionSearch.order"> - <ng-container *ngTemplateOutlet="tabTmpl; context: { - isOpen: isOpen, - view: view - }"> - </ng-container> - </div> + + </sxplr-tab> </ng-template> <!-- status panel for (for nehuba viewer) --> @@ -366,41 +323,36 @@ <!-- special mode top left --> -<ng-template #specialModeTopLeftTmpl - let-mode="mode" - let-toggleMatDrawer="toggleMatDrawer"> - - <div class="tab-toggle-container"> - - <ng-container [ngSwitch]="mode"> - <!-- annotating top left --> - <ng-template [ngSwitchCase]="ARIA_LABELS.VIEWER_MODE_ANNOTATING"> - <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { - matColor: 'primary', - fontIcon: 'fa-list', - tooltip: 'Annotation list', - click: toggleMatDrawer, - badge: toolPanel?.annBadges$ | async - }"> - </ng-container> - - <annotating-tools-panel class="z-index-10 leave-me-alone" - #toolPanel="annoToolsPanel"> - </annotating-tools-panel> - </ng-template> - - <ng-template [ngSwitchCase]="ARIA_LABELS.VIEWER_MODE_KEYFRAME"> +<ng-template #specialModeTopLeftTmpl let-mode="mode"> + <ng-template [ngIf]="view$ | async" let-view> - <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { - matColor: 'primary', - fontIcon: 'fa-play', - tooltip: 'Annotation list', - click: toggleMatDrawer - }"> - </ng-container> - </ng-template> - </ng-container> - </div> + <div class="special-mode-topleft-wrapper"> + + <ng-container [ngSwitch]="mode"> + <!-- annotating top left --> + <ng-template [ngSwitchCase]="ARIA_LABELS.VIEWER_MODE_ANNOTATING"> + <sxplr-tab + sxplr-tab-icon="fas fa-list" + sxplr-tab-color="primary" + (sxplr-tab-click)="controlFullNav(!view.fullSidenavExpanded)" + [sxplr-tab-badge]="toolPanel?.annBadges$ | async"> + </sxplr-tab> + + <annotating-tools-panel class="z-index-10 leave-me-alone" + #toolPanel="annoToolsPanel"> + </annotating-tools-panel> + </ng-template> + + <ng-template [ngSwitchCase]="ARIA_LABELS.VIEWER_MODE_KEYFRAME"> + <sxplr-tab + sxplr-tab-icon="fas fa-play" + sxplr-tab-color="primary" + (sxplr-tab-click)="controlFullNav(!view.fullSidenavExpanded)"> + </sxplr-tab> + </ng-template> + </ng-container> + </div> + </ng-template> </ng-template> @@ -440,7 +392,7 @@ </ng-template> <!-- bottom left --> -<ng-template #bottomLeftTmpl let-showFullSideNav="showFullSideNav"> +<ng-template #bottomLeftTmpl> <ng-template [ngIf]="view$ | async" let-view> @@ -453,7 +405,7 @@ sxplr-of-y-hidden sxplr-align-items-stretch"> - <sxplr-bottom-menu (onRegionClick)="showFullSideNav()"></sxplr-bottom-menu> + <sxplr-bottom-menu (onRegionClick)="controlHalfNav(true); controlFullNav(true)"></sxplr-bottom-menu> </div> @@ -591,119 +543,8 @@ </div> </ng-template> -<!-- template for rendering tab --> -<ng-template #tabTmpl - let-isOpen="isOpen" - let-iavAdditionallayers="iavAdditionallayers" - let-click="click" - let-badge="badge" - let-view="view"> - - <!-- if mat drawer is open --> - <ng-template [ngIf]="isOpen" [ngIfElse]="tabTmpl_closedTmpl"> - <ng-template [ngTemplateOutlet]="tabTmpl_defaultTmpl" - [ngTemplateOutletContext]="{ - fontIcon: 'fa-chevron-left', - click: click, - badge: badge - }"> - </ng-template> - </ng-template> - - <!-- if matdrawer is closed --> - <ng-template #tabTmpl_closedTmpl> - - <!-- if additional layers are being shown --> - <ng-template [ngIf]="iavAdditionallayers?.length > 0" [ngIfElse]="tabTmpl_noAdditionalLayers"> - <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { - matColor: 'accent', - fontIcon: 'fa-database', - tooltip: 'Explore dataset preview', - click: click - }"> - </ng-container> - </ng-template> - - <!-- if additional layers not not being shown --> - <ng-template #tabTmpl_noAdditionalLayers> - - <!-- if region selected === 1 --> - <ng-template [ngIf]="view.regionSelected?.length === 1" [ngIfElse]="tabTmpl_nothingSelected"> - - <div sxplr-sapiviews-core-region - [sxplr-sapiviews-core-region-detail-flag]="true" - [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" - [sxplr-sapiviews-core-region-template]="view.selectedTemplate" - [sxplr-sapiviews-core-region-parcellation]="view.selectedParcellation" - [sxplr-sapiviews-core-region-region]="view.regionSelected[0]" - #tabTmpl_iavRegion="sapiViewsCoreRegion"> - - </div> - - <!-- TODO fix with sapiView/core/region directive --> - <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { - matColor: 'accent', - customColor: tabTmpl_iavRegion.regionRgbString, - customColorDarkmode: tabTmpl_iavRegion.regionDarkmode, - fontIcon: 'fa-brain', - tooltip: 'Explore ' + view.regionSelected[0].name, - click: click - }"> - - </ng-container> - </ng-template> - - <!-- nothing is selected --> - <ng-template #tabTmpl_nothingSelected> - <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { - matColor: 'primary', - fontIcon: 'fa-sitemap', - tooltip: 'Explore regions', - click: click, - badge: badge - }"> - </ng-container> - </ng-template> - </ng-template> - </ng-template> - - -</ng-template> - -<ng-template #tabTmpl_defaultTmpl - let-matColor="matColor" - let-fontIcon="fontIcon" - let-customColor="customColor" - let-customColorDarkmode="customColorDarkmode" - let-tooltip="tooltip" - let-badge="badge" - let-badgeColor="badgeColor" - let-click="click"> - <!-- (click)="sideNavMasterSwitch.toggle()" --> - <button mat-raised-button - [attr.aria-label]="ARIA_LABELS.TOGGLE_SIDE_PANEL" - [matTooltip]="tooltip" - class="pe-all tab-toggle" - [ngClass]="{ - 'darktheme': customColorDarkmode === true, - 'lighttheme': customColorDarkmode === false - }" - (click)="click && click()" - [style.backgroundColor]="customColor" - [color]="(!customColor && matColor) ? matColor : null" - [matBadge]="badge" - [matBadgeColor]="badgeColor || 'warn'"> - - <span [ngClass]="{'sxplr-custom-cmp text': !!customColor}"> - <i class="fas" [ngClass]="fontIcon || 'fa-question'"></i> - </span> - </button> -</ng-template> - - <!-- region sidenav tmpl --> <ng-template #sidenavRegionTmpl - let-showFullSidenavSwitch="showFullSidenavSwitch" let-view="view"> <!-- region search autocomplete --> @@ -759,11 +600,19 @@ </div> <!-- collapse btn --> - <ng-template [ngTemplateOutlet]="collapseBtn" - [ngTemplateOutletContext]="{ - collapse: showFullSidenavSwitch.close.bind(showFullSidenavSwitch) - }"> - </ng-template> + + <div class="h-0 w-100 collapse-position d-flex flex-column justify-content-end align-items-center"> + + <button mat-raised-button class="mat-elevation-z8" + [attr.aria-label]="ARIA_LABELS.COLLAPSE" + (click)="controlFullNav(false)"> + <i class="fas fa-chevron-up"></i> + <span> + collapse + </span> + </button> + </div> + </ng-template> @@ -838,22 +687,6 @@ </ng-template> </ng-template> -<!-- collapse btn --> -<ng-template #collapseBtn let-collapse="collapse"> - - <div class="h-0 w-100 collapse-position d-flex flex-column justify-content-end align-items-center"> - - <button mat-raised-button class="mat-elevation-z8" - [attr.aria-label]="ARIA_LABELS.COLLAPSE" - (click)="collapse()"> - <i class="fas fa-chevron-up"></i> - <span> - collapse - </span> - </button> - </div> -</ng-template> - <!-- region tmpl placeholder --> <ng-template #regionPlaceholderTmpl> <div class="placeholder-region-detail bs-border-box ml-15px-n mr-15px-n mat-elevation-z4"> @@ -870,10 +703,9 @@ (iav-key-event)="disposeCtxMenu()" (iav-outsideClick)="disposeCtxMenu()"> - <mat-card-content class="d-flex flex-column"> + <mat-card-content class="context-menu-container"> <div *ngFor="let tmplRef of tmplRefs" - [ngStyle]="{ order: tmplRef.order || 0 }" - class="context-menu-container"> + [ngStyle]="{ order: tmplRef.order || 0 }"> <ng-template [ngIf]="tmplRef.tmpl" [ngIfElse]="fallbackTmpl" @@ -892,9 +724,16 @@ </ng-template> <ng-template #lastViewedPointTmpl let-data> + + <mat-divider></mat-divider> + + <mat-list class="sxplr-p-0"> + <mat-list-item> + Last selected spatial object + </mat-list-item> + </mat-list> + <mat-action-list class="sxplr-p-0"> - <mat-divider></mat-divider> - <div mat-subheader>Last selected spatial object</div> <ng-template [ngIf]="data?.point"> <button mat-list-item (click)="selectPoint(data, data.template)">