@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
@@ -0,0 +1,198 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+
4
+ import {SubscriptionManager} from '../../model/subscription-manager';
5
+
6
+ import '../style.css';
7
+
8
+ import {StringInput} from '../types';
9
+
10
+ import $ from 'cash-dom';
11
+ import {DataManager} from '../../model/data-manager';
12
+ import {EventBus} from '../../model/event-bus';
13
+
14
+ export class PatternLoadControlsManager {
15
+ private subscriptions = new SubscriptionManager();
16
+ private authorSelectedByUser = false;
17
+
18
+ constructor(
19
+ private eventBus: EventBus,
20
+ private dataManager: DataManager
21
+ ) {
22
+ this.eventBus.patternLoadRequested$.subscribe((patternHash: string) => this.handlePatternChoice(patternHash));
23
+
24
+ this.eventBus.patternDeletionRequested$.subscribe(async (patternName: string) => {
25
+ await this.dataManager.deletePattern(patternName, this.eventBus);
26
+ });
27
+ }
28
+
29
+ private async handlePatternChoice(patternHash: string): Promise<void> {
30
+ let patternConfiguration = await this.dataManager.getPatternConfig(patternHash);
31
+ if (patternConfiguration === null)
32
+ patternConfiguration = this.dataManager.getDefaultPatternConfig();
33
+
34
+ this.eventBus.setPatternConfig(patternConfiguration);
35
+ this.eventBus.updateControlsUponPatternLoaded(patternHash);
36
+ this.eventBus.setLastLoadedPatternConfig(patternConfiguration);
37
+ }
38
+
39
+ private isCurrentUserSelected(): boolean {
40
+ return this.eventBus.getSelectedAuthor() !== this.dataManager.getOtherUsersAuthorshipCategory();
41
+ }
42
+
43
+ createControls(): HTMLElement[] {
44
+ const inputsContainer = this.getPatternInputsContainer();
45
+ return [
46
+ ui.h1('Load'),
47
+ inputsContainer,
48
+ ];
49
+ }
50
+
51
+ private getPatternInputsContainer(): HTMLDivElement {
52
+ const inputsContainer = ui.divH(this.createPatternInputs());
53
+
54
+ this.eventBus.patternListUpdated$.subscribe(() => {
55
+ this.subscriptions.unsubscribeAll();
56
+
57
+ // todo: change values of selectedPattern and selectedUser here
58
+
59
+ $(inputsContainer).empty();
60
+ $(inputsContainer).append(this.createPatternInputs());
61
+ });
62
+
63
+ return inputsContainer;
64
+ }
65
+
66
+ private createPatternInputs(): HTMLElement[] {
67
+ const authorChoiceInput = this.createAuthorChoiceInput();
68
+ const patternChoiceInputContainer = this.createPatternChoiceInputContainer();
69
+ const deletePatternButton = this.createDeletePatternButton();
70
+
71
+ return [
72
+ authorChoiceInput.root,
73
+ patternChoiceInputContainer,
74
+ deletePatternButton
75
+ ];
76
+ }
77
+
78
+ private createPatternChoiceInputContainer(): HTMLDivElement {
79
+ const patternChoiceInput = this.createPatternChoiceInput();
80
+ const patternChoiceInputContainer = ui.div([patternChoiceInput.root]);
81
+
82
+ const subscription = this.eventBus.userSelection$.subscribe(() => {
83
+ $(patternChoiceInputContainer).empty();
84
+ $(patternChoiceInputContainer).append(this.createPatternChoiceInput().root);
85
+ });
86
+ this.subscriptions.add(subscription);
87
+
88
+ return patternChoiceInputContainer;
89
+ }
90
+
91
+ private createAuthorChoiceInput(): StringInput {
92
+ const possibleValues = [this.dataManager.getCurrentUserAuthorshipCategory()];
93
+ if (this.dataManager.getOtherUsersPatternNames().length > 0)
94
+ possibleValues.push(this.dataManager.getOtherUsersAuthorshipCategory());
95
+
96
+ const authorChoiceInput = ui.choiceInput(
97
+ 'Author',
98
+ this.eventBus.getSelectedAuthor(),
99
+ possibleValues,
100
+ (userName: string) => {
101
+ this.authorSelectedByUser = true;
102
+ this.eventBus.selectAuthor(userName);
103
+ }
104
+ );
105
+ this.setAuthorChoiceInputStyle(authorChoiceInput);
106
+ authorChoiceInput.setTooltip('Select pattern author');
107
+
108
+ return authorChoiceInput;
109
+ }
110
+
111
+ private setAuthorChoiceInputStyle(userChoiceInput: StringInput): void {
112
+ $(userChoiceInput.input).css({
113
+ 'max-width': '100px',
114
+ 'min-width': '100px',
115
+ });
116
+ $(userChoiceInput.root).css({
117
+ 'padding-right': '30px',
118
+ 'padding-left': '30px',
119
+ });
120
+ }
121
+
122
+ private createPatternChoiceInput(): StringInput {
123
+ const patternList = this.isCurrentUserSelected() ?
124
+ this.dataManager.getCurrentUserPatternNames() :
125
+ this.dataManager.getOtherUsersPatternNames();
126
+
127
+ if (this.authorSelectedByUser) {
128
+ const patternHash = this.dataManager.getPatternHash(patternList[0], this.isCurrentUserSelected());
129
+ this.eventBus.requestPatternLoad(patternHash);
130
+ this.eventBus.updateUrlState(patternHash);
131
+ this.authorSelectedByUser = false;
132
+ }
133
+
134
+ const defaultValue = this.getPatternName(patternList);
135
+ const choiceInput = ui.choiceInput('Pattern', defaultValue, patternList);
136
+ choiceInput.setTooltip('Select pattern to load');
137
+
138
+ $(choiceInput.input).css({
139
+ 'max-width': '100px',
140
+ 'min-width': '100px',
141
+ });
142
+
143
+ this.subscriptions.add(
144
+ choiceInput.onInput(
145
+ () => {
146
+ const patternHash = this.dataManager.getPatternHash(choiceInput.value!, this.isCurrentUserSelected());
147
+ this.eventBus.requestPatternLoad(patternHash);
148
+ this.eventBus.updateUrlState(patternHash);
149
+ }
150
+ )
151
+ );
152
+
153
+ this.subscriptions.add(
154
+ this.eventBus.patternLoaded$.subscribe(() => {
155
+ const patternName = this.eventBus.getPatternName();
156
+ if (!choiceInput.value?.includes(patternName))
157
+ choiceInput.value = this.getPatternName(patternList);
158
+ })
159
+ );
160
+
161
+ return choiceInput;
162
+ }
163
+
164
+ private getPatternName(patternList: string[]): string {
165
+ return patternList.find((patternName) => patternName.includes(this.eventBus.getPatternName())) ?? patternList[0];
166
+ }
167
+
168
+ private createDeletePatternButton(): HTMLButtonElement {
169
+ const button = ui.button(
170
+ ui.iconFA('trash-alt'),
171
+ () => {
172
+ if (this.eventBus.getPatternName() === this.dataManager.getDefaultPatternName()) {
173
+ grok.shell.warning('Cannot delete example pattern');
174
+ return;
175
+ }
176
+ this.showDeletePatternDialog();
177
+ }
178
+ );
179
+
180
+ ui.tooltip.bind(button, 'Delete pattern from user storage');
181
+
182
+ const subscription = this.eventBus.userSelection$.subscribe(() => {
183
+ // $(button).toggle(this.isCurrentUserSelected());
184
+ button.disabled = !this.isCurrentUserSelected();
185
+ });
186
+ this.subscriptions.add(subscription);
187
+
188
+ return button;
189
+ }
190
+
191
+ private showDeletePatternDialog(): void {
192
+ const dialog = ui.dialog('Delete pattern');
193
+ const patternName = this.eventBus.getPatternName();
194
+ dialog.add(ui.divText(`Are you sure you want to delete pattern ${patternName}?`));
195
+ dialog.onOK(() => this.eventBus.requestPatternDeletion(patternName));
196
+ dialog.show();
197
+ }
198
+ }
@@ -0,0 +1,69 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+
3
+ import {EventBus} from '../../model/event-bus';
4
+ import {isOverhangNucleotide} from '../../model/utils';
5
+ import {BooleanInput} from '../types';
6
+
7
+ import $ from 'cash-dom';
8
+
9
+ /** Toggles for numeric labels over nucleotides in SVG */
10
+ export class NumericLabelVisibilityControls {
11
+ private togglesContainer: HTMLDivElement = ui.div([]);
12
+
13
+ constructor(
14
+ private eventBus: EventBus
15
+ ) {
16
+ this.eventBus.uniqueNucleotidesChanged$().subscribe(() => {
17
+ this.updateContainer();
18
+ });
19
+ }
20
+
21
+ getContainer(): HTMLDivElement {
22
+ return this.togglesContainer;
23
+ }
24
+
25
+ private updateContainer(): void {
26
+ $(this.togglesContainer).empty();
27
+ $(this.togglesContainer).append(this.createInputs());
28
+ }
29
+
30
+ private createInputs(): HTMLDivElement {
31
+ const uniqueNucleotideBases = this.eventBus.getUniqueNucleotides();
32
+ const nucleotidesWithoutOverhangs = uniqueNucleotideBases.filter((n) => !isOverhangNucleotide(n));
33
+
34
+ const inputBases = nucleotidesWithoutOverhangs.map(
35
+ (nucleotide: string) => this.createSingleInput(nucleotide)
36
+ );
37
+
38
+ inputBases.sort(
39
+ (inputA, inputB) => inputA.captionLabel.textContent!.localeCompare(inputB.captionLabel.textContent!)
40
+ );
41
+
42
+ return ui.divH(inputBases.map((input) => input.root));
43
+ }
44
+
45
+ private createSingleInput(nucleotide: string): BooleanInput {
46
+ const initialValue = this.eventBus.getModificationsWithNumericLabels().includes(nucleotide);
47
+ const input = ui.boolInput(
48
+ nucleotide,
49
+ initialValue,
50
+ (value: boolean) => this.handleNumericLabelToggle(nucleotide, value)
51
+ );
52
+ $(input.root).css('padding-right', '20px');
53
+
54
+ input.setTooltip(`Show numeric labels for ${nucleotide}`);
55
+
56
+ return input;
57
+ }
58
+
59
+ private handleNumericLabelToggle(nucleotide: string, isVisible: boolean): void {
60
+ const labelledNucleotides = this.eventBus.getModificationsWithNumericLabels();
61
+ const hasNumericLabel = labelledNucleotides.includes(nucleotide);
62
+ if (hasNumericLabel === isVisible)
63
+ return;
64
+
65
+ const newArray = isVisible ? labelledNucleotides.concat(nucleotide) :
66
+ labelledNucleotides.filter((n) => n !== nucleotide);
67
+ this.eventBus.updateModificationsWithNumericLabels(newArray);
68
+ }
69
+ }
@@ -0,0 +1,148 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+
4
+ import {EventBus} from '../../model/event-bus';
5
+ import {DataManager} from '../../model/data-manager';
6
+ import {PatternExistsError, PatternNameExistsError} from '../../model/types';
7
+ import {SvgDisplayManager} from '../svg-utils/svg-display-manager';
8
+ import {NumericLabelVisibilityControls} from './numeric-label-visibility-controls';
9
+ import {TranslationExamplesBlock} from './translation-examples-block';
10
+
11
+ export class PatternAppRightSection {
12
+ private svgDisplay: HTMLDivElement;
13
+
14
+ constructor(
15
+ private eventBus: EventBus,
16
+ private dataManager: DataManager
17
+ ) {
18
+ this.svgDisplay = SvgDisplayManager.createSvgDiv(eventBus);
19
+ };
20
+
21
+ getLayout(): HTMLDivElement {
22
+ const numericLabelTogglesContainer = new NumericLabelVisibilityControls(this.eventBus).getContainer();
23
+ const downloadAndEditControls = this.generateDownloadControls();
24
+ const translationExamplesContainer = new TranslationExamplesBlock(
25
+ this.eventBus, this.dataManager
26
+ ).createContainer();
27
+ const layout = ui.panel([
28
+ this.svgDisplay,
29
+ numericLabelTogglesContainer,
30
+ downloadAndEditControls,
31
+ translationExamplesContainer,
32
+ // ui.h1('Additional modifications'),
33
+ // ui.form([
34
+ // terminalModificationInputs[STRAND.SENSE][FIVE_PRIME_END],
35
+ // terminalModificationInputs[STRAND.SENSE][THREE_PRIME_END],
36
+ // ]),
37
+ // asModificationDiv,
38
+ ], {style: {overflowX: 'scroll', padding: '12px 24px'}});
39
+ return layout;
40
+ }
41
+
42
+ private generateDownloadControls(): HTMLDivElement {
43
+ return ui.divH([
44
+ this.createSavePatternButton(),
45
+ this.createDownloadPngButton(),
46
+ this.createShareLinkButton(),
47
+ ], {style: {gap: '12px', marginTop: '12px'}});
48
+ }
49
+
50
+ private createDownloadPngButton(): HTMLButtonElement {
51
+ const svgDownloadButton = ui.button('Get PNG', () => this.eventBus.requestSvgSave());
52
+
53
+ ui.tooltip.bind(svgDownloadButton, 'Download pattern as PNG');
54
+
55
+ return svgDownloadButton;
56
+ }
57
+
58
+ private createShareLinkButton(): HTMLButtonElement {
59
+ const shareLinkButton = ui.button(
60
+ ui.iconFA('link'),
61
+ () => navigator.clipboard.writeText(window.location.href)
62
+ .then(() => grok.shell.info('Link to pattern copied to clipboard'))
63
+ );
64
+
65
+ this.eventBus.patternHasUnsavedChanges$.subscribe((hasUnsavedChanges: boolean) => {
66
+ shareLinkButton.disabled = hasUnsavedChanges;
67
+ });
68
+
69
+ ui.tooltip.bind(shareLinkButton, 'Share pattern link');
70
+ return shareLinkButton;
71
+ }
72
+
73
+ private createSavePatternButton(): HTMLButtonElement {
74
+ const savePatternButton = ui.button('Save', () => this.processSaveButtonClick());
75
+
76
+ this.eventBus.patternHasUnsavedChanges$.subscribe((hasUnsavedChanges: boolean) => {
77
+ savePatternButton.disabled = !hasUnsavedChanges;
78
+ });
79
+
80
+ ui.tooltip.bind(savePatternButton, 'Save pattern to user storage');
81
+
82
+ return savePatternButton;
83
+ }
84
+
85
+ private processSaveButtonClick(): void {
86
+ const patternName = this.eventBus.getPatternName();
87
+ if (patternName === this.dataManager.getDefaultPatternName()) {
88
+ grok.shell.warning(`Cannot save default pattern`);
89
+ return;
90
+ }
91
+ if (patternName === '') {
92
+ grok.shell.warning(`Insert pattern name`);
93
+ return;
94
+ }
95
+ this.dataManager.savePatternToUserStorage(this.eventBus)
96
+ .then(() => {
97
+ grok.shell.info(`Pattern ${patternName} saved`);
98
+ })
99
+ .catch((e) => this.handleErrorWhileSavingPattern(e));
100
+ }
101
+
102
+ private handleErrorWhileSavingPattern(e: Error): void {
103
+ if (e instanceof PatternNameExistsError) {
104
+ new OverwritePatternDialog(this.eventBus, this.dataManager).show();
105
+ } else if (e instanceof PatternExistsError) {
106
+ grok.shell.warning(ui.div([
107
+ ui.divText(`Pattern already exists`),
108
+ ui.button('Load', () => {
109
+ const hash = e.message;
110
+ this.eventBus.requestLoadPatternInNewTab(hash);
111
+ }),
112
+ ]));
113
+ } else {
114
+ console.error('Error while saving pattern', e);
115
+ }
116
+ }
117
+ }
118
+
119
+ class OverwritePatternDialog {
120
+ constructor(
121
+ private eventBus: EventBus,
122
+ private dataManager: DataManager
123
+ ) { }
124
+
125
+ show(): void {
126
+ const patternName = this.eventBus.getPatternName();
127
+ const dialog = ui.dialog(`Pattern "${patternName}" already exists`);
128
+ dialog.add(ui.divText(
129
+ `Pattern "${patternName}" already exists. Do you want to overwrite it?`
130
+ ));
131
+ dialog.show();
132
+
133
+ dialog.onOK(() => this.processOverwriteNamesakePattern());
134
+ }
135
+
136
+ private processOverwriteNamesakePattern(): void {
137
+ const patternName = this.eventBus.getPatternName();
138
+ this.dataManager.overwritePatternInUserStorage(this.eventBus)
139
+ .then(() => {
140
+ grok.shell.info(`Pattern ${patternName} overwritten`);
141
+ })
142
+ .catch((e) => {
143
+ console.error('Error while overwriting pattern in user storage', e);
144
+ grok.shell.error('Error while overwriting pattern');
145
+ });
146
+ }
147
+ }
148
+
@@ -0,0 +1,79 @@
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 ui from 'datagrok-api/ui';
4
+
5
+ import $ from 'cash-dom';
6
+ import _ from 'lodash';
7
+
8
+ import {EventBus} from '../../../model/event-bus';
9
+ import {PatternConfiguration} from '../../../model/types';
10
+ import {HeaderControls} from './header-controls';
11
+ import {StrandControls} from './strand-controls';
12
+ import {SubscriptionManager} from '../../../model/subscription-manager';
13
+ import {DataManager} from '../../../model/data-manager';
14
+
15
+ export class StrandEditorDialog {
16
+ private static isDialogOpen = false;
17
+ private static instance: StrandEditorDialog;
18
+
19
+ private initialPatternConfig: PatternConfiguration;
20
+ private subscriptions = new SubscriptionManager();
21
+
22
+ private constructor(
23
+ private eventBus: EventBus,
24
+ private dataManager: DataManager
25
+ ) { }
26
+
27
+ static open(eventBus: EventBus, dataManager: DataManager): void {
28
+ if (StrandEditorDialog.isDialogOpen)
29
+ return;
30
+
31
+ if (!StrandEditorDialog.instance)
32
+ StrandEditorDialog.instance = new StrandEditorDialog(eventBus, dataManager);
33
+
34
+ StrandEditorDialog.instance.openDialog();
35
+ }
36
+
37
+ private openDialog(): void {
38
+ this.initialPatternConfig = _.cloneDeep(this.eventBus.getPatternConfig());
39
+ StrandEditorDialog.isDialogOpen = true;
40
+ this.createDialog().show();
41
+ }
42
+
43
+ private createDialog(): DG.Dialog {
44
+ const editorBody = ui.divV([]);
45
+ this.subscriptions.add(
46
+ this.eventBus.strandsUpdated$.subscribe(() => this.onStrandsUpdated(editorBody))
47
+ );
48
+
49
+ const dialog = ui.dialog('Edit strands')
50
+ .add(editorBody)
51
+ .onOK(() => {})
52
+ .onCancel(() => this.resetToInitialState());
53
+
54
+ this.subscriptions.add(
55
+ dialog.onClose.subscribe(() => {
56
+ StrandEditorDialog.isDialogOpen = false;
57
+ this.subscriptions.unsubscribeAll();
58
+ })
59
+ );
60
+
61
+ return dialog;
62
+ }
63
+
64
+ private onStrandsUpdated(editorBody: HTMLDivElement) {
65
+ const headerControls = new HeaderControls(
66
+ this.eventBus, this.initialPatternConfig, this.subscriptions
67
+ ).create();
68
+ const strandControls = new StrandControls(this.eventBus, this.dataManager, this.subscriptions).create();
69
+
70
+ $(editorBody).empty();
71
+ $(editorBody).append(headerControls, strandControls);
72
+ }
73
+
74
+ private resetToInitialState(): void {
75
+ // this.eventBus.setLastLoadedPatternConfig(this.initialPatternConfig);
76
+ this.eventBus.setPatternConfig(this.initialPatternConfig);
77
+ }
78
+ }
79
+
@@ -0,0 +1,105 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+
3
+ import {STRAND, STRANDS} from '../../../model/const';
4
+ import {EventBus} from '../../../model/event-bus';
5
+ import {PatternConfiguration, PhosphorothioateLinkageFlags} from '../../../model/types';
6
+ import {BooleanInput} from '../../types';
7
+ import {SubscriptionManager} from '../../../model/subscription-manager';
8
+
9
+ export class HeaderControls {
10
+ constructor(
11
+ private eventBus: EventBus,
12
+ private initialPatternConfig: PatternConfiguration,
13
+ private subscriptions: SubscriptionManager
14
+ ) { }
15
+
16
+ create(): HTMLDivElement {
17
+ const container = ui.divV([
18
+ ui.h1('PTO'),
19
+ ui.divH([
20
+ this.createAllPtoActivationInput().root,
21
+ ...this.createFirstPtoInputs().map((input) => input.root),
22
+ ], {style: {gap: '12px'}})
23
+ ]);
24
+
25
+ return container;
26
+ }
27
+
28
+ private areAllPtoLinkagesSet(flags: PhosphorothioateLinkageFlags): boolean {
29
+ const totalNumberOfPTOFlags = STRANDS.map(
30
+ (strand) => flags[strand].filter((flag) => flag).length
31
+ )
32
+ .reduce((a, b) => a + b, 0);
33
+
34
+ const totalNumberOfNucleotides = STRANDS.map(
35
+ (strand) => this.initialPatternConfig.nucleotideSequences[strand].length
36
+ ).reduce((a, b) => a + b, 0);
37
+
38
+ // There are +1 more PTO flags in each strand than there are nucleotides
39
+ const addendum = STRANDS.filter((strand) => flags[strand].length).length;
40
+ return totalNumberOfPTOFlags === totalNumberOfNucleotides + addendum;
41
+ }
42
+
43
+ private createAllPtoActivationInput(): BooleanInput {
44
+ const flags = this.initialPatternConfig.phosphorothioateLinkageFlags;
45
+ const initialValue = this.areAllPtoLinkagesSet(flags);
46
+ const allPtoActivationInput = ui.boolInput('All PTO', initialValue);
47
+
48
+ allPtoActivationInput.onInput(() => {
49
+ const value = allPtoActivationInput.value!;
50
+ this.eventBus.setAllPTOLinkages(value);
51
+ });
52
+
53
+ const subscription = this.eventBus.phosphorothioateLingeFlagsChanged$.subscribe(() => {
54
+ const flags = this.eventBus.getPatternConfig().phosphorothioateLinkageFlags;
55
+ const newValue = this.areAllPtoLinkagesSet(flags);
56
+ allPtoActivationInput.value = newValue;
57
+ });
58
+ this.subscriptions.add(subscription);
59
+
60
+ this.addStyleToPtoInput(allPtoActivationInput);
61
+ ui.tooltip.bind(allPtoActivationInput.captionLabel, 'Activate all phosphothioates');
62
+
63
+ return allPtoActivationInput;
64
+ }
65
+
66
+ private addStyleToPtoInput(allPtoActivationInput: BooleanInput): void {
67
+ const label = allPtoActivationInput.captionLabel;
68
+ label.classList.add('ui-label-right');
69
+ Object.assign(label.style, {
70
+ textAlign: 'left',
71
+ maxWidth: '100px',
72
+ minWidth: '40px',
73
+ width: 'auto'
74
+ });
75
+ }
76
+
77
+ private createFirstPtoInputs(): BooleanInput [] {
78
+ return STRANDS.map((strand) => {
79
+ if (!this.eventBus.isAntisenseStrandActive() && strand === STRAND.ANTISENSE)
80
+ return;
81
+ const initialValue = this.isFirstPtoActive(strand);
82
+ const firstPtoInput = ui.boolInput(`First ${strand} PTO`, initialValue);
83
+
84
+ firstPtoInput.onInput(() => {
85
+ const value = firstPtoInput.value!;
86
+ this.eventBus.setPhosphorothioateLinkageFlag(strand, 0, value);
87
+ });
88
+
89
+ const subscription = this.eventBus.phosphorothioateLingeFlagsChanged$.subscribe((flags) => {
90
+ const newValue = flags[strand][0];
91
+ firstPtoInput.value = newValue;
92
+ });
93
+ this.subscriptions.add(subscription);
94
+
95
+ this.addStyleToPtoInput(firstPtoInput);
96
+ ui.tooltip.bind(firstPtoInput.captionLabel, `Activate first phosphothioate in ${strand}`);
97
+ return firstPtoInput;
98
+ }).filter((input) => input !== undefined) as BooleanInput[];
99
+ }
100
+
101
+ private isFirstPtoActive(strand: STRAND): boolean {
102
+ return this.initialPatternConfig.phosphorothioateLinkageFlags[strand][0];
103
+ }
104
+ }
105
+