@datagrok/sequence-translator 1.2.7 → 1.3.0

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 (107) hide show
  1. package/.eslintrc.json +5 -5
  2. package/CHANGELOG.md +14 -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 +22 -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 +487 -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 +94 -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 +79 -0
  38. package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +38 -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 +200 -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 +77 -0
  52. package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
  53. package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
  54. package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
  55. package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +44 -0
  56. package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +94 -0
  57. package/src/apps/pattern/view/svg-utils/svg-renderer.ts +51 -0
  58. package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
  59. package/src/apps/pattern/view/svg-utils/title-block.ts +53 -0
  60. package/src/apps/pattern/view/svg-utils/utils.ts +37 -0
  61. package/src/apps/pattern/view/types.ts +14 -0
  62. package/src/apps/pattern/view/ui.ts +61 -0
  63. package/src/{model/structure-app → apps/structure/model}/mol-transformations.ts +3 -3
  64. package/src/{model/structure-app → apps/structure/model}/monomer-code-parser.ts +9 -10
  65. package/src/{model/structure-app → apps/structure/model}/oligo-structure.ts +4 -4
  66. package/src/{model/structure-app → apps/structure/model}/sequence-to-molfile.ts +2 -2
  67. package/src/{view/apps/oligo-structure.ts → apps/structure/view/ui.ts} +31 -17
  68. package/src/{model/translator-app → apps/translator/model}/conversion-utils.ts +25 -7
  69. package/src/{model/translator-app → apps/translator/model}/format-converter.ts +7 -12
  70. package/src/{view/const/oligo-translator.ts → apps/translator/view/const.ts} +1 -1
  71. package/src/{view/apps/oligo-translator.ts → apps/translator/view/ui.ts} +88 -42
  72. package/src/demo/demo-st-ui.ts +12 -32
  73. package/src/package.ts +91 -55
  74. package/src/plugins/mermade.ts +9 -9
  75. package/src/polytool/const.ts +28 -0
  76. package/src/polytool/csv-to-json-monomer-lib-converter.ts +40 -0
  77. package/src/polytool/cyclized.ts +56 -0
  78. package/src/polytool/monomer-lib-handler.ts +115 -0
  79. package/src/polytool/pt-conversion.ts +307 -0
  80. package/src/polytool/pt-dialog.ts +115 -0
  81. package/src/polytool/pt-enumeration.ts +127 -0
  82. package/src/polytool/pt-rules.ts +73 -0
  83. package/src/polytool/utils.ts +27 -0
  84. package/src/tests/const.ts +5 -5
  85. package/src/tests/formats-support.ts +6 -6
  86. package/src/tests/formats-to-helm.ts +5 -5
  87. package/src/tests/helm-to-nucleotides.ts +5 -10
  88. package/tsconfig.json +3 -9
  89. package/webpack.config.js +3 -0
  90. package/files/axolabs-style.json +0 -97
  91. package/src/model/data-loading-utils/json-loader.ts +0 -38
  92. package/src/model/pattern-app/const.ts +0 -33
  93. package/src/model/pattern-app/draw-svg.ts +0 -193
  94. package/src/model/pattern-app/helpers.ts +0 -96
  95. package/src/model/pattern-app/oligo-pattern.ts +0 -111
  96. package/src/view/app-ui.ts +0 -193
  97. package/src/view/apps/oligo-pattern.ts +0 -759
  98. /package/src/{model → apps/common/model}/helpers.ts +0 -0
  99. /package/src/{model → apps/common/model}/monomer-lib/const.ts +0 -0
  100. /package/src/{view/utils → apps/common/view/components}/app-info-dialog.ts +0 -0
  101. /package/src/{view/utils → apps/common/view/components}/colored-input/input-painters.ts +0 -0
  102. /package/src/{view/style/colored-text-input.css → apps/common/view/components/colored-input/style.css} +0 -0
  103. /package/src/{view/utils → apps/common/view/components}/router.ts +0 -0
  104. /package/src/{model/structure-app → apps/structure/model}/const.ts +0 -0
  105. /package/src/{view/style/structure-app.css → apps/structure/view/style.css} +0 -0
  106. /package/src/{model/translator-app → apps/translator/model}/const.ts +0 -0
  107. /package/src/{view/style/translator-app.css → apps/translator/view/style.css} +0 -0
