From 63833c364f139f5269b8c841266406250b9b4868 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Wed, 3 Apr 2019 17:31:58 +0200 Subject: [PATCH] WIP: adapt to mobile --- src/ui/btnShadow.style.css | 23 +++ src/ui/menuicons/menuicons.component.ts | 9 +- src/ui/menuicons/menuicons.template.html | 80 +++++---- .../mobileOverlay/mobileOverlay.component.ts | 153 ++++++++++-------- .../mobileOverlay/mobileOverlay.style.css | 20 ++- .../mobileOverlay/mobileOverlay.template.html | 31 ++-- .../nehubaContainer.component.ts | 41 +---- .../nehubaContainer/nehubaContainer.style.css | 44 +++-- .../nehubaContainer.template.html | 19 ++- .../signinBanner/signinBanner.components.ts | 3 +- .../signinBanner/signinBanner.template.html | 18 ++- src/util/generator.ts | 12 ++ 12 files changed, 281 insertions(+), 172 deletions(-) create mode 100644 src/ui/btnShadow.style.css diff --git a/src/ui/btnShadow.style.css b/src/ui/btnShadow.style.css new file mode 100644 index 000000000..817edee68 --- /dev/null +++ b/src/ui/btnShadow.style.css @@ -0,0 +1,23 @@ +.btnWrapper +{ + display:flex; + align-items: center; + justify-content: center; +} + + +.btnWrapper > .btn +{ + width: 2.5em; + height: 2.5em; + + display: flex; + align-items: center; + justify-content: center; +} + +.btnWrapper.btnWrapper-lg > .btn +{ + width: 3em; + height: 3em; +} \ No newline at end of file diff --git a/src/ui/menuicons/menuicons.component.ts b/src/ui/menuicons/menuicons.component.ts index f6e80ca7c..0a48cfa4a 100644 --- a/src/ui/menuicons/menuicons.component.ts +++ b/src/ui/menuicons/menuicons.component.ts @@ -5,12 +5,14 @@ import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component"; import { LayerBrowser } from "src/ui/layerbrowser/layerbrowser.component"; import { DataBrowser } from "src/ui/databrowserModule/databrowser/databrowser.component"; import { PluginBannerUI } from "../pluginBanner/pluginBanner.component"; +import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; @Component({ selector: 'menu-icons', templateUrl: './menuicons.template.html', styleUrls: [ - './menuicons.style.css' + './menuicons.style.css', + '../btnShadow.style.css' ] }) @@ -37,9 +39,14 @@ export class MenuIconsBar{ pluginBanner: ComponentRef<PluginBannerUI> = null pbWidget: ComponentRef<WidgetUnit> = null + get isMobile(){ + return this.constantService.mobile + } + constructor( private widgetServices:WidgetServices, private injector:Injector, + private constantService:AtlasViewerConstantsServices, cfr: ComponentFactoryResolver ){ this.dbcf = cfr.resolveComponentFactory(DataBrowser) diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html index 1b6ac141f..0bb0ffb64 100644 --- a/src/ui/menuicons/menuicons.template.html +++ b/src/ui/menuicons/menuicons.template.html @@ -1,37 +1,59 @@ <logo-container> </logo-container> -<div *ngIf="false" class="btn btn-sm btn-secondary rounded-circle"> - <i class="fas fa-brain"> - - </i> -</div> + <div - [tooltip]="dataBrowserTitle" - placement="right" - (click)="clickSearch($event)" - [ngClass]="databrowserIsShowing ? 'btn-primary' : 'btn-secondary'" - class="btn btn-sm rounded-circle"> - <i class="fas fa-search"> - - </i> + *ngIf="isMobile" + [ngClass]="isMobile ? 'btnWrapper-lg' : ''" + class="btnWrapper"> + <div + class="shadow btn btn-sm btn-outline-secondary rounded-circle"> + <i class="fas fa-bars"> + + </i> + </div> </div> + <div - tooltip="Layer" - placement="right" - (click)="clickLayer($event)" - [ngClass]="layerbrowserIsShowing ? 'btn-primary' : 'btn-secondary'" - class="btn btn-sm rounded-circle"> - <i class="fas fa-layer-group"> - - </i> + [ngClass]="isMobile ? 'btnWrapper-lg' : ''" + class="btnWrapper"> + <div + [tooltip]="dataBrowserTitle" + placement="right" + (click)="clickSearch($event)" + [ngClass]="databrowserIsShowing ? 'btn-primary' : 'btn-secondary'" + class="shadow btn btn-sm rounded-circle"> + <i class="fas fa-search"> + + </i> + </div> </div> + <div - tooltip="Plugins" - (click)="clickPlugins($event)" - placement="right" - [ngClass]="pluginbrowserIsShowing ? 'btn-primary' : 'btn-secondary'" - class="btn btn-sm rounded-circle"> - <i class="fas fa-tools"> - - </i> + [ngClass]="isMobile ? 'btnWrapper-lg' : ''" + class="btnWrapper"> + <div + tooltip="Layer" + placement="right" + (click)="clickLayer($event)" + [ngClass]="layerbrowserIsShowing ? 'btn-primary' : 'btn-secondary'" + class="shadow btn btn-sm rounded-circle"> + <i class="fas fa-layer-group"> + + </i> + </div> </div> + +<div + [ngClass]="isMobile ? 'btnWrapper-lg' : ''" + class="btnWrapper"> + <div + tooltip="Plugins" + (click)="clickPlugins($event)" + placement="right" + [ngClass]="pluginbrowserIsShowing ? 'btn-primary' : 'btn-secondary'" + class="shadow btn btn-sm rounded-circle"> + <i class="fas fa-tools"> + + </i> + </div> +</div> \ No newline at end of file diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts index 1435c56ec..cd0241392 100644 --- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts +++ b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.component.ts @@ -1,6 +1,7 @@ -import { Component, Input, Output,EventEmitter, ElementRef, ViewChild, AfterViewInit, ChangeDetectionStrategy, OnDestroy } from "@angular/core"; +import { Component, Input, Output,EventEmitter, ElementRef, ViewChild, AfterViewInit, ChangeDetectionStrategy, OnDestroy, OnInit, OnChanges } from "@angular/core"; import { fromEvent, Subject, Observable, merge, concat, of, combineLatest } from "rxjs"; -import { map, switchMap, takeUntil, filter, scan, take } from "rxjs/operators"; +import { map, switchMap, takeUntil, filter, scan, take, tap } from "rxjs/operators"; +import { clamp } from "src/util/generator"; @Component({ selector : 'mobile-overlay', @@ -24,15 +25,15 @@ div:not(.active) > span:before display: inline-block; } ` - ], - changeDetection: ChangeDetectionStrategy.OnPush + ] }) -export class MobileOverlay implements AfterViewInit, OnDestroy{ +export class MobileOverlay implements OnInit, OnDestroy{ @Input() tunableProperties : string [] = [] @Output() deltaValue : EventEmitter<{delta:number, selectedProp : string}> = new EventEmitter() @ViewChild('initiator', {read: ElementRef}) initiator : ElementRef @ViewChild('mobileMenuContainer', {read: ElementRef}) menuContainer : ElementRef + @ViewChild('intersector', {read: ElementRef}) intersector: ElementRef private _onDestroySubject : Subject<boolean> = new Subject() @@ -42,25 +43,38 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{ ? this._focusedProperties : this.tunableProperties[0] } + get focusedIndex(){ + return this._focusedProperties + ? this.tunableProperties.findIndex(p => p === this._focusedProperties) + : 0 + } public showScreen$ : Observable<boolean> public showProperties$ : Observable<boolean> + public showDelta$: Observable<boolean> + public showInitiator$: Observable<boolean> private _drag$ : Observable<any> - + private intersectionObserver: IntersectionObserver + ngOnDestroy(){ this._onDestroySubject.next(true) this._onDestroySubject.complete() } - ngAfterViewInit(){ + ngOnInit(){ + const config = { + root: this.intersector.nativeElement, + threshold: [...Array(10)].map((_, k) => k / 10) + } - this.showScreen$ = merge( - fromEvent(this.initiator.nativeElement, 'touchstart'), - fromEvent(this.initiator.nativeElement, 'touchend'), - ).pipe( - map((ev:TouchEvent) => ev.touches.length === 1) - ) + this.intersectionObserver = new IntersectionObserver((arg) => { + if (arg[0].isIntersecting) { + this.focusItemIndex = 2- Math.floor(arg[0].intersectionRatio * this.tunableProperties.length) + } + }, config) + this.intersectionObserver.observe(this.menuContainer.nativeElement) + this._drag$ = fromEvent(this.initiator.nativeElement, 'touchmove').pipe( takeUntil(fromEvent(this.initiator.nativeElement, 'touchend').pipe( filter((ev:TouchEvent) => ev.touches.length === 0) @@ -73,21 +87,58 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{ filter(ev => ev.length === 2) ) - this.showProperties$ = fromEvent(this.initiator.nativeElement, 'touchstart').pipe( - switchMap(() => concat( - this._drag$.pipe( - map(double => ({ - deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX, - deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY - })), - scan((acc, _curr) => acc), - map(v => v.deltaY ** 2 > v.deltaX ** 2) - ), - of(false) - ) + this.showProperties$ = concat( + of(false), + fromEvent(this.initiator.nativeElement, 'touchstart').pipe( + switchMap(() => concat( + this._drag$.pipe( + map(double => ({ + deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX, + deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY + })), + scan((acc, _curr) => acc), + map(v => v.deltaY ** 2 > v.deltaX ** 2) + ), + of(false) + )) ) ) + + this.showDelta$ = concat( + of(false), + fromEvent(this.initiator.nativeElement, 'touchstart').pipe( + switchMap(() => concat( + this._drag$.pipe( + map(double => ({ + deltaX : double[1].touches[0].screenX - double[0].touches[0].screenX, + deltaY : double[1].touches[0].screenY - double[0].touches[0].screenY + })), + scan((acc, _curr) => acc), + map(v => v.deltaX ** 2 > v.deltaY ** 2) + ), + of(false) + )) + ) + ) + + this.showInitiator$ = combineLatest( + this.showProperties$, + this.showDelta$ + ).pipe( + map(([flag1, flag2]) => !flag1 && !flag2) + ) + + this.showScreen$ = combineLatest( + merge( + fromEvent(this.initiator.nativeElement, 'touchstart'), + fromEvent(this.initiator.nativeElement, 'touchend') + ), + this.showInitiator$ + ).pipe( + map(([ev, showInitiator] : [TouchEvent, boolean]) => showInitiator && ev.touches.length === 1) + ) + fromEvent(this.initiator.nativeElement, 'touchstart').pipe( switchMap(() => this._drag$.pipe( map(double => ({ @@ -130,7 +181,16 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{ filter(v => v[0]), map(v => v[1]), takeUntil(this._onDestroySubject) - ).subscribe(v => this.scrollHeight = v.deltaY) + ).subscribe(v => { + const deltaY = v.deltaY + const cellHeight = this.menuContainer && this.tunableProperties && this.tunableProperties.length > 0 && this.menuContainer.nativeElement.offsetHeight / this.tunableProperties.length + const adjHeight = - this.focusedIndex * cellHeight - cellHeight * 0.5 + + const min = - cellHeight * 0.5 + const max = - this.tunableProperties.length * cellHeight + cellHeight * 0.5 + const finalYTranslate = clamp(adjHeight + deltaY, min, max ) + this.menuTransform = `translate(0px, ${finalYTranslate}px)` + }) this.showProperties$.pipe( takeUntil(this._onDestroySubject), @@ -139,47 +199,12 @@ export class MobileOverlay implements AfterViewInit, OnDestroy{ if(this.focusItemIndex >= 0){ this._focusedProperties = this.tunableProperties[this.focusItemIndex] } - this.scrollHeight = 0 }) + } - scrollHeight : number = 0 + public menuTransform = `translate(0px, 0px)` - get defaultY(){ - return this.tunableProperties.findIndex(p => p === this.focusedProperty) * this.menuCellHeight - } - - get menuTransform(){ - return `translate(0px, ${this.menuYTranslate}px)` - } + public focusItemIndex: number = 0 - get menuYTranslate(){ - return this.menuContainer - ? Math.max( - Math.min( - this.defaultY + this.scrollHeight, - this.menuContainerHeight / 2 - this.menuCellHeight / 2 - ), - - this.menuContainerHeight / 2 + this.menuCellHeight / 2 - ) - : this.defaultY - } - - get menuContainerHeight(){ - return this.menuContainer - ? this.menuContainer.nativeElement.offsetHeight - : 0 - } - - get menuCellHeight(){ - return this.tunableProperties.length > 0 - ? this.menuContainerHeight / this.tunableProperties.length - : 0 - } - - get focusItemIndex():number{ - return this.menuContainer - ? Math.floor((this.menuContainerHeight / 2 - this.menuYTranslate) / this.menuCellHeight) - : -1 - } } \ No newline at end of file diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css index 96faf8aea..f1532380e 100644 --- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css +++ b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.style.css @@ -18,24 +18,34 @@ left: 0; position: absolute; - display: flex; - justify-content: center; - align-items: center; - color : black; background-color: rgba(255, 255, 255, 0.5); } + :host-context([darktheme="true"]) [screen] { color : white; background-color: rgba(0, 0, 0, 0.5); } +[intersector] +{ + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 50%; + + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +} + [mobileMenuContainer] { z-index: 1000; - transform: translate(0, 155px); } .scrollFocus:after diff --git a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html index 541b687d8..ed89d1001 100644 --- a/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html +++ b/src/ui/nehubaContainer/mobileOverlay/mobileOverlay.template.html @@ -1,18 +1,27 @@ -<div screen *ngIf = "showProperties$ | async"> - <div [style.transform] = "menuTransform" class = "btn-group-vertical" role = "group" mobileMenuContainer #mobileMenuContainer> - <div - *ngFor = "let p of tunableProperties; let i = index" - [ngClass] = "{'active' : p === focusedProperty, 'scrollFocus' : i === focusItemIndex}" - class = "btn btn-default theme-controlled"> - <span> - {{ p }} - </span> +<div screen [hidden]="!(showProperties$ | async)"> + <div intersector #intersector> + <div [style.transform] = "menuTransform" class = "btn-group-vertical" role = "group" mobileMenuContainer #mobileMenuContainer> + <div + *ngFor = "let p of tunableProperties; let i = index" + [ngClass] = "{'active' : p === focusedProperty, 'scrollFocus': i === focusItemIndex}" + class = "btn btn-default theme-controlled property scrollFocus"> + <!-- scrollFocus class --> + <span> + {{ p }} + </span> + </div> </div> </div> + {{ menuTransform }} {{ focusItemIndex }} </div> -<ng-content *ngIf = "showScreen$ | async" select = "[guide]" guide> + +<ng-content *ngIf="showDelta$ | async" select="[delta]" guide> </ng-content> -<div #initiator> + +<ng-content *ngIf="showScreen$ | async" select="[guide]" guide> +</ng-content> + +<div [hidden]="!(showInitiator$ | async)" #initiator> <ng-content select="[initiator]"> </ng-content> </div> \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 0cb0bf48b..58a0fe4c1 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -577,53 +577,18 @@ export class NehubaContainer implements OnInit, OnDestroy{ public showObliqueRotate$ : Observable<boolean> ngAfterViewInit(){ - // if(this.isMobile){ - - // this.showObliqueScreen$ = merge( - // fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchstart'), - // fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchend') - // ).pipe( - // map((ev:TouchEvent) => ev.touches.length === 1) - // ) - - // fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchstart').pipe( - // switchMap(() => fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchmove').pipe( - // takeUntil(fromEvent(this.mobileObliqueCtrl.nativeElement, 'touchend').pipe( - // filter((ev:TouchEvent) => ev.touches.length === 0) - // )), - // map((ev:TouchEvent) => (ev.preventDefault(), ev.stopPropagation(), ev)), - // filter((ev:TouchEvent) => ev.touches.length === 1), - // map((ev:TouchEvent) => [ev.touches[0].screenX, ev.touches[0].screenY] ), - - // /* TODO reduce boiler plate, export */ - // scan((acc,curr) => acc.length < 2 - // ? acc.concat([curr]) - // : acc.slice(1).concat([curr]), []), - // filter(isdouble => isdouble.length === 2), - // map(double => ({ - // deltaX : double[1][0] - double[0][0], - // deltaY : double[1][1] - double[0][1] - // })) - // )) - // ) - // .subscribe(({deltaX, deltaY}) => { - - - - // }) - // } } ngOnDestroy(){ this.subscriptions.forEach(s=>s.unsubscribe()) } - get tunableMobileProperties(){ - return ['Oblique Rotate X', 'Oblique Rotate Y', 'Oblique Rotate Z'] - } + public tunableMobileProperties = ['Oblique Rotate X', 'Oblique Rotate Y', 'Oblique Rotate Z'] + public selectedProp = null handleMobileOverlayEvent(obj:any){ const {delta, selectedProp} = obj + this.selectedProp = selectedProp const idx = this.tunableMobileProperties.findIndex(p => p === selectedProp) idx === 0 diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css index 4234ac6d1..ce3d02047 100644 --- a/src/ui/nehubaContainer/nehubaContainer.style.css +++ b/src/ui/nehubaContainer/nehubaContainer.style.css @@ -142,13 +142,15 @@ div.loadingIndicator div.spinnerAnimationCircle div[mobileObliqueCtrl] { font-size: 200%; - margin-top:-2.5rem; - margin-left:-2.5rem; - padding-left: 1rem; - padding-top: 1rem; position: absolute; top: 50%; left: 50%; + width: 0; + height: 0; + + display: flex; + align-items: center; + justify-content: center; pointer-events: all; } @@ -164,16 +166,39 @@ div[mobileObliqueScreen] pointer-events: all; } -div[mobileObliqueGuide] +div.base { position : absolute; - top: 40%; - width: 100%; + top: 50%; + left: 50%; + width: 0; + height: 0; display:flex; - flex-direction: column; + flex-direction: column-reverse; align-items: center; } +div[delta] +{ + white-space: nowrap +} + +div[mobileObliqueGuide] +{ + background-color: rgba(250,250,250,0.8); +} + +div[mobileObliqueGuide] > * +{ + white-space: nowrap; +} + +:host-context([darktheme="true"]) div[mobileObliqueGuide] +{ + + background-color: rgba(50,50,50,0.8); +} + div#scratch-pad { position: absolute; @@ -182,4 +207,5 @@ div#scratch-pad width: 100%; height: 100%; pointer-events: none; -} \ No newline at end of file +} + diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index 82b5510ac..dbe225ff5 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -155,15 +155,22 @@ </div> <mobile-overlay - *ngIf="isMobile && viewerLoaded" + *ngIf="isMobile && viewerLoaded" [tunableProperties]="tunableMobileProperties" (deltaValue)="handleMobileOverlayEvent($event)"> - <div mobileObliqueGuide guide> - <div> - <i class="fas fa-resize-vertical"></i> oblique mode + <div class="base" delta> + <div mobileObliqueGuide class="p-2 mb-4 shadow"> + {{ selectedProp }} </div> - <div> - <i class="fas fa-resize-horizontal"></i> rotate slice + </div> + <div class="base" guide> + <div mobileObliqueGuide class="p-2 mb-4 shadow"> + <div> + <i class="fas fa-arrows-alt-v"></i> oblique mode + </div> + <div> + <i class="fas fa-arrows-alt-h"></i> rotate slice + </div> </div> </div> <div mobileObliqueCtrl initiator> diff --git a/src/ui/signinBanner/signinBanner.components.ts b/src/ui/signinBanner/signinBanner.components.ts index eaff2a05a..117e8a019 100644 --- a/src/ui/signinBanner/signinBanner.components.ts +++ b/src/ui/signinBanner/signinBanner.components.ts @@ -11,7 +11,8 @@ import { map, filter, distinctUntilChanged } from "rxjs/operators"; selector: 'signin-banner', templateUrl: './signinBanner.template.html', styleUrls: [ - './signinBanner.style.css' + './signinBanner.style.css', + '../btnShadow.style.css' ], changeDetection: ChangeDetectionStrategy.OnPush }) diff --git a/src/ui/signinBanner/signinBanner.template.html b/src/ui/signinBanner/signinBanner.template.html index e07fa7365..a5d7389f6 100644 --- a/src/ui/signinBanner/signinBanner.template.html +++ b/src/ui/signinBanner/signinBanner.template.html @@ -1,4 +1,5 @@ <dropdown-component + *ngIf="!isMobile" (itemSelected)="changeTemplate($event)" [activeDisplay]="displayActiveTemplate" [selectedItem]="selectedTemplate$ | async" @@ -23,11 +24,12 @@ </div> <!-- signin --> -<div - *ngIf="!isMobile" - (click)="showSignin()" - class="btn btn-outline-secondary btn-sm rounded-circle"> - <i - [ngClass]="user ? 'fa-user' : 'fa-sign-in-alt'" - class="fas"></i> -</div> +<div class="btnWrapper"> + <div + (click)="showSignin()" + class="btn btn-outline-secondary btn-sm rounded-circle"> + <i + [ngClass]="user ? 'fa-user' : 'fa-sign-in-alt'" + class="fas"></i> + </div> +</div> \ No newline at end of file diff --git a/src/util/generator.ts b/src/util/generator.ts index 90992f8fd..5db5d5015 100644 --- a/src/util/generator.ts +++ b/src/util/generator.ts @@ -12,4 +12,16 @@ export function* timedValues(ms:number = 500,mode:string = 'linear'){ yield getValue( (Date.now() - startTime) / ms ) } return 1 +} + +export function clamp(val: number, min: number, max: number) { + const _min = min < max ? min : max + const _max = min < max ? max : min + return Math.min( + Math.max( + val, + _min + ), + _max + ) } \ No newline at end of file -- GitLab