@datagrok/sequence-translator 1.2.6 → 1.2.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.
Files changed (102) hide show
  1. package/.eslintrc.json +5 -5
  2. package/CHANGELOG.md +12 -0
  3. package/dist/package-test.js +2 -1
  4. package/dist/package-test.js.LICENSE.txt +8 -0
  5. package/dist/package-test.js.map +1 -1
  6. package/dist/package.js +2 -1
  7. package/dist/package.js.LICENSE.txt +8 -0
  8. package/dist/package.js.map +1 -1
  9. package/files/pattern-app-data.json +80 -0
  10. package/package.json +21 -14
  11. package/src/{model → apps/common/model}/const.ts +1 -1
  12. package/src/{model/data-loading-utils → apps/common/model/data-loader}/const.ts +7 -2
  13. package/src/apps/common/model/data-loader/json-loader.ts +48 -0
  14. package/src/{model/data-loading-utils → apps/common/model/data-loader}/types.ts +13 -6
  15. package/src/{model → apps/common/model}/monomer-lib/lib-wrapper.ts +9 -12
  16. package/src/apps/common/model/oligo-toolkit-package.ts +30 -0
  17. package/src/{model → apps/common/model}/parsing-validation/format-detector.ts +5 -5
  18. package/src/{model → apps/common/model}/parsing-validation/format-handler.ts +18 -19
  19. package/src/{model → apps/common/model}/parsing-validation/sequence-validator.ts +1 -1
  20. package/src/apps/common/view/app-ui-base.ts +28 -0
  21. package/src/apps/common/view/combined-app-ui.ts +66 -0
  22. package/src/{view/utils → apps/common/view/components}/colored-input/colored-text-input.ts +1 -1
  23. package/src/{view/utils → apps/common/view/components}/draw-molecule.ts +1 -1
  24. package/src/{view/utils → apps/common/view/components}/molecule-img.ts +3 -3
  25. package/src/{view/const/ui.ts → apps/common/view/const.ts} +4 -4
  26. package/src/apps/common/view/isolated-app-ui.ts +43 -0
  27. package/src/{view/monomer-lib-viewer/viewer.ts → apps/common/view/monomer-lib-viewer.ts} +2 -2
  28. package/src/apps/common/view/utils.ts +29 -0
  29. package/src/apps/pattern/model/const.ts +121 -0
  30. package/src/apps/pattern/model/data-manager.ts +297 -0
  31. package/src/apps/pattern/model/event-bus.ts +470 -0
  32. package/src/apps/pattern/model/router.ts +46 -0
  33. package/src/apps/pattern/model/subscription-manager.ts +21 -0
  34. package/src/apps/pattern/model/translator.ts +68 -0
  35. package/src/apps/pattern/model/types.ts +52 -0
  36. package/src/apps/pattern/model/utils.ts +110 -0
  37. package/src/apps/pattern/view/components/bulk-convert/column-input.ts +69 -0
  38. package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +37 -0
  39. package/src/apps/pattern/view/components/bulk-convert/table-input.ts +95 -0
  40. package/src/apps/pattern/view/components/edit-block-controls.ts +196 -0
  41. package/src/apps/pattern/view/components/left-section.ts +44 -0
  42. package/src/apps/pattern/view/components/load-block-controls.ts +198 -0
  43. package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +69 -0
  44. package/src/apps/pattern/view/components/right-section.ts +148 -0
  45. package/src/apps/pattern/view/components/strand-editor/dialog.ts +79 -0
  46. package/src/apps/pattern/view/components/strand-editor/header-controls.ts +105 -0
  47. package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +159 -0
  48. package/src/apps/pattern/view/components/terminal-modification-editor.ts +127 -0
  49. package/src/apps/pattern/view/components/translation-examples-block.ts +139 -0
  50. package/src/{view/style/pattern-app.css → apps/pattern/view/style.css} +4 -0
  51. package/src/apps/pattern/view/svg-utils/const.ts +63 -0
  52. package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +498 -0
  53. package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +45 -0
  54. package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +82 -0
  55. package/src/apps/pattern/view/svg-utils/svg-renderer.ts +396 -0
  56. package/src/apps/pattern/view/svg-utils/utils.ts +37 -0
  57. package/src/apps/pattern/view/types.ts +14 -0
  58. package/src/apps/pattern/view/ui.ts +61 -0
  59. package/src/{model/structure-app → apps/structure/model}/mol-transformations.ts +3 -3
  60. package/src/{model/structure-app → apps/structure/model}/monomer-code-parser.ts +9 -10
  61. package/src/{model/structure-app → apps/structure/model}/oligo-structure.ts +4 -4
  62. package/src/{model/structure-app → apps/structure/model}/sequence-to-molfile.ts +2 -2
  63. package/src/{view/apps/oligo-structure.ts → apps/structure/view/ui.ts} +31 -17
  64. package/src/{model/translator-app → apps/translator/model}/conversion-utils.ts +25 -7
  65. package/src/{model/translator-app → apps/translator/model}/format-converter.ts +7 -12
  66. package/src/{view/const/oligo-translator.ts → apps/translator/view/const.ts} +2 -0
  67. package/src/apps/translator/view/ui.ts +547 -0
  68. package/src/demo/demo-st-ui.ts +12 -32
  69. package/src/package.ts +76 -56
  70. package/src/plugins/mermade.ts +9 -9
  71. package/src/polytool/const.ts +40 -0
  72. package/src/polytool/csv-to-json-monomer-lib-converter.ts +40 -0
  73. package/src/polytool/cyclized.ts +56 -0
  74. package/src/polytool/monomer-lib-handler.ts +115 -0
  75. package/src/polytool/transformation.ts +326 -0
  76. package/src/polytool/ui.ts +59 -0
  77. package/src/polytool/utils.ts +20 -0
  78. package/src/tests/const.ts +5 -5
  79. package/src/tests/formats-support.ts +6 -6
  80. package/src/tests/formats-to-helm.ts +5 -5
  81. package/src/tests/helm-to-nucleotides.ts +5 -5
  82. package/tsconfig.json +4 -10
  83. package/webpack.config.js +3 -0
  84. package/files/axolabs-style.json +0 -97
  85. package/src/model/data-loading-utils/json-loader.ts +0 -38
  86. package/src/model/pattern-app/const.ts +0 -33
  87. package/src/model/pattern-app/draw-svg.ts +0 -193
  88. package/src/model/pattern-app/helpers.ts +0 -96
  89. package/src/model/pattern-app/oligo-pattern.ts +0 -111
  90. package/src/view/app-ui.ts +0 -193
  91. package/src/view/apps/oligo-pattern.ts +0 -759
  92. package/src/view/apps/oligo-translator.ts +0 -184
  93. /package/src/{model → apps/common/model}/helpers.ts +0 -0
  94. /package/src/{model → apps/common/model}/monomer-lib/const.ts +0 -0
  95. /package/src/{view/utils → apps/common/view/components}/app-info-dialog.ts +0 -0
  96. /package/src/{view/utils → apps/common/view/components}/colored-input/input-painters.ts +0 -0
  97. /package/src/{view/style/colored-text-input.css → apps/common/view/components/colored-input/style.css} +0 -0
  98. /package/src/{view/utils → apps/common/view/components}/router.ts +0 -0
  99. /package/src/{model/structure-app → apps/structure/model}/const.ts +0 -0
  100. /package/src/{view/style/structure-app.css → apps/structure/view/style.css} +0 -0
  101. /package/src/{model/translator-app → apps/translator/model}/const.ts +0 -0
  102. /package/src/{view/style/translator-app.css → apps/translator/view/style.css} +0 -0
