diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1b20989..0618343 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -31,6 +31,7 @@ import { TabViewModule } from "primeng/tabview"; import { ScrollPanelModule } from "primeng/scrollpanel"; import { MultiSelectModule } from "primeng/multiselect"; import { InputNumberModule } from "primeng/inputnumber"; +import { InputSwitchModule } from "primeng/inputswitch"; import { SliderModule } from "primeng/slider"; import { LandingPageComponent } from "./components/landing-page/landing-page.component"; @@ -74,6 +75,7 @@ import { MainLayoutComponent } from './components/novostoic/main-layout/main-lay import { CenterLayoutComponent } from './components/novostoic/center-layout/center-layout.component'; import { MoleculeImageComponent } from './components/novostoic/molecule-image/molecule-image.component'; import { ChemicalInputComponent } from './components/novostoic/chemical-input/chemical-input.component'; +import { MoleculeInfoOverlayComponent } from './components/novostoic/molecule-info-overlay/molecule-info-overlay.component'; const initAppFn = (envService: EnvironmentService) => { return () => envService.loadEnvConfig("/assets/config/envvars.json"); @@ -103,6 +105,7 @@ const initAppFn = (envService: EnvironmentService) => { CenterLayoutComponent, MoleculeImageComponent, ChemicalInputComponent, + MoleculeInfoOverlayComponent, // FileDragNDropDirective, // ConfigurationComponent, // ResultsComponent, @@ -135,6 +138,7 @@ const initAppFn = (envService: EnvironmentService) => { TableModule, TabViewModule, InputTextModule, + InputSwitchModule, ListboxModule, OverlayPanelModule, SidebarModule, diff --git a/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.html b/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.html index e87e5a0..34b720e 100644 --- a/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.html +++ b/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.html @@ -46,11 +46,14 @@
>
- + > + No. Reactions - Gibbs Free Energy - Actions + Status + - + + {{ rowIndex + 1 }} {{ row.reaction }} - {{ row.gibbsEnergy | number:"1.0-2" }} ± {{ row.std | number:"1.1-1" }} kJ/mol -
- - + {{ row.gibbsEnergy > 0 ? '+' : ''}}{{ row.gibbsEnergy | number:"1.0-2" }} + ± {{ row.std | number:"1.1-1" }} kJ/mol + + +
+ +
Feasible
+
+
+ +
+ +
Infeasible
+
+
+ + + + + + + + + +
+ + + +
+ + {{ reactant.name }} + + + +
+
+ + + + +
+ + {{ product.name }} + + + +
+ +
diff --git a/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.ts b/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.ts index ae11107..4ebd9e9 100644 --- a/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.ts +++ b/src/app/components/novostoic/dg-predictor-result/dg-predictor-result.component.ts @@ -1,8 +1,7 @@ import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { BehaviorSubject, filter, map, of, skipUntil, switchMap, takeWhile, tap, timer } from "rxjs"; -import { JobType, JobStatus } from "~/app/api/mmli-backend/v1"; -import { ThermodynamicalFeasibilityResponse } from "~/app/models/dg-predictor"; +import { BehaviorSubject, combineLatest, filter, forkJoin, map, of, switchMap, take, tap } from "rxjs"; +import { JobType } from "~/app/api/mmli-backend/v1"; import { JobResult } from "~/app/models/job-result"; import { NovostoicService } from "~/app/services/novostoic.service"; @@ -26,7 +25,19 @@ export class DgPredictorResultComponent extends JobResult { }, ]; - response$ = this.jobResultResponse$; + response$ = this.jobResultResponse$.pipe( + map((data) => data.map((d: any, i: number) => ({ + ...d, + id: i, + reactants: Object.values(d.molecules) + .filter((val: any) => val['amount'] < 0) + .sort((a: any, b: any) => a['is_cofactor'] ? 1 : b['is_cofactor'] ? -1 : 0), + products: Object.values(d.molecules) + .filter((val: any) => val['amount'] > 0) + .sort((a: any, b: any) => a['is_cofactor'] ? -1 : b['is_cofactor'] ? 1 : 0), + }))), + tap(console.log) + ); constructor( private route: ActivatedRoute, diff --git a/src/app/components/novostoic/dg-predictor/dg-predictor.component.html b/src/app/components/novostoic/dg-predictor/dg-predictor.component.html index 7b5d87d..998121c 100644 --- a/src/app/components/novostoic/dg-predictor/dg-predictor.component.html +++ b/src/app/components/novostoic/dg-predictor/dg-predictor.component.html @@ -72,48 +72,10 @@
-
-
- Assign a number to the new molecule that - does not have a Kegg ID -
-
- - N - - -
- -
- Enter the new molecule’s SMILES or InChl -
- - -
- Enter Reactions with Kegg ID and/or the - unique number you assign to the new - molecule -
- -
-
+
+
Reaction {{ i + 1 }}
+
+
+
+ Have new molecule(s) not listed in KEGG database and MetaNetX database + + +
+ +
+ +
+
+ Enter the new molecule's SMILES or InChl + + +
+ +
+ + +
+
+
+ +
+ {{ novelMoleculeInput.controls['alias'].value }} +
+
+ +
+ + Note: This new molecule already has a unique number assigned; use that same number for this entry. + +
+
+
+
+
+ Enter a reaction with database IDs of known compounds. +
+ +
+
diff --git a/src/app/components/novostoic/main-layout/main-layout.component.html b/src/app/components/novostoic/main-layout/main-layout.component.html index eeaccc7..838904e 100644 --- a/src/app/components/novostoic/main-layout/main-layout.component.html +++ b/src/app/components/novostoic/main-layout/main-layout.component.html @@ -1,4 +1,4 @@ -
+
\ No newline at end of file diff --git a/src/app/components/novostoic/main-layout/main-layout.component.ts b/src/app/components/novostoic/main-layout/main-layout.component.ts index aec06ab..256d519 100644 --- a/src/app/components/novostoic/main-layout/main-layout.component.ts +++ b/src/app/components/novostoic/main-layout/main-layout.component.ts @@ -5,7 +5,7 @@ import { Component } from '@angular/core'; templateUrl: './main-layout.component.html', styleUrls: ['./main-layout.component.scss'], host: { - class: 'grow flex' + class: 'grow flex w-screen max-w-screen' } }) export class MainLayoutComponent { diff --git a/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.html b/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.html new file mode 100644 index 0000000..c4e8f1e --- /dev/null +++ b/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.html @@ -0,0 +1,35 @@ + +
+
+ +
+
+ +
+
+
+ + +
+ Common Name + {{ molecule.name }} +
+
+ SMILES + {{ molecule.smiles }} +
+
+ KEGG ID + {{ molecule.kegg_id }} +
+
\ No newline at end of file diff --git a/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.scss b/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.spec.ts b/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.spec.ts new file mode 100644 index 0000000..8a3da20 --- /dev/null +++ b/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MoleculeInfoOverlayComponent } from './molecule-info-overlay.component'; + +describe('MoleculeInfoOverlayComponent', () => { + let component: MoleculeInfoOverlayComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MoleculeInfoOverlayComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MoleculeInfoOverlayComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.ts b/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.ts new file mode 100644 index 0000000..b98f12c --- /dev/null +++ b/src/app/components/novostoic/molecule-info-overlay/molecule-info-overlay.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import { OverlayPanel } from 'primeng/overlaypanel'; +import { ChemicalAutoCompleteResponse } from '~/app/api/mmli-backend/v1'; +import { NovostoicMolecule } from '~/app/models/overall-stoichiometry'; + +@Component({ + selector: 'app-molecule-info-overlay', + templateUrl: './molecule-info-overlay.component.html', + styleUrls: ['./molecule-info-overlay.component.scss'] +}) +export class MoleculeInfoOverlayComponent { + @Input() molecule: NovostoicMolecule | Partial; + @Input() showSmiles: boolean; + @Input() imageBorderStyle: string; + @ViewChild('overlay') overlay: OverlayPanel; + + hide() { + this.overlay.hide(); + } + + show($event: MouseEvent) { + this.overlay.show($event); + } +} diff --git a/src/app/components/novostoic/overall-stoichiometry-result/overall-stoichiometry-result.component.html b/src/app/components/novostoic/overall-stoichiometry-result/overall-stoichiometry-result.component.html index 7153206..717b311 100644 --- a/src/app/components/novostoic/overall-stoichiometry-result/overall-stoichiometry-result.component.html +++ b/src/app/components/novostoic/overall-stoichiometry-result/overall-stoichiometry-result.component.html @@ -281,21 +281,4 @@
Target Molecule
- - - -
- Common Name - {{ molecule.name }} -
-
- KEGG ID - {{ molecule.kegg_id }} -
-
- SMILES - {{ molecule.smiles }} -
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.html b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.html index 806534d..fb636d3 100644 --- a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.html +++ b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.html @@ -102,14 +102,14 @@
*ngFor="let step of stepsArray; let i = index" > STEP {{ step }} @@ -132,14 +132,25 @@
- PATHWAY {{ i + 1}} @@ -152,7 +163,9 @@
$implicit: reaction, label: 'R' + (i + 1) + '.' + (j + 1), pathwayIndex: i, + reactionIdx: j, inFirstColumn: j === 0, + highlight: highlightPathway === i, inLastColumn: j === pathway.reactions.length - 1 } " > @@ -161,9 +174,11 @@
> @@ -181,7 +196,9 @@
[class.strip_pattern_to_right_thermo_infeasible]="pathway.reactions[pathway.reactions.length - 1].isPrediction && pathway.reactions[pathway.reactions.length - 1].isThermodynamicalInfeasible" [class.strip_pattern_to_right_thermo_feasible]="pathway.reactions[pathway.reactions.length - 1].isPrediction && !pathway.reactions[pathway.reactions.length - 1].isThermodynamicalInfeasible" >
- + @@ -236,20 +253,26 @@
No Pathway Found
-
- Common Name - {{ molecule.name }} -
-
- SMILES - {{ molecule.smiles }} -
-
- KEGG ID - {{ molecule.kegg_id }} -
+ +
+
+ Common Name + {{ molecule.name ? molecule.name : 'N/A'}} +
+
+ SMILES + {{ molecule.smiles ? molecule.smiles : 'N/A'}} +
+
+ KEGG ID + {{ molecule.kegg_id ? molecule.kegg_id : 'N/A'}} +
+
+ InChi + {{ molecule.inchi ? molecule.inchi : 'N/A' }} +
+
+
No Pathway Found let-label="label" let-inLastColumn="inLastColumn" let-inFirstColumn="inFirstColumn" + let-highlight="highlight" + let-reactionIdx="reactionIdx" > - +
No Pathway Found [class.border-[#C79807]]="reaction.isThermodynamicalInfeasible" [class.bg-[#FEFBF3]]="reaction.isThermodynamicalInfeasible" - (click)="reactionDetail.toggle($event)" + (click)="reactionDetail.toggle($event);$event.stopPropagation()" > No Pathway Found kJ/mol -
+
+ ~ {{ reaction.primaryPrecursor.name }} - + {{ reactant.molecule? reactant.molecule.name : 'NA' }} - {{ reactant.molecule? reactant.molecule.name : 'NA' }} + - {{ reaction.targetMolecule.name }} + ~
-
-
-
- ∆G - {{ reaction.deltaG.gibbsEnergy }}±{{ reaction.deltaG.std }}kJ/mol -
-
- Reaction ID - {{ reaction.reactionId }} -
-
-
-
- Enz - {{ reaction.enzymes.length > 1 ? 'Multiple' : - reaction.enzymes[0] }} -
-
- Confidence Score - {{ reaction.confidenceScore }} -
-
- - - + + + +
@@ -382,15 +390,17 @@
No Pathway Found
- +
-
+
No Pathway Found class="flex flex-col mx-4 bg-gray-50" > - -
Pathway {{ selectedPathway$.value + 1 }}:
+ +
Pathway {{ selectedPathway + 1 }}:
{{ pathway.reactions[0].primaryPrecursor!.name }} @@ -417,7 +427,7 @@
No Pathway Found
- +
@@ -426,7 +436,7 @@
No Pathway Found
{{ - reactions![selectedPathway$.value].length + reactions![selectedPathway].length }}
Predicted Steps
@@ -436,7 +446,7 @@
No Pathway Found
- {{ deltaGs![selectedPathway$.value].gibbsEnergy }}±{{ deltaGs![selectedPathway$.value].std | number:'1.1-1' }}kJ/mol + {{ deltaGs![selectedPathway].gibbsEnergy | number: '1.1-2' }}±{{ deltaGs![selectedPathway].std | number:'1.1-1' }}kJ/mol
Delta G
@@ -471,13 +481,17 @@
No Pathway Found
-
-
+
- Reaction {{selectedPathway$.value + 1}}.{{ i + 1 }} - + Reaction {{selectedPathway + 1}}.{{ i + 1 }}
Delta G Calculation @@ -495,7 +509,11 @@
No Pathway Found
@@ -510,7 +528,7 @@
No Pathway Found
Reaction Rule: -
{{reaction.reactionRule}}
+
{{reaction.reactionRule ? reaction.reactionRule : 'N/A'}}
@@ -638,13 +656,16 @@
No Pathway Found
-
- -80 - +50 + {{ novostoicService.thermoFeasibilityMin }} + +{{ novostoicService.thermoFeasibilityMax }}
diff --git a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss index 7c20353..d615dd9 100644 --- a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss +++ b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss @@ -1,20 +1,27 @@ -$predicted_color: #C79807; +$infeasible_color: #C79807; $indicator_color: var(--surface-500); .strip_pattern { - background: repeating-linear-gradient(white, white 3.9px, black 4px, black 7.9px);; + background: repeating-linear-gradient(transparent, transparent 3.9px, black 4px, black 7.9px);; } .strip_pattern_to_right_thermo_infeasible { - background: repeating-linear-gradient(to right, white, white 3px, $predicted_color 3.1px, $predicted_color 6.1px); + background: repeating-linear-gradient(to right, transparent, transparent 3px, $infeasible_color 3.1px, $infeasible_color 6.1px); } .strip_pattern_to_right_thermo_feasible { - background: repeating-linear-gradient(to right, white, white 3px, $indicator_color 3.1px, $indicator_color 6.1px); + background: repeating-linear-gradient(to right, transparent, transparent 3px, $indicator_color 3.1px, $indicator_color 6.1px); } .strip_pattern_border { - border-image: url("/assets/border.png") 2 round; + + &.thermodynamic_feasible { + background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='%239FA9B7FF' stroke-width='2' stroke-dasharray='8%2c 7' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e"); + } + + &.thermodynamic_infeasible { + background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='%23C79807FF' stroke-width='3' stroke-dasharray='9%2c 13' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e"); + } } .primary_precursor_container { @@ -102,11 +109,11 @@ $indicator_color: var(--surface-500); } .thermo_infeasible::before { - border-left-color: $predicted_color !important; + border-left-color: $infeasible_color !important; } .next_thermo_infeasible::after { - background: $predicted_color !important; + background: $infeasible_color !important; } .pathway_dialog { @@ -138,3 +145,17 @@ $indicator_color: var(--surface-500); @apply border-t; } } + +.has-indicator::after { + content: 'Click the whole pathway to learn more'; + @apply absolute; + @apply bg-white; + @apply text-center; + @apply block; + @apply bottom-0; + @apply left-0; + @apply w-full; + @apply py-1; + @apply border border-solid border-y border-[--surface-d]; + @apply shadow-sm; + } \ No newline at end of file diff --git a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts index 878c2b7..b59fa0d 100644 --- a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts +++ b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts @@ -43,7 +43,7 @@ export class PathwaySearchResultComponent extends JobResult { reactants: reaction.reactants.filter((reactant) => { return reactant.molecule.name !== reaction.primaryPrecursor?.name; }), - isThermodynamicalInfeasible: reaction.deltaG.gibbsEnergy > 20, + isThermodynamicalInfeasible: reaction.deltaG.gibbsEnergy > this.novostoicService.thermoFeasibilityMax, })) } }) @@ -52,8 +52,10 @@ export class PathwaySearchResultComponent extends JobResult { tap((data) => console.log('response', data)) ); - visible$ = new BehaviorSubject(false); - selectedPathway$ = new BehaviorSubject(0); + visible: boolean = false; + highlightPathway: number | null = null; + selectedPathway: number = 0; + selectedReactionIdx: number | null = null; pathwayDeltaGs$ = this.response$.pipe( map((response) => response.pathways.map((pathway) => pathway.reactions.reduce((p, v) => ({ @@ -124,8 +126,8 @@ export class PathwaySearchResultComponent extends JobResult { ]).pipe(map(([intermediates, cofactors]) => intermediates.length + cofactors.length)); selectedThermoFeasibleMode$ = new BehaviorSubject<'all' | 'any' | null>(null); - feasibleRangeMin$ = new BehaviorSubject(-80); - feasibleRangeMax$ = new BehaviorSubject(20); + feasibleRangeMin$ = new BehaviorSubject(this.novostoicService.thermoFeasibilityMin); + feasibleRangeMax$ = new BehaviorSubject(this.novostoicService.thermoFeasibilityMax); feasibleRange$ = combineLatest([ this.feasibleRangeMin$, this.feasibleRangeMax$, @@ -144,26 +146,26 @@ export class PathwaySearchResultComponent extends JobResult { const intermediatesSet = new Set(intermediates.map((intermediate) => intermediate.name)); const cofactorsSet = new Set(cofactors.map((cofactor) => cofactor.name)); return response.pathways.map((pathway) => { - let intermediatesMatch = true; - let cofactorsMatch = true; + let intermediatesMatch = false; + let cofactorsMatch = false; let thermodynamicalMatch = true; let thermodynamicalRangeMatch = true; pathway.reactions.forEach((reaction) => { - if (intermediatesSet.size && intermediatesMatch && reaction.targetMolecule) { - intermediatesMatch = intermediatesSet.has(reaction.targetMolecule.name); + if (reaction.targetMolecule) { + intermediatesMatch ||= intermediatesSet.size > 0 ? intermediatesSet.has(reaction.targetMolecule.name) : true; } - if (cofactorsSet.size && cofactorsMatch) { + + if (cofactorsSet.size) { reaction.reactants.forEach((reactant) => { - if (cofactorsMatch && cofactorsSet.has(reactant.molecule.name)) { - cofactorsMatch = true; - } + cofactorsMatch ||= cofactorsSet.has(reactant.molecule.name); }); reaction.products.forEach((product) => { - if (cofactorsMatch && cofactorsSet.has(product.molecule.name)) { - cofactorsMatch = true; - } + cofactorsMatch ||= cofactorsSet.has(product.molecule.name); }); + } else { + cofactorsMatch = true; } + if (mode && thermodynamicalMatch) { thermodynamicalMatch = mode === 'any' ? thermodynamicalMatch && !reaction.isThermodynamicalInfeasible @@ -178,6 +180,8 @@ export class PathwaySearchResultComponent extends JobResult { thermodynamicalMatch = !thermodynamicalMatch; } + console.log('pathway', pathway, intermediatesMatch, cofactorsMatch, thermodynamicalMatch, thermodynamicalRangeMatch); + return { ...pathway, match: intermediatesMatch && cofactorsMatch && thermodynamicalMatch && thermodynamicalRangeMatch, @@ -258,7 +262,7 @@ export class PathwaySearchResultComponent extends JobResult { ); constructor( - private novostoicService: NovostoicService, + protected novostoicService: NovostoicService, private route: ActivatedRoute, ) { super(novostoicService) @@ -267,8 +271,8 @@ export class PathwaySearchResultComponent extends JobResult { resetFilters() { this.intermediatesFilters$.next([]); this.coFactorsFilters$.next([]); - this.feasibleRangeMax$.next(20); - this.feasibleRangeMin$.next(-80); + this.feasibleRangeMax$.next(this.novostoicService.thermoFeasibilityMax); + this.feasibleRangeMin$.next(this.novostoicService.thermoFeasibilityMin); this.selectedThermoFeasibleMode$.next(null); } diff --git a/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html b/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html index d6b3581..ec3daa6 100644 --- a/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html +++ b/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html @@ -6,23 +6,11 @@ (mouseenter)="primaryPrecursorOverlay.show($event)" (mouseleave)="primaryPrecursorOverlay.hide()" > - -
-
- -
-
- -
-
-
+ @@ -36,27 +24,10 @@ (mouseleave)="coreactantOverlay.hide()" >{{ reactant.amount | number: '1.1-2' }} {{ reactant.molecule.name }} - -
-
- - Co-Reactant -
-
- -
-
- -
-
-
+ @@ -77,27 +48,10 @@ [class]="moleculeStyleClass" >{{ reactant.amount | number: '1.1-2' }} {{ reactant.molecule.name }} - -
-
- - Co-Product -
-
- -
-
- -
-
-
+ + @@ -109,40 +63,11 @@ (mouseenter)="targetMoleculeOverlay.show($event)" (mouseleave)="targetMoleculeOverlay.hide()" > - -
-
- -
-
- -
-
-
+ -
- - -
- Common Name - {{ molecule.name }} -
-
- SMILES - {{ molecule.smiles }} -
-
- KEGG ID - {{ molecule.kegg_id }} -
-
+
\ No newline at end of file diff --git a/src/app/models/dg-predictor.ts b/src/app/models/dg-predictor.ts index 44f1b1d..366ed60 100644 --- a/src/app/models/dg-predictor.ts +++ b/src/app/models/dg-predictor.ts @@ -1,10 +1,11 @@ import { FormArray, FormControl, FormGroup, Validators } from "@angular/forms"; +import { debounceTime } from "rxjs"; interface ReactionFormControl { type: FormControl; + containNovelMolecule?: FormControl; reactionSmiles?: FormControl; - moleculeNumber?: FormControl; - moleculeInchiOrSmiles?: FormControl; + novelMolecules?: FormArray; reactionKeggId?: FormControl; } @@ -12,12 +13,15 @@ export class ThermodynamicalFeasibilityRequest { form = new FormGroup({ ph: new FormControl(5, [Validators.required]), ionicStrength: new FormControl(0.3, [Validators.required]), - reactions: new FormArray([this.createReactionKeggIDFormControl()]), + reactions: new FormArray([ + this.createReactionKeggIDFormControl() + ]), agreeToSubscription: new FormControl(false), subscriberEmail: new FormControl("", [Validators.email]), reactionInputType: new FormControl("keggId", [Validators.required]), }); + private uniqueNovelMolecule = new Set(); private createReactionSmilesFormControl() { return new FormGroup({ type: new FormControl("smiles"), @@ -28,18 +32,22 @@ export class ThermodynamicalFeasibilityRequest { private createReactionKeggIDFormControl() { const formGroup = new FormGroup({ type: new FormControl("keggId"), - moleculeNumber: new FormControl("", []), - moleculeInchiOrSmiles: new FormControl("", []), + containNovelMolecule: new FormControl(false), + novelMolecules: new FormArray([] as any[]), reactionKeggId: new FormControl("", [Validators.required]), }); - formGroup.controls["moleculeInchiOrSmiles"]?.valueChanges.subscribe((value) => { + formGroup.controls["containNovelMolecule"]?.valueChanges.subscribe((value) => { if (value) { - formGroup.controls["moleculeNumber"]?.addValidators([Validators.required]); + this.addNovelMolecule(formGroup); } else { - formGroup.controls["moleculeNumber"]?.clearValidators(); + formGroup.controls["novelMolecules"]?.controls.forEach((_, i) => { + this.removeNovelMolecule(formGroup, i); + }); } - formGroup.controls["moleculeNumber"]?.updateValueAndValidity(); + formGroup.controls["novelMolecules"]?.controls.forEach((control) => { + control.updateValueAndValidity(); + }); }); return formGroup; @@ -70,14 +78,21 @@ export class ThermodynamicalFeasibilityRequest { reactions: [ { type: "keggId", - moleculeNumber: "00001", - moleculeInchiOrSmiles: "InChI=1S/C14H12O/c15-14-8-4-7-13(11-14)10-9-12-5-2-1-3-6-12/h1-11,15H/b10-9+", + containNovelMolecule: true, + novelMolecules: [ + { + iid: "iid-0.123456789", + alias: "N00001", + value: "InChI=1S/C14H12O/c15-14-8-4-7-13(11-14)10-9-12-5-2-1-3-6-12/h1-11,15H/b10-9+", + moleculeExisted: false + } + ], reactionKeggId: "C01745 + C00004 <=> N00001 + C00003 + C00001", } ], agreeToSubscription: false, subscriberEmail: "", - reactionInputType: "smiles", + reactionInputType: "keggId", }); return request; @@ -98,6 +113,62 @@ export class ThermodynamicalFeasibilityRequest { } } + addNovelMolecule(subform: FormGroup) { + const group = new FormGroup({ + iid: new FormControl(`iid-${Math.random()}`), + alias: new FormControl("", [Validators.required]), + value: new FormControl("", [Validators.required]), + moleculeExisted: new FormControl(false), + }) + + // listen to value changes and update the molecule index + group.controls['value'].valueChanges + .subscribe((value) => { + let aliasSet = false; + + this.uniqueNovelMolecule.clear(); + group.controls['moleculeExisted'].setValue(false); + + this.form.controls["reactions"].controls.forEach((reaction) => { + reaction.controls['novelMolecules']?.controls.forEach((g) => { + if (g.controls['iid'] === group.controls['iid']) { + return; + } + + const moleculeValue = g.controls['value'].value; + this.uniqueNovelMolecule.add(moleculeValue); + if (moleculeValue === value) { + group.controls['alias'].setValue(g.controls['alias'].value); + group.controls['moleculeExisted'].setValue(true); + aliasSet = true; + } + }); + }); + + if (!aliasSet) { + const moleculeIndex = `${this.uniqueNovelMolecule.size + 1}`.padStart(5, "0"); + group.controls['alias'].setValue(`N${moleculeIndex}`); + } + }); + + const moleculeIndex = `${this.uniqueNovelMolecule.size + 1}`.padStart(5, "0"); + group.controls['alias'].setValue(`N${moleculeIndex}`); + + subform.controls['novelMolecules']!.push(group); + } + + removeNovelMolecule(subform: FormGroup, idx: number) { + subform.controls["novelMolecules"]?.controls.forEach((group) => { + group.controls['value'].clearValidators(); + group.controls['alias'].clearValidators(); + }); + + subform.controls['novelMolecules']!.removeAt(idx); + if (!subform.controls['novelMolecules']!.length) { + this.addNovelMolecule(subform); + } + } + clearAllReactions() { this.clearAllInputHelper(this.form.controls["reactions"]); } @@ -114,10 +185,11 @@ export class ThermodynamicalFeasibilityRequest { add_info: {}, } - if (reaction.moleculeInchiOrSmiles) { - payload["add_info"] = { - ['N' + reaction.moleculeNumber]: reaction.moleculeInchiOrSmiles, - }; + if (reaction.containNovelMolecule) { + payload['add_info'] = {}; + reaction.novelMolecules?.forEach((group, i) => { + payload["add_info"][group.alias] = group.value; + }); } return payload; diff --git a/src/app/models/job-result.ts b/src/app/models/job-result.ts index 430e956..7d7af9e 100644 --- a/src/app/models/job-result.ts +++ b/src/app/models/job-result.ts @@ -1,4 +1,4 @@ -import { timer, switchMap, tap, takeWhile, BehaviorSubject, skipUntil, filter, map, delay, shareReplay } from "rxjs"; +import { timer, switchMap, tap, takeWhile, BehaviorSubject, skipUntil, filter, map, delay, shareReplay, share, combineLatest } from "rxjs"; import { JobStatus, JobType } from "../api/mmli-backend/v1"; import { NovostoicService } from "../services/novostoic.service"; @@ -7,13 +7,14 @@ export class JobResult { jobType: JobType; isLoading$ = new BehaviorSubject(true); + resultLoaded$ = new BehaviorSubject(false); statusResponse$ = timer(0, 10000).pipe( switchMap(() => this.service.getResultStatus( this.jobType, this.jobId, )), - tap(() => this.isLoading$.next(true)), + tap(() => this.resultLoaded$.value ? null : this.isLoading$.next(true)), takeWhile((data) => data.phase === JobStatus.Processing || data.phase === JobStatus.Queued @@ -26,6 +27,7 @@ export class JobResult { switchMap(() => this.service.getResult(this.jobType, this.jobId)), delay(1000), tap(() => this.isLoading$.next(false)), + tap(() => this.resultLoaded$.next(true)), tap((data) => { console.log('result: ', data) }), shareReplay(1), ); diff --git a/src/app/services/novostoic.service.ts b/src/app/services/novostoic.service.ts index ee7ab74..ba871c6 100644 --- a/src/app/services/novostoic.service.ts +++ b/src/app/services/novostoic.service.ts @@ -23,6 +23,9 @@ export class NovostoicService { submitted_at: "2020-01-01 10:10:10" }; + readonly thermoFeasibilityMax = 50; + readonly thermoFeasibilityMin = -8000; + constructor( private apiService: DefaultService,