@datagrok/sequence-translator 1.10.5 → 1.10.7
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/CHANGELOG.md +4 -0
- package/detectors.js +3 -1
- package/dist/455.js +1 -1
- package/dist/455.js.map +1 -1
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +9 -6
- package/scripts/build-monomer-lib.py +0 -1
- package/src/apps/common/view/components/colored-input/input-painters.ts +1 -1
- package/src/apps/structure/model/monomer-code-parser.ts +1 -1
- package/src/apps/translator/model/conversion-utils.ts +1 -1
- package/src/demo/demo-st-ui.ts +1 -1
- package/src/package-test.ts +1 -1
- package/src/package.g.ts +14 -0
- package/src/package.ts +27 -13
- package/src/polytool/pt-enumerate-seq-dialog.ts +54 -7
- package/src/polytool/pt-monomer-selection-dialog.ts +200 -0
- package/src/polytool/pt-placeholders-breadth-input.ts +39 -0
- package/src/polytool/pt-placeholders-input.ts +34 -0
- package/src/tests/files-tests.ts +1 -1
- package/src/tests/formats-support.ts +1 -1
- package/src/tests/formats-to-helm.ts +1 -1
- package/src/tests/helm-to-nucleotides.ts +1 -1
- package/src/tests/polytool-chain-from-notation-tests.ts +1 -1
- package/src/tests/polytool-chain-parse-notation-tests.ts +1 -1
- package/src/tests/polytool-convert-tests.ts +1 -1
- package/src/tests/polytool-detectors-custom-notation-test.ts +1 -1
- package/src/tests/polytool-enumerate-breadth-tests.ts +1 -1
- package/src/tests/polytool-enumerate-tests.ts +1 -1
- package/src/tests/polytool-unrule-tests.ts +1 -1
- package/src/tests/toAtomicLevel-tests.ts +1 -1
- package/src/tests/utils/detect-macromolecule-utils.ts +1 -1
- package/src/utils/cyclized.ts +8 -2
- package/test-console-output-1.log +207 -1594
- package/test-record-1.mp4 +0 -0
- package/webpack.config.js +22 -1
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.
|
|
4
|
+
"version": "1.10.7",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Davit Rizhinashvili",
|
|
7
7
|
"email": "drizhinashvili@datagrok.ai"
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@datagrok-libraries/bio": "^5.
|
|
25
|
+
"@datagrok-libraries/bio": "^5.61.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",
|
|
29
29
|
"@types/react": "^18.0.15",
|
|
30
30
|
"cash-dom": "^8.1.0",
|
|
31
|
-
"datagrok-api": "^1.
|
|
31
|
+
"datagrok-api": "^1.26.0",
|
|
32
32
|
"lodash": "^4.17.21",
|
|
33
33
|
"object-hash": "^3.0.0",
|
|
34
34
|
"openchemlib": "6.0.1",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"ts-loader": "^9.3.1",
|
|
37
37
|
"typeahead-standalone": "4.14.1",
|
|
38
38
|
"typescript": "^5.4.2",
|
|
39
|
-
"wu": "^2.1.0"
|
|
39
|
+
"wu": "^2.1.0",
|
|
40
|
+
"@datagrok-libraries/test": "^1.1.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@datagrok-libraries/helm-web-editor": "^1.1.16",
|
|
@@ -54,7 +55,6 @@
|
|
|
54
55
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
55
56
|
"@typescript-eslint/parser": "^7.2.0",
|
|
56
57
|
"css-loader": "^6.7.3",
|
|
57
|
-
"datagrok-tools": "^4.14.55",
|
|
58
58
|
"eslint": "^8.57.0",
|
|
59
59
|
"eslint-config-google": "^0.14.0",
|
|
60
60
|
"style-loader": "^3.3.1",
|
|
@@ -89,5 +89,8 @@
|
|
|
89
89
|
"canView": [
|
|
90
90
|
"All users"
|
|
91
91
|
],
|
|
92
|
-
"category": "Bioinformatics"
|
|
92
|
+
"category": "Bioinformatics",
|
|
93
|
+
"overrides": {
|
|
94
|
+
"datagrok-api": "$datagrok-api"
|
|
95
|
+
}
|
|
93
96
|
}
|
|
@@ -24,7 +24,7 @@ export function demoPainter(input: string): HTMLSpanElement[] {
|
|
|
24
24
|
return spans;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/* todo: port to another place */
|
|
28
28
|
export function highlightInvalidSubsequence(input: string, th: ITranslationHelper): HTMLSpanElement[] {
|
|
29
29
|
// validate sequence
|
|
30
30
|
let cutoff = 0;
|
|
@@ -84,7 +84,7 @@ export class MonomerSequenceParser {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
/* todo: to be eliminated after full helm support */
|
|
88
88
|
function monomerHasLeftPhosphateLinker(monomerSymbol: string): boolean {
|
|
89
89
|
return _package.jsonData.monomersWithPhosphate['left'].includes(monomerSymbol);
|
|
90
90
|
}
|
|
@@ -51,7 +51,7 @@ export function getNucleotidesSequence(helmString: string, monomerLib: MonomerLi
|
|
|
51
51
|
return nucleotides;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
/* todo: remove after refactoring as a workaround */
|
|
55
55
|
export function convert(
|
|
56
56
|
sequence: string, sourceFormat: string, targetFormat: string, th: ITranslationHelper
|
|
57
57
|
): string | null {
|
package/src/demo/demo-st-ui.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as grok from 'datagrok-api/grok';
|
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
|
-
import {delay} from '@datagrok-libraries/
|
|
5
|
+
import {delay} from '@datagrok-libraries/test/src/test';
|
|
6
6
|
import {PackageFunctions} from '../package';
|
|
7
7
|
import {tryCatch} from '../apps/common/model/helpers';
|
|
8
8
|
|
package/src/package-test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as grok from 'datagrok-api/grok';
|
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
|
-
import {runTests, tests, TestContext, initAutoTests as initTests} from '@datagrok-libraries/
|
|
5
|
+
import {runTests, tests, TestContext, initAutoTests as initTests} from '@datagrok-libraries/test/src/test';
|
|
6
6
|
|
|
7
7
|
import './tests/formats-to-helm';
|
|
8
8
|
import './tests/helm-to-nucleotides';
|
package/src/package.g.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as DG from 'datagrok-api/dg';
|
|
|
4
4
|
//name: Oligo Toolkit
|
|
5
5
|
//tags: app
|
|
6
6
|
//output: view result
|
|
7
|
+
//meta.role: app
|
|
7
8
|
//meta.icon: img/icons/toolkit.png
|
|
8
9
|
//meta.browsePath: Peptides | Oligo Toolkit
|
|
9
10
|
export async function oligoToolkitApp() : Promise<any> {
|
|
@@ -11,6 +12,7 @@ export async function oligoToolkitApp() : Promise<any> {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
//tags: init
|
|
15
|
+
//meta.role: init
|
|
14
16
|
export async function init() : Promise<void> {
|
|
15
17
|
await PackageFunctions.init();
|
|
16
18
|
}
|
|
@@ -18,6 +20,7 @@ export async function init() : Promise<void> {
|
|
|
18
20
|
//name: Oligo Translator
|
|
19
21
|
//tags: app
|
|
20
22
|
//output: view result
|
|
23
|
+
//meta.role: app
|
|
21
24
|
//meta.icon: img/icons/translator.png
|
|
22
25
|
//meta.browsePath: Peptides | Oligo Toolkit
|
|
23
26
|
export async function oligoTranslatorApp() : Promise<any> {
|
|
@@ -27,6 +30,7 @@ export async function oligoTranslatorApp() : Promise<any> {
|
|
|
27
30
|
//name: Oligo Pattern
|
|
28
31
|
//tags: app
|
|
29
32
|
//output: view result
|
|
33
|
+
//meta.role: app
|
|
30
34
|
//meta.icon: img/icons/pattern.png
|
|
31
35
|
//meta.browsePath: Peptides | Oligo Toolkit
|
|
32
36
|
export async function oligoPatternApp() : Promise<any> {
|
|
@@ -36,6 +40,7 @@ export async function oligoPatternApp() : Promise<any> {
|
|
|
36
40
|
//name: Oligo Structure
|
|
37
41
|
//tags: app
|
|
38
42
|
//output: view result
|
|
43
|
+
//meta.role: app
|
|
39
44
|
//meta.icon: img/icons/structure.png
|
|
40
45
|
//meta.browsePath: Peptides | Oligo Toolkit
|
|
41
46
|
export async function oligoStructureApp() : Promise<any> {
|
|
@@ -113,6 +118,7 @@ export async function polyToolConvertTopMenu() : Promise<void> {
|
|
|
113
118
|
//tags: editor
|
|
114
119
|
//input: funccall call
|
|
115
120
|
//output: column result
|
|
121
|
+
//meta.role: editor
|
|
116
122
|
export async function getPolyToolConvertEditor(call: DG.FuncCall) : Promise<any> {
|
|
117
123
|
return await PackageFunctions.getPolyToolConvertEditor(call);
|
|
118
124
|
}
|
|
@@ -157,6 +163,7 @@ export async function createMonomerLibraryForPolyTool(file: DG.FileInfo) : Promi
|
|
|
157
163
|
//tags: app
|
|
158
164
|
//meta.icon: img/icons/structure.png
|
|
159
165
|
//meta.browsePath: Peptides | PolyTool
|
|
166
|
+
//meta.role: app
|
|
160
167
|
export async function ptEnumeratorHelmApp() : Promise<void> {
|
|
161
168
|
await PackageFunctions.ptEnumeratorHelmApp();
|
|
162
169
|
}
|
|
@@ -165,6 +172,7 @@ export async function ptEnumeratorHelmApp() : Promise<void> {
|
|
|
165
172
|
//tags: app
|
|
166
173
|
//meta.icon: img/icons/structure.png
|
|
167
174
|
//meta.browsePath: Peptides | PolyTool
|
|
175
|
+
//meta.role: app
|
|
168
176
|
export async function ptEnumeratorChemApp() : Promise<void> {
|
|
169
177
|
await PackageFunctions.ptEnumeratorChemApp();
|
|
170
178
|
}
|
|
@@ -213,3 +221,9 @@ export async function getPolyToolCombineDialog() : Promise<void> {
|
|
|
213
221
|
export function applyNotationProviderForCyclized(col: DG.Column<any>, separator: string) : void {
|
|
214
222
|
PackageFunctions.applyNotationProviderForCyclized(col, separator);
|
|
215
223
|
}
|
|
224
|
+
|
|
225
|
+
//output: dynamic result
|
|
226
|
+
//meta.role: notationProviderConstructor
|
|
227
|
+
export async function harmonizedSequenceNotationProviderConstructor() : Promise<any> {
|
|
228
|
+
return await PackageFunctions.harmonizedSequenceNotationProviderConstructor();
|
|
229
|
+
}
|
package/src/package.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as grok from 'datagrok-api/grok';
|
|
|
3
3
|
import * as ui from 'datagrok-api/ui';
|
|
4
4
|
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
|
-
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
6
|
+
import {NOTATION, NOTATION_PROVIDER_CONSTRUCTOR_ROLE} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
7
7
|
import {SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
8
8
|
|
|
9
9
|
import {OligoToolkitPackage} from './apps/common/model/oligo-toolkit-package';
|
|
@@ -79,7 +79,8 @@ export class PackageFunctions {
|
|
|
79
79
|
@grok.decorators.app({
|
|
80
80
|
icon: 'img/icons/toolkit.png',
|
|
81
81
|
browsePath: 'Peptides | Oligo Toolkit',
|
|
82
|
-
name: 'Oligo Toolkit'
|
|
82
|
+
name: 'Oligo Toolkit',
|
|
83
|
+
tags: ['app']
|
|
83
84
|
})
|
|
84
85
|
static async oligoToolkitApp(): Promise<DG.ViewBase> {
|
|
85
86
|
await _package.initLibData();
|
|
@@ -92,7 +93,7 @@ export class PackageFunctions {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
|
|
95
|
-
@grok.decorators.init()
|
|
96
|
+
@grok.decorators.init({tags: ['init']})
|
|
96
97
|
static async init(): Promise<void> {
|
|
97
98
|
if (initSequenceTranslatorPromise === null)
|
|
98
99
|
_package.startInit(initSequenceTranslatorPromise = initSequenceTranslatorInt());
|
|
@@ -103,7 +104,8 @@ export class PackageFunctions {
|
|
|
103
104
|
@grok.decorators.app({
|
|
104
105
|
icon: 'img/icons/translator.png',
|
|
105
106
|
browsePath: 'Peptides | Oligo Toolkit',
|
|
106
|
-
name: 'Oligo Translator'
|
|
107
|
+
name: 'Oligo Translator',
|
|
108
|
+
tags: ['app']
|
|
107
109
|
})
|
|
108
110
|
static async oligoTranslatorApp(): Promise<DG.ViewBase> {
|
|
109
111
|
const view = await getSpecifiedAppView(APP_NAME.TRANSLATOR);
|
|
@@ -114,7 +116,8 @@ export class PackageFunctions {
|
|
|
114
116
|
@grok.decorators.app({
|
|
115
117
|
icon: 'img/icons/pattern.png',
|
|
116
118
|
browsePath: 'Peptides | Oligo Toolkit',
|
|
117
|
-
name: 'Oligo Pattern'
|
|
119
|
+
name: 'Oligo Pattern',
|
|
120
|
+
tags: ['app']
|
|
118
121
|
})
|
|
119
122
|
static async oligoPatternApp(): Promise<DG.ViewBase> {
|
|
120
123
|
const view = await getSpecifiedAppView(APP_NAME.PATTERN);
|
|
@@ -125,7 +128,8 @@ export class PackageFunctions {
|
|
|
125
128
|
@grok.decorators.app({
|
|
126
129
|
icon: 'img/icons/structure.png',
|
|
127
130
|
browsePath: 'Peptides | Oligo Toolkit',
|
|
128
|
-
name: 'Oligo Structure'
|
|
131
|
+
name: 'Oligo Structure',
|
|
132
|
+
tags: ['app']
|
|
129
133
|
})
|
|
130
134
|
static async oligoStructureApp(): Promise<DG.ViewBase> {
|
|
131
135
|
const view = await getSpecifiedAppView(APP_NAME.STRUCTURE);
|
|
@@ -229,7 +233,7 @@ export class PackageFunctions {
|
|
|
229
233
|
await polyToolConvertUI();
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
@grok.decorators.editor()
|
|
236
|
+
@grok.decorators.editor({tags: ['editor']})
|
|
233
237
|
static async getPolyToolConvertEditor(
|
|
234
238
|
call: DG.FuncCall): Promise<DG.Column<string> | null> {
|
|
235
239
|
const funcEditor = await PolyToolConvertFuncEditor.create(call);
|
|
@@ -296,10 +300,11 @@ export class PackageFunctions {
|
|
|
296
300
|
@grok.decorators.func({
|
|
297
301
|
meta: {
|
|
298
302
|
icon: 'img/icons/structure.png',
|
|
299
|
-
browsePath: 'Peptides | PolyTool'
|
|
303
|
+
browsePath: 'Peptides | PolyTool',
|
|
304
|
+
role: 'app'
|
|
300
305
|
},
|
|
301
|
-
|
|
302
|
-
|
|
306
|
+
name: 'HELM Enumerator',
|
|
307
|
+
tags: ['app']
|
|
303
308
|
})
|
|
304
309
|
static async ptEnumeratorHelmApp(): Promise<void> {
|
|
305
310
|
await polyToolEnumerateHelmUI();
|
|
@@ -309,10 +314,11 @@ export class PackageFunctions {
|
|
|
309
314
|
@grok.decorators.func({
|
|
310
315
|
meta: {
|
|
311
316
|
icon: 'img/icons/structure.png',
|
|
312
|
-
browsePath: 'Peptides | PolyTool'
|
|
317
|
+
browsePath: 'Peptides | PolyTool',
|
|
318
|
+
role: 'app'
|
|
313
319
|
},
|
|
314
|
-
|
|
315
|
-
|
|
320
|
+
name: 'Chem Enumerator',
|
|
321
|
+
tags: ['app']
|
|
316
322
|
})
|
|
317
323
|
static async ptEnumeratorChemApp(): Promise<void> {
|
|
318
324
|
polyToolEnumerateChemUI();
|
|
@@ -394,6 +400,14 @@ export class PackageFunctions {
|
|
|
394
400
|
col.tags[PolyToolTags.dataRole] = 'template';
|
|
395
401
|
col.temp[SeqTemps.notationProvider] = new CyclizedNotationProvider(separator, _package.helmHelper);
|
|
396
402
|
}
|
|
403
|
+
|
|
404
|
+
@grok.decorators.func({
|
|
405
|
+
name: 'harmonizedSequenceNotationProviderConstructor',
|
|
406
|
+
meta: {role: 'notationProviderConstructor'}
|
|
407
|
+
})
|
|
408
|
+
static async harmonizedSequenceNotationProviderConstructor(): Promise<typeof CyclizedNotationProvider> {
|
|
409
|
+
return CyclizedNotationProvider;
|
|
410
|
+
}
|
|
397
411
|
}
|
|
398
412
|
|
|
399
413
|
//name: getSpecifiedAppView
|
|
@@ -19,11 +19,13 @@ import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
|
|
|
19
19
|
import {InputColumnBase} from '@datagrok-libraries/bio/src/types/input';
|
|
20
20
|
import {SeqValueBase} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
21
21
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
22
|
+
import {SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
22
23
|
|
|
23
24
|
import {PolyToolEnumeratorParams, PolyToolEnumeratorType, PolyToolEnumeratorTypes} from './types';
|
|
24
25
|
import {getLibrariesList, LIB_PATH} from './utils';
|
|
25
26
|
import {doPolyToolEnumerateHelm, PT_HELM_EXAMPLE} from './pt-enumeration-helm';
|
|
26
27
|
import {PolyToolPlaceholdersInput} from './pt-placeholders-input';
|
|
28
|
+
import {showMonomerSelectionDialog} from './pt-monomer-selection-dialog';
|
|
27
29
|
import {defaultErrorHandler} from '../utils/err-info';
|
|
28
30
|
import {PolyToolPlaceholdersBreadthInput} from './pt-placeholders-breadth-input';
|
|
29
31
|
import {PT_UI_DIALOG_ENUMERATION, PT_UI_GET_HELM, PT_UI_HIGHLIGHT_MONOMERS, PT_UI_RULES_USED, PT_UI_USE_CHIRALITY} from './const';
|
|
@@ -32,11 +34,13 @@ import {RuleInputs, RULES_PATH, RULES_STORAGE_NAME} from './conversion/pt-rules'
|
|
|
32
34
|
import {Chain} from './conversion/pt-chain';
|
|
33
35
|
import {polyToolConvert} from './pt-dialog';
|
|
34
36
|
|
|
35
|
-
import {_package, PackageFunctions} from '../package';
|
|
37
|
+
import {_package, applyNotationProviderForCyclized, PackageFunctions} from '../package';
|
|
36
38
|
import {buildMonomerHoverLink} from '@datagrok-libraries/bio/src/monomer-works/monomer-hover';
|
|
37
39
|
import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
|
|
38
40
|
|
|
39
41
|
import {PolymerTypes} from '@datagrok-libraries/js-draw-lite/src/types/org';
|
|
42
|
+
import { CyclizedNotationProvider } from '../utils/cyclized';
|
|
43
|
+
import { INotationProvider } from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
40
44
|
|
|
41
45
|
type PolyToolEnumerateInputs = {
|
|
42
46
|
macromolecule: HelmInputBase;
|
|
@@ -161,9 +165,22 @@ async function getPolyToolEnumerateDialog(
|
|
|
161
165
|
const getValue = (cell?: DG.Cell): [SeqValueBase, PolyToolDataRole] => {
|
|
162
166
|
let resSeqValue: SeqValueBase;
|
|
163
167
|
let resDataRole: PolyToolDataRole;
|
|
168
|
+
let resHelm: string | null = null;
|
|
164
169
|
if (cell && cell.rowIndex >= 0 && cell?.column.semType == DG.SEMTYPE.MACROMOLECULE) {
|
|
165
170
|
const sh = seqHelper.getSeqHandler(cell.column);
|
|
166
171
|
resSeqValue = sh.getValue(cell.rowIndex);
|
|
172
|
+
// if (cell.column.temp?.[SeqTemps.notationProvider])
|
|
173
|
+
if (cell.column.temp?.[SeqTemps.notationProvider] && !(cell.column.temp[SeqTemps.notationProvider] instanceof CyclizedNotationProvider)) {
|
|
174
|
+
const notationProvider = cell.column.temp[SeqTemps.notationProvider] as INotationProvider;
|
|
175
|
+
resHelm = notationProvider.getHelm(resSeqValue.value, {});
|
|
176
|
+
// create temp helm column to apply notation provider for cyclized sequences
|
|
177
|
+
let seqCol: DG.Column;
|
|
178
|
+
DG.DataFrame.fromColumns([seqCol = DG.Column.fromList(DG.COLUMN_TYPE.STRING, 'seq', [resHelm])]);
|
|
179
|
+
seqCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
180
|
+
seqCol.meta.units = NOTATION.HELM;
|
|
181
|
+
const sh = seqHelper.getSeqHandler(seqCol);
|
|
182
|
+
resSeqValue = sh.getValue(0);
|
|
183
|
+
}
|
|
167
184
|
resDataRole = (resSeqValue.tags[PolyToolTags.dataRole] as PolyToolDataRole.template) ?? PolyToolDataRole.macromolecule;
|
|
168
185
|
} else {
|
|
169
186
|
const seqCol = DG.Column.fromList(DG.COLUMN_TYPE.STRING, 'seq', [PT_HELM_EXAMPLE]);
|
|
@@ -200,8 +217,13 @@ async function getPolyToolEnumerateDialog(
|
|
|
200
217
|
if (aa.T === 'ATOM') {
|
|
201
218
|
try {
|
|
202
219
|
if (!seqValue.isDna() && !seqValue.isRna()) {
|
|
203
|
-
|
|
204
|
-
|
|
220
|
+
if (cell?.column?.temp?.[SeqTemps.notationProvider] instanceof CyclizedNotationProvider) {
|
|
221
|
+
const canonicalSymbol = seqValue.getSplitted().getCanonical(aa.bio!.continuousId - 1);
|
|
222
|
+
return monomerLibFuncs.getMonomer(aa.bio!.type, canonicalSymbol);
|
|
223
|
+
} else {
|
|
224
|
+
const canonicalSymbol = aa.elem;
|
|
225
|
+
return monomerLibFuncs.getMonomer(aa.bio!.type, canonicalSymbol);
|
|
226
|
+
}
|
|
205
227
|
} else {
|
|
206
228
|
const canonicalSymbol = seqValue.getSplittedWithSugarsAndPhosphates().getCanonical(aa.bio!.continuousId - 1);
|
|
207
229
|
return monomerLibFuncs.getMonomer(aa.bio!.type, canonicalSymbol);
|
|
@@ -273,6 +295,30 @@ async function getPolyToolEnumerateDialog(
|
|
|
273
295
|
inputs.library.root.style.setProperty('display', 'none');
|
|
274
296
|
inputs.trivialNameCol.addOptions(trivialNameSampleDiv);
|
|
275
297
|
|
|
298
|
+
// Wire up monomer cell double-click to open selection dialog
|
|
299
|
+
inputs.placeholders.onMonomerCellEdit = async (position: number, currentMonomers: string[]) => {
|
|
300
|
+
const mol = inputs.macromolecule.molValue;
|
|
301
|
+
if (position < 0 || position >= mol.atoms.length)
|
|
302
|
+
return null;
|
|
303
|
+
const atom = mol.atoms[position];
|
|
304
|
+
const helmType: HelmType = atom.biotype()!;
|
|
305
|
+
const polymerType = helmTypeToPolymerType(helmType);
|
|
306
|
+
return showMonomerSelectionDialog(monomerLib, polymerType, currentMonomers);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Wire up breadth monomer cell double-click to open selection dialog
|
|
310
|
+
inputs.placeholdersBreadth.onMonomerCellEdit = async (
|
|
311
|
+
start: number, _end: number, currentMonomers: string[],
|
|
312
|
+
) => {
|
|
313
|
+
const mol = inputs.macromolecule.molValue;
|
|
314
|
+
if (start < 0 || start >= mol.atoms.length)
|
|
315
|
+
return null;
|
|
316
|
+
const atom = mol.atoms[start];
|
|
317
|
+
const helmType: HelmType = atom.biotype()!;
|
|
318
|
+
const polymerType = helmTypeToPolymerType(helmType);
|
|
319
|
+
return showMonomerSelectionDialog(monomerLib, polymerType, currentMonomers);
|
|
320
|
+
};
|
|
321
|
+
|
|
276
322
|
let placeholdersValidity: string | null = null;
|
|
277
323
|
inputs.placeholders.addValidator((value: string): string | null => {
|
|
278
324
|
const errors: string[] = [];
|
|
@@ -475,7 +521,7 @@ async function getPolyToolEnumerateDialog(
|
|
|
475
521
|
const cell = grok.shell.tv.dataFrame.currentCell;
|
|
476
522
|
if (cell.column.semType !== DG.SEMTYPE.MACROMOLECULE) return;
|
|
477
523
|
|
|
478
|
-
[seqValue, dataRole] = getValue();
|
|
524
|
+
[seqValue, dataRole] = getValue(cell);
|
|
479
525
|
fillForCurrentCell(seqValue, dataRole, cell);
|
|
480
526
|
}));
|
|
481
527
|
|
|
@@ -554,9 +600,9 @@ async function getPolyToolEnumerateDialog(
|
|
|
554
600
|
const exec = async (): Promise<void> => {
|
|
555
601
|
try {
|
|
556
602
|
const srcHelm = inputs.macromolecule.stringValue;
|
|
557
|
-
const helmSelections
|
|
603
|
+
const helmSelections = wu.enumerate<HelmAtom>(inputs.macromolecule.molValue.atoms)
|
|
558
604
|
.filter(([a, aI]) => a.highlighted)
|
|
559
|
-
.map(([a, aI]) =>
|
|
605
|
+
.map(([a, aI]) => a).toArray();
|
|
560
606
|
if (inputs.enumeratorType.value === PolyToolEnumeratorTypes.Library) {
|
|
561
607
|
if (helmSelections.length === 0) {
|
|
562
608
|
grok.shell.warning('PolyTool: position for enumeration was not selected');
|
|
@@ -595,7 +641,8 @@ async function getPolyToolEnumerateDialog(
|
|
|
595
641
|
grok.shell.warning(`Monomer Library '${monLibName}' was not found`);
|
|
596
642
|
return;
|
|
597
643
|
}
|
|
598
|
-
const
|
|
644
|
+
const polymerType = helmTypeToPolymerType(helmSelections[0].biotype() ?? 'HELM_AA');
|
|
645
|
+
const peptideMonomers = monLib.getMonomerSymbolsByType(polymerType);
|
|
599
646
|
placeHoldersValue[0].monomers = peptideMonomers;
|
|
600
647
|
enumerationType = PolyToolEnumeratorTypes.Single;
|
|
601
648
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import {HelmType, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
6
|
+
import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
7
|
+
import {polymerTypeToHelmType} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
|
|
8
|
+
|
|
9
|
+
const MAX_SUGGESTIONS = 20;
|
|
10
|
+
|
|
11
|
+
/** Shows a dialog for selecting monomers with autocomplete and tag-based display.
|
|
12
|
+
* @returns comma-separated monomer symbols, or null if cancelled */
|
|
13
|
+
export async function showMonomerSelectionDialog(
|
|
14
|
+
monomerLib: IMonomerLib, polymerType: PolymerType, presetMonomers?: string[],
|
|
15
|
+
): Promise<string[] | null> {
|
|
16
|
+
return new Promise<string[] | null>((resolve) => {
|
|
17
|
+
const helmType: HelmType = polymerTypeToHelmType(polymerType);
|
|
18
|
+
const allSymbols = monomerLib.getMonomerSymbolsByType(polymerType);
|
|
19
|
+
|
|
20
|
+
const selectedMonomers: string[] = presetMonomers ? [...presetMonomers] : [];
|
|
21
|
+
|
|
22
|
+
const tagsHost = ui.div([], {style: {display: 'flex', flexWrap: 'wrap', gap: '4px', marginTop: '8px', maxWidth: '400px'}});
|
|
23
|
+
const input = ui.input.string('Monomers', {value: ''});
|
|
24
|
+
const inputEl = input.input as HTMLInputElement;
|
|
25
|
+
inputEl.setAttribute('autocomplete', 'off');
|
|
26
|
+
inputEl.placeholder = 'Type to search...';
|
|
27
|
+
|
|
28
|
+
let currentMenu: DG.Menu | null = null;
|
|
29
|
+
let menuItems: HTMLElement[] = [];
|
|
30
|
+
let highlightedIndex = -1;
|
|
31
|
+
|
|
32
|
+
function renderTags(): void {
|
|
33
|
+
tagsHost.innerHTML = '';
|
|
34
|
+
for (const symbol of selectedMonomers) {
|
|
35
|
+
const removeBtn = ui.iconFA('times', () => {
|
|
36
|
+
const idx = selectedMonomers.indexOf(symbol);
|
|
37
|
+
if (idx >= 0) {
|
|
38
|
+
selectedMonomers.splice(idx, 1);
|
|
39
|
+
renderTags();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
removeBtn.style.marginLeft = '4px';
|
|
43
|
+
removeBtn.style.cursor = 'pointer';
|
|
44
|
+
|
|
45
|
+
const tag = ui.div([ui.span([symbol]), removeBtn], {
|
|
46
|
+
style: {
|
|
47
|
+
display: 'inline-flex', alignItems: 'center',
|
|
48
|
+
padding: '2px 6px', borderRadius: '4px',
|
|
49
|
+
backgroundColor: 'var(--grey-2)', border: '1px solid var(--grey-3)',
|
|
50
|
+
fontSize: '12px', cursor: 'default',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
// Tooltip on hover
|
|
54
|
+
tag.addEventListener('mouseenter', (e) => {
|
|
55
|
+
const tooltip = monomerLib.getTooltip(helmType, symbol);
|
|
56
|
+
ui.tooltip.show(tooltip, tag.getBoundingClientRect().left, tag.getBoundingClientRect().bottom + 16);
|
|
57
|
+
});
|
|
58
|
+
tag.addEventListener('mouseleave', () => { ui.tooltip.hide(); });
|
|
59
|
+
|
|
60
|
+
tagsHost.appendChild(tag);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function addMonomer(symbol: string): void {
|
|
65
|
+
if (!selectedMonomers.includes(symbol)) {
|
|
66
|
+
selectedMonomers.push(symbol);
|
|
67
|
+
renderTags();
|
|
68
|
+
}
|
|
69
|
+
inputEl.value = '';
|
|
70
|
+
hideMenu();
|
|
71
|
+
inputEl.focus();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hideMenu(): void {
|
|
75
|
+
if (currentMenu) {
|
|
76
|
+
currentMenu.hide();
|
|
77
|
+
currentMenu = null;
|
|
78
|
+
}
|
|
79
|
+
menuItems = [];
|
|
80
|
+
highlightedIndex = -1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getSuggestions(query: string): {symbol: string, monomer: Monomer | null}[] {
|
|
84
|
+
const q = query.toLowerCase();
|
|
85
|
+
const results: {symbol: string, monomer: Monomer | null, rank: number}[] = [];
|
|
86
|
+
|
|
87
|
+
for (const symbol of allSymbols) {
|
|
88
|
+
if (selectedMonomers.includes(symbol))
|
|
89
|
+
continue;
|
|
90
|
+
const monomer = monomerLib.getMonomer(polymerType, symbol);
|
|
91
|
+
const symLower = symbol.toLowerCase();
|
|
92
|
+
const nameLower = monomer?.name?.toLowerCase() ?? '';
|
|
93
|
+
|
|
94
|
+
if (symLower.startsWith(q))
|
|
95
|
+
results.push({symbol, monomer, rank: 0});
|
|
96
|
+
else if (symLower.includes(q))
|
|
97
|
+
results.push({symbol, monomer, rank: 1});
|
|
98
|
+
else if (nameLower.includes(q))
|
|
99
|
+
results.push({symbol, monomer, rank: 2});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
results.sort((a, b) => a.rank - b.rank || a.symbol.localeCompare(b.symbol));
|
|
103
|
+
return results.slice(0, MAX_SUGGESTIONS);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function showSuggestions(): void {
|
|
107
|
+
hideMenu();
|
|
108
|
+
const query = inputEl.value.trim();
|
|
109
|
+
if (!query)
|
|
110
|
+
return;
|
|
111
|
+
|
|
112
|
+
const suggestions = getSuggestions(query);
|
|
113
|
+
if (suggestions.length === 0)
|
|
114
|
+
return;
|
|
115
|
+
|
|
116
|
+
currentMenu = DG.Menu.popup();
|
|
117
|
+
const maxElement = suggestions.reduce((max, s) => {
|
|
118
|
+
const label = s.monomer?.name ? `${s.symbol} - ${s.monomer.name}` : s.symbol;
|
|
119
|
+
if (max.length < label.length)
|
|
120
|
+
return label;
|
|
121
|
+
return max;
|
|
122
|
+
}, '');
|
|
123
|
+
currentMenu.item(maxElement, () => {}); // Dummy item to set menu width
|
|
124
|
+
|
|
125
|
+
const causedBy = new MouseEvent('mousemove', {clientX: inputEl.getBoundingClientRect().left, clientY: inputEl.getBoundingClientRect().bottom});
|
|
126
|
+
currentMenu.show({causedBy: causedBy,
|
|
127
|
+
y: inputEl.offsetHeight + inputEl.offsetTop, x: inputEl.offsetLeft});
|
|
128
|
+
|
|
129
|
+
// collect menu items for keyboard navigation
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
currentMenu?.clear();
|
|
132
|
+
for (const s of suggestions) {
|
|
133
|
+
const label = s.monomer?.name ? `${s.symbol} - ${s.monomer.name}` : s.symbol;
|
|
134
|
+
currentMenu?.item(label, () => { addMonomer(s.symbol); });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const menuRoot = document.querySelector('.d4-menu-popup:last-of-type');
|
|
138
|
+
if (menuRoot)
|
|
139
|
+
menuItems = Array.from(menuRoot.querySelectorAll('.d4-menu-item')) as HTMLElement[];
|
|
140
|
+
|
|
141
|
+
highlightedIndex = -1;
|
|
142
|
+
}, 0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function updateHighlight(newIndex: number): void {
|
|
146
|
+
if (menuItems.length === 0)
|
|
147
|
+
return;
|
|
148
|
+
if (highlightedIndex >= 0 && highlightedIndex < menuItems.length)
|
|
149
|
+
menuItems[highlightedIndex].classList.remove('d4-menu-item-hover');
|
|
150
|
+
highlightedIndex = newIndex;
|
|
151
|
+
if (highlightedIndex >= 0 && highlightedIndex < menuItems.length) {
|
|
152
|
+
menuItems[highlightedIndex].classList.add('d4-menu-item-hover');
|
|
153
|
+
menuItems[highlightedIndex].scrollIntoView({block: 'nearest'});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
inputEl.addEventListener('input', () => { showSuggestions(); });
|
|
158
|
+
|
|
159
|
+
inputEl.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
160
|
+
if (e.key === 'ArrowDown') {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
if (menuItems.length > 0) {
|
|
163
|
+
const next = (highlightedIndex + 1) % menuItems.length;
|
|
164
|
+
updateHighlight(next);
|
|
165
|
+
}
|
|
166
|
+
} else if (e.key === 'ArrowUp') {
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
if (menuItems.length > 0) {
|
|
169
|
+
const prev = (highlightedIndex - 1 + menuItems.length) % menuItems.length;
|
|
170
|
+
updateHighlight(prev);
|
|
171
|
+
}
|
|
172
|
+
} else if (e.key === 'Enter') {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
e.stopPropagation();
|
|
175
|
+
if (highlightedIndex >= 0 && highlightedIndex < menuItems.length) {
|
|
176
|
+
menuItems[highlightedIndex].click();
|
|
177
|
+
} else {
|
|
178
|
+
// If input exactly matches a symbol, add it directly
|
|
179
|
+
const val = inputEl.value.trim();
|
|
180
|
+
if (val && allSymbols.includes(val))
|
|
181
|
+
addMonomer(val);
|
|
182
|
+
}
|
|
183
|
+
} else if (e.key === 'Escape') {
|
|
184
|
+
hideMenu();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
renderTags();
|
|
189
|
+
|
|
190
|
+
const dlg = ui.dialog({title: 'Select Monomers', showFooter: true})
|
|
191
|
+
.add(ui.div([input.root, tagsHost], {style: {minWidth: '350px', minHeight: '200px'}}))
|
|
192
|
+
.onOK(() => { resolve(selectedMonomers); })
|
|
193
|
+
.onCancel(() => { resolve(null); })
|
|
194
|
+
.show({resizable: true});
|
|
195
|
+
// dlg.root.addEventListener('close', () => { hideMenu(); });
|
|
196
|
+
|
|
197
|
+
inputEl.focus();
|
|
198
|
+
setTimeout(() => { showSuggestions(); }, 0);
|
|
199
|
+
});
|
|
200
|
+
}
|