@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.
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.0.2",
4
+ "version": "1.0.5",
5
+ "author": {
6
+ "name": "Vadym Kovadlo",
7
+ "email": "vkovadlo@datagrok.ai"
8
+ },
5
9
  "description": "SequenceTranslator is a [package](https://datagrok.ai/help/develop/develop#packages) for the [Datagrok](https://datagrok.ai) platform, used to translate [oligonucleotide](https://en.wikipedia.org/wiki/Oligonucleotide) sequences between [different representations](https://github.com/datagrok-ai/public/tree/master/packages/SequenceTranslator#sequence-representations).",
6
10
  "repository": {
7
11
  "type": "git",
@@ -10,46 +14,47 @@
10
14
  },
11
15
  "dependencies": {
12
16
  "@datagrok-libraries/utils": "^0.1.0",
13
- "@types/react": "latest",
17
+ "@types/react": "^18.0.15",
14
18
  "datagrok-api": "^1.1.7",
15
19
  "datagrok-tools": "^4.1.2",
16
20
  "npm": "^8.11.0",
21
+ "openchemlib": "6.0.1",
17
22
  "save-svg-as-png": "^1.4.17",
18
- "ts-loader": "latest",
19
- "typescript": "latest",
20
- "openchemlib": "6.0.1"
23
+ "ts-loader": "^9.3.1",
24
+ "typescript": "^4.7.4"
21
25
  },
22
26
  "scripts": {
23
27
  "link-api": "npm link datagrok-api",
24
- "debug-sequencetranslator": "grok publish --rebuild",
25
- "release-sequencetranslator": "grok publish --rebuild --release",
28
+ "debug-sequencetranslator": "grok publish",
29
+ "release-sequencetranslator": "grok publish localhost --release",
26
30
  "build-sequencetranslator": "webpack",
27
31
  "build": "webpack",
28
- "debug-sequencetranslator-public": "grok publish public --rebuild",
29
- "release-sequencetranslator-public": "grok publish public --rebuild --release",
30
- "debug-sequencetranslator-local": "grok publish local --rebuild",
31
- "release-sequencetranslator-local": "grok publish local --rebuild --release",
32
+ "debug-sequencetranslator-public": "grok publish public",
33
+ "release-sequencetranslator-public": "grok publish public --release",
34
+ "debug-sequencetranslator-local": "grok publish local",
35
+ "release-sequencetranslator-local": "grok publish local --release",
32
36
  "test": "set HOST=dev && jest",
33
37
  "test-dev": "set HOST=dev && jest",
34
38
  "test-local": "set HOST=localhost && jest"
35
39
  },
36
40
  "sources": [
37
41
  "css/style.css",
38
- "common/openchemlib-full.js"
42
+ "vendors/openchemlib-full.js"
39
43
  ],
40
44
  "devDependencies": {
45
+ "@types/jest": "^27.0.0",
46
+ "@types/jquery": "^3.5.14",
41
47
  "@typescript-eslint/eslint-plugin": "^4.29.1",
42
48
  "@typescript-eslint/parser": "^4.29.1",
43
49
  "cash-dom": "^8.1.0",
44
50
  "eslint": "^7.32.0",
45
51
  "eslint-config-google": "^0.14.0",
46
- "webpack": "^5.31.0",
47
- "webpack-cli": "^4.6.0",
48
- "jest-html-reporter": "^3.5.0",
49
52
  "jest": "^27.0.0",
50
- "@types/jest": "^27.0.0",
53
+ "jest-html-reporter": "^3.5.0",
54
+ "puppeteer": "^13.7.0",
51
55
  "ts-jest": "^27.0.0",
52
- "puppeteer": "^13.7.0"
56
+ "webpack": "^5.31.0",
57
+ "webpack-cli": "^4.6.0"
53
58
  },
54
59
  "category": "Bioinformatics"
55
60
  }
@@ -16,34 +16,53 @@ beforeAll(async () => {
16
16
  }, P_START_TIMEOUT);
17
17
 
