@datagrok/bio 2.16.4 → 2.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Aleksandr Tanas",
6
6
  "email": "atanas@datagrok.ai"
7
7
  },
8
- "version": "2.16.4",
8
+ "version": "2.16.5",
9
9
  "description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
10
10
  "repository": {
11
11
  "type": "git",
@@ -16,7 +16,7 @@ import {MonomerLibManager} from '../lib-manager';
16
16
  import {LIB_PATH} from '../consts';
17
17
 
18
18
  import '../../../../css/monomer-manager.css';
19
- import { MONOMER_RENDERER_TAGS } from '@datagrok-libraries/bio/src/utils/cell-renderer';
19
+ import {MONOMER_RENDERER_TAGS} from '@datagrok-libraries/bio/src/utils/cell-renderer';
20
20
 
21
21
  // columns of monomers dataframe, note that rgroups is hidden and will be displayed as separate columns
22
22
  export enum MONOMER_DF_COLUMN_NAMES {
@@ -51,7 +51,6 @@ export const MONOMER_DF_COLUMNS = {
51
51
 
52
52
 
53
53
  export class MonomerManager implements IMonomerManager {
54
-
55
54
  private adjustColWidths() {
56
55
  setTimeout(() => {
57
56
  if (this.tv?.grid) {
@@ -87,7 +86,6 @@ export class MonomerManager implements IMonomerManager {
87
86
  }, 500);
88
87
  }
89
88
  }
90
-
91
89
  }, () => this.tv?.dataFrame);
92
90
  }
93
91
 
@@ -203,6 +201,7 @@ export class MonomerManager implements IMonomerManager {
203
201
  }
204
202
  })
205
203
  );
204
+ this.tv.grid && (this.tv.grid.props.allowEdit = false); // disable editing
206
205
  return this.tv;
207
206
  }
208
207
 
@@ -239,6 +238,10 @@ export class MonomerManager implements IMonomerManager {
239
238
  });
240
239
  ribbons = ribbons.filter((r) => r.length > 0);
241
240
 
241
+ const newMonomerButton = ui.icons.add(() => {
242
+ this._newMonomerForm.setEmptyMonomer();
243
+ }, 'Add New Monomer');
244
+
242
245
  const editButton = ui.icons.edit(() => {
243
246
  if ((this.tv?.dataFrame?.currentRowIdx ?? -1) < 0) return;
244
247
  this.cloneMonomer(this.tv!.dataFrame.rows.get(this.tv!.dataFrame.currentRowIdx));
@@ -277,19 +280,19 @@ export class MonomerManager implements IMonomerManager {
277
280
  DG.Utils.download(libName!, lib!, 'text/plain');
278
281
  }, 'Download Monomer Library');
279
282
 
280
- ribbons.push([editButton, deleteButton, downloadButton]);
283
+ ribbons.push([newMonomerButton, editButton, deleteButton, downloadButton]);
281
284
  this.tv.setRibbonPanels(ribbons);
282
285
 
283
286
 
284
287
  this.tv.name = MonomerManager.VIEW_NAME;
285
288
  this.libInput = ui.input.choice('Monomer Library', {value: libName, items: availableMonLibs, nullable: false, onValueChanged: async () => {
286
- try {
287
- const df = await this.getMonomersDf(this.libInput.value!);
289
+ try {
290
+ const df = await this.getMonomersDf(this.libInput.value!);
288
291
  this.tv!.dataFrame = df;
289
292
  this.adjustColWidths();
290
- } catch (e) {
291
- console.error(e);
292
- }
293
+ } catch (e) {
294
+ console.error(e);
295
+ }
293
296
  }});
294
297
  this.libInput.addOptions(ui.icons.add(() => { this.createNewLibDialog(); }, 'Create new monomer library...'));
295
298
  const monForm = this._newMonomerForm.form;
@@ -370,6 +373,7 @@ export class MonomerManager implements IMonomerManager {
370
373
  df.col(rgName)!.semType = DG.SEMTYPE.MOLECULE;
371
374
  });
