From d328fd9843957c0ca30200520861d643cd944e3c Mon Sep 17 00:00:00 2001 From: tmushayahama Date: Wed, 6 May 2020 00:07:32 -0700 Subject: [PATCH] added the popup https://github.com/geneontology/noctua-landing-page/issues/16 --- src/@noctua.editor/index.ts | 1 + .../inline-reference.service.ts | 145 ++++++++++++++++++ .../reference-dropdown-ref.ts | 10 ++ .../reference-dropdown.component.html | 35 +++++ .../reference-dropdown.component.scss | 32 ++++ .../reference-dropdown.component.ts | 125 +++++++++++++++ .../reference-dropdown.tokens.ts | 3 + src/@noctua.editor/models/editor-category.ts | 9 ++ src/@noctua.editor/noctua-editor.module.ts | 27 ++++ src/@noctua.editor/services/dialog.service.ts | 44 ++++++ .../search-filter.component.html | 4 + .../search-filter.component.scss | 10 ++ .../search-filter/search-filter.component.ts | 23 ++- src/@noctua.search/noctua-search.module.ts | 4 +- 14 files changed, 466 insertions(+), 6 deletions(-) create mode 100644 src/@noctua.editor/index.ts create mode 100644 src/@noctua.editor/inline-reference/inline-reference.service.ts create mode 100644 src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown-ref.ts create mode 100644 src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.html create mode 100644 src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.scss create mode 100644 src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.ts create mode 100644 src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.tokens.ts create mode 100644 src/@noctua.editor/models/editor-category.ts create mode 100644 src/@noctua.editor/noctua-editor.module.ts create mode 100644 src/@noctua.editor/services/dialog.service.ts diff --git a/src/@noctua.editor/index.ts b/src/@noctua.editor/index.ts new file mode 100644 index 0000000..7d7862e --- /dev/null +++ b/src/@noctua.editor/index.ts @@ -0,0 +1 @@ +export * from './noctua-editor.module'; diff --git a/src/@noctua.editor/inline-reference/inline-reference.service.ts b/src/@noctua.editor/inline-reference/inline-reference.service.ts new file mode 100644 index 0000000..68cb19a --- /dev/null +++ b/src/@noctua.editor/inline-reference/inline-reference.service.ts @@ -0,0 +1,145 @@ +import { Injectable, Inject, Injector, ElementRef, ComponentRef, ViewChild } from '@angular/core'; +import { + Overlay, + OverlayRef, + OverlayConfig, + OriginConnectionPosition, + OverlayConnectionPosition, + PositionStrategy +} from '@angular/cdk/overlay'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { ReferenceDropdownOverlayRef } from './reference-dropdown/reference-dropdown-ref'; +import { referenceDropdownData } from './reference-dropdown/reference-dropdown.tokens'; + +import { NoctuaReferenceDropdownComponent } from './reference-dropdown/reference-dropdown.component'; + +export interface SearchCriiteria { + gp: string; + url: string; +} + +export interface ReferenceDropdownDialogConfig { + panelClass?: string; + hasBackdrop?: boolean; + backdropClass?: string; + positionStrategy?: PositionStrategy; + width?: string; + data?: any; +} + +const DEFAULT_CONFIG: ReferenceDropdownDialogConfig = { + hasBackdrop: true, + backdropClass: 'dark-backdrop', + panelClass: 'tm-file-preview-dialog-panel', + // width: '600px', + data: null +}; + +@Injectable({ + providedIn: 'root' +}) +export class InlineReferenceService { + + constructor( + private injector: Injector, + private overlay: Overlay) { } + + + open(elementToConnectTo: ElementRef, config: ReferenceDropdownDialogConfig = {}) { + const dialogConfig = { ...DEFAULT_CONFIG, ...config }; + + dialogConfig['positionStrategy'] = this._getPosition(elementToConnectTo); + // dialogConfig['width'] = '420px'; + const originRect = elementToConnectTo.nativeElement; + const overlayRef = this.createOverlay(dialogConfig); + const dialogRef = new ReferenceDropdownOverlayRef(overlayRef); + const overlayComponent = this.attachDialogContainer(overlayRef, dialogConfig, dialogRef); + + overlayRef.backdropClick().subscribe(_ => dialogRef.close()); + + return dialogRef; + } + + close(data): void { + // this.overlayRef.dispose(); + } + + private createInjector(config: ReferenceDropdownDialogConfig, dialogRef: ReferenceDropdownOverlayRef): PortalInjector { + const injectionTokens = new WeakMap(); + + injectionTokens.set(ReferenceDropdownOverlayRef, dialogRef); + injectionTokens.set(referenceDropdownData, config.data); + + return new PortalInjector(this.injector, injectionTokens); + } + + private attachDialogContainer(overlayRef: OverlayRef, config: ReferenceDropdownDialogConfig, dialogRef: ReferenceDropdownOverlayRef) { + const injector = this.createInjector(config, dialogRef); + + const containerPortal = new ComponentPortal(NoctuaReferenceDropdownComponent, null, injector); + const containerRef: ComponentRef = overlayRef.attach(containerPortal); + + return containerRef.instance; + } + + private createOverlay(config: ReferenceDropdownDialogConfig) { + const overlayConfig = this.getOverlayConfig(config); + + return this.overlay.create(overlayConfig); + } + + private getOverlayConfig(config: ReferenceDropdownDialogConfig): OverlayConfig { + const overlayConfig = new OverlayConfig({ + hasBackdrop: config.hasBackdrop, + backdropClass: config.backdropClass, + width: config.width, + panelClass: config.panelClass, + scrollStrategy: this.overlay.scrollStrategies.block(), + positionStrategy: config.positionStrategy + }); + + return overlayConfig; + } + + private _getPosition(elementToConnectTo: ElementRef) { + const origin = { + topLeft: { originX: 'start', originY: 'top' } as OriginConnectionPosition, + topRight: { originX: 'end', originY: 'top' } as OriginConnectionPosition, + bottomLeft: { originX: 'start', originY: 'bottom' } as OriginConnectionPosition, + bottomRight: { originX: 'end', originY: 'bottom' } as OriginConnectionPosition, + topCenter: { originX: 'center', originY: 'top' } as OriginConnectionPosition, + bottomCenter: { originX: 'center', originY: 'bottom' } as OriginConnectionPosition + }; + + const overlay = { + topLeft: { overlayX: 'start', overlayY: 'top' } as OverlayConnectionPosition, + topRight: { overlayX: 'end', overlayY: 'top' } as OverlayConnectionPosition, + bottomLeft: { overlayX: 'start', overlayY: 'bottom' } as OverlayConnectionPosition, + bottomRight: { overlayX: 'end', overlayY: 'bottom' } as OverlayConnectionPosition, + topCenter: { overlayX: 'center', overlayY: 'top' } as OverlayConnectionPosition, + bottomCenter: { overlayX: 'center', overlayY: 'bottom' } as OverlayConnectionPosition + }; + + return this.overlay.position() + .flexibleConnectedTo(elementToConnectTo) + .withFlexibleDimensions(true) + .withPush(true) + .withPositions([{ + overlayX: 'end', + overlayY: 'top', + originX: 'end', + originY: 'bottom' + }]); + //.withOffsetY(1) + //.withDirection('ltr') + //.withFallbackPosition(origin.bottomRight, overlay.topRight) + //.withFallbackPosition(origin.topLeft, overlay.bottomLeft) + //.withFallbackPosition(origin.topRight, overlay.bottomRight) + // .withFallbackPosition(origin.topCenter, overlay.bottomCenter) + // .withFallbackPosition(origin.bottomCenter, overlay.topCenter) + } + + getLink() { + + } +} diff --git a/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown-ref.ts b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown-ref.ts new file mode 100644 index 0000000..64c87b5 --- /dev/null +++ b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown-ref.ts @@ -0,0 +1,10 @@ +import { OverlayRef } from '@angular/cdk/overlay'; + +export class ReferenceDropdownOverlayRef { + + constructor(private overlayRef: OverlayRef) { } + + close(): void { + this.overlayRef.dispose(); + } +} diff --git a/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.html b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.html new file mode 100644 index 0000000..442ad22 --- /dev/null +++ b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.html @@ -0,0 +1,35 @@ +
+
+ + + + {{evidenceDB.label}} + + + + + + + + +
+
+ +
+
diff --git a/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.scss b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.scss new file mode 100644 index 0000000..35901fe --- /dev/null +++ b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.scss @@ -0,0 +1,32 @@ +@import "src/@noctua/scss/noctua"; +@import "src/@noctua.common/scss/noctua.common"; + +:host { + padding-top: 8px; + width: 100%; + @include deep-width(400px); + @include mat-elevation(4); + + background-color: #fbf9de; + + .noc-edit-field { + @include deep-width(300px); + } + + .noc-article-info { + padding: 12px 5px; + + .noc-article-title { + font-size: 12px; + max-height: 60px; + overflow: hidden; + } + + .noc-article-author { + max-height: 60px; + overflow: hidden; + } + + .noc-article-date {} + } +} diff --git a/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.ts b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.ts new file mode 100644 index 0000000..774ddfe --- /dev/null +++ b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.component.ts @@ -0,0 +1,125 @@ +import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'; + +import { + NoctuaFormConfigService, + NoctuaAnnotonFormService, + AnnotonError, + noctuaFormConfig, + Article, + NoctuaLookupService +} from 'noctua-form-base'; + +import { referenceDropdownData } from './reference-dropdown.tokens'; +import { ReferenceDropdownOverlayRef } from './reference-dropdown-ref'; +//import { NoctuaFormDialogService } from 'app/main/apps/noctua-form'; +import { SparqlService } from '@noctua.sparql/services/sparql/sparql.service'; + +@Component({ + selector: 'noc-reference-dropdown', + templateUrl: './reference-dropdown.component.html', + styleUrls: ['./reference-dropdown.component.scss'] +}) + +export class NoctuaReferenceDropdownComponent implements OnInit, OnDestroy { + evidenceDBForm: FormGroup; + formControl: FormControl; + article: Article; + + private _unsubscribeAll: Subject; + + constructor(public dialogRef: ReferenceDropdownOverlayRef, + @Inject(referenceDropdownData) public data: any, + private noctuaLookupService: NoctuaLookupService, + // private noctuaFormDialogService: NoctuaFormDialogService, + public noctuaFormConfigService: NoctuaFormConfigService, + public noctuaAnnotonFormService: NoctuaAnnotonFormService, + ) { + this._unsubscribeAll = new Subject(); + this.formControl = data.formControl; + } + + ngOnInit(): void { + this.evidenceDBForm = this._createEvidenceDBForm(); + this._onValueChange(); + } + + clearValues() { + + } + + save() { + const self = this; + const db = this.evidenceDBForm.value.db; + const accession = this.evidenceDBForm.value.accession; + const errors = []; + let canSave = true; + + if (accession.trim() === '') { + const error = new AnnotonError('error', 1, `${db.name} accession is required`); + errors.push(error); + // self.noctuaFormDialogService.openAnnotonErrorsDialog(errors); + canSave = false; + } + + if (canSave) { + this.formControl.setValue(db.name + ':' + accession.trim()); + this.close(); + } + } + + cancelEvidenceDb() { + this.evidenceDBForm.controls['accession'].setValue(''); + } + + private _createEvidenceDBForm() { + return new FormGroup({ + db: new FormControl(this.noctuaFormConfigService.evidenceDBs.selected), + accession: new FormControl('', + [ + Validators.required, + ]) + }); + } + + private _onValueChange() { + const self = this; + + self.evidenceDBForm.valueChanges.pipe( + takeUntil(this._unsubscribeAll), + distinctUntilChanged(), + debounceTime(400) + ).subscribe(data => { + self.article = null; + self._updateArticle(data); + }); + } + + close() { + this.dialogRef.close(); + } + + private _updateArticle(value) { + const self = this; + + if (value.db.name === noctuaFormConfig.evidenceDB.options.pmid.name && value.accession) { + const pmid = value.accession.trim(); + + if (pmid === '') { + return; + } + this.noctuaLookupService.getPubmedInfo(pmid).pipe( + takeUntil(this._unsubscribeAll)) + .subscribe((article: Article) => { + self.article = article; + }); + } + } + + ngOnDestroy(): void { + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.tokens.ts b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.tokens.ts new file mode 100644 index 0000000..2bf1b77 --- /dev/null +++ b/src/@noctua.editor/inline-reference/reference-dropdown/reference-dropdown.tokens.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const referenceDropdownData = new InjectionToken('referenceDropdownData'); \ No newline at end of file diff --git a/src/@noctua.editor/models/editor-category.ts b/src/@noctua.editor/models/editor-category.ts new file mode 100644 index 0000000..cd6d270 --- /dev/null +++ b/src/@noctua.editor/models/editor-category.ts @@ -0,0 +1,9 @@ +export enum EditorCategory { + relationship = 'relationship', + term = 'term', + evidence = 'evidence', + reference = 'reference', + with = 'with', + evidenceAll = 'evidenceAll', + all = 'all' +} diff --git a/src/@noctua.editor/noctua-editor.module.ts b/src/@noctua.editor/noctua-editor.module.ts new file mode 100644 index 0000000..b7dcf85 --- /dev/null +++ b/src/@noctua.editor/noctua-editor.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { NoctuaSharedModule } from '@noctua/shared.module'; +import { NoctuaReferenceDropdownComponent } from './inline-reference/reference-dropdown/reference-dropdown.component'; + +@NgModule({ + declarations: [ + NoctuaReferenceDropdownComponent, + ], + imports: [ + CommonModule, + RouterModule, + FormsModule, + ReactiveFormsModule, + NoctuaSharedModule + ], + exports: [ + NoctuaReferenceDropdownComponent, + ], + entryComponents: [ + NoctuaReferenceDropdownComponent, + ] +}) +export class NoctuaEditorModule { +} diff --git a/src/@noctua.editor/services/dialog.service.ts b/src/@noctua.editor/services/dialog.service.ts new file mode 100644 index 0000000..a4d45bf --- /dev/null +++ b/src/@noctua.editor/services/dialog.service.ts @@ -0,0 +1,44 @@ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import 'rxjs/add/operator/map'; +import { NoctuaConfirmDialogComponent } from '@noctua/components/confirm-dialog/confirm-dialog.component'; + +@Injectable({ + providedIn: 'root' +}) +export class NoctuaEditorDialogService { + + dialogRef: any; + + constructor(private httpClient: HttpClient, + private snackBar: MatSnackBar, + private _matDialog: MatDialog) { + } + + openSuccessfulSaveToast(message: string, action: string) { + this.snackBar.open(message, action, { + duration: 10000, + verticalPosition: 'top' + }); + } + + openConfirmDialog(searchCriteria, success): void { + this.dialogRef = this._matDialog.open(NoctuaConfirmDialogComponent, { + panelClass: 'noc-search-database-dialog', + data: { + searchCriteria: searchCriteria + }, + width: '600px', + }); + this.dialogRef.afterClosed() + .subscribe(response => { + if (response) { + success(response); + } + }); + } + +} diff --git a/src/@noctua.search/components/search-filter/search-filter.component.html b/src/@noctua.search/components/search-filter/search-filter.component.html index d084613..670150d 100644 --- a/src/@noctua.search/components/search-filter/search-filter.component.html +++ b/src/@noctua.search/components/search-filter/search-filter.component.html @@ -103,6 +103,10 @@ [matChipInputFor]="pmidChipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="false" (matChipInputTokenEnd)="add($event, noctuaSearchService.filterType.pmids)"> + diff --git a/src/@noctua.search/components/search-filter/search-filter.component.scss b/src/@noctua.search/components/search-filter/search-filter.component.scss index 2417e6e..dd5a25d 100644 --- a/src/@noctua.search/components/search-filter/search-filter.component.scss +++ b/src/@noctua.search/components/search-filter/search-filter.component.scss @@ -16,6 +16,16 @@ // white-space: nowrap; } + .noc-evidence-db-trigger { + @include deep-height(20px); + @include deep-width(20px); + line-height: 20px; + + mat-icon { + @include noc-icon-size(18px); + } + } + .noc-filter-form { padding-bottom: 200px; diff --git a/src/@noctua.search/components/search-filter/search-filter.component.ts b/src/@noctua.search/components/search-filter/search-filter.component.ts index a7275d3..1e6cb7c 100644 --- a/src/@noctua.search/components/search-filter/search-filter.component.ts +++ b/src/@noctua.search/components/search-filter/search-filter.component.ts @@ -16,6 +16,7 @@ import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/materia import * as _moment from 'moment'; // tslint:disable-next-line:no-duplicate-imports import { default as _rollupMoment } from 'moment'; +import { InlineReferenceService } from '@noctua.editor/inline-reference/inline-reference.service'; const moment = _rollupMoment || _moment; @@ -66,11 +67,13 @@ export class SearchFilterComponent implements OnInit, OnDestroy { private unsubscribeAll: Subject; - constructor(public noctuaUserService: NoctuaUserService, - public noctuaSearchMenuService: NoctuaSearchMenuService, - public noctuaFormConfigService: NoctuaFormConfigService, - private noctuaLookupService: NoctuaLookupService, - public noctuaSearchService: NoctuaSearchService) { + constructor + (public noctuaUserService: NoctuaUserService, + private inlineReferenceService: InlineReferenceService, + public noctuaSearchMenuService: NoctuaSearchMenuService, + public noctuaFormConfigService: NoctuaFormConfigService, + private noctuaLookupService: NoctuaLookupService, + public noctuaSearchService: NoctuaSearchService) { this.gpNode = EntityDefinition.generateBaseTerm([EntityDefinition.GoMolecularEntity]); this.termNode = EntityDefinition.generateBaseTerm([ @@ -185,6 +188,16 @@ export class SearchFilterComponent implements OnInit, OnDestroy { this.filterForm.controls[filterType].setValue(''); } + openAddReference(event, name: string) { + + const data = { + formControl: this.filterForm.controls[name] as FormControl, + }; + this.inlineReferenceService.open(event.target, { data }); + + } + + downloadFilter() { this.noctuaSearchService.downloadSearchConfig(); } diff --git a/src/@noctua.search/noctua-search.module.ts b/src/@noctua.search/noctua-search.module.ts index 589aa59..69d0fd7 100644 --- a/src/@noctua.search/noctua-search.module.ts +++ b/src/@noctua.search/noctua-search.module.ts @@ -11,6 +11,7 @@ import { SearchFormComponent } from './components/search-form/search-form.compon import { SearchFilterComponent } from './components/search-filter/search-filter.component'; import { SearchRelationComponent } from './components/search-relation/search-relation.component'; import { SearchHistoryComponent } from './components/search-history/search-history.component'; +import { NoctuaEditorModule } from '@noctua.editor'; @NgModule({ declarations: [ @@ -27,7 +28,8 @@ import { SearchHistoryComponent } from './components/search-history/search-histo RouterModule, FormsModule, ReactiveFormsModule, - NoctuaSharedModule + NoctuaSharedModule, + NoctuaEditorModule ], exports: [ SearchFilterComponent,