@@ -1,759 +0,0 @@
1
- /* Do not change these import lines to match external modules in webpack configuration */
2
- import * as grok from 'datagrok-api/grok';
3
- import * as ui from 'datagrok-api/ui';
4
- import * as DG from 'datagrok-api/dg';
5
-
6
- import {axolabsStyleMap} from '../../model/data-loading-utils/json-loader';
7
- import {
8
- DEFAULT_PTO, DEFAULT_SEQUENCE_LENGTH, MAX_SEQUENCE_LENGTH, USER_STORAGE_KEY, SS, AS, STRAND_NAME, STRANDS, TERMINAL, TERMINAL_KEYS, THREE_PRIME, FIVE_PRIME, JSON_FIELD as FIELD
9
- } from '../../model/pattern-app/const';
10
- import {isOverhang} from '../../model/pattern-app/helpers';
11
- import {generateExample, translateSequence, getShortName, isCurrentUserCreatedThisPattern, findDuplicates, addColumnWithIds, addColumnWithTranslatedSequences} from '../../model//pattern-app/oligo-pattern';
12
- import {drawAxolabsPattern} from '../../model/pattern-app/draw-svg';
13
- // todo: remove ts-ignore
14
- //@ts-ignore
15
- import * as svg from 'save-svg-as-png';
16
- import $ from 'cash-dom';
17
-
18
- type BooleanInput = DG.InputBase<boolean | null>;
19
- type StringInput = DG.InputBase<string | null>;
20
-
21
- export class PatternLayoutHandler {
22
- get htmlDivElement() {
23
- function updateModification(strand: string) {
24
- modificationItems[strand].innerHTML = '';
25
- ptoLinkages[strand] = ptoLinkages[strand].concat(Array(maxStrandLength[strand] - baseInputsObject[strand].length).fill(fullyPto));
26
- baseInputsObject[strand] = baseInputsObject[strand].concat(Array(maxStrandLength[strand] - baseInputsObject[strand].length).fill(sequenceBase));
27
- let nucleotideCounter = 0;
28
- for (let i = 0; i < strandLengthInput[strand].value!; i++) {
29
- ptoLinkages[strand][i] = ui.boolInput('', ptoLinkages[strand][i].value!, () => {
30
- updateSvgScheme();
31
- updateOutputExamples();
32
- });
33
- baseInputsObject[strand][i] = ui.choiceInput('', baseInputsObject[strand][i].value, baseChoices, (v: string) => {
34
- if (!enumerateModifications.includes(v)) {
35
- enumerateModifications.push(v);
36
- isEnumerateModificationsDiv.append(
37
- ui.divText('', {style: {width: '25px'}}),
38
- ui.boolInput(v, true, (boolV: boolean) => {
39
- if (boolV) {
40
- if (!enumerateModifications.includes(v))
41
- enumerateModifications.push(v);
42
- } else {
43
- const index = enumerateModifications.indexOf(v, 0);
44
- if (index > -1)
45
- enumerateModifications.splice(index, 1);
46
- }
47
- updateSvgScheme();
48
- }).root,
49
- );
50
- }
51
- updateModification(AS);
52
- updateSvgScheme();
53
- updateOutputExamples();
54
- });
55
- $(baseInputsObject[strand][i].root).addClass('st-pattern-choice-input');
56
- if (!isOverhang(baseInputsObject[strand][i].value!))
57
- nucleotideCounter++;
58
-
59
- modificationItems[strand].append(
60
- ui.divH([
61
- ui.div([ui.label(isOverhang(baseInputsObject[strand][i].value!) ? '' : String(nucleotideCounter))],
62
- {style: {width: '20px'}})!,
63
- ui.block75([baseInputsObject[strand][i].root])!,
64
- ui.div([ptoLinkages[strand][i]])!,
65
- ], {style: {alignItems: 'center'}}),
66
- );
67
- }
68
- }
69
-
70
- function updateUiForNewSequenceLength() {
71
- if (Object.values(strandLengthInput).every((input) => input.value! < MAX_SEQUENCE_LENGTH)) {
72
- STRANDS.forEach((strand) => {
73
- if (strandLengthInput[strand].value! > maxStrandLength[strand])
74
- maxStrandLength[strand] = strandLengthInput[strand].value!;
75
- updateModification(strand);
76
- })
77
-
78
- updateSvgScheme();
79
- updateInputExamples();
80
- updateOutputExamples();
81
- } else {
82
- ui.dialog('Out of range')
83
- .add(ui.divText('Sequence length should be less than ' +
84
- MAX_SEQUENCE_LENGTH.toString() + ' due to UI constrains.'))
85
- .onOK(()=> {Object.values(strandLengthInput).every((input)=> input.value = 34)})
86
- .onCancel(()=> {Object.values(strandLengthInput).every((input)=> input.value = 34)})
87
- .showModal(false);
88
- }
89
- }
90
-
91
- // todo: unify with updateBases
92
- function updatePto(newPtoValue: boolean): void {
93
- STRANDS.forEach((strand) => {
94
- for (let i = 0; i < ptoLinkages[strand].length; i++)
95
- ptoLinkages[strand][i].value = newPtoValue;
96
- })
97
- updateSvgScheme();
98
- }
99
-
100
- function updateBases(newBasisValue: string): void {
101
- STRANDS.forEach((strand) => {
102
- for (let i = 0; i < baseInputsObject[strand].length; i++)
103
- baseInputsObject[strand][i].value = newBasisValue;
104
- })
105
- updateSvgScheme();
106
- }
107
-
108
- function updateInputExamples() {
109
- STRANDS.forEach((s) => {
110
- if (strandColumnInput[s].value === '')
111
- inputExample[s].value = generateExample(strandLengthInput[s].value!, sequenceBase.value!);
112
- });
113
- }
114
-
115
- function updateOutputExamples() {
116
- const conditions = [true, createAsStrand.value];
117
- STRANDS.forEach((strand, i) => {
118
- if (conditions[i]) {
119
- outputExample[strand].value = translateSequence(inputExample[strand].value, baseInputsObject[strand], ptoLinkages[strand], terminalModification[strand][FIVE_PRIME], terminalModification[strand][THREE_PRIME], firstPto[strand].value!);
120
- }
121
- })
122
- }
123
-
124
- function updateSvgScheme() {
125
- svgDiv.innerHTML = '';
126
- svgDiv.append(
127
- ui.span([
128
-
129
- // todo: refactor the funciton, reduce # of args
130
- drawAxolabsPattern(
131
- getShortName(saveAs.value),
132
- createAsStrand.value!,
133
-
134
- baseInputsObject[SS].slice(0, strandLengthInput[SS].value!).map((e) => e.value!),
135
- baseInputsObject[AS].slice(0, strandLengthInput[AS].value!).map((e) => e.value!),
136
-
137
- [firstPto[SS].value!].concat(ptoLinkages[SS].slice(0, strandLengthInput[SS].value!).map((e) => e.value!)),
138
- [firstPto[AS].value!].concat(ptoLinkages[AS].slice(0, strandLengthInput[AS].value!).map((e) => e.value!)),
139
-
140
- terminalModification[SS][THREE_PRIME].value,
141
- terminalModification[SS][FIVE_PRIME].value,
142
-
143
- terminalModification[AS][THREE_PRIME].value,
144
- terminalModification[AS][FIVE_PRIME].value,
145
-
146
- comment.value,
147
- enumerateModifications,
148
- ),
149
- ]),
150
- );
151
- }
152
-
153
- // todo: rename
154
- function detectDefaultBasis(array: string[]) {
155
- const modeMap: {[index: string]: number} = {};
156
- let maxEl = array[0];
157
- let maxCount = 1;
158
- for (let i = 0; i < array.length; i++) {
159
- const el = array[i];
160
- if (modeMap[el] === null)
161
- modeMap[el] = 1;
162
- else
163
- modeMap[el]++;
164
- if (modeMap[el] > maxCount) {
165
- maxEl = el;
166
- maxCount = modeMap[el];
167
- }
168
- }
169
- return maxEl;
170
- }
171
-
172
- async function parsePatternAndUpdateUi(newName: string) {
173
- const pi = DG.TaskBarProgressIndicator.create('Loading pattern...');
174
- await grok.dapi.userDataStorage.get(USER_STORAGE_KEY, false).then((entities) => {
175
- const obj = JSON.parse(entities[newName]);
176
- sequenceBase.value = detectDefaultBasis(obj[FIELD.AS_BASES].concat(obj[FIELD.SS_BASES]));
177
- createAsStrand.value = (obj[FIELD.AS_BASES].length > 0);
178
- saveAs.value = newName;
179
-
180
- let fields = [FIELD.SS_BASES, FIELD.AS_BASES];
181
- STRANDS.forEach((strand, i) => {
182
- baseInputsObject[strand] = [];
183
- const field = fields[i];
184
- for (let j = 0; j < obj[field].length; j++)
185
- baseInputsObject[strand].push(ui.choiceInput('', obj[field][j], baseChoices));
186
- })
187
-
188
- fields = [FIELD.SS_PTO, FIELD.AS_PTO];
189
- STRANDS.forEach((s, i) => {
190
- const field = fields[i];
191
- firstPto[s].value = obj[field][0];
192
- ptoLinkages[s] = [];
193
- for (let j = 1; j < obj[field].length; j++)
194
- ptoLinkages[s].push(ui.boolInput('', obj[field][j]));
195
- });
196
-
197
- fields = [FIELD.SS_BASES, FIELD.AS_BASES];
198
- STRANDS.forEach((strand, i) => {
199
- strandLengthInput[strand].value = obj[fields[i]].length;
200
- })
201
-
202
- const field = [[FIELD.SS_3, FIELD.SS_5], [FIELD.AS_3, FIELD.AS_5]];
203
- STRANDS.forEach((strand, i) => {
204
- TERMINAL_KEYS.forEach((terminal, j) => {
205
- terminalModification[strand][terminal].value = obj[field[i][j]];
206
- })
207
- })
208
- comment.value = obj[FIELD.COMMENT];
209
- });
210
- pi.close();
211
- }
212
-
213
- function allColumnValuesOfEqualLength(colName: string): boolean {
214
- const col = tableInput.value!.getCol(colName);
215
- let allLengthsAreTheSame = true;
216
- for (let i = 1; i < col.length; i++) {
217
- if (col.get(i - 1).length !== col.get(i).length && col.get(i).length !== 0) {
218
- allLengthsAreTheSame = false;
219
- break;
220
- }
221
- }
222
- if (!allLengthsAreTheSame) {
223
- const dialog = ui.dialog('Sequences lengths mismatch');
224
- $(dialog.getButton('OK')).hide();
225
- dialog
226
- .add(ui.divText('The sequence length should match the number of Raw sequences in the input file'))
227
- .add(ui.divText('\'ADD COLUMN\' to see sequences lengths'))
228
- .addButton('ADD COLUMN', () => {
229
- tableInput.value!.columns.addNewInt('Sequences lengths in ' + colName).init((j: number) => col.get(j).length);
230
- grok.shell.info('Column with lengths added to \'' + tableInput.value!.name + '\'');
231
- dialog.close();
232
- grok.shell.v = grok.shell.getTableView(tableInput.value!.name);
233
- })
234
- .show();
235
- }
236
- if (col.get(0).length !== strandLengthInput[SS].value) {
237
- const d = ui.dialog('Length was updated by value to from imported file');
238
- d.add(ui.divText('Latest modifications may not take effect during translation'))
239
- .onOK(() => grok.shell.info('Lengths changed')).show();
240
- }
241
- return allLengthsAreTheSame;
242
- }
243
-
244
- async function getCurrentUserName(): Promise<string> {
245
- return await grok.dapi.users.current().then((user) => {
246
- return ' (created by ' + user.friendlyName + ')';
247
- });
248
- }
249
-
250
- async function postPatternToUserStorage() {
251
- const currUserName = await getCurrentUserName();
252
- saveAs.value = (saveAs.stringValue.includes('(created by ')) ?
253
- getShortName(saveAs.value) + currUserName :
254
- saveAs.stringValue + currUserName;
255
- return grok.dapi.userDataStorage.postValue(
256
- USER_STORAGE_KEY,
257
- saveAs.value,
258
- JSON.stringify({
259
- [FIELD.SS_BASES]: baseInputsObject[SS].slice(0, strandLengthInput[SS].value!).map((e) => e.value),
260
- [FIELD.AS_BASES]: baseInputsObject[AS].slice(0, strandLengthInput[AS].value!).map((e) => e.value),
261
- [FIELD.SS_PTO]: [firstPto[SS].value].concat(ptoLinkages[SS].slice(0, strandLengthInput[SS].value!).map((e) => e.value)),
262
- [FIELD.AS_PTO]: [firstPto[AS].value].concat(ptoLinkages[AS].slice(0, strandLengthInput[AS].value!).map((e) => e.value)),
263
- [FIELD.SS_3]: terminalModification[SS][THREE_PRIME].value,
264
- [FIELD.SS_5]:terminalModification[SS][FIVE_PRIME].value,
265
- [FIELD.AS_3]: terminalModification[AS][THREE_PRIME].value,
266
- [FIELD.AS_5]: terminalModification[AS][FIVE_PRIME].value,
267
- [FIELD.COMMENT]: comment.value,
268
- }),
269
- false,
270
- ).then(() => grok.shell.info('Pattern \'' + saveAs.value + '\' was successfully uploaded!'));
271
- }
272
-
273
- async function updatePatternsList() {
274
- grok.dapi.userDataStorage.get(USER_STORAGE_KEY, false).then(async (entities) => {
275
- const lstMy: string[] = [];
276
- const lstOthers: string[] = [];
277
-
278
- // TODO: display short name, but use long for querying userdataStorage
279
- for (const ent of Object.keys(entities)) {
280
- if (await isCurrentUserCreatedThisPattern(ent))
281
- lstOthers.push(ent);
282
- else
283
- lstMy.push(ent);//getShortName(ent));
284
- }
285
-
286
- let loadPattern = ui.choiceInput('Load pattern', '', lstMy, (v: string) => parsePatternAndUpdateUi(v));
287
-
288
- const currentUserName = (await grok.dapi.users.current()).friendlyName;
289
- const otherUsers = 'Other users';
290
-
291
- const patternListChoiceInput = ui.choiceInput('', currentUserName, [currentUserName, otherUsers], (v: string) => {
292
- const currentList = v === currentUserName ? lstMy : lstOthers;
293
- loadPattern = ui.choiceInput('Load pattern', '', currentList, (v: string) => parsePatternAndUpdateUi(v));
294
-
295
- loadPattern.root.append(patternListChoiceInput.input);
296
- loadPattern.root.append(loadPattern.input);
297
- // @ts-ignore
298
- loadPattern.input.style.maxWidth = '120px';
299
- loadPattern.input.style.marginLeft = '12px';
300
- loadPattern.setTooltip('Apply Existing Pattern');
301
-
302
- loadPatternDiv.innerHTML = '';
303
- loadPatternDiv.append(loadPattern.root);
304
- loadPattern.root.append(
305
- ui.div([
306
- ui.button(ui.iconFA('trash-alt', () => {}), async () => {
307
- if (loadPattern.value === null)
308
- grok.shell.warning('Choose pattern to delete');
309
- else if (await isCurrentUserCreatedThisPattern(saveAs.value))
310
- grok.shell.warning('Cannot delete pattern, created by other user');
311
- else {
312
- await grok.dapi.userDataStorage.remove(USER_STORAGE_KEY, loadPattern.value, false)
313
- .then(() => grok.shell.info('Pattern \'' + loadPattern.value + '\' deleted'));
314
- }
315
- await updatePatternsList();
316
- }),
317
- ], 'ui-input-options'),
318
- );
319
- });
320
- patternListChoiceInput.input.style.maxWidth = '142px';
321
- loadPattern.root.append(patternListChoiceInput.input);
322
- loadPattern.root.append(loadPattern.input);
323
- // @ts-ignore
324
- loadPattern.input.style.maxWidth = '100px';
325
- loadPattern.setTooltip('Apply Existing Pattern');
326
-
327
- loadPatternDiv.innerHTML = '';
328
- loadPatternDiv.append(loadPattern.root);
329
- loadPattern.root.append(
330
- ui.div([
331
- ui.button(ui.iconFA('trash-alt', () => {}), async () => {
332
- if (loadPattern.value === null)
333
- grok.shell.warning('Choose pattern to delete');
334
- else if (await isCurrentUserCreatedThisPattern(saveAs.value))
335
- grok.shell.warning('Cannot delete pattern, created by other user');
336
- else {
337
- await grok.dapi.userDataStorage.remove(USER_STORAGE_KEY, loadPattern.value, false)
338
- .then(() => grok.shell.info('Pattern \'' + loadPattern.value + '\' deleted'));
339
- }
340
- await updatePatternsList();
341
- }),
342
- ], 'ui-input-options'),
343
- );
344
- });
345
- }
346
-
347
- async function savePattern() {
348
- await grok.dapi.userDataStorage.get(USER_STORAGE_KEY, false)
349
- .then((entities) => {
350
- if (Object.keys(entities).includes(saveAs.value)) {
351
- const dialog = ui.dialog('Pattern already exists');
352
- $(dialog.getButton('OK')).hide();
353
- dialog
354
- .add(ui.divText('Pattern name \'' + saveAs.value + '\' already exists.'))
355
- .add(ui.divText('Replace pattern?'))
356
- .addButton('YES', async () => {
357
- await grok.dapi.userDataStorage.remove(USER_STORAGE_KEY, saveAs.value, false)
358
- .then(() => postPatternToUserStorage());
359
- dialog.close();
360
- })
361
- .show();
362
- } else
363
- postPatternToUserStorage();
364
- });
365
- await updatePatternsList();
366
- }
367
-
368
- function validateStrandColumn(colName: string, strand: string): void {
369
- const allLengthsAreTheSame: boolean = allColumnValuesOfEqualLength(colName);
370
- const firstSequence = tableInput.value!.getCol(colName).get(0);
371
- if (allLengthsAreTheSame && firstSequence.length !== strandLengthInput[strand].value)
372
- strandLengthInput[strand].value = tableInput.value!.getCol(colName).get(0).length;
373
- inputExample[strand].value = firstSequence;
374
- }
375
-
376
- function validateIdsColumn(colName: string) {
377
- const col = tableInput.value!.getCol(colName);
378
- if (col.type !== DG.TYPE.INT)
379
- grok.shell.error('Column should contain integers only');
380
- //@ts-ignore
381
- else if (col.categories.filter((e) => e !== '').length < col.toList().filter((e) => e !== '').length) {
382
- const duplicates = findDuplicates(col.getRawData());
383
- ui.dialog('Non-unique IDs')
384
- .add(ui.divText('Press \'OK\' to select rows with non-unique values'))
385
- .onOK(() => {
386
- const selection = tableInput.value!.selection;
387
- selection.init((i: number) => duplicates.indexOf(col.get(i)) > -1);
388
- grok.shell.v = grok.shell.getTableView(tableInput.value!.name);
389
- grok.shell.info('Rows are selected in table \'' + tableInput.value!.name + '\'');
390
- })
391
- .show();
392
- }
393
- }
394
-
395
- const baseChoices: string[] = Object.keys(axolabsStyleMap);
396
- const defaultBase: string = baseChoices[0];
397
- const enumerateModifications = [defaultBase];
398
- const sequenceBase = ui.choiceInput('Sequence basis', defaultBase, baseChoices, (v: string) => {
399
- updateBases(v);
400
- updateOutputExamples();
401
- });
402
- const fullyPto = ui.boolInput('Fully PTO', DEFAULT_PTO, (v: boolean) => {
403
- STRANDS.forEach((s) => { firstPto[s].value = v; })
404
- updatePto(v);
405
- updateOutputExamples();
406
- });
407
- fullyPto.captionLabel.classList.add('ui-label-right');
408
- fullyPto.captionLabel.style.textAlign = 'left';
409
- fullyPto.captionLabel.style.maxWidth = '100px';
410
- fullyPto.captionLabel.style.maxWidth = '100px';
411
- fullyPto.captionLabel.style.minWidth = '40px';
412
- fullyPto.captionLabel.style.width = 'auto';
413
-
414
- const maxStrandLength = Object.fromEntries(STRANDS.map(
415
- (strand) => [strand, DEFAULT_SEQUENCE_LENGTH]
416
- ));
417
- // todo: remove vague legacy 'items' from name
418
- const modificationItems = Object.fromEntries(STRANDS.map(
419
- (strand) => [strand, ui.div([])]
420
- ));
421
- const ptoLinkages = Object.fromEntries(STRANDS.map(
422
- (strand) => [strand, Array<BooleanInput>(DEFAULT_SEQUENCE_LENGTH)
423
- .fill(ui.boolInput('', DEFAULT_PTO))]
424
- ));
425
- const baseInputsObject = Object.fromEntries(STRANDS.map(
426
- (strand) => {
427
- const choiceInputs = Array<StringInput>(DEFAULT_SEQUENCE_LENGTH)
428
- .fill(ui.choiceInput('', defaultBase, baseChoices));
429
- return [strand, choiceInputs];
430
- }
431
- ));
432
- const strandLengthInput = Object.fromEntries(STRANDS.map(
433
- (strand) => {
434
- const input = ui.intInput(`${STRAND_NAME[strand]} length`, DEFAULT_SEQUENCE_LENGTH, () => updateUiForNewSequenceLength());
435
- input.setTooltip(`Length of ${STRAND_NAME[strand].toLowerCase()}, including overhangs`);
436
- return [strand, input];
437
- }));
438
- const strandVar = Object.fromEntries(STRANDS.map((strand) => [strand, '']));
439
- const inputExample = Object.fromEntries(STRANDS.map(
440
- (strand) => [strand, ui.textInput(
441
- ``, generateExample(strandLengthInput[strand].value!, sequenceBase.value!))
442
- ]));
443
-
444
- const strandColumnInput = Object.fromEntries(STRANDS.map((strand) => {
445
- const input = ui.choiceInput(`${STRAND_NAME[strand]} column`, '', [], (colName: string) => {
446
- validateStrandColumn(colName, strand);
447
- strandVar[strand] = colName;
448
- });
449
- return [strand, input];
450
- }));
451
-
452
- const firstPto = Object.fromEntries(STRANDS.map((strand) => {
453
- const input = ui.boolInput(`First ${strand} PTO`, fullyPto.value!, () => updateSvgScheme());
454
- input.setTooltip(`ps linkage before first nucleotide of ${STRAND_NAME[strand].toLowerCase()}`);
455
-
456
- input.captionLabel.classList.add('ui-label-right');
457
- input.captionLabel.style.textAlign = 'left';
458
- input.captionLabel.style.maxWidth = '100px';
459
- input.captionLabel.style.minWidth = '40px';
460
- input.captionLabel.style.width = 'auto';
461
-
462
- return [strand, input];
463
- }));
464
-
465
- const terminalModification = Object.fromEntries(STRANDS.map((strand) => {
466
- const inputs = Object.fromEntries(TERMINAL_KEYS.map((key) => {
467
- const input = ui.stringInput(`${strand} ${TERMINAL[key]}\' Modification`, '', () => {
468
- updateSvgScheme();
469
- updateOutputExamples();
470
- });
471
- input.setTooltip(`Additional ${strand} ${TERMINAL[key]}\' Modification`);
472
- return [key, input];
473
- }));
474
- return [strand, inputs];
475
- }));
476
-
477
- const outputExample = Object.fromEntries(STRANDS.map((strand) => {
478
- const input = ui.textInput('', translateSequence(
479
- inputExample[strand].value, baseInputsObject[strand], ptoLinkages[strand], terminalModification[strand][THREE_PRIME],terminalModification[strand][FIVE_PRIME], firstPto[strand].value!
480
- ));
481
- input.input.style.minWidth = 'none';
482
- input.input.style.flexGrow = '1';
483
- $(input.root.lastChild).css('height', 'auto');
484
- return [strand, input];
485
- }));
486
-
487
- const modificationSection = Object.fromEntries(STRANDS.map((strand) => {
488
- const panel = ui.block([
489
- ui.h1(`${STRAND_NAME[strand]}`),
490
- ui.divH([
491
- ui.div([ui.divText('#')], {style: {width: '20px'}})!,
492
- ui.block75([ui.divText('Modification')])!,
493
- ui.div([ui.divText('PTO')])!,
494
- ]),
495
- modificationItems[strand],
496
- ], {style: {paddingTop: '12px'}});
497
- return [strand, panel];
498
- }));
499
-
500
- STRANDS.forEach((s) => {
501
-
502
- inputExample[s].input.style.resize = 'none';
503
- outputExample[s].input.style.resize = 'none';
504
- inputExample[s].input.style.minWidth = 'none';
505
- inputExample[s].input.style.flexGrow = '1';
506
- outputExample[s].input.style.minWidth = 'none';
507
- outputExample[s].input.style.flexGrow = '1';
508
- let options = ui.div([
509
- ui.button(ui.iconFA('copy', () => {}), () => {
510
- navigator.clipboard.writeText(outputExample[s].value).then(() =>
511
- grok.shell.info('Sequence was copied to clipboard'));
512
- }),
513
- ], 'ui-input-options');
514
- options.style.height = 'inherit';
515
- outputExample[s].root.append(
516
- options
517
- );
518
- })
519
-
520
- // const inputIdColumnDiv = ui.div([]);
521
- const svgDiv = ui.div([]);
522
- const asExampleDiv = ui.div([], 'ui-form ui-form-wide');
523
- const loadPatternDiv = ui.div([]);
524
- const asModificationDiv = ui.form([]);
525
- const isEnumerateModificationsDiv = ui.divH([
526
- ui.boolInput(defaultBase, true, (v: boolean) => {
527
- if (v) {
528
- if (!enumerateModifications.includes(defaultBase))
529
- enumerateModifications.push(defaultBase);
530
- } else {
531
- const index = enumerateModifications.indexOf(defaultBase, 0);
532
- if (index > -1)
533
- enumerateModifications.splice(index, 1);
534
- }
535
- updateSvgScheme();
536
- updateOutputExamples();
537
- }).root,
538
- ]);
539
-
540
- const asLengthDiv = ui.div([strandLengthInput[AS].root]);
541
-
542
- function getTableInput(tableList: DG.DataFrame[]): DG.InputBase {
543
- const tableInput = ui.tableInput('Tables', tableList[0], tableList, () => {
544
- const table = tableInput.value;
545
- if (table === null) {
546
- console.warn('Table is null');
547
- return;
548
- }
549
- const tableName = table!.name;
550
- if (!grok.shell.tableNames.includes(tableName)) {
551
- const view = grok.shell.v;
552
- grok.shell.addTableView(table!);
553
- grok.shell.v = view;
554
- }
555
- const columnNames = table.columns.names();
556
-
557
- STRANDS.forEach((strand) => {
558
- const defaultColumn = columnNames[0];
559
- validateStrandColumn(defaultColumn, strand);
560
- strandVar[strand] = defaultColumn;
561
- const input = ui.choiceInput(`${STRAND_NAME[strand]} column`, defaultColumn, columnNames, (colName: string) => {
562
- validateStrandColumn(colName, strand);
563
- strandVar[strand] = colName;
564
- console.log(`clicked ${strand} var:`, strandVar[strand]);
565
- });
566
- $(strandColumnInput[strand].root).replaceWith(input.root);
567
- })
568
-
569
- idVar = columnNames[0];
570
- // todo: unify with inputStrandColumn
571
- const idInput = ui.choiceInput('ID column', columnNames[0], columnNames, (colName: string) => {
572
- validateIdsColumn(colName);
573
- idVar = colName;
574
- });
575
- $(inputIdColumn.root).replaceWith(idInput.root);
576
- });
577
- return tableInput;
578
- }
579
-
580
- const tableInput = getTableInput([]);
581
-
582
- // todo: unify with strandVar
583
- let idVar = '';
584
- const inputIdColumn = ui.choiceInput('ID column', '', [], (colName: string) => {
585
- validateIdsColumn(colName);
586
- idVar = colName;
587
- });
588
- // inputIdColumnDiv.append(inputIdColumn.root);
589
-
590
- updatePatternsList();
591
-
592
- const createAsStrand = ui.boolInput('Anti sense strand', true, (v: boolean) => {
593
- modificationSection[AS].hidden = !v;
594
- strandColumnInput[AS].root.hidden = !v;
595
- asLengthDiv.hidden = !v;
596
- asModificationDiv.hidden = !v;
597
- asExampleDiv.hidden = !v;
598
- firstPto[AS].root.hidden = !v;
599
- updateSvgScheme();
600
- });
601
- createAsStrand.setTooltip('Create antisense strand sections on SVG and table to the right');
602
-
603
- const saveAs = ui.textInput('Save as', 'Pattern name', () => updateSvgScheme());
604
- saveAs.setTooltip('Name Of New Pattern');
605
-
606
-
607
- TERMINAL_KEYS.forEach((terminal) => {
608
- asModificationDiv.append(terminalModification[AS][terminal].root);
609
- })
610
-
611
- const comment = ui.textInput('Comment', '', () => updateSvgScheme());
612
-
613
- const savePatternButton = ui.bigButton('Save', () => {
614
- if (saveAs.value !== '')
615
- savePattern().then(() => grok.shell.info('Pattern saved'));
616
- else {
617
- const name = ui.stringInput('Enter name', '');
618
- ui.dialog('Pattern Name')
619
- .add(name.root)
620
- .onOK(() => {
621
- saveAs.value = name.value;
622
- savePattern().then(() => grok.shell.info('Pattern saved'));
623
- })
624
- .show();
625
- }
626
- });
627
- saveAs.addOptions(savePatternButton);
628
-
629
- const convertSequenceButton = ui.bigButton('Convert', () => {
630
- const condition = [true, createAsStrand.value];
631
- console.log(`strand vars:`, Object.values(strandVar));
632
- if (STRANDS.some((s, i) => condition[i] && strandVar[s] === ''))
633
- grok.shell.info('Please select table and columns on which to apply pattern');
634
- else if (STRANDS.some((s) => strandLengthInput[s].value !== inputExample[s].value.length)) {
635
- const dialog = ui.dialog('Length Mismatch');
636
- $(dialog.getButton('OK')).hide();
637
- dialog
638
- .add(ui.divText('Length of sequences in columns doesn\'t match entered length. Update length value?'))
639
- .addButton('YES', () => {
640
- STRANDS.forEach((s) => {
641
- strandLengthInput[s].value = tableInput.value!.getCol(strandColumnInput[s].value!).getString(0).length;
642
- })
643
- dialog.close();
644
- })
645
- .show();
646
- } else {
647
- if (idVar !== '')
648
- addColumnWithIds(tableInput.value!.name, idVar, getShortName(saveAs.value));
649
- const condition = [true, createAsStrand.value];
650
- STRANDS.forEach((strand, i) => {
651
- if (condition[i])
652
- addColumnWithTranslatedSequences(
653
- tableInput.value!.name, strandVar[strand], baseInputsObject[strand], ptoLinkages[strand],
654
- terminalModification[strand][FIVE_PRIME], terminalModification[strand][THREE_PRIME], firstPto[strand].value!);
655
- })
656
- grok.shell.v = grok.shell.getTableView(tableInput.value!.name);
657
- grok.shell.info(((createAsStrand.value) ? 'Columns were' : 'Column was') +
658
- ' added to table \'' + tableInput.value!.name + '\'');
659
- updateOutputExamples();
660
- }
661
- });
662
-
663
- asExampleDiv.append(inputExample[AS].root);
664
- asExampleDiv.append(outputExample[AS].root);
665
-
666
- updateUiForNewSequenceLength();
667
-
668
- const exampleSection = ui.div([
669
- ui.h1('Conversion preview'),
670
- inputExample[SS].root,
671
- outputExample[SS].root,
672
- asExampleDiv,
673
- ], 'ui-form ui-form-wide');
674
-
675
- const inputsSection = ui.block50([
676
- ui.h1('Convert options'),
677
- tableInput.root,
678
- strandColumnInput[SS].root,
679
- strandColumnInput[AS].root,
680
- inputIdColumn.root,
681
- ui.buttonsInput([
682
- convertSequenceButton,
683
- ]),
684
- ]);
685
- inputsSection.classList.add('ui-form');
686
-
687
- const downloadButton = ui.link('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value,
688
- {backgroundColor: 'white'}), 'Download pattern as PNG image', '');
689
-
690
- const editPattern = ui.link('Edit pattern', ()=>{
691
- ui.dialog('Edit pattern')
692
- .add(ui.divV([
693
- ui.h1('PTO'),
694
- ui.divH([
695
- fullyPto.root,
696
- firstPto[SS].root,
697
- firstPto[AS].root,
698
- ], {style:{gap:'12px'}})
699
- ]))
700
- .add(ui.divH([
701
- modificationSection[SS],
702
- modificationSection[AS],
703
- ], {style:{gap:'24px'}}))
704
- .onOK(()=>{grok.shell.info('Saved')})
705
- .show()
706
- }, 'Edit pattern', '');
707
-
708
- strandLengthInput[SS].addCaption('Length');
709
-
710
- return ui.splitH([
711
- ui.box(
712
- ui.div([
713
- ui.h1('Pattern'),
714
- createAsStrand.root,
715
- strandLengthInput[SS],
716
- strandLengthInput[AS],
717
- sequenceBase.root,
718
- comment.root,
719
- loadPatternDiv,
720
- saveAs.root,
721
- ui.h1('Convert'),
722
- tableInput.root,
723
- strandColumnInput[SS],
724
- strandColumnInput[AS],
725
- inputIdColumn.root,
726
- ui.buttonsInput([
727
- convertSequenceButton,
728
- ]),
729
- ], 'ui-form')
730
- , {style:{maxWidth:'450px'}}),
731
- ui.panel([
732
- svgDiv,
733
- isEnumerateModificationsDiv,
734
- ui.divH([
735
- downloadButton,
736
- editPattern
737
- ], {style:{gap:'12px', marginTop:'12px'}}),
738
- ui.divH([
739
- ui.divV([
740
- ui.h1('Sense strand'),
741
- inputExample[SS].root,
742
- outputExample[SS].root,
743
- ], 'ui-block'),
744
- ui.divV([
745
- ui.h1('Anti sense'),
746
- inputExample[AS],
747
- outputExample[AS]
748
- ], 'ui-block'),
749
- ], {style:{gap:'24px', marginTop:'24px'}}),
750
- ui.h1('Additional modifications'),
751
- ui.form([
752
- terminalModification[SS][FIVE_PRIME],
753
- terminalModification[SS][THREE_PRIME],
754
- ]),
755
- asModificationDiv,
756
- ], {style: {overflowX: 'scroll', padding:'12px 24px'}})
757
- ], {}, true)
758
- }
759
- }