@datagrok/sequence-translator 1.3.14 → 1.3.15

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/dist/package-test.js +1 -1
  3. package/dist/package-test.js.map +1 -1
  4. package/dist/package.js +1 -1
  5. package/dist/package.js.map +1 -1
  6. package/files/polytool-rules/rules_example.json +34 -34
  7. package/files/samples/cyclized.csv +6 -0
  8. package/package.json +9 -10
  9. package/src/apps/common/view/components/colored-input/colored-text-input.ts +2 -2
  10. package/src/apps/pattern/view/components/bulk-convert/column-input.ts +2 -2
  11. package/src/apps/pattern/view/components/bulk-convert/table-input.ts +8 -6
  12. package/src/apps/pattern/view/components/edit-block-controls.ts +5 -5
  13. package/src/apps/pattern/view/components/load-block-controls.ts +7 -3
  14. package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +1 -1
  15. package/src/apps/pattern/view/components/strand-editor/header-controls.ts +2 -2
  16. package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +2 -2
  17. package/src/apps/pattern/view/components/terminal-modification-editor.ts +1 -1
  18. package/src/apps/structure/view/ui.ts +5 -5
  19. package/src/apps/translator/view/ui.ts +27 -18
  20. package/src/package-test.ts +1 -0
  21. package/src/package.ts +34 -12
  22. package/src/polytool/pt-conversion.ts +2 -33
  23. package/src/polytool/pt-convert-editor.ts +116 -0
  24. package/src/polytool/pt-dialog.ts +177 -97
  25. package/src/polytool/pt-enumeration-helm-dialog.ts +338 -282
  26. package/src/polytool/pt-enumeration-helm.ts +6 -2
  27. package/src/polytool/pt-placeholders-input.ts +1 -2
  28. package/src/polytool/utils.ts +0 -7
  29. package/src/tests/polytool-convert-tests.ts +99 -0
  30. package/src/tests/polytool-enumerate-tests.ts +21 -5
  31. package/src/utils/context-menu.ts +7 -10
