@datagrok/sequence-translator 1.0.14 → 1.0.16

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.
@@ -44,22 +44,22 @@ export const AXOLABS_MAP:
44
44
  color: 'rgb(255,192,0)',
45
45
  },
46
46
  'A': {
47
- fullName: 'Adenine',
47
+ fullName: 'Adenosine',
48
48
  symbols: ['a', 'a', 'a', 'a'],
49
49
  color: RNA_COLOR,
50
50
  },
51
51
  'C': {
52
- fullName: 'Cytosine',
52
+ fullName: 'Cytidine',
53
53
  symbols: ['c', 'c', 'c', 'c'],
54
54
  color: RNA_COLOR,
55
55
  },
56
56
  'G': {
57
- fullName: 'Guanine',
57
+ fullName: 'Guanosine',
58
58
  symbols: ['g', 'g', 'g', 'g'],
59
59
  color: RNA_COLOR,
60
60
  },
61
61
  'U': {
62
- fullName: 'Uracil',
62
+ fullName: 'Uridine',
63
63
  symbols: ['u', 'u', 'u', 'u'],
64
64
  color: RNA_COLOR,
65
65
  },
@@ -1,35 +1,28 @@
1
1
  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
+
5
+ import * as rxjs from 'rxjs';
4
6
  import {convertSequence, undefinedInputSequence, isValidSequence} from '../structures-works/sequence-codes-tools';
5
7
  import {map} from '../structures-works/map';
6
8
  import {MODIFICATIONS} from '../structures-works/const';
7
9
  import {sequenceToSmiles, sequenceToMolV3000} from '../structures-works/from-monomers';
8
10
  import $ from 'cash-dom';
9
11
  import {download} from '../helpers';
12
+ import {extractAtomDataV3000} from '../structures-works/mol-transformations';
13
+ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
10
14
 
11
15
  const defaultInput = 'fAmCmGmAmCpsmU'; // todo: capitalize constants
12
16
  const sequenceWasCopied = 'Copied'; // todo: wrap hardcoded literals into constants
13
17
  const tooltipSequence = 'Copy sequence';
14
18
 
