@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.
- package/.eslintrc.json +5 -5
- package/CHANGELOG.md +14 -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 +22 -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 +487 -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 +94 -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 +79 -0
- package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +38 -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 +200 -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 +77 -0
- package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
- package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
- package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
- package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +44 -0
- package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +94 -0
- package/src/apps/pattern/view/svg-utils/svg-renderer.ts +51 -0
- package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
- package/src/apps/pattern/view/svg-utils/title-block.ts +53 -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} +1 -1
- package/src/{view/apps/oligo-translator.ts → apps/translator/view/ui.ts} +88 -42
- package/src/demo/demo-st-ui.ts +12 -32
- package/src/package.ts +91 -55
- package/src/plugins/mermade.ts +9 -9
- package/src/polytool/const.ts +28 -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/pt-conversion.ts +307 -0
- package/src/polytool/pt-dialog.ts +115 -0
- package/src/polytool/pt-enumeration.ts +127 -0
- package/src/polytool/pt-rules.ts +73 -0
- package/src/polytool/utils.ts +27 -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 -10
- package/tsconfig.json +3 -9
- 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/{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,14 @@
|
|
|
1
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
|
+
|
|
3
|
+
import {STRANDS, STRAND_ENDS, TERMINI} from '../model/const';
|
|
4
|
+
|
|
5
|
+
export type BooleanInput = DG.InputBase<boolean | null>;
|
|
6
|
+
export type StringInput = DG.InputBase<string | null>;
|
|
7
|
+
export type NumberInput = DG.InputBase<number | null>;
|
|
8
|
+
|
|
9
|
+
export type Position = { x: number, y: number };
|
|
10
|
+
|
|
11
|
+
export type StrandEndToNumberMap = Record<typeof STRAND_ENDS[number], number>;
|
|
12
|
+
export type StrandToNumberMap = Record<typeof STRANDS[number], number>;
|
|
13
|
+
export type StrandEndToSVGElementsMap = Record<typeof STRAND_ENDS[number], SVGElement | null>;
|
|
14
|
+
export type TerminusToSVGElementMap = Record<typeof TERMINI[number], SVGElement | null>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as ui from 'datagrok-api/ui';
|
|
2
|
+
|
|
3
|
+
import {APP_NAME} from '../../common/view/const';
|
|
4
|
+
import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
|
|
5
|
+
import {DataManager} from '../model/data-manager';
|
|
6
|
+
import {EventBus} from '../model/event-bus';
|
|
7
|
+
import {URLRouter} from '../model/router';
|
|
8
|
+
import {PatternAppLeftSection} from './components/left-section';
|
|
9
|
+
import {PatternAppRightSection} from './components/right-section';
|
|
10
|
+
import {PatternConfigRecord} from '../model/types';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export class OligoPatternUI extends IsolatedAppUIBase {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(APP_NAME.PATTERN);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected getContent(): Promise<HTMLDivElement> {
|
|
19
|
+
return getContent();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async function getContent(): Promise<HTMLDivElement> {
|
|
25
|
+
const dataManager = await DataManager.getInstance();
|
|
26
|
+
const urlRouter = new URLRouter();
|
|
27
|
+
|
|
28
|
+
const initialPatternRecord = await getInitialPatternRecord(dataManager, urlRouter);
|
|
29
|
+
const eventBus = new EventBus(dataManager, initialPatternRecord);
|
|
30
|
+
urlRouter.subscribeToObservables(eventBus);
|
|
31
|
+
|
|
32
|
+
const leftSection = new PatternAppLeftSection(eventBus, dataManager).getLayout();
|
|
33
|
+
const rightSection = new PatternAppRightSection(eventBus, dataManager).getLayout();
|
|
34
|
+
|
|
35
|
+
const isResizeable = true;
|
|
36
|
+
|
|
37
|
+
const layout = ui.splitH([
|
|
38
|
+
leftSection,
|
|
39
|
+
rightSection,
|
|
40
|
+
], {}, isResizeable);
|
|
41
|
+
|
|
42
|
+
return layout;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getInitialPatternRecord(
|
|
46
|
+
dataManager: DataManager,
|
|
47
|
+
urlRouter: URLRouter
|
|
48
|
+
): Promise<PatternConfigRecord> {
|
|
49
|
+
const patternHash = urlRouter.getPatternHash();
|
|
50
|
+
if (!patternHash) {
|
|
51
|
+
urlRouter.clearPatternURL();
|
|
52
|
+
return dataManager.getDefaultPatternRecord();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let initialPatternRecord = await dataManager.getPatternRecord(patternHash);
|
|
56
|
+
if (!initialPatternRecord) {
|
|
57
|
+
urlRouter.clearPatternURL();
|
|
58
|
+
initialPatternRecord = dataManager.getDefaultPatternRecord();
|
|
59
|
+
}
|
|
60
|
+
return initialPatternRecord;
|
|
61
|
+
}
|
|
@@ -36,7 +36,7 @@ export function linkStrandsV3000(
|
|
|
36
36
|
|
|
37
37
|
let inverted = false;
|
|
38
38
|
const molBlocks = strands.senseStrands.concat(strands.antiStrands);
|
|
39
|
-
/** Minimal value of
|
|
39
|
+
/** Minimal value of ANTISENSE_STRAND and AS2 shift */
|
|
40
40
|
let ssYShift = 0;
|
|
41
41
|
|
|
42
42
|
for (let i = 0; i < molBlocks.length; i++) {
|
|
@@ -47,12 +47,12 @@ export function linkStrandsV3000(
|
|
|
47
47
|
|
|
48
48
|
if (i >= strands.senseStrands.length) {
|
|
49
49
|
if (inverted === false) {
|
|
50
|
-
//
|
|
50
|
+
// ANTISENSE_STRAND strand
|
|
51
51
|
inverted = true;
|
|
52
52
|
xShift = 0;
|
|
53
53
|
}
|
|
54
54
|
} else {
|
|
55
|
-
//
|
|
55
|
+
// SENSE_STRAND strands
|
|
56
56
|
ssYShift = Math.min(ssYShift, Math.min(
|
|
57
57
|
...coordinates.y.filter((item) => item < 0)
|
|
58
58
|
));
|
|
@@ -4,15 +4,15 @@ import * as ui from 'datagrok-api/ui';
|
|
|
4
4
|
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
6
|
import {PHOSPHATE_SYMBOL} from './const';
|
|
7
|
-
import {sortByReverseLength} from '
|
|
8
|
-
import {MonomerLibWrapper} from '
|
|
9
|
-
import {
|
|
7
|
+
import {sortByReverseLength} from '../../common/model/helpers';
|
|
8
|
+
import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
|
|
9
|
+
import {MONOMERS_WITH_PHOSPHATE} from '../../common/model/data-loader/json-loader';
|
|
10
10
|
|
|
11
11
|
/** Wrapper for parsing a strand and getting a sequence of monomer IDs (with
|
|
12
12
|
* omitted linkers, if needed) */
|
|
13
13
|
export class MonomerSequenceParser {
|
|
14
14
|
constructor(
|
|
15
|
-
private sequence: string,
|
|
15
|
+
private sequence: string,
|
|
16
16
|
// todo: remove from the list of parameters
|
|
17
17
|
private codeMap: Map<string, string>
|
|
18
18
|
) {
|
|
@@ -39,9 +39,8 @@ export class MonomerSequenceParser {
|
|
|
39
39
|
const nextMonomerIsPhosphate = (i + 1 < parsedRawCodes.length && monomerIsPhosphateLinker(this.getSymbolForCode(parsedRawCodes[i + 1])));
|
|
40
40
|
|
|
41
41
|
// todo: refactor as molfile-specific
|
|
42
|
-
if (!isPhosphate && !monomerHasRightPhosphateLinker(monomerSymbol) && !nextMonomerIsPhosphate && !lastMonomer)
|
|
42
|
+
if (!isPhosphate && !monomerHasRightPhosphateLinker(monomerSymbol) && !nextMonomerIsPhosphate && !lastMonomer)
|
|
43
43
|
monomerSymbolSequence.push(PHOSPHATE_SYMBOL);
|
|
44
|
-
}
|
|
45
44
|
});
|
|
46
45
|
return monomerSymbolSequence;
|
|
47
46
|
}
|
|
@@ -70,20 +69,20 @@ export class MonomerSequenceParser {
|
|
|
70
69
|
|
|
71
70
|
// todo: port to monomer handler
|
|
72
71
|
private getAllCodesOfFormat(): string[] {
|
|
73
|
-
|
|
72
|
+
const allCodesInTheFormat = Array.from(this.codeMap.keys());
|
|
74
73
|
return sortByReverseLength(allCodesInTheFormat);
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
// todo: to be eliminated after full helm support
|
|
79
78
|
function monomerHasLeftPhosphateLinker(monomerSymbol: string): boolean {
|
|
80
|
-
return
|
|
79
|
+
return MONOMERS_WITH_PHOSPHATE['left'].includes(monomerSymbol);
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
function monomerHasRightPhosphateLinker(monomerSymbol: string): boolean {
|
|
84
|
-
return
|
|
83
|
+
return MONOMERS_WITH_PHOSPHATE['right'].includes(monomerSymbol);
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
function monomerIsPhosphateLinker(monomerSymbol: string): boolean {
|
|
88
|
-
return
|
|
87
|
+
return MONOMERS_WITH_PHOSPHATE['phosphate'].includes(monomerSymbol);
|
|
89
88
|
}
|
|
@@ -4,11 +4,11 @@ import * as DG from 'datagrok-api/dg';
|
|
|
4
4
|
|
|
5
5
|
import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
6
6
|
|
|
7
|
-
import {download} from '
|
|
7
|
+
import {download} from '../../common/model/helpers';
|
|
8
8
|
import {SequenceToMolfileConverter} from './sequence-to-molfile';
|
|
9
9
|
import {linkStrandsV3000} from './mol-transformations';
|
|
10
|
-
import {DEFAULT_FORMATS} from '
|
|
11
|
-
import {FormatDetector} from '
|
|
10
|
+
import {DEFAULT_FORMATS} from '../../common/model/const';
|
|
11
|
+
import {FormatDetector} from '../../common/model/parsing-validation/format-detector';
|
|
12
12
|
|
|
13
13
|
export type StrandData = {
|
|
14
14
|
strand: string,
|
|
@@ -62,7 +62,7 @@ export function saveSdf(
|
|
|
62
62
|
nonEmptyStrands.length === 0 ||
|
|
63
63
|
nonEmptyStrands.length === 1 && ss.strand === ''
|
|
64
64
|
) {
|
|
65
|
-
grok.shell.warning('Enter
|
|
65
|
+
grok.shell.warning('Enter SENSE_STRAND and optionally ANTISENSE_STRAND/AS2 to save SDF');
|
|
66
66
|
} else {
|
|
67
67
|
let result: string;
|
|
68
68
|
if (oneEntity) {
|
|
@@ -4,7 +4,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
4
4
|
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
6
|
import {MonomerSequenceParser} from './monomer-code-parser';
|
|
7
|
-
import {MonomerLibWrapper} from '
|
|
7
|
+
import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
|
|
8
8
|
|
|
9
9
|
export class SequenceToMolfileConverter {
|
|
10
10
|
constructor(
|
|
@@ -24,7 +24,7 @@ export class SequenceToMolfileConverter {
|
|
|
24
24
|
parsedSequence.forEach((monomerSymbol, idx) => {
|
|
25
25
|
const monomerMolfile = this.getMonomerMolfile(monomerSymbol, idx);
|
|
26
26
|
monomerMolfiles.push(monomerMolfile);
|
|
27
|
-
})
|
|
27
|
+
});
|
|
28
28
|
let molfile = this.getPolymerMolfile(monomerMolfiles);
|
|
29
29
|
if (this.invert) {
|
|
30
30
|
molfile = this.reflect(molfile);
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
3
|
import * as grok from 'datagrok-api/grok';
|
|
3
4
|
import * as ui from 'datagrok-api/ui';
|
|
4
|
-
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
|
-
import * as rxjs from 'rxjs';
|
|
7
|
-
import '../style/structure-app.css';
|
|
8
6
|
import $ from 'cash-dom';
|
|
7
|
+
import * as rxjs from 'rxjs';
|
|
8
|
+
import './style.css';
|
|
9
9
|
|
|
10
10
|
import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
12
|
+
import {ColoredTextInput} from '../../common/view/components/colored-input/colored-text-input';
|
|
13
|
+
import {highlightInvalidSubsequence} from '../../common/view/components/colored-input/input-painters';
|
|
14
|
+
import {MoleculeImage} from '../../common/view/components/molecule-img';
|
|
15
|
+
import {APP_NAME} from '../../common/view/const';
|
|
16
|
+
import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
|
|
17
|
+
import {getLinkedMolfile, saveSdf, StrandData} from '../model/oligo-structure';
|
|
17
18
|
|
|
18
19
|
const enum DIRECTION {
|
|
19
20
|
STRAIGHT = '5′ → 3′',
|
|
@@ -21,7 +22,7 @@ const enum DIRECTION {
|
|
|
21
22
|
};
|
|
22
23
|
const STRANDS = ['ss', 'as', 'as2'] as const;
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
class StructureAppLayout {
|
|
25
26
|
constructor() {
|
|
26
27
|
this.onInput = new rxjs.Subject<string>();
|
|
27
28
|
this.onInvalidInput = new rxjs.Subject<string>();
|
|
@@ -98,7 +99,7 @@ export class StructureLayoutHandler {
|
|
|
98
99
|
const selected = (idx === 0) ? DIRECTION.STRAIGHT : DIRECTION.INVERSE;
|
|
99
100
|
return [key, ui.choiceInput(
|
|
100
101
|
`${key.toUpperCase()} direction`, selected, [DIRECTION.STRAIGHT, DIRECTION.INVERSE]
|
|
101
|
-
)]
|
|
102
|
+
)];
|
|
102
103
|
}
|
|
103
104
|
)
|
|
104
105
|
);
|
|
@@ -106,8 +107,9 @@ export class StructureLayoutHandler {
|
|
|
106
107
|
STRANDS.forEach((strand, idx) => {
|
|
107
108
|
directionChoiceInput[strand].onChanged(() => {
|
|
108
109
|
let value = directionChoiceInput[strand].value === DIRECTION.INVERSE;
|
|
109
|
-
// warning: the next line is necessary
|
|
110
|
-
|
|
110
|
+
// warning: the next line is necessary
|
|
111
|
+
// until the legacy notion of direction used in the molfile generation gets fixed
|
|
112
|
+
if (idx > 0) value = !value;
|
|
111
113
|
this.directionInversion[strand] = value;
|
|
112
114
|
this.onInput.next();
|
|
113
115
|
});
|
|
@@ -126,7 +128,7 @@ export class StructureLayoutHandler {
|
|
|
126
128
|
const clearBlock = Object.fromEntries(
|
|
127
129
|
STRANDS.map(
|
|
128
130
|
(key) => {
|
|
129
|
-
const clearIcon = ui.icons.delete(() => { coloredInput[key].inputBase.value = '' });
|
|
131
|
+
const clearIcon = ui.icons.delete(() => { coloredInput[key].inputBase.value = ''; });
|
|
130
132
|
const clearButton = ui.button(clearIcon, () => {});
|
|
131
133
|
ui.tooltip.bind(clearButton, `Clear ${key.toUpperCase()}`);
|
|
132
134
|
return [key, clearIcon];
|
|
@@ -163,12 +165,12 @@ export class StructureLayoutHandler {
|
|
|
163
165
|
|
|
164
166
|
private getStrandData() {
|
|
165
167
|
return Object.fromEntries(
|
|
166
|
-
STRANDS.map((strand
|
|
167
|
-
|
|
168
|
+
STRANDS.map((strand) => {
|
|
169
|
+
const invert = this.directionInversion[strand];
|
|
168
170
|
return [strand, {
|
|
169
171
|
strand: this.inputBase[strand].value.replace(/\s*/g, ''),
|
|
170
172
|
invert: invert
|
|
171
|
-
}]
|
|
173
|
+
}];
|
|
172
174
|
})
|
|
173
175
|
);
|
|
174
176
|
}
|
|
@@ -192,7 +194,7 @@ export class StructureLayoutHandler {
|
|
|
192
194
|
const errStr = errorToConsole(err);
|
|
193
195
|
console.error(errStr);
|
|
194
196
|
}
|
|
195
|
-
// todo:
|
|
197
|
+
// todo: compute relative numbers
|
|
196
198
|
const canvasWidth = 650;
|
|
197
199
|
const canvasHeight = 150;
|
|
198
200
|
const molImgObj = new MoleculeImage(molfile);
|
|
@@ -201,3 +203,15 @@ export class StructureLayoutHandler {
|
|
|
201
203
|
$(this.moleculeImgDiv).find('canvas').css('float', 'inherit');
|
|
202
204
|
}
|
|
203
205
|
}
|
|
206
|
+
|
|
207
|
+
export class OligoStructureUI extends IsolatedAppUIBase {
|
|
208
|
+
constructor() {
|
|
209
|
+
super(APP_NAME.STRUCTURE);
|
|
210
|
+
this.layout = new StructureAppLayout();
|
|
211
|
+
}
|
|
212
|
+
private readonly layout: StructureAppLayout;
|
|
213
|
+
|
|
214
|
+
protected getContent(): Promise<HTMLDivElement> {
|
|
215
|
+
return this.layout.getHtmlDivElement();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import {DEFAULT_FORMATS, NUCLEOTIDES} from '
|
|
1
|
+
import {DEFAULT_FORMATS, NUCLEOTIDES} from '../../common/model/const';
|
|
2
|
+
import {NUCLEOTIDES_FORMAT} from '../view/const';
|
|
2
3
|
import {UNKNOWN_SYMBOL} from './const';
|
|
3
4
|
import {FormatConverter} from './format-converter';
|
|
4
|
-
import {
|
|
5
|
-
import {MonomerLibWrapper} from '
|
|
5
|
+
import {CODES_TO_HELM_DICT} from '../../common/model/data-loader/json-loader';
|
|
6
|
+
import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
|
|
6
7
|
|
|
7
8
|
export function getTranslatedSequences(sequence: string, indexOfFirstInvalidChar: number, sourceFormat: string): {[key: string]: string} {
|
|
8
|
-
const supportedFormats = Object.keys(
|
|
9
|
+
const supportedFormats = Object.keys(CODES_TO_HELM_DICT).concat([DEFAULT_FORMATS.HELM]) as string[];
|
|
9
10
|
|
|
10
11
|
if (!sequence || (indexOfFirstInvalidChar !== -1 && sourceFormat !== DEFAULT_FORMATS.HELM))
|
|
11
12
|
return {};
|
|
12
13
|
|
|
13
14
|
if (!supportedFormats.includes(sourceFormat))
|
|
14
|
-
throw new Error(`${sourceFormat} format is not supported by SequenceTranslator`)
|
|
15
|
+
throw new Error(`${sourceFormat} format is not supported by SequenceTranslator`);
|
|
15
16
|
|
|
16
17
|
const outputFormats = supportedFormats.filter((el) => el != sourceFormat)
|
|
17
18
|
.sort((a, b) => a.localeCompare(b));
|
|
@@ -25,8 +26,8 @@ export function getTranslatedSequences(sequence: string, indexOfFirstInvalidChar
|
|
|
25
26
|
translation = null;
|
|
26
27
|
}
|
|
27
28
|
return [format, translation];
|
|
28
|
-
}).filter(([
|
|
29
|
-
)
|
|
29
|
+
}).filter(([_, translation]) => translation)
|
|
30
|
+
);
|
|
30
31
|
const helm = (sourceFormat === DEFAULT_FORMATS.HELM) ? sequence : result[DEFAULT_FORMATS.HELM];
|
|
31
32
|
const nucleotides = getNucleotidesSequence(helm, MonomerLibWrapper.getInstance());
|
|
32
33
|
if (nucleotides)
|
|
@@ -47,3 +48,20 @@ export function getNucleotidesSequence(helmString: string, monomerLib: MonomerLi
|
|
|
47
48
|
}).map((el) => el ? el : UNKNOWN_SYMBOL).join('');
|
|
48
49
|
return nucleotides;
|
|
49
50
|
}
|
|
51
|
+
|
|
52
|
+
// todo: remove after refactoring as a workaround
|
|
53
|
+
export function convert(sequence: string, sourceFormat: string, targetFormat: string): string | null {
|
|
54
|
+
const converter = new FormatConverter(sequence, sourceFormat);
|
|
55
|
+
if (targetFormat === NUCLEOTIDES_FORMAT) {
|
|
56
|
+
const helm = converter.convertTo(DEFAULT_FORMATS.HELM);
|
|
57
|
+
const nucleotides = getNucleotidesSequence(helm, MonomerLibWrapper.getInstance());
|
|
58
|
+
return nucleotides;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return converter.convertTo(targetFormat);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getSupportedTargetFormats(): string[] {
|
|
65
|
+
const supportedTargetFormats = Object.keys(CODES_TO_HELM_DICT).concat([DEFAULT_FORMATS.HELM, NUCLEOTIDES_FORMAT]).sort() as string[];
|
|
66
|
+
return supportedTargetFormats;
|
|
67
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
import {DEFAULT_FORMATS} from '
|
|
2
|
+
import {DEFAULT_FORMATS} from '../../common/model/const';
|
|
3
3
|
import {PHOSPHATE_SYMBOL, UNKNOWN_SYMBOL} from './const';
|
|
4
|
-
import {FormatHandler, getRegExpPattern} from '
|
|
4
|
+
import {FormatHandler, getRegExpPattern} from '../../common/model/parsing-validation/format-handler';
|
|
5
5
|
|
|
6
6
|
const HELM_WRAPPER = {
|
|
7
7
|
LEFT: 'RNA1{',
|
|
@@ -16,21 +16,16 @@ export class FormatConverter {
|
|
|
16
16
|
convertTo(targetFormat: string): string {
|
|
17
17
|
const formats = this.formats.getFormatNames();
|
|
18
18
|
|
|
19
|
-
if (this.sourceFormat === DEFAULT_FORMATS.HELM && formats.includes(targetFormat))
|
|
20
|
-
return this.helmToFormat(this.sequence, targetFormat);
|
|
21
|
-
else if (formats.includes(this.sourceFormat) && targetFormat === DEFAULT_FORMATS.HELM)
|
|
22
|
-
return this.formatToHelm(this.sequence, this.sourceFormat);
|
|
23
|
-
else if ([this.sourceFormat, targetFormat].every((el) => formats.includes(el))) {
|
|
19
|
+
if (this.sourceFormat === DEFAULT_FORMATS.HELM && formats.includes(targetFormat)) { return this.helmToFormat(this.sequence, targetFormat); } else if (formats.includes(this.sourceFormat) && targetFormat === DEFAULT_FORMATS.HELM) { return this.formatToHelm(this.sequence, this.sourceFormat); } else if ([this.sourceFormat, targetFormat].every((el) => formats.includes(el))) {
|
|
24
20
|
const helm = this.formatToHelm(this.sequence, this.sourceFormat);
|
|
25
21
|
return this.helmToFormat(helm, targetFormat);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
throw new Error (`ST: unsupported translation direction ${this.sourceFormat} -> ${targetFormat}`);
|
|
22
|
+
} else {
|
|
23
|
+
throw new Error(`ST: unsupported translation direction ${this.sourceFormat} -> ${targetFormat}`);
|
|
29
24
|
}
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
private helmToFormat(helmSequence: string, targetFormat: string): string {
|
|
33
|
-
const wrapperRegExp = new RegExp(getRegExpPattern(Object.values(HELM_WRAPPER)), 'g')
|
|
28
|
+
const wrapperRegExp = new RegExp(getRegExpPattern(Object.values(HELM_WRAPPER)), 'g');
|
|
34
29
|
let result = helmSequence.replace(wrapperRegExp, '');
|
|
35
30
|
|
|
36
31
|
const dict = this.formats.getHelmToFormatDict(targetFormat);
|
|
@@ -54,7 +49,7 @@ export class FormatConverter {
|
|
|
54
49
|
const phosphateRegExp = this.formats.getPhosphateHelmCodesRegExp(sourceFormat);
|
|
55
50
|
|
|
56
51
|
let helm = sequence.replace(formatRegExp, (match) => {
|
|
57
|
-
const result = formatCodes.includes(match) ?
|
|
52
|
+
const result = formatCodes.includes(match) ? dict[match] + '.' : '?';
|
|
58
53
|
return result;
|
|
59
54
|
});
|
|
60
55
|
helm = helm.replace(/\?+/g, `${UNKNOWN_SYMBOL}.`);
|
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
3
|
import * as grok from 'datagrok-api/grok';
|
|
3
4
|
import * as ui from 'datagrok-api/ui';
|
|
4
|
-
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
7
|
+
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
8
8
|
|
|
9
9
|
import * as rxjs from 'rxjs';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {DEFAULT_AXOLABS_INPUT} from '
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
10
|
+
|
|
11
|
+
import {DEFAULT_FORMATS} from '../../common/model/const';
|
|
12
|
+
import {CODES_TO_HELM_DICT} from '../../common/model/data-loader/json-loader';
|
|
13
|
+
import {download} from '../../common/model/helpers';
|
|
14
|
+
import {FormatDetector} from '../../common/model/parsing-validation/format-detector';
|
|
15
|
+
import {SequenceValidator} from '../../common/model/parsing-validation/sequence-validator';
|
|
16
|
+
import {ColoredTextInput} from '../../common/view/components/colored-input/colored-text-input';
|
|
17
|
+
import {highlightInvalidSubsequence} from '../../common/view/components/colored-input/input-painters';
|
|
18
|
+
import {MoleculeImage} from '../../common/view/components/molecule-img';
|
|
19
|
+
import {APP_NAME, DEFAULT_AXOLABS_INPUT} from '../../common/view/const';
|
|
20
|
+
import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
|
|
21
|
+
import {MonomerLibViewer} from '../../common/view/monomer-lib-viewer';
|
|
22
|
+
import {SequenceToMolfileConverter} from '../../structure/model/sequence-to-molfile';
|
|
23
|
+
import {convert, getSupportedTargetFormats, getTranslatedSequences} from '../model/conversion-utils';
|
|
24
|
+
import {FormatConverter} from '../model/format-converter';
|
|
25
|
+
import {NUCLEOTIDES_FORMAT, SEQUENCE_COPIED_MSG, SEQ_TOOLTIP_MSG} from './const';
|
|
26
|
+
import './style.css';
|
|
25
27
|
|
|
26
28
|
const enum REQUIRED_COLUMN_LABEL {
|
|
27
29
|
SEQUENCE = 'Sequence',
|
|
28
30
|
}
|
|
29
|
-
const REQUIRED_COLUMN_LABELS = [
|
|
31
|
+
const REQUIRED_COLUMN_LABELS = [REQUIRED_COLUMN_LABEL.SEQUENCE];
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
class TranslatorAppLayout {
|
|
32
34
|
private eventBus: EventBus;
|
|
33
|
-
private inputFormats = Object.keys(
|
|
35
|
+
private inputFormats = Object.keys(CODES_TO_HELM_DICT).concat(DEFAULT_FORMATS.HELM);
|
|
36
|
+
|
|
34
37
|
constructor() {
|
|
35
38
|
this.moleculeImgDiv = ui.div([]);
|
|
36
39
|
this.moleculeImgDiv.className = 'mol-host';
|
|
@@ -77,7 +80,7 @@ export class TranslatorLayoutHandler {
|
|
|
77
80
|
singleSequenceControls,
|
|
78
81
|
bulkControls,
|
|
79
82
|
ui.block([ui.box(this.moleculeImgDiv)])
|
|
80
|
-
])
|
|
83
|
+
])
|
|
81
84
|
);
|
|
82
85
|
|
|
83
86
|
this.formatChoiceInput.value = this.format;
|
|
@@ -92,15 +95,26 @@ export class TranslatorLayoutHandler {
|
|
|
92
95
|
|
|
93
96
|
const tableControlsManager = new TableControlsManager(this.eventBus);
|
|
94
97
|
const tableControls = tableControlsManager.createUIComponents();
|
|
95
|
-
const inputFormats = ui.choiceInput(
|
|
96
|
-
|
|
98
|
+
const inputFormats = ui.choiceInput(
|
|
99
|
+
'Input format',
|
|
100
|
+
DEFAULT_FORMATS.AXOLABS,
|
|
101
|
+
this.inputFormats,
|
|
102
|
+
(value: string) => this.eventBus.selectInputFormat(value)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const outputFormats = ui.choiceInput(
|
|
106
|
+
'Output format',
|
|
107
|
+
NUCLEOTIDES_FORMAT,
|
|
108
|
+
getSupportedTargetFormats(),
|
|
109
|
+
(value: string) => this.eventBus.selectOutputFormat(value)
|
|
110
|
+
);
|
|
97
111
|
const convertBulkButton = this.createConvertBulkButton();
|
|
98
112
|
|
|
99
113
|
const tableControlsContainer = ui.div([
|
|
100
114
|
...tableControls,
|
|
101
115
|
inputFormats,
|
|
102
116
|
outputFormats,
|
|
103
|
-
convertBulkButton
|
|
117
|
+
convertBulkButton
|
|
104
118
|
], 'ui-form');
|
|
105
119
|
|
|
106
120
|
const bulkTranslationControls = ui.block25([
|
|
@@ -130,7 +144,7 @@ export class TranslatorLayoutHandler {
|
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
const inputFormat = this.eventBus.getSelectedInputFormat();
|
|
133
|
-
const outputFormat =
|
|
147
|
+
const outputFormat = this.eventBus.getSelectedOutputFormat();
|
|
134
148
|
const sequenceColumn = this.eventBus.getSelectedColumn(REQUIRED_COLUMN_LABEL.SEQUENCE);
|
|
135
149
|
if (!sequenceColumn) {
|
|
136
150
|
grok.shell.warning('No sequence column selected');
|
|
@@ -142,15 +156,20 @@ export class TranslatorLayoutHandler {
|
|
|
142
156
|
DG.TYPE.STRING,
|
|
143
157
|
newColumnName,
|
|
144
158
|
sequenceColumn.toList().map((sequence: string) => {
|
|
145
|
-
const
|
|
146
|
-
return
|
|
159
|
+
const result = convert(sequence, inputFormat, outputFormat);
|
|
160
|
+
return result;
|
|
147
161
|
})
|
|
148
162
|
);
|
|
149
163
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
if (outputFormat === NUCLEOTIDES_FORMAT || outputFormat === DEFAULT_FORMATS.HELM) {
|
|
165
|
+
translatedColumn.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
166
|
+
const units = outputFormat == NUCLEOTIDES_FORMAT ? NOTATION.FASTA : NOTATION.HELM;
|
|
167
|
+
translatedColumn.setTag(DG.TAGS.UNITS, units);
|
|
168
|
+
const seqHandler = SeqHandler.forColumn(translatedColumn as DG.Column<string>);
|
|
169
|
+
const setUnits = outputFormat == NUCLEOTIDES_FORMAT ? SeqHandler.setUnitsToFastaColumn :
|
|
170
|
+
SeqHandler.setUnitsToHelmColumn;
|
|
171
|
+
setUnits(seqHandler);
|
|
172
|
+
}
|
|
154
173
|
|
|
155
174
|
// add newColumn to the table
|
|
156
175
|
selectedTable.columns.add(translatedColumn);
|
|
@@ -176,7 +195,7 @@ export class TranslatorLayoutHandler {
|
|
|
176
195
|
const formatChoiceInput = ui.div([this.formatChoiceInput]);
|
|
177
196
|
|
|
178
197
|
const clearButton = ui.button(
|
|
179
|
-
ui.icons.delete(() => { sequenceColoredInput.inputBase.value = '' }),
|
|
198
|
+
ui.icons.delete(() => { sequenceColoredInput.inputBase.value = ''; }),
|
|
180
199
|
() => {}
|
|
181
200
|
);
|
|
182
201
|
ui.tooltip.bind(clearButton, 'Clear input');
|
|
@@ -201,7 +220,7 @@ export class TranslatorLayoutHandler {
|
|
|
201
220
|
ui.h1('Single sequence'),
|
|
202
221
|
singleSequenceInputControls,
|
|
203
222
|
singleSequenceOutputTable,
|
|
204
|
-
])
|
|
223
|
+
]);
|
|
205
224
|
|
|
206
225
|
return singleSequenceControls;
|
|
207
226
|
}
|
|
@@ -222,7 +241,8 @@ export class TranslatorLayoutHandler {
|
|
|
222
241
|
private updateTable(): void {
|
|
223
242
|
this.outputTableDiv.innerHTML = '';
|
|
224
243
|
// todo: does not detect correctly (U-A)(U-A)
|
|
225
|
-
const indexOfInvalidChar = (!this.format) ? 0 :
|
|
244
|
+
const indexOfInvalidChar = (!this.format) ? 0 :
|
|
245
|
+
(new SequenceValidator(this.sequence)).getInvalidCodeIndex(this.format!);
|
|
226
246
|
const translatedSequences = getTranslatedSequences(this.sequence, indexOfInvalidChar, this.format!);
|
|
227
247
|
const tableRows = [];
|
|
228
248
|
|
|
@@ -320,14 +340,11 @@ class TableInputManager {
|
|
|
320
340
|
this.eventBus.tableSelected$.subscribe(() => this.handleTableChoice());
|
|
321
341
|
}
|
|
322
342
|
|
|
323
|
-
private getTableFromEventData(eventData:
|
|
324
|
-
|
|
325
|
-
throw new Error(`EventData does not contain a dataframe`, eventData);
|
|
326
|
-
|
|
327
|
-
return eventData.args.dataFrame as DG.DataFrame;
|
|
343
|
+
private getTableFromEventData(eventData: DG.EventData<DG.DataFrameArgs>): DG.DataFrame {
|
|
344
|
+
return eventData.args.dataFrame;
|
|
328
345
|
}
|
|
329
346
|
|
|
330
|
-
private handleTableAdded(eventData:
|
|
347
|
+
private handleTableAdded(eventData: DG.EventData<DG.DataFrameArgs>): void {
|
|
331
348
|
const table = this.getTableFromEventData(eventData);
|
|
332
349
|
|
|
333
350
|
if (this.availableTables.some((t: DG.DataFrame) => t.name === table.name))
|
|
@@ -462,6 +479,7 @@ export class EventBus {
|
|
|
462
479
|
return [columnLabel, columnSelection$];
|
|
463
480
|
}));
|
|
464
481
|
private _inputFormatSelection$ = new rxjs.BehaviorSubject<string>(DEFAULT_FORMATS.AXOLABS);
|
|
482
|
+
private _outputFormatSelection$ = new rxjs.BehaviorSubject<string>(NUCLEOTIDES_FORMAT);
|
|
465
483
|
|
|
466
484
|
private constructor() {}
|
|
467
485
|
|
|
@@ -490,7 +508,7 @@ export class EventBus {
|
|
|
490
508
|
getSelectedColumn(columnLabel: REQUIRED_COLUMN_LABEL): DG.Column<string> | null {
|
|
491
509
|
return this._columnSelection[columnLabel].getValue();
|
|
492
510
|
}
|
|
493
|
-
|
|
511
|
+
|
|
494
512
|
getSelectedInputFormat(): string {
|
|
495
513
|
return this._inputFormatSelection$.getValue();
|
|
496
514
|
}
|
|
@@ -498,4 +516,32 @@ export class EventBus {
|
|
|
498
516
|
selectInputFormat(format: string): void {
|
|
499
517
|
this._inputFormatSelection$.next(format);
|
|
500
518
|
}
|
|
519
|
+
|
|
520
|
+
selectOutputFormat(format: string): void {
|
|
521
|
+
this._outputFormatSelection$.next(format);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
getSelectedOutputFormat(): string {
|
|
525
|
+
return this._outputFormatSelection$.getValue();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export class OligoTranslatorUI extends IsolatedAppUIBase {
|
|
530
|
+
private readonly topPanel: HTMLElement[];
|
|
531
|
+
private readonly layout = new TranslatorAppLayout();
|
|
532
|
+
|
|
533
|
+
constructor() {
|
|
534
|
+
super(APP_NAME.TRANSLATOR);
|
|
535
|
+
|
|
536
|
+
const viewMonomerLibIcon = ui.iconFA('book', MonomerLibViewer.view, 'View monomer library');
|
|
537
|
+
this.topPanel = [
|
|
538
|
+
viewMonomerLibIcon,
|
|
539
|
+
];
|
|
540
|
+
this.view.setRibbonPanels([this.topPanel]);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
protected getContent(): Promise<HTMLDivElement> {
|
|
544
|
+
return this.layout.getHtmlElement();
|
|
545
|
+
};
|
|
501
546
|
}
|
|
547
|
+
|