@datagrok/sequence-translator 0.0.8 → 0.0.9
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/jest.config.js +33 -0
- package/package.json +12 -4
- package/setup.cmd +1 -1
- package/src/ICDs.ts +3 -0
- package/src/IDPs.ts +3 -0
- package/src/__jest__/remote.test.ts +49 -0
- package/src/__jest__/test-node.ts +96 -0
- package/src/package-test.ts +2 -1
- package/src/package.ts +99 -68
- package/src/sources.ts +3 -0
- package/src/structures-works/from-monomers.ts +40 -9
- package/src/structures-works/map.ts +28 -6
- package/src/structures-works/mol-transformations.ts +428 -0
- package/src/structures-works/save-sense-antisense.ts +29 -22
- package/src/users.ts +3 -0
- package/test-SequenceTranslator-2e08c8e54bde-1367d435.html +245 -0
- package/tsconfig.json +2 -1
package/jest.config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"roots": [
|
|
3
|
+
"<rootDir>/src"
|
|
4
|
+
],
|
|
5
|
+
"testMatch": [
|
|
6
|
+
"**/__jest__/**/*.test.+(ts|tsx)"
|
|
7
|
+
],
|
|
8
|
+
moduleFileExtensions: [
|
|
9
|
+
'ts',
|
|
10
|
+
'js',
|
|
11
|
+
],
|
|
12
|
+
"transform": {
|
|
13
|
+
"^.+\\.(ts|tsx)$": "ts-jest"
|
|
14
|
+
},
|
|
15
|
+
transformIgnorePatterns: ['^.+\\.js$'],
|
|
16
|
+
globals: {
|
|
17
|
+
'ts-jest': {
|
|
18
|
+
'tsconfig': {
|
|
19
|
+
"target": "es6",
|
|
20
|
+
"module": "es2020",
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
reporters: [
|
|
25
|
+
"default",
|
|
26
|
+
[
|
|
27
|
+
"./node_modules/jest-html-reporter",
|
|
28
|
+
{
|
|
29
|
+
"includeConsoleLog": true
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
]
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/sequence-translator",
|
|
3
|
-
"friendlyName": "
|
|
4
|
-
"version": "0.0.
|
|
3
|
+
"friendlyName": "Sequence Translator",
|
|
4
|
+
"version": "0.0.9",
|
|
5
5
|
"description": "",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@datagrok-libraries/utils": "^0.1.0",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"debug-sequencetranslator-public": "grok publish public --rebuild",
|
|
24
24
|
"release-sequencetranslator-public": "grok publish public --rebuild --release",
|
|
25
25
|
"debug-sequencetranslator-local": "grok publish local --rebuild",
|
|
26
|
-
"release-sequencetranslator-local": "grok publish local --rebuild --release"
|
|
26
|
+
"release-sequencetranslator-local": "grok publish local --rebuild --release",
|
|
27
|
+
"test": "set HOST=dev && jest",
|
|
28
|
+
"test-dev": "set HOST=dev && jest",
|
|
29
|
+
"test-local": "set HOST=localhost && jest"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@typescript-eslint/eslint-plugin": "^4.29.1",
|
|
@@ -32,6 +35,11 @@
|
|
|
32
35
|
"eslint": "^7.32.0",
|
|
33
36
|
"eslint-config-google": "^0.14.0",
|
|
34
37
|
"webpack": "^5.31.0",
|
|
35
|
-
"webpack-cli": "^4.6.0"
|
|
38
|
+
"webpack-cli": "^4.6.0",
|
|
39
|
+
"@types/jest": "latest",
|
|
40
|
+
"jest": "latest",
|
|
41
|
+
"jest-html-reporter": "latest",
|
|
42
|
+
"puppeteer": "^13.1.2",
|
|
43
|
+
"ts-jest": "^27.1.2"
|
|
36
44
|
}
|
|
37
45
|
}
|
package/setup.cmd
CHANGED
package/src/ICDs.ts
ADDED
package/src/IDPs.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as utils from './test-node';
|
|
6
|
+
import puppeteer from 'puppeteer';
|
|
7
|
+
|
|
8
|
+
const P_START_TIMEOUT: number = 100000;
|
|
9
|
+
let browser: puppeteer.Browser;
|
|
10
|
+
let page: puppeteer.Page;
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
let out = await utils.getBrowserPage(puppeteer);
|
|
14
|
+
browser = out.browser;
|
|
15
|
+
page = out.page;
|
|
16
|
+
}, P_START_TIMEOUT);
|
|
17
|
+
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
await browser.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('TEST', async () => {
|
|
23
|
+
const target_package:string = process.env.TARGET_PACKAGE ?? 'SequenceTranslator';
|
|
24
|
+
console.log(`Testing ${target_package} package`);
|
|
25
|
+
|
|
26
|
+
//console.log(require('root-require')('package.json').version);
|
|
27
|
+
let r = await page.evaluate((target_package):Promise<object> => {
|
|
28
|
+
return new Promise<object>((resolve, reject) => {
|
|
29
|
+
(<any>window).grok.functions.eval(target_package + ':test()').then((df: any) => {
|
|
30
|
+
let cStatus = df.columns.byName('success');
|
|
31
|
+
let cMessage = df.columns.byName('result');
|
|
32
|
+
let cCat = df.columns.byName('category');
|
|
33
|
+
let cName = df.columns.byName('name');
|
|
34
|
+
let failed = false;
|
|
35
|
+
let report = '';
|
|
36
|
+
for (let i = 0; i < df.rowCount; i++)
|
|
37
|
+
if (!cStatus.get(i)) {
|
|
38
|
+
report += `${cCat.get(i)}.${cName.get(i)}: ${cMessage.get(i)}\n`;
|
|
39
|
+
failed = true;
|
|
40
|
+
}
|
|
41
|
+
resolve({report, failed});
|
|
42
|
+
}).catch((e: any) => reject(e));
|
|
43
|
+
});
|
|
44
|
+
}, target_package);
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
console.log(r.report);
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
expect(r.failed).toBe(false);
|
|
49
|
+
}, 100000);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import * as yaml from 'js-yaml';
|
|
6
|
+
const fetch = require('node-fetch');
|
|
7
|
+
|
|
8
|
+
export async function getToken(url: string, key: string) {
|
|
9
|
+
let response = await fetch(`${url}/users/login/dev/${key}`, {method: 'POST'});
|
|
10
|
+
let json = await response.json();
|
|
11
|
+
if (json.isSuccess == true)
|
|
12
|
+
return json.token;
|
|
13
|
+
else
|
|
14
|
+
throw 'Unable to login to server. Check your dev key';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getWebUrl(url: string, token: string) {
|
|
18
|
+
let response = await fetch(`${url}/admin/plugins/admin/settings`, {headers: {Authorization: token}});
|
|
19
|
+
let json = await response.json();
|
|
20
|
+
return json.settings.webRoot;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const grokDir = path.join(os.homedir(), '.grok');
|
|
24
|
+
const confPath = path.join(grokDir, 'config.yaml');
|
|
25
|
+
|
|
26
|
+
function mapURL(conf: Config): Indexable {
|
|
27
|
+
let urls: Indexable = {};
|
|
28
|
+
for (let server in conf.servers) {
|
|
29
|
+
urls[conf['servers'][server]['url']] = conf['servers'][server];
|
|
30
|
+
}
|
|
31
|
+
return urls;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getDevKey(hostKey: string): {url: string, key: string} {
|
|
35
|
+
let config = yaml.load(fs.readFileSync(confPath, 'utf8')) as any;
|
|
36
|
+
let host = hostKey == '' ? config.default : hostKey;
|
|
37
|
+
host = host.trim();
|
|
38
|
+
let urls = mapURL(config);
|
|
39
|
+
let key = '';
|
|
40
|
+
let url = '';
|
|
41
|
+
try {
|
|
42
|
+
let url = new URL(host).href;
|
|
43
|
+
if (url.endsWith('/')) url = url.slice(0, -1);
|
|
44
|
+
if (url in urls) key = config['servers'][urls[url]]['key'];
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (config['servers'][host] == null)
|
|
47
|
+
throw `Unknown server alias. Please add it to ${confPath}`;
|
|
48
|
+
url = config['servers'][host]['url'];
|
|
49
|
+
key = config['servers'][host]['key'];
|
|
50
|
+
}
|
|
51
|
+
return {url, key};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getBrowserPage(puppeteer: any): Promise<{browser: any, page: any}> {
|
|
55
|
+
let url:string = process.env.HOST ?? '';
|
|
56
|
+
let cfg = getDevKey(url);
|
|
57
|
+
url = cfg.url;
|
|
58
|
+
|
|
59
|
+
let key = cfg.key;
|
|
60
|
+
let token = await getToken(url, key);
|
|
61
|
+
url = await getWebUrl(url, token);
|
|
62
|
+
console.log(`Using web root: ${url}`);
|
|
63
|
+
|
|
64
|
+
let browser = await puppeteer.launch({
|
|
65
|
+
args: ['--disable-dev-shm-usage', '--disable-features=site-per-process'],
|
|
66
|
+
ignoreHTTPSErrors: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let page = await browser.newPage();
|
|
70
|
+
await page.goto(`${url}/oauth/`);
|
|
71
|
+
await page.setCookie({name: 'auth', value: token});
|
|
72
|
+
await page.evaluate((token: any) => {
|
|
73
|
+
window.localStorage.setItem('auth', token);
|
|
74
|
+
}, token);
|
|
75
|
+
await page.goto(url);
|
|
76
|
+
try {
|
|
77
|
+
await page.waitForSelector('.grok-preloader');
|
|
78
|
+
await page.waitForFunction(() => document.querySelector('.grok-preloader') == null, {timeout: 100000});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return {browser, page};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
interface Config {
|
|
87
|
+
servers: {
|
|
88
|
+
[alias: string]: {
|
|
89
|
+
url: string,
|
|
90
|
+
key: string
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
default: string,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface Indexable { [key: string]: any }
|
package/src/package-test.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
import {runTests} from '@datagrok-libraries/utils/src/test';
|
|
2
|
+
import {runTests, tests} from '@datagrok-libraries/utils/src/test';
|
|
3
3
|
import './tests/smiles-tests';
|
|
4
4
|
|
|
5
5
|
export const _package = new DG.Package();
|
|
6
|
+
export {tests}
|
|
6
7
|
|
|
7
8
|
//name: test
|
|
8
9
|
//output: dataframe result
|
package/src/package.ts
CHANGED
|
@@ -6,9 +6,14 @@ import * as OCL from 'openchemlib/full.js';
|
|
|
6
6
|
import $ from 'cash-dom';
|
|
7
7
|
import {defineAxolabsPattern} from './defineAxolabsPattern';
|
|
8
8
|
import {saveSenseAntiSense} from './structures-works/save-sense-antisense';
|
|
9
|
-
import {sequenceToSmiles} from './structures-works/from-monomers';
|
|
9
|
+
import {sequenceToSmiles, sequenceToMolV3000} from './structures-works/from-monomers';
|
|
10
10
|
import {convertSequence, undefinedInputSequence} from './structures-works/sequence-codes-tools';
|
|
11
|
+
import {map, COL_NAMES, MODIFICATIONS} from './structures-works/map';
|
|
11
12
|
import {SALTS_CSV} from './salts';
|
|
13
|
+
import {USERS_CSV} from './users';
|
|
14
|
+
import {ICDS} from './ICDs';
|
|
15
|
+
import {SOURCES} from './sources';
|
|
16
|
+
import {IDPS} from './IDPs';
|
|
12
17
|
|
|
13
18
|
export const _package = new DG.Package();
|
|
14
19
|
|
|
@@ -72,12 +77,8 @@ export function sequenceTranslator(): void {
|
|
|
72
77
|
});
|
|
73
78
|
switchInput.enabled = true;
|
|
74
79
|
} else {
|
|
75
|
-
asoGapmersGrid.onCellPrepare(function(gc) {
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
omeAndFluoroGrid.onCellPrepare(function(gc) {
|
|
79
|
-
gc.style.backColor = 0xFFFFFFFF;
|
|
80
|
-
});
|
|
80
|
+
asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
|
|
81
|
+
omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
outputTableDiv.append(
|
|
@@ -92,18 +93,18 @@ export function sequenceTranslator(): void {
|
|
|
92
93
|
const canvas = ui.canvas(300, 170);
|
|
93
94
|
canvas.addEventListener('click', () => {
|
|
94
95
|
const canv = ui.canvas($(window).width(), $(window).height());
|
|
95
|
-
const
|
|
96
|
+
const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true);
|
|
96
97
|
// @ts-ignore
|
|
97
|
-
OCL.StructureView.drawMolecule(canv, OCL.Molecule.
|
|
98
|
+
OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
98
99
|
ui.dialog('Molecule: ' + inputSequenceField.value)
|
|
99
100
|
.add(canv)
|
|
100
101
|
.showModal(true);
|
|
101
102
|
});
|
|
102
103
|
$(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
|
|
103
104
|
$(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
|
|
104
|
-
const
|
|
105
|
+
const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true);
|
|
105
106
|
// @ts-ignore
|
|
106
|
-
OCL.StructureView.drawMolecule(canvas, OCL.Molecule.
|
|
107
|
+
OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
107
108
|
moleculeSvgDiv.append(canvas);
|
|
108
109
|
} else
|
|
109
110
|
moleculeSvgDiv.innerHTML = '';
|
|
@@ -216,8 +217,7 @@ export function sequenceTranslator(): void {
|
|
|
216
217
|
|
|
217
218
|
const topPanel = [
|
|
218
219
|
ui.iconFA('download', () => {
|
|
219
|
-
const
|
|
220
|
-
const result = `${OCL.Molecule.fromSmiles(smiles).toMolfile()}\n`;
|
|
220
|
+
const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''));
|
|
221
221
|
const element = document.createElement('a');
|
|
222
222
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
|
|
223
223
|
element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
|
|
@@ -250,21 +250,23 @@ export function sequenceTranslator(): void {
|
|
|
250
250
|
async function saveTableAsSdFile(table: DG.DataFrame) {
|
|
251
251
|
if (!table.columns.contains('Compound Name')) {
|
|
252
252
|
grok.shell.warning(
|
|
253
|
-
'File saved without columns \'
|
|
253
|
+
'File saved without columns \'' +
|
|
254
|
+
[COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
|
|
255
|
+
COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
|
|
256
|
+
);
|
|
254
257
|
}
|
|
255
|
-
const structureColumn = table.
|
|
258
|
+
const structureColumn = table.col(COL_NAMES.SEQUENCE)!;
|
|
259
|
+
const typeColumn = table.col(COL_NAMES.TYPE)!;
|
|
256
260
|
let result = '';
|
|
257
261
|
for (let i = 0; i < table.rowCount; i++) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
result += (typeColumn.get(i) == 'SS') ?
|
|
263
|
+
sequenceToMolV3000(structureColumn.get(i), false, true) + '\n' + `> <Sequence>\nSense Strand\n\n` :
|
|
264
|
+
sequenceToMolV3000(structureColumn.get(i), true, true) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
|
|
265
|
+
for (const col of table.columns) {
|
|
266
|
+
if (col.name != COL_NAMES.SEQUENCE)
|
|
263
267
|
result += `> <${col.name}>\n${col.get(i)}\n\n`;
|
|
264
|
-
result += '$$$$';
|
|
265
|
-
} catch (error) {
|
|
266
|
-
console.error(error);
|
|
267
268
|
}
|
|
269
|
+
result += '$$$$\n\n';
|
|
268
270
|
}
|
|
269
271
|
const element = document.createElement('a');
|
|
270
272
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
|
|
@@ -272,83 +274,112 @@ async function saveTableAsSdFile(table: DG.DataFrame) {
|
|
|
272
274
|
element.click();
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
const weightsObj: {[code: string]: number} = {};
|
|
278
|
+
for (const synthesizer of Object.keys(map)) {
|
|
279
|
+
for (const technology of Object.keys(map[synthesizer])) {
|
|
280
|
+
for (const code of Object.keys(map[synthesizer][technology]))
|
|
281
|
+
weightsObj[code] = map[synthesizer][technology][code].weight;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const [key, value] of Object.entries(MODIFICATIONS))
|
|
285
|
+
weightsObj[key] = value.molecularWeight;
|
|
286
|
+
|
|
287
|
+
function sortByStringLengthInDescendingOrder(array: string[]): string[] {
|
|
288
|
+
return array.sort(function(a, b) {return b.length - a.length;});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function stringifyItems(items: string[]): string {
|
|
292
|
+
return '["' + items.join('", "') + '"]';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
|
|
296
|
+
const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
|
|
297
|
+
let weight = 0;
|
|
298
|
+
let i = 0;
|
|
299
|
+
while (i < sequence.length) {
|
|
300
|
+
const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
|
|
301
|
+
weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
|
|
302
|
+
i += matchedCode!.length;
|
|
303
|
+
}
|
|
304
|
+
return weight - 61.97;
|
|
305
|
+
}
|
|
306
|
+
|
|
275
307
|
//tags: autostart
|
|
276
308
|
export function autostartOligoSdFileSubscription() {
|
|
277
309
|
grok.events.onViewAdded.subscribe((v: any) => {
|
|
278
|
-
if (v.type == 'TableView' && v.dataFrame.columns.contains(
|
|
310
|
+
if (v.type == 'TableView' && v.dataFrame.columns.contains(COL_NAMES.TYPE))
|
|
279
311
|
oligoSdFile(v.dataFrame);
|
|
280
312
|
});
|
|
281
313
|
}
|
|
282
314
|
|
|
283
315
|
export function oligoSdFile(table: DG.DataFrame) {
|
|
284
316
|
const saltsDf = DG.DataFrame.fromCsv(SALTS_CSV);
|
|
317
|
+
const usersDf = DG.DataFrame.fromCsv(USERS_CSV);
|
|
318
|
+
const sourcesDf = DG.DataFrame.fromCsv(SOURCES);
|
|
319
|
+
const icdsDf = DG.DataFrame.fromCsv(ICDS);
|
|
320
|
+
const idpsDf = DG.DataFrame.fromCsv(IDPS);
|
|
321
|
+
|
|
285
322
|
function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
|
|
286
|
-
if (t.columns.contains(
|
|
323
|
+
if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
|
|
287
324
|
return grok.shell.error('Columns already exist!');
|
|
288
325
|
|
|
289
|
-
|
|
290
|
-
|
|
326
|
+
const sequence = t.col(COL_NAMES.SEQUENCE)!;
|
|
327
|
+
const equivalents = t.col(COL_NAMES.EQUIVALENTS)!;
|
|
291
328
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const equivalents = t.col('Equivalents')!;
|
|
295
|
-
|
|
296
|
-
t.columns.addNewString('Compound Name').init((i: number) => sequence.get(i));
|
|
297
|
-
t.columns.addNewString('Compound Comments').init((i: number) => (i > 0 && i % 2 == 0) ?
|
|
329
|
+
t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
|
|
330
|
+
t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
|
|
298
331
|
sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
|
|
299
332
|
sequence.getString(i),
|
|
300
333
|
);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
t.columns.addNewFloat(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const n = (v == null) ? 0 : chargeCol[saltNames.indexOf(salt.get(i))];
|
|
308
|
-
return n * equivalents.get(i);
|
|
334
|
+
t.columns.addNewFloat(COL_NAMES.CPD_MW)
|
|
335
|
+
.init((i: number) => molecularWeight(sequence.get(i), weightsObj));
|
|
336
|
+
const mwCol = t.col(COL_NAMES.CPD_MW)!;
|
|
337
|
+
t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
|
|
338
|
+
const mw = mwCol.get(i);
|
|
339
|
+
return mw * equivalents.get(i);
|
|
309
340
|
});
|
|
310
|
-
t.columns.addNewCalculated(
|
|
341
|
+
t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
|
|
342
|
+
'${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
|
|
343
|
+
);
|
|
311
344
|
|
|
312
345
|
addColumnsPressed = true;
|
|
313
346
|
return newDf = t;
|
|
314
347
|
}
|
|
315
348
|
|
|
316
|
-
const columnsOrder = ['Chemistry', 'Number', 'Type', 'Chemistry Name', 'Internal compound ID',
|
|
317
|
-
'IDP', 'Sequence', 'Compound Name', 'Compound Comments', 'Salt', 'Equivalents', 'Purity', 'Cpd MW', 'Salt mass',
|
|
318
|
-
'Batch MW', 'Source', 'ICD', 'Owner'];
|
|
319
349
|
let newDf: DG.DataFrame;
|
|
320
350
|
let addColumnsPressed = false;
|
|
321
351
|
|
|
322
352
|
const d = ui.div([
|
|
323
353
|
ui.icons.edit(() => {
|
|
324
354
|
d.innerHTML = '';
|
|
355
|
+
if (table.col(COL_NAMES.IDP)!.type != DG.COLUMN_TYPE.STRING)
|
|
356
|
+
table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
|
|
325
357
|
d.append(
|
|
326
|
-
ui.link('Add Columns',
|
|
327
|
-
|
|
328
|
-
grok.shell.tableView(table.name).grid.columns.setOrder(
|
|
329
|
-
}, 'Add columns:
|
|
358
|
+
ui.link('Add Columns', () => {
|
|
359
|
+
addColumns(table, saltsDf);
|
|
360
|
+
grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
|
|
361
|
+
}, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
|
|
362
|
+
COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
|
|
330
363
|
ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
|
|
331
364
|
);
|
|
332
|
-
const view = grok.shell.getTableView(table.name);
|
|
333
|
-
const typeCol = view.grid.col('Type')!;
|
|
334
|
-
const saltCol = view.grid.col('Salt')!;
|
|
335
|
-
saltCol.cellType = 'html';
|
|
336
|
-
typeCol.cellType = 'html';
|
|
337
|
-
view.grid.onCellPrepare(function(gc: DG.GridCell) {
|
|
338
|
-
if (gc.isTableCell) {
|
|
339
|
-
if (gc.gridColumn.name == 'Type')
|
|
340
|
-
gc.style.element = ui.choiceInput('', gc.cell.value, ['AS', 'SS', 'Duplex']).root;
|
|
341
|
-
else if (gc.gridColumn.name == 'Salt') {
|
|
342
|
-
gc.style.element = ui.choiceInput('', gc.cell.value, saltsDf.columns.byIndex(1).toList(), () => {
|
|
343
|
-
view.dataFrame.col('Salt')!.set(gc.gridRow, '');
|
|
344
|
-
}).root;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
365
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
366
|
+
const view = grok.shell.getTableView(table.name)!;
|
|
367
|
+
|
|
368
|
+
view.table!.col(COL_NAMES.TYPE)!.setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
|
|
369
|
+
view.table!.col(COL_NAMES.OWNER)!.setTag(DG.TAGS.CHOICES, stringifyItems(usersDf.columns.byIndex(0).toList()));
|
|
370
|
+
view.table!.col(COL_NAMES.SALT)!.setTag(DG.TAGS.CHOICES, stringifyItems(saltsDf.columns.byIndex(1).toList()));
|
|
371
|
+
view.table!.col(COL_NAMES.SOURCE)!.setTag(DG.TAGS.CHOICES, stringifyItems(sourcesDf.columns.byIndex(0).toList()));
|
|
372
|
+
view.table!.col(COL_NAMES.ICD)!.setTag(DG.TAGS.CHOICES, stringifyItems(icdsDf.columns.byIndex(0).toList()));
|
|
373
|
+
view.table!.col(COL_NAMES.IDP)!.setTag(DG.TAGS.CHOICES, stringifyItems(idpsDf.columns.byIndex(0).toList()));
|
|
374
|
+
|
|
375
|
+
grok.events.onContextMenu.subscribe((args) => {
|
|
376
|
+
if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
|
|
377
|
+
.includes(args.args.context.table.currentCol.name)) {
|
|
378
|
+
args.args.menu.item('Fill Column With Value', () => {
|
|
379
|
+
const v = args.args.context.table.currentCell.value;
|
|
380
|
+
args.args.context.table.currentCell.column.init(v);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
352
383
|
});
|
|
353
384
|
}),
|
|
354
385
|
]);
|
package/src/sources.ts
ADDED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS} from './map';
|
|
2
2
|
import {isValidSequence} from './sequence-codes-tools';
|
|
3
|
+
import {getNucleotidesMol} from './mol-transformations';
|
|
4
|
+
|
|
5
|
+
export function sequenceToMolV3000(sequence: string, inverted: boolean = false, oclRender: boolean = false): string {
|
|
6
|
+
const obj = getObjectWithCodesAndSmiles(sequence);
|
|
7
|
+
let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
|
|
8
|
+
let i = 0;
|
|
9
|
+
const smilesCodes:string[] = [];
|
|
10
|
+
const codesList = [];
|
|
11
|
+
const links = ['s', 'ps', '*'];
|
|
12
|
+
const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
|
|
13
|
+
const dropdowns = Object.keys(MODIFICATIONS);
|
|
14
|
+
codes = codes.concat(dropdowns);
|
|
15
|
+
while (i < sequence.length) {
|
|
16
|
+
const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
|
|
17
|
+
i += code.length;
|
|
18
|
+
inverted ? codesList.unshift(code) : codesList.push(code);
|
|
19
|
+
}
|
|
20
|
+
for (let i = 0; i < codesList.length; i++) {
|
|
21
|
+
if (dropdowns.includes(codesList[i])) {
|
|
22
|
+
smilesCodes.push((i >= codesList.length / 2) ?
|
|
23
|
+
MODIFICATIONS[codesList[i]].right : MODIFICATIONS[codesList[i]].left);
|
|
24
|
+
smilesCodes.push(stadardPhosphateLinkSmiles);
|
|
25
|
+
} else {
|
|
26
|
+
if (links.includes(codesList[i]) ||
|
|
27
|
+
includesStandardLinkAlready.includes(codesList[i]) ||
|
|
28
|
+
(i < codesList.length - 1 && links.includes(codesList[i + 1]))
|
|
29
|
+
)
|
|
30
|
+
smilesCodes.push(obj[codesList[i]]);
|
|
31
|
+
else {
|
|
32
|
+
smilesCodes.push(obj[codesList[i]]);
|
|
33
|
+
smilesCodes.push(stadardPhosphateLinkSmiles);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return getNucleotidesMol(smilesCodes, oclRender);
|
|
39
|
+
}
|
|
3
40
|
|
|
4
41
|
export function sequenceToSmiles(sequence: string, inverted: boolean = false): string {
|
|
5
42
|
const obj = getObjectWithCodesAndSmiles(sequence);
|
|
@@ -18,15 +55,9 @@ export function sequenceToSmiles(sequence: string, inverted: boolean = false): s
|
|
|
18
55
|
}
|
|
19
56
|
for (let i = 0; i < codesList.length; i++) {
|
|
20
57
|
if (dropdowns.includes(codesList[i])) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
MODIFICATIONS[codesList[i]].left;
|
|
25
|
-
} else if (i < codesList.length - 1) {
|
|
26
|
-
smiles += (i >= codesList.length / 2) ?
|
|
27
|
-
MODIFICATIONS[codesList[i]].right + stadardPhosphateLinkSmiles:
|
|
28
|
-
MODIFICATIONS[codesList[i]].left + stadardPhosphateLinkSmiles;
|
|
29
|
-
}
|
|
58
|
+
smiles += (i >= codesList.length / 2) ?
|
|
59
|
+
MODIFICATIONS[codesList[i]].right + stadardPhosphateLinkSmiles:
|
|
60
|
+
MODIFICATIONS[codesList[i]].left + stadardPhosphateLinkSmiles;
|
|
30
61
|
} else {
|
|
31
62
|
if (links.includes(codesList[i]) ||
|
|
32
63
|
includesStandardLinkAlready.includes(codesList[i]) ||
|
|
@@ -11,14 +11,36 @@ export const TECHNOLOGIES = {
|
|
|
11
11
|
ASO_GAPMERS: 'For ASO Gapmers',
|
|
12
12
|
SI_RNA: 'For 2\'-OMe and 2\'-F modified siRNA',
|
|
13
13
|
};
|
|
14
|
+
export const COL_NAMES = {
|
|
15
|
+
CHEMISTRY: 'Chemistry',
|
|
16
|
+
NUMBER: 'Number',
|
|
17
|
+
TYPE: 'Type',
|
|
18
|
+
CHEMISTRY_NAME: 'Chemistry Name',
|
|
19
|
+
INTERNAL_COMPOUND_ID: 'Internal compound ID',
|
|
20
|
+
IDP: 'IDP',
|
|
21
|
+
SEQUENCE: 'Sequence',
|
|
22
|
+
COMPOUND_NAME: 'Compound Name',
|
|
23
|
+
COMPOUND_COMMENTS: 'Compound Comments',
|
|
24
|
+
SALT: 'Salt',
|
|
25
|
+
EQUIVALENTS: 'Equivalents',
|
|
26
|
+
PURITY: 'Purity',
|
|
27
|
+
CPD_MW: 'Cpd MW',
|
|
28
|
+
SALT_MASS: 'Salt mass',
|
|
29
|
+
BATCH_MW: 'Batch MW',
|
|
30
|
+
SOURCE: 'Source',
|
|
31
|
+
ICD: 'ICD',
|
|
32
|
+
OWNER: 'Owner',
|
|
33
|
+
};
|
|
14
34
|
// interface CODES {
|
|
15
35
|
// }
|
|
16
|
-
export const MODIFICATIONS: {[index: string]: {left: string, right: string}} = {
|
|
36
|
+
export const MODIFICATIONS: {[index: string]: {molecularWeight: number, left: string, right: string}} = {
|
|
17
37
|
'(invabasic)': {
|
|
38
|
+
molecularWeight: 118.13,
|
|
18
39
|
left: 'O[C@@H]1C[C@@H]O[C@H]1CO',
|
|
19
40
|
right: 'O[C@@H]1C[C@@H]O[C@H]1CO',
|
|
20
41
|
},
|
|
21
42
|
'(GalNAc-2-JNJ)': {
|
|
43
|
+
molecularWeight: 1273.3,
|
|
22
44
|
left: 'C(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
|
|
23
45
|
'(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
|
|
24
46
|
'(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)NC(=O)CCCC(=O)NCC(O)CO',
|
|
@@ -49,7 +71,7 @@ export const map: {[synthesizer: string]:
|
|
|
49
71
|
'name': 'Guanine',
|
|
50
72
|
'weight': 329.21,
|
|
51
73
|
'normalized': 'dG',
|
|
52
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
74
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
53
75
|
},
|
|
54
76
|
'C': {
|
|
55
77
|
'name': 'Cytosine',
|
|
@@ -75,7 +97,7 @@ export const map: {[synthesizer: string]:
|
|
|
75
97
|
'name': 'Guanine',
|
|
76
98
|
'weight': 329.21,
|
|
77
99
|
'normalized': 'dG',
|
|
78
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
100
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
79
101
|
},
|
|
80
102
|
'C': {
|
|
81
103
|
'name': 'Cytosine',
|
|
@@ -139,7 +161,7 @@ export const map: {[synthesizer: string]:
|
|
|
139
161
|
'name': 'Guanine',
|
|
140
162
|
'weight': 329.21,
|
|
141
163
|
'normalized': 'dG',
|
|
142
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
164
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
143
165
|
},
|
|
144
166
|
'T': {
|
|
145
167
|
'name': 'Tyrosine',
|
|
@@ -341,13 +363,13 @@ export const map: {[synthesizer: string]:
|
|
|
341
363
|
'name': 'Guanine',
|
|
342
364
|
'weight': 329.21,
|
|
343
365
|
'normalized': 'dG',
|
|
344
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
366
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
345
367
|
},
|
|
346
368
|
'dG': {
|
|
347
369
|
'name': 'Guanine',
|
|
348
370
|
'weight': 329.21,
|
|
349
371
|
'normalized': 'dG',
|
|
350
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
372
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
351
373
|
},
|
|
352
374
|
'T': {
|
|
353
375
|
'name': 'Tyrosine',
|