Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion spa/src/app/_ngrx/app.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ import { FileState } from "../files/_ngrx/file.state";
import { HttpState } from "../http/_ngrx/http.state";
import { SystemState } from "../system/_ngrx/system.state";
import { TerraState } from "../files/_ngrx/terra/terra.state";
import { ModalState } from "../modal/_ngrx/modal.state";

export interface AppState extends AuthState, ConfigState, FileState, HttpState, SystemState, TerraState {}
export interface AppState extends AuthState, ConfigState, FileState, HttpState, ModalState, SystemState, TerraState {}
8 changes: 3 additions & 5 deletions spa/src/app/files/files.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { HttpClientModule } from "@angular/common/http";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterModule } from "@angular/router";

import { MatAutocompleteModule } from "@angular/material/autocomplete";
Expand Down Expand Up @@ -73,6 +72,7 @@ import { HCATableProjectsComponent } from "./hca-table-projects/hca-table-projec
import { HCATableSamplesComponent } from "./hca-table-samples/hca-table-samples.component";
import { HCATableSortComponent } from "./hca-table-sort/hca-table-sort.component";
import { HCATooltipComponent } from "./hca-tooltip/hca-tooltip.component";
import { ModalModule } from "../modal/modal.module";
import { ProjectService } from "./project/project.service";
import { ProjectDeprecatedComponent } from "./project-deprecated/project-deprecated.component";
import { ProjectDownloadMatrixModalContainerComponent } from "./project-download-matrix-modal-container/project-download-matrix-modal-container.component";
Expand All @@ -99,8 +99,8 @@ import { TableScroll } from "./table-scroll/table-scroll.component";

