@datagrok/sequence-translator 1.4.4 → 1.4.6
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 +24 -0
- package/detectors.js +24 -0
- package/dist/455.js +2 -0
- package/dist/455.js.map +1 -0
- 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/files/polytool-rules/rules_example.json +2 -2
- package/files/samples/HELM.csv +6 -0
- package/files/samples/cyclized.csv +4 -3
- package/files/samples/cyclized_MSA.csv +5 -0
- package/package.json +13 -13
- package/src/apps/common/model/oligo-toolkit-package.ts +23 -4
- package/src/apps/common/view/components/molecule-img.ts +2 -2
- package/src/apps/pattern/model/data-manager.ts +9 -9
- package/src/apps/translator/view/ui.ts +8 -8
- package/src/consts.ts +12 -0
- package/src/global.d.ts +13 -0
- package/src/package-test.ts +3 -0
- package/src/package.ts +32 -5
- package/src/polytool/pt-conversion.ts +395 -81
- package/src/polytool/pt-dialog.ts +29 -13
- package/src/polytool/pt-enumeration-helm-dialog.ts +79 -34
- package/src/polytool/pt-enumeration-helm.ts +12 -8
- package/src/polytool/pt-placeholders-breadth-input.ts +4 -4
- package/src/polytool/pt-placeholders-input.ts +6 -6
- package/src/polytool/pt-unrule-dialog.ts +7 -3
- package/src/polytool/pt-unrule.ts +4 -3
- package/src/polytool/types.ts +4 -4
- package/src/tests/polytool-chain-from-notation-tests.ts +108 -18
- package/src/tests/polytool-chain-parse-notation-tests.ts +100 -0
- package/src/tests/polytool-convert-tests.ts +102 -25
- package/src/tests/polytool-detectors-custom-notation-test.ts +43 -0
- package/src/tests/polytool-enumerate-breadth-tests.ts +4 -4
- package/src/tests/toAtomicLevel-tests.ts +1 -1
- package/src/tests/utils/detect-macromolecule-utils.ts +64 -0
- package/src/tests/{utils.ts → utils/index.ts} +2 -2
- package/src/utils/context-menu.ts +2 -5
- package/src/utils/cyclized.ts +88 -0
- package/src/utils/dimerized.ts +10 -0
|
@@ -5,41 +5,131 @@ import * as DG from 'datagrok-api/dg';
|
|
|
5
5
|
import {before, after, category, expect, test, expectArray, testEvent, delay} from '@datagrok-libraries/utils/src/test';
|
|
6
6
|
import {Chain} from '../polytool/pt-conversion';
|
|
7
7
|
import {getRules} from '../polytool/pt-rules';
|
|
8
|
+
import {getHelmHelper, IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
9
|
+
|
|
10
|
+
category('PolyTool: Chain', () => {
|
|
11
|
+
let helmHelper: IHelmHelper;
|
|
12
|
+
|
|
13
|
+
before(async () => {
|
|
14
|
+
helmHelper = await getHelmHelper();
|
|
15
|
+
});
|
|
8
16
|
|
|
9
|
-
category('PolyTool: Chain: fromNotation', () => {
|
|
10
17
|
const tests = {
|
|
11
18
|
'cyclized': {
|
|
12
|
-
|
|
19
|
+
data: {
|
|
20
|
+
templateSeq: 'R-F-C(1)-T-G-H-F-Y-P-C(1)-meI',
|
|
21
|
+
templateHelm: 'PEPTIDE1{R.F.[C(1)].T.G.H.F.Y.P.[C(1)].[meI]}$$$$V2.0',
|
|
22
|
+
mmHelm: 'PEPTIDE1{R.F.C.T.G.H.F.Y.P.C.[meI]}$PEPTIDE1,PEPTIDE1,3:R3-10:R3$$$V2.0',
|
|
23
|
+
},
|
|
13
24
|
tgt: {
|
|
14
|
-
monomerCount: [11], linkageCount:
|
|
15
|
-
|
|
25
|
+
templateChain: {monomerCount: [11], linkageCount: 0},
|
|
26
|
+
mmChain: {monomerCount: [11], linkageCount: 1,}
|
|
16
27
|
},
|
|
17
28
|
},
|
|
18
29
|
'reaction1': {
|
|
19
|
-
|
|
30
|
+
data: {
|
|
31
|
+
templateSeq: 'R-F-azG(4)-T-G-H-F-Y-P-aG(4)-meI',
|
|
32
|
+
templateHelm: 'PEPTIDE1{R.F.[azG(4)].T.G.H.F.Y.P.[aG(4)].[meI]}$$$$V2.0',
|
|
33
|
+
mmHelm: 'PEPTIDE1{R.F.[GGaz].T.G.H.F.Y.P}|PEPTIDE2{[meI]}$PEPTIDE1,PEPTIDE1,3:R3-9:R2|PEPTIDE1,PEPTIDE2,3:R4-1:R1$$$V2.0',
|
|
34
|
+
},
|
|
20
35
|
tgt: {
|
|
21
|
-
monomerCount: [
|
|
22
|
-
|
|
36
|
+
templateChain: {monomerCount: [11], linkageCount: 0,},
|
|
37
|
+
mmChain: {monomerCount: [9, 1], linkageCount: 2,}
|
|
23
38
|
}
|
|
24
39
|
},
|
|
25
40
|
'reaction2': {
|
|
26
|
-
|
|
41
|
+
data: {
|
|
42
|
+
templateSeq: 'R-F-aG(4)-T-G-H-F-Y-P-azG(4)-meI',
|
|
43
|
+
templateHelm: 'PEPTIDE1{R.F.[aG(4)].T.G.H.F.Y.P.[azG(4)].[meI]}$$$$V2.0',
|
|
44
|
+
mmHelm: 'PEPTIDE1{R.F}|PEPTIDE2{T.G.H.F.Y.P.[GGaz].[meI]}$PEPTIDE1,PEPTIDE2,2:R2-7:R3|PEPTIDE2,PEPTIDE2,1:R1-7:R4,$$$V2.0',
|
|
45
|
+
},
|
|
46
|
+
tgt: {
|
|
47
|
+
templateChain: {monomerCount: [11], linkageCount: 0,},
|
|
48
|
+
mmChain: {monomerCount: [2, 8], linkageCount: 2,}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
'dimerized1': {
|
|
52
|
+
data: {
|
|
53
|
+
templateSeq: '(#3)Succ-{A(CHOL)-F-C(1)-T-G-H-Y-P-C(1)-NH2}',
|
|
54
|
+
templateHelm: 'PEPTIDE1{[(#3)Succ]}' + '|' +
|
|
55
|
+
'PEPTIDE2{[A(CHOL)].F.[C(1)].T.G.H.Y.P.[C(1)].[NH2]}' + '$' +
|
|
56
|
+
'PEPTIDE1,PEPTIDE2,1:R1-1:R1' + '$$$' + 'V2.0',
|
|
57
|
+
mmHelm: 'PEPTIDE1{[Succ].[A(CHOL)].F.C.T.G.H.Y.P.C.[NH2]}' + '|' +
|
|
58
|
+
'PEPTIDE2{[A(CHOL)].F.C.T.G.H.Y.P.C.[NH2]}' + '$' +
|
|
59
|
+
'PEPTIDE1,PEPTIDE2,1:R1-1:R1' + '|' +
|
|
60
|
+
'PEPTIDE1,PEPTIDE1,4:R3-10:R3' + '|' +
|
|
61
|
+
'PEPTIDE2,PEPTIDE2,3:R3-9:R3' + '$$$V2.0',
|
|
62
|
+
},
|
|
63
|
+
tgt: {
|
|
64
|
+
templateChain: {monomerCount: [1, 10], linkageCount: 1},
|
|
65
|
+
mmChain: {monomerCount: [11, 10], linkageCount: 3,}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
'dimerized2': {
|
|
69
|
+
data: {
|
|
70
|
+
templateSeq: '($3)Succ-{R-F-C(1)-T-G-H-F-P-C(1)-NH2}($3){A(CHOL)-F-C(1)-Y-H-G-D-N-C(1)-meI}',
|
|
71
|
+
templateHelm: 'PEPTIDE1{[($3)Succ]}' + '|' +
|
|
72
|
+
'PEPTIDE2{R.F.[C(1)].T.G.H.F.P.[C(1)].[NH2]}' + '|' +
|
|
73
|
+
'PEPTIDE3{[($3)A(CHOL)].F.[C(1)].Y.H.G.D.N.[C(1)].[meI]}' + '$' +
|
|
74
|
+
'PEPTIDE1,PEPTIDE2,1:R1-1:R1' + '$$$' + 'V2.0',
|
|
75
|
+
mmHelm: 'PEPTIDE1{[Succ].R.F.C.T.G.H.F.P.C.[NH2]}' + '|' +
|
|
76
|
+
'PEPTIDE2{[A(CHOL)].F.C.Y.H.G.D.N.C.[meI]}' + '$' +
|
|
77
|
+
'PEPTIDE1,PEPTIDE2,1:R1-1:R1' + '|' +
|
|
78
|
+
'PEPTIDE1,PEPTIDE1,4:R3-10:R3' + '|' +
|
|
79
|
+
'PEPTIDE2,PEPTIDE2,3:R3-9:R3' + '$$$V2.0',
|
|
80
|
+
},
|
|
27
81
|
tgt: {
|
|
28
|
-
|
|
29
|
-
monomerCount: [
|
|
30
|
-
helm: 'PEPTIDE1{R.F}|PEPTIDE2{T.G.H.F.Y.P.[GGaz].[meI]}$PEPTIDE1,PEPTIDE2,2:R2-7:R3|PEPTIDE2,PEPTIDE2,1:R1-7:R4,$$$',
|
|
82
|
+
templateChain: {monomerCount: [1, 10, 10], linkageCount: 1,},
|
|
83
|
+
mmChain: {monomerCount: [11, 10], linkageCount: 3,}
|
|
31
84
|
}
|
|
32
85
|
}
|
|
33
|
-
|
|
34
86
|
};
|
|
35
87
|
|
|
36
|
-
for (const [testName,
|
|
37
|
-
test(
|
|
88
|
+
for (const [testName, {data, tgt}] of Object.entries(tests)) {
|
|
89
|
+
test(`fromNotation-${testName}`, async () => {
|
|
38
90
|
const rules = await getRules(['rules_example.json']);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
expect(
|
|
91
|
+
const resMmChain = Chain.fromNotation(data.templateSeq, rules, helmHelper);
|
|
92
|
+
resMmChain.check(true);
|
|
93
|
+
expectArray(resMmChain.monomers.map((mL) => mL.length), tgt.mmChain.monomerCount);
|
|
94
|
+
expect(resMmChain.linkages.length, tgt.mmChain.linkageCount);
|
|
95
|
+
expect(resMmChain.getHelm(), data.mmHelm);
|
|
43
96
|
}, testName == 'reaction2' ? {skipReason: 'reverse reaction'} : undefined);
|
|
44
97
|
}
|
|
98
|
+
|
|
99
|
+
for (const [testName, {data, tgt}] of Object.entries(tests)) {
|
|
100
|
+
test(`parseNotation-${testName}`, async () => {
|
|
101
|
+
const rules = await getRules(['rules_example.json']);
|
|
102
|
+
const resTemplateChain = Chain.parseNotation(data.templateSeq, helmHelper);
|
|
103
|
+
resTemplateChain.check(true);
|
|
104
|
+
expectArray(resTemplateChain.monomers.map((mL) => mL.length), tgt.templateChain.monomerCount);
|
|
105
|
+
expect(resTemplateChain.linkages.length, tgt.templateChain.linkageCount);
|
|
106
|
+
expect(resTemplateChain.getHelm(), data.templateHelm);
|
|
107
|
+
expect(resTemplateChain.getNotation(), data.templateSeq);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const [testName, {data, tgt}] of Object.entries(tests)) {
|
|
112
|
+
test(`parseHelm-${testName}`, async () => {
|
|
113
|
+
const rules = await getRules(['rules_example.json']);
|
|
114
|
+
const resTemplateChain = Chain.parseHelm(data.templateHelm, helmHelper);
|
|
115
|
+
resTemplateChain.check(true);
|
|
116
|
+
expectArray(resTemplateChain.monomers.map((mL) => mL.length), tgt.templateChain.monomerCount);
|
|
117
|
+
expect(resTemplateChain.linkages.length, tgt.templateChain.linkageCount);
|
|
118
|
+
expect(resTemplateChain.getHelm(), data.templateHelm);
|
|
119
|
+
expect(resTemplateChain.getNotation(), data.templateSeq);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const [testName, {data, tgt}] of Object.entries(tests)) {
|
|
124
|
+
test(`applyRules-${testName}`, async () => {
|
|
125
|
+
const rules = await getRules(['rules_example.json']);
|
|
126
|
+
const resTemplateChain = Chain.parseNotation(data.templateSeq, helmHelper);
|
|
127
|
+
const resMmChain = resTemplateChain.applyRules(rules);
|
|
128
|
+
resMmChain.check(true);
|
|
129
|
+
expectArray(resMmChain.monomers.map((mL) => mL.length), tgt.mmChain.monomerCount);
|
|
130
|
+
expect(resMmChain.linkages.length, tgt.mmChain.linkageCount);
|
|
131
|
+
expect(resMmChain.getHelm(), data.mmHelm);
|
|
132
|
+
}, {skipReason: 'applyRules is not implemented'});
|
|
133
|
+
}
|
|
134
|
+
|
|
45
135
|
});
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
|
|
5
|
+
import {before, after, category, expect, test, expectArray, testEvent, delay} from '@datagrok-libraries/utils/src/test';
|
|
6
|
+
import {Chain, getInnerIdx, getOuterIdx} from '../polytool/pt-conversion';
|
|
7
|
+
import {getRules} from '../polytool/pt-rules';
|
|
8
|
+
import {getHelmHelper, IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
9
|
+
|
|
10
|
+
category('PolyTool: Chain: parseNotation', () => {
|
|
11
|
+
let helmHelper: IHelmHelper;
|
|
12
|
+
|
|
13
|
+
before(async () => {
|
|
14
|
+
helmHelper = await getHelmHelper(); // initialize JSDraw2 and org
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const tests = {
|
|
18
|
+
'cyclized': {
|
|
19
|
+
src: {seq: 'R-F-C(1)-T-G-H-F-Y-P-C(1)-meI'},
|
|
20
|
+
tgt: {
|
|
21
|
+
//monomerCount: [11], linkageCount: 1,
|
|
22
|
+
helm: 'PEPTIDE1{R.F.[C(1)].T.G.H.F.Y.P.[C(1)].[meI]}$$$$V2.0',
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
'dimerized1': {
|
|
26
|
+
src: {seq: '(#3)Succ-{A(CHOL)-F-C(1)-T-G-H-Y-P-C(1)-NH2}'},
|
|
27
|
+
tgt: {
|
|
28
|
+
// TODO: Target test data requires clarification
|
|
29
|
+
//monomerCount: [2, 8], linkageCount: 0,
|
|
30
|
+
helm: 'PEPTIDE1{[(#3)Succ]}' + '|' +
|
|
31
|
+
'PEPTIDE2{[A(CHOL)].F.[C(1)].T.G.H.Y.P.[C(1)].[NH2]}' + '$' +
|
|
32
|
+
'PEPTIDE1,PEPTIDE2,1:R1-1:R1' + '$$$' + 'V2.0',
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
'dimerized2': {
|
|
36
|
+
src: {seq: '($3)Succ-{R-F-C(1)-T-G-H-F-P-C(1)-NH2}($2){A(CHOL)-F-C(1)-T-G-H-F-P-C(1)-NH2}'},
|
|
37
|
+
tgt: {
|
|
38
|
+
// TODO: Target test data requires clarification
|
|
39
|
+
//monomerCount: [2, 8], linkageCount: 0,
|
|
40
|
+
helm: 'PEPTIDE1{[($3)Succ]}' + '|' +
|
|
41
|
+
'PEPTIDE2{R.F.[C(1)].T.G.H.F.P.[C(1)].[NH2]}' + '|' +
|
|
42
|
+
'PEPTIDE3{[($2)A(CHOL)].F.[C(1)].T.G.H.F.P.[C(1)].[NH2]}' + '$' +
|
|
43
|
+
'PEPTIDE1,PEPTIDE2,1:R1-1:R1' + '$$$' + 'V2.0',
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for (const [testName, testData] of Object.entries(tests)) {
|
|
49
|
+
test(`${testName}`, async () => {
|
|
50
|
+
const rules = await getRules(['rules_example.json']);
|
|
51
|
+
const resChain = await Chain.parseNotation(testData.src.seq, helmHelper);
|
|
52
|
+
//expectArray(resChain.monomers.map((mL) => mL.length), testData.tgt.monomerCount);
|
|
53
|
+
//expect(resChain.linkages.length, testData.tgt.linkageCount);
|
|
54
|
+
// expect(resChain.getNotationHelm(), testData.tgt.helm);
|
|
55
|
+
// expect(resChain.getNotation(), testData.src.seq);
|
|
56
|
+
|
|
57
|
+
const resMol = resChain.mol!;
|
|
58
|
+
const hwe = helmHelper.createHelmWebEditor();
|
|
59
|
+
hwe.editor.setMol(resMol!);
|
|
60
|
+
const resMolHelm = hwe.editor.getHelm();
|
|
61
|
+
|
|
62
|
+
const resHelm = resChain.getNotationHelm();
|
|
63
|
+
|
|
64
|
+
expect(resMolHelm, testData.tgt.helm);
|
|
65
|
+
expect(resHelm, testData.tgt.helm);
|
|
66
|
+
}, testName == 'reaction2' ? {skipReason: 'reverse reaction'} : undefined);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const innerIdxTests = {
|
|
70
|
+
'0-in-4-4-3': {inIdx: 0, spIdx: 0, outIdx: 0, monomers: [['0', '1', '2', '3'], ['4', '5', '6', '7'], ['8', '9', '10']]},
|
|
71
|
+
'3-in-4-4-3': {inIdx: 3, spIdx: 0, outIdx: 3, monomers: [['0', '1', '2', '3'], ['4', '5', '6', '7'], ['8', '9', '10']]},
|
|
72
|
+
'4-in-4-4-3': {inIdx: 0, spIdx: 1, outIdx: 4, monomers: [['0', '1', '2', '3'], ['4', '5', '6', '7'], ['8', '9', '10']]},
|
|
73
|
+
'7-in-4-4-3': {inIdx: 3, spIdx: 1, outIdx: 7, monomers: [['0', '1', '2', '3'], ['4', '5', '6', '7'], ['8', '9', '10']]},
|
|
74
|
+
'8-in-4-4-3': {inIdx: 0, spIdx: 2, outIdx: 8, monomers: [['0', '1', '2', '3'], ['4', '5', '6', '7'], ['8', '9', '10']]},
|
|
75
|
+
// '11-in-4-4-3': {inIdx: 0, spIdx: 3, outIdx: 11, monomers: [['0', '1', '2', '3'], ['4', '5', '6', '7'], ['8', '9', '10']]},
|
|
76
|
+
// '12-in-4-4-3': {inIdx: 1, spIdx: 3, outIdx: 12, monomers: [['0', '1', '2', '3'], ['4', '5', '6', '7'], ['8', '9', '10']]},
|
|
77
|
+
'0-in-1-1-6-3': {inIdx: 0, spIdx: 0, outIdx: 0, monomers: [['0'], ['1'], ['2', '3', '4', '5', '6', '7'], ['8', '9', '10']]},
|
|
78
|
+
'1-in-1-1-6-3': {inIdx: 0, spIdx: 1, outIdx: 1, monomers: [['0'], ['1'], ['2', '3', '4', '5', '6', '7'], ['8', '9', '10']]},
|
|
79
|
+
'2-in-1-1-6-3': {inIdx: 0, spIdx: 2, outIdx: 2, monomers: [['0'], ['1'], ['2', '3', '4', '5', '6', '7'], ['8', '9', '10']]},
|
|
80
|
+
'3-in-1-1-6-3': {inIdx: 1, spIdx: 2, outIdx: 3, monomers: [['0'], ['1'], ['2', '3', '4', '5', '6', '7'], ['8', '9', '10']]},
|
|
81
|
+
'7-in-1-1-6-3': {inIdx: 5, spIdx: 2, outIdx: 7, monomers: [['0'], ['1'], ['2', '3', '4', '5', '6', '7'], ['8', '9', '10']]},
|
|
82
|
+
'8-in-1-1-6-3': {inIdx: 0, spIdx: 3, outIdx: 8, monomers: [['0'], ['1'], ['2', '3', '4', '5', '6', '7'], ['8', '9', '10']]},
|
|
83
|
+
// '11-in-1-1-6-3': {inIdx: 0, spIdx: 4}, src: {outIdx: 11, monomers: [['0'], ['1'], ['2', '3', '4', '5', '6', '7'], ['8', '9', '10']]}},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
for (const [testName, {inIdx, spIdx, outIdx, monomers}] of Object.entries(innerIdxTests)) {
|
|
87
|
+
test(`innerIdx-${testName}`, async () => {
|
|
88
|
+
const [resInIdx, resSpIdx] = getInnerIdx(outIdx, monomers);
|
|
89
|
+
expect(resInIdx, inIdx);
|
|
90
|
+
expect(resSpIdx, spIdx);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [testName, {inIdx, spIdx, outIdx, monomers}] of Object.entries(innerIdxTests)) {
|
|
95
|
+
test(`outerIdx-${testName}`, async () => {
|
|
96
|
+
const resOutIdx = getOuterIdx(inIdx, spIdx, monomers);
|
|
97
|
+
expect(resOutIdx, outIdx);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
@@ -2,25 +2,35 @@ 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 {before, after, category, expect, test, expectArray, testEvent, delay} from '@datagrok-libraries/utils/src/test';
|
|
5
|
+
import {before, after, category, expect, test, expectArray, testEvent, delay, expectObject} from '@datagrok-libraries/utils/src/test';
|
|
6
6
|
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
7
7
|
import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
|
|
8
8
|
import {
|
|
9
9
|
getUserLibSettings, setUserLibSettings
|
|
10
10
|
} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
|
|
11
11
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
12
|
+
import {getSeqHelper, ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
13
|
+
import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
|
|
14
|
+
import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
|
|
15
|
+
import {getHelmHelper, IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
12
16
|
|
|
13
|
-
import {doPolyToolConvert} from '../polytool/pt-conversion';
|
|
17
|
+
import {doPolyToolConvert, getOverriddenLibrary} from '../polytool/pt-conversion';
|
|
14
18
|
import {getRules} from '../polytool/pt-rules';
|
|
15
19
|
|
|
16
|
-
import {_package} from '../package-test';
|
|
17
20
|
|
|
21
|
+
import {_package} from '../package-test';
|
|
18
22
|
|
|
19
23
|
category('PolyTool: Convert', () => {
|
|
24
|
+
let helmHelper: IHelmHelper;
|
|
25
|
+
let seqHelper: ISeqHelper;
|
|
26
|
+
let rdKitModule: RDModule;
|
|
20
27
|
let monomerLibHelper: IMonomerLibHelper;
|
|
21
28
|
let userLibSettings: UserLibSettings; //backup
|
|
22
29
|
|
|
23
30
|
before(async () => {
|
|
31
|
+
helmHelper = await getHelmHelper();
|
|
32
|
+
seqHelper = await getSeqHelper();
|
|
33
|
+
rdKitModule = await getRdKitModule();
|
|
24
34
|
monomerLibHelper = await getMonomerLibHelper();
|
|
25
35
|
userLibSettings = await getUserLibSettings();
|
|
26
36
|
|
|
@@ -32,43 +42,107 @@ category('PolyTool: Convert', () => {
|
|
|
32
42
|
await monomerLibHelper.loadMonomerLib(true);
|
|
33
43
|
});
|
|
34
44
|
|
|
35
|
-
const tests
|
|
36
|
-
'cyclized': {
|
|
37
|
-
src:
|
|
38
|
-
|
|
39
|
-
'C
|
|
40
|
-
'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'PEPTIDE1{C.T.G.H.F.Y.P.C.[meI]}$PEPTIDE1,PEPTIDE1,1:R3-8:R3$$$',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
const tests = {
|
|
46
|
+
'cyclized-C(1)-2-1': {
|
|
47
|
+
src: {seq: 'R-F-C(1)-T-G-H-F-Y-P-C(1)-meI'},
|
|
48
|
+
tgt: {
|
|
49
|
+
helm: 'PEPTIDE1{R.F.C.T.G.H.F.Y.P.C.[meI]}$PEPTIDE1,PEPTIDE1,3:R3-10:R3$$$V2.0',
|
|
50
|
+
mol: {atomCount: 95, bondCount: 100, inchiKey: 'LMJUFVBPWWJJPN-AJJYTACESA-N',},
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
'cyclized-C(1)-0-1': {
|
|
54
|
+
src: {seq: 'C(1)-T-G-H-F-Y-P-C(1)-meI'},
|
|
55
|
+
tgt: {
|
|
56
|
+
helm: 'PEPTIDE1{C.T.G.H.F.Y.P.C.[meI]}$PEPTIDE1,PEPTIDE1,1:R3-8:R3$$$V2.0',
|
|
57
|
+
mol: {atomCount: 73, bondCount: 77, inchiKey: 'KLFRBMUPPMMGJM-HXTBFBBASA-N',},
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
'cyclized-C(1)-2-0': {
|
|
61
|
+
src: {seq: 'R-F-C(1)-T-G-H-F-Y-P-C(1)'},
|
|
62
|
+
tgt: {
|
|
63
|
+
helm: 'PEPTIDE1{R.F.C.T.G.H.F.Y.P.C}$PEPTIDE1,PEPTIDE1,3:R3-10:R3$$$V2.0',
|
|
64
|
+
mol: {atomCount: 86, bondCount: 91, inchiKey: 'WIHSRTQGMICACU-DDDKLKPZSA-N',},
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
'cyclized-C(1)-0-0': {
|
|
68
|
+
src: {seq: 'C(1)-T-G-H-F-Y-P-C(1)'},
|
|
69
|
+
tgt: {
|
|
70
|
+
helm: 'PEPTIDE1{C.T.G.H.F.Y.P.C}$PEPTIDE1,PEPTIDE1,1:R3-8:R3$$$V2.0',
|
|
71
|
+
mol: {atomCount: 64, bondCount: 68, inchiKey: 'LOSMDBLEXLWPLB-OFZKBENXSA-N',},
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
'cyclized-D(2)-NH2(2)-3-0': {
|
|
75
|
+
src: {seq: 'R-F-D(2)-T-G-H-F-Y-P-NH2(2)'},
|
|
76
|
+
tgt: {
|
|
77
|
+
helm: 'PEPTIDE1{R.F.D.T.G.H.F.Y.P.[NH2]}$PEPTIDE1,PEPTIDE1,10:R2-3:R3$$$V2.0',
|
|
78
|
+
mol: {atomCount: 81, bondCount: 86, inchiKey: 'CBMGNYKOZWNVNK-AHGCAHLCSA-N',},
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
'cyclized-D(2)-NH2(2)-0-0': {
|
|
82
|
+
src: {seq: 'D(2)-T-G-H-F-Y-P-NH2(2)'},
|
|
83
|
+
tgt: {
|
|
84
|
+
helm: 'PEPTIDE1{D.T.G.H.F.Y.P.[NH2]}$PEPTIDE1,PEPTIDE1,8:R2-1:R3$$$V2.0',
|
|
85
|
+
mol: {atomCount: 59, bondCount: 63, inchiKey: 'HGRHAUQBJXFERJ-MUFWPYSASA-N',},
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
'cyclized-azG(4)-aG(4)-2-1': {
|
|
89
|
+
src: {seq: 'R-F-azG(4)-T-G-H-F-Y-P-aG(4)-meI'},
|
|
90
|
+
tgt: {
|
|
91
|
+
helm: 'PEPTIDE1{R.F.[GGaz].T.G.H.F.Y.P}|PEPTIDE2{[meI]}$PEPTIDE1,PEPTIDE1,3:R3-9:R2|PEPTIDE1,PEPTIDE2,3:R4-1:R1$$$V2.0',
|
|
92
|
+
mol: {atomCount: 97, bondCount: 103, inchiKey: 'WJSYGVBGPCCSJF-PERUNASMSA-N',},
|
|
93
|
+
}
|
|
94
|
+
},
|
|
52
95
|
};
|
|
53
96
|
|
|
54
97
|
for (const [testName, testData] of Object.entries(tests)) {
|
|
55
|
-
test(
|
|
98
|
+
test(`toHelm-${testName}`, async () => {
|
|
56
99
|
const rules = await getRules(['rules_example.json']);
|
|
57
|
-
const res = doPolyToolConvert(testData.src, rules);
|
|
58
|
-
|
|
100
|
+
const res = doPolyToolConvert([testData.src.seq], rules, helmHelper);
|
|
101
|
+
expect(res[0], testData.tgt.helm);
|
|
59
102
|
});
|
|
60
103
|
}
|
|
61
104
|
|
|
105
|
+
for (const [testName, testData] of Object.entries(tests)) {
|
|
106
|
+
test(`toAtomicLevel-${testName}`, async () => {
|
|
107
|
+
const rules = await getRules(['rules_example.json']);
|
|
108
|
+
const helmList = doPolyToolConvert([testData.src.seq], rules, helmHelper);
|
|
109
|
+
|
|
110
|
+
const lib = await getOverriddenLibrary(rules);
|
|
111
|
+
|
|
112
|
+
const helmCol = DG.Column.fromStrings('helm', helmList);
|
|
113
|
+
helmCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
114
|
+
helmCol.meta.units = NOTATION.HELM;
|
|
115
|
+
const talRes = await seqHelper.helmToAtomicLevel(helmCol, false, false, lib);
|
|
116
|
+
if (talRes.warnings && talRes.warnings.length > 0)
|
|
117
|
+
throw new Error(talRes.warnings[0]);
|
|
118
|
+
expect(talRes.molCol != null, true, '.molCol is not null');
|
|
119
|
+
const k = 11;
|
|
120
|
+
const molfile = talRes.molCol!.get(0)!;
|
|
121
|
+
const mol = rdKitModule.get_mol(molfile);
|
|
122
|
+
try {
|
|
123
|
+
const resMol = {
|
|
124
|
+
atomCount: mol.get_num_atoms(), bondCount: mol.get_num_bonds(),
|
|
125
|
+
inchiKey: rdKitModule.get_inchikey_for_inchi(mol.get_inchi())
|
|
126
|
+
};
|
|
127
|
+
expectObject(resMol, testData.tgt.mol);
|
|
128
|
+
} finally {
|
|
129
|
+
mol.delete();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
62
133
|
|
|
63
134
|
test('ui-col-wo-table', async () => {
|
|
64
135
|
const packageName = _package.name;
|
|
65
|
-
const
|
|
136
|
+
const seqList = Object.values(tests).map((td) => td.src.seq);
|
|
137
|
+
const helmList = Object.values(tests).map((td) => td.tgt.helm);
|
|
138
|
+
const seqCol = DG.Column.fromStrings('seq', seqList);
|
|
66
139
|
const helmCol: DG.Column = await grok.functions.call(`${packageName}:polyToolConvert2`, {
|
|
67
140
|
seqCol: seqCol,
|
|
68
141
|
generateHelm: true,
|
|
69
142
|
chiralityEngine: true,
|
|
70
143
|
rules: ['rules_example.json']
|
|
71
144
|
});
|
|
145
|
+
expectArray(helmCol.toList(), helmList);
|
|
72
146
|
expect(helmCol.semType, DG.SEMTYPE.MACROMOLECULE);
|
|
73
147
|
expect(helmCol.meta.units, NOTATION.HELM);
|
|
74
148
|
expect(helmCol.dataFrame, null);
|
|
@@ -76,7 +150,9 @@ category('PolyTool: Convert', () => {
|
|
|
76
150
|
|
|
77
151
|
test('ui-col', async () => {
|
|
78
152
|
const packageName = _package.name;
|
|
79
|
-
const
|
|
153
|
+
const seqList = Object.values(tests).map((td) => td.src.seq);
|
|
154
|
+
const helmList = Object.values(tests).map((td) => td.tgt.helm);
|
|
155
|
+
const seqCol = DG.Column.fromStrings('seq', seqList);
|
|
80
156
|
const df = DG.DataFrame.fromColumns([seqCol]);
|
|
81
157
|
const tv = grok.shell.addTableView(df);
|
|
82
158
|
|
|
@@ -86,6 +162,7 @@ category('PolyTool: Convert', () => {
|
|
|
86
162
|
chiralityEngine: true,
|
|
87
163
|
rules: ['rules_example.json']
|
|
88
164
|
});
|
|
165
|
+
expectArray(helmCol.toList(), helmList);
|
|
89
166
|
expect(helmCol.semType, DG.SEMTYPE.MACROMOLECULE);
|
|
90
167
|
expect(helmCol.meta.units, NOTATION.HELM);
|
|
91
168
|
expect(helmCol.dataFrame.id, df.id);
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
|
|
5
|
+
import {category, test, expect, before} from '@datagrok-libraries/utils/src/test';
|
|
6
|
+
import {ISeqHelper, getSeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
7
|
+
import {ALIGNMENT, ALPHABET, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
8
|
+
import {_testNeg, _testPos, DetectorTestData, DfReaderFunc, PosCol} from './utils/detect-macromolecule-utils';
|
|
9
|
+
|
|
10
|
+
category('PolyTool: detectors', () => {
|
|
11
|
+
let seqHelper: ISeqHelper;
|
|
12
|
+
|
|
13
|
+
before(async () => {
|
|
14
|
+
seqHelper = await getSeqHelper();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const tests: DetectorTestData = {
|
|
18
|
+
'cyclized1': {
|
|
19
|
+
csv: `n,seq
|
|
20
|
+
1,R-F-C(1)-T-G-H-F-Y-G-H-F-Y-G-H-F-Y-P-C(1)-meI
|
|
21
|
+
2,C(1)-T-G-H-F-Y-P-C(1)-meI
|
|
22
|
+
3,R-F-C(1)-T-G-H-F-Y-P-C(1)
|
|
23
|
+
4,C(1)-T-G-H-F-H-P-C(1)
|
|
24
|
+
5,R-F-D(2)-T-G-H-F-Y-P-NH2(2)
|
|
25
|
+
6,R-F-aG(4)-T-G-H-F-Y-P-azG(4)-meI`,
|
|
26
|
+
pos: {'seq': new PosCol(NOTATION.CUSTOM, ALIGNMENT.SEQ, ALPHABET.UN, 13, true, '-')}
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const [testName, testData] of Object.entries(tests)) {
|
|
31
|
+
test(`${testName}`, async () => {
|
|
32
|
+
const reader: DfReaderFunc = async (): Promise<DG.DataFrame> => {
|
|
33
|
+
return DG.DataFrame.fromCsv(testData.csv);
|
|
34
|
+
};
|
|
35
|
+
for (const negColName of testData.neg ?? [])
|
|
36
|
+
await _testNeg(reader, negColName);
|
|
37
|
+
for (const [posColName, posCol] of Object.entries(testData.pos ?? {})) {
|
|
38
|
+
await _testPos(reader, posColName, seqHelper, posCol.units, posCol.aligned,
|
|
39
|
+
posCol.alphabet, posCol.alphabetSize, posCol.alphabetIsMultichar, posCol.separator);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
@@ -41,7 +41,7 @@ category('PolyTool: Enumerate', () => {
|
|
|
41
41
|
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
42
42
|
params: {
|
|
43
43
|
type: PolyToolEnumeratorTypes.Single,
|
|
44
|
-
|
|
44
|
+
breadthPlaceholders: [
|
|
45
45
|
{start: 2, end: 4, monomers: ['K']},
|
|
46
46
|
],
|
|
47
47
|
},
|
|
@@ -55,7 +55,7 @@ category('PolyTool: Enumerate', () => {
|
|
|
55
55
|
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
56
56
|
params: {
|
|
57
57
|
type: PolyToolEnumeratorTypes.Single,
|
|
58
|
-
|
|
58
|
+
breadthPlaceholders: [
|
|
59
59
|
{start: 2, end: 4, monomers: ['K']},
|
|
60
60
|
],
|
|
61
61
|
keepOriginal: true,
|
|
@@ -71,7 +71,7 @@ category('PolyTool: Enumerate', () => {
|
|
|
71
71
|
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
72
72
|
params: {
|
|
73
73
|
type: PolyToolEnumeratorTypes.Single,
|
|
74
|
-
|
|
74
|
+
breadthPlaceholders: [
|
|
75
75
|
{start: 2, end: 4, monomers: ['K', 'Y']},
|
|
76
76
|
],
|
|
77
77
|
},
|
|
@@ -88,7 +88,7 @@ category('PolyTool: Enumerate', () => {
|
|
|
88
88
|
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
89
89
|
params: {
|
|
90
90
|
type: PolyToolEnumeratorTypes.Single,
|
|
91
|
-
|
|
91
|
+
breadthPlaceholders: [
|
|
92
92
|
{start: 2, end: 4, monomers: ['K']},
|
|
93
93
|
{start: 2, end: 4, monomers: ['Y']},
|
|
94
94
|
],
|
|
@@ -45,7 +45,7 @@ category('toAtomicLevel', () => {
|
|
|
45
45
|
expect(ggazM != null, true, `Monomer 'GGaz' not found.`);
|
|
46
46
|
|
|
47
47
|
const overrideMonomerLibData: MonomerLibData = {[PolymerTypes.PEPTIDE]: {'GGaz': ggazM}};
|
|
48
|
-
const overriddenMonomerLib = systemMonomerLib.override(overrideMonomerLibData);
|
|
48
|
+
const overriddenMonomerLib = systemMonomerLib.override(overrideMonomerLibData, 'test');
|
|
49
49
|
|
|
50
50
|
const seqHelper = await getSeqHelper();
|
|
51
51
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
|
|
4
|
+
import {ALIGNMENT, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
5
|
+
import {delay, expect} from '@datagrok-libraries/utils/src/test';
|
|
6
|
+
import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
7
|
+
|
|
8
|
+
export type DetectorTestData = { [testName: string]: { csv: string, neg?: string[], pos?: { [colName: string]: PosCol } } };
|
|
9
|
+
|
|
10
|
+
export type DfReaderFunc = () => Promise<DG.DataFrame>;
|
|
11
|
+
|
|
12
|
+
export class PosCol {
|
|
13
|
+
constructor(
|
|
14
|
+
public readonly units: NOTATION,
|
|
15
|
+
public readonly aligned: ALIGNMENT | null,
|
|
16
|
+
public readonly alphabet: string | null,
|
|
17
|
+
public readonly alphabetSize: number,
|
|
18
|
+
public readonly alphabetIsMultichar?: boolean,
|
|
19
|
+
public readonly separator?: string,
|
|
20
|
+
) { };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function _testNeg(readDf: DfReaderFunc, colName: string) {
|
|
24
|
+
const df: DG.DataFrame = await readDf();
|
|
25
|
+
const col: DG.Column = df.getCol(colName)!;
|
|
26
|
+
const semType: string = await grok.functions
|
|
27
|
+
.call('Bio:detectMacromolecule', {col: col}) as unknown as string;
|
|
28
|
+
if (semType)
|
|
29
|
+
col.semType = semType;
|
|
30
|
+
|
|
31
|
+
if (col.semType === DG.SEMTYPE.MACROMOLECULE) {
|
|
32
|
+
const msg = `Negative test detected semType='${col.semType}', units='${col.meta.units}'.`;
|
|
33
|
+
throw new Error(msg);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function _testPos(
|
|
38
|
+
readDf: DfReaderFunc, colName: string, seqHelper: ISeqHelper,
|
|
39
|
+
units: string, aligned: string | null, alphabet: string | null, alphabetSize: number, alphabetIsMultichar?: boolean,
|
|
40
|
+
separator: string | null = null,
|
|
41
|
+
) {
|
|
42
|
+
const df: DG.DataFrame = await readDf();
|
|
43
|
+
const col: DG.Column = df.col(colName)!;
|
|
44
|
+
const semType: string = await grok.functions
|
|
45
|
+
.call('Bio:detectMacromolecule', {col: col}) as unknown as string;
|
|
46
|
+
if (semType)
|
|
47
|
+
col.semType = semType;
|
|
48
|
+
|
|
49
|
+
expect(col.semType, DG.SEMTYPE.MACROMOLECULE);
|
|
50
|
+
expect(col.meta.units, units);
|
|
51
|
+
expect(col.getTag(bioTAGS.aligned), aligned);
|
|
52
|
+
expect(col.getTag(bioTAGS.alphabet), alphabet);
|
|
53
|
+
if (separator)
|
|
54
|
+
expect(col.getTag(bioTAGS.separator), separator);
|
|
55
|
+
|
|
56
|
+
const sh = seqHelper.getSeqHandler(col);
|
|
57
|
+
expect(sh.getAlphabetSize(), alphabetSize);
|
|
58
|
+
expect(sh.getAlphabetIsMultichar(), alphabetIsMultichar);
|
|
59
|
+
if (!sh.isHelm()) {
|
|
60
|
+
expect(sh.aligned, aligned);
|
|
61
|
+
expect(sh.alphabet, alphabet);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
@@ -2,8 +2,8 @@ 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 {DEFAULT_FORMATS} from '
|
|
6
|
-
import {ITranslationHelper} from '
|
|
5
|
+
import {DEFAULT_FORMATS} from '../../apps/common/model/const';
|
|
6
|
+
import {ITranslationHelper} from '../../types';
|
|
7
7
|
|
|
8
8
|
export class OligoToolkitTestPackage extends DG.Package {
|
|
9
9
|
async getTranslationHelper(): Promise<ITranslationHelper> {
|
|
@@ -2,9 +2,6 @@ import * as grok from 'datagrok-api/grok';
|
|
|
2
2
|
import * as DG from 'datagrok-api/dg';
|
|
3
3
|
import * as ui from 'datagrok-api/ui';
|
|
4
4
|
|
|
5
|
-
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
6
|
-
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
7
|
-
|
|
8
5
|
import {defaultErrorHandler} from './err-info';
|
|
9
6
|
import {polyToolEnumerateHelmUI} from '../polytool/pt-enumeration-helm-dialog';
|
|
10
7
|
import {polyToolEnumerateChemUI} from '../polytool/pt-dialog';
|
|
@@ -43,11 +40,11 @@ function addContextMenuForCell(gridCell: DG.GridCell, menu: DG.Menu): boolean {
|
|
|
43
40
|
if (gridCell && gridCell.tableColumn) {
|
|
44
41
|
switch (gridCell.tableColumn.semType) {
|
|
45
42
|
case DG.SEMTYPE.MACROMOLECULE: {
|
|
46
|
-
menu.item('PolyTool-Enumerate', () => { polyToolEnumerateHelmUI(gridCell.cell); });
|
|
43
|
+
menu.item('PolyTool-Enumerate', () => { polyToolEnumerateHelmUI(gridCell.cell).then(() => {}); });
|
|
47
44
|
return true;
|
|
48
45
|
}
|
|
49
46
|
case DG.SEMTYPE.MOLECULE: {
|
|
50
|
-
menu.item('PolyTool-Enumerate', () => { polyToolEnumerateChemUI(gridCell.cell); });
|
|
47
|
+
menu.item('PolyTool-Enumerate', () => { polyToolEnumerateChemUI(gridCell.cell).then(() => {}); });
|
|
51
48
|
return true;
|
|
52
49
|
}
|
|
53
50
|
}
|