@datagrok/sequence-translator 1.3.5 → 1.3.10
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/CHANGELOG.md +37 -0
- package/detectors.js +32 -0
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +10 -6
- package/src/apps/common/view/components/colored-input/style.css +3 -2
- package/src/apps/pattern/model/const.ts +8 -5
- package/src/apps/pattern/model/data-manager.ts +44 -16
- package/src/apps/pattern/model/event-bus.ts +2 -0
- package/src/apps/pattern/model/translator.ts +45 -8
- package/src/apps/pattern/model/types.ts +8 -6
- package/src/apps/pattern/view/components/bulk-convert/column-input.ts +4 -10
- package/src/apps/pattern/view/components/bulk-convert/table-input.ts +5 -8
- package/src/apps/pattern/view/components/edit-block-controls.ts +6 -15
- package/src/apps/pattern/view/components/load-block-controls.ts +11 -10
- package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +4 -5
- package/src/apps/pattern/view/components/right-section.ts +50 -1
- package/src/apps/pattern/view/components/strand-editor/header-controls.ts +2 -2
- package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +2 -2
- package/src/apps/pattern/view/components/terminal-modification-editor.ts +1 -1
- package/src/apps/pattern/view/components/translation-examples-block.ts +1 -1
- package/src/apps/pattern/view/ui.ts +1 -1
- package/src/apps/structure/view/ui.ts +5 -5
- package/src/apps/translator/view/ui.ts +15 -27
- package/src/package.ts +52 -11
- package/src/plugins/mermade.ts +1 -1
- package/src/polytool/pt-conversion.ts +2 -2
- package/src/polytool/pt-dialog.ts +99 -18
- package/src/polytool/pt-enumeration-chem.ts +105 -0
- package/src/polytool/pt-enumeration-helm.ts +22 -0
- package/src/polytool/pt-ui.ts +25 -0
- package/src/polytool/utils.ts +34 -3
- package/src/utils/context-menu.ts +57 -0
- package/src/utils/err-info.ts +13 -0
- package/webpack.config.js +2 -4
- package/src/polytool/pt-enumeration.ts +0 -141
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/sequence-translator",
|
|
3
3
|
"friendlyName": "Sequence Translator",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexey Choposky",
|
|
7
7
|
"email": "achopovsky@datagrok.ai"
|
|
@@ -22,23 +22,26 @@
|
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@datagrok-libraries/bio": "
|
|
25
|
+
"@datagrok-libraries/bio": "5.42.5",
|
|
26
26
|
"@datagrok-libraries/chem-meta": "^1.2.5",
|
|
27
27
|
"@datagrok-libraries/tutorials": "^1.3.12",
|
|
28
|
-
"@datagrok-libraries/utils": "^4.2.
|
|
28
|
+
"@datagrok-libraries/utils": "^4.2.13",
|
|
29
29
|
"@types/react": "^18.0.15",
|
|
30
30
|
"cash-dom": "^8.1.0",
|
|
31
|
-
"datagrok-api": "^1.
|
|
31
|
+
"datagrok-api": "^1.20.0",
|
|
32
32
|
"lodash": "^4.17.21",
|
|
33
33
|
"object-hash": "^3.0.0",
|
|
34
34
|
"openchemlib": "6.0.1",
|
|
35
35
|
"save-svg-as-png": "^1.4.17",
|
|
36
36
|
"ts-loader": "^9.3.1",
|
|
37
37
|
"typeahead-standalone": "4.14.1",
|
|
38
|
-
"typescript": "^5.4.2"
|
|
38
|
+
"typescript": "^5.4.2",
|
|
39
|
+
"wu": "latest"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@datagrok/
|
|
42
|
+
"@datagrok-libraries/helm-web-editor": "^1.1.6",
|
|
43
|
+
"@datagrok-libraries/js-draw-lite": "^0.0.4",
|
|
44
|
+
"@datagrok/bio": "^2.13.3",
|
|
42
45
|
"@datagrok/chem": "1.9.2",
|
|
43
46
|
"@types/jquery": "^3.5.14",
|
|
44
47
|
"@types/js-yaml": "^4.0.5",
|
|
@@ -46,6 +49,7 @@
|
|
|
46
49
|
"@types/node-fetch": "^2.6.2",
|
|
47
50
|
"@types/object-hash": "^3.0.6",
|
|
48
51
|
"@types/react": "^18.0.15",
|
|
52
|
+
"@types/wu": "latest",
|
|
49
53
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
50
54
|
"@typescript-eslint/parser": "^7.2.0",
|
|
51
55
|
"css-loader": "^6.7.3",
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
max-width: 100%;
|
|
20
20
|
padding: 2px;
|
|
21
21
|
line-height: normal;
|
|
22
|
-
font-family: 'Roboto
|
|
23
|
-
font-size:
|
|
22
|
+
font-family: 'Roboto', 'Roboto Local', sans-serif;
|
|
23
|
+
font-size: 13px;
|
|
24
|
+
padding-top: 6px !important;
|
|
24
25
|
color: transparent;
|
|
25
26
|
white-space: pre-wrap;
|
|
26
27
|
word-wrap: break-word;
|
|
@@ -57,6 +57,12 @@ export namespace LEGEND_SETTINGS_KEYS {
|
|
|
57
57
|
export namespace PATTERN_RECORD_KEYS {
|
|
58
58
|
export const PATTERN_CONFIG = 'patternConfig';
|
|
59
59
|
export const AUTHOR_ID = 'authorID';
|
|
60
|
+
export const DATE = 'date';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export namespace DATE_KEYS {
|
|
64
|
+
export const CREATE = 'create';
|
|
65
|
+
export const MODIFY = 'modify';
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
export const GRAPH_SETTINGS_KEY_LIST = [
|
|
@@ -72,11 +78,6 @@ export const LEGEND_SETTINGS_KEY_LIST = [
|
|
|
72
78
|
LEGEND_SETTINGS_KEYS.NUCLEOTIDES_WITH_NUMERIC_LABELS
|
|
73
79
|
];
|
|
74
80
|
|
|
75
|
-
export const PATTERN_RECORD_KEY_LIST = [
|
|
76
|
-
PATTERN_RECORD_KEYS.PATTERN_CONFIG,
|
|
77
|
-
PATTERN_RECORD_KEYS.AUTHOR_ID
|
|
78
|
-
];
|
|
79
|
-
|
|
80
81
|
export const EXAMPLE_PATTERN_CONFIG =
|
|
81
82
|
{
|
|
82
83
|
'patternConfig': {
|
|
@@ -119,3 +120,5 @@ export const EXAMPLE_PATTERN_CONFIG =
|
|
|
119
120
|
},
|
|
120
121
|
'authorID': ''
|
|
121
122
|
};
|
|
123
|
+
|
|
124
|
+
export const DEFAULT_DATE = '2024-01-01T18:00:00.000Z';
|
|
@@ -3,7 +3,8 @@ import * as grok from 'datagrok-api/grok';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
EXAMPLE_PATTERN_CONFIG,
|
|
6
|
-
GRAPH_SETTINGS_KEY_LIST as GKL, LEGEND_SETTINGS_KEYS as L,
|
|
6
|
+
GRAPH_SETTINGS_KEY_LIST as GKL, LEGEND_SETTINGS_KEYS as L,
|
|
7
|
+
OTHER_USERS, PATTERN_RECORD_KEYS as R, STORAGE_NAME, DATE_KEYS as D
|
|
7
8
|
} from './const';
|
|
8
9
|
import {
|
|
9
10
|
PatternConfigRecord, PatternConfiguration, PatternExistsError, PatternNameExistsError, RawPatternRecords
|
|
@@ -76,7 +77,7 @@ export class DataManager {
|
|
|
76
77
|
return patternHash;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
async
|
|
80
|
+
async getPatternRecordByHash(hash: string): Promise<PatternConfigRecord | null> {
|
|
80
81
|
if (hash === null || hash === '')
|
|
81
82
|
return null;
|
|
82
83
|
try {
|
|
@@ -91,7 +92,7 @@ export class DataManager {
|
|
|
91
92
|
async getPatternConfig(hash: string | null): Promise<PatternConfiguration | null> {
|
|
92
93
|
if (hash === '' || hash === null)
|
|
93
94
|
return null;
|
|
94
|
-
const record = await this.
|
|
95
|
+
const record = await this.getPatternRecordByHash(hash);
|
|
95
96
|
const config = record === null ? null : record[R.PATTERN_CONFIG] as PatternConfiguration;
|
|
96
97
|
return config;
|
|
97
98
|
}
|
|
@@ -144,13 +145,13 @@ export class DataManager {
|
|
|
144
145
|
return hash;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
private async getRecordFromPattern(
|
|
148
|
-
|
|
148
|
+
private async getRecordFromPattern(
|
|
149
|
+
patternConfig: PatternConfiguration
|
|
150
|
+
): Promise<PatternConfigRecord> {
|
|
151
|
+
return {
|
|
149
152
|
[R.PATTERN_CONFIG]: patternConfig,
|
|
150
153
|
[R.AUTHOR_ID]: await grok.dapi.users.current().then((u) => u.id),
|
|
151
154
|
};
|
|
152
|
-
const stringifiedRecord = JSON.stringify(record);
|
|
153
|
-
return stringifiedRecord;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
private getHash(patternConfig: PatternConfiguration): string {
|
|
@@ -173,13 +174,21 @@ export class DataManager {
|
|
|
173
174
|
const patternName = patternConfig[L.PATTERN_NAME];
|
|
174
175
|
this.validatePatternNameUniqueness(patternName);
|
|
175
176
|
|
|
176
|
-
const
|
|
177
|
+
const recordObj = await this.getRecordFromPattern(patternConfig);
|
|
178
|
+
const timestamp = new Date().toISOString();
|
|
179
|
+
recordObj[R.DATE] = {
|
|
180
|
+
[D.CREATE]: timestamp,
|
|
181
|
+
[D.MODIFY]: timestamp,
|
|
182
|
+
};
|
|
183
|
+
const record = JSON.stringify(recordObj);
|
|
177
184
|
await grok.dapi.userDataStorage.postValue(STORAGE_NAME, hash, record, false);
|
|
178
185
|
this.currentUserPatternNameToHash.set(patternName, hash);
|
|
179
186
|
|
|
180
187
|
eventBus.selectAuthor(this.getCurrentUserAuthorshipCategory());
|
|
181
188
|
|
|
182
189
|
eventBus.updatePatternList();
|
|
190
|
+
eventBus.requestPatternLoad(hash);
|
|
191
|
+
eventBus.updateUrlState(hash);
|
|
183
192
|
} catch (e) {
|
|
184
193
|
if (e instanceof PatternNameExistsError || e instanceof PatternExistsError)
|
|
185
194
|
throw e;
|
|
@@ -188,17 +197,33 @@ export class DataManager {
|
|
|
188
197
|
}
|
|
189
198
|
}
|
|
190
199
|
|
|
191
|
-
async
|
|
200
|
+
async overwriteExistingPatternInUserStorage(
|
|
192
201
|
eventBus: EventBus
|
|
193
202
|
): Promise<void> {
|
|
194
203
|
const patternConfig = eventBus.getPatternConfig();
|
|
195
|
-
const hash = this.getHash(patternConfig);
|
|
196
204
|
const patternName = patternConfig[L.PATTERN_NAME];
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
const oldHash = this.currentUserPatternNameToHash.get(patternName);
|
|
206
|
+
|
|
207
|
+
if (oldHash === undefined)
|
|
208
|
+
throw new Error(`Old hash is undefined`);
|
|
209
|
+
const newHash = this.getHash(patternConfig);
|
|
210
|
+
const newRecordObj = await this.getRecordFromPattern(patternConfig);
|
|
211
|
+
const timestamp = new Date().toISOString();
|
|
212
|
+
newRecordObj[R.DATE] = {
|
|
213
|
+
[D.MODIFY]: timestamp,
|
|
214
|
+
};
|
|
215
|
+
const oldPattern = await grok.dapi.userDataStorage.getValue(STORAGE_NAME, oldHash, false);
|
|
216
|
+
const oldPatternsRecord = JSON.parse(oldPattern) as PatternConfigRecord;
|
|
217
|
+
if (oldPatternsRecord[R.DATE] !== undefined && oldPatternsRecord[R.DATE][D.CREATE] != undefined)
|
|
218
|
+
newRecordObj[R.DATE][D.CREATE] = oldPatternsRecord[R.DATE][D.CREATE];
|
|
219
|
+
|
|
220
|
+
const newRecord = JSON.stringify(newRecordObj);
|
|
221
|
+
await grok.dapi.userDataStorage.postValue(STORAGE_NAME, newHash, newRecord, false);
|
|
222
|
+
await grok.dapi.userDataStorage.remove(STORAGE_NAME, oldHash, false);
|
|
223
|
+
|
|
224
|
+
this.currentUserPatternNameToHash.set(patternName, newHash);
|
|
225
|
+
eventBus.requestPatternLoad(newHash);
|
|
226
|
+
eventBus.updateUrlState(newHash);
|
|
202
227
|
}
|
|
203
228
|
|
|
204
229
|
async deletePattern(
|
|
@@ -274,7 +299,10 @@ export class DataManager {
|
|
|
274
299
|
this.currentUserPatternNameToHash.set(patternName, patternHash);
|
|
275
300
|
} else {
|
|
276
301
|
if (!userIdsToUserNames.has(authorID)) {
|
|
277
|
-
|
|
302
|
+
let userFriendlyName = '<UNKNOWN_USER>';
|
|
303
|
+
try {
|
|
304
|
+
userFriendlyName = (await grok.dapi.users.find(authorID)).friendlyName;
|
|
305
|
+
} catch (e) {}
|
|
278
306
|
userIdsToUserNames.set(authorID, userFriendlyName);
|
|
279
307
|
}
|
|
280
308
|
const fullPatternName = patternName + ` (created by ${userIdsToUserNames.get(authorID)})`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
3
|
import {STRAND, STRANDS, TERMINI, TERMINUS} from './const';
|
|
3
4
|
import {EventBus} from './event-bus';
|
|
4
|
-
import {ITranslationHelper} from '../../../types';
|
|
5
5
|
import {_package} from '../../../package';
|
|
6
6
|
import {JsonData} from '../../common/model/data-loader/json-loader';
|
|
7
7
|
|
|
@@ -11,13 +11,18 @@ export function bulkTranslate(eventBus: EventBus): void {
|
|
|
11
11
|
grok.shell.warning('Please select a table');
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const strandInputData = STRANDS.filter(
|
|
15
15
|
(strand) => !(strand === STRAND.ANTISENSE && !eventBus.isAntisenseStrandActive())
|
|
16
|
-
).map((strand) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
).map((strand) => {
|
|
17
|
+
return {
|
|
18
|
+
strand,
|
|
19
|
+
column: eventBus.getSelectedStrandColumn(strand)
|
|
20
|
+
};
|
|
21
|
+
})
|
|
22
|
+
.filter((el) => el.column);
|
|
23
|
+
|
|
24
|
+
if (strandInputData.length === 0) {
|
|
25
|
+
grok.shell.warning('Select a sense strand column');
|
|
21
26
|
return;
|
|
22
27
|
}
|
|
23
28
|
|
|
@@ -26,9 +31,41 @@ export function bulkTranslate(eventBus: EventBus): void {
|
|
|
26
31
|
|
|
27
32
|
const idColumn = df.getCol(idColumnName);
|
|
28
33
|
|
|
29
|
-
const
|
|
34
|
+
const strandColData = strandInputData.map((el) => {
|
|
35
|
+
return {strand: el.strand, column: df.getCol(el.column!)};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!areStrandColsValid(strandColData, eventBus)) {
|
|
39
|
+
grok.shell.warning(`Some strands in the table input do not match pattern length`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
strandColData.forEach((strandColData) => {
|
|
44
|
+
const inputCol = strandColData.column;
|
|
45
|
+
const strand = strandColData.strand;
|
|
46
|
+
const modifications = eventBus.getNucleotideSequences()[strand];
|
|
47
|
+
const terminals = eventBus.getTerminalModifications()[strand];
|
|
48
|
+
const ptoFlags = eventBus.getPhosphorothioateLinkageFlags()[strand];
|
|
49
|
+
|
|
50
|
+
const outputColName = `${eventBus.getPatternName()}(${inputCol.name})`;
|
|
51
|
+
df.columns.addNewString(outputColName).init((i) => {
|
|
52
|
+
const input = inputCol.get(i);
|
|
53
|
+
return applyPatternToRawSequence(
|
|
54
|
+
input, modifications, ptoFlags, terminals
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
30
58
|
}
|
|
31
59
|
|
|
60
|
+
function areStrandColsValid(strandColumns: {strand: STRAND, column: DG.Column<string>}[], eventBus: EventBus) {
|
|
61
|
+
const nucleotides = eventBus.getNucleotideSequences();
|
|
62
|
+
return strandColumns.every((el) => {
|
|
63
|
+
const patternLength = nucleotides[el.strand].length;
|
|
64
|
+
return el.column.toList().every((input) => input.length === patternLength);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
32
69
|
export function applyPatternToRawSequence(
|
|
33
70
|
rawNucleotideSequence: string,
|
|
34
71
|
modifications: string[],
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
-
import * as grok from 'datagrok-api/grok';
|
|
3
|
-
import * as ui from 'datagrok-api/ui';
|
|
4
|
-
import * as DG from 'datagrok-api/dg';
|
|
5
|
-
|
|
6
1
|
import {
|
|
7
2
|
TERMINI, STRANDS,
|
|
8
|
-
GRAPH_SETTINGS_KEYS as G, LEGEND_SETTINGS_KEYS as L, PATTERN_RECORD_KEYS as R
|
|
3
|
+
GRAPH_SETTINGS_KEYS as G, LEGEND_SETTINGS_KEYS as L, PATTERN_RECORD_KEYS as R,
|
|
4
|
+
DATE_KEYS as D
|
|
9
5
|
} from './const';
|
|
10
6
|
|
|
11
7
|
export type StrandType = typeof STRANDS[number];
|
|
@@ -30,9 +26,15 @@ export type PatternLegendSettings = {
|
|
|
30
26
|
|
|
31
27
|
export type PatternConfiguration = PatternGraphSettings & PatternLegendSettings;
|
|
32
28
|
|
|
29
|
+
type DateRecord = {
|
|
30
|
+
[D.CREATE]?: string,
|
|
31
|
+
[D.MODIFY]: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
33
34
|
export type PatternConfigRecord = {
|
|
34
35
|
[R.PATTERN_CONFIG]: PatternConfiguration,
|
|
35
36
|
[R.AUTHOR_ID]: string,
|
|
37
|
+
[R.DATE]?: DateRecord
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export class PatternNameExistsError extends Error {
|
|
@@ -53,11 +53,8 @@ export class ColumnInputManager {
|
|
|
53
53
|
this.selectedTable.columns.names().sort((a, b) => a.localeCompare(b)) :
|
|
54
54
|
[];
|
|
55
55
|
const strandColumnInput = Object.fromEntries(STRANDS.map((strand) => {
|
|
56
|
-
const input = ui.
|
|
57
|
-
|
|
58
|
-
columns[0],
|
|
59
|
-
columns,
|
|
60
|
-
(colName: string) => this.eventBus.selectStrandColumn(strand, colName)
|
|
56
|
+
const input = ui.input.choice(`${STRAND_LABEL[strand]} column`, {value: columns[0], items: columns,
|
|
57
|
+
onValueChanged: (input) => this.eventBus.selectStrandColumn(strand, input.value)}
|
|
61
58
|
);
|
|
62
59
|
this.eventBus.selectStrandColumn(strand, columns[0]);
|
|
63
60
|
return [strand, input.root];
|
|
@@ -67,11 +64,8 @@ export class ColumnInputManager {
|
|
|
67
64
|
|
|
68
65
|
private createIdColumnInput(): HTMLElement {
|
|
69
66
|
const columns = this.selectedTable ? this.selectedTable.columns.names() : [];
|
|
70
|
-
const idColumnInput = ui.
|
|
71
|
-
|
|
72
|
-
columns[0],
|
|
73
|
-
columns,
|
|
74
|
-
(colName: string) => this.eventBus.selectIdColumn(colName)
|
|
67
|
+
const idColumnInput = ui.input.choice('ID column', {value: columns[0], items: columns,
|
|
68
|
+
onValueChanged: (input) => this.eventBus.selectIdColumn(input.value)}
|
|
75
69
|
);
|
|
76
70
|
this.eventBus.selectIdColumn(columns[0]);
|
|
77
71
|
return idColumnInput.root;
|
|
@@ -63,16 +63,13 @@ export class TableInputManager {
|
|
|
63
63
|
private createTableInput(): DG.InputBase<DG.DataFrame | null> {
|
|
64
64
|
const currentlySelectedTable = this.eventBus.getTableSelection();
|
|
65
65
|
|
|
66
|
-
const tableInput = ui.
|
|
67
|
-
|
|
68
|
-
currentlySelectedTable,
|
|
69
|
-
this.availableTables,
|
|
70
|
-
(table: DG.DataFrame) => {
|
|
66
|
+
const tableInput = ui.input.table('Tables', {value: currentlySelectedTable!, items: this.availableTables,
|
|
67
|
+
onValueChanged: (input) => {
|
|
71
68
|
// WARNING: non-null check necessary to prevent resetting columns to
|
|
72
69
|
// null upon handling onTableAdded
|
|
73
|
-
if (
|
|
74
|
-
this.eventBus.selectTable(
|
|
75
|
-
});
|
|
70
|
+
if (input.value !== null)
|
|
71
|
+
this.eventBus.selectTable(input.value);
|
|
72
|
+
}});
|
|
76
73
|
return tableInput;
|
|
77
74
|
}
|
|
78
75
|
|
|
@@ -74,9 +74,9 @@ export class PatternEditControlsManager {
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
private createAntisenseStrandToggle(): HTMLElement {
|
|
77
|
-
const toggleAntisenseStrand = ui.
|
|
77
|
+
const toggleAntisenseStrand = ui.input.toggle(
|
|
78
78
|
`${STRAND_LABEL[STRAND.ANTISENSE]} strand`,
|
|
79
|
-
this.eventBus.isAntisenseStrandActive()
|
|
79
|
+
{value: this.eventBus.isAntisenseStrandActive()}
|
|
80
80
|
);
|
|
81
81
|
|
|
82
82
|
toggleAntisenseStrand.onInput(
|
|
@@ -96,10 +96,7 @@ export class PatternEditControlsManager {
|
|
|
96
96
|
const createStrandLengthInput = (strand: StrandType) => {
|
|
97
97
|
const sequenceLength = this.eventBus.getNucleotideSequences()[strand].length;
|
|
98
98
|
|
|
99
|
-
const input = ui.
|
|
100
|
-
`${STRAND_LABEL[strand]} length`,
|
|
101
|
-
sequenceLength
|
|
102
|
-
);
|
|
99
|
+
const input = ui.input.int(`${STRAND_LABEL[strand]} length`, {value: sequenceLength});
|
|
103
100
|
input.onInput(() => updateStrandLengthInputs(strand, input));
|
|
104
101
|
|
|
105
102
|
this.eventBus.nucleotideSequencesChanged$.subscribe(() => {
|
|
@@ -142,7 +139,7 @@ export class PatternEditControlsManager {
|
|
|
142
139
|
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
143
140
|
const defaultNucleotideBase = this.dataManager.fetchDefaultNucleobase();
|
|
144
141
|
|
|
145
|
-
const sequenceBaseInput = ui.
|
|
142
|
+
const sequenceBaseInput = ui.input.choice('Sequence basis', {value: defaultNucleotideBase, items: availableNucleoBases});
|
|
146
143
|
|
|
147
144
|
sequenceBaseInput.onInput(() => this.eventBus.replaceSequenceBase(sequenceBaseInput.value!));
|
|
148
145
|
|
|
@@ -155,10 +152,7 @@ export class PatternEditControlsManager {
|
|
|
155
152
|
}
|
|
156
153
|
|
|
157
154
|
private createPatternCommentInput(): StringInput {
|
|
158
|
-
const patternCommentInput = ui.
|
|
159
|
-
'Comment',
|
|
160
|
-
this.eventBus.getComment()
|
|
161
|
-
);
|
|
155
|
+
const patternCommentInput = ui.input.textArea('Comment', {value: this.eventBus.getComment()});
|
|
162
156
|
|
|
163
157
|
$(patternCommentInput.root).addClass('st-pattern-text-input');
|
|
164
158
|
|
|
@@ -174,10 +168,7 @@ export class PatternEditControlsManager {
|
|
|
174
168
|
}
|
|
175
169
|
|
|
176
170
|
private createPatternNameInputBlock(): HTMLElement {
|
|
177
|
-
const patternNameInput = ui.
|
|
178
|
-
'Pattern name',
|
|
179
|
-
this.eventBus.getPatternName()
|
|
180
|
-
);
|
|
171
|
+
const patternNameInput = ui.input.textArea('Pattern name', {value: this.eventBus.getPatternName()});
|
|
181
172
|
|
|
182
173
|
$(patternNameInput.root).addClass('st-pattern-text-input');
|
|
183
174
|
|
|
@@ -93,15 +93,16 @@ export class PatternLoadControlsManager {
|
|
|
93
93
|
if (this.dataManager.getOtherUsersPatternNames().length > 0)
|
|
94
94
|
possibleValues.push(this.dataManager.getOtherUsersAuthorshipCategory());
|
|
95
95
|
|
|
96
|
-
const authorChoiceInput = ui.
|
|
97
|
-
'Author',
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
96
|
+
const authorChoiceInput = ui.input.choice(
|
|
97
|
+
'Author', {value: this.eventBus.getSelectedAuthor(), items: possibleValues});
|
|
98
|
+
|
|
99
|
+
authorChoiceInput.onInput(() => {
|
|
100
|
+
this.authorSelectedByUser = true;
|
|
101
|
+
if (authorChoiceInput.value === null)
|
|
102
|
+
throw new Error('author choice must be non-null');
|
|
103
|
+
this.eventBus.selectAuthor(authorChoiceInput.value);
|
|
104
|
+
});
|
|
105
|
+
|
|
105
106
|
this.setAuthorChoiceInputStyle(authorChoiceInput);
|
|
106
107
|
authorChoiceInput.setTooltip('Select pattern author');
|
|
107
108
|
|
|
@@ -132,7 +133,7 @@ export class PatternLoadControlsManager {
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
const defaultValue = this.getPatternName(patternList);
|
|
135
|
-
const choiceInput = ui.
|
|
136
|
+
const choiceInput = ui.input.choice('Pattern', {value: defaultValue, items: patternList});
|
|
136
137
|
choiceInput.setTooltip('Select pattern to load');
|
|
137
138
|
|
|
138
139
|
$(choiceInput.input).css({
|
|
@@ -44,11 +44,10 @@ export class NumericLabelVisibilityControls {
|
|
|
44
44
|
|
|
45
45
|
private createSingleInput(nucleotide: string): BooleanInput {
|
|
46
46
|
const initialValue = this.eventBus.getModificationsWithNumericLabels().includes(nucleotide);
|
|
47
|
-
const input = ui.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
);
|
|
47
|
+
const input = ui.input.bool(nucleotide, {
|
|
48
|
+
value: initialValue,
|
|
49
|
+
onValueChanged: (input) => this.handleNumericLabelToggle(nucleotide, input.value)
|
|
50
|
+
});
|
|
52
51
|
$(input.root).css('padding-right', '20px');
|
|
53
52
|
|
|
54
53
|
input.setTooltip(`Show numeric labels for ${nucleotide}`);
|
|
@@ -7,6 +7,7 @@ import {PatternExistsError, PatternNameExistsError} from '../../model/types';
|
|
|
7
7
|
import {SvgDisplayManager} from '../svg-utils/svg-display-manager';
|
|
8
8
|
import {NumericLabelVisibilityControls} from './numeric-label-visibility-controls';
|
|
9
9
|
import {TranslationExamplesBlock} from './translation-examples-block';
|
|
10
|
+
import {DEFAULT_DATE, PATTERN_RECORD_KEYS as P, DATE_KEYS as D, LEGEND_SETTINGS_KEYS as L} from '../../model/const';
|
|
10
11
|
|
|
11
12
|
export class PatternAppRightSection {
|
|
12
13
|
private svgDisplay: HTMLDivElement;
|
|
@@ -44,6 +45,7 @@ export class PatternAppRightSection {
|
|
|
44
45
|
this.createSavePatternButton(),
|
|
45
46
|
this.createDownloadPngButton(),
|
|
46
47
|
this.createShareLinkButton(),
|
|
48
|
+
this.createInfoButton(),
|
|
47
49
|
], {style: {gap: '12px', marginTop: '12px'}});
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -55,6 +57,53 @@ export class PatternAppRightSection {
|
|
|
55
57
|
return svgDownloadButton;
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
private createInfoButton(): HTMLButtonElement {
|
|
61
|
+
const shareLinkButton = ui.button(
|
|
62
|
+
ui.iconFA('info-circle'),
|
|
63
|
+
() => this.openInfoDialog()
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
this.eventBus.patternHasUnsavedChanges$.subscribe((hasUnsavedChanges: boolean) => {
|
|
67
|
+
shareLinkButton.disabled = hasUnsavedChanges;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
ui.tooltip.bind(shareLinkButton, 'View pattern metadata');
|
|
71
|
+
return shareLinkButton;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async openInfoDialog() {
|
|
75
|
+
let author = this.dataManager.getCurrentUserName();
|
|
76
|
+
let patternName = this.dataManager.getDefaultPatternName();
|
|
77
|
+
let createDate = DEFAULT_DATE;
|
|
78
|
+
let modifyDate = DEFAULT_DATE;
|
|
79
|
+
const hash = new URLSearchParams(window.location.search).get('pattern');
|
|
80
|
+
if (hash !== null) {
|
|
81
|
+
const record = await this.dataManager.getPatternRecordByHash(hash);
|
|
82
|
+
if (record !== null) {
|
|
83
|
+
const authorID = record[P.AUTHOR_ID];
|
|
84
|
+
const userFriendlyName = (await grok.dapi.users.find(authorID)).friendlyName;
|
|
85
|
+
author = userFriendlyName;
|
|
86
|
+
patternName = record[P.PATTERN_CONFIG][L.PATTERN_NAME];
|
|
87
|
+
if (record[P.DATE] !== undefined) {
|
|
88
|
+
const create = record[P.DATE][D.CREATE];
|
|
89
|
+
if (create !== undefined)
|
|
90
|
+
createDate = create;
|
|
91
|
+
const modify = record[P.DATE][D.MODIFY];
|
|
92
|
+
if (modify !== undefined)
|
|
93
|
+
modifyDate = modify;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const message = ui.divV([
|
|
99
|
+
ui.divText(`Author: ${author}`),
|
|
100
|
+
ui.divText(`Pattern Name: ${patternName}`),
|
|
101
|
+
ui.divText(`Created: ${new Date(createDate).toLocaleString()}`),
|
|
102
|
+
ui.divText(`Modified: ${new Date(modifyDate).toLocaleString()}`),
|
|
103
|
+
]);
|
|
104
|
+
grok.shell.info(message);
|
|
105
|
+
}
|
|
106
|
+
|
|
58
107
|
private createShareLinkButton(): HTMLButtonElement {
|
|
59
108
|
const shareLinkButton = ui.button(
|
|
60
109
|
ui.iconFA('link'),
|
|
@@ -135,7 +184,7 @@ class OverwritePatternDialog {
|
|
|
135
184
|
|
|
136
185
|
private processOverwriteNamesakePattern(): void {
|
|
137
186
|
const patternName = this.eventBus.getPatternName();
|
|
138
|
-
this.dataManager.
|
|
187
|
+
this.dataManager.overwriteExistingPatternInUserStorage(this.eventBus)
|
|
139
188
|
.then(() => {
|
|
140
189
|
grok.shell.info(`Pattern ${patternName} overwritten`);
|
|
141
190
|
})
|
|
@@ -43,7 +43,7 @@ export class HeaderControls {
|
|
|
43
43
|
private createAllPtoActivationInput(): BooleanInput {
|
|
44
44
|
const flags = this.initialPatternConfig.phosphorothioateLinkageFlags;
|
|
45
45
|
const initialValue = this.areAllPtoLinkagesSet(flags);
|
|
46
|
-
const allPtoActivationInput = ui.
|
|
46
|
+
const allPtoActivationInput = ui.input.bool('All PTO', {value: initialValue});
|
|
47
47
|
|
|
48
48
|
allPtoActivationInput.onInput(() => {
|
|
49
49
|
const value = allPtoActivationInput.value!;
|
|
@@ -79,7 +79,7 @@ export class HeaderControls {
|
|
|
79
79
|
if (!this.eventBus.isAntisenseStrandActive() && strand === STRAND.ANTISENSE)
|
|
80
80
|
return;
|
|
81
81
|
const initialValue = this.isFirstPtoActive(strand);
|
|
82
|
-
const firstPtoInput = ui.
|
|
82
|
+
const firstPtoInput = ui.input.bool(`First ${strand} PTO`, {value: initialValue});
|
|
83
83
|
|
|
84
84
|
firstPtoInput.onInput(() => {
|
|
85
85
|
const value = firstPtoInput.value!;
|
|
@@ -78,7 +78,7 @@ export class StrandControls {
|
|
|
78
78
|
);
|
|
79
79
|
const nucleotides = this.eventBus.getNucleotideSequences()[strand];
|
|
80
80
|
const choiceInputs = nucleotides.map((nucleotide, index) => {
|
|
81
|
-
const input = ui.
|
|
81
|
+
const input = ui.input.choice<string>('', {value: nucleotide, items: nucleotideBaseChoices});
|
|
82
82
|
input.onInput(() => {
|
|
83
83
|
const newValue = input.value!;
|
|
84
84
|
this.eventBus.setNucleotide(strand, index, newValue);
|
|
@@ -92,7 +92,7 @@ export class StrandControls {
|
|
|
92
92
|
private createPTOFlagInputs(strand: StrandType): BooleanInput[] {
|
|
93
93
|
const ptoLinkageFlags = this.eventBus.getPhosphorothioateLinkageFlags()[strand].slice(1);
|
|
94
94
|
const ptoLinkageInputs = ptoLinkageFlags.map((flag, index) => {
|
|
95
|
-
const input = ui.
|
|
95
|
+
const input = ui.input.bool('', {value: flag});
|
|
96
96
|
input.onInput(() => {
|
|
97
97
|
const newValue = input.value!;
|
|
98
98
|
this.eventBus.setPhosphorothioateLinkageFlag(strand, index + 1, newValue);
|
|
@@ -106,7 +106,7 @@ class StrandTerminalModificationControls {
|
|
|
106
106
|
|
|
107
107
|
private createInputForTerminus(strand: StrandType, terminus: TERMINUS): StringInput {
|
|
108
108
|
const initialValue = this.eventBus.getTerminalModifications()[strand][terminus];
|
|
109
|
-
const input = ui.
|
|
109
|
+
const input = ui.input.textArea(terminus, {value: initialValue});
|
|
110
110
|
this.applyStylingToInput(input);
|
|
111
111
|
|
|
112
112
|
input.onInput(() => {
|
|
@@ -55,7 +55,7 @@ async function getInitialPatternRecord(
|
|
|
55
55
|
return dataManager.getDefaultPatternRecord();
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
let initialPatternRecord = await dataManager.
|
|
58
|
+
let initialPatternRecord = await dataManager.getPatternRecordByHash(patternHash);
|
|
59
59
|
if (!initialPatternRecord) {
|
|
60
60
|
urlRouter.clearPatternURL();
|
|
61
61
|
initialPatternRecord = dataManager.getDefaultPatternRecord();
|
|
@@ -33,11 +33,11 @@ class StructureAppLayout {
|
|
|
33
33
|
this.onInvalidInput = new rxjs.Subject<string>();
|
|
34
34
|
this.inputBase = Object.fromEntries(
|
|
35
35
|
STRANDS.map(
|
|
36
|
-
(key) => [key, ui.
|
|
36
|
+
(key) => [key, ui.input.textArea('', {value: '', onValueChanged: () => { this.onInput.next(); }})]
|
|
37
37
|
)
|
|
38
38
|
);
|
|
39
|
-
this.useChiralInput = ui.
|
|
40
|
-
this.saveAllStrandsInput = ui.
|
|
39
|
+
this.useChiralInput = ui.input.bool('Use chiral', {value: true});
|
|
40
|
+
this.saveAllStrandsInput = ui.input.bool('Save as one entity', {value: true});
|
|
41
41
|
ui.tooltip.bind(this.saveAllStrandsInput.root, 'Save SDF with all strands in one molfile');
|
|
42
42
|
this.directionInversion = Object.fromEntries(
|
|
43
43
|
STRANDS.map((key) => [key, false])
|
|
@@ -102,8 +102,8 @@ class StructureAppLayout {
|
|
|
102
102
|
STRANDS.map(
|
|
103
103
|
(key, idx) => {
|
|
104
104
|
const selected = (idx === 0) ? DIRECTION.STRAIGHT : DIRECTION.INVERSE;
|
|
105
|
-
return [key, ui.
|
|
106
|
-
`${key.toUpperCase()} direction`, selected, [DIRECTION.STRAIGHT, DIRECTION.INVERSE]
|
|
105
|
+
return [key, ui.input.choice(
|
|
106
|
+
`${key.toUpperCase()} direction`, {value: selected, items: [DIRECTION.STRAIGHT, DIRECTION.INVERSE]}
|
|
107
107
|
)];
|
|
108
108
|
}
|
|
109
109
|
)
|