@@ -0,0 +1,487 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as DG from 'datagrok-api/dg';
3
+ import * as rxjs from 'rxjs';
4
+ import {debounceTime, throttleTime, map, skip, switchMap} from 'rxjs/operators';
5
+
6
+ import {
7
+ GRAPH_SETTINGS_KEYS as G, LEGEND_SETTINGS_KEYS as L, PATTERN_RECORD_KEYS as R, STRAND, STRANDS, TERMINI, TERMINUS
8
+ } from './const';
9
+ import {DataManager} from './data-manager';
10
+ import {
11
+ NucleotideSequences, PatternConfigRecord, PatternConfiguration,
12
+ PhosphorothioateLinkageFlags, StrandTerminusModifications, StrandType
13
+ } from './types';
14
+ import {
15
+ getMostFrequentNucleotide, getUniqueNucleotides, getUniqueNucleotidesWithNumericLabels, StrandEditingUtils
16
+ } from './utils';
17
+
18
+ import _ from 'lodash';
19
+
20
+ /** Manager of all events in the application, *the* central state manager.
21
+ * Use for communication between app's components to avoid tight coupling. */
22
+ export class EventBus {
23
+ private _patternAuthorSelection$: rxjs.BehaviorSubject<string>;
24
+
25
+ private _patternName$: rxjs.BehaviorSubject<string>;
26
+
27
+ private _isAntisenseStrandActive$: rxjs.BehaviorSubject<boolean>;
28
+
29
+ private _nucleotideSequences$: rxjs.BehaviorSubject<NucleotideSequences>;
30
+ private _phosphorothioateLinkageFlags: rxjs.BehaviorSubject<PhosphorothioateLinkageFlags>;
31
+ private _terminalModifications: rxjs.BehaviorSubject<StrandTerminusModifications>;
32
+ private _comment$: rxjs.BehaviorSubject<string>;
33
+ private _modificationsWithNumericLabels$: rxjs.BehaviorSubject<string[]>;
34
+ private _sequenceBase$: rxjs.BehaviorSubject<string>;
35
+
36
+ private _patternListUpdated$ = new rxjs.Subject<void>();
37
+
38
+ private _patternLoadRequested$ = new rxjs.Subject<string>();
39
+ private _patternLoaded$ = new rxjs.Subject<string>();
40
+ private _uniqueNucleotides$ = new rxjs.BehaviorSubject<string[]>([]);
41
+
42
+ private _patternDeletionRequested$ = new rxjs.Subject<string>();
43
+ private _tableSelection$ = new rxjs.BehaviorSubject<DG.DataFrame | null>(null);
44
+
45
+ private _svgSaveRequested$ = new rxjs.Subject<void>();
46
+ private _loadPatternInNewTabRequested$ = new rxjs.Subject<string>();
47
+ private _urlStateUpdated$ = new rxjs.Subject<string>();
48
+
49
+ private _patternHasUnsavedChanges$ = new rxjs.BehaviorSubject<boolean>(false);
50
+ private _lastLoadedPatternConfig: rxjs.BehaviorSubject<PatternConfiguration >;
51
+
52
+ private _selectedStrandColumn = new rxjs.BehaviorSubject<{[strand: string]: string | null} | null>(null);
53
+ private _selectedIdColumn = new rxjs.BehaviorSubject<string | null>(null);
54
+
55
+
56
+ constructor(
57
+ private dataManager: DataManager,
58
+ initialPaternConfigRecord: PatternConfigRecord
59
+ ) {
60
+ this.initializeAuthorSelection(initialPaternConfigRecord);
61
+ this.initializePatternState(initialPaternConfigRecord);
62
+ this._lastLoadedPatternConfig = new rxjs.BehaviorSubject(
63
+ _.cloneDeep(this.getPatternConfig())
64
+ );
65
+ this.setupSubscriptions();
66
+ }
67
+
68
+ private setupSubscriptions(): void {
69
+ this._nucleotideSequences$.subscribe(() => {
70
+ this.updateUniqueNucleotides();
71
+ this.updateSequenceBase();
72
+ });
73
+
74
+ this._isAntisenseStrandActive$.subscribe((isActive) => {
75
+ if (isActive)
76
+ return;
77
+
78
+ TERMINI.forEach((terminus) => {
79
+ this.updateTerminusModification(STRAND.ANTISENSE, terminus, '');
80
+ });
81
+ });
82
+
83
+ this.patternStateChanged$.pipe(
84
+ debounceTime(20)
85
+ ).subscribe(() => {
86
+ const lastLoadedConfig = this._lastLoadedPatternConfig.getValue();
87
+ const currentConfig = this.getPatternConfig();
88
+ const hasUnsavedChanges = !_.isEqual(currentConfig, lastLoadedConfig);
89
+ this._patternHasUnsavedChanges$.next(hasUnsavedChanges);
90
+ });
91
+ }
92
+
93
+ private updateUniqueNucleotides(): void {
94
+ const sequences = this._nucleotideSequences$.getValue();
95
+ const uniqueNucleotides = getUniqueNucleotides(sequences);
96
+ this._uniqueNucleotides$.next(uniqueNucleotides);
97
+ }
98
+
99
+ private updateSequenceBase(): void {
100
+ const nucleotideSequences = this._nucleotideSequences$.getValue();
101
+ const mostFrequentNucleotide = getMostFrequentNucleotide(nucleotideSequences);
102
+ this._sequenceBase$.next(mostFrequentNucleotide);
103
+ }
104
+
105
+ get nucleotideSequencesChanged$(): rxjs.Observable<NucleotideSequences> {
106
+ return this._nucleotideSequences$.asObservable();
107
+ }
108
+
109
+ private initializeAuthorSelection(
110
+ initialConfigRecord: PatternConfigRecord
111
+ ) {
112
+ const patternAuthorId = initialConfigRecord[R.AUTHOR_ID];
113
+ if (this.dataManager.isCurrentUserId(patternAuthorId))
114
+ this._patternAuthorSelection$ = new rxjs.BehaviorSubject(this.dataManager.getCurrentUserAuthorshipCategory());
115
+ else
116
+ this._patternAuthorSelection$ = new rxjs.BehaviorSubject(this.dataManager.getOtherUsersAuthorshipCategory());
117
+ }
118
+
119
+ private initializePatternState(
120
+ initialConfigRecord: PatternConfigRecord
121
+ ) {
122
+ const initialPattern = initialConfigRecord[R.PATTERN_CONFIG];
123
+ this._patternName$ = new rxjs.BehaviorSubject(initialPattern[L.PATTERN_NAME]);
124
+ this._isAntisenseStrandActive$ = new rxjs.BehaviorSubject(
125
+ initialPattern[G.IS_ANTISENSE_STRAND_INCLUDED]
126
+ );
127
+ this._nucleotideSequences$ = new rxjs.BehaviorSubject(
128
+ initialPattern[G.NUCLEOTIDE_SEQUENCES]
129
+ );
130
+ this._phosphorothioateLinkageFlags = new rxjs.BehaviorSubject(
131
+ initialPattern[G.PHOSPHOROTHIOATE_LINKAGE_FLAGS]
132
+ );
133
+ this._terminalModifications = new rxjs.BehaviorSubject(
134
+ initialPattern[G.STRAND_TERMINUS_MODIFICATIONS]
135
+ );
136
+ this._comment$ = new rxjs.BehaviorSubject(
137
+ initialPattern[L.PATTERN_COMMENT]
138
+ );
139
+ this._modificationsWithNumericLabels$ = new rxjs.BehaviorSubject(
140
+ initialPattern[L.NUCLEOTIDES_WITH_NUMERIC_LABELS]
141
+ );
142
+ this._sequenceBase$ = new rxjs.BehaviorSubject(
143
+ getMostFrequentNucleotide(initialPattern[G.NUCLEOTIDE_SEQUENCES])
144
+ );
145
+ }
146
+
147
+ getPatternName(): string {
148
+ return this._patternName$.getValue();
149
+ }
150
+
151
+ updatePatternName(patternName: string) {
152
+ this._patternName$.next(patternName);
153
+ }
154
+
155
+ get antisenseStrandToggled$(): rxjs.Observable<boolean> {
156
+ return this._isAntisenseStrandActive$.asObservable();
157
+ }
158
+
159
+ isAntisenseStrandActive(): boolean {
160
+ return this._isAntisenseStrandActive$.getValue();
161
+ }
162
+
163
+ toggleAntisenseStrand(isActive: boolean) {
164
+ if (!isActive)
165
+ this.updateStrandLength(STRAND.ANTISENSE, 0);
166
+ else
167
+ this.updateStrandLength(STRAND.ANTISENSE, this.getNucleotideSequences()[STRAND.SENSE].length);
168
+
169
+ this._isAntisenseStrandActive$.next(isActive);
170
+ }
171
+
172
+ getNucleotideSequences(): NucleotideSequences {
173
+ return this._nucleotideSequences$.getValue();
174
+ }
175
+
176
+ updateNucleotideSequences(nucleotideSequences: NucleotideSequences) {
177
+ this._nucleotideSequences$.next(nucleotideSequences);
178
+ }
179
+
180
+ updateStrandLength(strand: StrandType, newStrandLength: number): void {
181
+ const originalNucleotides = this.getNucleotideSequences()[strand];
182
+ if (originalNucleotides.length === newStrandLength) return;
183
+
184
+ const originalPTOFlags = this.getPhosphorothioateLinkageFlags()[strand];
185
+
186
+ if (newStrandLength === 0) {
187
+ this.setNewStrandData([], [], strand);
188
+ return;
189
+ }
190
+
191
+ if (originalNucleotides.length > newStrandLength) {
192
+ const {nucleotides, ptoFlags} = StrandEditingUtils.getTruncatedStrandData(
193
+ originalNucleotides, originalPTOFlags, newStrandLength
194
+ );
195
+ this.setNewStrandData(nucleotides, ptoFlags, strand);
196
+ return;
197
+ }
198
+
199
+ const sequenceBase = this.getSequenceBase();
200
+ const {nucleotides, ptoFlags} = StrandEditingUtils.getExtendedStrandData(
201
+ originalNucleotides, originalPTOFlags, newStrandLength, sequenceBase
202
+ );
203
+ this.setNewStrandData(nucleotides, ptoFlags, strand);
204
+ }
205
+
206
+ private setNewStrandData(
207
+ newNucleotides: string[],
208
+ newPTOFlags: boolean[],
209
+ strand: StrandType
210
+ ): void {
211
+ this.updateNucleotideSequences({
212
+ ...this.getNucleotideSequences(),
213
+ [strand]: newNucleotides
214
+ });
215
+ this.updatePhosphorothioateLinkageFlags({
216
+ ...this.getPhosphorothioateLinkageFlags(),
217
+ [strand]: newPTOFlags
218
+ });
219
+ }
220
+
221
+ getPhosphorothioateLinkageFlags(): PhosphorothioateLinkageFlags {
222
+ return this._phosphorothioateLinkageFlags.getValue();
223
+ }
224
+
225
+ updatePhosphorothioateLinkageFlags(phosphorothioateLinkageFlags: PhosphorothioateLinkageFlags) {
226
+ this._phosphorothioateLinkageFlags.next(phosphorothioateLinkageFlags);
227
+ }
228
+
229
+ get phosphorothioateLingeFlagsChanged$(): rxjs.Observable<PhosphorothioateLinkageFlags> {
230
+ return this._phosphorothioateLinkageFlags.asObservable();
231
+ }
232
+
233
+ getTerminalModifications(): StrandTerminusModifications {
234
+ return this._terminalModifications.getValue();
235
+ }
236
+
237
+ updateTerminalModifications(terminalModifications: StrandTerminusModifications) {
238
+ this._terminalModifications.next(terminalModifications);
239
+ }
240
+
241
+ updateTerminusModification(strand: StrandType, terminus: TERMINUS, modification: string) {
242
+ const terminalModifications = this.getTerminalModifications();
243
+ terminalModifications[strand][terminus] = modification;
244
+ this.updateTerminalModifications(terminalModifications);
245
+ }
246
+
247
+ terminalModificationsUpdated$(): rxjs.Observable<StrandTerminusModifications> {
248
+ return this._terminalModifications.asObservable();
249
+ }
250
+
251
+ getComment(): string {
252
+ return this._comment$.getValue();
253
+ }
254
+
255
+ updateComment(comment: string) {
256
+ this._comment$.next(comment);
257
+ }
258
+
259
+ getModificationsWithNumericLabels(): string[] {
260
+ return this._modificationsWithNumericLabels$.getValue();
261
+ }
262
+
263
+ updateModificationsWithNumericLabels(modificationsWithNumericLabels: string[]) {
264
+ const newValue = getUniqueNucleotidesWithNumericLabels(modificationsWithNumericLabels);
265
+ this._modificationsWithNumericLabels$.next(newValue);
266
+ }
267
+
268
+ get patternLoadRequested$(): rxjs.Observable<string> {
269
+ return this._patternLoadRequested$.asObservable();
270
+ }
271
+
272
+ requestPatternLoad(patternHash: string) {
273
+ this._patternLoadRequested$.next(patternHash);
274
+ }
275
+
276
+ get patternListUpdated$(): rxjs.Observable<void> {
277
+ return this._patternListUpdated$.asObservable();
278
+ }
279
+
280
+ updatePatternList() {
281
+ this._patternListUpdated$.next();
282
+ }
283
+
284
+ get tableSelectionChanged$(): rxjs.Observable<DG.DataFrame | null> {
285
+ return this._tableSelection$.asObservable();
286
+ }
287
+
288
+ selectTable(table: DG.DataFrame | null) {
289
+ this._tableSelection$.next(table);
290
+ }
291
+
292
+ getTableSelection(): DG.DataFrame | null {
293
+ return this._tableSelection$.getValue();
294
+ }
295
+
296
+ requestPatternDeletion(patternName: string) {
297
+ this._patternDeletionRequested$.next(patternName);
298
+ }
299
+
300
+ get patternDeletionRequested$(): rxjs.Observable<string> {
301
+ return this._patternDeletionRequested$.asObservable();
302
+ }
303
+
304
+ replaceSequenceBase(newNucleobase: string) {
305
+ const oldNucleotideSequences = this._nucleotideSequences$.getValue();
306
+ const newNucleotideSequences = {} as NucleotideSequences;
307
+ STRANDS.forEach((strand) => {
308
+ newNucleotideSequences[strand] = oldNucleotideSequences[strand].map(() => newNucleobase);
309
+ });
310
+ this._nucleotideSequences$.next(newNucleotideSequences);
311
+
312
+ const labelledNucleotides = this._modificationsWithNumericLabels$.getValue();
313
+ if (!labelledNucleotides.includes(newNucleobase))
314
+ this.updateModificationsWithNumericLabels(labelledNucleotides.concat(newNucleobase));
315
+ }
316
+
317
+ get patternStateChanged$(): rxjs.Observable<void> {
318
+ const observable = rxjs.merge(
319
+ this._patternName$.pipe(debounceTime(300), map(() => {})),
320
+ this._isAntisenseStrandActive$,
321
+ this._nucleotideSequences$,
322
+ this._phosphorothioateLinkageFlags,
323
+ this._terminalModifications,
324
+ this._comment$.pipe(debounceTime(300)),
325
+ this._modificationsWithNumericLabels$
326
+ ) as rxjs.Observable<void>;
327
+
328
+ return observable;
329
+ }
330
+
331
+ getSequenceBase(): string {
332
+ return this._sequenceBase$.getValue();
333
+ }
334
+
335
+ uniqueNucleotidesChanged$(): rxjs.Observable<string[]> {
336
+ // WARNING: switchMap is necessary to preserve order of events
337
+ const observable = this.patternStateChanged$.pipe(
338
+ switchMap(() => this._uniqueNucleotides$)
339
+ );
340
+
341
+ return observable;
342
+ }
343
+
344
+ getUniqueNucleotides(): string[] {
345
+ return this._uniqueNucleotides$.getValue();
346
+ }
347
+
348
+ get svgSaveRequested$(): rxjs.Observable<void> {
349
+ return this._svgSaveRequested$.asObservable();
350
+ }
351
+
352
+ requestSvgSave() {
353
+ this._svgSaveRequested$.next();
354
+ }
355
+
356
+ setAllPTOLinkages(value: boolean) {
357
+ const flags = this.getPhosphorothioateLinkageFlags();
358
+ STRANDS.forEach((strand) => {
359
+ flags[strand] = flags[strand].map(() => value);
360
+ });
361
+ this.updatePhosphorothioateLinkageFlags(flags);
362
+ }
363
+
364
+ setPatternConfig(patternConfiguration: PatternConfiguration) {
365
+ this._patternName$.next(patternConfiguration[L.PATTERN_NAME]);
366
+ this._isAntisenseStrandActive$.next(patternConfiguration[G.IS_ANTISENSE_STRAND_INCLUDED]);
367
+ this._nucleotideSequences$.next(patternConfiguration[G.NUCLEOTIDE_SEQUENCES]);
368
+ this._phosphorothioateLinkageFlags.next(patternConfiguration[G.PHOSPHOROTHIOATE_LINKAGE_FLAGS]);
369
+ this._terminalModifications.next(patternConfiguration[G.STRAND_TERMINUS_MODIFICATIONS]);
370
+ this._comment$.next(patternConfiguration[L.PATTERN_COMMENT]);
371
+ this._modificationsWithNumericLabels$.next(patternConfiguration[L.NUCLEOTIDES_WITH_NUMERIC_LABELS]);
372
+ }
373
+
374
+ setLastLoadedPatternConfig(patternConfiguration: PatternConfiguration) {
375
+ this._lastLoadedPatternConfig.next(
376
+ _.cloneDeep(patternConfiguration)
377
+ );
378
+ }
379
+
380
+ getPatternConfig(): PatternConfiguration {
381
+ return {
382
+ [L.PATTERN_NAME]: this.getPatternName(),
383
+ [G.IS_ANTISENSE_STRAND_INCLUDED]: this.isAntisenseStrandActive(),
384
+ [G.NUCLEOTIDE_SEQUENCES]: this.getNucleotideSequences(),
385
+ [G.PHOSPHOROTHIOATE_LINKAGE_FLAGS]: this.getPhosphorothioateLinkageFlags(),
386
+ [G.STRAND_TERMINUS_MODIFICATIONS]: this.getTerminalModifications(),
387
+ [L.PATTERN_COMMENT]: this.getComment(),
388
+ [L.NUCLEOTIDES_WITH_NUMERIC_LABELS]: this.getModificationsWithNumericLabels(),
389
+ };
390
+ }
391
+
392
+ setPhosphorothioateLinkageFlag(strand: StrandType, index: number, newValue: boolean) {
393
+ const flags = this.getPhosphorothioateLinkageFlags();
394
+ flags[strand][index] = newValue;
395
+ this.updatePhosphorothioateLinkageFlags(flags);
396
+ }
397
+
398
+ setNucleotide(strand: StrandType, index: number, value: string) {
399
+ const sequences = this.getNucleotideSequences();
400
+ sequences[strand][index] = value;
401
+ const labelledModifications = this.getModificationsWithNumericLabels();
402
+ this.updateModificationsWithNumericLabels(labelledModifications.concat(value));
403
+ this.updateNucleotideSequences(sequences);
404
+ }
405
+
406
+ get strandsUpdated$(): rxjs.Observable<void> {
407
+ return rxjs.merge(
408
+ this._isAntisenseStrandActive$.asObservable().pipe(map(() => {})),
409
+ this._nucleotideSequences$.asObservable().pipe(map(() => {})),
410
+ this._patternLoaded$.asObservable().pipe(map(() => {}))
411
+ ).pipe(
412
+ debounceTime(10)
413
+ ) as rxjs.Observable<void>;
414
+ }
415
+
416
+ get strandsLinkagesAndTerminalsUpdated$(): rxjs.Observable<void> {
417
+ return rxjs.merge(
418
+ this.strandsUpdated$,
419
+ this._phosphorothioateLinkageFlags.asObservable().pipe(map(() => {})),
420
+ this._terminalModifications.asObservable().pipe(map(() => {}))
421
+ );
422
+ }
423
+
424
+ updateControlsUponPatternLoaded(patternHash: string) {
425
+ this._patternLoaded$.next(patternHash);
426
+ }
427
+
428
+ get patternLoaded$(): rxjs.Observable<string> {
429
+ return this._patternLoaded$.asObservable();
430
+ }
431
+
432
+ get userSelection$(): rxjs.Observable<string> {
433
+ return this._patternAuthorSelection$.asObservable().pipe(skip(1));
434
+ }
435
+
436
+ selectAuthor(username: string) {
437
+ this._patternAuthorSelection$.next(username);
438
+ }
439
+
440
+ getSelectedAuthor(): string {
441
+ return this._patternAuthorSelection$.getValue();
442
+ }
443
+
444
+ get loadPatternInNewTabRequested$(): rxjs.Observable<string> {
445
+ return this._loadPatternInNewTabRequested$.asObservable();
446
+ }
447
+
448
+ requestLoadPatternInNewTab(patternHash: string) {
449
+ this._loadPatternInNewTabRequested$.next(patternHash);
450
+ }
451
+
452
+ updateUrlState(patternHash: string) {
453
+ this._urlStateUpdated$.next(patternHash);
454
+ }
455
+
456
+ get urlStateUpdated$(): rxjs.Observable<string> {
457
+ return this._urlStateUpdated$.asObservable();
458
+ }
459
+
460
+ get patternHasUnsavedChanges$(): rxjs.Observable<boolean> {
461
+ return this._patternHasUnsavedChanges$.asObservable();
462
+ }
463
+
464
+ selectStrandColumn(strand: StrandType, colName: string | null) {
465
+ this._selectedStrandColumn.next({...this._selectedStrandColumn.getValue(), [strand]: colName});
466
+ }
467
+
468
+ getSelectedStrandColumn(strand: StrandType): string | null {
469
+ const value = this._selectedStrandColumn.getValue();
470
+ return value ? value[strand] : null;
471
+ }
472
+
473
+ selectIdColumn(colName: string) {
474
+ this._selectedIdColumn.next(colName);
475
+ }
476
+
477
+ getSelectedIdColumn(): string | null {
478
+ return this._selectedIdColumn.getValue();
479
+ }
480
+
481
+ get updateSvgContainer$(): rxjs.Observable<void> {
482
+ return this.patternStateChanged$.pipe(
483
+ debounceTime(100)
484
+ );
485
+ }
486
+ }
487
+
@@ -0,0 +1,46 @@
1
+ import {EventBus} from '../model/event-bus';
2
+
3
+ const PATTERN_KEY = 'pattern';
4
+
5
+ export class URLRouter {
6
+ private urlSearchParams: URLSearchParams;
7
+ constructor() {
8
+ this.urlSearchParams = new URLSearchParams(window.location.search);
9
+ }
10
+
11
+ subscribeToObservables(eventBus: EventBus): void {
12
+ eventBus.urlStateUpdated$.subscribe((hash) => this.setPatternURL(hash));
13
+ eventBus.loadPatternInNewTabRequested$.subscribe((hash) => {
14
+ const url = `${window.location.origin}${window.location.pathname}?${PATTERN_KEY}=${hash}`;
15
+ window.open(url, '_blank');
16
+ });
17
+
18
+ window.addEventListener('popstate', () => {
19
+ this.urlSearchParams = new URLSearchParams(window.location.search);
20
+ const patternHash = this.getPatternHash();
21
+ if (patternHash === null)
22
+ return;
23
+
24
+ eventBus.requestPatternLoad(patternHash);
25
+ });
26
+ }
27
+
28
+ getPatternHash(): string | null {
29
+ return this.urlSearchParams.get(PATTERN_KEY);
30
+ }
31
+
32
+ private setPatternURL(patternHash: string): void {
33
+ if (patternHash === null || patternHash === '') {
34
+ this.clearPatternURL();
35
+ return;
36
+ }
37
+
38
+ this.urlSearchParams.set(PATTERN_KEY, patternHash);
39
+ window.history.pushState({}, '', `${window.location.pathname}?${this.urlSearchParams}`);
40
+ }
41
+
42
+ clearPatternURL(): void {
43
+ this.urlSearchParams.delete(PATTERN_KEY);
44
+ window.history.pushState({}, '', `${window.location.pathname}`);
45
+ }
46
+ }
@@ -0,0 +1,21 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+ import {Subscription} from 'rxjs';
3
+
4
+ export class SubscriptionManager {
5
+ private rxjsSubscriptions = [] as Subscription[];
6
+ private dgSubscriptions = [] as DG.StreamSubscription[];
7
+
8
+ add(subscription: Subscription | DG.StreamSubscription) {
9
+ if (subscription instanceof Subscription)
10
+ this.rxjsSubscriptions.push(subscription);
11
+ else
12
+ this.dgSubscriptions.push(subscription);
13
+ }
14
+
15
+ unsubscribeAll() {
16
+ for (const subs of [this.rxjsSubscriptions, this.dgSubscriptions]) {
17
+ subs.forEach((sub) => sub.unsubscribe());
18
+ subs.length = 0;
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,94 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import {STRAND, STRANDS, TERMINI, TERMINUS} from './const';
3
+ import {PATTERN_APP_DATA} from '../../common/model/data-loader/json-loader';
4
+ import {EventBus} from './event-bus';
5
+
6
+ export function bulkTranslate(eventBus: EventBus): void {
7
+ const df = eventBus.getTableSelection();
8
+ if (!df) {
9
+ grok.shell.warning('Please select a table');
10
+ return;
11
+ }
12
+ const strandColNames = STRANDS.filter(
13
+ (strand) => !(strand === STRAND.ANTISENSE && !eventBus.isAntisenseStrandActive())
14
+ ).map((strand) => eventBus.getSelectedStrandColumn(strand))
15
+ .filter((colName) => colName) as string[];
16
+
17
+ if (strandColNames.length === 0) {
18
+ grok.shell.warning('Please column for sense strand');
19
+ return;
20
+ }
21
+
22
+ const idColumnName = eventBus.getSelectedIdColumn();
23
+ if (!idColumnName) throw new Error('No ID column selected');
24
+
25
+ const idColumn = df.getCol(idColumnName);
26
+
27
+ const strandCols = strandColNames.map((colName) => df.getCol(colName));
28
+ }
29
+
30
+ export function applyPatternToRawSequence(
31
+ rawNucleotideSequence: string,
32
+ modifications: string[],
33
+ ptoFlags: boolean[],
34
+ terminalModifications: Record<TERMINUS, string>
35
+ ): string {
36
+ const rawNucleotides = rawNucleotideSequence.split('');
37
+
38
+ const modifiedNucleotides = rawNucleotides.map((nucleotide, i) => {
39
+ const modifiedNucleotide = getModifiedNucleotide(
40
+ nucleotide,
41
+ modifications[i]
42
+
43
+ );
44
+ return modifiedNucleotide;
45
+ });
46
+
47
+ const modificationsWithPTOLinkages = getModificationsWithPTOLinkages(
48
+ modifiedNucleotides, ptoFlags, terminalModifications
49
+ );
50
+
51
+ return modificationsWithPTOLinkages.join('');
52
+ }
53
+
54
+ function getModifiedNucleotide(nucleotide: string, modification: string): string {
55
+ const format = Object.keys(PATTERN_APP_DATA)[0];
56
+ const substitution = PATTERN_APP_DATA[format][modification].substitution;
57
+ return nucleotide.replace(/([AGCTU])/, substitution);
58
+ }
59
+
60
+ function getPhosphorothioateLinkageSymbol(): string {
61
+ return 'ps';
62
+ }
63
+
64
+ function getModificationsWithPTOLinkages(
65
+ modifiedNucleotides: string[],
66
+ ptoFlags: boolean[],
67
+ terminalModifications: Record<TERMINUS, string>
68
+ ): string[] {
69
+ const modificationsWithPTOLinkages = new Array<string>(
70
+ modifiedNucleotides.length + ptoFlags.filter((flag) => flag).length + TERMINI.length
71
+ );
72
+
73
+ const ptoLinkage = getPhosphorothioateLinkageSymbol();
74
+
75
+ modificationsWithPTOLinkages[0] = terminalModifications[TERMINUS.FIVE_PRIME];
76
+ modificationsWithPTOLinkages[modificationsWithPTOLinkages.length - 1] = terminalModifications[TERMINUS.THREE_PRIME];
77
+
78
+ let idxShift = 1;
79
+
80
+ if (ptoFlags[0]) {
81
+ modificationsWithPTOLinkages[idxShift] = ptoLinkage;
82
+ idxShift++;
83
+ }
84
+
85
+ modifiedNucleotides.forEach((nucleotide, i) => {
86
+ modificationsWithPTOLinkages[i + idxShift] = nucleotide;
87
+ if (ptoFlags[i + 1]) {
88
+ modificationsWithPTOLinkages[i + idxShift + 1] = ptoLinkage;
89
+ idxShift++;
90
+ }
91
+ });
92
+
93
+ return modificationsWithPTOLinkages;
94
+ }
@@ -0,0 +1,52 @@
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 {
7
+ TERMINI, STRANDS,
8
+ GRAPH_SETTINGS_KEYS as G, LEGEND_SETTINGS_KEYS as L, PATTERN_RECORD_KEYS as R
9
+ } from './const';
10
+
11
+ export type StrandType = typeof STRANDS[number];
12
+ export type TerminalType = typeof TERMINI[number];
13
+
14
+ export type NucleotideSequences = Record<StrandType, string[]>;
15
+ export type PhosphorothioateLinkageFlags = Record<StrandType, boolean[]>;
16
+ export type StrandTerminusModifications = Record<StrandType, Record<TerminalType, string>>;
17
+
18
+ export type PatternGraphSettings = {
19
+ [G.IS_ANTISENSE_STRAND_INCLUDED]: boolean,
20
+ [G.NUCLEOTIDE_SEQUENCES]: NucleotideSequences,
21
+ [G.PHOSPHOROTHIOATE_LINKAGE_FLAGS]: PhosphorothioateLinkageFlags,
22
+ [G.STRAND_TERMINUS_MODIFICATIONS]: StrandTerminusModifications,
23
+ }
24
+
25
+ export type PatternLegendSettings = {
26
+ [L.PATTERN_NAME]: string,
27
+ [L.PATTERN_COMMENT]: string,
28
+ [L.NUCLEOTIDES_WITH_NUMERIC_LABELS]: string[],
29
+ }
30
+
31
+ export type PatternConfiguration = PatternGraphSettings & PatternLegendSettings;
32
+
33
+ export type PatternConfigRecord = {
34
+ [R.PATTERN_CONFIG]: PatternConfiguration,
35
+ [R.AUTHOR_ID]: string,
36
+ }
37
+
38
+ export class PatternNameExistsError extends Error {
39
+ constructor(message: string) {
40
+ super(message);
41
+ this.name = 'PatternNameExistsError';
42
+ }
43
+ }
44
+
45
+ export class PatternExistsError extends Error {
46
+ constructor(message: string) {
47
+ super(message);
48
+ this.name = 'PatternExistsError';
49
+ }
50
+ }
51
+
52
+ export type RawPatternRecords = {[patternName: string]: string};