15
- export async function mainView(): Promise<HTMLDivElement> {
16
- const monomersLibAddress = 'System:AppData/SequenceTranslator/helmLib.json';
19
+ export async function mainView(onSequenceChanged: (seq: string) => void): Promise<HTMLDivElement> {
20
+ const onInput: rxjs.Subject<string> = new rxjs.Subject<string>();
21
+
17
22
  async function updateTableAndMolecule(sequence: string, inputFormat: string): Promise<void> {
18
23
  moleculeSvgDiv.innerHTML = '';
19
24
  outputTableDiv.innerHTML = '';
20
25
  const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
21
- let errorsExist = false;
22
-
23
- // external helm-like monomers library
24
- const fileExists = await grok.dapi.files.exists(monomersLibAddress);
25
- if (!fileExists) {
26
- // todo: improve behaviour in this case
27
- grok.shell.warning('Please, provide the file with monomers library as System:AppData/SequenceTranslator/helmLib.json');
28
- pi.close();
29
- return;
30
- }
31
-
32
- const monomersLib = await grok.dapi.files.readAsText(monomersLibAddress);
33
26
 
34
27
  try {
35
28
  sequence = sequence.replace(/\s/g, '');
@@ -46,16 +39,16 @@ export async function mainView(): Promise<HTMLDivElement> {
46
39
  tableRows.push({
47
40
  'key': key,
48
41
  'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
49
- ui.divH([
50
- ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
51
- ui.tooltip.bind(
52
- ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
53
- 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
54
- '. See tables with valid codes on the right',
55
- ),
56
- ]) : //@ts-ignore
57
- ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
58
- .then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
42
+ ui.divH([
43
+ ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
44
+ ui.tooltip.bind(
45
+ ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
46
+ 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
47
+ '. See tables with valid codes on the right',
48
+ ),
49
+ ]) : //@ts-ignore
50
+ ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
51
+ .then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
59
52
  });
60
53
  }
61
54
 
@@ -67,27 +60,71 @@ export async function mainView(): Promise<HTMLDivElement> {
67
60
  );
68
61
 
69
62
  if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
70
- const canvas = ui.canvas(300, 170);
71
- canvas.addEventListener('click', () => {
72
- const canv = ui.canvas($(window).width(), $(window).height());
73
- const mol = sequenceToMolV3000(
74
- inputSequenceField.value.replace(/\s/g, ''), false, true,
75
- output.synthesizer![0],
76
- );
77
- console.log(mol);
78
- // @ts-ignore
79
- OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
80
- ui.dialog('Molecule: ' + inputSequenceField.value)
81
- .add(canv)
82
- .showModal(true);
63
+ const formCanvasWidth = 500;
64
+ const formCanvasHeight = 170;
65
+ const formCanvas = ui.canvas(
66
+ formCanvasWidth * window.devicePixelRatio, formCanvasHeight * window.devicePixelRatio);
67
+ formCanvas.style.width = `${formCanvasWidth}px`;
68
+ formCanvas.style.height = `${formCanvasHeight}px`;
69
+
70
+ formCanvas.addEventListener('click', async () => {
71
+ try {
72
+ const mol = sequenceToMolV3000(
73
+ inputSequenceField.value.replace(/\s/g, ''), false, true,
74
+ output.synthesizer![0],
75
+ );
76
+ console.log(mol);
77
+
78
+ const addDiv = ui.div([], {style: {overflowX: 'scroll'}});
79
+
80
+ // addDiv size required, but now available before dialog show()
81
+ const coordinates = extractAtomDataV3000(mol);
82
+ const cw: number = $(window).width() * 0.80; // addDiv.clientWidth
83
+ const ch: number = $(window).height() * 0.70; // addDiv.clientHeight
84
+ const molWidth: number = Math.max(...coordinates.x) - Math.min(...coordinates.x);
85
+ const molHeight: number = Math.max(...coordinates.y) - Math.min(...coordinates.y);
86
+
87
+ const wR: number = cw / molWidth;
88
+ const hR: number = ch / molHeight;
89
+ const r: number = hR; // Math.max(wR, hR);
90
+ const dlgCanvasWidth = r * molWidth;
91
+ const dlgCanvasHeight = r * molHeight;
92
+
93
+ const dlgCanvas = ui.canvas(dlgCanvasWidth * window.devicePixelRatio, dlgCanvasHeight * window.devicePixelRatio);
94
+ dlgCanvas.style.width = `${dlgCanvasWidth}px`;
95
+ dlgCanvas.style.height = `${dlgCanvasHeight}px`;
96
+
97
+ // // @ts-ignore
98
+ // OCL.StructureView.drawMolecule(dlgCanvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
99
+ // await grok.chem.canvasMol(0, 0, dlgCanvas.width, dlgCanvas.height, dlgCanvas, mol, null,
100
+ // {setNewCoords: false, normalizeDepiction: false, straightenDepiction: false});
101
+ await grok.functions.call('Chem:canvasMol', {
102
+ x: 0, y: 0, w: dlgCanvas.width, h: dlgCanvas.height, canvas: dlgCanvas,
103
+ molString: mol, scaffoldMolString: '',
104
+ options: {normalizeDepiction: false, straightenDepiction: false}
105
+ });
106
+
107
+ addDiv.appendChild(dlgCanvas);
108
+ ui.dialog('Molecule: ' + inputSequenceField.value)
109
+ .add(addDiv)
110
+ .showModal(true);
111
+ } catch (err) {
112
+ const errStr = errorToConsole(err);
113
+ console.error(errStr);
114
+ }
83
115
  });
84
- $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
85
- $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
116
+ $(formCanvas).on('mouseover', () => $(formCanvas).css('cursor', 'zoom-in'));
117
+ $(formCanvas).on('mouseout', () => $(formCanvas).css('cursor', 'default'));
86
118
  const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
87
119
  output.synthesizer![0]);
88
- // @ts-ignore
89
- OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
90
- moleculeSvgDiv.append(canvas);
120
+ // // @ts-ignore
121
+ // OCL.StructureView.drawMolecule(formCanvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
122
+ await grok.functions.call('Chem:canvasMol', {
123
+ x: 0, y: 0, w: formCanvas.width, h: formCanvas.height, canvas: formCanvas,
124
+ molString: mol, scaffoldMolString: '',
125
+ options: {normalizeDepiction: false, straightenDepiction: false}
126
+ });
127
+ moleculeSvgDiv.append(formCanvas);
91
128
  } else
92
129
  moleculeSvgDiv.innerHTML = '';
93
130
  } finally {
@@ -102,7 +139,13 @@ export async function mainView(): Promise<HTMLDivElement> {
102
139
  const moleculeSvgDiv = ui.block([]);
103
140
  const outputTableDiv = ui.div([]);
104
141
  const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => {
142
+ // Send event to DG.debounce()
143
+ onInput.next(sequence);
144
+ });
145
+
146
+ DG.debounce<string>(onInput, 300).subscribe((sequence) => {
105
147
  updateTableAndMolecule(sequence, inputFormatChoiceInput.value!);
148
+ onSequenceChanged(sequence);
106
149
  });
107
150
 
108
151
  const asoGapmersGrid = DG.Viewer.grid(
@@ -139,9 +182,9 @@ export async function mainView(): Promise<HTMLDivElement> {
139
182
  );
140
183
 
141
184
  const overhangModificationsGrid = DG.Viewer.grid(
142
- DG.DataFrame.fromColumns([
143
- DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
144
- ])!, {showRowHeader: false, showCellTooltip: false, allowEdit: false},
185
+ DG.DataFrame.fromColumns([
186
+ DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
187
+ ])!, {showRowHeader: false, showCellTooltip: false, allowEdit: false},
145
188
  );
146
189
  updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!);
147
190
 
@@ -171,7 +214,6 @@ export async function mainView(): Promise<HTMLDivElement> {
171
214
 
172
215
  const downloadMolFileIcon = ui.iconFA('download', async () => {
173
216
  const clearSequence = inputSequenceField.value.replace(/\s/g, '');
174
- const monomersLib = await grok.dapi.files.readAsText(monomersLibAddress);
175
217
  const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
176
218
  inputFormatChoiceInput.value!);
177
219
  download(clearSequence + '.mol', encodeURIComponent(result));
@@ -203,9 +245,8 @@ export async function mainView(): Promise<HTMLDivElement> {
203
245
  appMainDescription,
204
246
  ui.div([
205
247
  ui.h1('Input sequence'),
206
- ui.div([
207
- inputSequenceField.root,
208
- ], 'input-base'),
248
+ ui.div([], 'input-base'),
249
+ inputSequenceField.root,
209
250
  ], 'inputSequence'),
210
251
  ui.div([inputFormatChoiceInput], {style: {padding: '5px 0'}}),
211
252
  ui.block([
package/src/package.ts CHANGED
@@ -6,6 +6,7 @@ import {defineAxolabsPattern} from './axolabs/define-pattern';
6
6
  import {saveSenseAntiSense} from './structures-works/save-sense-antisense';
7
7
  import {mainView} from './main/main-view';
8
8
  import {IMonomerLib, MonomerWorks, readLibrary} from '@datagrok-libraries/bio';
9
+ import {OligoSdFileApp} from './apps/oligo-sd-file-app';
9
10
 
10
11
  export const _package = new DG.Package();
11
12
 
@@ -16,11 +17,11 @@ export let monomerWorks: MonomerWorks | null = null;
16
17
 
17
18
  export function getMonomerWorks() {
18
19
  return monomerWorks;
19
- };
20
+ }
20
21
 
21
22
  export function getMonomerLib() {
22
23
  return monomerLib;
23
- };
24
+ }
24
25
 
25
26
  //name: Sequence Translator
26
27
  //tags: app
@@ -35,14 +36,60 @@ export async function sequenceTranslator(): Promise<void> {
35
36
  windows.showToolbox = false;
36
37
  windows.showHelp = false;
37
38
 
39
+ let urlParams: URLSearchParams = new URLSearchParams(window.location.search);
40
+ let mainSeq: string = 'fAmCmGmAmCpsmU';
38
41
  const v = grok.shell.newView('Sequence Translator', []);
39
42
  v.box = true;
40
- v.append(ui.tabControl({
41
- 'MAIN': await mainView(),
43
+ const tc = ui.tabControl({
44
+ 'MAIN': await mainView((seq) => {
45
+ mainSeq = seq;
46
+ urlParams = new URLSearchParams();
47
+ urlParams.set('seq', mainSeq);
48
+ updatePath();
49
+ }),
42
50
  'AXOLABS': defineAxolabsPattern(),
43
51
  'SDF': saveSenseAntiSense(),
44
- }));
52
+ });
53
+ tc.onTabChanged.subscribe((value) => {
54
+ if (tc.currentPane.name != 'MAIN')
55
+ urlParams.delete('seq');
56
+ else
57
+ urlParams.set('seq', mainSeq);
58
+ updatePath();
59
+ });
60
+
61
+ function updatePath() {
62
+ const urlParamsTxt: string = Object.entries(urlParams)
63
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join('&');
64
+ v.path = '/apps/SequenceTranslator/SequenceTranslator' + `/${tc.currentPane.name}/?${urlParamsTxt}`;
65
+ }
66
+
67
+ const pathParts: string[] = window.location.pathname.split('/');
68
+ if (pathParts.length >= 5) {
69
+ const tabName: string = pathParts[5];
70
+ tc.currentPane = tc.getPane(tabName);
71
+ }
72
+
73
+ v.append(tc);
74
+ console.debug('SequenceTranslator: app sequenceTranslator() ' + `v.path='${v.path}', v.basePath='${v.basePath}'.`);
45
75
  }
46
76
 
47
77
  //tags: autostart
48
78
  autostartOligoSdFileSubscription();
79
+
80
+ //name: oligoSdFileApp
81
+ //description: Test/demo app for oligoSdFile
82
+ export async function oligoSdFileApp() {
83
+ console.debug('SequenceTranslator: package.ts oligoSdFileApp()');
84
+
85
+ const pi = DG.TaskBarProgressIndicator.create('open oligoSdFile app');
86
+ try {
87
+ grok.shell.windows.showProperties = false;
88
+ grok.shell.windows.showHelp = false;
89
+
90
+ const app = new OligoSdFileApp();
91
+ await app.init();
92
+ } finally {
93
+ pi.close();
94
+ }
95
+ }
@@ -1,18 +1,5 @@
1
- export const MODIFICATIONS: {[index: string]: {molecularWeight: number, left: string, right: string}} = {
2
- '(invabasic)': {
3
- molecularWeight: 118.13,
4
- left: 'O[C@@H]1C[C@@H]O[C@H]1CO',
5
- right: 'O[C@@H]1C[C@@H]O[C@H]1CO',
6
- },
7
- '(GalNAc-2-JNJ)': {
8
- molecularWeight: 1273.3,
9
- left: 'C(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
10
- '(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
11
- '(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)NC(=O)CCCC(=O)NCC(O)CO',
12
- right: 'OCC(O)CNC(=O)CCCC(=O)NC(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
13
- '(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)'+
14
- '(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)',
15
- },
16
- };
1
+ import * as map from './map';
2
+
3
+ export const MODIFICATIONS = map.MODIFICATIONS;
17
4
 
18
5
  export const standardPhosphateLinkSmiles = 'OP(=O)(O)O';
@@ -1,33 +1,35 @@
1
- import {lcmsToGcrs} from './map';
1
+ import {lcmsToGcrs, MODIFICATIONS} from './map';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
  import {DELIMITER} from './map';
4
+ import {sortByStringLengthInDescendingOrder} from '../helpers';
4
5
  //name: gcrsToLcms
5
6
  //input: string nucleotides {semType: GCRS}
6
7
  //output: string result {semType: LCMS}
7
8
  export function gcrsToLcms(sequence: string): string {
8
- const df = DG.DataFrame.fromCsv(lcmsToGcrs);
9
- const arr1 = df.getCol('GCRS').toList();
10
- const arr2 = df.getCol('LCMS').toList();
11
- const obj: {[i: string]: string} = {};
12
- arr1.forEach((element, index) => obj[element] = arr2[index]);
13
- obj[DELIMITER] = DELIMITER;
14
- // for (let i = 0; i < arr1.length; i++) {
15
- // arr1[i] = arr1[i].replace('(', '\\(');
16
- // arr1[i] = arr1[i].replace(')', '\\)');
17
- // }
18
- // const regExp = new RegExp('(' + arr1.join('|') + ')', 'g');
19
- // let r1 = sequence.replace(regExp, function(code) {return obj[code];});
20
- const codes = arr1.concat(DELIMITER).sort(function(a, b) {return b.length - a.length;});
21
- let i = 0;
22
- let r1 = '';
23
- while (i < sequence.length) {
24
- const matchedCode = codes.find((c) => c == sequence.slice(i, i + c.length));
25
- r1 += obj[sequence.slice(i, i + matchedCode.length)];
26
- i += matchedCode.length;
9
+ try {
10
+ const df = DG.DataFrame.fromCsv(lcmsToGcrs);
11
+ const arr1: string[] = df.getCol('GCRS').toList();
12
+ const arr2: string[] = df.getCol('LCMS').toList();
13
+ const obj: { [i: string]: string } = {};
14
+ arr1.forEach((element, index) => obj[element] = arr2[index]);
15
+ obj[DELIMITER] = DELIMITER;
16
+ const codes = arr1
17
+ .concat(DELIMITER)
18
+ .concat(Object.keys(MODIFICATIONS));
19
+ const sortedCodes = sortByStringLengthInDescendingOrder(codes);
20
+ let i = 0;
21
+ let r1 = '';
22
+ while (i < sequence.length) {
23
+ const matchedCode = sortedCodes.find((c) => c == sequence.slice(i, i + c.length))!;
24
+ r1 += obj[sequence.slice(i, i + matchedCode.length)];
25
+ i += matchedCode.length;
26
+ }
27
+ while (r1.indexOf('//') != -1)
28
+ r1 = r1.replace('//', '/');
29
+ return r1;
30
+ } catch {
31
+ return '<error>';
27
32
  }
28
- while (r1.indexOf('//') != -1)
29
- r1 = r1.replace('//', '/');
30
- return r1;
31
33
  }
32
34
 
33
35
  //name: asoGapmersNucleotidesToBioSpring
@@ -35,9 +37,9 @@ export function gcrsToLcms(sequence: string): string {
35
37
  //output: string result {semType: BioSpring / Gapmers}
36
38
  export function asoGapmersNucleotidesToBioSpring(nucleotides: string): string {
37
39
  let count: number = -1;
38
- const objForEdges: {[index: string]: string} = {
40
+ const objForEdges: { [index: string]: string } = {
39
41
  '(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': '5*', 'A': '6*', 'C': '7*', 'G': '8*'};
40
- const objForCenter: {[index: string]: string} = {
42
+ const objForCenter: { [index: string]: string } = {
41
43
  '(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': 'T*', 'A': 'A*', 'C': '9*', 'G': 'G*'};
42
44
  return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|T|C|G)/g, function(x: string) {
43
45
  count++;
@@ -51,7 +53,7 @@ export function asoGapmersNucleotidesToBioSpring(nucleotides: string): string {
51
53
  //output: string result {semType: GCRS / Gapmers}
52
54
  export function asoGapmersNucleotidesToGcrs(nucleotides: string): string {
53
55
  let count: number = -1;
54
- const objForEdges: {[index: string]: string} = {
56
+ const objForEdges: { [index: string]: string } = {
55
57
  '(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': 'moeUnps',
56
58
  'A': 'moeAnps', 'C': 'moe5mCnps', 'G': 'moeGnps'};
57
59
  const objForCenter: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',