From 1dd613baf20c78ed5646ff359bd07cfbeba3206b Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Thu, 8 Aug 2019 17:50:40 +0200 Subject: [PATCH] feat: prettify menu icons --- .../atlasViewer.constantService.service.ts | 22 ++- .../atlasViewer.pluginService.service.ts | 60 ++++++-- .../widgetUnit/widgetService.service.ts | 22 ++- .../widgetUnit/widgetUnit.component.ts | 27 +++- src/ui/menuicons/menuicons.component.ts | 90 ++++++++--- src/ui/menuicons/menuicons.style.css | 24 ++- src/ui/menuicons/menuicons.template.html | 141 +++++++++--------- .../sharedModules/angularMaterial.module.ts | 7 +- src/ui/ui.module.ts | 4 + src/util/pipes/menuIconKgSearchBtnCls.pipe.ts | 14 ++ src/util/pipes/menuIconPluginBtnCls.pipe.ts | 15 ++ 11 files changed, 309 insertions(+), 117 deletions(-) create mode 100644 src/util/pipes/menuIconKgSearchBtnCls.pipe.ts create mode 100644 src/util/pipes/menuIconPluginBtnCls.pipe.ts diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index a452c83b0..4bf66c8f1 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -1,8 +1,9 @@ import { Injectable } from "@angular/core"; -import { Store } from "@ngrx/store"; -import { ViewerStateInterface, Property } from "../services/stateStore.service"; -import { Subject } from "rxjs"; +import { Store, select } from "@ngrx/store"; +import { ViewerStateInterface } from "../services/stateStore.service"; +import { Subject, Observable } from "rxjs"; import { ACTION_TYPES, ViewerConfiguration } from 'src/services/state/viewerConfig.store' +import { map, shareReplay } from "rxjs/operators"; export const CM_THRESHOLD = `0.05` export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;` @@ -14,6 +15,7 @@ export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r export class AtlasViewerConstantsServices{ public darktheme: boolean = false + public darktheme$: Observable<boolean> public mobile: boolean public loadExportNehubaPromise : Promise<boolean> @@ -247,6 +249,13 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float /* https://stackoverflow.com/a/25394023/6059235 */ this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(ua) + this.darktheme$ = this.store.pipe( + select('viewerState'), + select('templateSelected'), + map(({useTheme}) => useTheme === 'dark'), + shareReplay(1) + ) + /** * set gpu limit if user is on mobile */ @@ -259,6 +268,13 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float }) } } + + catchError(e: Error | string){ + /** + * DO NOT REMOVE + * general catch all & reflect in UI + */ + } } const parseURLToElement = (url:string):HTMLElement=>{ diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts index 7adc86452..9d9ac4124 100644 --- a/src/atlasViewer/atlasViewer.pluginService.service.ts +++ b/src/atlasViewer/atlasViewer.pluginService.service.ts @@ -6,8 +6,8 @@ import { PluginUnit } from "./pluginUnit/pluginUnit.component"; import { WidgetServices } from "./widgetUnit/widgetService.service"; import '../res/css/plugin_styles.css' -import { interval } from "rxjs"; -import { take, takeUntil } from "rxjs/operators"; +import { interval, BehaviorSubject, Observable, merge, of } from "rxjs"; +import { take, takeUntil, map, shareReplay } from "rxjs/operators"; import { Store } from "@ngrx/store"; import { WidgetUnit } from "./widgetUnit/widgetUnit.component"; import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service"; @@ -79,6 +79,24 @@ export class PluginServices{ .then(arr=> this.fetchedPluginManifests = arr) .catch(console.error) + + this.minimisedPlugins$ = merge( + of(new Set()), + this.widgetService.minimisedWindow$ + ).pipe( + map(set => { + const returnSet = new Set() + for (let [pluginName, wu] of this.mapPluginNameToWidgetUnit) { + if (set.has(wu)) { + returnSet.add(pluginName) + } + } + return returnSet + }), + shareReplay(1) + ) + + this.launchedPlugins$ = new BehaviorSubject(new Set()) } launchNewWidget = (manifest) => this.launchPlugin(manifest) @@ -108,21 +126,39 @@ export class PluginServices{ ]) } - public launchedPlugins: Set<string> = new Set() + private launchedPlugins: Set<string> = new Set() + public launchedPlugins$: BehaviorSubject<Set<string>> + pluginHasLaunched(pluginName:string) { + return this.launchedPlugins.has(pluginName) + } + addPluginToLaunchedSet(pluginName:string){ + this.launchedPlugins.add(pluginName) + this.launchedPlugins$.next(this.launchedPlugins) + } + removePluginToLaunchedSet(pluginName:string){ + this.launchedPlugins.delete(pluginName) + this.launchedPlugins$.next(this.launchedPlugins) + } + private mapPluginNameToWidgetUnit: Map<string, WidgetUnit> = new Map() - pluginMinimised(pluginManifest:PluginManifest){ - return this.widgetService.minimisedWindow.has( this.mapPluginNameToWidgetUnit.get(pluginManifest.name) ) + pluginIsMinimised(pluginName:string) { + return this.widgetService.isMinimised( this.mapPluginNameToWidgetUnit.get(pluginName) ) } + public minimisedPlugins$: Observable<Set<string>> + public orphanPlugins: Set<PluginManifest> = new Set() launchPlugin(plugin:PluginManifest){ - if(this.apiService.interactiveViewer.pluginControl[plugin.name]) - { - console.warn('plugin already launched. blinking for 10s.') - this.apiService.interactiveViewer.pluginControl[plugin.name].blink(10) + if(this.apiService.interactiveViewer.pluginControl[plugin.name]){ + // if already launched, toggle minimise/restore + const wu = this.mapPluginNameToWidgetUnit.get(plugin.name) - this.widgetService.minimisedWindow.delete(wu) + if (this.widgetService.isMinimised(wu)) { + this.widgetService.unminimise(wu) + } else { + this.widgetService.minimise(wu) + } return Promise.reject('plugin already launched') } return this.readyPlugin(plugin) @@ -163,7 +199,7 @@ export class PluginServices{ const shutdownCB = [ () => { - this.launchedPlugins.delete(plugin.name) + this.removePluginToLaunchedSet(plugin.name) } ] @@ -191,7 +227,7 @@ export class PluginServices{ title : plugin.displayName || plugin.name }) - this.launchedPlugins.add(plugin.name) + this.addPluginToLaunchedSet(plugin.name) this.mapPluginNameToWidgetUnit.set(plugin.name, widgetCompRef.instance) const unsubscribeOnPluginDestroy = [] diff --git a/src/atlasViewer/widgetUnit/widgetService.service.ts b/src/atlasViewer/widgetUnit/widgetService.service.ts index 521d69ac8..3d45db7d4 100644 --- a/src/atlasViewer/widgetUnit/widgetService.service.ts +++ b/src/atlasViewer/widgetUnit/widgetService.service.ts @@ -2,7 +2,7 @@ import { ComponentRef, ComponentFactory, Injectable, ViewContainerRef, Component import { WidgetUnit } from "./widgetUnit.component"; import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service"; -import { Subscription } from "rxjs"; +import { Subscription, BehaviorSubject } from "rxjs"; @Injectable({ @@ -20,7 +20,8 @@ export class WidgetServices{ private clickedListener : Subscription[] = [] - public minimisedWindow: Set<WidgetUnit> = new Set() + public minimisedWindow$: BehaviorSubject<Set<WidgetUnit>> + private minimisedWindow: Set<WidgetUnit> = new Set() constructor( private cfr:ComponentFactoryResolver, @@ -28,6 +29,7 @@ export class WidgetServices{ private injector : Injector ){ this.widgetUnitFactory = this.cfr.resolveComponentFactory(WidgetUnit) + this.minimisedWindow$ = new BehaviorSubject(this.minimisedWindow) } clearAllWidgets(){ @@ -40,6 +42,16 @@ export class WidgetServices{ minimise(wu:WidgetUnit){ this.minimisedWindow.add(wu) + this.minimisedWindow$.next(new Set(this.minimisedWindow)) + } + + isMinimised(wu:WidgetUnit){ + return this.minimisedWindow.has(wu) + } + + unminimise(wu:WidgetUnit){ + this.minimisedWindow.delete(wu) + this.minimisedWindow$.next(new Set(this.minimisedWindow)) } addNewWidget(guestComponentRef:ComponentRef<any>,options?:Partial<WidgetOptionsInterface>):ComponentRef<WidgetUnit>{ @@ -93,9 +105,12 @@ export class WidgetServices{ this.clickedListener.push( _component.instance.clickedEmitter.subscribe((widgetUnit:WidgetUnit)=>{ + /** + * TODO this operation + */ if(widgetUnit.state !== 'floating') return - const widget = [...this.widgetComponentRefs].find(widget=>widget.instance===widgetUnit) + const widget = [...this.widgetComponentRefs].find(widget=>widget.instance === widgetUnit) if(!widget) return const idx = this.floatingContainer.indexOf(widget.hostView) @@ -103,7 +118,6 @@ export class WidgetServices{ return this.floatingContainer.detach(idx) this.floatingContainer.insert(widget.hostView) - }) ) diff --git a/src/atlasViewer/widgetUnit/widgetUnit.component.ts b/src/atlasViewer/widgetUnit/widgetUnit.component.ts index fc32210cd..b1667492c 100644 --- a/src/atlasViewer/widgetUnit/widgetUnit.component.ts +++ b/src/atlasViewer/widgetUnit/widgetUnit.component.ts @@ -1,6 +1,8 @@ -import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit } from "@angular/core"; +import { Component, ViewChild, ViewContainerRef,ComponentRef, HostBinding, HostListener, Output, EventEmitter, Input, ElementRef, OnInit, OnDestroy } from "@angular/core"; import { WidgetServices } from "./widgetService.service"; import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.service"; +import { Subscription, Observable } from "rxjs"; +import { map } from "rxjs/operators"; @Component({ @@ -10,7 +12,7 @@ import { AtlasViewerConstantsServices } from "../atlasViewer.constantService.ser ] }) -export class WidgetUnit implements OnInit{ +export class WidgetUnit implements OnInit, OnDestroy{ @ViewChild('container',{read:ViewContainerRef}) container : ViewContainerRef @ViewChild('emptyspan',{read:ElementRef}) emtpy : ElementRef @@ -24,9 +26,10 @@ export class WidgetUnit implements OnInit{ height : string = this.state === 'docked' ? null : '0px' @HostBinding('style.display') - get isMinimised(){ - return this.widgetServices.minimisedWindow.has(this) ? 'none' : null - } + isMinimised: string + + isMinimised$: Observable<boolean> + /** * TODO * upgrade to angular>=7, and use cdk to handle draggable components @@ -59,6 +62,7 @@ export class WidgetUnit implements OnInit{ public guestComponentRef : ComponentRef<any> public widgetServices:WidgetServices public cf : ComponentRef<WidgetUnit> + private subscriptions: Subscription[] = [] public id: string constructor( @@ -69,6 +73,19 @@ export class WidgetUnit implements OnInit{ ngOnInit(){ this.canBeDocked = typeof this.widgetServices.dockedContainer !== 'undefined' + + this.isMinimised$ = this.widgetServices.minimisedWindow$.pipe( + map(set => set.has(this)) + ) + this.subscriptions.push( + this.isMinimised$.subscribe(flag => this.isMinimised = flag ? 'none' : null) + ) + } + + ngOnDestroy(){ + while(this.subscriptions.length > 0){ + this.subscriptions.pop().unsubscribe() + } } /** diff --git a/src/ui/menuicons/menuicons.component.ts b/src/ui/menuicons/menuicons.component.ts index c488a4e52..133a9b68f 100644 --- a/src/ui/menuicons/menuicons.component.ts +++ b/src/ui/menuicons/menuicons.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver, AfterViewInit } from "@angular/core"; +import { Component, ComponentRef, Injector, ComponentFactory, ComponentFactoryResolver } from "@angular/core"; import { WidgetServices } from "src/atlasViewer/widgetUnit/widgetService.service"; import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component"; @@ -7,9 +7,10 @@ import { DataBrowser } from "src/ui/databrowserModule/databrowser/databrowser.co import { PluginBannerUI } from "../pluginBanner/pluginBanner.component"; import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; import { DatabrowserService } from "../databrowserModule/databrowser.service"; -import { PluginServices } from "src/atlasViewer/atlasViewer.pluginService.service"; +import { PluginServices, PluginManifest } from "src/atlasViewer/atlasViewer.pluginService.service"; import { Store, select } from "@ngrx/store"; -import { Observable } from "rxjs"; +import { Observable, BehaviorSubject, combineLatest } from "rxjs"; +import { map, shareReplay } from "rxjs/operators"; @Component({ selector: 'menu-icons', @@ -43,8 +44,23 @@ export class MenuIconsBar{ pluginBanner: ComponentRef<PluginBannerUI> = null pbWidget: ComponentRef<WidgetUnit> = null - get isMobile(){ - return this.constantService.mobile + isMobile: boolean = false + mobileRespBtnClass: string + + public darktheme$: Observable<boolean> + + public themedBtnClass$: Observable<string> + + public skeletonBtnClass$: Observable<string> + + private layerBrowserExists$: BehaviorSubject<boolean> = new BehaviorSubject(false) + public layerBrowserBtnClass$: Observable<string> + + public toolBtnClass$: Observable<string> + public getKgSearchBtnCls$: Observable<[Set<WidgetUnit>, string]> + + get darktheme(){ + return this.constantService.darktheme } public selectedTemplate$: Observable<any> @@ -59,6 +75,9 @@ export class MenuIconsBar{ store: Store<any> ){ + this.isMobile = this.constantService.mobile + this.mobileRespBtnClass = this.constantService.mobile ? 'btn-lg' : 'btn-sm' + this.dbService.createDatabrowser = this.clickSearch.bind(this) this.dbcf = cfr.resolveComponentFactory(DataBrowser) @@ -69,6 +88,40 @@ export class MenuIconsBar{ select('viewerState'), select('templateSelected') ) + + this.themedBtnClass$ = this.constantService.darktheme$.pipe( + map(flag => flag ? 'btn-dark' : 'btn-light' ), + shareReplay(1) + ) + + this.skeletonBtnClass$ = this.constantService.darktheme$.pipe( + map(flag => `${this.mobileRespBtnClass} ${flag ? 'text-light' : 'text-dark'}`), + shareReplay(1) + ) + + this.layerBrowserBtnClass$ = combineLatest( + this.layerBrowserExists$, + this.themedBtnClass$ + ).pipe( + map(([flag,themedBtnClass]) => `${this.mobileRespBtnClass} ${flag ? 'btn-primary' : themedBtnClass}`) + ) + + this.launchedPlugins$ = this.pluginServices.launchedPlugins$.pipe( + map(set => Array.from(set)) + ) + + this.getPluginBtnClass$ = combineLatest( + this.pluginServices.launchedPlugins$, + this.pluginServices.minimisedPlugins$, + this.themedBtnClass$ + ) + + this.darktheme$ = this.constantService.darktheme$ + + this.getKgSearchBtnCls$ = combineLatest( + this.widgetServices.minimisedWindow$, + this.themedBtnClass$ + ) } /** @@ -98,7 +151,7 @@ export class MenuIconsBar{ } public catchError(e) { - + this.constantService.catchError(e) } public clickLayer(event: MouseEvent){ @@ -117,7 +170,10 @@ export class MenuIconsBar{ titleHTML: '<i class="fas fa-layer-group"></i> Layer Browser' }) + this.layerBrowserExists$.next(true) + this.lbWidget.onDestroy(() => { + this.layerBrowserExists$.next(false) this.layerBrowser = null this.lbWidget = null }) @@ -154,19 +210,19 @@ export class MenuIconsBar{ this.pbWidget.instance.position = [left, top] } - get databrowserIsShowing() { - return this.dataBrowser !== null - } - - get layerbrowserIsShowing() { - return this.layerBrowser !== null + public clickPluginIcon(manifest: PluginManifest){ + this.pluginServices.launchPlugin(manifest) + .catch(this.constantService.catchError) } - get pluginbrowserIsShowing() { - return this.pluginBanner !== null + public searchIconClickHandler(wu: WidgetUnit){ + if (this.widgetServices.isMinimised(wu)) { + this.widgetServices.unminimise(wu) + } else { + this.widgetServices.minimise(wu) + } } - get dataBrowserTitle() { - return `Browse` - } + public getPluginBtnClass$: Observable<[Set<string>, Set<string>, string]> + public launchedPlugins$: Observable<string[]> } \ No newline at end of file diff --git a/src/ui/menuicons/menuicons.style.css b/src/ui/menuicons/menuicons.style.css index 8a1663369..e36c074da 100644 --- a/src/ui/menuicons/menuicons.style.css +++ b/src/ui/menuicons/menuicons.style.css @@ -20,4 +20,26 @@ :host >>> .tooltip.right .tooltip-arrow { border-right-color: rgba(128, 128, 128, 0.5); -} \ No newline at end of file +} + +.sleight-of-hand-back > *:not(:first-child) +{ + margin-left: 0.1em; +} + +.sleight-of-hand:hover * +{ + transition: opacity 300ms ease-in-out; +} + +.sleight-of-hand:not(:hover) .sleight-of-hand-back +{ + opacity: 0; + pointer-events: none; +} + +.sleight-of-hand:hover .sleight-of-hand-front +{ + opacity: 0; + pointer-events: none; +} diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html index 214281632..eec24e233 100644 --- a/src/ui/menuicons/menuicons.template.html +++ b/src/ui/menuicons/menuicons.template.html @@ -3,93 +3,90 @@ <!-- hide icons when templates has yet been selected --> <ng-template [ngIf]="selectedTemplate$ | async"> + + <!-- layer browser --> <div - *ngIf="false" - [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> + matTooltip="Layer" + matTooltipPosition="right" + (click)="clickLayer($event)" + [class]="layerBrowserBtnClass$ | async" + class="btn"> + <i class="fas fa-layer-group"> + </i> </div> - <div - [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> + <!-- tools --> + <div class="position-relative sleight-of-hand"> + + <!-- shown after mouse over --> + <div class="position-absolute d-flex flex-row sleight-of-hand-back"> + + <!-- add new tool btn --> + <div + matTooltip="Add new plugin" + [matTooltipShowDelay]="100" + matTooltipPosition="below" + [class]="(skeletonBtnClass$ | async)"> + <i class="fas fa-plus"></i> + </div> + + <!-- render all fetched tools --> + <div + *ngFor="let manifest of pluginServices.fetchedPluginManifests" + [matTooltip]="manifest.displayName || manifest.name" + matTooltipPosition="below" + (click)="clickPluginIcon(manifest)" + [class]="(getPluginBtnClass$ | async | menuIconPluginBtnClsPipe : manifest.name) + ' btn w-1em bs-content-box ' + mobileRespBtnClass"> + {{ (manifest.displayName || manifest.name).slice(0, 1) }} + </div> + + <small + *ngIf="pluginServices.fetchedPluginManifests.length === 0" + [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'"> + No plugins loaded. + </small> </div> - </div> - <div - *ngIf="false" - [ngClass]="isMobile ? 'btnWrapper-lg' : ''" - class="btnWrapper"> + <!-- shown icon prior to mouse over --> <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> + [matBadge]="(launchedPlugins$ | async)?.length > 0 ? (launchedPlugins$ | async)?.length : null" + [class]="(skeletonBtnClass$ | async) + ' position-relative btn sleight-of-hand-front'"> + <i class="fas fa-tools"></i> </div> </div> - <div - *ngFor="let manifest of pluginServices.fetchedPluginManifests" - [tooltip]="manifest.displayName || manifest.name" - placement="right" - [ngClass]="isMobile ? 'btnWrapper-lg' : ''" - class="btnWrapper"> + <!-- search kg --> + <div class="position-relative sleight-of-hand"> - <div - (click)="pluginServices.launchPlugin(manifest).catch(catchError)" - [ngClass]="!pluginServices.launchedPlugins.has(manifest.name) ? 'btn-outline-secondary' : pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'" - class="shadow btn btn-sm rounded-circle"> - {{ (manifest.displayName || manifest.name).slice(0, 1) }} - </div> - </div> + <!-- shown after mouse over --> + <div class="position-absolute d-flex flex-row sleight-of-hand-back align-items-center"> - <div - *ngFor="let manifest of pluginServices.orphanPlugins" - [tooltip]="manifest.displayName || manifest.name" - placement="right" - [ngClass]="isMobile ? 'btnWrapper-lg' : ''" - class="btnWrapper"> + <div + [ngClass]="(skeletonBtnClass$ | async) + ' position-relative btn text-muted'"> + <i class="fas fa-search"></i> + </div> - <div - (click)="pluginServices.launchPlugin(manifest).catch(catchError)" - [ngClass]="pluginServices.pluginMinimised(manifest) ? 'btn-outline-info' : 'btn-info'" - class="shadow btn btn-sm rounded-circle"> - {{ (manifest.displayName || manifest.name).slice(0, 1) }} + <!-- render all searched kg --> + <div + *ngFor="let wu of dbService.instantiatedWidgetUnits" + [class]="(getKgSearchBtnCls$ | async | menuIconKgSearchBtnClsPipe : wu) + ' btn w-1em bs-content-box ' + mobileRespBtnClass" + [matTooltip]="wu.title" + (click)="searchIconClickHandler(wu)" + matTooltipPosition="below"> + <i class="fas fa-search"></i> + </div> + + <small + *ngIf="dbService.instantiatedWidgetUnits.length === 0" + [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'"> + Right click any area to search + </small> </div> - </div> - <div - *ngFor="let wu of dbService.instantiatedWidgetUnits" - [ngClass]="isMobile ? 'btnWrapper-lg' : ''" - placement="right" - [tooltip]="wu.title" - class="btnWrapper"> + <!-- shown icon prior to mouse over --> <div - (click)="widgetServices.minimisedWindow.delete(wu)" - [ngClass]="widgetServices.minimisedWindow.has(wu) ? 'btn-outline-info' : 'btn-info'" - class="shadow btn btn-sm rounded-circle"> + [class]="(skeletonBtnClass$ | async) + ' position-relative btn sleight-of-hand-front'" + [matBadge]="dbService.instantiatedWidgetUnits.length > 0 ? dbService.instantiatedWidgetUnits.length : null"> <i class="fas fa-search"></i> </div> </div> diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts index b63e2dfaa..1efcdd9f3 100644 --- a/src/ui/sharedModules/angularMaterial.module.ts +++ b/src/ui/sharedModules/angularMaterial.module.ts @@ -4,12 +4,13 @@ import { MatSidenavModule, MatCardModule, MatTabsModule, - MatTooltipModule + MatTooltipModule, + MatBadgeModule } from '@angular/material'; import { NgModule } from '@angular/core'; @NgModule({ - imports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], - exports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], + imports: [MatBadgeModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], + exports: [MatBadgeModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], }) export class AngularMaterialModule { } \ No newline at end of file diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index a7c08e93c..d7087401f 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -48,6 +48,8 @@ import { KGToS } from "./kgtos/kgtos.component"; import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module' import { TemplateParcellationsDecorationPipe } from "src/util/pipes/templateParcellationDecoration.pipe"; import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe"; +import { MenuIconPluginBtnClsPipe } from "src/util/pipes/menuIconPluginBtnCls.pipe"; +import { MenuIconKgSearchBtnClsPipe } from "src/util/pipes/menuIconKgSearchBtnCls.pipe"; @NgModule({ @@ -99,6 +101,8 @@ import { AppendtooltipTextPipe } from "src/util/pipes/appendTooltipText.pipe"; FilterNameBySearch, TemplateParcellationsDecorationPipe, AppendtooltipTextPipe, + MenuIconPluginBtnClsPipe, + MenuIconKgSearchBtnClsPipe, /* directive */ DownloadDirective, diff --git a/src/util/pipes/menuIconKgSearchBtnCls.pipe.ts b/src/util/pipes/menuIconKgSearchBtnCls.pipe.ts new file mode 100644 index 000000000..1b6b1d7b5 --- /dev/null +++ b/src/util/pipes/menuIconKgSearchBtnCls.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component"; + +@Pipe({ + name: 'menuIconKgSearchBtnClsPipe' +}) + +export class MenuIconKgSearchBtnClsPipe implements PipeTransform{ + public transform([minimisedWidgetUnit, themedBtnCls]: [Set<WidgetUnit>, string], wu: WidgetUnit, ){ + return minimisedWidgetUnit.has(wu) + ? themedBtnCls + ' border-primary' + : 'btn-primary' + } +} \ No newline at end of file diff --git a/src/util/pipes/menuIconPluginBtnCls.pipe.ts b/src/util/pipes/menuIconPluginBtnCls.pipe.ts new file mode 100644 index 000000000..94df7495c --- /dev/null +++ b/src/util/pipes/menuIconPluginBtnCls.pipe.ts @@ -0,0 +1,15 @@ +import { PipeTransform, Pipe } from "@angular/core"; + +@Pipe({ + name: 'menuIconPluginBtnClsPipe' +}) + +export class MenuIconPluginBtnClsPipe implements PipeTransform{ + public transform([launchedSet, minimisedSet, themedBtnCls], pluginName){ + return `${launchedSet.has(pluginName) + ? minimisedSet.has(pluginName) + ? themedBtnCls + ' border-primary' + : 'btn-primary' + : themedBtnCls}` + } +} \ No newline at end of file -- GitLab