@datagrok/sequence-translator 1.0.2 → 1.0.5

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.
@@ -0,0 +1,225 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+ import * as DG from 'datagrok-api/dg';
4
+ import {convertSequence, undefinedInputSequence, isValidSequence} from '../structures-works/sequence-codes-tools';
5
+ import {map, MODIFICATIONS} from '../structures-works/map';
6
+ import {sequenceToSmiles, sequenceToMolV3000} from '../structures-works/from-monomers';
7
+
8
+ import $ from 'cash-dom';
9
+
10
+ const defaultInput = 'fAmCmGmAmCpsmU';
11
+ const sequenceWasCopied = 'Copied';
12
+ const tooltipSequence = 'Copy sequence';
13
+
14
+ export function mainView() {
15
+ function updateTableAndMolecule(sequence: string, inputFormat: string, isSet: boolean): void {
16
+ moleculeSvgDiv.innerHTML = '';
17
+ outputTableDiv.innerHTML = '';
18
+ const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
19
+ let errorsExist = false;
20
+ try {
21
+ sequence = sequence.replace(/\s/g, '');
22
+ const output = isValidSequence(sequence, null);
23
+ if (isSet)
24
+ output.synthesizer = [inputFormat];
25
+ inputFormatChoiceInput.value = output.synthesizer![0];
26
+ const outputSequenceObj = convertSequence(sequence, output);
27
+ const tableRows = [];
28
+
29
+ for (const key of Object.keys(outputSequenceObj).slice(1)) {
30
+ const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
31
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
32
+ -1;
33
+ if ('indexOfFirstNotValidChar' in outputSequenceObj) {
34
+ const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
35
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
36
+ -1;
37
+ if (indexOfFirstNotValidChar != -1)
38
+ errorsExist = true;
39
+ }
40
+
41
+ tableRows.push({
42
+ 'key': key,
43
+ 'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
44
+ ui.divH([
45
+ ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
46
+ ui.tooltip.bind(
47
+ ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
48
+ 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
49
+ '. See tables with valid codes on the right',
50
+ ),
51
+ ]) : //@ts-ignore
52
+ ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
53
+ .then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
54
+ });
55
+ }
56
+
57
+ if (errorsExist) {
58
+ const synthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer.slice(0, -6);
59
+ asoGapmersGrid.onCellPrepare(function(gc) {
60
+ gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
61
+ });
62
+ omeAndFluoroGrid.onCellPrepare(function(gc) {
63
+ gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
64
+ });
65
+ switchInput.enabled = true;
66
+ } else {
67
+ asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
68
+ omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
69
+ }
70
+
71
+ outputTableDiv.append(
72
+ ui.div([
73
+ DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
74
+ [item.key, item.value], ['Code', 'Sequence']).root,
75
+ ]),
76
+ );
77
+
78
+ if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
79
+ const canvas = ui.canvas(300, 170);
80
+ canvas.addEventListener('click', () => {
81
+ const canv = ui.canvas($(window).width(), $(window).height());
82
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
83
+ output.synthesizer![0]);
84
+ // @ts-ignore
85
+ OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
86
+ ui.dialog('Molecule: ' + inputSequenceField.value)
87
+ .add(canv)
88
+ .showModal(true);
89
+ });
90
+ $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
91
+ $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
92
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
93
+ output.synthesizer![0]);
94
+ // @ts-ignore
95
+ OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
96
+ moleculeSvgDiv.append(canvas);
97
+ } else
98
+ moleculeSvgDiv.innerHTML = '';
99
+ } finally {
100
+ pi.close();
101
+ }
102
+ }
103
+
104
+ const inputFormatChoiceInput = ui.choiceInput(
105
+ 'Input format: ', 'Janssen GCRS Codes', Object.keys(map), (format: string) => {
106
+ updateTableAndMolecule(inputSequenceField.value.replace(/\s/g, ''), format, true);
107
+ });
108
+ const moleculeSvgDiv = ui.block([]);
109
+ const outputTableDiv = ui.div([]);
110
+ const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence,
111
+ inputFormatChoiceInput.value!, false));
112
+
113
+ const asoDf = DG.DataFrame.fromObjects([
114
+ {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
115
+ {'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
116
+ {'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
117
+ {'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
118
+ {'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
119
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
120
+ {'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
121
+ {'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
122
+ {'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
123
+ {'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
124
+ {'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
125
+ {'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
126
+ {'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
127
+ {'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
128
+ ])!;
129
+ const asoGapmersGrid = DG.Viewer.grid(asoDf, {showRowHeader: false, showCellTooltip: false});
130
+
131
+ asoDf.onCurrentCellChanged.subscribe((_) => {
132
+ navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'));
133
+ });
134
+
135
+ const omeAndFluoroGrid = DG.Viewer.grid(
136
+ DG.DataFrame.fromObjects([
137
+ {'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
138
+ {'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
139
+ {'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
140
+ {'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
141
+ {'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
142
+ {'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
143
+ {'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
144
+ {'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
145
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
146
+ ])!, {showRowHeader: false, showCellTooltip: false},
147
+ );
148
+
149
+ const overhangModificationsGrid = DG.Viewer.grid(
150
+ DG.DataFrame.fromColumns([
151
+ DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
152
+ ])!, {showRowHeader: false, showCellTooltip: false},
153
+ );
154
+ updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!, true);
155
+
156
+ const codesTablesDiv = ui.splitV([
157
+ ui.box(ui.h2('ASO Gapmers'), {style: {maxHeight: '40px'}}),
158
+ asoGapmersGrid.root,
159
+ ui.box(ui.h2('2\'-OMe and 2\'-F siRNA'), {style: {maxHeight: '40px'}}),
160
+ omeAndFluoroGrid.root,
161
+ ui.box(ui.h2('Overhang modifications'), {style: {maxHeight: '40px'}}),
162
+ overhangModificationsGrid.root,
163
+ ], {style: {maxWidth: '350px'}});
164
+
165
+ const appMainDescription = ui.info([
166
+ ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
167
+ ui.divText('Paste sequence into the text field below'),
168
+ ui.divText('\n How to convert many sequences:', {style: {'font-weight': 'bolder'}}),
169
+ ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok'),
170
+ ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
171
+ ui.divText('This will add the result column to the right of the table'),
172
+ ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.');
173
+
174
+ $(codesTablesDiv).hide();
175
+ const switchInput = ui.switchInput('Codes', false, (v: boolean) => (v) ?
176
+ $(codesTablesDiv).show() :
177
+ $(codesTablesDiv).hide(),
178
+ );
179
+
180
+ const topPanel = [
181
+ ui.iconFA('download', () => {
182
+ const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
183
+ inputFormatChoiceInput.value!);
184
+ const element = document.createElement('a');
185
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
186
+ element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
187
+ element.click();
188
+ }, 'Save .mol file'),
189
+ ui.iconFA('copy', () => {
190
+ navigator.clipboard.writeText(
191
+ sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!))
192
+ .then(() => grok.shell.info(sequenceWasCopied));
193
+ }, 'Copy SMILES'),
194
+ switchInput.root,
195
+ ];
196
+
197
+ const v = grok.shell.v;
198
+ const tabControl = grok.shell.sidebar;
199
+ tabControl.onTabChanged.subscribe((_) =>
200
+ v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
201
+ v.setRibbonPanels([topPanel]);
202
+
203
+ return ui.box(
204
+ ui.splitH([
205
+ ui.splitV([
206
+ ui.panel([
207
+ appMainDescription,
208
+ ui.div([
209
+ ui.h1('Input sequence'),
210
+ ui.div([
211
+ inputSequenceField.root,
212
+ ], 'input-base'),
213
+ ], 'inputSequence'),
214
+ ui.div([inputFormatChoiceInput], {style: {padding: '5px 0'}}),
215
+ ui.block([
216
+ ui.h1('Output'),
217
+ outputTableDiv,
218
+ ]),
219
+ moleculeSvgDiv,
220
+ ], 'sequence'),
221
+ ]),
222
+ codesTablesDiv,
223
+ ], {style: {height: '100%', width: '100%'}}),
224
+ );
225
+ }
package/src/package.ts CHANGED
@@ -1,26 +1,13 @@
1
- /* Do not change these import lines. Datagrok will import API library in exactly the same manner */
2
1
  import * as grok from 'datagrok-api/grok';
3
2
  import * as ui from 'datagrok-api/ui';
4
3
  import * as DG from 'datagrok-api/dg';
5
- import * as OCL from 'openchemlib/full.js';
6
- import $ from 'cash-dom';
7
- import {defineAxolabsPattern} from './defineAxolabsPattern';
4
+ import {autostartOligoSdFileSubscription} from './autostart/registration';
5
+ import {defineAxolabsPattern} from './axolabs/define-pattern';
8
6
  import {saveSenseAntiSense} from './structures-works/save-sense-antisense';
9
- import {sequenceToSmiles, sequenceToMolV3000} from './structures-works/from-monomers';
10
- import {convertSequence, undefinedInputSequence, isValidSequence, getFormat} from
11
- './structures-works/sequence-codes-tools';
12
- import {map, COL_NAMES, MODIFICATIONS} from './structures-works/map';
13
- import {SALTS_CSV} from './salts';
14
- import {USERS_CSV} from './users';
15
- import {ICDS} from './ICDs';
16
- import {SOURCES} from './sources';
17
- import {IDPS} from './IDPs';
7
+ import {mainView} from './main/main-view';
18
8
 
19
9
  export const _package = new DG.Package();
20
10
 
21
- const defaultInput = 'fAmCmGmAmCpsmU';
22
- const sequenceWasCopied = 'Copied';
23
- const tooltipSequence = 'Copy sequence';
24
11
 
25
12
  //name: Sequence Translator
26
13
  //tags: app
@@ -30,361 +17,15 @@ export function sequenceTranslator(): void {
30
17
  windows.showToolbox = false;
31
18
  windows.showHelp = false;
32
19
 
33
- function updateTableAndMolecule(sequence: string, inputFormat: string, isSet: boolean): void {
34
- moleculeSvgDiv.innerHTML = '';
35
- outputTableDiv.innerHTML = '';
36
- const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
37
- let errorsExist = false;
38
- try {
39
- sequence = sequence.replace(/\s/g, '');
40
- const output = isValidSequence(sequence, null);
41
- if (isSet)
42
- output.synthesizer = [inputFormat];
43
- inputFormatChoiceInput.value = output.synthesizer![0];
44
- const outputSequenceObj = convertSequence(sequence, output);
45
- const tableRows = [];
46
-
47
- for (const key of Object.keys(outputSequenceObj).slice(1)) {
48
- const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
49
- JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
50
- -1;
51
- if ('indexOfFirstNotValidChar' in outputSequenceObj) {
52
- const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
53
- JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
54
- -1;
55
- if (indexOfFirstNotValidChar != -1)
56
- errorsExist = true;
57
- }
58
-
59
- tableRows.push({
60
- 'key': key,
61
- 'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
62
- ui.divH([
63
- ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
64
- ui.tooltip.bind(
65
- ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
66
- 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
67
- '. See tables with valid codes on the right',
68
- ),
69
- ]) : //@ts-ignore
70
- ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
71
- .then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
72
- });
73
- }
74
-
75
- if (errorsExist) {
76
- const synthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer.slice(0, -6);
77
- asoGapmersGrid.onCellPrepare(function(gc) {
78
- gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
79
- });
80
- omeAndFluoroGrid.onCellPrepare(function(gc) {
81
- gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
82
- });
83
- switchInput.enabled = true;
84
- } else {
85
- asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
86
- omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
87
- }
88
-
89
- outputTableDiv.append(
90
- ui.div([
91
- DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
92
- [item.key, item.value], ['Code', 'Sequence']).root,
93
- ]),
94
- );
95
-
96
- if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
97
- const canvas = ui.canvas(300, 170);
98
- canvas.addEventListener('click', () => {
99
- const canv = ui.canvas($(window).width(), $(window).height());
100
- const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
101
- output.synthesizer![0]);
102
- // @ts-ignore
103
- OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
104
- ui.dialog('Molecule: ' + inputSequenceField.value)
105
- .add(canv)
106
- .showModal(true);
107
- });
108
- $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
109
- $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
110
- const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
111
- output.synthesizer![0]);
112
- // @ts-ignore
113
- OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
114
- moleculeSvgDiv.append(canvas);
115
- } else
116
- moleculeSvgDiv.innerHTML = '';
117
- } finally {
118
- pi.close();
119
- }
120
- }
121
-
122
- const inputFormatChoiceInput = ui.choiceInput(
123
- 'Input format: ', 'Janssen GCRS Codes', Object.keys(map), (format: string) => {
124
- updateTableAndMolecule(inputSequenceField.value.replace(/\s/g, ''), format, true);
125
- });
126
- const moleculeSvgDiv = ui.block([]);
127
- const outputTableDiv = ui.div([]);
128
- const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence,
129
- inputFormatChoiceInput.value!, false));
130
-
131
- const asoDf = DG.DataFrame.fromObjects([
132
- {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
133
- {'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
134
- {'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
135
- {'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
136
- {'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
137
- {'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
138
- {'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
139
- {'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
140
- {'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
141
- {'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
142
- {'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
143
- {'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
144
- {'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
145
- {'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
146
- ])!;
147
- const asoGapmersGrid = DG.Viewer.grid(asoDf, {showRowHeader: false, showCellTooltip: false});
148
-
149
- asoDf.onCurrentCellChanged.subscribe((_) => {
150
- navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'));
151
- });
152
-
153
- const omeAndFluoroGrid = DG.Viewer.grid(
154
- DG.DataFrame.fromObjects([
155
- {'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
156
- {'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
157
- {'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
158
- {'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
159
- {'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
160
- {'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
161
- {'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
162
- {'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
163
- {'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
164
- ])!, {showRowHeader: false, showCellTooltip: false},
165
- );
166
-
167
- const overhangModificationsGrid = DG.Viewer.grid(
168
- DG.DataFrame.fromColumns([
169
- DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
170
- ])!, {showRowHeader: false, showCellTooltip: false},
171
- );
172
- updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!, true);
173
-
174
- const appMainDescription = ui.info([
175
- ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
176
- ui.divText('Paste sequence into the text field below'),
177
- ui.divText('\n How to convert many sequences:', {style: {'font-weight': 'bolder'}}),
178
- ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok'),
179
- ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
180
- ui.divText('This will add the result column to the right of the table'),
181
- ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.');
182
-
183
- const codesTablesDiv = ui.splitV([
184
- ui.box(ui.h2('ASO Gapmers'), {style: {maxHeight: '40px'}}),
185
- asoGapmersGrid.root,
186
- ui.box(ui.h2('2\'-OMe and 2\'-F siRNA'), {style: {maxHeight: '40px'}}),
187
- omeAndFluoroGrid.root,
188
- ui.box(ui.h2('Overhang modifications'), {style: {maxHeight: '40px'}}),
189
- overhangModificationsGrid.root,
190
- ], {style: {maxWidth: '350px'}});
191
-
192
- const tabControl = ui.tabControl({
193
- 'MAIN': ui.box(
194
- ui.splitH([
195
- ui.splitV([
196
- ui.panel([
197
- appMainDescription,
198
- ui.div([
199
- ui.h1('Input sequence'),
200
- ui.div([
201
- inputSequenceField.root,
202
- ], 'input-base'),
203
- ], 'inputSequence'),
204
- ui.div([inputFormatChoiceInput], {style: {padding: '5px 0'}}),
205
- ui.block([
206
- ui.h1('Output'),
207
- outputTableDiv,
208
- ]),
209
- moleculeSvgDiv,
210
- ], 'sequence'),
211
- ]),
212
- codesTablesDiv,
213
- ], {style: {height: '100%', width: '100%'}}),
214
- ),
215
- 'AXOLABS': defineAxolabsPattern(),
216
- 'SDF': saveSenseAntiSense(),
217
- });
218
-
219
- const v = grok.shell.newView('Sequence Translator', [tabControl]);
20
+ const v = grok.shell.newView('Sequence Translator', [
21
+ ui.tabControl({
22
+ 'MAIN': mainView(),
23
+ 'AXOLABS': defineAxolabsPattern(),
24
+ 'SDF': saveSenseAntiSense(),
25
+ }),
26
+ ]);
220
27
  v.box = true;
221
-
222
- const switchInput = ui.switchInput('Codes', true, (v: boolean) => (v) ?
223
- $(codesTablesDiv).show() :
224
- $(codesTablesDiv).hide(),
225
- );
226
-
227
- const topPanel = [
228
- ui.iconFA('download', () => {
229
- const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
230
- inputFormatChoiceInput.value!);
231
- const element = document.createElement('a');
232
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
233
- element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
234
- element.click();
235
- }, 'Save .mol file'),
236
- ui.iconFA('copy', () => {
237
- navigator.clipboard.writeText(
238
- sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!))
239
- .then(() => grok.shell.info(sequenceWasCopied));
240
- }, 'Copy SMILES'),
241
- switchInput.root,
242
- ];
243
-
244
- tabControl.onTabChanged.subscribe((_) =>
245
- v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
246
- v.setRibbonPanels([topPanel]);
247
- }
248
-
249
- async function saveTableAsSdFile(table: DG.DataFrame) {
250
- if (!table.columns.contains('Compound Name')) {
251
- grok.shell.warning(
252
- 'File saved without columns \'' +
253
- [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
254
- COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
255
- );
256
- }
257
- const structureColumn = table.col(COL_NAMES.SEQUENCE)!;
258
- const typeColumn = table.col(COL_NAMES.TYPE)!;
259
- let result = '';
260
- for (let i = 0; i < table.rowCount; i++) {
261
- const format = getFormat(structureColumn.get(i));
262
- result += (typeColumn.get(i) == 'SS') ?
263
- sequenceToMolV3000(structureColumn.get(i), false, true, format!) + '\n' + `> <Sequence>\nSense Strand\n\n` :
264
- sequenceToMolV3000(structureColumn.get(i), true, true, format!) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
265
- for (const col of table.columns) {
266
- if (col.name != COL_NAMES.SEQUENCE)
267
- result += `> <${col.name}>\n${col.get(i)}\n\n`;
268
- }
269
- result += '$$$$\n\n';
270
- }
271
- const element = document.createElement('a');
272
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
273
- element.setAttribute('download', table.name + '.sdf');
274
- element.click();
275
- }
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
28
  }
306
29
 
307
30
  //tags: autostart
308
- export function autostartOligoSdFileSubscription() {
309
- grok.events.onViewAdded.subscribe((v: any) => {
310
- if (v.type == 'TableView' && v.dataFrame.columns.contains(COL_NAMES.TYPE))
311
- oligoSdFile(v.dataFrame);
312
- });
313
- }
314
-
315
- export function oligoSdFile(table: DG.DataFrame) {
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
-
322
- function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
323
- if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
324
- return grok.shell.error('Columns already exist!');
325
-
326
- const sequence = t.col(COL_NAMES.SEQUENCE)!;
327
- const salt = t.col(COL_NAMES.SALT)!;
328
- const equivalents = t.col(COL_NAMES.EQUIVALENTS)!;
329
-
330
- t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
331
- t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
332
- sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
333
- sequence.getString(i),
334
- );
335
- const molWeightCol = saltsDf.col('MOLWEIGHT')!;
336
- const saltNamesList = saltsDf.col('DISPLAY')!.toList();
337
- t.columns.addNewFloat(COL_NAMES.CPD_MW)
338
- .init((i: number) => molecularWeight(sequence.get(i), weightsObj));
339
- t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
340
- const saltRowIndex = saltNamesList.indexOf(salt.get(i));
341
- const mw = molWeightCol.get(saltRowIndex);
342
- return mw * equivalents.get(i);
343
- });
344
- t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
345
- '${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
346
- );
347
-
348
- addColumnsPressed = true;
349
- return newDf = t;
350
- }
351
-
352
- let newDf: DG.DataFrame;
353
- let addColumnsPressed = false;
354
-
355
- const d = ui.div([
356
- ui.icons.edit(() => {
357
- d.innerHTML = '';
358
- if (table.col(COL_NAMES.IDP)!.type != DG.COLUMN_TYPE.STRING)
359
- table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
360
- d.append(
361
- ui.link('Add Columns', () => {
362
- addColumns(table, saltsDf);
363
- grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
364
- }, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
365
- COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
366
- ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
367
- );
368
-
369
- const view = grok.shell.getTableView(table.name)!;
370
-
371
- view.table!.col(COL_NAMES.TYPE)!.setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
372
- view.table!.col(COL_NAMES.OWNER)!.setTag(DG.TAGS.CHOICES, stringifyItems(usersDf.columns.byIndex(0).toList()));
373
- view.table!.col(COL_NAMES.SALT)!.setTag(DG.TAGS.CHOICES, stringifyItems(saltsDf.columns.byIndex(0).toList()));
374
- view.table!.col(COL_NAMES.SOURCE)!.setTag(DG.TAGS.CHOICES, stringifyItems(sourcesDf.columns.byIndex(0).toList()));
375
- view.table!.col(COL_NAMES.ICD)!.setTag(DG.TAGS.CHOICES, stringifyItems(icdsDf.columns.byIndex(0).toList()));
376
- view.table!.col(COL_NAMES.IDP)!.setTag(DG.TAGS.CHOICES, stringifyItems(idpsDf.columns.byIndex(0).toList()));
377
-
378
- grok.events.onContextMenu.subscribe((args) => {
379
- if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
380
- .includes(args.args.context.table.currentCol.name)) {
381
- args.args.menu.item('Fill Column With Value', () => {
382
- const v = args.args.context.table.currentCell.value;
383
- args.args.context.table.currentCell.column.init(v);
384
- });
385
- }
386
- });
387
- }),
388
- ]);
389
- grok.shell.v.setRibbonPanels([[d]]);
390
- }
31
+ autostartOligoSdFileSubscription();
@@ -15,7 +15,10 @@ export function gcrsToLcms(sequence: string): string {
15
15
  arr1[i] = arr1[i].replace(')', '\\)');
16
16
  }
17
17
  const regExp = new RegExp('(' + arr1.join('|') + ')', 'g');
18
- return sequence.replace(regExp, function(code) {return obj[code];});
18
+ let r1 = sequence.replace(regExp, function(code) {return obj[code];});
19
+ r1 = r1.replace('//', '/');
20
+ r1 = r1.replace('//', '/');
21
+ return r1.replace('//', '/');
19
22
  }
20
23
 
21
24
  //name: asoGapmersNucleotidesToBioSpring
@@ -1,4 +1,4 @@
1
- import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS} from './map';
1
+ import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS, delimiter} from './map';
2
2
  import {isValidSequence} from './sequence-codes-tools';
3
3
  import {getNucleotidesMol} from './mol-transformations';
4
4
 
@@ -12,7 +12,7 @@ export function sequenceToMolV3000(sequence: string, inverted: boolean = false,
12
12
  const links = ['s', 'ps', '*'];
13
13
  const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
14
14
  const dropdowns = Object.keys(MODIFICATIONS);
15
- codes = codes.concat(dropdowns);
15
+ codes = codes.concat(dropdowns).concat(delimiter);
16
16
  while (i < sequence.length) {
17
17
  const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
18
18
  i += code.length;
@@ -48,7 +48,7 @@ export function sequenceToSmiles(sequence: string, inverted: boolean = false, fo
48
48
  const links = ['s', 'ps', '*'];
49
49
  const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
50
50
  const dropdowns = Object.keys(MODIFICATIONS);
51
- codes = codes.concat(dropdowns);
51
+ codes = codes.concat(dropdowns).concat(delimiter);
52
52
  while (i < sequence.length) {
53
53
  const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
54
54
  i += code.length;
@@ -98,6 +98,7 @@ function getObjectWithCodesAndSmiles(sequence: string, format: string) {
98
98
  obj[code] = map[format][technology][code].SMILES;
99
99
  }
100
100
  }
101
+ obj[delimiter] = '';
101
102
  // TODO: create object based from synthesizer type to avoid key(codes) duplicates
102
103
  const output = isValidSequence(sequence, format);
103
104
  if (output.synthesizer!.includes(SYNTHESIZERS.MERMADE_12))