src/app/app.component.ts
This is the main angular component that all the other components branch off from. It is in charge of the header and drawer components who have many sub-components.
OnInit
changeDetection | ChangeDetectionStrategy.OnPush |
selector | ccf-root |
styleUrls | ./app.component.scss |
templateUrl | ./app.component.html |
Properties |
|
Methods |
Accessors |
constructor(el: ElementRef
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
Defined in src/app/app.component.ts:141
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
Creates an instance of app component.
Parameters :
|
asMutable | ||||||
asMutable(value: Immutable
|
||||||
Defined in src/app/app.component.ts:350
|
||||||
Type parameters :
|
||||||
Parameters :
Returns :
T
|
closeiFrameViewer |
closeiFrameViewer()
|
Defined in src/app/app.component.ts:330
|
Function to easily close the iFrame viewer.
Returns :
void
|
createSelectionLabel | ||||||
createSelectionLabel(ontologySelection: OntologySelection[])
|
||||||
Defined in src/app/app.component.ts:289
|
||||||
Creates selection label for the results-browser to display based on an array of selected ontology nodes.
Parameters :
Returns :
string
|
isItemSelected | ||||||
isItemSelected(item: string)
|
||||||
Defined in src/app/app.component.ts:342
|
||||||
Parameters :
Returns :
any
|
ontologySelected | ||||||||||||
ontologySelected(ontologySelection: OntologySelection[] | undefined, type: "anatomical-structures" | "cell-type" | "biomarkers")
|
||||||||||||
Defined in src/app/app.component.ts:252
|
||||||||||||
Captures changes in the ontologySelection and uses them to update the results-browser label and the filter object in the data store.
Parameters :
Returns :
void
|
openiFrameViewer | ||||||||
openiFrameViewer(url: string)
|
||||||||
Defined in src/app/app.component.ts:315
|
||||||||
Opens the iframe viewer with an url
Parameters :
Returns :
void
|
reset | ||||||||||||||||
reset(left: DrawerComponent, right: DrawerComponent, filterbox: FiltersPopoverComponent)
|
||||||||||||||||
Defined in src/app/app.component.ts:223
|
||||||||||||||||
Resets the drawers and filter components to their default state.
Parameters :
Returns :
void
|
resetView |
resetView()
|
Defined in src/app/app.component.ts:232
|
Returns :
void
|
toggleScheme |
toggleScheme()
|
Defined in src/app/app.component.ts:242
|
Toggles scheme between light and dark mode
Returns :
void
|
toggleSelection | ||||||
toggleSelection(value: string[])
|
||||||
Defined in src/app/app.component.ts:346
|
||||||
Parameters :
Returns :
void
|
acceptableViewerDomains |
Type : string[]
|
Default value : environment.acceptableViewerDomains || []
|
Defined in src/app/app.component.ts:107
|
Acceptable viewer domains (others will open in new window) |
Readonly baseHref$ |
Default value : this.globalConfig.getOption('baseHref')
|
Defined in src/app/app.component.ts:141
|
biomarkerSelectionLabel |
Type : string
|
Default value : 'biomarker'
|
Defined in src/app/app.component.ts:88
|
Readonly biomarkersTreeModel$ |
Type : Observable<OntologyTreeModel>
|
Decorators :
@Select(DataStateSelectors.biomarkersTreeModel)
|
Defined in src/app/app.component.ts:67
|
Readonly biomarkerTerms$ |
Type : Observable<string[]>
|
Defined in src/app/app.component.ts:130
|
bodyUI |
Type : BodyUiComponent
|
Decorators :
@ViewChild('bodyUI', {static: false})
|
Defined in src/app/app.component.ts:58
|
cellTypeSelectionLabel |
Type : string
|
Default value : 'cell'
|
Defined in src/app/app.component.ts:86
|
Readonly cellTypeTerms$ |
Type : Observable<string[]>
|
Defined in src/app/app.component.ts:129
|
Readonly cellTypeTreeModel$ |
Type : Observable<OntologyTreeModel>
|
Decorators :
@Select(DataStateSelectors.cellTypesTreeModel)
|
Defined in src/app/app.component.ts:61
|
Readonly filter$ |
Default value : this.globalConfig.getOption('filter')
|
Defined in src/app/app.component.ts:139
|
Readonly header$ |
Default value : this.globalConfig.getOption('header')
|
Defined in src/app/app.component.ts:135
|
Readonly homeUrl$ |
Default value : this.globalConfig.getOption('homeUrl')
|
Defined in src/app/app.component.ts:136
|
Readonly loadingMessage$ |
Default value : this.data.state$.pipe(map((x) => x?.statusMessage))
|
Defined in src/app/app.component.ts:126
|
Readonly loginDisabled$ |
Default value : this.globalConfig.getOption('loginDisabled')
|
Defined in src/app/app.component.ts:138
|
Readonly logoTooltip$ |
Default value : this.globalConfig.getOption('logoTooltip')
|
Defined in src/app/app.component.ts:137
|
menuOptions |
Type : string[]
|
Default value : ['AS', 'CT', 'B']
|
Defined in src/app/app.component.ts:78
|
ontologySelectionLabel |
Type : string
|
Default value : 'body'
|
Defined in src/app/app.component.ts:84
|
Used to keep track of the ontology label to be passed down to the results-browser component. |
Readonly ontologyTerms$ |
Type : Observable<string[]>
|
Defined in src/app/app.component.ts:128
|
Readonly ontologyTreeModel$ |
Type : Observable<OntologyTreeModel>
|
Decorators :
@Select(DataStateSelectors.anatomicalStructuresTreeModel)
|
Defined in src/app/app.component.ts:64
|
organListVisible |
Default value : true
|
Defined in src/app/app.component.ts:97
|
Whether or not organ carousel is open |
Readonly removeSpatialSearch |
Default value : actionAsFn(RemoveSearch)
|
Decorators :
@Dispatch()
|
Defined in src/app/app.component.ts:76
|
Readonly selectableSearches$ |
Type : Observable<SpatialSearchFilterItem>
|
Decorators :
@Select(SpatialSearchFilterSelectors.items)
|
Defined in src/app/app.component.ts:70
|
Readonly selectedOrgans$ |
Default value : this.globalConfig.getOption('selectedOrgans')
|
Defined in src/app/app.component.ts:140
|
selectedtoggleOptions |
Type : string[]
|
Default value : []
|
Defined in src/app/app.component.ts:92
|
selectionLabel |
Type : string
|
Default value : 'body | cell | biomarker'
|
Defined in src/app/app.component.ts:90
|
Readonly setSelectedSearches |
Default value : actionAsFn(SetSelectedSearches)
|
Decorators :
@Dispatch()
|
Defined in src/app/app.component.ts:73
|
Readonly spinnerActive$ |
Default value : this.data.state$.pipe(map((state) => state?.status !== 'Ready'))
|
Defined in src/app/app.component.ts:124
|
Emits true whenever the overlay spinner should activate. |
Readonly theme$ |
Default value : this.globalConfig.getOption('theme')
|
Defined in src/app/app.component.ts:132
|
Readonly themeMode$ |
Default value : new ReplaySubject<'light' | 'dark'>(1)
|
Defined in src/app/app.component.ts:133
|
tooltips |
Type : string[]
|
Default value : ['Anatomical Structures', 'Cell Types', 'Biomarkers']
|
Defined in src/app/app.component.ts:79
|
url |
Type : string
|
Default value : ''
|
Defined in src/app/app.component.ts:102
|
Emitted url object from the results browser item |
viewerOpen |
Default value : false
|
Defined in src/app/app.component.ts:113
|
Variable to keep track of whether the viewer is open or not |
isLightTheme |
getisLightTheme()
|
Defined in src/app/app.component.ts:115
|
isFirefox |
getisFirefox()
|
Defined in src/app/app.component.ts:119
|
loggedIn |
getloggedIn()
|
Defined in src/app/app.component.ts:337
|
Gets login token
Returns :
boolean
|
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Injector,
OnInit,
ViewChild,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Dispatch } from '@ngxs-labs/dispatch-decorator';
import { Select } from '@ngxs/store';
import { CCFDatabaseOptions, Filter, OntologyTreeModel } from 'ccf-database';
import { BodyUiComponent, DataSourceService, GlobalConfigState, OrganInfo, TrackingPopupComponent } from 'ccf-shared';
import { ConsentService } from 'ccf-shared/analytics';
import { Observable, ReplaySubject, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { Immutable } from '@angular-ru/common/typings';
import { environment } from '../environments/environment';
import { OntologySelection } from './core/models/ontology-selection';
import { AppRootOverlayContainer } from './core/services/app-root-overlay/app-root-overlay.service';
import { ThemingService } from './core/services/theming/theming.service';
import { actionAsFn } from './core/store/action-as-fn';
import { DataStateSelectors } from './core/store/data/data.selectors';
import { DataState } from './core/store/data/data.state';
import { ListResultsState } from './core/store/list-results/list-results.state';
import { SceneState } from './core/store/scene/scene.state';
import { RemoveSearch, SetSelectedSearches } from './core/store/spatial-search-filter/spatial-search-filter.actions';
import { SpatialSearchFilterSelectors } from './core/store/spatial-search-filter/spatial-search-filter.selectors';
import { SpatialSearchFilterItem } from './core/store/spatial-search-filter/spatial-search-filter.state';
import { FiltersPopoverComponent } from './modules/filters/filters-popover/filters-popover.component';
import { DrawerComponent } from './shared/components/drawer/drawer/drawer.component';
interface AppOptions extends CCFDatabaseOptions {
theme?: string;
header?: boolean;
homeUrl?: string;
logoTooltip?: string;
selectedOrgans?: string[];
loginEnabled?: boolean;
baseHref?: string;
filter?: Partial<Filter>;
loginDisabled?: boolean;
}
/**
* This is the main angular component that all the other components branch off from.
* It is in charge of the header and drawer components who have many sub-components.
*/
@Component({
selector: 'ccf-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent implements OnInit {
@ViewChild('bodyUI', { static: false }) bodyUI!: BodyUiComponent;
@Select(DataStateSelectors.cellTypesTreeModel)
readonly cellTypeTreeModel$!: Observable<OntologyTreeModel>;
@Select(DataStateSelectors.anatomicalStructuresTreeModel)
readonly ontologyTreeModel$!: Observable<OntologyTreeModel>;
@Select(DataStateSelectors.biomarkersTreeModel)
readonly biomarkersTreeModel$!: Observable<OntologyTreeModel>;
@Select(SpatialSearchFilterSelectors.items)
readonly selectableSearches$!: Observable<SpatialSearchFilterItem>;
@Dispatch()
readonly setSelectedSearches = actionAsFn(SetSelectedSearches);
@Dispatch()
readonly removeSpatialSearch = actionAsFn(RemoveSearch);
menuOptions: string[] = ['AS', 'CT', 'B'];
tooltips: string[] = ['Anatomical Structures', 'Cell Types', 'Biomarkers'];
/**
* Used to keep track of the ontology label to be passed down to the
* results-browser component.
*/
ontologySelectionLabel = 'body';
cellTypeSelectionLabel = 'cell';
biomarkerSelectionLabel = 'biomarker';
selectionLabel = 'body | cell | biomarker';
selectedtoggleOptions: string[] = [];
/**
* Whether or not organ carousel is open
*/
organListVisible = true;
/**
* Emitted url object from the results browser item
*/
url = '';
/**
* Acceptable viewer domains (others will open in new window)
*/
acceptableViewerDomains: string[] = environment.acceptableViewerDomains || [];
/**
* Variable to keep track of whether the viewer is open
* or not
*/
viewerOpen = false;
get isLightTheme(): boolean {
return this.theming.getTheme().endsWith('light');
}
get isFirefox(): boolean {
return navigator.userAgent.indexOf('Firefox') !== -1;
}
/** Emits true whenever the overlay spinner should activate. */
readonly spinnerActive$ = this.data.state$.pipe(map((state) => state?.status !== 'Ready'));
readonly loadingMessage$ = this.data.state$.pipe(map((x) => x?.statusMessage));
readonly ontologyTerms$: Observable<readonly string[]>;
readonly cellTypeTerms$: Observable<readonly string[]>;
readonly biomarkerTerms$: Observable<readonly string[]>;
readonly theme$ = this.globalConfig.getOption('theme');
readonly themeMode$ = new ReplaySubject<'light' | 'dark'>(1);
readonly header$ = this.globalConfig.getOption('header');
readonly homeUrl$ = this.globalConfig.getOption('homeUrl');
readonly logoTooltip$ = this.globalConfig.getOption('logoTooltip');
readonly loginDisabled$ = this.globalConfig.getOption('loginDisabled');
readonly filter$ = this.globalConfig.getOption('filter');
readonly selectedOrgans$ = this.globalConfig.getOption('selectedOrgans');
readonly baseHref$ = this.globalConfig.getOption('baseHref');
/**
* Creates an instance of app component.
*
* @param data The data state.
*/
constructor(
el: ElementRef<HTMLElement>,
injector: Injector,
readonly data: DataState,
readonly theming: ThemingService,
readonly scene: SceneState,
readonly listResultsState: ListResultsState,
readonly consentService: ConsentService,
readonly snackbar: MatSnackBar,
overlay: AppRootOverlayContainer,
readonly dataSource: DataSourceService,
private readonly globalConfig: GlobalConfigState<AppOptions>,
cdr: ChangeDetectorRef,
) {
theming.initialize(el, injector);
overlay.setRootElement(el);
data.tissueBlockData$.subscribe();
data.aggregateData$.subscribe();
data.ontologyTermOccurencesData$.subscribe();
data.cellTypeTermOccurencesData$.subscribe();
data.biomarkerTermOccurencesData$.subscribe();
data.sceneData$.subscribe();
data.filter$.subscribe();
data.technologyFilterData$.subscribe();
data.providerFilterData$.subscribe();
this.ontologyTerms$ = data.filter$.pipe(map((x) => x?.ontologyTerms));
this.cellTypeTerms$ = data.filter$.pipe(map((x) => x?.cellTypeTerms));
this.biomarkerTerms$ = data.filter$.pipe(map((x) => x?.biomarkerTerms));
this.filter$.subscribe((filter = {}) => data.updateFilter(filter));
this.baseHref$.subscribe((ref) => this.globalConfig.patchState({ baseHref: ref ?? '' }));
combineLatest([scene.referenceOrgans$, this.selectedOrgans$]).subscribe(([refOrgans, selected]) => {
scene.setSelectedReferenceOrgansWithDefaults(refOrgans as OrganInfo[], selected ?? []);
});
combineLatest([this.theme$, this.themeMode$]).subscribe(([theme, mode]) => {
this.theming.setTheme(`${theme}-theme-${mode}`);
cdr.markForCheck();
});
this.selectedtoggleOptions = this.menuOptions;
}
ngOnInit(): void {
const snackBar = this.snackbar.openFromComponent(TrackingPopupComponent, {
data: {
preClose: () => {
snackBar.dismiss();
},
},
duration: this.consentService.consent === 'not-set' ? Infinity : 3000,
});
if (window.matchMedia) {
// Sets initial theme according to user theme preference
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
this.themeMode$.next('dark');
} else {
this.themeMode$.next('light');
}
// Listens for changes in user theme preference
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
this.themeMode$.next(e.matches ? 'dark' : 'light');
});
} else {
this.themeMode$.next('light');
}
}
/**
* Resets the drawers and filter components to their default state.
*
* @param left The left drawer component gets passed in so we can call it's methods to control it's state
* @param right The right drawer component gets passed in so we can call it's methods to control it's state
* @param filterbox The filter's popover component gets passed in so we can control it's popover's state
*/
reset(left: DrawerComponent, right: DrawerComponent, filterbox: FiltersPopoverComponent): void {
left.open();
left.closeExpanded();
right.open();
right.closeExpanded();
filterbox.removeBox();
this.resetView();
}
resetView(): void {
this.bodyUI.target = [0, 0, 0];
this.bodyUI.rotation = 0;
this.bodyUI.rotationX = 0;
this.bodyUI.bounds = { x: 2.2, y: 2, z: 0.4 };
}
/**
* Toggles scheme between light and dark mode
*/
toggleScheme(): void {
this.themeMode$.next(this.isLightTheme ? 'dark' : 'light');
}
/**
* Captures changes in the ontologySelection and uses them to update the results-browser label
* and the filter object in the data store.
*
* @param ontologySelection the list of currently selected organ nodes
*/
ontologySelected(
ontologySelection: OntologySelection[] | undefined,
type: 'anatomical-structures' | 'cell-type' | 'biomarkers',
): void {
if (ontologySelection) {
if (type === 'anatomical-structures') {
this.data.updateFilter({ ontologyTerms: ontologySelection.map((selection) => selection.id) });
this.ontologySelectionLabel = this.createSelectionLabel(ontologySelection);
} else if (type === 'cell-type') {
this.data.updateFilter({ cellTypeTerms: ontologySelection.map((selection) => selection.id) });
this.cellTypeSelectionLabel = this.createSelectionLabel(ontologySelection);
} else if (type === 'biomarkers') {
this.data.updateFilter({ biomarkerTerms: ontologySelection.map((selection) => selection.id) });
this.biomarkerSelectionLabel = this.createSelectionLabel(ontologySelection);
}
this.selectionLabel = [
this.ontologySelectionLabel || 'body',
this.cellTypeSelectionLabel || 'cell',
this.biomarkerSelectionLabel || 'biomarker',
].join(' | ');
if (ontologySelection[0] && ontologySelection[0].label === 'body') {
this.resetView();
}
return;
}
this.data.updateFilter({ ontologyTerms: [], cellTypeTerms: [], biomarkerTerms: [] });
this.ontologySelectionLabel = '';
this.cellTypeSelectionLabel = '';
}
/**
* Creates selection label for the results-browser to display based on an
* array of selected ontology nodes.
*/
createSelectionLabel(ontologySelection: OntologySelection[]): string {
if (ontologySelection.length === 0) {
return '';
}
if (ontologySelection.length === 1) {
return ontologySelection[0].label;
}
let selectionString = '';
ontologySelection.forEach((selection, index) => {
selectionString += selection.label;
// Don't add a comma if it's the last item in the array.
if (index < ontologySelection.length - 1) {
selectionString += ', ';
}
});
return selectionString;
}
/**
* Opens the iframe viewer with an url
*
* @param url The url
*/
openiFrameViewer(url: string): void {
const isWhitelisted = this.acceptableViewerDomains.some((domain) => url?.startsWith(domain));
if (isWhitelisted) {
this.url = url;
this.viewerOpen = !!url;
} else {
// Open link in new tab
window.open(url, '_blank');
this.closeiFrameViewer();
}
}
/**
* Function to easily close the iFrame viewer.
*/
closeiFrameViewer(): void {
this.viewerOpen = false;
}
/**
* Gets login token
*/
get loggedIn(): boolean {
const token = this.globalConfig.snapshot.hubmapToken ?? '';
return token.length > 0;
}
isItemSelected(item: string) {
return this.selectedtoggleOptions.includes(item);
}
toggleSelection(value: string[]) {
this.selectedtoggleOptions = value;
}
asMutable<T>(value: Immutable<T>): T {
return value as T;
}
}
<div class="ccf-app mat-app-background">
<ccf-spinner-overlay [text]="(loadingMessage$ | async) ?? ''" [active]="(spinnerActive$ | async) ?? false">
</ccf-spinner-overlay>
<ccf-header
[class.hide]="(header$ | async) === false"
[logoTooltip]="(logoTooltip$ | async) ?? ''"
[homeUrl]="(homeUrl$ | async) ?? ''"
[loggedIn]="loggedIn"
[loginDisabled]="(loginDisabled$ | async) ?? false"
[baseRef]="(baseHref$ | async) ?? ''"
*ngIf="(spinnerActive$ | async) === false"
>
</ccf-header>
<ccf-drawer-container class="main-drawers" [class.header-hidden]="(header$ | async) === false">
<ccf-drawer class="left-drawer" opened #left (stateChange)="filterbox.removeBox()">
<div class="left-drawer-container">
<div class="filter-data">
<ccf-filters-popover
[filters]="$any(data.filter$ | async)"
[drawerExpanded]="right.expanded"
(filtersChange)="data.updateFilter($event)"
[technologyFilters]="(data.technologyFilterData$ | async) ?? []"
[providerFilters]="(data.providerFilterData$ | async) ?? []"
[spatialSearchFilters]="$any(selectableSearches$ | async)"
(spatialSearchSelected)="setSelectedSearches($event)"
(spatialSearchRemoved)="removeSpatialSearch($event)"
#filterbox
>
</ccf-filters-popover>
<div class="filter-text">
<div class="sex filter-tag">
Sex: <strong>{{ (data.filter$ | async)?.sex }}</strong>
</div>
<div class="age filter-tag">
Age:
<strong>
{{ $any(data.filter$ | async)?.ageRange[0] }}-{{ $any(data.filter$ | async)?.ageRange[1] }}
</strong>
</div>
<div class="bmi filter-tag">
BMI:
<strong>
{{ $any(data.filter$ | async)?.bmiRange[0] }}-{{ $any(data.filter$ | async)?.bmiRange[1] }}
</strong>
</div>
</div>
</div>
<ccf-button-toggle
class="button-toggle-group"
[menuOptions]="menuOptions"
[enableTooltip]="true"
[tooltips]="tooltips"
[selectedItems]="selectedtoggleOptions"
(selectionChange)="toggleSelection($event)"
></ccf-button-toggle>
<div class="ontologies">
<ccf-ontology-selection
class="ontology-selection"
[class.firefox]="isFirefox"
*ngIf="isItemSelected('AS')"
[showtoggle]="false"
[treeModel]="(ontologyTreeModel$ | async)!"
[termData]="(data.ontologyTermsFullData$ | async) ?? {}"
[occurenceData]="(data.ontologyTermOccurencesData$ | async) ?? {}"
placeholderText="Search anatomical structures..."
(ontologySelection)="ontologySelected($any($event), 'anatomical-structures')"
[header]="(header$ | async) ?? false"
>
</ccf-ontology-selection>
<ccf-ontology-selection
class="cell-type-selection"
[class.firefox]="isFirefox"
*ngIf="isItemSelected('CT')"
[showtoggle]="false"
[treeModel]="(cellTypeTreeModel$ | async)!"
[termData]="(data.cellTypeTermsFullData$ | async) ?? {}"
[occurenceData]="(data.cellTypeTermOccurencesData$ | async) ?? {}"
placeholderText="Search cell types..."
(ontologySelection)="ontologySelected($any($event), 'cell-type')"
[header]="(header$ | async) ?? false"
>
</ccf-ontology-selection>
<ccf-ontology-selection
class="biomarker-selection"
[class.firefox]="isFirefox"
*ngIf="isItemSelected('B')"
[showtoggle]="true"
[treeModel]="(biomarkersTreeModel$ | async)!"
[termData]="(data.biomarkerTermsFullData$ | async) ?? {}"
[occurenceData]="(data.biomarkerTermOccurencesData$ | async) ?? {}"
placeholderText="Search Biomarkers..."
(ontologySelection)="ontologySelected($any($event), 'biomarkers')"
[header]="(header$ | async) ?? false"
>
</ccf-ontology-selection>
<div class="no-selection-notice" *ngIf="selectedtoggleOptions.length === 0">
No anatomical structures, cell types, or biomarkers selected. Use the above AS, CT, and B buttons to view
the registered listings.
</div>
</div>
<ccf-drawer-toggle-button></ccf-drawer-toggle-button>
</div>
</ccf-drawer>
<ccf-drawer class="right-drawer" position="end" opened #right (stateChange)="filterbox.removeBox()">
<ccf-viewer class="portal-view" [class.opened]="viewerOpen" [url]="url" (closed)="viewerOpen = false">
</ccf-viewer>
<div class="drawer-icons">
<div class="drawer-icons-left">
<button
class="button"
(click)="filterbox.removeBox(); right.toggleExpanded()"
[matTooltip]="right.expanded ? 'Exit Fullscreen' : 'Enter Fullscreen'"
>
<mat-icon class="icon">{{ right.expanded ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
<button
class="scheme-toggle button"
(click)="toggleScheme()"
[matTooltip]="isLightTheme ? 'Enter Dark Mode' : 'Enter Light Mode'"
>
<mat-icon class="icon">{{ isLightTheme ? 'brightness_2' : 'brightness_5' }} </mat-icon>
</button>
<button class="button">
<mat-icon class="refresh icon" (click)="reset(left, right, filterbox)" matTooltip="Reset View"
>refresh
</mat-icon>
</button>
</div>
<ccf-info-button
videoID="YAHJqvD3Q_8"
infoTitle="HRA Exploration User Interface"
[documentationUrl]="(baseHref$ | async) + 'assets/docs/README.md'"
matTooltip="Open Info"
>
</ccf-info-button>
</div>
<ccf-results-browser
[listResults]="(listResultsState.listResults$ | async) ?? []"
[aggregateData]="(data.aggregateData$ | async) ?? []"
[resultLabel]="selectionLabel"
(listResultSelected)="listResultsState.selectListResult(asMutable($event))"
(listResultDeselected)="listResultsState.deselectListResult(asMutable($event))"
(linkClicked)="openiFrameViewer($event)"
[highlighted]="(listResultsState.highlightedNodeId$ | async) ?? ''"
(itemHovered)="listResultsState.highlightNode($event)"
(itemUnhovered)="listResultsState.unHighlightNode()"
[header]="(header$ | async) ?? false"
>
</ccf-results-browser>
<ccf-drawer-toggle-button></ccf-drawer-toggle-button>
</ccf-drawer>
<ccf-drawer-content [class.header-hidden]="(header$ | async) === false">
<div [class.closed]="!organListVisible" class="selector-drawer" [class.expanded]="selector.expanded">
<ccf-organ-selector
#selector
class="organ-selector"
[multiselect]="true"
[occurenceData]="(data.ontologyTermOccurencesData$ | async) ?? {}"
[organList]="$any(scene.referenceOrgans$ | async)"
(organsChanged)="scene.setSelectedReferenceOrgans($event)"
[selectedOrgans]="$any(scene.selectedReferenceOrgans$ | async)"
>
</ccf-organ-selector>
</div>
<div class="close-button-wrapper" [class.closed]="!organListVisible">
<div *ngIf="organListVisible" class="close-button" (click)="selector.expanded = !selector.expanded">
<mat-icon class="expand-collapse-icon" aria-hidden="false" aria-label="Expand carousel drawer">
{{ selector.expanded ? 'arrow_drop_up' : 'arrow_drop_down' }}
</mat-icon>
</div>
<div *ngIf="!selector.expanded" class="close-button" (click)="organListVisible = !organListVisible">
<mat-icon class="expand-collapse-icon" aria-hidden="false" aria-label="Close carousel drawer">
{{ organListVisible ? 'arrow_drop_up' : 'arrow_drop_down' }}
</mat-icon>
</div>
</div>
<ccf-run-spatial-search></ccf-run-spatial-search>
<ccf-body-ui
#bodyUI
class="stage-content"
[scene]="$any(scene.scene$ | async)"
(nodeClick)="scene.sceneNodeClicked($event)"
(nodeHoverStart)="scene.sceneNodeHovered($event)"
(nodeHoverStop)="scene.sceneNodeUnhover()"
[bounds]="{ x: 2.2, y: 2, z: 0.4 }"
[class.expanded-stage]="!organListVisible"
[class.selector-expanded]="selector.expanded"
>
</ccf-body-ui>
</ccf-drawer-content>
</ccf-drawer-container>
</div>
./app.component.scss
::ng-deep .cdk-overlay-container {
position: absolute;
font-size: 1.2rem;
}
:host {
display: block;
position: relative;
}
.ccf-app {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
font-size: 1rem;
position: relative;
text-align: left;
ccf-header {
z-index: 99;
&.hide {
display: none;
}
}
ccf-drawer-container {
height: calc(100% - 5rem);
width: 100%;
opacity: 1;
overflow: hidden;
transform: scale(1);
&.header-hidden {
height: 100%;
}
ccf-drawer {
width: 28.5rem;
div {
.button-toggle-group {
height: 4rem;
display: flex;
align-items: center;
}
}
.drawer-icons {
display: flex;
padding-left: 1.5rem;
height: 5rem;
padding-right: 1.5rem;
align-items: center;
justify-content: space-between;
.drawer-icons-left {
display: flex;
justify-content: flex-start;
align-items: center;
.button {
padding: 0;
border: none;
cursor: pointer;
outline: none;
border-radius: 0.25rem;
padding: 0.65rem;
transition: 0.6s;
}
}
}
}
ccf-drawer-content {
overflow: hidden;
border-radius: 0.5rem;
height: calc(100vh - 4rem);
.selector-drawer {
top: 0rem;
display: flex;
flex-direction: column;
position: relative;
transition: all 0.5s ease-in-out;
height: 5rem;
justify-content: flex-start;
&.expanded {
height: 15rem;
overflow-y: auto;
}
&.closed {
height: 1.5rem;
top: -5rem;
}
}
ccf-run-spatial-search {
position: relative;
float: right;
height: 0px;
z-index: 1;
top: 1.5rem;
right: 0.5rem;
}
.stage-content {
transition: all 0.5s ease-in-out;
border-bottom-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
.expanded-stage {
height: calc(100% - 3rem);
}
.selector-expanded {
height: calc(100% - 16.5rem);
}
&.header-hidden {
height: calc(100vh - 1rem);
}
}
.close-button-wrapper {
display: flex;
justify-content: center;
border-top-right-radius: 0.5rem;
border-top-left-radius: 0.5rem;
height: 0;
.close-button {
display: flex;
justify-content: center;
height: 1.0625rem;
width: 3rem;
align-self: center;
border-radius: 0rem 0rem 0.25rem 0.25rem;
cursor: pointer;
transition: 0.6s;
position: relative;
top: 0.5rem;
z-index: 1;
.mat-icon {
position: relative;
bottom: 0.2rem;
}
}
}
.left-drawer {
padding-left: 1.5rem;
padding-right: 1rem;
.left-drawer-container {
height: 100%;
.ontologies {
height: calc(100% - 9rem);
display: flex;
gap: 1.5rem;
flex-direction: column;
ccf-ontology-selection {
flex: 1 1;
overflow: auto;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 0.25rem;
}
&.firefox {
padding-right: 0.5rem;
}
&.biomarker-selection {
flex-basis: 30%;
}
}
.no-selection-notice {
font-weight: 400;
line-height: normal;
}
}
}
::ng-deep .cff-drawer-inner-container {
overflow: hidden;
}
.filter-data {
height: 5rem;
display: flex;
align-items: center;
.filter-text {
display: flex;
justify-content: space-between;
width: 75%;
margin-left: 1.5rem;
.filter-tag {
font-weight: 300;
strong {
font-weight: 600 !important;
}
}
}
}
}
}
.right-drawer {
.portal-view {
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 425%;
height: calc(100% + -1.5rem);
transform: translateX(-425%);
width: calc(100% - 28.5rem);
transition: transform 0.5s cubic-bezier(0.82, 0.085, 0.395, 0.895);
&.opened {
transform: translateX(0);
transition: width 0s 0.5s;
}
}
&:not(.ccf-drawer-opened) .portal-view {
width: 100vw;
&.opened {
transition: width 0s;
}
}
.shaded-toggle {
box-shadow: -1px 0 4px #212121;
}
::ng-deep .cff-drawer-inner-container {
overflow: hidden;
}
}
}
::-webkit-scrollbar {
width: 0.5rem;
}
::ng-deep .mdc-snackbar__surface {
box-shadow: none !important;
}