@datagrok/sequence-translator 1.10.6 → 1.10.8
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/dist/455.js +1 -1
- package/dist/455.js.map +1 -1
- 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 +2 -3
- package/src/package-api.ts +4 -0
- package/src/package.g.ts +6 -0
- package/src/package.ts +9 -1
- package/src/polytool/const.ts +7 -0
- package/src/polytool/pt-enumerate-seq-dialog.ts +168 -27
- package/src/polytool/pt-enumeration-helm.ts +37 -0
- package/src/polytool/pt-monomer-selection-dialog.ts +237 -0
- package/src/polytool/pt-placeholders-breadth-input.ts +52 -0
- package/src/polytool/pt-placeholders-input.ts +47 -0
- package/src/polytool/types.ts +2 -0
- package/src/tests/polytool-enumerate-tests.ts +60 -0
- package/src/utils/cyclized.ts +8 -2
- package/test-console-output-1.log +105 -237
- package/test-record-1.mp4 +0 -0
- package/webpack.config.js +22 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import {HelmType, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
6
|
+
import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
7
|
+
import {polymerTypeToHelmType} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
|
|
8
|
+
|
|
9
|
+
import {parseMonomerSymbolList} from './pt-placeholders-input';
|
|
10
|
+
|
|
11
|
+
const MAX_SUGGESTIONS = 20;
|
|
12
|
+
|
|
13
|
+
/** Shows a dialog for selecting monomers with autocomplete and tag-based display.
|
|
14
|
+
* @returns comma-separated monomer symbols, or null if cancelled */
|
|
15
|
+
export async function showMonomerSelectionDialog(
|
|
16
|
+
monomerLib: IMonomerLib, polymerType: PolymerType, presetMonomers?: string[],
|
|
17
|
+
): Promise<string[] | null> {
|
|
18
|
+
return new Promise<string[] | null>((resolve) => {
|
|
19
|
+
const helmType: HelmType = polymerTypeToHelmType(polymerType);
|
|
20
|
+
const allSymbols = monomerLib.getMonomerSymbolsByType(polymerType);
|
|
21
|
+
|
|
22
|
+
const selectedMonomers: string[] = presetMonomers ? [...presetMonomers] : [];
|
|
23
|
+
|
|
24
|
+
const tagsHost = ui.div([], {style: {display: 'flex', flexWrap: 'wrap', gap: '4px', marginTop: '8px', maxWidth: '400px'}});
|
|
25
|
+
const input = ui.input.string('Monomers', {value: ''});
|
|
26
|
+
const inputEl = input.input as HTMLInputElement;
|
|
27
|
+
inputEl.setAttribute('autocomplete', 'off');
|
|
28
|
+
inputEl.placeholder = 'Type to search...';
|
|
29
|
+
|
|
30
|
+
let currentMenu: DG.Menu | null = null;
|
|
31
|
+
let menuItems: HTMLElement[] = [];
|
|
32
|
+
let highlightedIndex = -1;
|
|
33
|
+
|
|
34
|
+
function renderTags(): void {
|
|
35
|
+
tagsHost.innerHTML = '';
|
|
36
|
+
for (const symbol of selectedMonomers) {
|
|
37
|
+
const removeBtn = ui.iconFA('times', () => {
|
|
38
|
+
const idx = selectedMonomers.indexOf(symbol);
|
|
39
|
+
if (idx >= 0) {
|
|
40
|
+
selectedMonomers.splice(idx, 1);
|
|
41
|
+
renderTags();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
removeBtn.style.marginLeft = '4px';
|
|
45
|
+
removeBtn.style.cursor = 'pointer';
|
|
46
|
+
|
|
47
|
+
const tag = ui.div([ui.span([symbol]), removeBtn], {
|
|
48
|
+
style: {
|
|
49
|
+
display: 'inline-flex', alignItems: 'center',
|
|
50
|
+
padding: '2px 6px', borderRadius: '4px',
|
|
51
|
+
backgroundColor: 'var(--grey-2)', border: '1px solid var(--grey-3)',
|
|
52
|
+
fontSize: '12px', cursor: 'default',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
// Tooltip on hover
|
|
56
|
+
tag.addEventListener('mouseenter', (e) => {
|
|
57
|
+
const tooltip = monomerLib.getTooltip(helmType, symbol);
|
|
58
|
+
ui.tooltip.show(tooltip, tag.getBoundingClientRect().left, tag.getBoundingClientRect().bottom + 16);
|
|
59
|
+
});
|
|
60
|
+
tag.addEventListener('mouseleave', () => { ui.tooltip.hide(); });
|
|
61
|
+
|
|
62
|
+
tagsHost.appendChild(tag);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function addMonomer(symbol: string): void {
|
|
67
|
+
if (!selectedMonomers.includes(symbol)) {
|
|
68
|
+
selectedMonomers.push(symbol);
|
|
69
|
+
renderTags();
|
|
70
|
+
}
|
|
71
|
+
inputEl.value = '';
|
|
72
|
+
hideMenu();
|
|
73
|
+
inputEl.focus();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hideMenu(): void {
|
|
77
|
+
if (currentMenu) {
|
|
78
|
+
currentMenu.hide();
|
|
79
|
+
currentMenu = null;
|
|
80
|
+
}
|
|
81
|
+
menuItems = [];
|
|
82
|
+
highlightedIndex = -1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getSuggestions(query: string): {symbol: string, monomer: Monomer | null}[] {
|
|
86
|
+
const q = query.toLowerCase();
|
|
87
|
+
const results: {symbol: string, monomer: Monomer | null, rank: number}[] = [];
|
|
88
|
+
|
|
89
|
+
for (const symbol of allSymbols) {
|
|
90
|
+
if (selectedMonomers.includes(symbol))
|
|
91
|
+
continue;
|
|
92
|
+
const monomer = monomerLib.getMonomer(polymerType, symbol);
|
|
93
|
+
const symLower = symbol.toLowerCase();
|
|
94
|
+
const nameLower = monomer?.name?.toLowerCase() ?? '';
|
|
95
|
+
|
|
96
|
+
if (symLower.startsWith(q))
|
|
97
|
+
results.push({symbol, monomer, rank: 0});
|
|
98
|
+
else if (symLower.includes(q))
|
|
99
|
+
results.push({symbol, monomer, rank: 1});
|
|
100
|
+
else if (nameLower.includes(q))
|
|
101
|
+
results.push({symbol, monomer, rank: 2});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
results.sort((a, b) => a.rank - b.rank || a.symbol.localeCompare(b.symbol));
|
|
105
|
+
return results.slice(0, MAX_SUGGESTIONS);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function showSuggestions(): void {
|
|
109
|
+
hideMenu();
|
|
110
|
+
const query = inputEl.value.trim();
|
|
111
|
+
if (!query)
|
|
112
|
+
return;
|
|
113
|
+
|
|
114
|
+
const suggestions = getSuggestions(query);
|
|
115
|
+
if (suggestions.length === 0)
|
|
116
|
+
return;
|
|
117
|
+
|
|
118
|
+
currentMenu = DG.Menu.popup();
|
|
119
|
+
const maxElement = suggestions.reduce((max, s) => {
|
|
120
|
+
const label = s.monomer?.name ? `${s.symbol} - ${s.monomer.name}` : s.symbol;
|
|
121
|
+
if (max.length < label.length)
|
|
122
|
+
return label;
|
|
123
|
+
return max;
|
|
124
|
+
}, '');
|
|
125
|
+
currentMenu.item(maxElement, () => {}); // Dummy item to set menu width
|
|
126
|
+
|
|
127
|
+
const causedBy = new MouseEvent('mousemove', {clientX: inputEl.getBoundingClientRect().left, clientY: inputEl.getBoundingClientRect().bottom});
|
|
128
|
+
currentMenu.show({causedBy: causedBy,
|
|
129
|
+
y: inputEl.offsetHeight + inputEl.offsetTop, x: inputEl.offsetLeft});
|
|
130
|
+
|
|
131
|
+
// collect menu items for keyboard navigation
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
currentMenu?.clear();
|
|
134
|
+
for (const s of suggestions) {
|
|
135
|
+
const label = s.monomer?.name ? `${s.symbol} - ${s.monomer.name}` : s.symbol;
|
|
136
|
+
currentMenu?.item(label, () => { addMonomer(s.symbol); });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const menuRoot = document.querySelector('.d4-menu-popup:last-of-type');
|
|
140
|
+
if (menuRoot)
|
|
141
|
+
menuItems = Array.from(menuRoot.querySelectorAll('.d4-menu-item')) as HTMLElement[];
|
|
142
|
+
|
|
143
|
+
highlightedIndex = -1;
|
|
144
|
+
}, 0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function updateHighlight(newIndex: number): void {
|
|
148
|
+
if (menuItems.length === 0)
|
|
149
|
+
return;
|
|
150
|
+
if (highlightedIndex >= 0 && highlightedIndex < menuItems.length)
|
|
151
|
+
menuItems[highlightedIndex].classList.remove('d4-menu-item-hover');
|
|
152
|
+
highlightedIndex = newIndex;
|
|
153
|
+
if (highlightedIndex >= 0 && highlightedIndex < menuItems.length) {
|
|
154
|
+
menuItems[highlightedIndex].classList.add('d4-menu-item-hover');
|
|
155
|
+
menuItems[highlightedIndex].scrollIntoView({block: 'nearest'});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
inputEl.addEventListener('input', () => { showSuggestions(); });
|
|
160
|
+
|
|
161
|
+
// Handle pasting multiple monomers (space / comma / newline separated)
|
|
162
|
+
inputEl.addEventListener('paste', (e: ClipboardEvent) => {
|
|
163
|
+
const pastedText = e.clipboardData?.getData('text');
|
|
164
|
+
if (!pastedText)
|
|
165
|
+
return;
|
|
166
|
+
|
|
167
|
+
// Split on newlines first, then parse each line (handles parenthesized symbols like hArg(Et,Et))
|
|
168
|
+
const lines = pastedText.split(/[\n\r]+/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
169
|
+
const candidates: string[] = [];
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
const parsed = parseMonomerSymbolList(line);
|
|
172
|
+
// If parseMonomerSymbolList returned a single token but the line has spaces and no commas,
|
|
173
|
+
// split on spaces as well (e.g. "K P F" -> ["K", "P", "F"])
|
|
174
|
+
if (parsed.length === 1 && line.includes(' ') && !line.includes(','))
|
|
175
|
+
candidates.push(...line.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
176
|
+
else
|
|
177
|
+
candidates.push(...parsed);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (candidates.length <= 1)
|
|
181
|
+
return; // Single symbol: let default paste + autocomplete handle it
|
|
182
|
+
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
|
|
185
|
+
for (const candidate of candidates) {
|
|
186
|
+
if (allSymbols.includes(candidate) && !selectedMonomers.includes(candidate))
|
|
187
|
+
selectedMonomers.push(candidate);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
renderTags();
|
|
191
|
+
inputEl.value = '';
|
|
192
|
+
hideMenu();
|
|
193
|
+
inputEl.focus();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
inputEl.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
197
|
+
if (e.key === 'ArrowDown') {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
if (menuItems.length > 0) {
|
|
200
|
+
const next = (highlightedIndex + 1) % menuItems.length;
|
|
201
|
+
updateHighlight(next);
|
|
202
|
+
}
|
|
203
|
+
} else if (e.key === 'ArrowUp') {
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
if (menuItems.length > 0) {
|
|
206
|
+
const prev = (highlightedIndex - 1 + menuItems.length) % menuItems.length;
|
|
207
|
+
updateHighlight(prev);
|
|
208
|
+
}
|
|
209
|
+
} else if (e.key === 'Enter') {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
e.stopPropagation();
|
|
212
|
+
if (highlightedIndex >= 0 && highlightedIndex < menuItems.length) {
|
|
213
|
+
menuItems[highlightedIndex].click();
|
|
214
|
+
} else {
|
|
215
|
+
// If input exactly matches a symbol, add it directly
|
|
216
|
+
const val = inputEl.value.trim();
|
|
217
|
+
if (val && allSymbols.includes(val))
|
|
218
|
+
addMonomer(val);
|
|
219
|
+
}
|
|
220
|
+
} else if (e.key === 'Escape') {
|
|
221
|
+
hideMenu();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
renderTags();
|
|
226
|
+
|
|
227
|
+
const dlg = ui.dialog({title: 'Select Monomers', showFooter: true})
|
|
228
|
+
.add(ui.div([input.root, tagsHost], {style: {minWidth: '350px', minHeight: '200px'}}))
|
|
229
|
+
.onOK(() => { resolve(selectedMonomers); })
|
|
230
|
+
.onCancel(() => { resolve(null); })
|
|
231
|
+
.show({resizable: true});
|
|
232
|
+
// dlg.root.addEventListener('close', () => { hideMenu(); });
|
|
233
|
+
|
|
234
|
+
inputEl.focus();
|
|
235
|
+
setTimeout(() => { showSuggestions(); }, 0);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
@@ -7,6 +7,15 @@ import {PolyToolBreadthPlaceholder} from './types';
|
|
|
7
7
|
import {parseMonomerSymbolList} from './pt-placeholders-input';
|
|
8
8
|
import {GridCellRenderArgs} from 'datagrok-api/dg';
|
|
9
9
|
|
|
10
|
+
/** Callback invoked when user double-clicks a Monomers cell in the breadth grid.
|
|
11
|
+
* @param start 0-based start position index
|
|
12
|
+
* @param end 0-based end position index
|
|
13
|
+
* @param currentMonomers current monomer symbols
|
|
14
|
+
* @returns new monomer symbols, or null if cancelled */
|
|
15
|
+
export type BreadthMonomerCellEditCallback = (
|
|
16
|
+
start: number, end: number, currentMonomers: string[],
|
|
17
|
+
) => Promise<string[] | null>;
|
|
18
|
+
|
|
10
19
|
export class PolyToolPlaceholdersBreadthInput extends DG.JsInputBase<DG.DataFrame> {
|
|
11
20
|
get inputType(): string { return 'Breadth'; }
|
|
12
21
|
|
|
@@ -20,6 +29,19 @@ export class PolyToolPlaceholdersBreadthInput extends DG.JsInputBase<DG.DataFram
|
|
|
20
29
|
this.setDataFrame(value);
|
|
21
30
|
}
|
|
22
31
|
|
|
32
|
+
public invalidateGrid(): void {
|
|
33
|
+
if (this.grid) {
|
|
34
|
+
const oldW = this.grid.root.style.width;
|
|
35
|
+
this.grid.root.style.width = '99.9%';
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
if (oldW)
|
|
38
|
+
this.grid.root.style.width = oldW;
|
|
39
|
+
else
|
|
40
|
+
this.grid.root.style.removeProperty('width');
|
|
41
|
+
}, 100);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
getStringValue(): string { return this.grid.dataFrame.toCsv(); }
|
|
24
46
|
|
|
25
47
|
setStringValue(str: string): void {
|
|
@@ -30,6 +52,9 @@ export class PolyToolPlaceholdersBreadthInput extends DG.JsInputBase<DG.DataFram
|
|
|
30
52
|
return dfToPlaceholdersBreadth(this.grid.dataFrame);
|
|
31
53
|
}
|
|
32
54
|
|
|
55
|
+
/** Set this callback to handle double-click editing of the Monomers column */
|
|
56
|
+
onMonomerCellEdit: BreadthMonomerCellEditCallback | null = null;
|
|
57
|
+
|
|
33
58
|
private readonly gridHost: HTMLDivElement;
|
|
34
59
|
public grid: DG.Grid;
|
|
35
60
|
|
|
@@ -91,6 +116,18 @@ export class PolyToolPlaceholdersBreadthInput extends DG.JsInputBase<DG.DataFram
|
|
|
91
116
|
}
|
|
92
117
|
});
|
|
93
118
|
|
|
119
|
+
// Make Monomers column non-editable (editing via double-click dialog)
|
|
120
|
+
const monomersGridCol = this.grid.columns.byName('Monomers');
|
|
121
|
+
if (monomersGridCol)
|
|
122
|
+
monomersGridCol.editable = false;
|
|
123
|
+
|
|
124
|
+
// Double-click on Monomers cell opens the monomer selection dialog
|
|
125
|
+
this.subs.push(this.grid.onCellDoubleClick.subscribe((gc: DG.GridCell) => {
|
|
126
|
+
if (gc.tableColumn?.name === 'Monomers' &&
|
|
127
|
+
gc.tableRowIndex != null && gc.tableRowIndex >= 0)
|
|
128
|
+
this.handleMonomerCellDoubleClick(gc.tableRowIndex);
|
|
129
|
+
}));
|
|
130
|
+
|
|
94
131
|
this.updateGridHeight(heightRowCount ?? this.grid.dataFrame.rowCount + 0.7);
|
|
95
132
|
this.subs.push(ui.onSizeChanged(this.grid.root)
|
|
96
133
|
.subscribe(this.gridRootOnSizeChanged.bind(this)));
|
|
@@ -140,6 +177,21 @@ export class PolyToolPlaceholdersBreadthInput extends DG.JsInputBase<DG.DataFram
|
|
|
140
177
|
|
|
141
178
|
// -- Handle events --
|
|
142
179
|
|
|
180
|
+
private async handleMonomerCellDoubleClick(tableRowIdx: number): Promise<void> {
|
|
181
|
+
if (!this.onMonomerCellEdit)
|
|
182
|
+
return;
|
|
183
|
+
const df = this.grid.dataFrame;
|
|
184
|
+
const start = parseInt(df.get('Start', tableRowIdx)) - 1;
|
|
185
|
+
const end = parseInt(df.get('End', tableRowIdx)) - 1;
|
|
186
|
+
if (isNaN(start) || isNaN(end))
|
|
187
|
+
return;
|
|
188
|
+
const currentStr: string = df.get('Monomers', tableRowIdx) ?? '';
|
|
189
|
+
const currentMonomers = parseMonomerSymbolList(currentStr);
|
|
190
|
+
const result = await this.onMonomerCellEdit(start, end, currentMonomers);
|
|
191
|
+
if (result !== null)
|
|
192
|
+
df.set('Monomers', tableRowIdx, result.join(', '));
|
|
193
|
+
}
|
|
194
|
+
|
|
143
195
|
private gridRootOnSizeChanged(): void {
|
|
144
196
|
this.grid.columns.byIndex(4)!.width = this.grid.root.clientWidth - this.grid.horzScroll.root.offsetWidth -
|
|
145
197
|
this.grid.columns.byIndex(0)!.width - this.grid.columns.byIndex(1)!.width -
|
|
@@ -7,6 +7,12 @@ import {fromEvent, Unsubscribable} from 'rxjs';
|
|
|
7
7
|
import {PolyToolPlaceholder} from './types';
|
|
8
8
|
import {GridCellRenderArgs} from 'datagrok-api/dg';
|
|
9
9
|
|
|
10
|
+
/** Callback invoked when user double-clicks a Monomers cell.
|
|
11
|
+
* @param position 0-based position index
|
|
12
|
+
* @param currentMonomers current monomer symbols
|
|
13
|
+
* @returns new monomer symbols, or null if cancelled */
|
|
14
|
+
export type MonomerCellEditCallback = (position: number, currentMonomers: string[]) => Promise<string[] | null>;
|
|
15
|
+
|
|
10
16
|
export class PolyToolPlaceholdersInput extends DG.JsInputBase<DG.DataFrame> {
|
|
11
17
|
get inputType(): string { return 'Positions'; }
|
|
12
18
|
|
|
@@ -38,11 +44,27 @@ export class PolyToolPlaceholdersInput extends DG.JsInputBase<DG.DataFrame> {
|
|
|
38
44
|
return dfToPlaceholders(this.grid.dataFrame);
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
/** Set this callback to handle double-click editing of the Monomers column */
|
|
48
|
+
onMonomerCellEdit: MonomerCellEditCallback | null = null;
|
|
49
|
+
|
|
41
50
|
private readonly gridHost: HTMLDivElement;
|
|
42
51
|
private grid!: DG.Grid;
|
|
43
52
|
|
|
44
53
|
private subs: Unsubscribable[] = [];
|
|
45
54
|
|
|
55
|
+
public invalidateGrid(): void {
|
|
56
|
+
if (this.grid) {
|
|
57
|
+
const oldW = this.grid.root.style.width;
|
|
58
|
+
this.grid.root.style.width = '99.9%';
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
if (oldW)
|
|
61
|
+
this.grid.root.style.width = oldW;
|
|
62
|
+
else
|
|
63
|
+
this.grid.root.style.removeProperty('width');
|
|
64
|
+
}, 100);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
protected constructor(
|
|
47
69
|
private readonly name: string | undefined,
|
|
48
70
|
heightRowCount?: number, options?: {},
|
|
@@ -89,6 +111,17 @@ export class PolyToolPlaceholdersInput extends DG.JsInputBase<DG.DataFrame> {
|
|
|
89
111
|
}
|
|
90
112
|
});
|
|
91
113
|
|
|
114
|
+
// Make Monomers column non-editable (editing is done via double-click dialog)
|
|
115
|
+
const monomersGridCol = this.grid.columns.byName('Monomers');
|
|
116
|
+
if (monomersGridCol)
|
|
117
|
+
monomersGridCol.editable = false;
|
|
118
|
+
|
|
119
|
+
// Double-click on Monomers cell opens the monomer selection dialog
|
|
120
|
+
this.subs.push(this.grid.onCellDoubleClick.subscribe((gc: DG.GridCell) => {
|
|
121
|
+
if (gc.tableColumn?.name === 'Monomers' && gc.tableRowIndex != null && gc.tableRowIndex >= 0)
|
|
122
|
+
this.handleMonomerCellDoubleClick(gc.tableRowIndex);
|
|
123
|
+
}));
|
|
124
|
+
|
|
92
125
|
this.updateGridHeight(heightRowCount ?? this.grid.dataFrame.rowCount + 0.7);
|
|
93
126
|
this.subs.push(ui.onSizeChanged(this.grid.root)
|
|
94
127
|
.subscribe(this.gridRootOnSizeChanged.bind(this)));
|
|
@@ -160,6 +193,20 @@ export class PolyToolPlaceholdersInput extends DG.JsInputBase<DG.DataFrame> {
|
|
|
160
193
|
|
|
161
194
|
// -- Handle events --
|
|
162
195
|
|
|
196
|
+
private async handleMonomerCellDoubleClick(tableRowIdx: number): Promise<void> {
|
|
197
|
+
if (!this.onMonomerCellEdit)
|
|
198
|
+
return;
|
|
199
|
+
const df = this.grid.dataFrame;
|
|
200
|
+
const position = parseInt(df.get('Position', tableRowIdx)) - 1;
|
|
201
|
+
if (isNaN(position))
|
|
202
|
+
return;
|
|
203
|
+
const currentMonomersStr: string = df.get('Monomers', tableRowIdx) ?? '';
|
|
204
|
+
const currentMonomers = parseMonomerSymbolList(currentMonomersStr);
|
|
205
|
+
const result = await this.onMonomerCellEdit(position, currentMonomers);
|
|
206
|
+
if (result !== null)
|
|
207
|
+
df.set('Monomers', tableRowIdx, result.join(', '));
|
|
208
|
+
}
|
|
209
|
+
|
|
163
210
|
private gridRootOnSizeChanged(): void {
|
|
164
211
|
this.grid.columns.byIndex(3)!.width = this.grid.root.clientWidth - this.grid.horzScroll.root.offsetWidth -
|
|
165
212
|
this.grid.columns.byIndex(0)!.width - this.grid.columns.byIndex(1)!.width -
|
package/src/polytool/types.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
|
6
6
|
|
|
7
7
|
export enum PolyToolEnumeratorTypes {
|
|
8
8
|
Single = 'single',
|
|
9
|
+
Parallel = 'parallel',
|
|
9
10
|
Matrix = 'matrix',
|
|
10
11
|
Library = 'library',
|
|
11
12
|
}
|
|
@@ -27,6 +28,7 @@ export type PolyToolEnumeratorParams = {
|
|
|
27
28
|
breadthPlaceholders?: PolyToolBreadthPlaceholder[];
|
|
28
29
|
keepOriginal?: boolean;
|
|
29
30
|
trivialName?: boolean;
|
|
31
|
+
fromHelmNotation?: {convert:(helm: string) => string; notationName: string};
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export class MonomerNotFoundError extends Error {
|
|
@@ -73,6 +73,66 @@ category('PolyTool: Enumerate', () => {
|
|
|
73
73
|
{seq: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.T.[C(1)].G.[NH2]}$$$$V2.0', name: '-[Tic]7T'},
|
|
74
74
|
]
|
|
75
75
|
},
|
|
76
|
+
'parallel1': {
|
|
77
|
+
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
78
|
+
params: {
|
|
79
|
+
type: PolyToolEnumeratorTypes.Parallel,
|
|
80
|
+
placeholders: [
|
|
81
|
+
{position: 1, monomers: ['D', 'L']},
|
|
82
|
+
{position: 6, monomers: ['Y', 'T']},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
tgt: [
|
|
86
|
+
{seq: 'PEPTIDE1{[Ac(1)].D.W.G.P.L.Y.[C(1)].G.[NH2]}$$$$V2.0', name: '-F2D-[Tic]7Y'},
|
|
87
|
+
{seq: 'PEPTIDE1{[Ac(1)].L.W.G.P.L.T.[C(1)].G.[NH2]}$$$$V2.0', name: '-F2L-[Tic]7T'},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
'parallel-three-positions': {
|
|
91
|
+
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
92
|
+
params: {
|
|
93
|
+
type: PolyToolEnumeratorTypes.Parallel,
|
|
94
|
+
placeholders: [
|
|
95
|
+
{position: 1, monomers: ['D', 'L', 'K']},
|
|
96
|
+
{position: 4, monomers: ['K', 'P', 'F4COO']},
|
|
97
|
+
{position: 6, monomers: ['Y', 'T', 'A']},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
tgt: [
|
|
101
|
+
{seq: 'PEPTIDE1{[Ac(1)].D.W.G.K.L.Y.[C(1)].G.[NH2]}$$$$V2.0', name: '-F2D-P5K-[Tic]7Y'},
|
|
102
|
+
{seq: 'PEPTIDE1{[Ac(1)].L.W.G.P.L.T.[C(1)].G.[NH2]}$$$$V2.0', name: '-F2L-P5P-[Tic]7T'},
|
|
103
|
+
{seq: 'PEPTIDE1{[Ac(1)].K.W.G.[F4COO].L.A.[C(1)].G.[NH2]}$$$$V2.0', name: '-F2K-P5[F4COO]-[Tic]7A'},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
'parallel-with-original': {
|
|
107
|
+
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
108
|
+
params: {
|
|
109
|
+
type: PolyToolEnumeratorTypes.Parallel,
|
|
110
|
+
placeholders: [
|
|
111
|
+
{position: 1, monomers: ['D', 'L']},
|
|
112
|
+
{position: 6, monomers: ['Y', 'T']},
|
|
113
|
+
],
|
|
114
|
+
keepOriginal: true,
|
|
115
|
+
},
|
|
116
|
+
tgt: [
|
|
117
|
+
{seq: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0', name: ''},
|
|
118
|
+
{seq: 'PEPTIDE1{[Ac(1)].D.W.G.P.L.Y.[C(1)].G.[NH2]}$$$$V2.0', name: '-F2D-[Tic]7Y'},
|
|
119
|
+
{seq: 'PEPTIDE1{[Ac(1)].L.W.G.P.L.T.[C(1)].G.[NH2]}$$$$V2.0', name: '-F2L-[Tic]7T'},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
'parallel-single-position': {
|
|
123
|
+
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
124
|
+
params: {
|
|
125
|
+
type: PolyToolEnumeratorTypes.Parallel,
|
|
126
|
+
placeholders: [
|
|
127
|
+
{position: 4, monomers: ['K', 'P', 'F4COO']},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
tgt: [
|
|
131
|
+
{seq: 'PEPTIDE1{[Ac(1)].F.W.G.K.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0', name: '-P5K'},
|
|
132
|
+
{seq: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0', name: '-P5P'},
|
|
133
|
+
{seq: 'PEPTIDE1{[Ac(1)].F.W.G.[F4COO].L.[Tic].[C(1)].G.[NH2]}$$$$V2.0', name: '-P5[F4COO]'},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
76
136
|
'matrix1': {
|
|
77
137
|
src: 'PEPTIDE1{[Ac(1)].F.W.G.P.L.[Tic].[C(1)].G.[NH2]}$$$$V2.0',
|
|
78
138
|
params:
|
package/src/utils/cyclized.ts
CHANGED
|
@@ -6,7 +6,7 @@ import wu from 'wu';
|
|
|
6
6
|
|
|
7
7
|
/* eslint-disable max-len */
|
|
8
8
|
import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
9
|
-
import {INotationProvider, ISeqSplitted, SeqSplittedBase, SplitterFunc} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
9
|
+
import {INotationProvider, ISeqSplitted, NotationProviderBase, SeqSplittedBase, SplitterFunc} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
10
10
|
import {getSplitterWithSeparator} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
11
11
|
import {GAP_SYMBOL, GapOriginals, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
12
12
|
import {CellRendererBackBase} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
|
|
@@ -22,16 +22,22 @@ import {CyclizedCellRendererBack} from './cell-renderer-cyclized';
|
|
|
22
22
|
|
|
23
23
|
/* eslint-enable max-len */
|
|
24
24
|
|
|
25
|
-
export class CyclizedNotationProvider implements INotationProvider {
|
|
25
|
+
export class CyclizedNotationProvider extends NotationProviderBase implements INotationProvider {
|
|
26
26
|
private readonly separatorSplitter: SplitterFunc;
|
|
27
27
|
public readonly splitter: SplitterFunc;
|
|
28
28
|
|
|
29
|
+
static override get notationName(): string { return 'Harmonized Sequence'; }
|
|
30
|
+
static override get implementsFromHelm(): boolean { return false; }
|
|
31
|
+
static override convertFromHelm(helm: string, options: any): string {
|
|
32
|
+
throw new Error('converting from helm not supported yet');
|
|
33
|
+
}
|
|
29
34
|
get defaultGapOriginal(): string { return ''; }
|
|
30
35
|
|
|
31
36
|
constructor(
|
|
32
37
|
public readonly separator: string,
|
|
33
38
|
protected readonly helmHelper: IHelmHelper
|
|
34
39
|
) {
|
|
40
|
+
super();
|
|
35
41
|
this.separatorSplitter = getSplitterWithSeparator(this.separator);
|
|
36
42
|
this.splitter = this._splitter.bind(this);
|
|
37
43
|
}
|