@datagrok/sequence-translator 1.10.10 → 1.10.12

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.10.10",
4
+ "version": "1.10.12",
5
5
  "author": {
6
6
  "name": "Davit Rizhinashvili",
7
7
  "email": "drizhinashvili@datagrok.ai"
@@ -22,7 +22,7 @@
22
22
  }
23
23
  ],
24
24
  "dependencies": {
25
- "@datagrok-libraries/bio": "^5.62.1",
25
+ "@datagrok-libraries/bio": "^5.63.6",
26
26
  "@datagrok-libraries/chem-meta": "^1.2.8",
27
27
  "@datagrok-libraries/tutorials": "^1.6.1",
28
28
  "@datagrok-libraries/utils": "^4.6.5",
@@ -42,9 +42,9 @@
42
42
  "devDependencies": {
43
43
  "@datagrok-libraries/helm-web-editor": "^1.1.16",
44
44
  "@datagrok-libraries/js-draw-lite": "^0.0.10",
45
- "@datagrok/bio": "^2.24.0",
46
- "@datagrok/chem": "^1.13.0",
47
- "@datagrok/helm": "^2.7.0",
45
+ "@datagrok/bio": "^2.26.0",
46
+ "@datagrok/chem": "^1.17.0",
47
+ "@datagrok/helm": "^2.13.0",
48
48
  "@types/jquery": "^3.5.32",
49
49
  "@types/js-yaml": "^4.0.5",
50
50
  "@types/lodash": "^4.14.202",
@@ -55,6 +55,7 @@
55
55
  "@typescript-eslint/eslint-plugin": "^7.2.0",
56
56
  "@typescript-eslint/parser": "^7.2.0",
57
57
  "css-loader": "^6.7.3",
58
+ "datagrok-tools": "^5.0.0",
58
59
  "eslint": "^8.57.0",
59
60
  "eslint-config-google": "^0.14.0",
60
61
  "style-loader": "^3.3.1",
@@ -1,6 +1,8 @@
1
1
  .st-colored-text-input > textarea {
2
2
  width: 100%;
3
- -webkit-text-fill-color: transparent;
3
+ color: transparent !important;
4
+ caret-color: var(--grey-6);
5
+ -webkit-text-fill-color: transparent !important;
4
6
  background-color: transparent;
5
7
  position: relative;
6
8
  z-index: 1;
@@ -2,11 +2,22 @@
2
2
 
3
3
  .st-structure-body {
4
4
  padding-right: 20px;
5
+ height: 100%;
6
+ display: flex;
7
+ flex-direction: column;
5
8
  }
6
9
 
7
10
  .st-structure-mol-img {
8
- margin-right: 30px;
9
- float: right;
11
+ flex: 1 1 0;
12
+ min-height: 100px;
13
+ min-width: 0;
14
+ transform: translateX(-50px);
15
+ overflow: hidden;
16
+ }
17
+
18
+ .st-structure-mol-img canvas {
19
+ max-width: 100% !important;
20
+ height: auto !important;
10
21
  }
11
22
 
12
23
  .st-structure-bool-button-block {
@@ -15,8 +26,9 @@
15
26
  }
16
27
 
17
28
  .st-structure-bottom {
18
- flex-direction: row-reverse;
29
+ flex: 1 1 0;
19
30
  padding-top: 20px;
31
+ min-height: 0;
20
32
  }
21
33
 
22
34
  .st-structure-clear-buttons {
@@ -52,7 +52,6 @@ class StructureAppLayout {
52
52
  );
53
53
  this.moleculeImgDiv = ui.block([]);
54
54
  $(this.moleculeImgDiv).addClass('st-structure-mol-img');
55
-
56
55
  DG.debounce<string>(this.onInput, 300).subscribe(async () => {
57
56
  await this.updateMoleculeImg();
58
57
  });
@@ -74,7 +73,7 @@ class StructureAppLayout {
74
73
  const tableLayout = this.getTableInput(th);
75
74
  const boolInputsAndButton = this.getBoolInputsAndButton();
76
75
  await this.updateMoleculeImg();
77
- const bottomDiv = ui.divH([boolInputsAndButton, this.moleculeImgDiv]);
76
+ const bottomDiv = ui.divH([this.moleculeImgDiv, boolInputsAndButton]);
78
77
  $(bottomDiv).addClass('st-structure-bottom');
79
78
 
80
79
  const layout = ui.divV([tableLayout, bottomDiv]);
@@ -186,13 +185,8 @@ class StructureAppLayout {
186
185
  const errStr = errorToConsole(err);
187
186
  console.error(errStr);
188
187
  }
189
- // todo: compute relative numbers
190
- const canvasWidth = 650;
191
- const canvasHeight = 150;
192
188
  const molImgObj = new MoleculeImage(molfile);
193
- await molImgObj.drawMolecule(this.moleculeImgDiv, canvasWidth, canvasHeight);
194
- // should the canvas be returned from the above function?
195
- $(this.moleculeImgDiv).find('canvas').css('float', 'inherit');
189
+ await molImgObj.drawMolecule(this.moleculeImgDiv, 650, 150);
196
190
  }
197
191
  }
198
192
 
@@ -56,8 +56,6 @@ class TranslatorAppLayout {
56
56
  }
57
57
  });
58
58
 
59
- $(this.formatChoiceInput.root.getElementsByTagName('select')[0]).css('width', '20%');
60
-
61
59
  this.sequenceInputBase = ui.input.textArea(
62
60
  '', {value: DEFAULT_AXOLABS_INPUT, onValueChanged: () => { this.onInput.next(); }}
63
61
  );
@@ -235,9 +233,13 @@ class TranslatorAppLayout {
235
233
  }
236
234
 
237
235
  private saveMolfile(): void {
238
- const result = (new SequenceToMolfileConverter(this.sequence, false,
239
- this.formatChoiceInput.value!)).convert() + '\n$$$$';
240
- download(this.sequence + '.sdf', encodeURIComponent(result));
236
+ try {
237
+ const result = (new SequenceToMolfileConverter(this.sequence, false,
238
+ this.formatChoiceInput.value!)).convert() + '\n$$$$';
239
+ download(this.sequence + '.sdf', encodeURIComponent(result));
240
+ } catch (e: any) {
241
+ grok.shell.warning('Unable to save SDF: ' + e.message);
242
+ }
241
243
  }
242
244
 
243
245
  private copySmiles(): void {
@@ -16,7 +16,6 @@ import {helmTypeToPolymerType} from '@datagrok-libraries/bio/src/monomer-works/m
16
16
  import {getSeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
17
17
  import '@datagrok-libraries/bio/src/types/input';
18
18
  import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
19
- import {InputColumnBase} from '@datagrok-libraries/bio/src/types/input';
20
19
  import {SeqValueBase} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
21
20
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
22
21
  import {SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
@@ -60,7 +59,8 @@ type PolyToolEnumerateInputs = {
60
59
  placeholders: PolyToolPlaceholdersInput;
61
60
  placeholdersBreadth: PolyToolPlaceholdersBreadthInput;
62
61
  enumeratorType: DG.ChoiceInput<PolyToolEnumeratorType>
63
- trivialNameCol: InputColumnBase,
62
+ trivialName: DG.InputBase<string>,
63
+ appendToTable: DG.InputBase<DG.DataFrame | null>,
64
64
  keepOriginal: DG.InputBase<boolean>;
65
65
  toAtomicLevel: DG.InputBase<boolean>;
66
66
  generateHelm: DG.InputBase<boolean>;
@@ -75,7 +75,7 @@ type PolyToolEnumerateHelmSerialized = {
75
75
  placeholders: string;
76
76
  placeholdersBreadth: string;
77
77
  enumeratorType: PolyToolEnumeratorType;
78
- trivialNameCol: string;
78
+ trivialName: string;
79
79
  keepOriginal: boolean;
80
80
  toAtomicLevel: boolean;
81
81
  generateHelm: boolean;
@@ -170,7 +170,6 @@ async function getPolyToolEnumerateDialog(
170
170
  const libHelper = await getMonomerLibHelper();
171
171
  const monomerLib = libHelper.getMonomerLib();
172
172
  const seqHelper = await getSeqHelper();
173
- const emptyDf: DG.DataFrame = DG.DataFrame.fromColumns([]);
174
173
 
175
174
  const helmHelper = await getHelmHelper();
176
175
  const monomerLibFuncs = helmHelper.buildMonomersFuncsFromLib(monomerLib);
@@ -216,8 +215,10 @@ async function getPolyToolEnumerateDialog(
216
215
  let srcId: { value: string, colName: string } | null = null;
217
216
  let ruleFileList: string[];
218
217
  let ruleInputs: RuleInputs;
219
- const trivialNameSampleDiv = ui.divText('', {style: {marginLeft: '8px', marginTop: '2px'}});
220
218
  const warningsTextDiv = ui.divText('', {style: {color: 'red'}});
219
+ const resultCountDiv = ui.divText('', {style: {
220
+ fontSize: '11px', color: 'var(--grey-4)', marginTop: '2px', marginLeft: '4px', whiteSpace: 'nowrap',
221
+ }});
221
222
 
222
223
  // === INPUT DEFINITIONS ===
223
224
  inputs = {
@@ -288,22 +289,17 @@ async function getPolyToolEnumerateDialog(
288
289
  onValueChanged: (value: string[]) => { ruleFileList = value; }
289
290
  })).getForm()
290
291
  },
291
- trivialNameCol: ui.input.column2(
292
+ trivialName: ui.input.string(
292
293
  'Trivial name', {
293
- table: cell?.dataFrame,
294
- filter: (col: DG.Column): boolean => {
295
- return col.type === DG.COLUMN_TYPE.STRING && col != cell?.column; /* id */
296
- },
297
- onValueChanged: (): void => {
298
- const valueCol = inputs.trivialNameCol.value;
299
- let newSrcId: typeof srcId = null;
300
- if (cell && valueCol) {
301
- const originalId = valueCol.get(cell.rowIndex)!;
302
- newSrcId = {value: originalId, colName: valueCol.name};
303
- }
304
- srcId = newSrcId;
305
- trivialNameSampleDiv.textContent = srcId ? `Original ID: ${srcId.value}` : '';
294
+ value: '',
295
+ onValueChanged: (value: string): void => {
296
+ const trimmed = value?.trim();
297
+ srcId = trimmed ? {value: trimmed, colName: 'Trivial name'} : null;
306
298
  },
299
+ }),
300
+ appendToTable: ui.input.table(
301
+ 'Append to table', {
302
+ items: grok.shell.tables,
307
303
  nullable: true,
308
304
  }),
309
305
  };
@@ -316,7 +312,22 @@ async function getPolyToolEnumerateDialog(
316
312
  };
317
313
  updateEnumTypeTooltip();
318
314
 
319
- inputs.trivialNameCol.addOptions(trivialNameSampleDiv);
315
+ // Attach a "pick from column" icon button to the trivial name input
316
+ if (cell?.dataFrame) {
317
+ const colIcon = ui.iconFA('columns', (e: MouseEvent) => {
318
+ DG.Menu.popup()
319
+ .singleColumnSelector(cell.dataFrame, {
320
+ columnFilter: (col: DG.Column) => col.type === DG.COLUMN_TYPE.STRING && col !== cell.column,
321
+ onChange: (_grid, col: DG.Column, currentRowChanged: boolean) => {
322
+ if (currentRowChanged)
323
+ inputs.trivialName.value = col.get(cell.rowIndex) ?? '';
324
+ },
325
+ })
326
+ .show({x: e.clientX, y: e.clientY});
327
+ }, 'Pick trivial name from a column');
328
+ inputs.trivialName.addOptions(colIcon);
329
+ }
330
+ inputs.trivialName.root.style.maxWidth = '450px';
320
331
 
321
332
  // Wire up monomer cell double-click to open selection dialog
322
333
  inputs.placeholders.onMonomerCellEdit = async (position: number, currentMonomers: string[]) => {
@@ -511,7 +522,9 @@ async function getPolyToolEnumerateDialog(
511
522
  defaultErrorHandler(err);
512
523
  }
513
524
  }));
514
- subs.push(inputs.placeholders.onChanged.subscribe(() => { updateViewMol(); }));
525
+ subs.push(inputs.placeholders.onChanged.subscribe(() => { updateViewMol(); updateResultCount(); }));
526
+ subs.push(inputs.placeholdersBreadth.onChanged.subscribe(() => { updateResultCount(); }));
527
+ subs.push(inputs.keepOriginal.onChanged.subscribe(() => { updateResultCount(); }));
515
528
 
516
529
  // TODO: suspect
517
530
  subs.push(ui.onSizeChanged(inputs.placeholders.root).subscribe(() => {
@@ -587,14 +600,49 @@ async function getPolyToolEnumerateDialog(
587
600
  //resizeInputs();
588
601
  };
589
602
 
590
- const fillTrivialNameList = (table?: DG.DataFrame) => {
591
- if (table) {
592
- inputs.trivialNameCol.setColumnInputTable(table);
593
- inputs.trivialNameCol.root.style.removeProperty('display');
594
- } else {
595
- inputs.trivialNameCol.setColumnInputTable(emptyDf);
596
- inputs.trivialNameCol.root.style.setProperty('display', 'none');
603
+ // Computes expected result count from current placeholders, breadth, and enumeration type
604
+ const updateResultCount = () => {
605
+ try {
606
+ const phs = inputs.placeholders.placeholdersValue.filter((ph) => ph.monomers.length > 0);
607
+ const bphs = inputs.placeholdersBreadth.placeholdersBreadthValue
608
+ .filter((ph) => ph.monomers.length > 0 && ph.start != null && ph.end != null);
609
+
610
+ let regularCount = 0;
611
+ switch (inputs.enumeratorType.value) {
612
+ case PolyToolEnumeratorTypes.Single:
613
+ regularCount = phs.reduce((sum, ph) => sum + ph.monomers.length, 0);
614
+ break;
615
+ case PolyToolEnumeratorTypes.Parallel: {
616
+ if (phs.length > 0) {
617
+ const allEqual = phs.every((ph) => ph.monomers.length === phs[0].monomers.length);
618
+ regularCount = allEqual ? phs[0].monomers.length : 0;
619
+ }
620
+ break;
621
+ }
622
+ case PolyToolEnumeratorTypes.Matrix:
623
+ regularCount = phs.length > 0 ? phs.reduce((prod, ph) => prod * ph.monomers.length, 1) : 0;
624
+ break;
625
+ }
626
+
627
+ let breadthCount = 0;
628
+ if (bphs.length > 0) {
629
+ breadthCount = bphs.reduce((prod, ph) => {
630
+ const rangeSize = Math.abs(ph.end - ph.start) + 1;
631
+ return prod * (ph.monomers.length * rangeSize);
632
+ }, 1);
633
+ }
634
+
635
+ let total = regularCount + breadthCount;
636
+ if (inputs.keepOriginal.value && total > 0)
637
+ total += 1;
638
+
639
+ resultCountDiv.textContent = total > 0 ? `${total} sequence${total !== 1 ? 's' : ''} will be generated` : '';
640
+ } catch (_) {
641
+ resultCountDiv.textContent = '';
597
642
  }
643
+ };
644
+
645
+ const fillTrivialNameList = (_table?: DG.DataFrame) => {
598
646
  if (resizeInputs)
599
647
  resizeInputs();
600
648
  };
@@ -607,6 +655,7 @@ async function getPolyToolEnumerateDialog(
607
655
 
608
656
  fillForCurrentCell(seqValue, dataRole, cell);
609
657
  updateViewRules();
658
+ updateResultCount();
610
659
 
611
660
  // === EXECUTION (OK button handler) ===
612
661
  // Pre-flight validates inputs, builds params, and runs enumeration.
@@ -663,7 +712,13 @@ async function getPolyToolEnumerateDialog(
663
712
  rules: await ruleInputs.getActive()
664
713
  } : false,
665
714
  helmHelper);
666
- grok.shell.addTableView(enumeratorResDf);
715
+ const appendTarget = inputs.appendToTable.value;
716
+ if (appendTarget) {
717
+ appendTarget.append(enumeratorResDf, true);
718
+ appendTarget.meta.detectSemanticTypes();
719
+ } else {
720
+ grok.shell.addTableView(enumeratorResDf);
721
+ }
667
722
  }
668
723
  } catch (err: any) {
669
724
  defaultErrorHandler(err);
@@ -677,7 +732,8 @@ async function getPolyToolEnumerateDialog(
677
732
  .add(ui.divH([
678
733
  ui.divV([
679
734
  inputs.placeholders.root,
680
- inputs.enumeratorType.root],
735
+ ui.divH([inputs.enumeratorType.root, resultCountDiv],
736
+ {style: {alignItems: 'center'}})],
681
737
  {style: {width: '50%'}}
682
738
  ),
683
739
  ui.divV([
@@ -686,8 +742,9 @@ async function getPolyToolEnumerateDialog(
686
742
  {style: {width: '100%'}}))
687
743
  .add(ui.divH([
688
744
  ui.divV([
689
- inputs.trivialNameCol.root,
690
- inputs.keepOriginal.root],
745
+ inputs.trivialName.root,
746
+ inputs.keepOriginal.root,
747
+ inputs.appendToTable.root],
691
748
  {style: {width: '50%'}}),
692
749
  ui.divV([
693
750
  ui.divH([inputs.toAtomicLevel.root, inputs.generateHelm.root]),
@@ -718,7 +775,7 @@ async function getPolyToolEnumerateDialog(
718
775
  placeholders: inputs.placeholders.stringValue,
719
776
  enumeratorType: inputs.enumeratorType.value,
720
777
  placeholdersBreadth: inputs.placeholdersBreadth.stringValue,
721
- trivialNameCol: inputs.trivialNameCol.stringValue,
778
+ trivialName: inputs.trivialName.value,
722
779
  keepOriginal: inputs.keepOriginal.value,
723
780
  toAtomicLevel: inputs.toAtomicLevel.value,
724
781
  generateHelm: inputs.generateHelm.value,
@@ -732,7 +789,8 @@ async function getPolyToolEnumerateDialog(
732
789
  inputs.enumeratorType.value = x.enumeratorType ?? PolyToolEnumeratorTypes.Single;
733
790
  inputs.placeholders.stringValue = x.placeholders;
734
791
  inputs.placeholdersBreadth.stringValue = x.placeholdersBreadth;
735
- inputs.trivialNameCol.stringValue = x.trivialNameCol;
792
+ if (x.trivialName)
793
+ inputs.trivialName.value = x.trivialName;
736
794
  inputs.keepOriginal.value = x.keepOriginal ?? false;
737
795
  inputs.toAtomicLevel.value = x.toAtomicLevel ?? true;
738
796
  inputs.generateHelm.value = x.generateHelm ?? true;
@@ -742,6 +800,7 @@ async function getPolyToolEnumerateDialog(
742
800
  setTimeout(() => {
743
801
  inputs.placeholders.invalidateGrid();
744
802
  inputs.placeholdersBreadth.invalidateGrid();
803
+ updateResultCount();
745
804
  }, 100);
746
805
  });
747
806
  return dialog;