diff --git a/src/main.module.ts b/src/main.module.ts index e92737b4ba2fd5594a9a8f1247f0b2fac8015e03..074035bbab9c77552bf863f17d8ba547f40b723c 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -51,6 +51,7 @@ import 'hammerjs' import 'src/res/css/extra_styles.css' import 'src/res/css/version.css' import 'src/theme.scss' +import { ShareModule } from './share'; @NgModule({ imports : [ @@ -66,6 +67,7 @@ import 'src/theme.scss' WidgetModule, PluginModule, LoggingModule, + ShareModule, EffectsModule.forRoot([ DataBrowserUseEffect, diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index 824eba66e26ff98beda53275f4014165c0bb7f55..fe7d4d4e320b24ee0149726a15a29781d31d646b 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -389,9 +389,14 @@ markdown-dom pre code overflow-y:hidden!important; } +.muted-7 +{ + opacity: 0.7!important; +} + .muted { - opacity : 0.5!important; + opacity: 0.5!important; } .card diff --git a/src/share/clipboardCopy.directive.spec.ts b/src/share/clipboardCopy.directive.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d4e27e30f0eccdb12312c17f89ead462eb975245 --- /dev/null +++ b/src/share/clipboardCopy.directive.spec.ts @@ -0,0 +1,91 @@ +import { Component } from "@angular/core"; +import { async, TestBed } from "@angular/core/testing"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { ClipboardCopy } from "./clipboardCopy.directive"; +import { By } from "@angular/platform-browser"; +import { Clipboard } from "@angular/cdk/clipboard"; + +@Component({ + template: '' +}) + +class TestCmp{} + +const dummyClipBoard = { + copy: val => { + + } +} + +describe('clipboardCopy.directive.ts', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + AngularMaterialModule + ], + declarations: [ + ClipboardCopy, + TestCmp + ], + providers:[ + { + provide: Clipboard, + useValue: dummyClipBoard + } + ] + }) + + // not yet compiled! + })) + + it('should be able to test directive', async(() => { + TestBed.overrideComponent(TestCmp, { + set: { + template: '<div iav-clipboard-copy></div>' + } + }).compileComponents() + + const fixture = TestBed.createComponent(TestCmp) + const directive = fixture.debugElement.query( By.directive( ClipboardCopy ) ) + expect(directive).not.toBeNull() + })) + + it('if copytarget is not defined, window.location.href will be copied', async(() => { + + const testPath = 'http://TESTHOST/TESTPATH' + const spy = spyOn(ClipboardCopy, 'getWindowLocationHref').and.returnValue(testPath) + const clipboardSpy = spyOn(dummyClipBoard, 'copy') + TestBed.overrideComponent(TestCmp, { + set: { + template: '<div iav-clipboard-copy></div>' + } + }).compileComponents() + + const fixture = TestBed.createComponent(TestCmp) + const directive = fixture.debugElement.query( By.directive( ClipboardCopy ) ) + + directive.triggerEventHandler('click', null) + expect(spy).toHaveBeenCalled() + expect(clipboardSpy).toHaveBeenCalledWith(testPath) + })) + + it('if copytarget is provided, copytarget will be copied', () => { + + const copyTarget = 'hello world' + const spy = spyOn(ClipboardCopy, 'getWindowLocationHref').and.returnValue('testPath') + const clipboardSpy = spyOn(dummyClipBoard, 'copy') + TestBed.overrideComponent(TestCmp, { + set: { + template: `<div iav-clipboard-copy="${copyTarget}"></div>` + } + }).compileComponents() + + const fixture = TestBed.createComponent(TestCmp) + fixture.detectChanges() + const directive = fixture.debugElement.query( By.directive( ClipboardCopy ) ) + + directive.triggerEventHandler('click', null) + expect(spy).not.toHaveBeenCalled() + expect(clipboardSpy).toHaveBeenCalledWith(copyTarget) + }) +}) diff --git a/src/share/clipboardCopy.directive.ts b/src/share/clipboardCopy.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..a02cbdd221c50059a7fa142b52f09e1aafd8d0be --- /dev/null +++ b/src/share/clipboardCopy.directive.ts @@ -0,0 +1,33 @@ +import { Directive, HostListener, Input } from "@angular/core"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { Clipboard } from '@angular/cdk/clipboard' + +@Directive({ + selector: '[iav-clipboard-copy]', + exportAs: 'iavClipboardCopy' +}) + +export class ClipboardCopy{ + + @Input('iav-clipboard-copy') + copyTarget: string + + constructor( + private snackBar: MatSnackBar, + private clipBoard: Clipboard, + ){ + + } + + static getWindowLocationHref() { + return window.location.href + } + + @HostListener('click') + onClick(){ + this.clipBoard.copy(this.copyTarget || ClipboardCopy.getWindowLocationHref()) + this.snackBar.open('Copied to clipboard!', null, { + duration: 1000 + }) + } +} diff --git a/src/share/index.ts b/src/share/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..76068712eb51fa49cac65e17b82e75e3c3e009e4 --- /dev/null +++ b/src/share/index.ts @@ -0,0 +1 @@ +export { ShareModule } from './share.module' diff --git a/src/share/share.module.ts b/src/share/share.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ef45901e51039c188a604ae49274bb328fb6e67 --- /dev/null +++ b/src/share/share.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; +import { ClipboardCopy } from "./clipboardCopy.directive"; + +@NgModule({ + imports: [ + AngularMaterialModule + ], + declarations: [ + ClipboardCopy + ], + exports: [ + ClipboardCopy + ] +}) + +export class ShareModule{} diff --git a/src/share/shareSaneLink.directive.ts b/src/share/shareSaneLink.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index c48e166f73673beee11964086742e44ac0330b19..0ddb3de683a2764633c34c412e2262322a7968cf 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -18,7 +18,6 @@ import { take, takeUntil, tap, - throttleTime, withLatestFrom } from "rxjs/operators"; import { LoggingService } from "src/logging"; diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css index 5023c2001888243b2a8e68e9838fb926d59adc2d..9737b28faebcf1d5c24d7e3ff7be187d708031c5 100644 --- a/src/ui/nehubaContainer/nehubaContainer.style.css +++ b/src/ui/nehubaContainer/nehubaContainer.style.css @@ -202,3 +202,12 @@ maximise-panel-button:hover, opacity: 1.0 !important; pointer-events: all !important; } + +.status-card-container +{ + position:absolute; + left:1em; + bottom:1em; + width : 20em; + pointer-events: all; +} diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index ac443889b7e292002686db6732e0373aae6746d2..175c62c6372131e4e3dfe8541e9c1897d1181d2f 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -26,12 +26,14 @@ <layout-floating-container *ngIf="viewerLoaded"> <!-- StatusCard container--> - <ui-status-card - *ngIf="!(useMobileUI$ | async)" - [selectedTemplateName]="selectedTemplate && selectedTemplate.name" - [isMobile]="useMobileUI$ | async" - [nehubaViewer]="nehubaViewer"> - </ui-status-card> + <div class="status-card-container muted-7"> + <ui-status-card + *ngIf="!(useMobileUI$ | async)" + [selectedTemplateName]="selectedTemplate && selectedTemplate.name" + [isMobile]="useMobileUI$ | async" + [nehubaViewer]="nehubaViewer"> + </ui-status-card> + </div> </layout-floating-container> <div id="scratch-pad"> diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index f86c403ce72df183cc704a283c059967bee5dca7..877f35d636aa30f74da7aa39e04215bec96e06b7 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, Renderer2 } from "@angular/core"; -import { fromEvent, Subscription, ReplaySubject } from 'rxjs' +import { fromEvent, Subscription, ReplaySubject, Subject, BehaviorSubject } from 'rxjs' import { pipeFromArray } from "rxjs/internal/util/pipe"; import { debounceTime, filter, map, scan } from "rxjs/operators"; import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; @@ -46,6 +46,11 @@ const scanFn: (acc: LayerLabelIndex[], curr: LayerLabelIndex) => LayerLabelIndex export class NehubaViewerUnit implements OnInit, OnDestroy { + public viewerPosInVoxel$ = new BehaviorSubject(null) + public viewerPosInReal$ = new BehaviorSubject(null) + public mousePosInVoxel$ = new BehaviorSubject(null) + public mousePosInReal$ = new BehaviorSubject(null) + private exportNehuba: any private viewer: any @@ -793,16 +798,28 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this._s4$ = this.nehubaViewer.navigationState.position.inRealSpace .filter(v => typeof v !== 'undefined' && v !== null) - .subscribe(v => this.navPosReal = v) + .subscribe(v => { + this.navPosReal = Array.from(v) as [number, number, number] + this.viewerPosInReal$.next(Array.from(v)) + }) this._s5$ = this.nehubaViewer.navigationState.position.inVoxels .filter(v => typeof v !== 'undefined' && v !== null) - .subscribe(v => this.navPosVoxel = v) + .subscribe(v => { + this.navPosVoxel = Array.from(v) as [number, number, number] + this.viewerPosInVoxel$.next(Array.from(v)) + }) this._s6$ = this.nehubaViewer.mousePosition.inRealSpace .filter(v => typeof v !== 'undefined' && v !== null) - .subscribe(v => (this.mousePosReal = v)) + .subscribe(v => { + this.mousePosReal = Array.from(v) as [number, number, number] + this.mousePosInReal$.next(Array.from(v)) + }) this._s7$ = this.nehubaViewer.mousePosition.inVoxels .filter(v => typeof v !== 'undefined' && v !== null) - .subscribe(v => (this.mousePosVoxel = v)) + .subscribe(v => { + this.mousePosVoxel = Array.from(v) as [number, number, number] + this.mousePosInVoxel$.next(Array.from(v)) + }) } private loadNewParcellation() { diff --git a/src/ui/nehubaContainer/statusCard/statusCard.component.ts b/src/ui/nehubaContainer/statusCard/statusCard.component.ts index 5dd2b1116da0cd8e70ff95add9b1db9cbdf82f74..0adce7701a6b3f819d211055dac29b1b8e36f718 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.component.ts +++ b/src/ui/nehubaContainer/statusCard/statusCard.component.ts @@ -1,31 +1,35 @@ -import { Component, Input, OnInit } from "@angular/core"; +import { Component, Input, OnInit, OnChanges, TemplateRef } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { LoggingService } from "src/logging"; import { CHANGE_NAVIGATION, IavRootStoreInterface, ViewerStateInterface } from "src/services/stateStore.service"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; -import { Observable, Subscription } from "rxjs"; -import { distinctUntilChanged, shareReplay } from "rxjs/operators"; +import { Observable, Subscription, of, combineLatest, BehaviorSubject } from "rxjs"; +import { distinctUntilChanged, shareReplay, map, filter, startWith } from "rxjs/operators"; +import { MatBottomSheet } from "@angular/material/bottom-sheet"; @Component({ selector : 'ui-status-card', templateUrl : './statusCard.template.html', styleUrls : ['./statusCard.style.css'], }) -export class StatusCardComponent implements OnInit{ +export class StatusCardComponent implements OnInit, OnChanges{ @Input() public selectedTemplateName: string; @Input() public isMobile: boolean; @Input() public nehubaViewer: NehubaViewerUnit; - @Input() public onHoverSegmentName: string; private selectedTemplateRoot$: Observable<any> private selectedTemplateRoot: any private subscriptions: Subscription[] = [] + public navVal$: Observable<string> + public mouseVal$: Observable<string> + constructor( private store: Store<ViewerStateInterface>, private log: LoggingService, private store$: Store<IavRootStoreInterface>, + private bottomSheet: MatBottomSheet ) { const viewerState$ = this.store$.pipe( select('viewerState'), @@ -45,22 +49,45 @@ export class StatusCardComponent implements OnInit{ ) } - public statusPanelRealSpace: boolean = true + ngOnChanges() { + if (!this.nehubaViewer) { + this.navVal$ = of(`neubaViewer is undefined`) + this.mouseVal$ = of(`neubaViewer is undefined`) + return + } + this.navVal$ = combineLatest( + this.statusPanelRealSpace$, + this.nehubaViewer.viewerPosInReal$.pipe( + filter(v => !!v) + ), + this.nehubaViewer.viewerPosInVoxel$.pipe( + filter(v => !!v) + ) + ).pipe( + map(([realFlag, real, voxel]) => realFlag + ? real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ') + : voxel.map(v => v.toFixed(3)).join(', ') ), + startWith(`nehubaViewer initialising`) + ) - get mouseCoord(): string { - return this.nehubaViewer ? - this.statusPanelRealSpace ? - this.nehubaViewer.mousePosReal ? - Array.from(this.nehubaViewer.mousePosReal.map(n => isNaN(n) ? 0 : n / 1e6)) - .map(n => n.toFixed(3) + 'mm').join(' , ') : - '0mm , 0mm , 0mm (mousePosReal not yet defined)' : - this.nehubaViewer.mousePosVoxel ? - this.nehubaViewer.mousePosVoxel.join(' , ') : - '0 , 0 , 0 (mousePosVoxel not yet defined)' : - '0 , 0 , 0 (nehubaViewer not defined)' + this.mouseVal$ = combineLatest( + this.statusPanelRealSpace$, + this.nehubaViewer.mousePosInReal$.pipe( + filter(v => !!v) + ), + this.nehubaViewer.mousePosInVoxel$.pipe( + filter(v => !!v) + ) + ).pipe( + map(([realFlag, real, voxel]) => realFlag + ? real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ') + : voxel.map(v => v.toFixed(3)).join(', s')), + startWith(``) + ) } - public editingNavState: boolean = false + public statusPanelRealSpace$ = new BehaviorSubject(true) + public statusPanelRealSpace: boolean = true public textNavigateTo(string: string) { if (string.split(/[\s|,]+/).length >= 3 && string.split(/[\s|,]+/).slice(0, 3).every(entry => !isNaN(Number(entry.replace(/mm/, ''))))) { @@ -74,13 +101,8 @@ export class StatusCardComponent implements OnInit{ } } - public navigationValue() { - return this.nehubaViewer ? - this.statusPanelRealSpace ? - Array.from(this.nehubaViewer.navPosReal.map(n => isNaN(n) ? 0 : n / 1e6)) - .map(n => n.toFixed(3) + 'mm').join(' , ') : - Array.from(this.nehubaViewer.navPosVoxel.map(n => isNaN(n) ? 0 : n)).join(' , ') : - `[0,0,0] (neubaViewer is undefined)` + showBottomSheet(tmpl: TemplateRef<any>){ + this.bottomSheet.open(tmpl) } /** diff --git a/src/ui/nehubaContainer/statusCard/statusCard.style.css b/src/ui/nehubaContainer/statusCard/statusCard.style.css index 868a99e2faf458301d36b3d9dae1a1763055eced..0a5c8a77bcbc9e710b534584989f56cd7364c304 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.style.css +++ b/src/ui/nehubaContainer/statusCard/statusCard.style.css @@ -1,29 +1,3 @@ -div[statusCard] -{ - position:absolute; - left:1em; - bottom:1em; - width : 20em; - pointer-events: all; -} - -input -{ - background-color:rgba(240, 240, 240, 0.8); -} - -:host-context([darktheme=false]) div[statusCard] -{ - background-color:rgba(230,230,230,0.8); -} - -:host-context([darktheme=true]) div[statusCard], -:host-context([darktheme=true]) input -{ - background-color:rgba(20,20,20,0.8); - color : rgba(250,250,250,0.8); -} - div[linksContainer] { display:inline-block; @@ -40,13 +14,7 @@ small[onHoverSegment] margin-left:2em; } - -:host-context([darktheme=false]) input[navigateInput] -{ - border:rgba(0,0,0,0.2) solid 1px; -} - -:host-context([darktheme=true]) input[navigateInput] +.share-btn { - border:rgba(255,255,255,0.2) solid 1px; + right: 0; } \ No newline at end of file diff --git a/src/ui/nehubaContainer/statusCard/statusCard.template.html b/src/ui/nehubaContainer/statusCard/statusCard.template.html index abb1d0047ffcecb30427ded8f9621b874c4d9854..3af8b0b15405798b77e17054b8f4a58a74b6a7b6 100644 --- a/src/ui/nehubaContainer/statusCard/statusCard.template.html +++ b/src/ui/nehubaContainer/statusCard/statusCard.template.html @@ -1,56 +1,109 @@ -<div statusCard> - <div linksContainer> - <span> - reset: - </span> - <a - href="#" - (click)="$event.preventDefault();resetNavigation({position:true})"> - position - </a> - - <a - href="#" - (click)="$event.preventDefault();resetNavigation({rotation:true})"> - rotation - </a> - - <a - href="#" - (click)="$event.preventDefault();resetNavigation({zoom:true})"> - zoom - </a> - - <br /> - <span> - space: - </span> - - <a href="#" (click)="$event.preventDefault();statusPanelRealSpace=!statusPanelRealSpace"> - {{statusPanelRealSpace ? 'physical' : 'voxel'}} - </a> - - </div> - - <br /> - <div textContainer> - <small>Navigation: </small> - <input - (keydown.enter)="textNavigateTo(navigateInput.value)" - (keydown.tab)="textNavigateTo(navigateInput.value)" - [ngModel]="navigationValue()" - spellcheck="false" - #navigateInput - navigateInput/> +<mat-card> + <mat-card-content> + + <!-- reset --> + <div class="d-flex"> + <span class="flex-grow-0 d-flex align-items-center"> + Reset + </span> + + <div class="flex-grow-1"></div> + + <button + mat-icon-button + (click)="resetNavigation({position:true})" + matTooltip="Reset position"> + <i class="fas fa-crosshairs"></i> + </button> + + <button + mat-icon-button + (click)="resetNavigation({rotation:true})" + matTooltip="Reset rotation"> + <i class="fas fa-compass"></i> + </button> + + <button + mat-icon-button + (click)="resetNavigation({zoom:true})" + matTooltip="Reset zoom"> + <i class="fas fa-search-plus"></i> + </button> + </div> + + <!-- space --> + <div class="d-flex"> + <span class="d-flex align-items-center"> + Voxel space + </span> + + <mat-slide-toggle + [checked]="statusPanelRealSpace$ | async" + (change)="statusPanelRealSpace$.next($event.checked)" + class="pl-2 pr-2"> + </mat-slide-toggle> + + <span class="d-flex align-items center"> + Physical space + </span> + </div> + + <!-- coord --> + <div class="d-flex"> + + <mat-form-field class="flex-grow-1"> + <mat-label> + {{ (statusPanelRealSpace$ | async) ? 'Physical Coord' : 'Voxel Coord' }} + </mat-label> + <input type="text" + matInput + (keydown.enter)="textNavigateTo(navInput.value)" + (keydown.tab)="textNavigateTo(navInput.value)" + [value]="navVal$ | async" + #navInput="matInput"> - <br /> - <small *ngIf="!isMobile">Cursor: </small> - <small *ngIf="!isMobile"> - {{ mouseCoord }} - </small> - <br *ngIf="!isMobile" /> - <small onHoverSegment> - {{ onHoverSegmentName }} - </small> - </div> -</div> + </mat-form-field> + + <div class="w-0 position-relative"> + <button + (click)="showBottomSheet(shareTmpl)" + mat-icon-button + class="position-absolute share-btn"> + <i class="fas fa-share-square"></i> + </button> + </div> + </div> + + <!-- cursor pos --> + <mat-form-field *ngIf="!isMobile" + class="w-100"> + <mat-label> + Cursor Pos + </mat-label> + <input type="text" + matInput + [readonly]="true" + [value]="mouseVal$ | async"> + </mat-form-field> + + </mat-card-content> +</mat-card> + +<!-- share template --> +<ng-template #shareTmpl> + <h4 class="mat-h4"> + Share via + </h4> + <mat-nav-list> + <mat-list-item iav-clipboard-copy> + <mat-icon + class="mr-4" + fontSet="fas" + fontIcon="fa-link"> + </mat-icon> + <span> + Copy link to this view + </span> + </mat-list-item> + </mat-nav-list> +</ng-template> \ No newline at end of file diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts index 67a76f2bfeeb97f148019a880bde7e75b3f54c4d..0455c50b110e7ed3dc425acf169a8f7815e3c20f 100644 --- a/src/ui/sharedModules/angularMaterial.module.ts +++ b/src/ui/sharedModules/angularMaterial.module.ts @@ -25,7 +25,9 @@ import {MatGridListModule} from "@angular/material/grid-list"; import {MatIconModule} from "@angular/material/icon"; import {MatExpansionModule} from "@angular/material/expansion"; import {MatMenuModule} from "@angular/material/menu"; +import { MatToolbarModule } from '@angular/material/toolbar' +import { ClipboardModule } from '@angular/cdk/clipboard' const defaultDialogOption: MatDialogConfig = new MatDialogConfig() @@ -56,6 +58,8 @@ const defaultDialogOption: MatDialogConfig = new MatDialogConfig() MatIconModule, MatMenuModule, ExperimentalScrollingModule, + MatToolbarModule, + ClipboardModule, ], exports: [ MatButtonModule, @@ -83,6 +87,8 @@ const defaultDialogOption: MatDialogConfig = new MatDialogConfig() MatIconModule, MatMenuModule, ExperimentalScrollingModule, + MatToolbarModule, + ClipboardModule, ], providers: [{ provide: MAT_DIALOG_DEFAULT_OPTIONS, diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 35f3c30a8d3bfd7431cf2f1b9bd170294c559ea3..066031db546e4e53b73137de8696482492c11170 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -81,6 +81,7 @@ import { SimpleRegionComponent } from "./parcellationRegion/regionSimple/regionS import { LandmarkUIComponent } from "./landmarkUI/landmarkUI.component"; import { NehubaModule } from "./nehubaContainer/nehuba.module"; import { LayerDetailComponent } from "./layerbrowser/layerDetail/layerDetail.component"; +import { ShareModule } from "src/share"; @NgModule({ imports : [ @@ -93,7 +94,8 @@ import { LayerDetailComponent } from "./layerbrowser/layerDetail/layerDetail.com UtilModule, ScrollingModule, AngularMaterialModule, - NehubaModule + NehubaModule, + ShareModule, ], declarations : [ NehubaContainer,