@@ -0,0 +1,116 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+ import * as DG from 'datagrok-api/dg';
4
+ import {_package} from '../package';
5
+ import {defaultErrorHandler} from '../utils/err-info';
6
+ import {PT_UI_DIALOG_CONVERSION, PT_UI_RULES_USED} from './const';
7
+ import {RuleInputs, RULES_PATH, RULES_STORAGE_NAME} from './pt-rules';
8
+
9
+ /** Inputs of polyToolConvert2 package function */
10
+ export enum P {
11
+ table = 'table',
12
+ seqCol = 'seqCol',
13
+ generateHelm = 'generateHelm',
14
+ chiralityEngine = 'chiralityEngine',
15
+ rules = 'rules'
16
+ }
17
+
18
+ type PolyToolConvertInputs = {
19
+ table: DG.InputBase<DG.DataFrame | null>;
20
+ seqCol: DG.InputBase<DG.Column | null>;
21
+ generateHelm: DG.InputBase<boolean>;
22
+ chiralityEngine: DG.InputBase<boolean>;
23
+ rules: { header: HTMLElement, form: HTMLDivElement };
24
+ }
25
+
26
+ export class PolyToolConvertFuncEditor {
27
+ private inputs: PolyToolConvertInputs;
28
+ private readonly ruleInputs: RuleInputs;
29
+
30
+ protected constructor(
31
+ private readonly call: DG.FuncCall,
32
+ ) {
33
+ this.ruleInputs = new RuleInputs(RULES_PATH, RULES_STORAGE_NAME, '.json');
34
+ }
35
+
36
+ private async initInputs(): Promise<void> {
37
+ const getParam = (pName: string) => this.call.inputParams[pName];
38
+
39
+ this.inputs = {
40
+ table: (() => {
41
+ const p = getParam(P.table);
42
+ return ui.input.table(p.property.caption, {value: p.value});
43
+ })(),
44
+ seqCol: (() => {
45
+ const p = getParam(P.seqCol);
46
+ return ui.input.column(p.property.caption, {value: p.value, table: p.value.dataFrame});
47
+ })(),
48
+ generateHelm: ui.input.forProperty(getParam(P.generateHelm).property),
49
+ chiralityEngine: ui.input.forProperty(getParam(P.chiralityEngine).property),
50
+ rules: {
51
+ header: ui.inlineText([PT_UI_RULES_USED]),
52
+ form: await this.ruleInputs.getForm(),
53
+ }
54
+ };
55
+ }
56
+
57
+ static async create(call: DG.FuncCall): Promise<PolyToolConvertFuncEditor> {
58
+ const editor = new PolyToolConvertFuncEditor(call);
59
+ await editor.initInputs();
60
+ return editor;
61
+ }
62
+
63
+ // -- Params --
64
+
65
+ private async getParams(): Promise<{ [p: string]: any }> {
66
+ return {
67
+ table: this.inputs.table.value!,
68
+ seqCol: this.inputs.seqCol.value!,
69
+ generateHelm: this.inputs.generateHelm.value,
70
+ chiralityEngine: this.inputs.chiralityEngine.value,
71
+ rules: await this.ruleInputs.getActive(),
72
+ };
73
+ }
74
+
75
+ public async showDialog(): Promise<DG.Column<string>> {
76
+ const formDiv = ui.div([
77
+ this.inputs.table,
78
+ this.inputs.seqCol,
79
+ this.inputs.generateHelm,
80
+ this.inputs.chiralityEngine,
81
+ this.inputs.rules.header,
82
+ this.inputs.rules.form,
83
+ ],
84
+ {style: {minWidth: '320px'}});
85
+
86
+ return new Promise((resolve, reject) => {
87
+ ui.dialog({title: PT_UI_DIALOG_CONVERSION})
88
+ .add(formDiv)
89
+ .onOK(async () => {
90
+ const callParams = await this.getParams();
91
+ const res = (await this.call.func.prepare(callParams).call(true)).getOutputParamValue() as DG.Column<string>;
92
+ resolve(res);
93
+ })
94
+ .onCancel(() => { reject(new Error('Cancelled by user')); })
95
+ .show();
96
+ });
97
+ }
98
+
99
+ // -- UI --
100
+
101
+ public widget(): DG.Widget {
102
+ throw new Error('not implemented');
103
+
104
+ // const inputsForm = ui.inputs(Object.entries(this.inputs)
105
+ // .filter(([inputName, _input]) => !['table', 'sequence'].includes(inputName))
106
+ // .map(([_inputName, input]) => input));
107
+ // const doBtn = ui.button('Convert', () => {
108
+ // (async () => {
109
+ // const callParams = this.getParams();
110
+ // await this.call.func.prepare(callParams).call(true);
111
+ // })()
112
+ // .catch((err: any) => { defaultErrorHandler(err, true); });
113
+ // });
114
+ // return DG.Widget.fromRoot(ui.divV([inputsForm, ui.div(doBtn)]));
115
+ }
116
+ }
@@ -4,16 +4,19 @@ import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import $ from 'cash-dom';
7
+ import {Unsubscribable} from 'rxjs';
7
8
 
8
9
  import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
10
+ import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
11
+ import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
12
+ import {getSeqHelper, ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
9
13
 
10
- import {RuleInputs, RULES_PATH, RULES_STORAGE_NAME} from './pt-rules';
11
- import {addTransformedColumn} from './pt-conversion';
12
-
13
- import {handleError} from './utils';
14
+ import {getRules, RuleInputs, RULES_PATH, RULES_STORAGE_NAME} from './pt-rules';
15
+ import {doPolyToolConvert} from './pt-conversion';
14
16
  import {defaultErrorHandler} from '../utils/err-info';
15
17
  import {getLibrariesList} from './utils';
16
18
  import {getEnumerationChem, PT_CHEM_EXAMPLE} from './pt-enumeration-chem';
19
+
17
20
  import {
18
21
  PT_ERROR_DATAFRAME, PT_UI_ADD_HELM, PT_UI_DIALOG_CONVERSION, PT_UI_DIALOG_ENUMERATION,
19
22
  PT_UI_GET_HELM, PT_UI_RULES_USED, PT_UI_USE_CHIRALITY, PT_WARNING_COLUMN
@@ -31,100 +34,126 @@ export function polyToolEnumerateChemUI(cell?: DG.Cell): void {
31
34
  });
32
35
  }
33
36
 
