diff --git a/deploy/app.js b/deploy/app.js index 454cb475b06ea437929fe59f6e75e504d639ac93..f741cd45ccec1e58fe2149fbcd8404374e3ae1cd 100644 --- a/deploy/app.js +++ b/deploy/app.js @@ -20,7 +20,7 @@ app.use((req, _, next) => { console.log({ type: 'visitorLog', method: 'main.bundle.js', - xForwardedFor: xForwardedFor.replace(/\ /g, '').split(',').map(hash), + xForwardedFor: xForwardedFor && xForwardedFor.replace(/\ /g, '').split(',').map(hash), ip: hash(ip) }) } diff --git a/deploy/catchError.js b/deploy/catchError.js index 38e1a000f5b78c53d5275bef01caf2ec9cbfb0db..a759a191bac20b02e7b6c854f993f37b1d34a6f7 100644 --- a/deploy/catchError.js +++ b/deploy/catchError.js @@ -1,11 +1,13 @@ -module.exports = ({code = 500, error = 'an error had occured', trace = 'undefined trace'}, req, res, next) => { +module.exports = (raw, req, res, next) => { /** * probably use more elaborate logging? */ + const { code, error, trace } = raw console.error('Catching error', { code, error, - trace + trace, + raw }) res.status(code).send() } \ No newline at end of file diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index d5b2c296496bf6c94cd3127c620dcc1812176f36..15ec031e726cfedd3c804778a102f68d662725c0 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -14,13 +14,22 @@ body /* required for fas tooltip directives */ overflow:hidden; } + +.touch-bottom.touch-left > .scale-bar-container +{ + bottom: 0; + right: 0; + left: auto; +} + div.scale-bar-container { text-align: center; background-color: rgba(0,0,0,.3); position: absolute; - left: 1em; - bottom: 1em; + margin: 1em; + bottom: 0; + left: 0; padding: 2px; font-weight: 700; pointer-events: none; @@ -84,9 +93,16 @@ div.scale-bar-container background-color:hsla(0,0%,80%,0.5); } +.touch-top.touch-left > label.perspective-panel-show-slice-views +{ + bottom: 0; + right: 0; +} + label.perspective-panel-show-slice-views { visibility: hidden; + position: absolute; } label.perspective-panel-show-slice-views:hover diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/ui/databrowserModule/databrowser.useEffect.ts index f9a57b51905c88e841669aa192463b0947d3c6be..b5aca56754c409e9598a8fb4ad30cd13bc1d055e 100644 --- a/src/ui/databrowserModule/databrowser.useEffect.ts +++ b/src/ui/databrowserModule/databrowser.useEffect.ts @@ -93,7 +93,7 @@ export class DataBrowserUseEffect implements OnDestroy{ * TODO emit proper error * possibly wipe corrupted local stoage here? */ - return null + return of(null) }) ) @@ -105,7 +105,7 @@ export class DataBrowserUseEffect implements OnDestroy{ from( this.kgSingleDatasetService.getInfoFromKg({ kgId })) .pipe(catchError(err => { console.log(`fetchInfoFromKg error`, err) - return null + return of(null) }))) ).pipe( filter(v => !!v), diff --git a/src/ui/layerbrowser/layerbrowser.component.ts b/src/ui/layerbrowser/layerbrowser.component.ts index 35178d86ea0c6322ccf0f799cc2eee64c7c701ae..6a99ffbaf392bc480aff27c810907c1a23df5ffb 100644 --- a/src/ui/layerbrowser/layerbrowser.component.ts +++ b/src/ui/layerbrowser/layerbrowser.component.ts @@ -3,7 +3,7 @@ import { NgLayerInterface } from "../../atlasViewer/atlasViewer.component"; import { Store, select } from "@ngrx/store"; import { ViewerStateInterface, isDefined, REMOVE_NG_LAYER, FORCE_SHOW_SEGMENT, safeFilter, getNgIds } from "../../services/stateStore.service"; import { Subscription, Observable, combineLatest } from "rxjs"; -import { filter, map, shareReplay, tap } from "rxjs/operators"; +import { filter, map, shareReplay, distinctUntilChanged } from "rxjs/operators"; import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; @Component({ @@ -84,7 +84,8 @@ export class LayerBrowser implements OnDestroy{ map(([baseNgLayerNames, loadedNgLayers]) => { const baseNameSet = new Set(baseNgLayerNames) return loadedNgLayers.filter(l => !baseNameSet.has(l.name)) - }) + }), + distinctUntilChanged() ) /** diff --git a/src/ui/layerbrowser/layerbrowser.style.css b/src/ui/layerbrowser/layerbrowser.style.css index 495211b5c91f0746e1c4d522c18a8d3dfdb3a835..b38ff0ea019b33d7e8797092569c7cc865408ea2 100644 --- a/src/ui/layerbrowser/layerbrowser.style.css +++ b/src/ui/layerbrowser/layerbrowser.style.css @@ -5,35 +5,7 @@ flex-direction: column-reverse; } -div[heading] -{ - padding: 0.2em 1em; - /* white-space: nowrap; */ -} -div[body] -{ - padding: 0.1em 1em; - background-color:rgba(0, 0, 0, 0.1); -} - -.layerContainer -{ - display: flex; - flex-direction: row; - align-items: center; -} - -.fas.blue -{ - color: #337ab7; -} - -.fas.red -{ - color: red; -} - .noLayerPlaceHolder { padding: 0.5em 1em; -} \ No newline at end of file +} diff --git a/src/ui/layerbrowser/layerbrowser.template.html b/src/ui/layerbrowser/layerbrowser.template.html index b5d5c8e45f37ff1e3ca3fe0de68e1edce3a14c1c..e60f92d8ef1a076550c71855747d9cd0e717a61e 100644 --- a/src/ui/layerbrowser/layerbrowser.template.html +++ b/src/ui/layerbrowser/layerbrowser.template.html @@ -1,3 +1,8 @@ +<!-- n.b. using mousedown for event trigger --> +<!-- Chrome & FF exhibit different behaviours when using click/mouseup as a event handler --> +<!-- in Chrome, it will complain that expression changed after change detection --> +<!-- in FF, the element changes, and focusout event is never fired properly --> + <ng-container *ngIf="nonBaseNgLayers$ | async as nonBaseNgLayers; else noLayerPlaceHolder"> <mat-list *ngIf="nonBaseNgLayers.length > 0; else noLayerPlaceHolder"> <mat-list-item *ngFor="let ngLayer of nonBaseNgLayers"> @@ -7,7 +12,7 @@ <button [matTooltipPosition]="matTooltipPosition" [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layer cannot be hidden' : 'toggle visibility'" - (click)="toggleVisibility(ngLayer)" + (mousedown)="toggleVisibility(ngLayer)" mat-icon-button [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers" [color]="ngLayer.visible ? 'primary' : null"> @@ -20,7 +25,7 @@ *ngIf="advancedMode" [matTooltipPosition]="matTooltipPosition" [matTooltip]="ngLayer.type === 'segmentation' ? segmentationTooltip() : 'only segmentation layer can hide/show segments'" - (click)="toggleForceShowSegment(ngLayer)" + (mousedown)="toggleForceShowSegment(ngLayer)" mat-icon-button> <i class="fas" @@ -33,7 +38,7 @@ <button color="warn" mat-icon-button - (click)="removeLayer(ngLayer)" + (mousedown)="removeLayer(ngLayer)" [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers" [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layers cannot be removed' : 'remove layer'"> <i [class]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'fas fa-lock muted' : 'fas fa-trash'"> diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html index 00b4065a9219b171ebc38c8df0dce6d503eaa460..9cae5deaa6047d1a1aab06773493bfe1981943c6 100644 --- a/src/ui/menuicons/menuicons.template.html +++ b/src/ui/menuicons/menuicons.template.html @@ -4,6 +4,57 @@ <!-- hide icons when templates has yet been selected --> <ng-template [ngIf]="selectedTemplate$ | async"> + <!-- selected regions --> + <sleight-of-hand + [doNotClose]="viewerStateController.focused"> + + <!-- shown prior to mouse over --> + <div sleight-of-hand-front> + <button + [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null" + [matBadgePosition]="badgetPosition" + matBadgeColor="accent" + mat-icon-button + color="primary"> + <i class="fas fa-brain"></i> + </button> + </div> + + <!-- shown upon mouseover --> + <div + sleight-of-hand-back + class="d-flex flex-row align-items-center soh-row"> + + <!-- place holder icon --> + <button + [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null" + [matBadgePosition]="badgetPosition" + matBadgeColor="accent" + mat-icon-button + color="primary"> + <i class="fas fa-brain"></i> + </button> + + <div class="position-relative"> + + <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light') + ' position-absolute card'"> + <viewer-state-controller #viewerStateController></viewer-state-controller> + </div> + + <!-- invisible icon to keep height of the otherwise unstable flex block --> + <div class="invisible pe-none"> + <i class="fas fa-brain"></i> + </div> + </div> + + <ng-template #noBrainRegionSelected> + <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'"> + Double click any brain region to select it. + </small> + </ng-template> + </div> + </sleight-of-hand> + <!-- layer browser --> <sleight-of-hand> <div sleight-of-hand-front> @@ -29,7 +80,7 @@ <i class="fas fa-layer-group"></i> </button> - <div class="position-relative"> + <div class="position-relative d-flex align-items-center"> <div [ngClass]="{'invisible pe-none': (layerBrowser.nonBaseNgLayers$ | async).length === 0}" class="position-absolute"> <mat-card> @@ -39,7 +90,7 @@ </div> <ng-container *ngIf="(layerBrowser.nonBaseNgLayers$ | async).length === 0" #noNonBaseNgLayerTemplate> - <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'"> + <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap position-absolute'"> No additional layers added </small> </ng-container> @@ -258,54 +309,4 @@ </sleight-of-hand> - <!-- selected regions --> - <sleight-of-hand - [doNotClose]="viewerStateController.focused"> - - <!-- shown prior to mouse over --> - <div sleight-of-hand-front> - <button - [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null" - [matBadgePosition]="badgetPosition" - matBadgeColor="accent" - mat-icon-button - color="primary"> - <i class="fas fa-brain"></i> - </button> - </div> - - <!-- shown upon mouseover --> - <div - sleight-of-hand-back - class="d-flex flex-row align-items-center soh-row"> - - <!-- place holder icon --> - <button - [matBadge]="(selectedRegions$ | async).length > 0 ? (selectedRegions$ | async).length : null" - [matBadgePosition]="badgetPosition" - matBadgeColor="accent" - mat-icon-button - color="primary"> - <i class="fas fa-brain"></i> - </button> - - <div class="position-relative"> - - <div [class]="((darktheme$ | async) ? 'bg-dark' : 'bg-light') + ' position-absolute card'"> - <viewer-state-controller #viewerStateController></viewer-state-controller> - </div> - - <!-- invisible icon to keep height of the otherwise unstable flex block --> - <div class="invisible pe-none"> - <i class="fas fa-brain"></i> - </div> - </div> - - <ng-template #noBrainRegionSelected> - <small [class]="((darktheme$ | async) ? 'bg-dark text-light' : 'bg-light text-dark') + ' muted pl-2 pr-2 p-1 text-nowrap'"> - Double click any brain region to select it. - </small> - </ng-template> - </div> - </sleight-of-hand> </ng-template> \ No newline at end of file diff --git a/src/ui/nehubaContainer/util.ts b/src/ui/nehubaContainer/util.ts index edd3a98f8fea9b48e40fe7cd16702f469a95ca93..d16c41ed10083b19ec7d49783e3db04e8a6cc1a3 100644 --- a/src/ui/nehubaContainer/util.ts +++ b/src/ui/nehubaContainer/util.ts @@ -18,10 +18,37 @@ const makeCol = (...els:HTMLElement[]) => { return container } -export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { - for (let panel of panels){ - panel.className = '' +const washPanels = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { + for (const panel of panels) { + if (panel) panel.className = `position-relative` } + return panels +} + +/** + * gives a clue of the approximate location of the panel, allowing position of checkboxes/scale bar to be placed in unobtrustive places + */ +const panelTouchSide = (panel: HTMLElement, { top, left, right, bottom }: any) => { + if (top) panel.classList.add(`touch-top`) + if (left) panel.classList.add(`touch-left`) + if (right) panel.classList.add(`touch-right`) + if (bottom) panel.classList.add(`touch-bottom`) + return panel +} + +const top = true +const left = true +const right = true +const bottom = true + +export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { + washPanels(panels) + + panelTouchSide(panels[0], { top, left, bottom }) + panelTouchSide(panels[1], { top, right }) + panelTouchSide(panels[2], { right }) + panelTouchSide(panels[3], { right, bottom }) + const majorContainer = makeCol(panels[0]) const minorContainer = makeCol(panels[1], panels[2], panels[3]) @@ -32,9 +59,13 @@ export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElem } export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { - for (let panel of panels){ - panel.className = '' - } + washPanels(panels) + + panelTouchSide(panels[0], { top, left, right }) + panelTouchSide(panels[1], { bottom, left }) + panelTouchSide(panels[2], { bottom }) + panelTouchSide(panels[3], { right, bottom }) + const majorContainer = makeRow(panels[0]) const minorContainer = makeRow(panels[1], panels[2], panels[3]) @@ -46,9 +77,13 @@ export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElemen export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { - for (let panel of panels){ - panel.className = '' - } + washPanels(panels) + + panelTouchSide(panels[0], { top, left }) + panelTouchSide(panels[1], { top, right }) + panelTouchSide(panels[2], { bottom, left }) + panelTouchSide(panels[3], { right, bottom }) + const majorContainer = makeRow(panels[0], panels[1]) const minorContainer = makeRow(panels[2], panels[3]) @@ -59,9 +94,10 @@ export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTML } export const getSinglePanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { - for (let panel of panels){ - panel.className = '' - } + washPanels(panels) + + panelTouchSide(panels[0], { top, left, bottom, right }) + const majorContainer = makeRow(panels[0]) const minorContainer = makeRow(panels[1], panels[2], panels[3])