@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.
- package/.eslintrc.json +5 -5
- package/CHANGELOG.md +12 -0
- package/dist/package-test.js +2 -1
- package/dist/package-test.js.LICENSE.txt +8 -0
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -1
- package/dist/package.js.LICENSE.txt +8 -0
- package/dist/package.js.map +1 -1
- package/files/pattern-app-data.json +80 -0
- package/package.json +21 -14
- package/src/{model → apps/common/model}/const.ts +1 -1
- package/src/{model/data-loading-utils → apps/common/model/data-loader}/const.ts +7 -2
- package/src/apps/common/model/data-loader/json-loader.ts +48 -0
- package/src/{model/data-loading-utils → apps/common/model/data-loader}/types.ts +13 -6
- package/src/{model → apps/common/model}/monomer-lib/lib-wrapper.ts +9 -12
- package/src/apps/common/model/oligo-toolkit-package.ts +30 -0
- package/src/{model → apps/common/model}/parsing-validation/format-detector.ts +5 -5
- package/src/{model → apps/common/model}/parsing-validation/format-handler.ts +18 -19
- package/src/{model → apps/common/model}/parsing-validation/sequence-validator.ts +1 -1
- package/src/apps/common/view/app-ui-base.ts +28 -0
- package/src/apps/common/view/combined-app-ui.ts +66 -0
- package/src/{view/utils → apps/common/view/components}/colored-input/colored-text-input.ts +1 -1
- package/src/{view/utils → apps/common/view/components}/draw-molecule.ts +1 -1
- package/src/{view/utils → apps/common/view/components}/molecule-img.ts +3 -3
- package/src/{view/const/ui.ts → apps/common/view/const.ts} +4 -4
- package/src/apps/common/view/isolated-app-ui.ts +43 -0
- package/src/{view/monomer-lib-viewer/viewer.ts → apps/common/view/monomer-lib-viewer.ts} +2 -2
- package/src/apps/common/view/utils.ts +29 -0
- package/src/apps/pattern/model/const.ts +121 -0
- package/src/apps/pattern/model/data-manager.ts +297 -0
- package/src/apps/pattern/model/event-bus.ts +470 -0
- package/src/apps/pattern/model/router.ts +46 -0
- package/src/apps/pattern/model/subscription-manager.ts +21 -0
- package/src/apps/pattern/model/translator.ts +68 -0
- package/src/apps/pattern/model/types.ts +52 -0
- package/src/apps/pattern/model/utils.ts +110 -0
- package/src/apps/pattern/view/components/bulk-convert/column-input.ts +69 -0
- package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +37 -0
- package/src/apps/pattern/view/components/bulk-convert/table-input.ts +95 -0
- package/src/apps/pattern/view/components/edit-block-controls.ts +196 -0
- package/src/apps/pattern/view/components/left-section.ts +44 -0
- package/src/apps/pattern/view/components/load-block-controls.ts +198 -0
- package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +69 -0
- package/src/apps/pattern/view/components/right-section.ts +148 -0
- package/src/apps/pattern/view/components/strand-editor/dialog.ts +79 -0
- package/src/apps/pattern/view/components/strand-editor/header-controls.ts +105 -0
- package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +159 -0
- package/src/apps/pattern/view/components/terminal-modification-editor.ts +127 -0
- package/src/apps/pattern/view/components/translation-examples-block.ts +139 -0
- package/src/{view/style/pattern-app.css → apps/pattern/view/style.css} +4 -0
- package/src/apps/pattern/view/svg-utils/const.ts +63 -0
- package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +498 -0
- package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +45 -0
- package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +82 -0
- package/src/apps/pattern/view/svg-utils/svg-renderer.ts +396 -0
- package/src/apps/pattern/view/svg-utils/utils.ts +37 -0
- package/src/apps/pattern/view/types.ts +14 -0
- package/src/apps/pattern/view/ui.ts +61 -0
- package/src/{model/structure-app → apps/structure/model}/mol-transformations.ts +3 -3
- package/src/{model/structure-app → apps/structure/model}/monomer-code-parser.ts +9 -10
- package/src/{model/structure-app → apps/structure/model}/oligo-structure.ts +4 -4
- package/src/{model/structure-app → apps/structure/model}/sequence-to-molfile.ts +2 -2
- package/src/{view/apps/oligo-structure.ts → apps/structure/view/ui.ts} +31 -17
- package/src/{model/translator-app → apps/translator/model}/conversion-utils.ts +25 -7
- package/src/{model/translator-app → apps/translator/model}/format-converter.ts +7 -12
- package/src/{view/const/oligo-translator.ts → apps/translator/view/const.ts} +2 -0
- package/src/apps/translator/view/ui.ts +547 -0
- package/src/demo/demo-st-ui.ts +12 -32
- package/src/package.ts +76 -56
- package/src/plugins/mermade.ts +9 -9
- package/src/polytool/const.ts +40 -0
- package/src/polytool/csv-to-json-monomer-lib-converter.ts +40 -0
- package/src/polytool/cyclized.ts +56 -0
- package/src/polytool/monomer-lib-handler.ts +115 -0
- package/src/polytool/transformation.ts +326 -0
- package/src/polytool/ui.ts +59 -0
- package/src/polytool/utils.ts +20 -0
- package/src/tests/const.ts +5 -5
- package/src/tests/formats-support.ts +6 -6
- package/src/tests/formats-to-helm.ts +5 -5
- package/src/tests/helm-to-nucleotides.ts +5 -5
- package/tsconfig.json +4 -10
- package/webpack.config.js +3 -0
- package/files/axolabs-style.json +0 -97
- package/src/model/data-loading-utils/json-loader.ts +0 -38
- package/src/model/pattern-app/const.ts +0 -33
- package/src/model/pattern-app/draw-svg.ts +0 -193
- package/src/model/pattern-app/helpers.ts +0 -96
- package/src/model/pattern-app/oligo-pattern.ts +0 -111
- package/src/view/app-ui.ts +0 -193
- package/src/view/apps/oligo-pattern.ts +0 -759
- package/src/view/apps/oligo-translator.ts +0 -184
- /package/src/{model → apps/common/model}/helpers.ts +0 -0
- /package/src/{model → apps/common/model}/monomer-lib/const.ts +0 -0
- /package/src/{view/utils → apps/common/view/components}/app-info-dialog.ts +0 -0
- /package/src/{view/utils → apps/common/view/components}/colored-input/input-painters.ts +0 -0
- /package/src/{view/style/colored-text-input.css → apps/common/view/components/colored-input/style.css} +0 -0
- /package/src/{view/utils → apps/common/view/components}/router.ts +0 -0
- /package/src/{model/structure-app → apps/structure/model}/const.ts +0 -0
- /package/src/{view/style/structure-app.css → apps/structure/view/style.css} +0 -0
- /package/src/{model/translator-app → apps/translator/model}/const.ts +0 -0
- /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,69 @@
|
|
|
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
|
+
// grok.shell.info(`Table ${this.selectedTable?.name} selection from column input manager`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private refreshColumnControls(): void {
|
|
34
|
+
$(this.columnControlsContainer).empty();
|
|
35
|
+
$(this.columnControlsContainer).append(this.constructColumnControls());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private constructColumnControls(): HTMLElement[] {
|
|
39
|
+
const strandColumnInput = this.createStrandColumnInput();
|
|
40
|
+
const senseStrandColumnInput = strandColumnInput[STRAND.SENSE];
|
|
41
|
+
const antisenseStrandColumnInput = strandColumnInput[STRAND.ANTISENSE];
|
|
42
|
+
|
|
43
|
+
const idColumnInput = this.createIdColumnInput();
|
|
44
|
+
|
|
45
|
+
return [senseStrandColumnInput, antisenseStrandColumnInput, idColumnInput];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private createStrandColumnInput(): Record<StrandType, HTMLElement> {
|
|
49
|
+
const columns = this.selectedTable ?
|
|
50
|
+
this.selectedTable.columns.names().sort((a, b) => a.localeCompare(b)) :
|
|
51
|
+
[];
|
|
52
|
+
const strandColumnInput = Object.fromEntries(STRANDS.map((strand) => {
|
|
53
|
+
const input = ui.choiceInput(
|
|
54
|
+
`${STRAND_LABEL[strand]} column`,
|
|
55
|
+
columns[0],
|
|
56
|
+
columns,
|
|
57
|
+
(colName: string) => this.eventBus.selectColumn(strand, colName)
|
|
58
|
+
);
|
|
59
|
+
return [strand, input.root];
|
|
60
|
+
})) as Record<StrandType, HTMLElement>;
|
|
61
|
+
return strandColumnInput;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private createIdColumnInput(): HTMLElement {
|
|
65
|
+
const columns = this.selectedTable ? this.selectedTable.columns.names() : [];
|
|
66
|
+
const idColumnInput = ui.choiceInput('ID column', columns[0], columns, () => { });
|
|
67
|
+
return idColumnInput.root;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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 {STRAND} from '../../../model/const';
|
|
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 convertSequenceButton = ui.bigButton('Convert', () => this.processConvertButtonClick());
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
title,
|
|
27
|
+
tableInput,
|
|
28
|
+
columnControls,
|
|
29
|
+
ui.buttonsInput([
|
|
30
|
+
convertSequenceButton,
|
|
31
|
+
]),
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private processConvertButtonClick(): void {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -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
|
+
}
|