@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,110 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as DG from 'datagrok-api/dg';
3
+
4
+ import {PATTERN_APP_DATA} from '../../common/model/data-loader/json-loader';
5
+ import {NucleotideSequences} from './types';
6
+
7
+
8
+ export function isOverhangNucleotide(nucleotide: string): boolean {
9
+ return nucleotide.endsWith('(o)');
10
+ }
11
+
12
+
13
+ export function getUniqueNucleotides(sequences: NucleotideSequences): string[] {
14
+ const nucleotides = Object.values(sequences).flat();
15
+ return getUniqueSortedStrings(nucleotides);
16
+ }
17
+
18
+
19
+ export function getUniqueNucleotidesWithNumericLabels(
20
+ modificationsWithNumericLabels: string[]
21
+ ): string[] {
22
+ const uniqueLabelledNucleotides = getUniqueSortedStrings(modificationsWithNumericLabels);
23
+
24
+ return uniqueLabelledNucleotides.filter((nucleotide) => !isOverhangNucleotide(nucleotide));
25
+ }
26
+
27
+
28
+ export function getMostFrequentNucleotide(sequences: NucleotideSequences): string {
29
+ const nucleotideToFrequencyMap = Object.values(sequences).flat().reduce((acc, nucleotide) => {
30
+ acc[nucleotide] = (acc[nucleotide] || 0) + 1;
31
+ return acc;
32
+ }, {} as {[key: string]: number});
33
+
34
+ const mostFrequentNucleotide = Object.entries(nucleotideToFrequencyMap)
35
+ .reduce((a, b) => a[1] > b[1] ? a : b, ['', 0])[0];
36
+
37
+ return mostFrequentNucleotide;
38
+ }
39
+
40
+ // export function addColumnWithIds(tableName: string, columnName: string, patternName: string) {
41
+ // const nameOfNewColumn = 'ID ' + patternName;
42
+ // const columns = grok.shell.table(tableName).columns;
43
+ // if (columns.contains(nameOfNewColumn))
44
+ // columns.remove(nameOfNewColumn);
45
+ // const columnWithIds = columns.byName(columnName);
46
+ // return columns.addNewString(nameOfNewColumn).init((i: number) => {
47
+ // return (columnWithIds.getString(i) === '') ? '' : columnWithIds.get(i) + '_' + patternName;
48
+ // });
49
+ // }
50
+
51
+ // export function addColumnWithTranslatedSequences(
52
+ // tableName: string,
53
+ // columnName: string,
54
+ // bases: DG.InputBase[],
55
+ // ptoLinkages: DG.InputBase[],
56
+ // startModification: DG.InputBase,
57
+ // endModification: DG.InputBase,
58
+ // firstPtoExist: boolean) {
59
+ // const nameOfNewColumn = 'Axolabs ' + columnName;
60
+ // const columns = grok.shell.table(tableName).columns;
61
+ // if (columns.contains(nameOfNewColumn))
62
+ // columns.remove(nameOfNewColumn);
63
+ // const columnWithInputSequences = columns.byName(columnName);
64
+ // return columns.addNewString(nameOfNewColumn).init((i: number) => {
65
+ // return columnWithInputSequences.getString(i) === '' ?
66
+ // '' :
67
+ // translateSequence(columnWithInputSequences.getString(i), bases, ptoLinkages, startModification, endModification,
68
+ // firstPtoExist);
69
+ // });
70
+ // }
71
+
72
+ export namespace StrandEditingUtils {
73
+ export function getTruncatedStrandData(
74
+ originalNucleotides: string[],
75
+ originalPTOFlags: boolean[],
76
+ newStrandLength: number
77
+ ): {nucleotides: string[], ptoFlags: boolean[]} {
78
+ const nucleotides = originalNucleotides.slice(0, newStrandLength);
79
+ const ptoFlags = originalPTOFlags.slice(0, newStrandLength + 1);
80
+ return {nucleotides, ptoFlags};
81
+ }
82
+
83
+ export function getExtendedStrandData(
84
+ originalNucleotides: string[],
85
+ originalPTOFlags: boolean[],
86
+ newStrandLength: number,
87
+ sequenceBase: string
88
+ ): {nucleotides: string[], ptoFlags: boolean[]} {
89
+ const appendedNucleotidesLength = newStrandLength - originalNucleotides.length;
90
+ const nucleotides = originalNucleotides.concat(
91
+ new Array(newStrandLength - originalNucleotides.length).fill(sequenceBase)
92
+ );
93
+
94
+ const appendedFlagsLength = (originalNucleotides.length === 0) ? newStrandLength + 1 : appendedNucleotidesLength;
95
+ const ptoFlags = originalPTOFlags.concat(
96
+ new Array(appendedFlagsLength).fill(true)
97
+ );
98
+
99
+ return {nucleotides, ptoFlags};
100
+ }
101
+ }
102
+
103
+ function getUniqueSortedStrings(array: string[]): string[] {
104
+ const uniqueStrings = Array.from(new Set(array));
105
+ const sorted = Array.from(uniqueStrings).sort(
106
+ (a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
107
+ );
108
+
109
+ return sorted;
110
+ }
@@ -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 '../../style.css';
7
+
8
+ import {STRAND, STRANDS, STRAND_LABEL} from '../../../model/const';
9
+ import {EventBus} from '../../../model/event-bus';
10
+ import {StrandType} from '../../../model/types';
11
+
12
+ export class ColumnInputManager {
13
+ private columnControlsContainer: HTMLDivElement = ui.div([]);
14
+
15
+ constructor(private eventBus: EventBus) {
16
+ this.eventBus.tableSelectionChanged$.subscribe(() => this.handleTableChoice());
17
+ this.refreshColumnControls();
18
+ }
19
+
20
+ getColumnControlsContainer(): HTMLDivElement {
21
+ return this.columnControlsContainer;
22
+ }
23
+
24
+ private get selectedTable(): DG.DataFrame | null {
25
+ return this.eventBus.getTableSelection();
26
+ }
27
+
28
+ private handleTableChoice(): void {
29
+ this.refreshColumnControls();
30
+ }
31
+
32
+ private refreshColumnControls(): void {
33
+ $(this.columnControlsContainer).empty();
34
+ $(this.columnControlsContainer).append(this.constructColumnControls());
35
+ }
36
+
37
+ private constructColumnControls(): HTMLElement[] {
38
+ const strandColumnInput = this.createStrandColumnInput();
39
+ const senseStrandColumnInput = strandColumnInput[STRAND.SENSE];
40
+ const antisenseStrandColumnInput = strandColumnInput[STRAND.ANTISENSE];
41
+
42
+ this.eventBus.antisenseStrandToggled$.subscribe((isAntisenseActive) => {
43
+ $(antisenseStrandColumnInput).toggle(isAntisenseActive);
44
+ });
45
+
46
+ const idColumnInput = this.createIdColumnInput();
47
+
48
+ return [senseStrandColumnInput, antisenseStrandColumnInput, idColumnInput];
49
+ }
50
+
51
+ private createStrandColumnInput(): Record<StrandType, HTMLElement> {
52
+ const columns = this.selectedTable ?
53
+ this.selectedTable.columns.names().sort((a, b) => a.localeCompare(b)) :
54
+ [];
55
+ const strandColumnInput = Object.fromEntries(STRANDS.map((strand) => {
56
+ const input = ui.choiceInput(
57
+ `${STRAND_LABEL[strand]} column`,
58
+ columns[0],
59
+ columns,
60
+ (colName: string) => this.eventBus.selectStrandColumn(strand, colName)
61
+ );
62
+ this.eventBus.selectStrandColumn(strand, columns[0]);
63
+ return [strand, input.root];
64
+ })) as Record<StrandType, HTMLElement>;
65
+ return strandColumnInput;
66
+ }
67
+
68
+ private createIdColumnInput(): HTMLElement {
69
+ const columns = this.selectedTable ? this.selectedTable.columns.names() : [];
70
+ const idColumnInput = ui.choiceInput(
71
+ 'ID column',
72
+ columns[0],
73
+ columns,
74
+ (colName: string) => this.eventBus.selectIdColumn(colName)
75
+ );
76
+ this.eventBus.selectIdColumn(columns[0]);
77
+ return idColumnInput.root;
78
+ }
79
+ }
@@ -0,0 +1,38 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+ import '../../style.css';
3
+
4
+ import {EventBus} from '../../../model/event-bus';
5
+ import {ColumnInputManager} from './column-input';
6
+ import {TableInputManager} from './table-input';
7
+ import {bulkTranslate} from '../../../model/translator';
8
+
9
+ export class TableControlsManager {
10
+ private tableInputManager: TableInputManager;
11
+ private columnInputManager: ColumnInputManager;
12
+
13
+ constructor(private eventBus: EventBus) {
14
+ this.tableInputManager = new TableInputManager(eventBus);
15
+ this.columnInputManager = new ColumnInputManager(eventBus);
16
+ }
17
+
18
+ createControls(): HTMLElement[] {
19
+ const title = ui.h1('Bulk convert');
20
+ const tableInput = this.tableInputManager.getTableInputContainer();
21
+ const columnControls = this.columnInputManager.getColumnControlsContainer();
22
+
23
+ const convertButton = ui.bigButton('Convert', () => this.processConvertButtonClick());
24
+
25
+ return [
26
+ title,
27
+ tableInput,
28
+ columnControls,
29
+ ui.buttonsInput([
30
+ convertButton,
31
+ ]),
32
+ ];
33
+ }
34
+
35
+ private processConvertButtonClick(): void {
36
+ bulkTranslate(this.eventBus);
37
+ }
38
+ }
@@ -0,0 +1,95 @@
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 grok from 'datagrok-api/grok';
4
+ import * as ui from 'datagrok-api/ui';
5
+
6
+ import $ from 'cash-dom';
7
+ import '../../style.css';
8
+
9
+ import {EventBus} from '../../../model/event-bus';
10
+
11
+ export class TableInputManager {
12
+ private availableTables: DG.DataFrame[] = [];
13
+ private tableInputContainer: HTMLDivElement = ui.div([]);
14
+
15
+ constructor(private eventBus: EventBus) {
16
+ this.subscribeToTableEvents();
17
+ this.refreshTableInput();
18
+ }
19
+
20
+ getTableInputContainer(): HTMLDivElement {
21
+ return this.tableInputContainer;
22
+ }
23
+
24
+ private subscribeToTableEvents(): void {
25
+ grok.events.onTableAdded.subscribe((eventData) => this.handleTableAdded(eventData));
26
+ grok.events.onTableRemoved.subscribe((eventData) => this.handleTableRemoved(eventData));
27
+ this.eventBus.tableSelectionChanged$.subscribe(() => this.handleTableChoice());
28
+ }
29
+
30
+ private getTableFromEventData(eventData: any): DG.DataFrame {
31
+ if (! eventData && eventData.args && eventData.args.dataFrame instanceof DG.DataFrame)
32
+ throw new Error(`EventData does not contain a dataframe`, eventData);
33
+
34
+ return eventData.args.dataFrame as DG.DataFrame;
35
+ }
36
+
37
+ private handleTableAdded(eventData: any): void {
38
+ const table = this.getTableFromEventData(eventData);
39
+
40
+ if (this.availableTables.some((t: DG.DataFrame) => t.name === table.name))
41
+ return;
42
+
43
+ this.availableTables.push(table);
44
+ this.eventBus.selectTable(table);
45
+ this.refreshTableInput();
46
+ }
47
+
48
+ private handleTableRemoved(eventData: any): void {
49
+ const removedTable = this.getTableFromEventData(eventData);
50
+ this.availableTables = this.availableTables.filter((table: DG.DataFrame) => table.name !== removedTable.name);
51
+
52
+ const table = this.availableTables[0];
53
+ this.eventBus.selectTable(table ? table : null);
54
+ this.refreshTableInput();
55
+ }
56
+
57
+ private refreshTableInput(): void {
58
+ const tableInput = this.createTableInput();
59
+ $(this.tableInputContainer).empty();
60
+ this.tableInputContainer.append(tableInput.root);
61
+ }
62
+
63
+ private createTableInput(): DG.InputBase<DG.DataFrame | null> {
64
+ const currentlySelectedTable = this.eventBus.getTableSelection();
65
+
66
+ const tableInput = ui.tableInput(
67
+ 'Tables',
68
+ currentlySelectedTable,
69
+ this.availableTables,
70
+ (table: DG.DataFrame) => {
71
+ // WARNING: non-null check necessary to prevent resetting columns to
72
+ // null upon handling onTableAdded
73
+ if (table !== null && table instanceof DG.DataFrame)
74
+ this.eventBus.selectTable(table);
75
+ });
76
+ return tableInput;
77
+ }
78
+
79
+ private handleTableChoice(): void {
80
+ const selectedTable = this.eventBus.getTableSelection();
81
+ if (!selectedTable) return;
82
+ if (!this.isTableDisplayed(selectedTable))
83
+ this.displayTable(selectedTable);
84
+ }
85
+
86
+ private isTableDisplayed(table: DG.DataFrame): boolean {
87
+ return grok.shell.tableNames.includes(table.name);
88
+ }
89
+
90
+ private displayTable(table: DG.DataFrame): void {
91
+ const previousView = grok.shell.v;
92
+ grok.shell.addTableView(table);
93
+ grok.shell.v = previousView;
94
+ }
95
+ }
@@ -0,0 +1,196 @@
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 grok from 'datagrok-api/grok';
4
+ import * as ui from 'datagrok-api/ui';
5
+
6
+ import $ from 'cash-dom';
7
+ import '../style.css';
8
+
9
+ import {MAX_SEQUENCE_LENGTH, STRAND, STRANDS, STRAND_LABEL} from '../../model/const';
10
+ import {EventBus} from '../../model/event-bus';
11
+ import {StrandType} from '../../model/types';
12
+ import {NumberInput, StringInput} from '../types';
13
+ import {StrandEditorDialog} from './strand-editor/dialog';
14
+ import {DataManager} from '../../model/data-manager';
15
+ import {TerminalModificationEditorDialog} from './terminal-modification-editor';
16
+
17
+ export class PatternEditControlsManager {
18
+ constructor(
19
+ private eventBus: EventBus,
20
+ private dataManager: DataManager
21
+ ) { }
22
+
23
+ createControls(): HTMLElement[] {
24
+ const antisenseStrandToggle = this.createAntisenseStrandToggle();
25
+ const strandLengthInputs = this.createStrandLengthInputs();
26
+
27
+ const senseStrandLengthInput = strandLengthInputs[STRAND.SENSE].root;
28
+ const antisenseStrandLengthInput = strandLengthInputs[STRAND.ANTISENSE].root;
29
+
30
+ const sequenceBaseInput = this.createSequenceBaseInput().root;
31
+ const patternCommentInput = this.createPatternCommentInput().root;
32
+ const patternNameInputBlock = this.createPatternNameInputBlock();
33
+
34
+ const editPatternButton = this.createEditPatternButton();
35
+ const editTerminalModificationsButton = this.createEditTerminalModificationsButton();
36
+
37
+ return [
38
+ ui.h1('Edit'),
39
+ antisenseStrandToggle,
40
+ senseStrandLengthInput,
41
+ antisenseStrandLengthInput,
42
+ sequenceBaseInput,
43
+ patternNameInputBlock,
44
+ patternCommentInput,
45
+ ui.buttonsInput([
46
+ editTerminalModificationsButton,
47
+ editPatternButton,
48
+ ]),
49
+ ];
50
+ }
51
+
52
+ private createEditPatternButton(): HTMLButtonElement {
53
+ const editPatternButton = ui.button(
54
+ 'Edit strands',
55
+ () => StrandEditorDialog.open(this.eventBus, this.dataManager)
56
+ );
57
+
58
+ ui.tooltip.bind(editPatternButton, 'Edit strand modifications and PTOs');
59
+
60
+ return editPatternButton;
61
+ }
62
+
63
+ private createEditTerminalModificationsButton(): HTMLButtonElement {
64
+ const editTerminalModificationsButton = ui.button(
65
+ 'Edit terminals',
66
+ () => TerminalModificationEditorDialog.open(this.eventBus)
67
+ );
68
+
69
+ ui.tooltip.bind(editTerminalModificationsButton, 'Edit terminal modifications');
70
+ $(editTerminalModificationsButton).css('margin-right', '20px');
71
+
72
+ return editTerminalModificationsButton;
73
+ }
74
+
75
+
76
+ private createAntisenseStrandToggle(): HTMLElement {
77
+ const toggleAntisenseStrand = ui.switchInput(
78
+ `${STRAND_LABEL[STRAND.ANTISENSE]} strand`,
79
+ this.eventBus.isAntisenseStrandActive()
80
+ );
81
+
82
+ toggleAntisenseStrand.onInput(
83
+ () => this.eventBus.toggleAntisenseStrand(toggleAntisenseStrand.value)
84
+ );
85
+
86
+ this.eventBus.patternLoaded$.subscribe(() => {
87
+ toggleAntisenseStrand.value = this.eventBus.isAntisenseStrandActive();
88
+ });
89
+
90
+ toggleAntisenseStrand.setTooltip('Toggle antisense strand');
91
+
92
+ return toggleAntisenseStrand.root;
93
+ }
94
+
95
+ private createStrandLengthInputs(): Record<string, NumberInput> {
96
+ const createStrandLengthInput = (strand: StrandType) => {
97
+ const sequenceLength = this.eventBus.getNucleotideSequences()[strand].length;
98
+
99
+ const input = ui.intInput(
100
+ `${STRAND_LABEL[strand]} length`,
101
+ sequenceLength
102
+ );
103
+ input.onInput(() => updateStrandLengthInputs(strand, input));
104
+
105
+ this.eventBus.nucleotideSequencesChanged$.subscribe(() => {
106
+ input.value = this.eventBus.getNucleotideSequences()[strand].length;
107
+ });
108
+
109
+ input.setTooltip(`Number of nucleotides in ${strand}, including overhangs`);
110
+ return [strand, input];
111
+ };
112
+
113
+ const updateStrandLengthInputs = (strand: StrandType, input: DG.InputBase<number | null>) => {
114
+ const length = input.value;
115
+ if (length === null) return;
116
+
117
+ if (length <= 0) {
118
+ grok.shell.warning(`Sequence length must be greater than 0`);
119
+ input.value = 1;
120
+ }
121
+ if (length > MAX_SEQUENCE_LENGTH) {
122
+ grok.shell.warning(`Sequence length must be less than ${MAX_SEQUENCE_LENGTH + 1}`);
123
+ input.value = MAX_SEQUENCE_LENGTH;
124
+ }
125
+
126
+ this.eventBus.updateStrandLength(strand, input.value!);
127
+ };
128
+
129
+ const strandLengthInputs = Object.fromEntries(
130
+ STRANDS.map((strand) => createStrandLengthInput(strand))
131
+ );
132
+
133
+ this.eventBus.antisenseStrandToggled$.subscribe((active: boolean) => {
134
+ $(strandLengthInputs[STRAND.ANTISENSE].root).toggle(active);
135
+ });
136
+
137
+ return strandLengthInputs;
138
+ }
139
+
140
+ private createSequenceBaseInput(): StringInput {
141
+ const availableNucleoBases = this.dataManager.fetchAvailableNucleotideBases()
142
+ .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
143
+ const defaultNucleotideBase = this.dataManager.fetchDefaultNucleobase();
144
+
145
+ const sequenceBaseInput = ui.choiceInput('Sequence basis', defaultNucleotideBase, availableNucleoBases);
146
+
147
+ sequenceBaseInput.onInput(() => this.eventBus.replaceSequenceBase(sequenceBaseInput.value!));
148
+
149
+ this.eventBus.nucleotideSequencesChanged$.subscribe(() => {
150
+ sequenceBaseInput.value = this.eventBus.getSequenceBase();
151
+ });
152
+
153
+ sequenceBaseInput.setTooltip('Most frequent nucleobase in the strands');
154
+ return sequenceBaseInput;
155
+ }
156
+
157
+ private createPatternCommentInput(): StringInput {
158
+ const patternCommentInput = ui.textInput(
159
+ 'Comment',
160
+ this.eventBus.getComment()
161
+ );
162
+
163
+ $(patternCommentInput.root).addClass('st-pattern-text-input');
164
+
165
+ patternCommentInput.onInput(
166
+ () => this.eventBus.updateComment(patternCommentInput.value!)
167
+ );
168
+
169
+ this.eventBus.patternLoaded$.subscribe(() => {
170
+ patternCommentInput.value = this.eventBus.getComment();
171
+ });
172
+
173
+ return patternCommentInput;
174
+ }
175
+
176
+ private createPatternNameInputBlock(): HTMLElement {
177
+ const patternNameInput = ui.textInput(
178
+ 'Pattern name',
179
+ this.eventBus.getPatternName()
180
+ );
181
+
182
+ $(patternNameInput.root).addClass('st-pattern-text-input');
183
+
184
+ patternNameInput.onInput(
185
+ () => this.eventBus.updatePatternName(patternNameInput.value)
186
+ );
187
+ this.eventBus.patternLoaded$.subscribe(() => {
188
+ patternNameInput.value = this.eventBus.getPatternName();
189
+ });
190
+
191
+ patternNameInput.setTooltip('Name under which pattern will be saved');
192
+
193
+ return patternNameInput.root;
194
+ }
195
+ }
196
+
@@ -0,0 +1,44 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+
3
+ import $ from 'cash-dom';
4
+
5
+ import {DataManager} from '../../model/data-manager';
6
+ import {EventBus} from '../../model/event-bus';
7
+ import {TableControlsManager} from './bulk-convert/table-controls';
8
+ import {PatternEditControlsManager} from './edit-block-controls';
9
+ import {PatternLoadControlsManager} from './load-block-controls';
10
+
11
+ export class PatternAppLeftSection {
12
+ constructor(
13
+ private eventBus: EventBus,
14
+ private dataManager: DataManager
15
+ ) { };
16
+
17
+ getLayout(): HTMLDivElement {
18
+ const loadControlsManager = new PatternLoadControlsManager(this.eventBus, this.dataManager);
19
+
20
+ const editControlsManager = new PatternEditControlsManager(this.eventBus, this.dataManager);
21
+ const tableControlsManager = new TableControlsManager(this.eventBus);
22
+
23
+ const loadControls = loadControlsManager.createControls();
24
+ const editControls = editControlsManager.createControls();
25
+ const tableControls = tableControlsManager.createControls();
26
+
27
+ const loadControlsContainer = ui.div(loadControls);
28
+ $(loadControlsContainer).css({'padding-bottom': '20px'});
29
+
30
+ const form = ui.div([
31
+ ...editControls,
32
+ ...tableControls
33
+ ], 'ui-form');
34
+
35
+ const container = ui.div([
36
+ loadControlsContainer,
37
+ form
38
+ ]);
39
+ $(container).css({'padding': '25px'});
40
+
41
+ const layout = ui.box(container, {style: {'maxWidth': '450px'}});
42
+ return layout;
43
+ }
44
+ }