18
18
  afterAll(async () => {
19
- await browser.close();
19
+ await browser?.close();
20
+ });
21
+
22
+ expect.extend({
23
+ checkOutput(received, expected, context) {
24
+ if (received === expected) {
25
+ return {
26
+ message: () => context,
27
+ pass: true
28
+ };
29
+ } else {
30
+ return {
31
+ message: () => context,
32
+ pass: false
33
+ };
34
+ }
35
+ }
20
36
  });
21
37
 
22
38
  it('TEST', async () => {
23
- const target_package:string = process.env.TARGET_PACKAGE ?? 'SequenceTranslator';
24
- console.log(`Testing ${target_package} package`);
39
+ const targetPackage:string = process.env.TARGET_PACKAGE ?? 'SequenceTranslator';
40
+ console.log(`Testing ${targetPackage} package`);
25
41
 
26
- //console.log(require('root-require')('package.json').version);
27
- let r = await page.evaluate((target_package):Promise<object> => {
42
+ let r = await page.evaluate((targetPackage):Promise<object> => {
28
43
  return new Promise<object>((resolve, reject) => {
29
- (<any>window).grok.functions.eval(target_package + ':test()').then((df: any) => {
44
+ (<any>window).grok.functions.eval(targetPackage + ':test()').then((df: any) => {
30
45
  let cStatus = df.columns.byName('success');
31
46
  let cMessage = df.columns.byName('result');
32
47
  let cCat = df.columns.byName('category');
33
48
  let cName = df.columns.byName('name');
34
49
  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`;
50
+ let passReport = '';
51
+ let failReport = '';
52
+ for (let i = 0; i < df.rowCount; i++) {
53
+ if (cStatus.get(i)) {
54
+ passReport += `Test result : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
55
+ } else {
39
56
  failed = true;
57
+ failReport += `Test result : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
40
58
  }
41
- resolve({report, failed});
59
+ }
60
+ resolve({failReport, passReport, failed});
42
61
  }).catch((e: any) => reject(e));
43
62
  });
44
- }, target_package);
63
+ }, targetPackage);
45
64
  // @ts-ignore
46
- console.log(r.report);
65
+ console.log(r.passReport);
47
66
  // @ts-ignore
48
- expect(r.failed).toBe(false);
67
+ expect(r.failed).checkOutput(false, r.failReport);
49
68
  }, 100000);
@@ -0,0 +1,194 @@
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 {siRnaAxolabsToGcrs, gcrsToNucleotides, asoGapmersBioSpringToGcrs, gcrsToMermade12,
5
+ } from '../structures-works/converters';
6
+ import {map, COL_NAMES, MODIFICATIONS} from '../structures-works/map';
7
+ import {getFormat} from '../structures-works/sequence-codes-tools';
8
+ import {sequenceToMolV3000} from '../structures-works/from-monomers';
9
+
10
+ import {SALTS_CSV} from '../salts';
11
+ import {USERS_CSV} from '../users';
12
+ import {ICDS} from '../ICDs';
13
+ import {SOURCES} from '../sources';
14
+ import {IDPS} from '../IDPs';
15
+
16
+ const weightsObj: {[code: string]: number} = {};
17
+ for (const synthesizer of Object.keys(map)) {
18
+ for (const technology of Object.keys(map[synthesizer])) {
19
+ for (const code of Object.keys(map[synthesizer][technology]))
20
+ weightsObj[code] ?? map[synthesizer][technology][code].weight;
21
+ }
22
+ }
23
+ for (const [key, value] of Object.entries(MODIFICATIONS))
24
+ weightsObj[key] = value.molecularWeight;
25
+
26
+
27
+ function sortByStringLengthInDescendingOrder(array: string[]): string[] {
28
+ return array.sort(function(a, b) {return b.length - a.length;});
29
+ }
30
+
31
+ function stringify(items: string[]): string {
32
+ return '["' + items.join('", "') + '"]';
33
+ }
34
+
35
+ function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
36
+ const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
37
+ let weight = 0;
38
+ let i = 0;
39
+ while (i < sequence.length) {
40
+ const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
41
+ weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
42
+ i += matchedCode.length;
43
+ }
44
+ return weight - 61.97;
45
+ }
46
+
47
+ async function saveTableAsSdFile(table: DG.DataFrame) {
48
+ if (!table.columns.contains('Compound Name')) {
49
+ grok.shell.warning(
50
+ 'File saved without columns \'' +
51
+ [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
52
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
53
+ );
54
+ }
55
+ const structureColumn = table.getCol(COL_NAMES.SEQUENCE);
56
+ const typeColumn = table.getCol(COL_NAMES.TYPE);
57
+ let result = '';
58
+ for (let i = 0; i < table.rowCount; i++) {
59
+ const format = getFormat(structureColumn.get(i))!;
60
+ result += (typeColumn.get(i) == 'SS') ?
61
+ sequenceToMolV3000(structureColumn.get(i), false, true, format) + '\n' + `> <Sequence>\nSense Strand\n\n` :
62
+ sequenceToMolV3000(structureColumn.get(i), true, true, format) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
63
+ for (const col of table.columns) {
64
+ if (col.name != COL_NAMES.SEQUENCE)
65
+ result += `> <${col.name}>\n${col.get(i)}\n\n`;
66
+ }
67
+ result += '$$$$\n\n';
68
+ }
69
+ const element = document.createElement('a');
70
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
71
+ element.setAttribute('download', table.name + '.sdf');
72
+ element.click();
73
+ }
74
+
75
+ export function autostartOligoSdFileSubscription() {
76
+ grok.events.onViewAdded.subscribe((v: any) => {
77
+ if (v.type == 'TableView') {
78
+ if (v.dataFrame.columns.contains(COL_NAMES.TYPE))
79
+ oligoSdFile(v.dataFrame);
80
+ grok.events.onContextMenu.subscribe((args) => {
81
+ const seqCol = args.args.context.table.currentCol;
82
+ if (DG.Detector.sampleCategories(seqCol, (s) => /^[fsACGUacgu]{6,}$/.test(s))) {
83
+ args.args.menu.item('Convert Axolabs to GCRS', () => {
84
+ args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
85
+ return siRnaAxolabsToGcrs(seqCol.get(i));
86
+ });
87
+ });
88
+ } else if (DG.Detector.sampleCategories(seqCol, (s) => /^[fmpsACGU]{6,}$/.test(s)) ||
89
+ DG.Detector.sampleCategories(seqCol, (s) => /^(?=.*moe)(?=.*5mC)(?=.*ps){6,}/.test(s))) {
90
+ args.args.menu.item('Convert GCRS to raw', () => {
91
+ args.args.context.table.columns.addNewString(seqCol.name + ' to raw').init((i: number) => {
92
+ return gcrsToNucleotides(seqCol.get(i));
93
+ });
94
+ });
95
+ args.args.menu.item('Convert GCRS to MM12', () => {
96
+ args.args.context.table.columns.addNewString(seqCol.name + ' to MM12').init((i: number) => {
97
+ return gcrsToMermade12(seqCol.get(i));
98
+ });
99
+ });
100
+ } else if (DG.Detector.sampleCategories(seqCol, (s) => /^[*56789ATGC]{6,}$/.test(s))) {
101
+ args.args.menu.item('Convert Biospring to GCRS', () => {
102
+ const seqCol = args.args.context.table.currentCol;
103
+ args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
104
+ return asoGapmersBioSpringToGcrs(seqCol.get(i));
105
+ });
106
+ });
107
+ } else if (DG.Detector.sampleCategories(seqCol, (s) => /^[*1-8]{6,}$/.test(s))) {
108
+ args.args.menu.item('Convert Biospring to GCRS', () => {
109
+ args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
110
+ return siRnaAxolabsToGcrs(seqCol.get(i));
111
+ });
112
+ });
113
+ }
114
+ });
115
+ }
116
+ });
117
+ }
118
+
119
+ export function oligoSdFile(table: DG.DataFrame) {
120
+ const saltsDf = DG.DataFrame.fromCsv(SALTS_CSV);
121
+ const usersDf = DG.DataFrame.fromCsv(USERS_CSV);
122
+ const sourcesDf = DG.DataFrame.fromCsv(SOURCES);
123
+ const icdsDf = DG.DataFrame.fromCsv(ICDS);
124
+ const idpsDf = DG.DataFrame.fromCsv(IDPS);
125
+
126
+ function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
127
+ if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
128
+ return grok.shell.error('Columns already exist');
129
+
130
+ const sequence = t.getCol(COL_NAMES.SEQUENCE);
131
+ const salt = t.getCol(COL_NAMES.SALT);
132
+ const equivalents = t.getCol(COL_NAMES.EQUIVALENTS);
133
+
134
+ t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
135
+ t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
136
+ sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
137
+ sequence.getString(i),
138
+ );
139
+ const molWeightCol = saltsDf.getCol('MOLWEIGHT');
140
+ const saltNamesList = saltsDf.getCol('DISPLAY').toList();
141
+ t.columns.addNewFloat(COL_NAMES.CPD_MW)
142
+ .init((i: number) => molecularWeight(sequence.get(i), weightsObj));
143
+ t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
144
+ const saltRowIndex = saltNamesList.indexOf(salt.get(i));
145
+ const mw = molWeightCol.get(saltRowIndex);
146
+ return mw * equivalents.get(i);
147
+ });
148
+ t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
149
+ '${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
150
+ );
151
+
152
+ addColumnsPressed = true;
153
+ return newDf = t;
154
+ }
155
+
156
+ let newDf: DG.DataFrame;
157
+ let addColumnsPressed = false;
158
+
159
+ const d = ui.div([
160
+ ui.icons.edit(() => {
161
+ d.innerHTML = '';
162
+ if (table.getCol(COL_NAMES.IDP).type != DG.COLUMN_TYPE.STRING)
163
+ table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
164
+ d.append(
165
+ ui.link('Add Columns', () => {
166
+ addColumns(table, saltsDf);
167
+ grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
168
+ }, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
169
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
170
+ ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
171
+ );
172
+
173
+ const view = grok.shell.getTableView(table.name);
174
+
175
+ view.dataFrame.getCol(COL_NAMES.TYPE).setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
176
+ view.dataFrame.getCol(COL_NAMES.OWNER).setTag(DG.TAGS.CHOICES, stringify(usersDf.columns.byIndex(0).toList()));
177
+ view.dataFrame.getCol(COL_NAMES.SALT).setTag(DG.TAGS.CHOICES, stringify(saltsDf.columns.byIndex(0).toList()));
178
+ view.dataFrame.getCol(COL_NAMES.SOURCE).setTag(DG.TAGS.CHOICES, stringify(sourcesDf.columns.byIndex(0).toList()));
179
+ view.dataFrame.getCol(COL_NAMES.ICD).setTag(DG.TAGS.CHOICES, stringify(icdsDf.columns.byIndex(0).toList()));
180
+ view.dataFrame.getCol(COL_NAMES.IDP).setTag(DG.TAGS.CHOICES, stringify(idpsDf.columns.byIndex(0).toList()));
181
+
182
+ grok.events.onContextMenu.subscribe((args) => {
183
+ if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
184
+ .includes(args.args.context.table.currentCol.name)) {
185
+ args.args.menu.item('Fill Column With Value', () => {
186
+ const v = args.args.context.table.currentCell.value;
187
+ args.args.context.table.currentCell.column.init(v);
188
+ });
189
+ }
190
+ });
191
+ }),
192
+ ]);
193
+ grok.shell.v.setRibbonPanels([[d]]);
194
+ }
@@ -94,7 +94,7 @@ export const axolabsMap:
94
94
  color: invAbasicColor,
95
95
  },
96
96
  '2\'-OMe-U(o)': {
97
- fullName: 'Nucleotide Uridine with 2O-Methyl protection (overhang)',
97
+ fullName: 'Nucleotide Uridine with 2\'O-Methyl protection (overhang)',
98
98
  symbols: ['mU', 'mU', 'mU', 'mU'],
99
99
  color: 'rgb(65,233,80)',
100
100
  },
@@ -1,4 +1,3 @@
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';
@@ -6,8 +5,8 @@ import * as DG from 'datagrok-api/dg';
6
5
  import * as svg from 'save-svg-as-png';
7
6
  import $ from 'cash-dom';
8
7
 
9
- import {drawAxolabsPattern} from './drawAxolabsPattern';
10
- import {axolabsMap} from './axolabsMap';
8
+ import {drawAxolabsPattern} from './draw-svg';
9
+ import {axolabsMap} from './constants';
11
10
 
12
11
  const baseChoices: string[] = Object.keys(axolabsMap);
13
12
  const defaultBase: string = baseChoices[0];
@@ -57,17 +56,23 @@ function getUserName(patternName: string): string[] {
57
56
 
58
57
  function translateSequence(
59
58
  sequence: string,
60
- bases: any,
61
- ptoLinkages: any,
62
- startModification: any,
63
- endModification: any,
59
+ bases: DG.InputBase[],
60
+ ptoLinkages: DG.InputBase[],
61
+ startModification: DG.InputBase,
62
+ endModification: DG.InputBase,
64
63
  firstPtoExist: boolean): string {
65
- let counter: number = -1;
64
+ let i: number = -1;
66
65
  let mainSequence = sequence.replace(/[AUGC]/g, function(x: string) {
67
- counter++;
66
+ i++;
68
67
  const indexOfSymbol = axolabsMap['RNA']['symbols'].indexOf(x);
69
- const symbol = axolabsMap[bases[counter].value]['symbols'][indexOfSymbol];
70
- return (ptoLinkages[counter].value) ? symbol + 's' : symbol;
68
+ let symbol = axolabsMap[bases[i].value]['symbols'][indexOfSymbol];
69
+ if (bases[i].value.slice(-3) == '(o)') {
70
+ if (i < sequence.length / 2 && bases[i + 1].value.slice(-3) != '(o)')
71
+ symbol = symbol + x + 'f';
72
+ else if (i > sequence.length / 2 && bases[i - 1].value.slice(-3) != '(o)')
73
+ symbol = x + 'f' + symbol;
74
+ }
75
+ return (ptoLinkages[i].value) ? symbol + 's' : symbol;
71
76
  });
72
77
  if (mainSequence.slice(0, 5).split('mU').length == 3)
73
78
  mainSequence = '(uu)' + mainSequence.slice(4);
@@ -82,16 +87,18 @@ function addColumnWithIds(tableName: string, columnName: string, patternName: st
82
87
  if (columns.contains(nameOfNewColumn))
83
88
  columns.remove(nameOfNewColumn);
84
89
  const columnWithIds = columns.byName(columnName);
85
- return columns.addNewString(nameOfNewColumn).init((i: number) => columnWithIds.get(i) + '_' + patternName);
90
+ return columns.addNewString(nameOfNewColumn).init((i: number) => {
91
+ return (columnWithIds.getString(i) == '') ? '' : columnWithIds.get(i) + '_' + patternName;
92
+ });
86
93
  }
87
94
 
88
95
  function addColumnWithTranslatedSequences(
89
96
  tableName: string,
90
97
  columnName: string,
91
- bases: any,
92
- ptoLinkages: any,
93
- startModification: any,
94
- endModification: any,
98
+ bases: DG.InputBase[],
99
+ ptoLinkages: DG.InputBase[],
100
+ startModification: DG.InputBase,
101
+ endModification: DG.InputBase,
95
102
  firstPtoExist: boolean) {
96
103
  const nameOfNewColumn = 'Axolabs ' + columnName;
97
104
  const columns = grok.shell.table(tableName).columns;
@@ -99,8 +106,10 @@ function addColumnWithTranslatedSequences(
99
106
  columns.remove(nameOfNewColumn);
100
107
  const columnWithInputSequences = columns.byName(columnName);
101
108
  return columns.addNewString(nameOfNewColumn).init((i: number) => {
102
- return translateSequence(columnWithInputSequences.getString(i),
103
- bases, ptoLinkages, startModification, endModification, firstPtoExist);
109
+ return columnWithInputSequences.getString(i) == '' ?
110
+ '' :
111
+ translateSequence(columnWithInputSequences.getString(i), bases, ptoLinkages, startModification, endModification,
112
+ firstPtoExist);
104
113
  });
105
114
  }
106
115
 
@@ -236,8 +245,9 @@ export function defineAxolabsPattern() {
236
245
  }
237
246
 
238
247
  function updateInputExamples() {
239
- ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
240
- if (createAsStrand.value)
248
+ if (inputSsColumn.value == '')
249
+ ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
250
+ if (createAsStrand.value && inputAsColumn.value == '')
241
251
  asInputExample.value = generateExample(asLength.value!, sequenceBase.value!);
242
252
  }
243
253
 
@@ -329,10 +339,10 @@ export function defineAxolabsPattern() {
329
339
  }
330
340
 
331
341
  function checkWhetherAllValuesInColumnHaveTheSameLength(colName: string): boolean {
332
- const col = tables.value!.columns.byName(colName);
342
+ const col = tables.value!.getCol(colName);
333
343
  let allLengthsAreTheSame = true;
334
344
  for (let i = 1; i < col.length; i++) {
335
- if (col.get(i - 1).length != col.get(i).length) {
345
+ if (col.get(i - 1).length != col.get(i).length && col.get(i).length != 0) {
336
346
  allLengthsAreTheSame = false;
337
347
  break;
338
348
  }
@@ -361,12 +371,13 @@ export function defineAxolabsPattern() {
361
371
  }
362
372
 
363
373
  async function postPatternToUserStorage() {
364
- const author = await getCurrentUserName();
365
- if (!saveAs.stringValue.includes('(created by '))
366
- saveAs.value = saveAs.stringValue + author;
374
+ const currUserName = await getCurrentUserName();
375
+ saveAs.value = (saveAs.stringValue.includes('(created by ')) ?
376
+ getShortName(saveAs.value) + currUserName :
377
+ saveAs.stringValue + currUserName;
367
378
  return grok.dapi.userDataStorage.postValue(
368
379
  userStorageKey,
369
- saveAs.stringValue,
380
+ saveAs.value,
370
381
  JSON.stringify({
371
382
  'ssBases': ssBases.slice(0, ssLength.value!).map((e) => e.value),
372
383
  'asBases': asBases.slice(0, asLength.value!).map((e) => e.value),
@@ -387,11 +398,12 @@ export function defineAxolabsPattern() {
387
398
  const lstMy: string[] = [];
388
399
  const lstOthers: string[] = [];
389
400
 
401
+ // TODO: display short name, but use long for querying userdataStorage
390
402
  for (const ent of Object.keys(entities)) {
391
403
  if (await isCurrentUserCreatedThisPattern(ent))
392
404
  lstOthers.push(ent);
393
405
  else
394
- lstMy.push(getShortName(ent));
406
+ lstMy.push(ent);//getShortName(ent));
395
407
  }
396
408
 
397
409
  let loadPattern = ui.choiceInput('Load Pattern', '', lstMy, (v: string) => parsePatternAndUpdateUi(v));
@@ -504,29 +516,27 @@ export function defineAxolabsPattern() {
504
516
  const asLength = ui.intInput('AS Length', defaultSequenceLength, () => updateUiForNewSequenceLength());
505
517
  const asLengthDiv = ui.div([asLength.root]);
506
518
 
507
- function validateSsColumn(colName: string) {
519
+ function validateSsColumn(colName: string): void {
508
520
  const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
509
- const firstSequence = tables.value!.columns.byName(colName).get(0);
521
+ const firstSequence = tables.value!.getCol(colName).get(0);
510
522
  if (allLengthsAreTheSame && firstSequence.length != ssLength.value)
511
- ssLength.value = tables.value!.columns.byName(colName).get(0).length;
523
+ ssLength.value = tables.value!.getCol(colName).get(0).length;
512
524
  ssInputExample.value = firstSequence;
513
525
  }
514
526
 
515
- function validateAsColumn(colName: string) {
527
+ function validateAsColumn(colName: string): void {
516
528
  const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
517
- const firstSequence = tables.value!.columns.byName(colName).get(0);
529
+ const firstSequence = tables.value!.getCol(colName).get(0);
518
530
  if (allLengthsAreTheSame && firstSequence.length != asLength.value)
519
- asLength.value = tables.value!.columns.byName(colName).get(0).length;
520
- asLengthDiv.innerHTML = '';
521
- asLengthDiv.append(asLength.root);
531
+ asLength.value = tables.value!.getCol(colName).get(0).length;
522
532
  asInputExample.value = firstSequence;
523
533
  }
524
534
 
525
535
  function validateIdsColumn(colName: string) {
526
- const col = tables.value!.columns.byName(colName);
536
+ const col = tables.value!.getCol(colName);
527
537
  if (col.type != DG.TYPE.INT)
528
538
  grok.shell.error('Column should contain integers only');
529
- else if (col.categories.length < col.length) {
539
+ else if (col.categories.filter((e) => e != '').length < col.toList().filter((e) => e != '').length) {
530
540
  const duplicates = findDuplicates(col.getRawData());
531
541
  ui.dialog('Non-unique IDs')
532
542
  .add(ui.divText('Press \'OK\' to select rows with non-unique values'))
@@ -541,25 +551,43 @@ export function defineAxolabsPattern() {
541
551
  }
542
552
 
543
553
  const tables = ui.tableInput('Tables', grok.shell.tables[0], grok.shell.tables, (t: DG.DataFrame) => {
544
- const inputSsColumn =
545
- ui.choiceInput('SS Column', '', t.columns.names(), (colName: string) => validateSsColumn(colName));
554
+ const inputSsColumn = ui.choiceInput('SS Column', '', t.columns.names(), (colName: string) => {
555
+ validateSsColumn(colName);
556
+ ssVar = colName;
557
+ });
546
558
  inputSsColumnDiv.innerHTML = '';
547
559
  inputSsColumnDiv.append(inputSsColumn.root);
548
- const inputAsColumn =
549
- ui.choiceInput('AS Column', '', t.columns.names(), (colName: string) => validateAsColumn(colName));
560
+ const inputAsColumn = ui.choiceInput('AS Column', '', t.columns.names(), (colName: string) => {
561
+ validateAsColumn(colName);
562
+ asVar = colName;
563
+ });
550
564
  inputAsColumnDiv.innerHTML = '';
551
565
  inputAsColumnDiv.append(inputAsColumn.root);
552
- const inputIdColumn =
553
- ui.choiceInput('ID Column', '', t.columns.names(), (colName: string) => validateIdsColumn(colName));
566
+ const inputIdColumn = ui.choiceInput('ID Column', '', t.columns.names(), (colName: string) => {
567
+ validateIdsColumn(colName);
568
+ idVar = colName;
569
+ });
554
570
  inputIdColumnDiv.innerHTML = '';
555
571
  inputIdColumnDiv.append(inputIdColumn.root);
556
572
  });
557
573
 
558
- const inputSsColumn = ui.choiceInput('SS Column', '', []);
574
+ let ssVar = '';
575
+ const inputSsColumn = ui.choiceInput('SS Column', '', [], (colName: string) => {
576
+ validateSsColumn(colName);
577
+ ssVar = colName;
578
+ });
559
579
  inputSsColumnDiv.append(inputSsColumn.root);
560
- const inputAsColumn = ui.choiceInput('AS Column', '', []);
580
+ let asVar = '';
581
+ const inputAsColumn = ui.choiceInput('AS Column', '', [], (colName: string) => {
582
+ validateAsColumn(colName);
583
+ asVar = colName;
584
+ });
561
585
  inputAsColumnDiv.append(inputAsColumn.root);
562
- const inputIdColumn = ui.choiceInput('ID Column', '', []);
586
+ let idVar = '';
587
+ const inputIdColumn = ui.choiceInput('ID Column', '', [], (colName: string) => {
588
+ validateIdsColumn(colName);
589
+ idVar = colName;
590
+ });
563
591
  inputIdColumnDiv.append(inputIdColumn.root);
564
592
 
565
593
  updatePatternsList();
@@ -638,7 +666,7 @@ export function defineAxolabsPattern() {
638
666
  });
639
667
 
640
668
  const convertSequenceButton = ui.button('Convert Sequences', () => {
641
- if (inputSsColumn.value == null || (createAsStrand.value && inputAsColumn.value == null))
669
+ if (ssVar == '' || (createAsStrand.value && asVar == ''))
642
670
  grok.shell.info('Please select table and columns on which to apply pattern');
643
671
  else if (ssLength.value != ssInputExample.value.length || asLength.value != asInputExample.value.length) {
644
672
  const dialog = ui.dialog('Length Mismatch');
@@ -646,20 +674,20 @@ export function defineAxolabsPattern() {
646
674
  dialog
647
675
  .add(ui.divText('Length of sequences in columns doesn\'t match entered length. Update length value?'))
648
676
  .addButton('YES', () => {
649
- ssLength.value = tables.value!.columns.byName(inputSsColumn.value!).getString(0).length;
650
- asLength.value = tables.value!.columns.byName(inputAsColumn.value!).getString(0).length;
677
+ ssLength.value = tables.value!.getCol(inputSsColumn.value!).getString(0).length;
678
+ asLength.value = tables.value!.getCol(inputAsColumn.value!).getString(0).length;
651
679
  dialog.close();
652
680
  })
653
681
  .show();
654
682
  } else {
655
- if (inputIdColumn.value != null)
656
- addColumnWithIds(tables.value!.name, inputIdColumn.value, getShortName(saveAs.value));
683
+ if (idVar != '')
684
+ addColumnWithIds(tables.value!.name, idVar, getShortName(saveAs.value));
657
685
  addColumnWithTranslatedSequences(
658
- tables.value!.name, inputSsColumn.value, ssBases, ssPtoLinkages,
686
+ tables.value!.name, ssVar, ssBases, ssPtoLinkages,
659
687
  ssFiveModification, ssThreeModification, firstSsPto.value!);
660
688
  if (createAsStrand.value) {
661
689
  addColumnWithTranslatedSequences(
662
- tables.value!.name, inputAsColumn.value!, asBases, asPtoLinkages,
690
+ tables.value!.name, asVar, asBases, asPtoLinkages,
663
691
  asFiveModification, asThreeModification, firstAsPto.value!);
664
692
  }
665
693
  grok.shell.v = grok.shell.getTableView(tables.value!.name);
@@ -668,10 +696,7 @@ export function defineAxolabsPattern() {
668
696
  }
669
697
  });
670
698
 
671
- const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!), () => {
672
- ssOutputExample.value = translateSequence(ssInputExample.value, ssBases, ssPtoLinkages,
673
- ssFiveModification, ssThreeModification, firstSsPto.value!);
674
- });
699
+ const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!));
675
700
  const ssOutputExample = ui.textInput(' ', translateSequence(
676
701
  ssInputExample.value, ssBases, ssPtoLinkages, ssThreeModification, ssFiveModification, firstSsPto.value!));
677
702
  (ssInputExample.input as HTMLElement).style.resize = 'none';
@@ -689,10 +714,7 @@ export function defineAxolabsPattern() {
689
714
  ], 'ui-input-options'),
690
715
  );
691
716
 
692
- const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!), () => {
693
- asOutputExample.value = translateSequence(
694
- asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!);
695
- });
717
+ const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!));
696
718
  const asOutputExample = ui.textInput(' ', translateSequence(
697
719
  asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!));
698
720
  (asInputExample.input as HTMLElement).style.resize = 'none';
@@ -736,11 +758,14 @@ export function defineAxolabsPattern() {
736
758
  ]),
737
759
  ], 'ui-form');
738
760
 
761
+ const downloadButton = ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value,
762
+ {backgroundColor: 'white'}));
763
+
739
764
  const mainSection = ui.panel([
740
765
  ui.block([
741
766
  svgDiv,
742
767
  ], {style: {overflowX: 'scroll'}}),
743
- ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value)),
768
+ downloadButton,
744
769
  isEnumerateModificationsDiv,
745
770
  ui.div([
746
771
  ui.div([
@@ -1,4 +1,4 @@
1
- import {axolabsMap} from './axolabsMap';
1
+ import {axolabsMap} from './constants';
2
2
 
3
3
  // https://uxdesign.cc/star-rating-make-svg-great-again-d4ce4731347e
4
4
  function getPointsToDrawStar(centerX: number, centerY: number): string {