372
375
  df.currentRowIdx = -1;
376
+ // eslint-disable-next-line rxjs/no-ignored-subscription
373
377
  df.onCurrentRowChanged.subscribe((_) => {
374
378
  try {
375
379
  if (df.currentRowIdx === -1 || this._newMonomerForm.molChanged)
@@ -541,6 +545,7 @@ class MonomerForm implements INewMonomerForm {
541
545
  await this.saveMonomer();
542
546
  });
543
547
  // this.saveButton.style.pointerEvents = 'revert';
548
+ // eslint-disable-next-line rxjs/no-async-subscribe
544
549
  this.molSketcher.subs.push(this.molSketcher.onChanged.subscribe(async () => {
545
550
  if (!this.triggerMolChange) {
546
551
  this.triggerMolChange = true;
@@ -551,6 +556,9 @@ class MonomerForm implements INewMonomerForm {
551
556
  let smiles = this.molSketcher.getSmiles();
552
557
  if (!smiles) {
553
558
  this.rgroupsGrid.items = [];
559
+ this.rgroupsGrid.render();
560
+ this.saveValidationResult = 'Monomer molecule is required';
561
+ this.invalidateSaveButton();
554
562
  return;
555
563
  }
556
564
  smiles = getCorrectedSmiles([], smiles);
@@ -559,6 +567,8 @@ class MonomerForm implements INewMonomerForm {
559
567
  if (rGroupMatches.length === 0) {
560
568
  this.rgroupsGrid.items = [];
561
569
  this.rgroupsGrid.render();
570
+ this.saveValidationResult = 'At least one R-group is required';
571
+ this.invalidateSaveButton();
562
572
  return;
563
573
  }
564
574
  const rGroupNums = rGroupMatches.map((match) => Number.parseInt(match[0].match(/[1-9]/g)![0]));
@@ -606,6 +616,7 @@ class MonomerForm implements INewMonomerForm {
606
616
  [HELM_RGROUP_FIELDS.LABEL]: 'Label',
607
617
  },
608
618
  });
619
+ // eslint-disable-next-line rxjs/no-ignored-subscription
609
620
  this.rgroupsGrid.onItemChanged.subscribe(() => this.onMonomerInputChanged());
610
621
 
611
622
  this.rgroupsGridRoot = ui.divV([this.rgroupsGrid.root]);
@@ -633,17 +644,19 @@ class MonomerForm implements INewMonomerForm {
633
644
  'Meta': ui.divV([this.metaGrid.root]),
634
645
  'Colors': this.colorsEditor.form,
635
646
  }, false);
647
+ }
636
648
 
649
+ invalidateSaveButton() {
650
+ if (this.saveValidationResult)
651
+ this.saveButton.classList.add('d4-disabled');
652
+ else
653
+ this.saveButton.classList.remove('d4-disabled');
637
654
  }
638
655
 
639
656
  onMonomerInputChanged() {
640
657
  setTimeout(() => {
641
658
  this.saveValidationResult = this.validateInputs();
642
- if (this.saveValidationResult)
643
- this.saveButton.classList.add('d4-disabled');
644
- else
645
- this.saveButton.classList.remove('d4-disabled');
646
-
659
+ this.invalidateSaveButton();
647
660
  const monomerExists = this.polymerTypeInput.value && this.polymerTypeInput.value &&
648
661
  !!this.getMonomerLib()?.getMonomer(this.polymerTypeInput.value as PolymerType, this.monomerSymbolInput.value);
649
662
 
@@ -651,6 +664,27 @@ class MonomerForm implements INewMonomerForm {
651
664
  }, 200);
652
665
  }
653
666
 
667
+ setEmptyMonomer() {
668
+ this.triggerMolChange = false;
669
+ this.molSketcher.setSmiles('');
670
+ // leave polymer and monomer type as is
671
+ this.monomerSymbolInput.value = '';
672
+ this.monomerNameInput.value = '';
673
+ this.monomerIdInput.value = null;
674
+ this.monomerNaturalAnalogInput.value = null;
675
+ this.rgroupsGrid.items = [];
676
+ this.metaGrid.items = [];
677
+ this.rgroupsGrid.render();
678
+ this.metaGrid.render();
679
+ this.rgroupsGridRoot.style.display = 'none';
680
+ this.onMonomerInputChanged();
681
+ this.colorsEditor.colors = {
682
+ line: '#000000',
683
+ background: '#000000',
684
+ text: '#000000',
685
+ };
686
+ }
687
+
654
688
  setMonomer(monomer: Monomer) {
655
689
  this.triggerMolChange = false;
656
690
  this.molSketcher.setSmiles(monomer.smiles);
@@ -661,7 +695,7 @@ class MonomerForm implements INewMonomerForm {
661
695
  this.monomerIdInput.value = monomer.id;
662
696
  this.monomerNaturalAnalogInput.value = monomer.naturalAnalog ?? null;
663
697
  this.rgroupsGrid.items = resolveRGroupInfo(monomer.rgroups);
664
- this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
698
+ this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, _v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
665
699
  return {Property: k, Value: v};
666
700
  });
667
701
  this.rgroupsGrid.render();
@@ -681,7 +715,7 @@ class MonomerForm implements INewMonomerForm {
681
715
  } catch (e) {
682
716
  console.error(e);
683
717
  }
684
-
718
+
685
719
  this.colorsEditor.colors = {
686
720
  line: colorsObj.line ?? '#000000',
687
721
  background: colorsObj.background ?? '#000000',
@@ -693,7 +727,7 @@ class MonomerForm implements INewMonomerForm {
693
727
  const rGroupsPane = this.inputsTabControl.panes.find((p) => p.name?.toLowerCase() === 'r-groups');
694
728
  rGroupsPane && (rGroupsPane.header.style.removeProperty('background-color'));
695
729
  if (!this.molSketcher.getSmiles()) return 'Monomer Molecule field is required';
696
- for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput, this.monomerIdInput]) {
730
+ for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput]) {
697
731
  if (i.value == null || i.value === '')
698
732
  return `${i.caption} field is required`;
699
733
  }
@@ -703,17 +737,18 @@ class MonomerForm implements INewMonomerForm {
703
737
  if (!rgroupError) {
704
738
  outerFor:
705
739
  for (const item of this.rgroupsGrid.items) {
706
- for (const [k, v] of Object.entries(item))
707
- if (!v){
740
+ for (const [k, v] of Object.entries(item)) {
741
+ if (!v) {
708
742
  rgroupError = `R-group ${k} field is required for ${item[HELM_RGROUP_FIELDS.LABEL]}`;
709
743
  break outerFor;
710
- }
744
+ }
745
+ }
711
746
  }
712
747
  }
713
748
 
714
- if (!rgroupError && this.rgroupsGrid.hasErrors()){
749
+ if (!rgroupError && this.rgroupsGrid.hasErrors())
715
750
  rgroupError = 'R-group fields contain errors';
716
- }
751
+
717
752
  if (rgroupError) {
718
753
  rGroupsPane && (rGroupsPane.header.style.setProperty('background-color', '#ff000030'));
719
754
  return rgroupError;
@@ -730,7 +765,6 @@ class MonomerForm implements INewMonomerForm {
730
765
  }
731
766
 
732
767
  get form() {
733
-
734
768
  this.inputsTabControl.root.classList.add('monomer-manager-form-tab-control');
735
769
  this.inputsTabControl.header.style.marginBottom = '10px';
736
770
  const saveB = ui.buttonsInput([this.saveButton]);
@@ -826,11 +860,11 @@ class MonomerForm implements INewMonomerForm {
826
860
  try {
827
861
  // first remove the existing monomer with that symbol
828
862
  const monomerIdx = libJSON.findIndex((m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType);
829
- if (monomerIdx >= 0) {
863
+ if (monomerIdx >= 0)
830
864
  libJSON[monomerIdx] = {...monomer, lib: undefined, wem: undefined};
831
- } else {
865
+ else
832
866
  libJSON.push({...monomer, lib: undefined, wem: undefined});
833
- }
867
+
834
868
  await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON));
835
869
  await (await MonomerLibManager.getInstance()).loadLibraries(true);
836
870
  await this.refreshTable(monomer.symbol);
@@ -839,6 +873,7 @@ class MonomerForm implements INewMonomerForm {
839
873
  grok.shell.error('Error saving monomer');
840
874
  console.error(e);
841
875
  }
876
+ this.onMonomerInputChanged();
842
877
  };
843
878
  let infoTable: HTMLDivElement | null = null;
844
879
  let promptMessage = '';
@@ -882,9 +917,9 @@ class MonomerForm implements INewMonomerForm {
882
917
  meta[item['Property']] = item['Value'];
883
918
  });
884
919
  const addingItem = this.metaGrid.addingItem;
885
- if (addingItem && addingItem['Property'] && addingItem['Value']) {
920
+ if (addingItem && addingItem['Property'] && addingItem['Value'])
886
921
  meta[addingItem['Property']] = addingItem['Value'];
887
- }
922
+
888
923
  //console.log(this.metaGrid.addingItem);
889
924
  if (this.colorsEditor.colors.line !== '#000000' || this.colorsEditor.colors.background !== '#000000' || this.colorsEditor.colors.text !== '#000000')
890
925
  meta.colors = {default: this.colorsEditor.colors};
@@ -975,7 +1010,7 @@ function getCorrectedMolBlock(molBlock: string) {
975
1010
 
976
1011
  if (rgpLineIdx === -1) {
977
1012
  // number of r groups has 3 empty slots before it, atom numbers have 4 empty slots before them
978
- let rgpLine = `M RGP${rgroupLineNums.length.toString().padStart(3,' ')}${Object.entries(rgroupLineNumbers).map(([atomLine, rGroupNum]) => `${atomLine.toString().padStart(4, ' ')}${rGroupNum.toString().padStart(4, ' ')}`).join('')}`;
1013
+ const rgpLine = `M RGP${rgroupLineNums.length.toString().padStart(3, ' ')}${Object.entries(rgroupLineNumbers).map(([atomLine, rGroupNum]) => `${atomLine.toString().padStart(4, ' ')}${rGroupNum.toString().padStart(4, ' ')}`).join('')}`;
979
1014
  const mEndIdx = lines.findIndex((line) => line.startsWith('M') && line.includes('END'));
980
1015
  lines.splice(mEndIdx, 0, rgpLine);
981
1016
  }
@@ -1042,9 +1077,8 @@ class ColorsEditor {
1042
1077
  };
1043
1078
 
1044
1079
  this._colors = colsHex;
1045
- for (const key in this._colorInputs) {
1080
+ for (const key in this._colorInputs)
1046
1081
  this._colorInputs[key as keyof ColorsEditor['_colors']].value = colsHex[key as keyof ColorsEditor['_colors']];
1047
- }
1048
1082
  }
1049
1083
 
1050
1084
  get colorsMetaFormat() {
@@ -1054,4 +1088,4 @@ class ColorsEditor {
1054
1088
  get form() {
1055
1089
  return ui.form(Object.values(this._colorInputs));
1056
1090
  }
1057
- }
1091
+ }
@@ -395,14 +395,14 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
395
395
 
396
396
  // -- Data --
397
397
  this.sequenceColumnName = this.string(PROPS.sequenceColumnName, defaults.sequenceColumnName,
398
- {category: PROPS_CATS.DATA});
398
+ {category: PROPS_CATS.DATA, semType: DG.SEMTYPE.MACROMOLECULE});
399
399
  const aggExcludeList = [DG.AGG.KEY, DG.AGG.PIVOT, DG.AGG.MISSING_VALUE_COUNT, DG.AGG.SKEW, DG.AGG.KURT,
400
400
  DG.AGG.SELECTED_ROWS_COUNT];
401
401
  const aggChoices = Object.values(DG.AGG).filter((agg) => !aggExcludeList.includes(agg));
402
402
  this.valueAggrType = this.string(PROPS.valueAggrType, defaults.valueAggrType,
403
403
  {category: PROPS_CATS.DATA, choices: aggChoices}) as DG.AggregationType;
404
404
  this.valueColumnName = this.string(PROPS.valueColumnName, defaults.valueColumnName,
405
- {category: PROPS_CATS.DATA});
405
+ {category: PROPS_CATS.DATA, columnTypeFilter: 'numerical'});
406
406
  this.startPositionName = this.string(PROPS.startPositionName, defaults.startPositionName,
407
407
  {category: PROPS_CATS.DATA});
408
408
  this.endPositionName = this.string(PROPS.endPositionName, defaults.endPositionName,
@@ -1022,20 +1022,18 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1022
1022
 
1023
1023
  // 2022-05-05 askalkin instructed to show WebLogo based on filter (not selection)
1024
1024
  const dfRowCount = this.dataFrame.rowCount;
1025
-
1025
+ const filterIndexes = dfFilter.getSelectedIndexes();
1026
1026
  for (let jPos = 0; jPos < length; ++jPos) {
1027
1027
  // Here we want to build lists of values for every monomer in position jPos
1028
- for (let rowI = 0; rowI < dfRowCount; ++rowI) {
1029
- if (dfFilter.get(rowI)) {
1030
- const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1031
- const om: string = jPos < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
1032
- this.seqHandler.defaultGapOriginal;
1033
- const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
1034
- const pi = this.positions[jPos];
1035
- const pmi = pi.getFreq(cm);
1036
- ++pi.sumRowCount;
1037
- pmi.value = ++pmi.rowCount;
1038
- }
1028
+ for (const rowI of filterIndexes) {
1029
+ const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1030
+ const om: string = jPos + this.startPosition < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
1031
+ this.seqHandler.defaultGapOriginal;
1032
+ const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
1033
+ const pi = this.positions[jPos];
1034
+ const pmi = pi.getFreq(cm);
1035
+ ++pi.sumRowCount;
1036
+ pmi.value = ++pmi.rowCount;
1039
1037
  }
1040
1038
  if (this.valueAggrType === DG.AGG.TOTAL_COUNT) continue;
1041
1039
 
@@ -1048,13 +1046,13 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1048
1046
  } catch { valueCol = null; }
1049
1047
  if (!valueCol) continue; // fallback to TOTAL_COUNT
1050
1048
 
1051
- for (let rowI = 0; rowI < dfRowCount; ++rowI) {
1052
- if (dfFilter.get(rowI)) { // respect the filter
1053
- const seqMList: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1054
- const cm: string = seqMList.getCanonical(this.startPosition + jPos);
1055
- const value: number | null = valueCol.get(rowI);
1056
- this.positions[jPos].getFreq(cm).push(value);
1057
- }
1049
+ for (const rowI of filterIndexes) {
1050
+ const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1051
+ const om: string = jPos + this.startPosition < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
1052
+ this.seqHandler.defaultGapOriginal;
1053
+ const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
1054
+ const value: number | null = valueCol.get(rowI);
1055
+ this.positions[jPos].getFreq(cm).push(value);
1058
1056
  }
1059
1057
  this.positions[jPos].aggregate(this.valueAggrType);
1060
1058
  }