diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts index 088c98dbefb4b85f4eb19b0f88ff9efcf59cf263..260cb31be905bc1980382fd53bb8be0a874b9b90 100644 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts +++ b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit, ViewChildren, QueryList, HostBinding } from "@angular/core"; +import {Component, OnInit, ViewChildren, QueryList, HostBinding, ElementRef, ViewChild} from "@angular/core"; import { select, Store } from "@ngrx/store"; import { distinctUntilChanged, map, withLatestFrom, shareReplay, groupBy, mergeMap, toArray, switchMap, scan, filter } from "rxjs/operators"; -import { Observable, Subscription, from, zip, of, combineLatest } from "rxjs"; +import {Observable, Subscription, from, zip, of, combineLatest, BehaviorSubject} from "rxjs"; import { viewerStateSelectTemplateWithId, viewerStateToggleLayer } from "src/services/state/viewerState.store.helper"; import { MatMenuTrigger } from "@angular/material/menu"; import { viewerStateGetSelectedAtlas, viewerStateAtlasLatestParcellationSelector, viewerStateSelectedTemplateFullInfoSelector, viewerStateSelectedTemplatePureSelector, viewerStateSelectedParcellationSelector } from "src/services/state/viewerState/selectors"; @@ -48,7 +48,7 @@ export class AtlasLayerSelector implements OnInit { ) public nonGroupedLayers$: Observable<any[]> = this.atlasLayersLatest$.pipe( - map(allParcellations => + map(allParcellations => allParcellations .filter(p => !p['groupName']) .filter(p => !p['baseLayer']) @@ -57,7 +57,7 @@ export class AtlasLayerSelector implements OnInit { public groupedLayers$: Observable<any[]> = combineLatest([ this.atlasLayersLatest$.pipe( - map(allParcellations => + map(allParcellations => allParcellations.filter(p => !p['baseLayer']) ), ), @@ -91,6 +91,18 @@ export class AtlasLayerSelector implements OnInit { public selectorExpanded: boolean = false public selectedTemplatePreviewUrl: string = '' + @ViewChild('expandedSelectorCard', {read: ElementRef}) expandedSelectorCard: ElementRef<HTMLElement> + public quickTourData = { + order: 4, + description: 'This is the atlas layer browser. If an atlas supports multiple template spaces or parcellation maps, you will find them here.', + position: 'top-right', + overwritePos: { + collapsed: {arrow: 'arrow2', arrowPosition: 'left', arrowAlign: 'bottom', arrowMargin: {left: -20}}, + expanded: {arrow: 'arrow5', arrowPosition: 'left', arrowAlign: 'center', left: this.expandedSelectorCard?.nativeElement.offsetWidth, margin: '-150px 0 0 0'} + } + } + public quickTourPosition$: BehaviorSubject<any> = new BehaviorSubject(this.quickTourData.overwritePos.collapsed) + public availableTemplates$ = this.store$.pipe<any[]>( select(viewerStateSelectedTemplateFullInfoSelector) ) @@ -144,6 +156,12 @@ export class AtlasLayerSelector implements OnInit { ) } + toggleSelector() { + this.selectorExpanded = !this.selectorExpanded + this.quickTourData.overwritePos.expanded.left = this.expandedSelectorCard?.nativeElement.offsetWidth + this.quickTourPosition$.next(this.selectorExpanded? this.quickTourData.overwritePos.expanded : this.quickTourData.overwritePos.collapsed) + } + selectTemplateWithName(template) { this.store$.dispatch( viewerStateSelectTemplateWithId({ payload: template }) @@ -204,7 +222,7 @@ export class AtlasLayerSelector implements OnInit { if (this.templateIncludesGroup(layer)) return layer.name else return `${layer.name} 🔄` } - + return layer.name } diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html index 350ed7322fe270dbe4a56c140693d99685954b30..1380db7c8377011a178bdb7b58342ac116658555 100644 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html +++ b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html @@ -2,7 +2,7 @@ <!-- selector panel when expanded --> - <mat-card class="selector-container position-absolute" [ngClass]="selectorExpanded ? 'scale-up-bl pe-all' : 'scale-down-bl pe-none'"> + <mat-card #expandedSelectorCard class="selector-container position-absolute" [ngClass]="selectorExpanded ? 'scale-up-bl pe-all' : 'scale-down-bl pe-none'"> <mat-card-content> <!-- templates --> @@ -63,13 +63,18 @@ </mat-card> <!-- place holder when not expanded --> - <div class="position-relative m-2 cursor-pointer scale-up-bl pe-all"> + <div class="position-relative m-2 cursor-pointer scale-up-bl pe-all" + quick-tour + [quick-tour-description]="quickTourData.description" + [quick-tour-order]="quickTourData.order" + [quick-tour-position]="quickTourData.position" + [quick-tour-overwrite-pos]="quickTourPosition$"> <button color="primary" matTooltip="Select layer" mat-mini-fab *ngIf="shouldShowRenderPlaceHolder$ | async" [attr.aria-label]="TOGGLE_ATLAS_LAYER_SELECTOR" - (click)="selectorExpanded = !selectorExpanded"> + (click)="toggleSelector()"> <i class="fas fa-layer-group"></i> </button> </div> diff --git a/src/atlasComponents/uiSelectors/module.ts b/src/atlasComponents/uiSelectors/module.ts index 034f9445353f4e43e0a2f7e8e1b62055eccdb419..96ee9a085d82d4d04e0f8bc4c85567a982b6ef0a 100644 --- a/src/atlasComponents/uiSelectors/module.ts +++ b/src/atlasComponents/uiSelectors/module.ts @@ -5,6 +5,7 @@ import { UtilModule } from "src/util"; import { DatabrowserModule } from "src/atlasComponents/databrowserModule"; import { AtlasDropdownSelector } from "./atlasDropdown/atlasDropdown.component"; import { AtlasLayerSelector } from "./atlasLayerSelector/atlasLayerSelector.component"; +import {QuickTourModule} from "src/ui/quickTour/module"; @NgModule({ imports: [ @@ -12,6 +13,7 @@ import { AtlasLayerSelector } from "./atlasLayerSelector/atlasLayerSelector.comp AngularMaterialModule, UtilModule, DatabrowserModule, + QuickTourModule ], declarations: [ AtlasDropdownSelector, @@ -23,4 +25,4 @@ import { AtlasLayerSelector } from "./atlasLayerSelector/atlasLayerSelector.comp ] }) -export class AtlasCmpUiSelectorsModule{} \ No newline at end of file +export class AtlasCmpUiSelectorsModule{} diff --git a/src/ui/help/helpOnePager/helpOnePager.template.html b/src/ui/help/helpOnePager/helpOnePager.template.html index 5309691f899126a11a1b67e5e8f1a40a97b39db1..3a0d4edf74d1d972d846ac8d8b409005ce0694f2 100644 --- a/src/ui/help/helpOnePager/helpOnePager.template.html +++ b/src/ui/help/helpOnePager/helpOnePager.template.html @@ -2,14 +2,11 @@ </markdown-dom> <div mat-dialog-actions align="center"> - - - <button mat-button color="primary" mat-dialog-close quick-tour> + <button mat-button color="primary" mat-dialog-close quick-tour-opener> <span class="d-flex align-items-center"> Take a tour - <i class = "far fa-play-circle"></i> + <i class = "far fa-play-circle ml-1"></i> </span> - </button> <a *ngIf="extQuickStarter" diff --git a/src/ui/quickTour/module.ts b/src/ui/quickTour/module.ts index b027e558fccd92fd39d2491193a96b0e8cbe52e1..d34eb0bb2ecdc3ade0328836d652b2ec7166fee7 100644 --- a/src/ui/quickTour/module.ts +++ b/src/ui/quickTour/module.ts @@ -3,7 +3,9 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { UtilModule } from "src/util"; import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; -import {QuickTourComponent} from "src/ui/quickTour/quickToutCmp/quickTour.component"; +import {QuickTourThis} from "src/ui/quickTour/quickTourThis.directive"; +import {QuickTourService} from "src/ui/quickTour/quickTour.service"; +import {QuickTourComponent} from "src/ui/quickTour/quickToutComponent/quickTour.component"; import {QuickTourDirective} from "src/ui/quickTour/quickTour.directive"; @NgModule({ @@ -13,15 +15,19 @@ import {QuickTourDirective} from "src/ui/quickTour/quickTour.directive"; UtilModule, ], declarations:[ - QuickTourDirective, + QuickTourThis, QuickTourComponent, + QuickTourDirective ], exports: [ QuickTourDirective, + QuickTourThis, ], - providers:[{ - provide: OverlayContainer, - useClass: FullscreenOverlayContainer - }] + providers:[ + {provide: OverlayContainer, + useClass: FullscreenOverlayContainer + }, + QuickTourService + ] }) export class QuickTourModule{} diff --git a/src/ui/quickTour/quickTour.directive.ts b/src/ui/quickTour/quickTour.directive.ts index 2ca41054593adf91776d3ef19a572a572c6f0224..318c8d0c206ef85cb9c5f125597d80a0e0c6ca80 100644 --- a/src/ui/quickTour/quickTour.directive.ts +++ b/src/ui/quickTour/quickTour.directive.ts @@ -2,13 +2,18 @@ import { Overlay, OverlayRef } from "@angular/cdk/overlay"; import { ComponentPortal } from "@angular/cdk/portal"; import {ComponentRef, Directive, HostListener} from "@angular/core"; import { take } from "rxjs/operators"; -import {QuickTourComponent} from "src/ui/quickTour/quickToutCmp/quickTour.component"; +import {QuickTourService} from "src/ui/quickTour/quickTour.service"; +import {QuickTourComponent} from "src/ui/quickTour/quickToutComponent/quickTour.component"; @Directive({ - selector: '[quick-tour]' + selector: '[quick-tour-opener]' }) export class QuickTourDirective { + + constructor(private overlay: Overlay, + private quickTourService: QuickTourService){} + public touring = false private overlayRef: OverlayRef @@ -23,15 +28,12 @@ export class QuickTourDirective { @HostListener('click') onClick(){ - + if (this.overlayRef) this.dispose() this.overlayRef = this.overlay.create({ - height: '0px', width: '0px', - hasBackdrop: false, positionStrategy: this.overlay.position().global(), - // panelClass: ['w-100', 'h-100'], }) this.cmpRef = this.overlayRef.attach( @@ -46,7 +48,7 @@ export class QuickTourDirective { } ) - + this.quickTourService.startTour() } dispose(){ @@ -62,7 +64,4 @@ export class QuickTourDirective { clear(){ this.touring = false } - - constructor(private overlay: Overlay){} - } diff --git a/src/ui/quickTour/quickTour.service.ts b/src/ui/quickTour/quickTour.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3fb92b6dc249c3d703351f1ce4b3c29ec111ced --- /dev/null +++ b/src/ui/quickTour/quickTour.service.ts @@ -0,0 +1,39 @@ +import {Injectable} from "@angular/core"; +import {QuickTourThis} from "src/ui/quickTour/quickTourThis.directive"; +import {BehaviorSubject, Subject} from "rxjs"; + +@Injectable() +export class QuickTourService { + + public currSlideNum: number = null + public currentTip$: BehaviorSubject<any> = new BehaviorSubject(null) + + public quickTourThisDirectives: QuickTourThis[] = [] + + public register(dir: QuickTourThis) { + if (this.quickTourThisDirectives.indexOf(dir) < 0) { + this.quickTourThisDirectives.push(dir) + this.quickTourThisDirectives.sort((a, b) => +a.order - +b.order) + } + } + + public unregister (dir: QuickTourThis) { + this.quickTourThisDirectives = this.quickTourThisDirectives.filter(d => d.order !== dir.order) + } + + public startTour() { + this.currSlideNum = 0 + this.currentTip$.next(this.quickTourThisDirectives[this.currSlideNum]) + } + + public nextSlide() { + this.currSlideNum++ + this.currentTip$.next(this.quickTourThisDirectives[this.currSlideNum]) + + } + + public backSlide() { + this.currSlideNum-- + this.currentTip$.next(this.quickTourThisDirectives[this.currSlideNum]) + } +} diff --git a/src/ui/quickTour/quickTourThis.directive.ts b/src/ui/quickTour/quickTourThis.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..262f0a0b9944ca34348228521a20e5288054e6c8 --- /dev/null +++ b/src/ui/quickTour/quickTourThis.directive.ts @@ -0,0 +1,41 @@ +import {Directive, ElementRef, Input, OnDestroy, OnInit} from "@angular/core"; +import {Observable} from "rxjs"; +import {QuickTourService} from "src/ui/quickTour/quickTour.service"; + +@Directive({ + selector: '[quick-tour]' +}) +export class QuickTourThis implements OnInit, OnDestroy { + + @Input('quick-tour-overwrite-pos') overwritePos$: Observable<{ + left?: number + top?: number + arrowPosition?: 'top' | 'top-right' | 'right' | 'bottom-right' | 'bottom' | 'bottom-left' | 'left' | 'top-left' + arrowAlign?: 'right' | 'left' | 'top' | 'bottom' | 'center' + arrowMargin?: any + arrowTransform?: string + arrow?: string + recalculate?: boolean + }> + + @Input('quick-tour-order') order: number = 0 + @Input('quick-tour-description') description: string = 'No description' + @Input('quick-tour-position') position: 'top' | 'top-right' | 'right' | 'bottom-right' | 'bottom' | 'bottom-left' | 'left' | 'top-left' + @Input('quick-tour-align') align: 'right' | 'left' | 'top' | 'bottom' | 'center' + + constructor(private quickTourService: QuickTourService, + private el: ElementRef) {} + + public calcPos() { + const { x, y, width, height } = (this.el.nativeElement as HTMLElement).getBoundingClientRect() + return {x, y, width, height} + } + + ngOnInit() { + this.quickTourService.register(this) + } + + ngOnDestroy() { + this.quickTourService.unregister(this) + } +} diff --git a/src/ui/quickTour/quickToutCmp/quickTour.component.ts b/src/ui/quickTour/quickToutCmp/quickTour.component.ts deleted file mode 100644 index 6c5e051665b813fb060ab6042c48c421f041c98f..0000000000000000000000000000000000000000 --- a/src/ui/quickTour/quickToutCmp/quickTour.component.ts +++ /dev/null @@ -1,327 +0,0 @@ -import {AfterViewInit, Component, EventEmitter, HostListener, OnDestroy, Output} from "@angular/core"; - -@Component({ - selector : 'quick-tour', - templateUrl : './quickTour.temlate.html', - styleUrls : ['./quickTour.style.css',], -}) -export class QuickTourComponent implements AfterViewInit, OnDestroy { - - @Output() destroy = new EventEmitter() - public currentTip = 0 - public numberOfSteps = null - private observers: any[] = [] - - public slides: any[] = [ - { - description: 'This is the atlas selector. Click here to choose between EBRAINS reference atlases of different species.', - tooltipRight: '10px', - tooltipTop: '65px', - arrowPosition: 'top', - arrowMargin: '0 0 0 60%', - arrow: 'arrow1', - - step: 1, - }, - - { - description: 'The planar views allow you to zoom in to full resolution (mouse wheel), pan the view (click+drag), and select oblique sections (shift+click+drag). You can double-click brain regions to select them.', - tooltipLeft: 'calc(50% - 50px)', - tooltipTop: 'calc(50% - 50px)', - arrowPosition: 'top', - arrow: 'arrow5', - textMargin: '-60px 0px 0px 45px', - - step: 2, - }, - { - description: 'The 3D view gives an overview of the brain with limited resolution. It can be independently rotated. Click the „eye“ icon on the bottom left to toggle pure surface view.', - tooltipLeft: 'calc(50% - 300px)', - tooltipTop: 'calc(50% - 160px)', - arrowPosition: 'bottom', - arrow: 'arrow6', - arrowMargin: '-10 0 0 calc(100% + 10px)', - arrowTransform: 'rotate(130deg)', - step: 3, - }, - - { - description: 'Use these icons in any of the views to maximize it and zoom in/out.', - tooltipRight: '122px', - tooltipTop: 'calc(50% - 28px)', - arrowPosition: 'top', - arrowMargin: '0 100% -8px calc(100% + 5px)', - arrow: 'arrow6', - - arrowTransform: 'rotate(40deg)', - - step: 4, - }, - - { - description: 'This is the atlas layer browser. If an atlas supports multiple template spaces or parcellation maps, you will find them here.', - tooltipLeft: '18px', - tooltipTop: 'calc(100vh - 230px)', - arrow: 'arrow2', - arrowMargin: '100% 0px 0px 0px', - arrowPosition: 'left', - autoNext: 'layerSelectorOpened', - viewAttached: 'sidebarRegion', - step: 5, - }, - - { - description: 'Choose other available templates or parcellations here.', - tooltipLeft: '300px', - tooltipTop: 'calc(100vh - 550px)', - arrow: 'arrow6', - arrowMargin: '30px 14px 0px 15px', - arrowTransform: 'rotate(-120deg)', - arrowPosition: 'left', - viewAttached: 'sidebarRegion', - documentClickOnNext: true, - step: 5, - }, - - - { - description: 'These „chips“ indicate the currently selected parcellation map as well as selected region. Click the chip to see different versions, if any. Click (i) to read more about a selected item. Click (x) to clear a selection.', - tooltipLeft: '18px', - tooltipTop: 'calc(100vh - 340px)', - arrow: 'arrow1', - arrowTransform: 'scaleX(-1) rotate(170deg)', - arrowMargin: '0 0 0 100px', - arrowPosition: 'bottom', - viewAttached: 'sidebarRegion', - documentClickOnNext: true, - step: 6, - }, - { - description: 'This is the coordinate navigator. Expand it to manipulate voxel and physical coordinates, to reset the view, or to create persistent links to the current view for sharing.', - tooltipLeft: '50px', - tooltipTop: '55px', - arrowPosition: 'top', - arrow: 'arrow1', - autoNext: 'viewerStatusOpened', - viewAttached: 'sidebar', - step: 7, - }, - - { - description: 'You can to manipulate voxel and physical coordinates, to reset the view, or to create persistent links to the current view for sharing.', - tooltipLeft: '30px', - tooltipTop: '130px', - arrowPosition: 'top', - arrow: 'arrow6', - arrowMargin: '0 0 0 50%', - viewAttached: 'sidebar', - step: 7, - }, - - { - description: 'Open sidebar to find the region quick search.', - tooltipTop: '80px', - arrow: 'arrow2', - arrowMargin: '-40px 0 0 0', - arrowTransform: 'scaleX(-1) rotate(180deg)', - arrowPosition: 'arrow2', - autoNext: 'sidebarOpened', - step: 8, - }, - - { - description: 'Use the region quick search for finding, selecting and navigating brain regions in the selected parcellation map.', - tooltipTop: '65px', - arrow: 'arrow6', - arrowMargin: '-50px 10px 0 40px', - arrowTransform: 'rotate(-55deg)', - arrowPosition: 'left', - documentClickOnNext: true, - step: 8, - }, - - { - description: 'These icons provide access to plugins, pinned datasets, and user documentation. Use the profile icon to login with your EBRAINS account.', - tooltipRight: '125px', - tooltipTop: '55px', - arrowPosition: 'top', - arrowMargin: '0 0 0 50%', - arrowTransform: 'scaleX(-1)', - arrow: 'arrow1', - - step: 9, - }, - ] - - - constructor() { - this.numberOfSteps = new Array([...this.slides].filter(s => s.step).pop()['step']) - } - - ngAfterViewInit() { - const layerSelectorEl = document.querySelector('atlas-layer-selector') - - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (this.slides[this.currentTip]?.autoNext === 'layerSelectorOpened' && mutation.target['dataset']['opened'] === 'true') { - this.currentTip += 1 - } else if (this.slides[this.currentTip-1]?.autoNext === 'layerSelectorOpened' && mutation.target['dataset']['opened'] === 'false') { - this.currentTip -= 1 - } - }) - }) - - const observerConfig = { - attributes: true, - childList: true, - characterData: true - } - - observer.observe(layerSelectorEl, observerConfig) - - this.observers.push(observer) - } - - @HostListener('document:click', ['$event']) - documentClick(event) { - // Auto change view when nehuba navigation status is opened - const nehubaStatus = document.querySelector('iav-cmp-viewer-nehuba-status') - const nehubaStatusContainer = nehubaStatus? nehubaStatus.querySelector('mat-card.expandedContainer') : null - if (this.slides[this.currentTip]?.autoNext === 'viewerStatusOpened' && nehubaStatusContainer) { - this.currentTip += 1 - } else if(this.slides[this.currentTip-1]?.autoNext === 'viewerStatusOpened' && !nehubaStatusContainer) { - this.currentTip -= 1 - } - - // Auto move to region search tip when sidebar is opened - const drawerElement = document.getElementsByTagName('mat-drawer')[0] as HTMLElement - const drawerOpened = drawerElement.getAttribute('data-mat-drawer-top-open') === 'true' - if (this.slides[this.currentTip]?.autoNext === 'sidebarOpened' && drawerOpened) { - const drawerWidth = drawerElement.offsetWidth - this.currentTip += 1 - this.slides[this.currentTip].tooltipLeft = drawerWidth-100 + 'px' - } else if(this.slides[this.currentTip-1]?.autoNext === 'sidebarOpened' && !drawerOpened) { - this.currentTip -= 1 - } - - // Check sidebar status when on open/close - const clickInsideCdkOverlay = document.querySelector('.cdk-overlay-container')?.contains(event.target) - if (!clickInsideCdkOverlay) { - this.checkSidebarStatus() - } - - } - - - goBack() { - // Click document for closing menu - if (this.slides[this.currentTip]?.documentClickOnNext) { - document.body.click() - } - // Moeve to prev tip - this.currentTip = this.slides.findIndex(s => s.step === this.slides[this.currentTip].step-1) - - //Checks - //// Layer selector status - this.checkAtlasLayerSelectorState() - //// Sidebar status - if (this.slides[this.currentTip]?.viewAttached === 'sidebar' || this.slides[this.currentTip]?.viewAttached === 'sidebarRegion') { - this.checkSidebarStatus() - } - } - - goForward() { - // Click document for closing menu - if (this.slides[this.currentTip]?.documentClickOnNext) { - document.body.click() - } - // Moeve to next tip - this.currentTip = this.slides.findIndex(s => s.step === this.slides[this.currentTip].step+1) - // Checks - //// Layer selector status - this.checkAtlasLayerSelectorState() - //// Sidebar status - if (this.slides[this.currentTip]?.viewAttached === 'sidebar' || this.slides[this.currentTip]?.viewAttached === 'sidebarRegion') { - this.checkSidebarStatus() - } - } - - moveToTipByIndex(index) { - // Click document for closing menu - if (this.slides[this.currentTip]?.documentClickOnNext) { - document.body.click() - } - // Moeve to next tip - this.currentTip = this.slides.findIndex(s => s.step === index+1) - // Checks - //// Layer selector status - this.checkAtlasLayerSelectorState() - //// Sidebar status - if (this.slides[this.currentTip]?.viewAttached === 'sidebar' || this.slides[this.currentTip]?.viewAttached === 'sidebarRegion') { - this.checkSidebarStatus() - } - } - - - checkAtlasLayerSelectorState() { - const layerSelectorEl = document.querySelector('atlas-layer-selector') - - if (this.slides[this.currentTip]?.autoNext === 'layerSelectorOpened' && layerSelectorEl['dataset']['opened'] === 'true') { - this.currentTip += 1 - } - } - - private lastTipForSidebar = null - private drawerOpened = false - private drawerExpanded = false - checkSidebarStatus() { - const drawerElement = document.getElementsByTagName('mat-drawer')[0] as HTMLElement - const expandDrawer = document.getElementsByTagName('mat-drawer')[1] as HTMLElement - const drawerOpened = drawerElement.getAttribute('data-mat-drawer-top-open') === 'true' - const drawerExpanded = expandDrawer.getAttribute('data-mat-drawer-fullleft-open') === 'true' - - const drawerWidth = drawerElement.offsetWidth - - if (this.slides[this.currentTip]?.viewAttached === 'sidebar' || this.slides[this.currentTip]?.viewAttached === 'sidebarRegion' - && this.slides[this.currentTip].tooltipLeft) { - - if (this.slides[this.currentTip]?.viewAttached === 'sidebar') { - if (drawerOpened - && (!this.drawerOpened || this.lastTipForSidebar !== this.currentTip) - && !this.slides[this.currentTip].movedForSidebar) { - this.slides[this.currentTip].tooltipLeft = (parseInt(this.slides[this.currentTip].tooltipLeft) + drawerWidth) + 'px' - this.slides[this.currentTip].movedForSidebar = true - } else if (!drawerOpened - && (this.drawerOpened || this.lastTipForSidebar !== this.currentTip) - && this.slides[this.currentTip].movedForSidebar) { - this.slides[this.currentTip].tooltipLeft = (parseInt(this.slides[this.currentTip].tooltipLeft) - drawerWidth) + 'px' - this.slides[this.currentTip].movedForSidebar = false - } - } else if (this.slides[this.currentTip]?.viewAttached === 'sidebarRegion') { - if (drawerExpanded - && (!this.drawerExpanded || this.lastTipForSidebar !== this.currentTip) - && !this.slides[this.currentTip].movedForSidebar) { - this.slides[this.currentTip].tooltipLeft = (parseInt(this.slides[this.currentTip].tooltipLeft) + drawerWidth) + 'px' - this.slides[this.currentTip].movedForSidebar = true - } else if (!drawerExpanded - && (this.drawerExpanded || this.lastTipForSidebar !== this.currentTip) - && this.slides[this.currentTip].movedForSidebar) { - this.slides[this.currentTip].tooltipLeft = (parseInt(this.slides[this.currentTip].tooltipLeft) - drawerWidth) + 'px' - this.slides[this.currentTip].movedForSidebar = false - } - } - } - - - this.drawerOpened = drawerOpened - this.drawerExpanded = drawerExpanded - this.lastTipForSidebar = this.currentTip - } - - ngOnDestroy(): void { - this.observers.forEach(o => { - o.disconnect() - }) - } - -} diff --git a/src/ui/quickTour/quickToutCmp/quickTour.temlate.html b/src/ui/quickTour/quickToutCmp/quickTour.temlate.html deleted file mode 100644 index 27cc13ef130fcfbeb0ef8a7474e6e757df7ee004..0000000000000000000000000000000000000000 --- a/src/ui/quickTour/quickToutCmp/quickTour.temlate.html +++ /dev/null @@ -1,137 +0,0 @@ -<div class="tour-help position-fixed pt-3 pr-3 pl-3 pb-1 d-flex" - [ngStyle]="{ - top: slides[currentTip]?.tooltipTop || null, - bottom: slides[currentTip].tooltipBottom || null, - left: slides[currentTip]?.tooltipLeft || null, - right: slides[currentTip]?.tooltipRight || null}" - [ngClass]="[slides[currentTip]?.arrowPosition === 'right'? 'flex-row-reverse' : '', - slides[currentTip]?.arrowPosition === 'left'? 'flex-row' : '', - slides[currentTip]?.arrowPosition === 'top'? 'flex-column' : '', - slides[currentTip]?.arrowPosition === 'bottom'? 'flex-column-reverse' : '']"> - - - <div *ngIf="slides[currentTip]?.arrow"> - <ng-content *ngTemplateOutlet="arrow"></ng-content> - </div> - - <div class="d-flex align-items-center justify-content-center h-100 tipCard pt-3 pr-3 pl-3" - [ngStyle]="{margin: slides[currentTip]?.textMargin? slides[currentTip].textMargin : null, - maxWidth: slides[currentTip]?.textWidth? slides[currentTip].textWidth : null}"> - <div class="iv-custom-comp d-flex flex-column"> - <h6 class="mb-2"> - {{slides[currentTip].description}} - </h6> - - <div class="text-center"> - <button mat-button iav-stop="mousedown mouseup" - (click)="goBack()" - *ngIf="currentTip"> - <i class="fas fa-angle-left"></i> - <span class="ml-1">BACK</span> - </button> - - <button mat-button iav-stop="mousedown mouseup" - (click)="goForward()" - *ngIf="currentTip < slides.length - 1"> - <span class="mr-1">NEXT</span> - <i class="fas fa-angle-right"></i> - </button> - - <button iav-stop="mousedown mouseup" - (click)="destroy.emit();" - mat-button>CLOSE</button> - </div> - - <div class="d-flex align-self-end mb-1 mt-1"> - <div *ngFor="let stepCircle of numberOfSteps; let i = index" - class="border mr-1 stepCircle" - [ngStyle]="{background: slides[currentTip]?.step === i+1? 'currentColor' : ''}" - (click)="moveToTipByIndex(i)"> - {{stepCircle}} - </div> - </div> - - </div> - </div> -</div> - -<ng-template #arrow> - <ng-content *ngTemplateOutlet="slides[currentTip]?.arrow === 'arrow1'? arrow1 : - slides[currentTip]?.arrow === 'arrow2'? arrow2: - slides[currentTip]?.arrow === 'arrow3'? arrow3: - slides[currentTip]?.arrow === 'arrow5'? arrow5: - slides[currentTip]?.arrow === 'arrow6'? arrow6: - slides[currentTip]?.arrow === 'arrow4'? arrow4: null"></ng-content> -</ng-template> - -<ng-template #arrow1> - <svg [ngStyle]="{transform: slides[currentTip]?.arrowTransform? slides[currentTip]?.arrowTransform : null, - margin: slides[currentTip]?.arrowMargin? slides[currentTip]?.arrowMargin : null}" - height="80px" class="overflow-visible" viewBox="166.586 231.427 12.015 71.497" xmlns="http://www.w3.org/2000/svg"> - <path class="stokeColor" style="fill-opacity: 0; paint-order: stroke markers; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3px;" d="M 183.239 326.14 C 176.831 309.209 176.634 285.376 182.647 254.643" transform="matrix(1, 0, 0, 1, -9.368863, -23.215925)"/> - <path class="stokeColor" d="M 187.97 263.39 C 187.12 262.245 184.536 259.536 182.665 254.732" transform="matrix(1, 0, 0, 1, -9.368863, -23.215925)"/> - <path class="stokeColor" d="M 182.791 254.976 C 181.626 254.227 174.925 262.146 176.09 262.895" transform="matrix(1, 0, 0, 1, -9.368863, -23.215925)"/> - </svg> -</ng-template> - -<ng-template #arrow2> - <svg [ngStyle]="{transform: slides[currentTip]?.arrowTransform? slides[currentTip]?.arrowTransform : null, - margin: slides[currentTip]?.arrowMargin? slides[currentTip]?.arrowMargin : null}" - width="80px" height="70px" class="overflow-visible" viewBox="150.239 144.229 67.46 40.886" xmlns="http://www.w3.org/2000/svg"> - <path class="stokeColor" d="M 296.081 265.092 Q 302.545 229.649 359.476 224.337" transform="matrix(1, 0, 0, 1, -141.776566, -80.108154)"/> - <path class="stokeColor" d="M 292.065 265.677 C 294.922 265.86 297.891 264.941 300.918 262.844 C 302.986 261.411 299.162 257.842 295.976 253.078" transform="matrix(0, 1, -1, 0, 415.938782, -116.389183)"/> - </svg> -</ng-template> - - - -<ng-template #arrow3> - <svg [ngStyle]="{transform: slides[currentTip]?.arrowTransform? slides[currentTip]?.arrowTransform : null, - margin: slides[currentTip]?.arrowMargin? slides[currentTip]?.arrowMargin : null}" - width="40px" class="overflow-visible" viewBox="210.782 56.841 29.636 288.223" xmlns="http://www.w3.org/2000/svg"> - <path class="stokeColor" d="M 212.078 57.419 C 231.858 81.463 223.38 117.762 225.382 146.924 C 226.221 159.143 228.267 171.776 232.453 183.328 C 233.961 187.49 240.011 194.737 240.418 198.726 C 240.445 198.997 234.593 204.524 233.912 205.886 C 231.231 211.249 230.106 218.152 229.665 224.115 C 227.666 251.12 229.249 278.318 227.395 305.339 C 226.51 318.233 217.11 333.266 217.11 345.064" style="stroke-linecap: round; stroke-linejoin: round; stroke-width: 3px; fill: rgba(0, 0, 0, 0);"/> - <path class="stokeColor" d="M 210.782 65.415 C 210.782 51.809 212.181 58.472 220.897 58.472" /> - <path class="stokeColor" d="M 214.504 338.782 C 214.906 351.198 220.239 339.913 224.34 339.913"/> - </svg> -</ng-template> - -<ng-template #arrow4 > - <svg [ngStyle]="{transform: slides[currentTip]?.arrowTransform? slides[currentTip]?.arrowTransform : null, - margin: slides[currentTip]?.arrowMargin? slides[currentTip]?.arrowMargin : null}" - height="38px" viewBox="299.543 321.915 170.933 30.964" xmlns="http://www.w3.org/2000/svg"> - <path class="stokeColor" d="M 301.52072039541815 323.60226246501526 C 311.5831169442529 340.51562343702426 341.60560254929175 334.7982673929286 359.2985534667969 337.2043762207031 C 367.2514321396593 338.2859081635777 375.2702876816191 342.35410524702206 382.34735107421875 345.8924865722656 C 385.22763076450417 347.33256521819686 387.1320707497497 354.70600700165824 389.4359130859375 352.4560852050781 C 390.0671934950275 351.839579670018 390.40266660252774 347.9536332932388 391.1954345703125 346.7441711425781 C 396.4823015920718 338.6784245203205 410.4743617782212 338.13712391197873 418.9132385253906 336.7287292480469 C 431.7553451427824 334.5854636244975 448.4256779742033 337.9491044314972 459.5848693847656 329.5787353515625 C 461.18996850294843 328.37477069724616 468.6846923828125 325.1523246569334 468.6846923828125 323.1907043457031" style="fill: rgb(0, 0, 0, 0);"/> - <path class="stokeColor" d="M 302.9312615546566 333.07007358944463 C 302.65366244614114 329.72018099572654 297.8531401351693 325.79953737428235 300.1758117675781 323.3697204589844 C 300.7803565677791 322.73728789250043 311.83416748046875 323.5789384146184 311.83416748046875 323.81036376953125" style="fill: rgba(0, 0, 0, 0);"/> - <path class="stokeColor" d="M 463.05231850248373 323.3282565340343 C 465.30665142189343 323.3282470703125 468.0848881713475 320.89690392424393 469.7531433105469 322.41314697265625 C 470.9482381993743 323.499344326286 470.57628053827574 331.8448791503906 469.25830078125 331.8448791503906" style="fill: rgba(0, 0, 0, 0);"/> - </svg> -</ng-template> - - -<ng-template #arrow5> - <svg [ngStyle]="{transform: slides[currentTip]?.arrowTransform? slides[currentTip]?.arrowTransform : null, - margin: slides[currentTip]?.arrowMargin? slides[currentTip]?.arrowMargin : null}" - height="100px" - viewBox="23.011 51.12 86.091 84.486" xmlns="http://www.w3.org/2000/svg"> - <path class="stokeColor" d="M 42.372 135.275 C 39.099 72.198 70.666 69.778 107.477 70.17"/> - <path class="stokeColor" d="M 28.277 125.699 L 41.622 125.699" transform="matrix(0.939693, -0.34202, 0.34202, 0.939693, -40.883873, 19.534008)"/> - <path class="stokeColor" d="M 33.304 121.099 L 25.073 128.035 L 33.062 134.606" transform="matrix(0.939693, -0.34202, 0.34202, 0.939693, -41.967846, 17.693502)"/> - <path class="stokeColor" d="M 92.404 63.192 L 105.749 63.192" transform="matrix(-0.34202, 0.939693, -0.939693, -0.34202, 192.343719, -8.296521)"/> - <path class="stokeColor" d="M 105.464 50.544 L 97.233 57.48 L 105.222 64.051" transform="matrix(-0.34202, 0.939693, -0.939693, -0.34202, 189.85376, -18.342039)"/> - <path class="stokeColor" d="M 42.968 77.934 L 56.313 77.934" transform="matrix(0.743145, 0.669131, -0.669131, 0.743145, 64.89846, -13.198224)"/> - <path class="stokeColor" d="M 49.142 66.866 L 40.911 73.802 L 48.9 80.373" transform="matrix(0.743145, 0.669131, -0.669131, 0.743145, 60.826363, -11.219056)"/> - </svg> - -</ng-template> - - - -<ng-template #arrow6> - - <svg [ngStyle]="{transform: slides[currentTip]?.arrowTransform? slides[currentTip]?.arrowTransform : null, - margin: slides[currentTip]?.arrowMargin? slides[currentTip]?.arrowMargin : null}" - height="50px" class="overflow-visible" viewBox="88.429 331.463 16.292 53.247" xmlns="http://www.w3.org/2000/svg"> - <path class="stokeColor" style="fill-rule: evenodd; fill: rgba(0, 0, 0, 0); stroke-width: 3px;" d="M 96.649 384.71 L 96.649 332.904"/> - <path class="stokeColor" style="fill-rule: evenodd; fill: rgba(0, 0, 0, 0); paint-order: fill; stroke-linejoin: round; stroke-linecap: round; stroke-width: 3px;" d="M 88.708 343.497 C 86.828 341.857 94.988 329.978 96.868 331.618 C 99.945 331.127 106.222 342.826 104.392 343.118"/> - </svg> -</ng-template> - - diff --git a/src/ui/quickTour/quickToutComponent/quickTour.component.ts b/src/ui/quickTour/quickToutComponent/quickTour.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b5055decc9b8d873161dd7e82d30e31616ebc175 --- /dev/null +++ b/src/ui/quickTour/quickToutComponent/quickTour.component.ts @@ -0,0 +1,206 @@ +import { + AfterViewInit, + Component, + ElementRef, + EventEmitter, + HostListener, + OnDestroy, + OnInit, + Output, ViewChild +} from "@angular/core"; +import {Observable, of, Subscription} from "rxjs"; +import {QuickTourService} from "src/ui/quickTour/quickTour.service"; +import {map, switchMap} from "rxjs/operators"; + +@Component({ + selector : 'quick-tour', + templateUrl : './quickTour.temlate.html', + styleUrls : ['./quickTour.style.css',], +}) +export class QuickTourComponent implements OnInit, AfterViewInit { + + slideWidth = 300 + + private tip + + public left + public top + public leftBeforeHardcodeChange + public topBeforeHardcodeChange + public description + public order + + private targetElWidth + private targetElHeight + private currentTip + + public overwritePos$: Observable<any> + public overwritePos: any + + public tipHidden = true + + private subscriptions: Subscription[] = [] + + @Output() destroy = new EventEmitter() + + @ViewChild('tipCard') tipCardEl: ElementRef + @ViewChild('arrowEl') arrowEl: ElementRef + + constructor(public quickTourService: QuickTourService) {} + + ngOnInit() { + this.subscriptions.push( + this.quickTourService.currentTip$.pipe( + + switchMap(tip => { + if (!tip) return + this.tip = null + this.tip = tip + + this.clear() + this.calculate() + + return tip.overwritePos$ || of(null) + + }), + map((op: any) => { + if (op) { + this.tipHidden = true + + setTimeout(() => { + if (op.recalculate) { + this.clear() + this.calculate() + } + this.overwritePos = null + this.overwritePos = op + + if (op.left) { + this.leftBeforeHardcodeChange = this.left + this.left = op.left + } else if (this.leftBeforeHardcodeChange) { + this.left = this.leftBeforeHardcodeChange + } + if (op.top) { + this.topBeforeHardcodeChange = this.top + this.top = op.top + } else if (this.topBeforeHardcodeChange) { + this.top = this.topBeforeHardcodeChange + } + setTimeout(() => this.calculateArrowPosition()) + }) + } + }) + ).subscribe(), + + ) + } + + + ngAfterViewInit() { + this.calculatePosition(this.currentTip, this.targetElWidth, this.targetElHeight) + } + + clear() { + this.left = null + this.top = null + this.leftBeforeHardcodeChange = null + this.topBeforeHardcodeChange = null + this.description = null + this.overwritePos$ = null + this.overwritePos = null + this.order = null + this.targetElWidth = null + this.targetElHeight = null + this.positionChangedByArrow = false + } + + calculate() { + const tip = this.tip + if (tip === null) return + + const { x, y, width, height } = tip.calcPos() + this.left = x + this.top = y + this.description = tip.description + this.overwritePos$ = tip.overwritePos$ + this.order = tip.order + + this.targetElWidth = width + this.targetElHeight = height + this.currentTip = tip + + if (this.tipCardEl) setTimeout(() =>this.calculatePosition(tip, width, height)) + } + + calculatePosition(tip, elementWidth, elementHeight) { + const cardWidth = this.tipCardEl.nativeElement.offsetWidth + const cardHeight = this.tipCardEl.nativeElement.offsetHeight + + if (tip.position) { + if (tip.position.includes('top')) { + this.top -= cardHeight + } + if (tip.position.includes('right')) { + this.left += elementWidth + } + if (tip.position.includes('bottom')) { + this.top += elementHeight + } + if (tip.position.includes('left')) { + this.left -= cardWidth + } + } + if (tip.align) { + if (tip.align === 'right') { + this.left -= cardWidth - elementWidth + } + if (tip.align === 'bottom') { + this.top -= cardHeight - elementHeight + } + if (tip.align === 'center') { + if (['right', 'left'].includes(tip.position)) { + this.top -= (cardHeight - elementHeight)/2 + } + if (['bottom', 'top'].includes(tip.position)) { + this.left -= (cardWidth - elementWidth)/2 + } + } + } + } + + calculateArrowPosition() { + if (['top-left', 'top-right'].includes(this.overwritePos.arrowPosition)) { + if (!this.overwritePos.arrowMargin || !this.overwritePos.arrowMargin.top) { + this.overwritePos.arrowMargin = {} + this.overwritePos.arrowMargin.top = 0 + } + this.overwritePos.arrowMargin.top += -this.arrowEl.nativeElement.offsetHeight + + if (!this.positionChangedByArrow) this.top += this.arrowEl.nativeElement.offsetHeight + this.positionChangedByArrow = true + } + + if (['bottom-left', 'bottom-right'].includes(this.overwritePos.arrowPosition)) { + if (!this.overwritePos.arrowMargin || !this.overwritePos.arrowMargin.top) { + this.overwritePos.arrowMargin = {} + this.overwritePos.arrowMargin.top = 0 + } + this.overwritePos.arrowMargin.top += this.tipCardEl.nativeElement.offsetHeight + } + + if (['bottom-left', 'bottom-right', 'bottom'].includes(this.overwritePos.arrowPosition)) { + if (!this.positionChangedByArrow) this.top -= this.arrowEl.nativeElement.offsetHeight + this.positionChangedByArrow = true + } + + this.tipHidden = false + } + + positionChangedByArrow = false + + get isLast() { + return this.quickTourService.currentTip$.value === [...this.quickTourService.quickTourThisDirectives].pop() + } + +} diff --git a/src/ui/quickTour/quickToutCmp/quickTour.style.css b/src/ui/quickTour/quickToutComponent/quickTour.style.css similarity index 84% rename from src/ui/quickTour/quickToutCmp/quickTour.style.css rename to src/ui/quickTour/quickToutComponent/quickTour.style.css index d3813b0c5273313fa3b1e00cfd16aa90007f3343..041c3bde2f6f7f9cd9a4cf0fb318a8c1d80ec522 100644 --- a/src/ui/quickTour/quickToutCmp/quickTour.style.css +++ b/src/ui/quickTour/quickToutComponent/quickTour.style.css @@ -1,5 +1,4 @@ :host { - /*z-index: 9999;*/ display: block; width: 100%; height: 100%; @@ -30,19 +29,12 @@ stroke: rgb(66,66,66); } -/*::ng-deep .mat-stroked-button:not([disabled]) {*/ -/* color: #1a1a1a;*/ -/*}*/ - .tour-help { - /*background-color: #efefef;*/ z-index: 901; } - .tipCard { border-radius: 10px; - max-width: 300px; text-align: left; } @@ -61,7 +53,6 @@ width: 100vw; height: 100vh; z-index: 899; - /*display: block;*/ top: 0; left: 0; @@ -84,7 +75,3 @@ height: 10px; border-radius: 5px; } - -/*.cover {*/ -/* pointer-events: none; z-index: 900;*/ -/*}*/ diff --git a/src/ui/quickTour/quickToutComponent/quickTour.temlate.html b/src/ui/quickTour/quickToutComponent/quickTour.temlate.html new file mode 100644 index 0000000000000000000000000000000000000000..fcd981fe9cc62d9083e8c18c5a514bc714001f46 --- /dev/null +++ b/src/ui/quickTour/quickToutComponent/quickTour.temlate.html @@ -0,0 +1,141 @@ +<div class="tour-help position-fixed d-flex" + [ngStyle]="{ + visibility: tipHidden? 'hidden' : 'visible', + top: top + 'px', + left: left + 'px'}" + [ngClass]="[overwritePos?.arrowPosition.includes('right')? + overwritePos?.arrowAlign === 'top'? 'flex-row-reverse align-items-start' : + overwritePos?.arrowAlign === 'center'? 'flex-row-reverse align-items-center' : + overwritePos?.arrowAlign === 'bottom'? 'flex-row-reverse align-items-end' : + overwritePos?.arrowPosition.includes('top')? 'align-items-start' : 'flex-row-reverse align-items-end' + :overwritePos?.arrowPosition.includes('left')? + overwritePos?.arrowAlign === 'top'? 'flex-row align-items-start' : + overwritePos?.arrowAlign === 'center'? 'flex-row align-items-center' : + overwritePos?.arrowAlign === 'bottom'? 'flex-row align-items-end' : + overwritePos?.arrowPosition.includes('top')? 'align-items-start' : 'align-items-end' + :overwritePos?.arrowPosition.includes('top')? + overwritePos?.arrowAlign === 'left'? 'flex-column align-items-start' : + overwritePos?.arrowAlign === 'center'? 'flex-column align-items-center' : + overwritePos?.arrowAlign === 'right'? 'flex-column align-items-end' : + overwritePos?.arrowPosition.includes('left')? 'align-items-start' : 'align-items-end' + :overwritePos?.arrowPosition.includes('bottom')? + overwritePos?.arrowAlign === 'left'? 'flex-column-reverse align-items-end' : + overwritePos?.arrowAlign === 'center'? 'flex-column-reverse align-items-center' : + overwritePos?.arrowAlign === 'right'? 'flex-column-reverse align-items-end' : + overwritePos?.arrowPosition.includes('left')? 'align-items-start' : 'align-items-end' + : '']"> + + + <div #arrowEl [ngStyle]="{ + marginLeft: this.overwritePos?.arrowMargin?.left && this.overwritePos.arrowMargin.left + 'px', + marginRight: this.overwritePos?.arrowMargin?.right && this.overwritePos.arrowMargin.right + 'px', + marginTop: this.overwritePos?.arrowMargin?.top && this.overwritePos.arrowMargin.top + 'px', + marginBottom: this.overwritePos?.arrowMargin?.bottom && this.overwritePos.arrowMargin.bottom + 'px'}"> + <ng-content *ngTemplateOutlet="arrow"></ng-content> + </div> + + <div #tipCard class="d-flex align-items-center justify-content-center h-100 tipCard pt-3 pr-3 pl-3" + [ngStyle]="{width: slideWidth + 'px'}"> + +<!-- [ngStyle]="{margin: slides[currentTip]?.textMargin? slides[currentTip].textMargin : null,--> +<!-- maxWidth: slides[currentTip]?.textWidth? slides[currentTip].textWidth : null}">--> + + <div class="iv-custom-comp d-flex flex-column"> + <h6 class="mb-2"> + {{description}} + </h6> + + <div class="text-center"> + <button mat-button iav-stop="mousedown mouseup" + (click)="quickTourService.backSlide()" + *ngIf="order > 0"> + <i class="fas fa-angle-left"></i> + <span class="ml-1">BACK</span> + </button> + + <button mat-button iav-stop="mousedown mouseup" + (click)="quickTourService.nextSlide()" + *ngIf="!isLast" + > + <span class="mr-1">NEXT</span> + <i class="fas fa-angle-right"></i> + </button> + + <button iav-stop="mousedown mouseup" + (click)="destroy.emit();" + mat-button>CLOSE</button> + + </div> + + <div class="d-flex align-self-end mb-1 mt-1"> + <div *ngFor="let stepCircle of quickTourService.quickTourThisDirectives; let i = index" + class="border mr-1 stepCircle" + [ngStyle]="{background: quickTourService.currSlideNum === i? 'currentColor' : ''}"> + </div> + </div> + + </div> + </div> +</div> + + +<ng-template #arrow> + <ng-content *ngTemplateOutlet="overwritePos?.arrow === 'arrow2'? arrow2: + overwritePos?.arrow === 'arrow3'? arrow3: + overwritePos?.arrow === 'arrow4'? arrow4: + overwritePos?.arrow === 'arrow5'? arrow5: + arrow1"></ng-content> +</ng-template> + +<ng-template #arrow1> + <svg [ngStyle]="{transform: overwritePos?.arrowTransform? overwritePos?.arrowTransform : null}" + height="80px" class="overflow-visible" viewBox="166.586 231.427 12.015 71.497" xmlns="http://www.w3.org/2000/svg"> + <path class="stokeColor" style="fill-opacity: 0; paint-order: stroke markers; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3px;" d="M 183.239 326.14 C 176.831 309.209 176.634 285.376 182.647 254.643" transform="matrix(1, 0, 0, 1, -9.368863, -23.215925)"/> + <path class="stokeColor" d="M 187.97 263.39 C 187.12 262.245 184.536 259.536 182.665 254.732" transform="matrix(1, 0, 0, 1, -9.368863, -23.215925)"/> + <path class="stokeColor" d="M 182.791 254.976 C 181.626 254.227 174.925 262.146 176.09 262.895" transform="matrix(1, 0, 0, 1, -9.368863, -23.215925)"/> + </svg> +</ng-template> + +<ng-template #arrow2> + <svg [ngStyle]="{transform: overwritePos?.arrowTransform? overwritePos?.arrowTransform : null}" + width="80px" height="50px" class="overflow-visible" viewBox="150.239 144.229 67.46 40.886" xmlns="http://www.w3.org/2000/svg"> + <path class="stokeColor" d="M 296.081 265.092 Q 302.545 229.649 359.476 224.337" transform="matrix(1, 0, 0, 1, -141.776566, -80.108154)"/> + <path class="stokeColor" d="M 292.065 265.677 C 294.922 265.86 297.891 264.941 300.918 262.844 C 302.986 261.411 299.162 257.842 295.976 253.078" transform="matrix(0, 1, -1, 0, 415.938782, -116.389183)"/> + </svg> +</ng-template> + +<ng-template #arrow3> + <svg [ngStyle]="{transform: overwritePos?.arrowTransform? overwritePos?.arrowTransform : null}" + height="100px" + viewBox="23.011 51.12 86.091 84.486" xmlns="http://www.w3.org/2000/svg"> + <path class="stokeColor" d="M 42.372 135.275 C 39.099 72.198 70.666 69.778 107.477 70.17"/> + <path class="stokeColor" d="M 28.277 125.699 L 41.622 125.699" transform="matrix(0.939693, -0.34202, 0.34202, 0.939693, -40.883873, 19.534008)"/> + <path class="stokeColor" d="M 33.304 121.099 L 25.073 128.035 L 33.062 134.606" transform="matrix(0.939693, -0.34202, 0.34202, 0.939693, -41.967846, 17.693502)"/> + <path class="stokeColor" d="M 92.404 63.192 L 105.749 63.192" transform="matrix(-0.34202, 0.939693, -0.939693, -0.34202, 192.343719, -8.296521)"/> + <path class="stokeColor" d="M 105.464 50.544 L 97.233 57.48 L 105.222 64.051" transform="matrix(-0.34202, 0.939693, -0.939693, -0.34202, 189.85376, -18.342039)"/> + <path class="stokeColor" d="M 42.968 77.934 L 56.313 77.934" transform="matrix(0.743145, 0.669131, -0.669131, 0.743145, 64.89846, -13.198224)"/> + <path class="stokeColor" d="M 49.142 66.866 L 40.911 73.802 L 48.9 80.373" transform="matrix(0.743145, 0.669131, -0.669131, 0.743145, 60.826363, -11.219056)"/> + </svg> + +</ng-template> + +<ng-template #arrow4> + <svg [ngStyle]="{transform: overwritePos?.arrowTransform? overwritePos?.arrowTransform : null}" + height="50px" class="overflow-visible" viewBox="88.429 331.463 16.292 53.247" xmlns="http://www.w3.org/2000/svg"> + <path class="stokeColor" style="fill-rule: evenodd; fill: rgba(0, 0, 0, 0); stroke-width: 3px;" d="M 96.649 384.71 L 96.649 332.904"/> + <path class="stokeColor" style="fill-rule: evenodd; fill: rgba(0, 0, 0, 0); paint-order: fill; stroke-linejoin: round; stroke-linecap: round; stroke-width: 3px;" d="M 88.708 343.497 C 86.828 341.857 94.988 329.978 96.868 331.618 C 99.945 331.127 106.222 342.826 104.392 343.118"/> + </svg> +</ng-template> + +<ng-template #arrow5> + <svg [ngStyle]="{transform: overwritePos?.arrowTransform? overwritePos?.arrowTransform : null}" + width="50px" viewBox="140.601 179.981 53.247 16.292" xmlns="http://www.w3.org/2000/svg"> + <path class="stokeColor" style="fill-rule: evenodd; fill: rgba(0, 0, 0, 0); stroke-width: 3px;" d="M 167.945 213.956 L 167.945 162.15" transform="matrix(0, -1, 1, 0, -20.107986, 355.997986)"/> + <path class="stokeColor" style="fill-rule: evenodd; fill: rgba(0, 0, 0, 0); paint-order: fill; stroke-linejoin: round; stroke-linecap: round; stroke-width: 3px;" d="M 138.751 194.144 C 136.871 192.504 145.031 180.625 146.911 182.265 C 149.988 181.774 156.265 193.473 154.435 193.765" transform="matrix(0, -1, 1, 0, -41.509438, 334.744568)"/> + </svg> +</ng-template> + + + + + diff --git a/src/ui/topMenu/module.ts b/src/ui/topMenu/module.ts index 8ae3bbf7341f50320612c758bd319527860a982a..2a59699313d69c7e77fabc76aa358254bbc7c934 100644 --- a/src/ui/topMenu/module.ts +++ b/src/ui/topMenu/module.ts @@ -13,6 +13,7 @@ import { KgTosModule } from "../kgtos/module"; import { ScreenshotModule } from "../screenshot"; import { AngularMaterialModule } from "../sharedModules/angularMaterial.module"; import { TopMenuCmp } from "./topMenuCmp/topMenu.components"; +import {QuickTourModule} from "src/ui/quickTour/module"; @NgModule({ imports: [ @@ -29,6 +30,7 @@ import { TopMenuCmp } from "./topMenuCmp/topMenu.components"; PluginModule, AuthModule, ScreenshotModule, + QuickTourModule ], declarations: [ TopMenuCmp diff --git a/src/ui/topMenu/topMenuCmp/topMenu.components.ts b/src/ui/topMenu/topMenuCmp/topMenu.components.ts index 4a78e0d12fb6231da10f7d954c8e6bdaa4bc850d..8c2234961e7e8b0b6a15ad34508cd400849e1a24 100644 --- a/src/ui/topMenu/topMenuCmp/topMenu.components.ts +++ b/src/ui/topMenu/topMenuCmp/topMenu.components.ts @@ -6,7 +6,7 @@ import { TemplateRef, } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { Observable } from "rxjs"; +import {Observable, of} from "rxjs"; import { map } from "rxjs/operators"; import { AuthService } from "src/auth"; import { IavRootStoreInterface, IDataEntry } from "src/services/stateStore.service"; @@ -51,6 +51,14 @@ export class TopMenuCmp { public pluginTooltipText: string = `Plugins and Tools` public screenshotTooltipText: string = 'Take screenshot' + quickTourData = { + description: 'These icons provide access to plugins, pinned datasets, and user documentation. Use the profile icon to login with your EBRAINS account.', + order: 8, + position: 'bottom', + align: 'center', + overwritePos: of({arrowPosition: 'top', arrowAlign: 'center', arrowTransform: 'scaleX(-1)'}) + } + constructor( private store$: Store<IavRootStoreInterface>, private authService: AuthService, diff --git a/src/ui/topMenu/topMenuCmp/topMenu.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html index e76a092a2be712b7493517e82d783eab05128f66..bbd9a4a8f518dd2380f863e3067c31766c6a56a3 100644 --- a/src/ui/topMenu/topMenuCmp/topMenu.template.html +++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html @@ -42,7 +42,13 @@ <ng-template #fullTmpl> <div class="d-flex flex-row-reverse" - [iav-key-listener]="keyListenerConfig" + quick-tour + [quick-tour-description]="quickTourData.description" + [quick-tour-order]="quickTourData.order" + [quick-tour-position]="quickTourData.position" + [quick-tour-align]="quickTourData.align" + [quick-tour-overwrite-pos]="quickTourData.overwritePos" + [iav-key-listener]="keyListenerConfig" (iav-key-event)="openTmplWithDialog(helperOnePager)"> <!-- signin --> diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index cec938a068e649b42055bd79dd15509736610df1..7bfc1b341467602de9ddcdcdefa5d75c889e00a8 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -18,6 +18,7 @@ import { NehubaModule } from "./nehuba"; import { ThreeSurferModule } from "./threeSurfer"; import { RegionAccordionTooltipTextPipe } from "./util/regionAccordionTooltipText.pipe"; import { ViewerCmp } from "./viewerCmp/viewerCmp.component"; +import {QuickTourModule} from "src/ui/quickTour/module"; @NgModule({ imports: [ @@ -36,6 +37,7 @@ import { ViewerCmp } from "./viewerCmp/viewerCmp.component"; AtlasCmptConnModule, ComponentsModule, BSFeatureModule, + QuickTourModule, ], declarations: [ ViewerCmp, diff --git a/src/viewerModule/nehuba/module.ts b/src/viewerModule/nehuba/module.ts index b0bdeec6eef7d448804954174d8d7e9f65866447..52617785c062aace265a6392c3e4f8d133abd483 100644 --- a/src/viewerModule/nehuba/module.ts +++ b/src/viewerModule/nehuba/module.ts @@ -23,6 +23,7 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { BehaviorSubject } from "rxjs"; import { StateModule } from "src/state"; import { AuthModule } from "src/auth"; +import {QuickTourModule} from "src/ui/quickTour/module"; @NgModule({ imports: [ @@ -46,7 +47,8 @@ import { AuthModule } from "src/auth"; StoreModule.forFeature( NEHUBA_VIEWER_FEATURE_KEY, reducer - ) + ), + QuickTourModule ], declarations: [ NehubaViewerContainerDirective, diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index af1bbefe8592dc18f515a9606a74461e7264404b..75747f62a1d569d569b4a1de14936762f27b0a3c 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -91,6 +91,28 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ private findPanelIndex = (panel: HTMLElement) => this.viewPanelWeakMap.get(panel) public nanometersToOffsetPixelsFn: Array<(...arg) => any> = [] + public quickTourSliceViewSlide = { + order: 1, + description: 'The planar views allow you to zoom in to full resolution (mouse wheel), pan the view (click+drag), and select oblique sections (shift+click+drag). You can double-click brain regions to select them.', + position: 'bottom-right', + overwritePos: of({arrow: 'arrow3', arrowPosition: 'left', arrowMargin: {right: -50, bottom: 130}}) + } + + public quickTour3dViewSlide = { + order: 2, + description: 'The 3D view gives an overview of the brain with limited resolution. It can be independently rotated. Click the „eye“ icon on the bottom left to toggle pure surface view.', + position: 'top-left', + overwritePos: of({arrow: 'arrow5', arrowPosition: 'right', arrowAlign: 'bottom', arrowMargin: {bottom: -25, left: -10}, arrowTransform: 'rotate(-130deg)'}) + } + + public quickTourIconsSlide = { + order: 3, + description: 'Use these icons in any of the views to maximize it and zoom in/out.', + position: 'bottom', + align: 'right', + overwritePos: of({arrow: 'arrow4', arrowPosition: 'top', arrowAlign: 'right', arrowMargin: {right: 50}}) + } + public customLandmarks$: Observable<any> = this.store$.pipe( select(viewerStateCustomLandmarkSelector), map(lms => lms.map(lm => ({ @@ -219,7 +241,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ const overwritingInitState = this.navigation ? cvtNavigationObjToNehubaConfig(this.navigation, initialNgState) : {} - + deepCopiedState.nehubaConfig.dataset.initialNgState = { ...initialNgState, ...overwritingInitState, @@ -294,7 +316,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ const { deregister, register } = clickInterceptor const selOnhoverRegion = this.selectHoveredRegion.bind(this) register(selOnhoverRegion, { last: true }) - this.onDestroyCb.push(() => deregister(selOnhoverRegion)) + this.onDestroyCb.push(() => deregister(selOnhoverRegion)) } /** @@ -315,7 +337,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ /** * TODO smarter with event stream */ - if (!viewPanels.every(v => !!v)) { + if (!viewPanels.every(v => !!v)) { this.log.error(`on relayout, not every view panel is populated. This should not occur!`) return } @@ -380,7 +402,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ const newLayers = ngLayers.filter(l => this.ngLayersRegister.layers?.findIndex(ol => ol.name === l.name) < 0) const removeLayers = this.ngLayersRegister.layers.filter(l => ngLayers?.findIndex(nl => nl.name === l.name) < 0) - + if (newLayers?.length > 0) { const newLayersObj: any = {} newLayers.forEach(({ name, source, ...rest }) => newLayersObj[name] = { @@ -591,7 +613,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ * TODO reenable with updated select_regions api */ this.log.warn(`showSegment is temporarily disabled`) - + // if(!this.selectedRegionIndexSet.has(labelIndex)) // this.store.dispatch({ // type : SELECT_REGIONS, @@ -609,7 +631,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ if (!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3)) { throw new Error('position needs to be a length 3 tuple of numbers ') } - + this.store$.dispatch(viewerStateAddUserLandmarks({ landmarks })) @@ -624,7 +646,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ * TODO reenable with updated select_regions api */ this.log.warn(`hideSegment is temporarily disabled`) - + // if(this.selectedRegionIndexSet.has(labelIndex)){ // this.store.dispatch({ // type :SELECT_REGIONS, @@ -654,7 +676,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ for (const [key, colormap] of this.nehubaContainerDirective.nehubaViewerInstance.multiNgIdColorMap.entries()) { const newColormap = new Map() newMainMap.set(key, newColormap) - + for (const [lableIndex, entry] of colormap.entries()) { newColormap.set(lableIndex, JSON.parse(JSON.stringify(entry))) } @@ -701,7 +723,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ }) }) this.onDestroyCb.push(() => setupViewerApiSub.unsubscribe()) - + // listen to navigation change from store const navSub = this.store$.pipe( select(viewerStateNavigationStateSelector) @@ -814,4 +836,4 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{ ) } -} \ No newline at end of file +} diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html index 65057b2b5f6b841514f2619dc063b96fe136afe7..6c4d101e21be13d14b9bf883463a980312a6e577 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html @@ -16,16 +16,32 @@ </div> <current-layout *ngIf="viewerLoaded" class="position-absolute w-100 h-100 d-block pe-none top-0 left-0"> - <div class="w-100 h-100 position-relative" cell-i> + <div class="w-100 h-100 position-relative" cell-i + quick-tour + [quick-tour-description]="quickTourSliceViewSlide.description" + [quick-tour-order]="quickTourSliceViewSlide.order" + [quick-tour-position]="quickTourSliceViewSlide.position" + [quick-tour-overwrite-pos]="quickTourSliceViewSlide.overwritePos"> <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 0 | parseAsNumber }"></ng-content> </div> - <div class="w-100 h-100 position-relative" cell-ii> + <div class="w-100 h-100 position-relative" cell-ii + quick-tour + [quick-tour-description]="quickTourIconsSlide.description" + [quick-tour-order]="quickTourIconsSlide.order" + [quick-tour-position]="quickTourIconsSlide.position" + [quick-tour-align]="quickTourIconsSlide.align" + [quick-tour-overwrite-pos]="quickTourIconsSlide.overwritePos"> <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 1 | parseAsNumber }"></ng-content> </div> <div class="w-100 h-100 position-relative" cell-iii> <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 2 | parseAsNumber }"></ng-content> </div> - <div class="w-100 h-100 position-relative" cell-iv> + <div class="w-100 h-100 position-relative" cell-iv + quick-tour + [quick-tour-description]="quickTour3dViewSlide.description" + [quick-tour-order]="quickTour3dViewSlide.order" + [quick-tour-position]="quickTour3dViewSlide.position" + [quick-tour-overwrite-pos]="quickTour3dViewSlide.overwritePos"> <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 3 | parseAsNumber }"></ng-content> </div> </current-layout> @@ -44,7 +60,7 @@ <spinner-cmp *ngIf="showPerpsectiveScreen$ | async"> </spinner-cmp> - + <mat-list> <mat-list-item> {{ showPerpsectiveScreen$ | async }} @@ -169,4 +185,4 @@ </ng-template> </button> </div> -</ng-template> \ No newline at end of file +</ng-template> diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.ts index b90ee5b3a823cdefb9926c29807dbd05e7391e9c..0ee28e85906d8b867a71563a68e6309a61b62f5a 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.ts @@ -1,8 +1,16 @@ -import { Component, OnInit, OnChanges, TemplateRef, HostBinding, Optional, Inject } from "@angular/core"; +import { + Component, + OnInit, + OnChanges, + TemplateRef, + HostBinding, + Optional, + Inject, ViewChild, ElementRef +} 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 {Observable, Subscription, of, combineLatest, BehaviorSubject} from "rxjs"; import { map, filter, startWith } from "rxjs/operators"; import { MatBottomSheet } from "@angular/material/bottom-sheet"; import { MatDialog } from "@angular/material/dialog"; @@ -43,6 +51,15 @@ export class StatusCardComponent implements OnInit, OnChanges{ public useTouchInterface$: Observable<boolean> + public quickTourPosition$: BehaviorSubject<any> = new BehaviorSubject({arrowPosition: 'top', arrowAlign: 'center'}) + public quickTourData = { + description: 'This is the coordinate navigator. Expand it to manipulate voxel and physical coordinates, to reset the view, or to create persistent links to the current view for sharing.', + order: 6, + position: 'bottom', + align: 'left', + overwritePos: this.quickTourPosition$ + } + public SHARE_BTN_ARIA_LABEL = ARIA_LABELS.SHARE_BTN public COPY_URL_TO_CLIPBOARD_ARIA_LABEL = ARIA_LABELS.SHARE_COPY_URL_CLIPBOARD public SHARE_CUSTOM_URL_ARIA_LABEL = ARIA_LABELS.SHARE_CUSTOM_URL diff --git a/src/viewerModule/nehuba/statusCard/statusCard.template.html b/src/viewerModule/nehuba/statusCard/statusCard.template.html index 0489d088a7818527404627c3f230661d3dd30408..a27bd519cecddddf68b96830f60a29d92c2721ca 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.template.html +++ b/src/viewerModule/nehuba/statusCard/statusCard.template.html @@ -1,4 +1,12 @@ -<mat-card class="expandedContainer p-2 pt-1" *ngIf="showFull; else showMin"> +<div quick-tour + [quick-tour-description]="quickTourData.description" + [quick-tour-order]="quickTourData.order" + [quick-tour-position]="quickTourData.position" + [quick-tour-align]="quickTourData.align" + [quick-tour-overwrite-pos]="quickTourData.overwritePos"> +<mat-card class="expandedContainer p-2 pt-1" *ngIf="showFull; else showMin" + +> <mat-card-content> <!-- reset --> @@ -34,7 +42,7 @@ <button mat-icon-button [attr.aria-label]="HIDE_FULL_STATUS_PANEL_ARIA_LABEL" - (click)="showFull = false"> + (click)="showFull = false; quickTourPosition$.next({arrowPosition: 'top', arrowAlign: 'center', recalculate: true})"> <i class="fas fa-angle-up"></i> </button> </div> @@ -49,7 +57,7 @@ [formControl]="statusPanelFormCtrl" class="pl-2 pr-2"> </mat-slide-toggle> - + <span class="d-flex align-items center"> Physical space </span> @@ -68,7 +76,7 @@ (keydown.tab)="textNavigateTo(navInput.value)" [value]="navVal$ | async" #navInput="matInput"> - + </mat-form-field> <div class="w-0 position-relative"> @@ -96,13 +104,14 @@ </mat-card-content> </mat-card> +</div> <!-- minimised status bar --> <ng-template #showMin> <div class="iv-custom-comp text overflow-visible text-nowrap d-inline-flex align-items-center m-1 mt-3" iav-media-query #media="iavMediaQuery"> - + <i aria-label="viewer navigation" class="fas fa-compass"></i> <span *ngIf="(media.mediaBreakPoint$ | async) < 3" class="pl-2"> {{ navVal$ | async }} @@ -123,7 +132,8 @@ <button mat-icon-button [attr.aria-label]="SHOW_FULL_STATUS_PANEL_ARIA_LABEL" - (click)="showFull = true"> + (click)="showFull = true; + quickTourPosition$.next({arrow: 'arrow4', arrowPosition: 'top', arrowAlign: 'center', recalculate: true})"> <i class="fas fa-angle-down"></i> </button> </div> @@ -150,12 +160,12 @@ <mat-list-item (click)="openDialog(shareSaneUrl, { ariaLabel: SHARE_CUSTOM_URL_DIALOG_ARIA_LABEL })" [attr.aria-label]="SHARE_CUSTOM_URL_ARIA_LABEL" [attr.tab-index]="10"> - <mat-icon + <mat-icon class="mr-4" fontSet="fas" fontIcon="fa-link"> </mat-icon> - + <span> Create custom URL </span> diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index 5c075f9841a69eaf2763a399ffd469abd7bcaa2a..69ee96fb96d05360eceac0e53f71ab3a06d9cb89 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -1,6 +1,6 @@ -import { Component, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core"; +import {AfterViewInit, Component, Inject, Input, OnDestroy, Optional, ViewChild} from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, Observable, Subject, Subscription } from "rxjs"; +import {BehaviorSubject, combineLatest, Observable, of, Subject, Subscription} from "rxjs"; import { distinctUntilChanged, filter, map, startWith } from "rxjs/operators"; import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer, viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions"; import { viewerStateContextedSelectedRegionsSelector, viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersionSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedTemplateSelector, viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors" @@ -77,6 +77,28 @@ export class ViewerCmp implements OnDestroy { @ViewChild('sideNavFullLeftSwitch', { static: true }) private sidenavLeftSwitch: SwitchDirective + public sidebarQuickTourPosition$: BehaviorSubject<any> = new BehaviorSubject({arrow: 'arrow5', left: 30, top: 50, arrowPosition: 'top-right', arrowTransform: 'rotate(25deg)'}) + quickTourRegionSearch = { + order: 7, + description: 'Use the region quick search for finding, selecting and navigating brain regions in the selected parcellation map.', + overwritePos: this.sidebarQuickTourPosition$ + } + quickTourAtlasSelector = { + order: 0, + description: 'This is the atlas selector. Click here to choose between EBRAINS reference atlases of different species.', + position: 'bottom', + align: 'right', + overwritePos: of({arrowPosition: 'top', arrowAlign: 'center'}) + } + quickTourChips = { + order: 5, + description: 'These „chips“ indicate the currently selected parcellation map as well as selected region. Click the chip to see different versions, if any. Click (i) to read more about a selected item. Click (x) to clear a selection.', + position: 'top', + align: 'center', + overwritePos: of({arrowPosition: 'bottom', arrowAlign: 'center', arrowTransform: 'scaleX(-1) rotate(180deg)', recalculate: true}) + } + + @Input() ismobile = false private subscriptions: Subscription[] = [] @@ -179,7 +201,7 @@ export class ViewerCmp implements OnDestroy { } } } - + public clearAdditionalLayer(layer: { ['@id']: string }){ this.store$.dispatch( viewerStateRemoveAdditionalLayer({ @@ -195,7 +217,7 @@ export class ViewerCmp implements OnDestroy { }) ) } - + public selectParcellation(parc: any) { this.store$.dispatch( viewerStateHelperSelectParcellationWithId({ @@ -209,6 +231,7 @@ export class ViewerCmp implements OnDestroy { } private openSideNavs() { + this.sidebarQuickTourPosition$.next({left: 30, top: 70, arrowPosition: 'top', arrowAlign: 'center', recalculate: true}) this.sidenavLeftSwitch && this.sidenavLeftSwitch.open() this.sidenavTopSwitch && this.sidenavTopSwitch.open() } diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 728a6b3be42e01f09fc9d00a4ed553a539de0bc2..4933f1db81a5121687936d5d3fb5b8fe0d090ff0 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -9,6 +9,7 @@ <mat-drawer-container [iav-switch-initstate]="false" iav-switch + (iav-switch-event)="sidebarQuickTourPosition$.next(sideNavTopSwitch.switchState? {left: 30, top: 70, arrowPosition: 'top', arrowAlign: 'center', recalculate: true} : {arrow: 'arrow5', left: 30, top: 50, arrowPosition: 'top-right', arrowTransform: 'rotate(25deg)'})" #sideNavTopSwitch="iavSwitch" class="mat-drawer-content-overflow-visible w-100 h-100 position-absolute invisible" [hasBackdrop]="false"> @@ -25,7 +26,11 @@ [disableClose]="true" #matDrawerTop="matDrawer"> - <div class="h-0 w-100 region-text-search-autocomplete-position"> + <div class="h-0 w-100 region-text-search-autocomplete-position" + quick-tour + [quick-tour-description]="quickTourRegionSearch.description" + [quick-tour-order]="quickTourRegionSearch.order" + [quick-tour-overwrite-pos]="quickTourRegionSearch.overwritePos"> <ng-container *ngTemplateOutlet="autocompleteTmpl"> </ng-container> </div> @@ -65,7 +70,7 @@ <div *ngIf="viewerLoaded" class="pe-all tab-toggle-container" - (click)="sideNavTopSwitch && sideNavTopSwitch.toggle()"> + (click)="sideNavTopSwitch && sideNavTopSwitch.toggle();"> <ng-container *ngTemplateOutlet="tabTmpl; context: { isOpen: sideNavTopSwitch.switchState, regionSelected: selectedRegions$ | async, @@ -91,7 +96,13 @@ </top-menu-cmp> <div *ngIf="viewerLoaded" - class="iv-custom-comp bg card m-2 mat-elevation-z2"> + class="iv-custom-comp bg card m-2 mat-elevation-z2" + quick-tour + [quick-tour-description]="quickTourAtlasSelector.description" + [quick-tour-order]="quickTourAtlasSelector.order" + [quick-tour-position]="quickTourAtlasSelector.position" + [quick-tour-align]="quickTourAtlasSelector.align" + [quick-tour-overwrite-pos]="quickTourAtlasSelector.overwritePos"> <atlas-dropdown-selector class="pe-all mt-2"> </atlas-dropdown-selector> </div> @@ -198,7 +209,13 @@ <!-- chips --> <div class="flex-grow-1 flex-shrink-1 overflow-x-auto"> - <mat-chip-list class="d-inline-block"> + <mat-chip-list class="d-inline-block" + quick-tour + [quick-tour-description]="quickTourChips.description" + [quick-tour-order]="quickTourChips.order" + [quick-tour-position]="quickTourChips.position" + [quick-tour-align]="quickTourChips.align" + [quick-tour-overwrite-pos]="quickTourChips.overwritePos"> <!-- additional layer --> <ng-container> @@ -257,7 +274,7 @@ <!-- if not supported, show not supported message --> <div *ngSwitchCase="'notsupported'">Template not supported by any of the viewers</div> - + <!-- by default, show splash screen --> <div *ngSwitchDefault> <ui-splashscreen class="position-absolute left-0 top-0">