@NgModule({
imports: [
BrowserAnimationsModule,
FormsModule,
HttpClientModule,
MatAutocompleteModule,
MatButtonModule,
MatCardModule,
Expand All @@ -121,9 +121,7 @@ import { TableScroll } from "./table-scroll/table-scroll.component";
RouterModule.forChild(routes),

CcPipeModule,

HttpClientModule,

ModalModule,
SharedModule
],
declarations: [
Expand Down
32 changes: 17 additions & 15 deletions spa/src/app/files/hca-project/hca-project.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,23 @@ <h4 class="fontsize-m semi-bold">Project Downloads</h4>
</p>
<p class="fontsize-xs rhs" *ngIf="state.projectMatrixUrls && !state.projectMatrixUrls.isAnyProjectMatrixUrlAvailable()">No expression matrix available</p>
</div>
<div *ngFor="let species of state.projectMatrixUrls.listSpeciesWithMatrixUrls()"
class="species-matrix-url">
<p class="fontsize-xs semi-bold subhead lhs">
<hca-tooltip [tooltipClass]="'hca-tooltip narrow'"
[tooltipContent]="'Download expression matrix'"
[tooltipDisabled]="false"
[tooltipPosition]="'above'">{{species}}
</hca-tooltip>
</p>
<p class="fontsize-xs rhs downloads">
<span *ngFor="let matrixUrl of state.projectMatrixUrls.listMatrixUrlsBySpecies(species)"><a
[href]="matrixUrl.url"><img
src="assets/images/icon/hca-download-primary.png"/><span>{{matrixUrl.name}}</span></a></span>
</p>
</div>
<ng-container *ngIf="state.projectMatrixUrls">
<div *ngFor="let species of state.projectMatrixUrls.listSpeciesWithMatrixUrls()"
class="species-matrix-url">
<p class="fontsize-xs semi-bold subhead lhs">
<hca-tooltip [tooltipClass]="'hca-tooltip narrow'"
[tooltipContent]="'Download expression matrix'"
[tooltipDisabled]="false"
[tooltipPosition]="'above'">{{species}}
</hca-tooltip>
</p>
<p class="fontsize-xs rhs downloads">
<span *ngFor="let matrixUrl of state.projectMatrixUrls.listMatrixUrlsBySpecies(species)"><a
[href]="matrixUrl.url"><img
src="assets/images/icon/hca-download-primary.png"/><span>{{matrixUrl.name}}</span></a></span>
</p>
</div>
</ng-container>
</div>
<div class="project-integrations">
<h4 class="fontsize-m semi-bold">External Resources</h4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import { MatDialog } from "@angular/material";
import { ActivatedRoute } from "@angular/router";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { Store } from "@ngrx/store";

// App dependencies
import { ProjectDownloadMatrixModalComponent } from "../project-download-matrix-modal/project-download-matrix-modal.component";
import { AppState } from "../../_ngrx/app.state";


@Component({
Expand All @@ -34,11 +32,12 @@ export class ProjectDownloadMatrixModalContainerComponent implements OnDestroy {
/**
* @param {MatDialog} dialog
* @param {ActivatedRoute} route
* @param {Store<AppState>} store
*/
constructor(dialog: MatDialog, route: ActivatedRoute, private store: Store<AppState>) {
constructor(dialog: MatDialog, route: ActivatedRoute) {

route.params.pipe(takeUntil(this.ngDestroy$)).subscribe(params => {
route.params.pipe(
takeUntil(this.ngDestroy$)
).subscribe(params => {

dialog.open(ProjectDownloadMatrixModalComponent, {
autoFocus: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<modal-layout *ngIf="state$ | async as state"
[loaded]="state.loaded"
(closed)="onClosedClicked()">
(closed)="redirectToProjects()">
<ng-container title *ngIf="state.loaded">
Download Project Expression Matrices
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Component for displaying project prepared expression matrices downloads inside modal. Displayed
* Component for displaying project prepared expression matrices downloads inside modal. The modal closes automatically
* on NavigationStart event. The follow actions causes a redirect to the projects page (and therefore closes the modal):
*
* 1. Hitting escape
* 2. Clicking the close icon
* 3. Clicking the HCA logo
*/

// Core dependencies
import { Component, HostListener, Inject, OnDestroy, OnInit } from "@angular/core";
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material";
import { Router } from "@angular/router";
import { NavigationStart, Router, RouterEvent } from "@angular/router";
import { select, Store } from "@ngrx/store";
import { combineLatest, BehaviorSubject, Observable, Subject } from "rxjs";
import { filter, map, takeUntil } from "rxjs/operators";
Expand All @@ -23,6 +28,8 @@ import { ProjectDownloadMatrixModalState } from "./project-download-matrix-modal
import { FetchProjectRequestAction } from "../_ngrx/table/table.actions";
import { selectSelectedProject } from "../_ngrx/file.selectors";
import { Project } from "../shared/project.model";
import { ModalOpenedAction } from "../../modal/_ngrx/modal-opened.action";
import { ModalClosedAction } from "../../modal/_ngrx/modal-closed.action";

@Component({
selector: "project-download-matrix-modal",
Expand All @@ -47,32 +54,33 @@ export class ProjectDownloadMatrixModalComponent implements OnDestroy, OnInit {
private dialogRef: MatDialogRef<ProjectDownloadMatrixModalComponent>,
@Inject(MAT_DIALOG_DATA) private data: any,
private router: Router) {
}

/**
* Close dialog on key up of escape key.
*/
@HostListener("window:keyup.esc") onKeyUp() {

this.dialogRef.close();
this.redirectToProjects()
this.store.dispatch(new ModalOpenedAction());
}

/**
* Close modal and redirect to projects list.
* Redirect to projects list - called from template on click of close icon, or on keyup of escape key. The resulting
* navigation event causes the modal to close. See initCloseOnNavigation.
*/
public onClosedClicked(): void {
@HostListener("window:keyup.esc")
public redirectToProjects(): void {

this.dialogRef.close();
this.redirectToProjects();
this.router.navigateByUrl(`/${EntityName.PROJECTS}`, {replaceUrl: true});
}

/**
* Redirect to projects list.
* Close the modal on any navigation event.
*/
public redirectToProjects(): void {
private initCloseOnNavigation() {

this.router.navigateByUrl(`/${EntityName.PROJECTS}`, {replaceUrl: true});
this.router.events.pipe(
filter((event: RouterEvent) => event instanceof NavigationStart),
filter(() => !!this.dialogRef),
takeUntil(this.ngDestroy$)
).subscribe(() => {
this.store.dispatch(new ModalClosedAction());
this.dialogRef.close();
});
}

/**
Expand Down Expand Up @@ -115,11 +123,13 @@ export class ProjectDownloadMatrixModalComponent implements OnDestroy, OnInit {
}

/**
* Grab the prepared matrix URLs for the selected project. Also listen for close events (click on backdrop or escape
* key) and redirect to projects list.
* Grab the prepared matrix URLs for the selected project. Also listen for navigation events, in which case we must
* close the modal.
*/
public ngOnInit(): void {


this.initCloseOnNavigation();

const projectId = this.data.projectId;

// Request project details so we can display the project title
Expand Down
15 changes: 15 additions & 0 deletions spa/src/app/modal/_ngrx/modal-closed.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Action triggered when modal is closed.
*/

// Core dependencies
import { Action } from "@ngrx/store";

export class ModalClosedAction implements Action {
public static ACTION_TYPE = "MODAL.CLOSED";
public readonly type = ModalClosedAction.ACTION_TYPE;
constructor() {}
}
15 changes: 15 additions & 0 deletions spa/src/app/modal/_ngrx/modal-opened.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Action triggered when modal is opened.
*/

// Core dependencies
import { Action } from "@ngrx/store";

export class ModalOpenedAction implements Action {
public static ACTION_TYPE = "MODAL.OPENED";
public readonly type = ModalOpenedAction.ACTION_TYPE;
constructor() {}
}
36 changes: 36 additions & 0 deletions spa/src/app/modal/_ngrx/modal.reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Modal reducer, handles actions related to updating modal state.
*/

// Core dependencies
import { Action } from "@ngrx/store";

// App dependencies
import { ModalClosedAction } from "./modal-closed.action";
import { ModalOpenedAction } from "./modal-opened.action";
import { ModalState } from "./modal.state";

/**
* @param state {ModalState}
* @param action {Action}
* @returns {ModalState}
*/
export function reducer(state: ModalState = ModalState.getDefaultState(), action: Action): ModalState {

switch (action.type) {

// Handle case where modal was closed
case ModalClosedAction.ACTION_TYPE:
return state.closeModal();

// Handle case where modal was opened
case ModalOpenedAction.ACTION_TYPE:
return state.openModal();

default:
return state;
}
}
22 changes: 22 additions & 0 deletions spa/src/app/modal/_ngrx/modal.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Selectors for querying modal-related state from the store.
*/

// Core dependencies
import { createSelector, createFeatureSelector } from "@ngrx/store";

// App dependencies
import { ModalState } from "./modal.state";

/**
* Get the modal state.
*/
export const selectModal = createFeatureSelector<ModalState>("modal");

/**
* Returns true if modal is currently open, otherwise false.
*/
export const selectModalOpen = createSelector(selectModal, (state: ModalState) => state.open);
47 changes: 47 additions & 0 deletions spa/src/app/modal/_ngrx/modal.state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Modal-related state.
*/

export class ModalState {

public readonly open: boolean;

/**
* @param {boolean} opened
*/
constructor(open: boolean) {

this.open = open;
}

/**
* Create default modal state, where modal is not currently open.
*
* @returns {ModalState}
*/
public static getDefaultState() {

return new ModalState(false);
}

/**
* Handle close of modal.
*
* @returns {ModalState}
*/
public closeModal(): ModalState {
return new ModalState(false);
}

/**
* Handle open of modal.
*
* @returns {ModalState}
*/
public openModal(): ModalState {
return new ModalState(true);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<span class="close" (click)="onCloseClicked()"></span>
<div *ngIf="loaded" [@fadeIn]>
<span class="close" (click)="onCloseClicked()"></span>
<h2><ng-content select="[title]"></ng-content></h2>
<status-panel>
<hca-section-title>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

// Core dependencies
import { animate, style, transition, trigger } from "@angular/animations";
import { DOCUMENT } from "@angular/common";
import { Component, EventEmitter, Inject, Input, Output, Renderer2 } from "@angular/core";
import { Component, EventEmitter, Input, Output } from "@angular/core";

@Component({
selector: "modal-layout",
Expand All @@ -28,21 +27,11 @@ export class ModalLayoutComponent {
@Input() loaded: boolean; // True when subtitle and content project content are ready to be displayed
@Output() closed = new EventEmitter<boolean>();

/**
* @param {Document} document
* @param {Renderer2} renderer
*/
constructor(@Inject(DOCUMENT) private document: Document, private renderer: Renderer2) {

this.renderer.addClass(this.document.body, "modal-open");
}

/**
* Let parent component know modal is to be closed.
*/
public onCloseClicked(): void {

this.renderer.removeClass(this.document.body, "modal-open");
this.closed.emit(true);
}
}
Loading