34
- export async function getPolyToolConversionDialog(targetCol?: DG.Column): Promise<DG.Dialog> {
35
- const targetColumns = grok.shell.t.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
36
- if (!targetColumns)
37
- throw new Error(PT_ERROR_DATAFRAME);
38
-
39
- const targetColumnInput = ui.input.column('Column', {
40
- table: grok.shell.t, value: targetColumns[0],
41
- filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE
42
- });
43
-
44
- targetColumnInput.value = targetCol ? targetCol : targetColumnInput.value;
45
-
46
- const generateHelmChoiceInput = ui.input.bool(PT_UI_GET_HELM, {value: true});
47
- ui.tooltip.bind(generateHelmChoiceInput.root, PT_UI_ADD_HELM);
48
-
49
- const chiralityEngineInput = ui.input.bool(PT_UI_USE_CHIRALITY, {value: false});
50
- const ruleInputs = new RuleInputs(RULES_PATH, RULES_STORAGE_NAME, '.json');
51
- const rulesHeader = ui.inlineText([PT_UI_RULES_USED]);
52
- ui.tooltip.bind(rulesHeader, 'Add or specify rules to use');
53
- const rulesForm = await ruleInputs.getForm();
54
-
55
- const div = ui.div([
56
- targetColumnInput,
57
- generateHelmChoiceInput,
58
- chiralityEngineInput,
59
- rulesHeader,
60
- rulesForm
61
- ]);
62
-
63
- const dialog = ui.dialog(PT_UI_DIALOG_CONVERSION)
64
- .add(div)
65
- .onOK(async () => {
66
- const pi = DG.TaskBarProgressIndicator.create('PolyTool converting');
67
- try {
68
- const sequencesCol = targetColumnInput.value;
69
- if (!sequencesCol) {
70
- grok.shell.warning(PT_WARNING_COLUMN);
71
- return;
72
- }
37
+ export async function polyToolConvertUI(): Promise<void> {
38
+ let dialog: DG.Dialog;
39
+ try {
40
+ dialog = await getPolyToolConvertDialog();
41
+ dialog.show();
42
+ } catch (err: any) {
43
+ const [errMsg, errStack] = errInfo(err);
44
+ grok.shell.warning('To run PolyTool Conversion, open a dataframe with macromolecules');
45
+ _package.logger.error(errMsg, undefined, errStack);
46
+ }
47
+ }
48
+
49
+ export async function getPolyToolConvertDialog(targetCol?: DG.Column): Promise<DG.Dialog> {
50
+ const subs: Unsubscribable[] = [];
51
+ const destroy = () => {
52
+ for (const sub of subs) sub.unsubscribe();
53
+ };
54
+ try {
55
+ const targetColumns = grok.shell.t.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
56
+ if (!targetColumns)
57
+ throw new Error(PT_ERROR_DATAFRAME);
58
+
59
+ const targetColumnInput = ui.input.column('Column', {
60
+ table: grok.shell.t, value: targetColumns[0],
61
+ filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE
62
+ });
63
+
64
+ targetColumnInput.value = targetCol ? targetCol : targetColumnInput.value;
65
+
66
+ const generateHelmChoiceInput = ui.input.bool(PT_UI_GET_HELM, {value: true});
67
+ ui.tooltip.bind(generateHelmChoiceInput.root, PT_UI_ADD_HELM);
73
68
 
74
- const files = await ruleInputs.getActive();
69
+ const chiralityEngineInput = ui.input.bool(PT_UI_USE_CHIRALITY, {value: false});
70
+ const ruleInputs = new RuleInputs(RULES_PATH, RULES_STORAGE_NAME, '.json');
71
+ const rulesHeader = ui.inlineText([PT_UI_RULES_USED]);
72
+ ui.tooltip.bind(rulesHeader, 'Add or specify rules to use');
73
+ const rulesForm = await ruleInputs.getForm();
75
74
 
76
- addTransformedColumn(sequencesCol!,
77
- generateHelmChoiceInput.value!,
78
- files,
79
- chiralityEngineInput.value!);
75
+ const div = ui.div([
76
+ targetColumnInput,
77
+ generateHelmChoiceInput,
78
+ chiralityEngineInput,
79
+ rulesHeader,
80
+ rulesForm
81
+ ]);
82
+
83
+
84
+ const exec = async (): Promise<void> => {
85
+ try {
86
+ const ruleFileList = await ruleInputs.getActive();
87
+ await polyToolConvert(targetColumnInput.value!, generateHelmChoiceInput.value!, chiralityEngineInput.value!, ruleFileList);
80
88
  } catch (err: any) {
81
- handleError(err);
82
- } finally {
83
- pi.close();
89
+ defaultErrorHandler(err);
84
90
  }
85
- });
86
-
87
- return dialog;
91
+ };
92
+
93
+ const dialog = ui.dialog(PT_UI_DIALOG_CONVERSION)
94
+ .add(div)
95
+ .onOK(() => { exec(); });
96
+ subs.push(dialog.onClose.subscribe(() => {
97
+ destroy();
98
+ }));
99
+
100
+ return dialog;
101
+ } catch (err: any) {
102
+ destroy(); // on failing to build a dialog
103
+ throw err;
104
+ }
88
105
  }
89
106
 
90
107
  async function getPolyToolEnumerationChemDialog(cell?: DG.Cell): Promise<DG.Dialog> {
91
- const [libList, helmHelper] = await Promise.all([
92
- getLibrariesList(), getHelmHelper()]);
93
-
94
- let molValue = PT_CHEM_EXAMPLE;//cell ? cell.value : PT_CHEM_EXAMPLE;
95
- const molInput = new DG.chem.Sketcher(DG.chem.SKETCHER_MODE.EXTERNAL);
96
- molInput.syncCurrentObject = false;
97
- // sketcher.setMolFile(col.tags[ALIGN_BY_SCAFFOLD_TAG]);
98
- molInput.onChanged.subscribe((_: any) => {
99
- molValue = molInput.getMolFile();
100
- });
101
- molInput.root.classList.add('ui-input-editor');
102
- molInput.root.style.marginTop = '3px';
103
- molInput.setMolFile(molValue);
104
-
105
- //const helmInput = helmHelper.createHelmInput('Macromolecule', {value: helmValue});
106
- const screenLibrary = ui.input.choice('Library to use', {value: null, items: libList});
107
-
108
- molInput.root.setAttribute('style', `min-width:250px!important;`);
109
- molInput.root.setAttribute('style', `max-width:250px!important;`);
110
- screenLibrary.input.setAttribute('style', `min-width:250px!important;`);
111
-
112
- const div = ui.div([
113
- molInput.root,
114
- screenLibrary.root
115
- ]);
116
-
117
- const cccSubs = grok.events.onCurrentCellChanged.subscribe(() => {
118
- const cell = grok.shell.tv.dataFrame.currentCell;
119
-
120
- if (cell.column.semType === DG.SEMTYPE.MOLECULE)
121
- molInput.setValue(cell.value);
122
- });
123
-
124
- // Displays the molecule from a current cell (monitors changes)
125
- const dialog = ui.dialog(PT_UI_DIALOG_ENUMERATION)
126
- .add(div)
127
- .onOK(async () => {
108
+ const subs: Unsubscribable[] = [];
109
+ const destroy = () => {
110
+ for (const sub of subs) sub.unsubscribe();
111
+ };
112
+ try {
113
+
114
+ const [libList, helmHelper] = await Promise.all([
115
+ getLibrariesList(), getHelmHelper()]);
116
+
117
+ const molStr = (cell && cell.rowIndex >= 0) ? cell.value : PT_CHEM_EXAMPLE;//cell ? cell.value : PT_CHEM_EXAMPLE;
118
+ let molfileValue: string = await (async (): Promise<string> => {
119
+ if (DG.chem.isMolBlock(molStr)) return molStr;
120
+ return (await grok.functions.call('Chem:convertMolNotation', {
121
+ molecule: molStr,
122
+ sourceNotation: cell?.column.getTag(DG.TAGS.UNITS) ?? DG.chem.Notation.Unknown,
123
+ targetNotation: DG.chem.Notation.MolBlock,
124
+ }));
125
+ })();
126
+
127
+ const molInput = new DG.chem.Sketcher(DG.chem.SKETCHER_MODE.EXTERNAL);
128
+ molInput.syncCurrentObject = false;
129
+ // sketcher.setMolFile(col.tags[ALIGN_BY_SCAFFOLD_TAG]);
130
+ molInput.onChanged.subscribe((_: any) => {
131
+ molfileValue = molInput.getMolFile();
132
+ });
133
+ molInput.root.classList.add('ui-input-editor');
134
+ molInput.root.style.marginTop = '3px';
135
+ molInput.setMolFile(molfileValue);
136
+
137
+ //const helmInput = helmHelper.createHelmInput('Macromolecule', {value: helmValue});
138
+ const screenLibrary = ui.input.choice('Library to use', {value: null, items: libList});
139
+
140
+ molInput.root.setAttribute('style', `min-width:250px!important;`);
141
+ molInput.root.setAttribute('style', `max-width:250px!important;`);
142
+ screenLibrary.input.setAttribute('style', `min-width:250px!important;`);
143
+
144
+ const div = ui.div([
145
+ molInput.root,
146
+ screenLibrary.root
147
+ ]);
148
+
149
+ subs.push(grok.events.onCurrentCellChanged.subscribe(() => {
150
+ const cell = grok.shell.tv.dataFrame.currentCell;
151
+
152
+ if (cell.column.semType === DG.SEMTYPE.MOLECULE)
153
+ molInput.setValue(cell.value);
154
+ }));
155
+
156
+ const exec = async (): Promise<void> => {
128
157
  try {
129
158
  const molString = molInput.getMolFile();
130
159
 
@@ -140,12 +169,63 @@ async function getPolyToolEnumerationChemDialog(cell?: DG.Cell): Promise<DG.Dial
140
169
  }
141
170
  } catch (err: any) {
142
171
  defaultErrorHandler(err);
143
- } finally {
144
- cccSubs.unsubscribe();
145
172
  }
146
- }).onCancel(() => {
147
- cccSubs.unsubscribe();
148
- });
173
+ };
174
+
175
+ // Displays the molecule from a current cell (monitors changes)
176
+ const dialog = ui.dialog(PT_UI_DIALOG_ENUMERATION)
177
+ .add(div)
178
+ .onOK(() => {
179
+ exec().finally(() => { destroy(); });
180
+ })
181
+ .onCancel(() => {
182
+ destroy();
183
+ });
184
+ subs.push(dialog.onClose.subscribe(() => {
185
+ destroy();
186
+ }));
187
+ return dialog;
188
+ } catch (err: any) {
189
+ destroy();
190
+ throw err;
191
+ }
192
+ }
149
193
 
150
- return dialog;
194
+ /** Returns Helm and molfile columns. */
195
+ export async function polyToolConvert(
196
+ seqCol: DG.Column<string>, generateHelm: boolean, chiralityEngine: boolean, ruleFiles: string[]
197
+ ): Promise<[DG.Column, DG.Column]> {
198
+ const pi = DG.TaskBarProgressIndicator.create('PolyTool converting...');
199
+ try {
200
+ const getUnusedName = (df: DG.DataFrame | undefined, colName: string): string => {
201
+ if (!df) return colName;
202
+ return df.columns.getUnusedName(colName);
203
+ };
204
+ await getHelmHelper(); // initializes JSDraw and org
205
+
206
+ const table = seqCol.dataFrame;
207
+ const rules = await getRules(ruleFiles);
208
+ const resList = doPolyToolConvert(seqCol.toList(), rules);
209
+
210
+ const resHelmColName = getUnusedName(table, `transformed(${seqCol.name})`);
211
+ const resHelmCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, resHelmColName, resList.length)
212
+ .init((rowIdx: number) => { return resList[rowIdx];});
213
+ resHelmCol.semType = DG.SEMTYPE.MACROMOLECULE;
214
+ resHelmCol.meta.units = NOTATION.HELM;
215
+ resHelmCol.setTag(DG.TAGS.CELL_RENDERER, 'helm');
216
+ if (generateHelm && table) table.columns.add(resHelmCol, true);
217
+
218
+ const seqHelper: ISeqHelper = await getSeqHelper();
219
+ const toAtomicLevelRes = await seqHelper.helmToAtomicLevel(resHelmCol, chiralityEngine, /* highlight */ generateHelm);
220
+ const resMolCol = toAtomicLevelRes.molCol;
221
+ resMolCol.name = getUnusedName(table, `molfile(${seqCol.name})`);
222
+ resMolCol.semType = DG.SEMTYPE.MOLECULE;
223
+ if (table) {
224
+ table.columns.add(resMolCol, true);
225
+ await grok.data.detectSemanticTypes(table);
226
+ }
227
+ return [resHelmCol, resMolCol];
228
+ } finally {
229
+ pi.close();
230
+ }
151
231
  }