@datagrok/helm 2.1.8 → 2.1.10
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/.eslintrc.json +3 -2
- 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 +3 -3
- package/src/cell-renderer.ts +20 -20
- package/src/package.ts +47 -46
- package/src/tests/helm-tests.ts +42 -5
- package/src/utils.ts +60 -47
- package/tsconfig.json +1 -1
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/helm",
|
|
3
3
|
"friendlyName": "Helm",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Oleksandra Serhiienko",
|
|
7
7
|
"email": "oserhiienko@datagrok.ai"
|
|
8
8
|
},
|
|
9
|
-
"description": "Provides support for HELM notation (importing, detecting, rendering, conversion).
|
|
9
|
+
"description": "Provides support for HELM notation (importing, detecting, rendering, conversion).",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@datagrok-libraries/bio": "^5.
|
|
11
|
+
"@datagrok-libraries/bio": "^5.31.0",
|
|
12
12
|
"@datagrok-libraries/utils": "^1.19.1",
|
|
13
13
|
"cash-dom": "^8.1.1",
|
|
14
14
|
"datagrok-api": "^1.10.2",
|
package/src/cell-renderer.ts
CHANGED
|
@@ -37,27 +37,27 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
|
|
|
37
37
|
} else if (argsX > maxLengthWordsSum[mid + 1]) {
|
|
38
38
|
left = mid + 1;
|
|
39
39
|
}
|
|
40
|
-
if (left == right)
|
|
40
|
+
if (left == right)
|
|
41
41
|
found = true;
|
|
42
|
-
}
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
left = (argsX >= maxLengthWordsSum[left]) ? left + 1 : left;
|
|
46
45
|
const monomers = findMonomers(gridCell.cell.value);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const s: string = gridCell.cell.value ?? '';
|
|
47
|
+
const subParts: string[] = parseHelm(s);
|
|
48
|
+
const allParts: string[] = getParts(subParts, s);
|
|
49
|
+
const tooltipMessage: HTMLElement[] = [];
|
|
51
50
|
for (let i = 0; i < allParts.length; ++i) {
|
|
52
|
-
if (monomers.has(allParts[i]))
|
|
51
|
+
if (monomers.has(allParts[i])) {
|
|
53
52
|
tooltipMessage[i] = ui.divV([
|
|
54
|
-
ui.divText(`Monomer ${allParts[i]} not found.`),
|
|
53
|
+
ui.divText(`Monomer '${allParts[i]}' not found.`),
|
|
55
54
|
ui.divText('Open the Context Panel, then expand Manage Libraries')
|
|
56
55
|
]);
|
|
56
|
+
}
|
|
57
57
|
}
|
|
58
|
-
(((tooltipMessage[left]?.childNodes.length ?? 0) > 0))
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
(((tooltipMessage[left]?.childNodes.length ?? 0) > 0)) ?
|
|
59
|
+
ui.tooltip.show(ui.div(tooltipMessage[left]), e.x + 16, e.y + 16) :
|
|
60
|
+
ui.tooltip.hide();
|
|
61
61
|
} catch (err: any) {
|
|
62
62
|
const errMsg: string = errorToConsole(err);
|
|
63
63
|
console.error('Helm: HelmCellRenderer.onMouseMove() error:\n' + errMsg);
|
|
@@ -71,8 +71,8 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
|
|
|
71
71
|
const undefinedColor = 'rgb(100,100,100)';
|
|
72
72
|
const grayColor = '#808080';
|
|
73
73
|
const monomers = findMonomers(gridCell.cell.value);
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const s: string = gridCell.cell.value ?? '';
|
|
75
|
+
const subParts: string[] = parseHelm(s);
|
|
76
76
|
if (monomers.size == 0 && grid) {
|
|
77
77
|
const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
|
|
78
78
|
host.setAttribute('dataformat', 'helm');
|
|
@@ -85,7 +85,7 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
|
|
|
85
85
|
if (!grid) {
|
|
86
86
|
const r = window.devicePixelRatio;
|
|
87
87
|
h = 28;
|
|
88
|
-
g.canvas.height = h*r;
|
|
88
|
+
g.canvas.height = h * r;
|
|
89
89
|
g.canvas.style.height = `${h}px`;
|
|
90
90
|
}
|
|
91
91
|
w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
|
|
@@ -96,20 +96,20 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
|
|
|
96
96
|
g.font = '12px monospace';
|
|
97
97
|
g.textBaseline = 'top';
|
|
98
98
|
let x1 = x;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const maxLengthWords: any = {};
|
|
100
|
+
const maxLengthWordSum: any = {};
|
|
101
|
+
const allParts: string[] = getParts(subParts, s);
|
|
102
102
|
for (let i = 0; i < allParts.length; ++i) {
|
|
103
103
|
maxLengthWords[i] = allParts[i].length * 7;
|
|
104
|
-
|
|
104
|
+
const color = monomers.has(allParts[i]) ? 'red' : grayColor;
|
|
105
105
|
g.fillStyle = undefinedColor;
|
|
106
106
|
x1 = printLeftOrCentered(x1, y, w, h, g, allParts[i], color, 0, true, 1.0);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
maxLengthWordSum[0] = maxLengthWords[0];
|
|
110
|
-
for (let i = 1; i < allParts.length; i++)
|
|
110
|
+
for (let i = 1; i < allParts.length; i++)
|
|
111
111
|
maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
gridCell.cell.column.temp = {
|
|
114
114
|
'helm-sum-maxLengthWords': maxLengthWordSum,
|
|
115
115
|
'helm-maxLengthWords': maxLengthWords
|
package/src/package.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types';
|
|
|
10
10
|
import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
|
|
11
11
|
import {findMonomers} from './utils';
|
|
12
12
|
import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
13
|
-
import {
|
|
13
|
+
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
14
14
|
|
|
15
15
|
export const _package = new DG.Package();
|
|
16
16
|
let monomerLib: IMonomerLib | null = null;
|
|
@@ -27,10 +27,10 @@ export async function initHelm(): Promise<void> {
|
|
|
27
27
|
try {
|
|
28
28
|
rewriteLibraries(); // initHelm()
|
|
29
29
|
|
|
30
|
-
const
|
|
30
|
+
const polymerTypeList: string[] = monomerLib.getPolymerTypes();
|
|
31
31
|
const msgStr: string = 'Monomer lib updated:<br />' + (
|
|
32
|
-
|
|
33
|
-
return `${
|
|
32
|
+
polymerTypeList.length == 0 ? 'empty' : polymerTypeList.map((polymerType) => {
|
|
33
|
+
return `${polymerType} ${monomerLib.getMonomerSymbolsByType(polymerType).length}`;
|
|
34
34
|
}).join('<br />'));
|
|
35
35
|
|
|
36
36
|
grok.shell.info(msgStr);
|
|
@@ -45,13 +45,13 @@ export async function initHelm(): Promise<void> {
|
|
|
45
45
|
|
|
46
46
|
function rewriteLibraries() {
|
|
47
47
|
org.helm.webeditor.Monomers.clear();
|
|
48
|
-
monomerLib.
|
|
49
|
-
const
|
|
50
|
-
|
|
48
|
+
monomerLib.getPolymerTypes().forEach((polymerType) => {
|
|
49
|
+
const monomerSymbols = monomerLib.getMonomerSymbolsByType(polymerType);
|
|
50
|
+
monomerSymbols.forEach((monomerSymbol) => {
|
|
51
51
|
let isBroken = false;
|
|
52
|
-
const monomer: Monomer = monomerLib.getMonomer(
|
|
52
|
+
const monomer: Monomer = monomerLib.getMonomer(polymerType, monomerSymbol);
|
|
53
53
|
const webEditorMonomer: WebEditorMonomer = {
|
|
54
|
-
id:
|
|
54
|
+
id: monomerSymbol,
|
|
55
55
|
m: monomer.molfile,
|
|
56
56
|
n: monomer.name,
|
|
57
57
|
na: monomer.naturalAnalog,
|
|
@@ -64,13 +64,13 @@ function rewriteLibraries() {
|
|
|
64
64
|
if (monomer.rgroups.length > 0) {
|
|
65
65
|
webEditorMonomer.rs = monomer.rgroups.length;
|
|
66
66
|
const at = {};
|
|
67
|
-
monomer.rgroups.forEach(it => {
|
|
67
|
+
monomer.rgroups.forEach((it) => {
|
|
68
68
|
at[it[RGROUP_LABEL]] = it[RGROUP_CAP_GROUP_NAME];
|
|
69
69
|
});
|
|
70
70
|
webEditorMonomer.at = at;
|
|
71
|
-
} else if (monomer
|
|
72
|
-
webEditorMonomer.rs = Object.keys(getRS(monomer
|
|
73
|
-
webEditorMonomer.at = getRS(monomer
|
|
71
|
+
} else if (monomer[SMILES] != null) {
|
|
72
|
+
webEditorMonomer.rs = Object.keys(getRS(monomer[SMILES].toString())).length;
|
|
73
|
+
webEditorMonomer.at = getRS(monomer[SMILES].toString());
|
|
74
74
|
} else {
|
|
75
75
|
isBroken = true;
|
|
76
76
|
}
|
|
@@ -81,7 +81,7 @@ function rewriteLibraries() {
|
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
// Obsolete
|
|
84
|
-
|
|
84
|
+
const grid: DG.Grid = grok.shell.tv.grid;
|
|
85
85
|
if (grid) grid.invalidate();
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -97,16 +97,17 @@ export function helmCellRenderer(): HelmCellRenderer {
|
|
|
97
97
|
function checkMonomersAndOpenWebEditor(cell?: DG.Cell, value?: string, units?: string) {
|
|
98
98
|
const cellValue = typeof units === 'undefined' ? cell.value : value;
|
|
99
99
|
const monomers = findMonomers(cellValue);
|
|
100
|
-
if (monomers.size == 0)
|
|
100
|
+
if (monomers.size == 0) {
|
|
101
101
|
webEditor(cell, value, units);
|
|
102
|
-
else
|
|
102
|
+
} else {
|
|
103
103
|
grok.shell.warning(`Monomers ${Array.from(monomers).join(', ')} are absent! <br/>` +
|
|
104
104
|
`Please, upload the monomer library! <br/>` +
|
|
105
105
|
`<a href="https://datagrok.ai/help/domains/bio/macromolecules" target="_blank">Learn more</a>`);
|
|
106
|
+
}
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
//tags: cellEditor
|
|
109
|
-
//description: Macromolecule
|
|
110
|
+
//description: Macromolecule
|
|
110
111
|
//input: grid_cell cell
|
|
111
112
|
//meta.columnTags: quality=Macromolecule, units=helm
|
|
112
113
|
export function editMoleculeCell(cell: DG.GridCell): void {
|
|
@@ -118,13 +119,13 @@ export function editMoleculeCell(cell: DG.GridCell): void {
|
|
|
118
119
|
//meta.action: Open Helm Web Editor
|
|
119
120
|
//input: string mol { semType: Macromolecule }
|
|
120
121
|
export function openEditor(mol: string): void {
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
const df = grok.shell.tv.grid.dataFrame;
|
|
123
|
+
const col = df.columns.bySemType('Macromolecule');
|
|
123
124
|
const colUnits = col.getTag(DG.TAGS.UNITS);
|
|
124
|
-
if (colUnits === NOTATION.HELM)
|
|
125
|
+
if (colUnits === NOTATION.HELM)
|
|
125
126
|
checkMonomersAndOpenWebEditor(df.currentCell, undefined, undefined);
|
|
126
|
-
|
|
127
|
-
const resStr = converter.convertStringToHelm(mol, '/');
|
|
127
|
+
const converter = new NotationConverter(col);
|
|
128
|
+
const resStr = converter.convertStringToHelm(mol, '/');
|
|
128
129
|
checkMonomersAndOpenWebEditor(df.currentCell, resStr, col.getTag(DG.TAGS.UNITS));
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -133,17 +134,17 @@ export function openEditor(mol: string): void {
|
|
|
133
134
|
//input: string helmString {semType: Macromolecule}
|
|
134
135
|
//output: widget result
|
|
135
136
|
export async function propertiesPanel(helmString: string) {
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
const grid = grok.shell.tv.grid;
|
|
138
|
+
const parent = grid.root.parentElement;
|
|
138
139
|
const host = ui.div([]);
|
|
139
140
|
parent.appendChild(host);
|
|
140
|
-
|
|
141
|
+
const editor = new JSDraw2.Editor(host, {viewonly: true});
|
|
141
142
|
host.style.width = '0px';
|
|
142
143
|
host.style.height = '0px';
|
|
143
144
|
editor.setHelm(helmString);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
const formula = editor.getFormula(true);
|
|
146
|
+
const molWeight = Math.round(editor.getMolWeight() * 100) / 100;
|
|
147
|
+
const coef = Math.round(editor.getExtinctionCoefficient(true) * 100) / 100;
|
|
147
148
|
parent.lastChild.remove();
|
|
148
149
|
return new DG.Widget(
|
|
149
150
|
ui.tableFromMap({
|
|
@@ -155,11 +156,11 @@ export async function propertiesPanel(helmString: string) {
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
function webEditor(cell?: DG.Cell, value?: string, units?: string) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
const view = ui.div();
|
|
160
|
+
const df = grok.shell.tv.grid.dataFrame;
|
|
161
|
+
const converter = new NotationConverter(df.columns.bySemType('Macromolecule'));
|
|
161
162
|
org.helm.webeditor.MolViewer.molscale = 0.8;
|
|
162
|
-
|
|
163
|
+
const app = new scil.helm.App(view, {
|
|
163
164
|
showabout: false,
|
|
164
165
|
mexfontsize: '90%',
|
|
165
166
|
mexrnapinontab: true,
|
|
@@ -169,9 +170,9 @@ function webEditor(cell?: DG.Cell, value?: string, units?: string) {
|
|
|
169
170
|
mexfavoritefirst: true,
|
|
170
171
|
mexfilter: true
|
|
171
172
|
});
|
|
172
|
-
|
|
173
|
+
const sizes = app.calculateSizes();
|
|
173
174
|
app.canvas.resize(sizes.rightwidth - 100, sizes.topheight - 210);
|
|
174
|
-
|
|
175
|
+
let s = {width: sizes.rightwidth - 100 + 'px', height: sizes.bottomheight + 'px'};
|
|
175
176
|
//@ts-ignore
|
|
176
177
|
scil.apply(app.sequence.style, s);
|
|
177
178
|
//@ts-ignore
|
|
@@ -181,22 +182,22 @@ function webEditor(cell?: DG.Cell, value?: string, units?: string) {
|
|
|
181
182
|
scil.apply(app.properties.parent.style, s);
|
|
182
183
|
app.structureview.resize(sizes.rightwidth, sizes.bottomheight + app.toolbarheight);
|
|
183
184
|
app.mex.resize(sizes.topheight - 80);
|
|
184
|
-
setTimeout(
|
|
185
|
-
if (typeof units === 'undefined')
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
if (typeof units === 'undefined')
|
|
186
187
|
app.canvas.helm.setSequence(cell.value, 'HELM');
|
|
187
|
-
|
|
188
|
+
else
|
|
188
189
|
app.canvas.helm.setSequence(value, 'HELM');
|
|
189
|
-
}
|
|
190
190
|
}, 200);
|
|
191
191
|
//@ts-ignore
|
|
192
192
|
ui.dialog({showHeader: false, showFooter: true})
|
|
193
193
|
.add(view)
|
|
194
194
|
.onOK(() => {
|
|
195
|
-
|
|
195
|
+
const helmValue = app.canvas.getHelm(true).replace(/<\/span>/g, '')
|
|
196
|
+
.replace(/<span style='background:#bbf;'>/g, '');
|
|
196
197
|
if (typeof units === 'undefined') {
|
|
197
198
|
cell.value = helmValue;
|
|
198
199
|
} else {
|
|
199
|
-
|
|
200
|
+
const convertedRes = converter.convertHelmToFastaSeparator(helmValue, units);
|
|
200
201
|
cell.value = convertedRes;
|
|
201
202
|
}
|
|
202
203
|
}).show({modal: true, fullScreen: true});
|
|
@@ -226,19 +227,19 @@ function getRS(smiles: string) {
|
|
|
226
227
|
|
|
227
228
|
//name: getMolfiles
|
|
228
229
|
//input: column col {semType: Macromolecule}
|
|
229
|
-
//output: column res
|
|
230
|
+
//output: column res
|
|
230
231
|
export function getMolfiles(col: DG.Column): DG.Column {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
const grid = grok.shell.tv.grid;
|
|
233
|
+
const parent = grid.root.parentElement;
|
|
234
|
+
const res = DG.Column.string('mols', col.length);
|
|
234
235
|
const host = ui.div([]);
|
|
235
236
|
parent.appendChild(host);
|
|
236
|
-
|
|
237
|
+
const editor = new JSDraw2.Editor(host, {viewonly: true});
|
|
237
238
|
host.style.width = '0px';
|
|
238
239
|
host.style.height = '0px';
|
|
239
240
|
res.init((i) => {
|
|
240
241
|
editor.setHelm(col.get(i));
|
|
241
|
-
|
|
242
|
+
const mol = editor.getMolfile();
|
|
242
243
|
return mol;
|
|
243
244
|
});
|
|
244
245
|
parent.lastChild.remove();
|
package/src/tests/helm-tests.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {after, before, category, delay, expect, test} from '@datagrok-libraries/utils/src/test';
|
|
1
|
+
import {after, before, category, delay, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
|
|
2
2
|
//import {findMonomers, helmToFasta, helmToPeptide, helmToRNA, initHelm} from '../package';
|
|
3
3
|
import {_package} from '../package-test';
|
|
4
4
|
import {parseHelm} from '../utils';
|
|
@@ -18,7 +18,9 @@ category('Helm', () => {
|
|
|
18
18
|
// expect(await helmToRNA('RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$'), 'UTGCA');
|
|
19
19
|
// expect(await helmToRNA('RNA1{P.R(U).P.R(T)}$$$$'), 'UT');
|
|
20
20
|
// // eslint-disable-next-line max-len
|
|
21
|
-
// expect(
|
|
21
|
+
// expect(
|
|
22
|
+
// await helmToRNA('RNA1{R(U)P.R(T)P}|RNA2{P.R(A)P.R(A)}$RNA1,RNA2,2:pair-6:pair|RNA1,RNA2,5:pair-3:pair$$$'), 'UT AA'
|
|
23
|
+
// );
|
|
22
24
|
// });
|
|
23
25
|
//
|
|
24
26
|
// test('helmToPeptide', async () => {
|
|
@@ -46,7 +48,32 @@ category('Helm', () => {
|
|
|
46
48
|
// expect(col.tags[DG.TAGS.UNITS], 'HELM');
|
|
47
49
|
// });
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
const complexMonomers = [
|
|
52
|
+
// tests parsing of complex monomemer in square brackets like [L-hArg(Et,Et)]
|
|
53
|
+
'PEPTIDE1{[L-hArg(Et,Et)].E.F.G}|PEPTIDE2{C.E}$PEPTIDE1,PEPTIDE2,2:R3-1:R1$$$V2.0',
|
|
54
|
+
// tests parsing of complex monomer and one as a side chain as well
|
|
55
|
+
'PEPTIDE1{[L-hArg(Et,Et)]([L-hArg(Et,Et)]).E.F.G}$$$$',
|
|
56
|
+
// tests parsing of complex monomer and one as a side chain as well
|
|
57
|
+
'RNA1{[dhp]([m1A])p.r(T)p.r(T)p.r(G)p}$$$$V2.0',
|
|
58
|
+
// tests complex monomer as side chain
|
|
59
|
+
'RNA1{r([mo6pur])p.r(T)p.r(T)p.r(G)p}$$$$V2.0',
|
|
60
|
+
// test complex monomer, side chain and linker
|
|
61
|
+
'RNA1{[afhna]([mo6pur])[36SS].r(T)p}$$$$V2.0'
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const complexMonomersExpected = [
|
|
65
|
+
['L-hArg(Et,Et)', 'E', 'F', 'G', 'C', 'E'],
|
|
66
|
+
['L-hArg(Et,Et)', 'L-hArg(Et,Et)', 'E', 'F', 'G'],
|
|
67
|
+
['dhp', 'm1A', 'p', 'r', 'T', 'p', 'r', 'T', 'p', 'r', 'G', 'p'],
|
|
68
|
+
['r', 'mo6pur', 'p', 'r', 'T', 'p', 'r', 'T', 'p', 'r', 'G', 'p'],
|
|
69
|
+
['afhna', 'mo6pur', '36SS', 'r', 'T', 'p']
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const complexTestNames = [
|
|
73
|
+
'comples-monomer', 'complex-monomer+side-chain', 'complex-monomer+side-chain-RNA',
|
|
74
|
+
'complex-side-chain', 'complex+side-chain+linker'
|
|
75
|
+
];
|
|
76
|
+
test('parseHelm', async () => {
|
|
50
77
|
const expectedResults = [
|
|
51
78
|
['meI', 'hHis', 'Aca', 'N', 'T', 'dK', 'Thr_PO3H2', 'D-Tyr_Et', 'Aze', 'dV', 'E', 'Phe_4Me'],
|
|
52
79
|
['A', 'C', 'T', 'G', 'W', 'E', 'Q'],
|
|
@@ -55,14 +82,24 @@ category('Helm', () => {
|
|
|
55
82
|
];
|
|
56
83
|
const helmStrings = [
|
|
57
84
|
'PEPTIDE1{meI.hHis.Aca.N.T.dK.Thr_PO3H2.Aca.D-Tyr_Et.Aze.dV.E.N.dV.Phe_4Me}$$$',
|
|
85
|
+
// eslint-disable-next-line max-len
|
|
58
86
|
'PEPTIDE1{A.C.T.G.C.T.W.G.T.W.E.C.W.C.Q.W}|PEPTIDE2{A.C.T.G.C.T.W.G.T.W.E.Q}$PEPTIDE1,PEPTIDE1,5:R3-14:R3|PEPTIDE2,PEPTIDE1,2:R3-12:R3$$$',
|
|
59
87
|
'RNA1{R(U)P.R(T)P}|RNA2{P.R(A)P.R(A)}$RNA1,RNA2,2:pair-6:pair|RNA1,RNA2,5:pair-3:pair$$$',
|
|
60
88
|
'PEPTIDE1{A.*.G.C}$$$$V2.0'
|
|
61
89
|
];
|
|
62
90
|
for (let idx = 0; idx < helmStrings.length; ++idx) {
|
|
63
|
-
|
|
64
|
-
|
|
91
|
+
const monomerSet = new Set(parseHelm(helmStrings[idx]));
|
|
92
|
+
const monomerArray = Array.from(monomerSet);
|
|
65
93
|
expect(JSON.stringify(monomerArray), JSON.stringify(expectedResults[idx]));
|
|
66
94
|
}
|
|
67
95
|
});
|
|
96
|
+
complexMonomers.forEach((helm, i) => {
|
|
97
|
+
test(complexTestNames[i], async () => {
|
|
98
|
+
const monomersArray = parseHelm(helm);
|
|
99
|
+
monomersArray.sort();
|
|
100
|
+
const expected = complexMonomersExpected[i];
|
|
101
|
+
expected.sort();
|
|
102
|
+
expectArray(monomersArray, expected);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
68
105
|
});
|
package/src/utils.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
import { RGROUP_CAP_GROUP_NAME, RGROUP_CAP_GROUP_SMILES, jsonSdfMonomerLibDict, MONOMER_SYMBOL, RGROUP_ALTER_ID, RGROUPS, RGROUP_LABEL, SDF_MONOMER_NAME } from "./constants";
|
|
3
2
|
|
|
4
3
|
export function getParts(subParts: string[], s: string): string[] {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const j = 0;
|
|
5
|
+
const allParts: string[] = [];
|
|
7
6
|
for (let k = 0; k < subParts.length; ++k) {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const indexOfMonomer = s.indexOf(subParts[k]);
|
|
8
|
+
const helmBeforeMonomer = s.slice(j, indexOfMonomer);
|
|
10
9
|
allParts.push(helmBeforeMonomer);
|
|
11
10
|
allParts.push(subParts[k]);
|
|
12
11
|
s = s.substring(indexOfMonomer + subParts[k].length);
|
|
@@ -16,32 +15,44 @@ export function getParts(subParts: string[], s: string): string[] {
|
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export function parseHelm(s: string) {
|
|
19
|
-
|
|
18
|
+
const sections = split(s, '$');
|
|
20
19
|
s = sections[0];
|
|
21
|
-
|
|
20
|
+
const monomers = [];
|
|
22
21
|
//@ts-ignore
|
|
23
22
|
if (!scil.Utils.isNullOrEmpty(s)) {
|
|
24
|
-
|
|
25
|
-
for (
|
|
26
|
-
|
|
23
|
+
const seqs = split(s, '|');
|
|
24
|
+
for (let i = 0; i < seqs.length; ++i) {
|
|
25
|
+
const e = detachAnnotation(seqs[i]);
|
|
27
26
|
s = e.str;
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
let p = s.indexOf('{');
|
|
30
29
|
|
|
31
30
|
s = s.substring(p + 1);
|
|
32
31
|
p = s.indexOf('}');
|
|
33
32
|
s = s.substring(0, p);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
for (
|
|
37
|
-
if (monomer
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
const ss = split(s, '.');
|
|
35
|
+
for (const monomer of ss) {
|
|
36
|
+
if (!monomer || monomer === '') continue;
|
|
37
|
+
if (monomer.startsWith('[') && monomer.includes(']')) {
|
|
38
|
+
const element = monomer.substring(1, monomer.indexOf(']'));
|
|
39
|
+
monomers.push(element);
|
|
40
|
+
const residue = monomer.substring(monomer.indexOf(']') + 1);
|
|
41
|
+
ss.push(residue);
|
|
42
|
+
} else if (monomer.includes('[') && monomer.endsWith(']')) {
|
|
43
|
+
const element = monomer.substring(monomer.lastIndexOf('[') + 1, monomer.length - 1);
|
|
44
|
+
monomers.push(element);
|
|
45
|
+
const residue = monomer.substring(0, monomer.lastIndexOf('['));
|
|
46
|
+
ss.push(residue);
|
|
47
|
+
} else if (monomer.includes('(') && monomer.includes(')')) {
|
|
48
|
+
// here we only want to split the string at first '(' and last ')'
|
|
49
|
+
// because entries like [L-hArg(Et,Et)]([L-hArg(Et,Et)]) where L-hArg(Et,Et) is a single monomer
|
|
50
|
+
const firstPiece = monomer.substring(0, monomer.indexOf('('));
|
|
51
|
+
const thirdPiece = monomer.substring(monomer.lastIndexOf(')') + 1);
|
|
52
|
+
const secondPiece = monomer.substring(firstPiece.length + 1, monomer.length - thirdPiece.length - 1);
|
|
53
|
+
const elements =[firstPiece, secondPiece, thirdPiece];
|
|
54
|
+
for (const el of elements)
|
|
55
|
+
ss.push(el);
|
|
45
56
|
} else {
|
|
46
57
|
monomers.push(monomer);
|
|
47
58
|
}
|
|
@@ -50,32 +61,33 @@ export function parseHelm(s: string) {
|
|
|
50
61
|
}
|
|
51
62
|
return monomers;
|
|
52
63
|
}
|
|
53
|
-
|
|
64
|
+
/* this function returns names of monomers that are NOT in the monomer library */
|
|
54
65
|
export function findMonomers(helmString: string) {
|
|
55
66
|
//@ts-ignore
|
|
56
67
|
const types = Object.keys(org.helm.webeditor.monomerTypeList());
|
|
57
68
|
const monomers: any = [];
|
|
58
|
-
const
|
|
59
|
-
for (
|
|
69
|
+
const monomerNames: any = [];
|
|
70
|
+
for (let i = 0; i < types.length; i++) {
|
|
60
71
|
//@ts-ignore
|
|
72
|
+
// eslint-disable-next-line new-cap
|
|
61
73
|
monomers.push(new scil.helm.Monomers.getMonomerSet(types[i]));
|
|
62
|
-
Object.keys(monomers[i]).forEach(k => {
|
|
63
|
-
|
|
74
|
+
Object.keys(monomers[i]).forEach((k) => {
|
|
75
|
+
monomerNames.push(monomers[i][k].id);
|
|
64
76
|
});
|
|
65
77
|
}
|
|
66
|
-
const
|
|
67
|
-
return new Set(
|
|
78
|
+
const splitString = parseHelm(helmString);
|
|
79
|
+
return new Set(splitString.filter((val) => !monomerNames.includes(val)));
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
function split(s: string, sep: string) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for (
|
|
78
|
-
|
|
83
|
+
const ret = [];
|
|
84
|
+
let frag = '';
|
|
85
|
+
let parentheses = 0;
|
|
86
|
+
let bracket = 0;
|
|
87
|
+
let braces = 0;
|
|
88
|
+
let quote = 0;
|
|
89
|
+
for (let i = 0; i < s.length; ++i) {
|
|
90
|
+
let c = s.substring(i, i + 1);
|
|
79
91
|
if (c == sep && bracket == 0 && parentheses == 0 && braces == 0 && quote == 0) {
|
|
80
92
|
ret.push(frag);
|
|
81
93
|
frag = '';
|
|
@@ -84,7 +96,7 @@ function split(s: string, sep: string) {
|
|
|
84
96
|
if (quote > 0) {
|
|
85
97
|
if (c == '\\' && i + 1 < s.length) {
|
|
86
98
|
++i;
|
|
87
|
-
|
|
99
|
+
const c2 = s.substring(i, i + 1);
|
|
88
100
|
frag += c2;
|
|
89
101
|
c += c2;
|
|
90
102
|
}
|
|
@@ -92,18 +104,19 @@ function split(s: string, sep: string) {
|
|
|
92
104
|
if (c == '\"') {
|
|
93
105
|
if (!(i > 0 && s.substring(i - 1, i) == '\\'))
|
|
94
106
|
quote = quote == 0 ? 1 : 0;
|
|
95
|
-
} else if (c == '[')
|
|
107
|
+
} else if (c == '[') {
|
|
96
108
|
++bracket;
|
|
97
|
-
else if (c == ']')
|
|
109
|
+
} else if (c == ']') {
|
|
98
110
|
--bracket;
|
|
99
|
-
else if (c == '(')
|
|
111
|
+
} else if (c == '(') {
|
|
100
112
|
++parentheses;
|
|
101
|
-
else if (c == ')')
|
|
113
|
+
} else if (c == ')') {
|
|
102
114
|
--parentheses;
|
|
103
|
-
else if (c == '{')
|
|
115
|
+
} else if (c == '{') {
|
|
104
116
|
++braces;
|
|
105
|
-
else if (c == '}')
|
|
117
|
+
} else if (c == '}') {
|
|
106
118
|
--braces;
|
|
119
|
+
}
|
|
107
120
|
}
|
|
108
121
|
}
|
|
109
122
|
ret.push(frag);
|
|
@@ -111,19 +124,19 @@ function split(s: string, sep: string) {
|
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
function detachAnnotation(s: string) {
|
|
114
|
-
|
|
127
|
+
const ret = _detachAppendix(s, '\"');
|
|
115
128
|
if (ret.tag != null)
|
|
116
129
|
return ret;
|
|
117
130
|
|
|
118
|
-
|
|
131
|
+
const r = _detachAppendix(s, '\'');
|
|
119
132
|
return {tag: ret.tag, repeat: r.tag, str: r.str};
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
function _detachAppendix(s: string, c: string) {
|
|
123
|
-
|
|
136
|
+
let tag = null;
|
|
124
137
|
//@ts-ignore
|
|
125
138
|
if (scil.Utils.endswith(s, c)) {
|
|
126
|
-
|
|
139
|
+
let p = s.length - 1;
|
|
127
140
|
while (p > 0) {
|
|
128
141
|
p = s.lastIndexOf(c, p - 1);
|
|
129
142
|
if (p <= 0 || s.substring(p - 1, p) != '\\')
|
package/tsconfig.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// "incremental": true, /* Enable incremental compilation */
|
|
7
7
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
|
8
8
|
"module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
|
9
|
-
"lib": ["
|
|
9
|
+
"lib": ["ES2022", "dom"], /* Specify library files to be included in the compilation. */
|
|
10
10
|
"allowJs": true, /* Allow javascript files to be compiled. */
|
|
11
11
|
// "checkJs": true, /* Report errors in .js files. */
|
|
12
12
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|