@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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/sequence-translator",
|
|
3
3
|
"friendlyName": "Sequence Translator",
|
|
4
|
-
"version": "1.10.
|
|
4
|
+
"version": "1.10.8",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Davit Rizhinashvili",
|
|
7
7
|
"email": "drizhinashvili@datagrok.ai"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@datagrok-libraries/bio": "^5.
|
|
25
|
+
"@datagrok-libraries/bio": "^5.61.6",
|
|
26
26
|
"@datagrok-libraries/chem-meta": "^1.2.8",
|
|
27
27
|
"@datagrok-libraries/tutorials": "^1.6.1",
|
|
28
28
|
"@datagrok-libraries/utils": "^4.6.5",
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
56
56
|
"@typescript-eslint/parser": "^7.2.0",
|
|
57
57
|
"css-loader": "^6.7.3",
|
|
58
|
-
"datagrok-tools": "^5.0.0",
|
|
59
58
|
"eslint": "^8.57.0",
|
|
60
59
|
"eslint-config-google": "^0.14.0",
|
|
61
60
|
"style-loader": "^3.3.1",
|
package/src/package-api.ts
CHANGED
|
@@ -147,4 +147,8 @@ export namespace funcs {
|
|
|
147
147
|
export async function applyNotationProviderForCyclized(col: DG.Column , separator: string ): Promise<void> {
|
|
148
148
|
return await grok.functions.call('SequenceTranslator:ApplyNotationProviderForCyclized', { col, separator });
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
export async function harmonizedSequenceNotationProviderConstructor(): Promise<any> {
|
|
152
|
+
return await grok.functions.call('SequenceTranslator:HarmonizedSequenceNotationProviderConstructor', {});
|
|
153
|
+
}
|
|
150
154
|
}
|
package/src/package.g.ts
CHANGED
|
@@ -221,3 +221,9 @@ export async function getPolyToolCombineDialog() : Promise<void> {
|
|
|
221
221
|
export function applyNotationProviderForCyclized(col: DG.Column<any>, separator: string) : void {
|
|
222
222
|
PackageFunctions.applyNotationProviderForCyclized(col, separator);
|
|
223
223
|
}
|
|
224
|
+
|
|
225
|
+
//output: dynamic result
|
|
226
|
+
//meta.role: notationProviderConstructor
|
|
227
|
+
export async function harmonizedSequenceNotationProviderConstructor() : Promise<any> {
|
|
228
|
+
return await PackageFunctions.harmonizedSequenceNotationProviderConstructor();
|
|
229
|
+
}
|
package/src/package.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as grok from 'datagrok-api/grok';
|
|
|
3
3
|
import * as ui from 'datagrok-api/ui';
|
|
4
4
|
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
|
-
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
6
|
+
import {NOTATION, NOTATION_PROVIDER_CONSTRUCTOR_ROLE} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
7
7
|
import {SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
8
8
|
|
|
9
9
|
import {OligoToolkitPackage} from './apps/common/model/oligo-toolkit-package';
|
|
@@ -400,6 +400,14 @@ export class PackageFunctions {
|
|
|
400
400
|
col.tags[PolyToolTags.dataRole] = 'template';
|
|
401
401
|
col.temp[SeqTemps.notationProvider] = new CyclizedNotationProvider(separator, _package.helmHelper);
|
|
402
402
|
}
|
|
403
|
+
|
|
404
|
+
@grok.decorators.func({
|
|
405
|
+
name: 'harmonizedSequenceNotationProviderConstructor',
|
|
406
|
+
meta: {role: 'notationProviderConstructor'}
|
|
407
|
+
})
|
|
408
|
+
static async harmonizedSequenceNotationProviderConstructor(): Promise<typeof CyclizedNotationProvider> {
|
|
409
|
+
return CyclizedNotationProvider;
|
|
410
|
+
}
|
|
403
411
|
}
|
|
404
412
|
|
|
405
413
|
//name: getSpecifiedAppView
|
package/src/polytool/const.ts
CHANGED
|
@@ -40,3 +40,10 @@ export const PT_UI_DIALOG_CONVERSION = 'Poly Tool Conversion';
|
|
|
40
40
|
export const PT_UI_DIALOG_UNRULE = 'Poly Tool Unrule';
|
|
41
41
|
export const PT_UI_DIALOG_ENUMERATION = 'Poly Tool Enumeration';
|
|
42
42
|
export const PT_UI_RULES_USED = 'Rules used';
|
|
43
|
+
|
|
44
|
+
export const PT_ENUM_TYPE_TOOLTIPS: Record<string, string> = {
|
|
45
|
+
'single': 'Each position is enumerated independently. Total results = sum of monomers across all positions.',
|
|
46
|
+
'parallel': 'The i-th result uses the i-th monomer from every position (zip). All positions must have the same number of monomers. Total results = number of monomers per position.',
|
|
47
|
+
'matrix': 'Cartesian product of all positions. Total results = product of monomer counts across all positions.',
|
|
48
|
+
'library': 'Substitutes all monomers from a selected library at a single position.',
|
|
49
|
+
};
|
|
@@ -25,9 +25,10 @@ import {PolyToolEnumeratorParams, PolyToolEnumeratorType, PolyToolEnumeratorType
|
|
|
25
25
|
import {getLibrariesList, LIB_PATH} from './utils';
|
|
26
26
|
import {doPolyToolEnumerateHelm, PT_HELM_EXAMPLE} from './pt-enumeration-helm';
|
|
27
27
|
import {PolyToolPlaceholdersInput} from './pt-placeholders-input';
|
|
28
|
+
import {showMonomerSelectionDialog} from './pt-monomer-selection-dialog';
|
|
28
29
|
import {defaultErrorHandler} from '../utils/err-info';
|
|
29
30
|
import {PolyToolPlaceholdersBreadthInput} from './pt-placeholders-breadth-input';
|
|
30
|
-
import {PT_UI_DIALOG_ENUMERATION, PT_UI_GET_HELM, PT_UI_HIGHLIGHT_MONOMERS, PT_UI_RULES_USED, PT_UI_USE_CHIRALITY} from './const';
|
|
31
|
+
import {PT_ENUM_TYPE_TOOLTIPS, PT_UI_DIALOG_ENUMERATION, PT_UI_GET_HELM, PT_UI_HIGHLIGHT_MONOMERS, PT_UI_RULES_USED, PT_UI_USE_CHIRALITY} from './const';
|
|
31
32
|
import {PolyToolDataRole, PolyToolTags} from '../consts';
|
|
32
33
|
import {RuleInputs, RULES_PATH, RULES_STORAGE_NAME} from './conversion/pt-rules';
|
|
33
34
|
import {Chain} from './conversion/pt-chain';
|
|
@@ -38,8 +39,22 @@ import {buildMonomerHoverLink} from '@datagrok-libraries/bio/src/monomer-works/m
|
|
|
38
39
|
import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
|
|
39
40
|
|
|
40
41
|
import {PolymerTypes} from '@datagrok-libraries/js-draw-lite/src/types/org';
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
42
|
+
import {CyclizedNotationProvider} from '../utils/cyclized';
|
|
43
|
+
import {INotationProvider, NotationProviderBase} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* PolyTool Enumeration Dialog
|
|
47
|
+
*
|
|
48
|
+
* Provides the UI for enumerating macromolecule variants. The user selects positions
|
|
49
|
+
* on a HELM molecule and specifies substitute monomers at each position. Supports
|
|
50
|
+
* multiple enumeration strategies (Single, Parallel, Matrix, Library) and optional
|
|
51
|
+
* downstream processing (atomic-level conversion, chirality, rules).
|
|
52
|
+
*
|
|
53
|
+
* Architecture:
|
|
54
|
+
* polyToolEnumerateHelmUI() - Entry point: creates, sizes, and shows the dialog
|
|
55
|
+
* getPolyToolEnumerateDialog() - Builds all inputs, validators, event wiring, and the dialog
|
|
56
|
+
* polyToolEnumerateSeq() - Executes enumeration and post-processes results into a DataFrame
|
|
57
|
+
*/
|
|
43
58
|
|
|
44
59
|
type PolyToolEnumerateInputs = {
|
|
45
60
|
macromolecule: HelmInputBase;
|
|
@@ -72,15 +87,21 @@ type PolyToolEnumerateHelmSerialized = {
|
|
|
72
87
|
library: string;
|
|
73
88
|
};
|
|
74
89
|
|
|
90
|
+
/** Entry point: creates, sizes, and shows the enumeration dialog. */
|
|
75
91
|
export async function polyToolEnumerateHelmUI(cell?: DG.Cell): Promise<void> {
|
|
76
92
|
await _package.initPromise;
|
|
77
93
|
|
|
94
|
+
// Capture viewport dimensions for dialog sizing
|
|
78
95
|
const maxWidth = window.innerWidth;
|
|
79
96
|
const maxHeight = window.innerHeight;
|
|
80
97
|
|
|
81
98
|
try {
|
|
82
99
|
// eslint-disable-next-line prefer-const
|
|
83
100
|
let dialog: DG.Dialog;
|
|
101
|
+
|
|
102
|
+
// Dynamically allocates remaining vertical space to flex-fit inputs (e.g. the macromolecule editor)
|
|
103
|
+
// after fixed-height inputs are laid out. fitInputs maps child element indices to proportional
|
|
104
|
+
// height weights; elements not in fitInputs get their natural height.
|
|
84
105
|
function resizeInputs() {
|
|
85
106
|
if (dialog == null) return;
|
|
86
107
|
|
|
@@ -106,18 +127,13 @@ export async function polyToolEnumerateHelmUI(cell?: DG.Cell): Promise<void> {
|
|
|
106
127
|
};
|
|
107
128
|
dialog = await getPolyToolEnumerateDialog(cell, resizeInputs);
|
|
108
129
|
|
|
130
|
+
// On first show, center the dialog at 70% of viewport; on subsequent resizes, just reflow inputs
|
|
109
131
|
let isFirstShow = true;
|
|
110
132
|
ui.onSizeChanged(dialog.root).subscribe(() => {
|
|
111
133
|
if (isFirstShow) {
|
|
112
|
-
// const dialogRootCash = $(dialog.root);
|
|
113
|
-
// const contentMaxHeight = maxHeight -
|
|
114
|
-
// dialogRootCash.find('div.d4-dialog-header').get(0)!.offsetHeight -
|
|
115
|
-
// dialogRootCash.find('div.d4-dialog-footer').get(0)!.offsetHeight;
|
|
116
|
-
|
|
117
134
|
const dialogWidth = maxWidth * 0.7;
|
|
118
135
|
const dialogHeight = maxHeight * 0.7;
|
|
119
136
|
|
|
120
|
-
// Centered, but resizable dialog
|
|
121
137
|
dialog.root.style.width = `${Math.min(maxWidth, dialogWidth)}px`;
|
|
122
138
|
dialog.root.style.height = `${Math.min(maxHeight, dialogHeight)}px`;
|
|
123
139
|
dialog.root.style.left = `${Math.floor((maxWidth - dialog.root.offsetWidth) / 2)}px`;
|
|
@@ -140,19 +156,21 @@ export async function polyToolEnumerateHelmUI(cell?: DG.Cell): Promise<void> {
|
|
|
140
156
|
}
|
|
141
157
|
}
|
|
142
158
|
|
|
159
|
+
/** Builds and configures the enumeration dialog with all inputs, validators, and event handlers. */
|
|
143
160
|
async function getPolyToolEnumerateDialog(
|
|
144
161
|
cell?: DG.Cell, resizeInputs?: () => void
|
|
145
162
|
): Promise<DG.Dialog> {
|
|
146
163
|
const logPrefix = `ST: PT: HelmDialog()`;
|
|
147
164
|
let inputs: PolyToolEnumerateInputs;
|
|
148
165
|
const subs: Unsubscribable[] = [];
|
|
149
|
-
//
|
|
166
|
+
// Track previous enumeration type to clean up state when switching to/from Library mode
|
|
150
167
|
let prevEnumerationType: PolyToolEnumeratorType = PolyToolEnumeratorTypes.Single;
|
|
151
168
|
const destroy = () => {
|
|
152
169
|
for (const sub of subs) sub.unsubscribe();
|
|
153
170
|
inputs.placeholders.detach();
|
|
154
171
|
};
|
|
155
172
|
try {
|
|
173
|
+
// --- Initialize helpers (monomer lib, sequence helper, HELM helper) ---
|
|
156
174
|
const libHelper = await getMonomerLibHelper();
|
|
157
175
|
const monomerLib = libHelper.getMonomerLib();
|
|
158
176
|
const seqHelper = await getSeqHelper();
|
|
@@ -161,6 +179,8 @@ async function getPolyToolEnumerateDialog(
|
|
|
161
179
|
const [_libList, helmHelper] = await Promise.all([getLibrariesList(), getHelmHelper()]);
|
|
162
180
|
const monomerLibFuncs = helmHelper.buildMonomersFuncsFromLib(monomerLib);
|
|
163
181
|
|
|
182
|
+
// Resolves the source macromolecule from the given cell or falls back to a default example.
|
|
183
|
+
// Returns the sequence value and its data role (macromolecule vs template).
|
|
164
184
|
const getValue = (cell?: DG.Cell): [SeqValueBase, PolyToolDataRole] => {
|
|
165
185
|
let resSeqValue: SeqValueBase;
|
|
166
186
|
let resDataRole: PolyToolDataRole;
|
|
@@ -196,12 +216,14 @@ async function getPolyToolEnumerateDialog(
|
|
|
196
216
|
|
|
197
217
|
let [seqValue, dataRole]: [SeqValueBase, PolyToolDataRole] = getValue(cell);
|
|
198
218
|
|
|
219
|
+
// --- UI state ---
|
|
199
220
|
let srcId: { value: string, colName: string } | null = null;
|
|
200
221
|
let ruleFileList: string[];
|
|
201
222
|
let ruleInputs: RuleInputs;
|
|
202
223
|
const trivialNameSampleDiv = ui.divText('', {style: {marginLeft: '8px', marginTop: '2px'}});
|
|
203
224
|
const warningsTextDiv = ui.divText('', {style: {color: 'red'}});
|
|
204
|
-
|
|
225
|
+
|
|
226
|
+
// === INPUT DEFINITIONS ===
|
|
205
227
|
inputs = {
|
|
206
228
|
macromolecule: helmHelper.createHelmInput(
|
|
207
229
|
'Macromolecule', {
|
|
@@ -291,26 +313,76 @@ async function getPolyToolEnumerateDialog(
|
|
|
291
313
|
library: ui.input.choice('Monomer Library', {items: _libList, value: _libList[0], nullable: true}) as DG.InputBase<string>,
|
|
292
314
|
};
|
|
293
315
|
// #### Inputs END
|
|
316
|
+
|
|
317
|
+
// Bind tooltip to enumerator type choice, updating on each change
|
|
318
|
+
const updateEnumTypeTooltip = () => {
|
|
319
|
+
const tooltipText = PT_ENUM_TYPE_TOOLTIPS[inputs.enumeratorType.value] ?? '';
|
|
320
|
+
ui.tooltip.bind(inputs.enumeratorType.input, tooltipText);
|
|
321
|
+
};
|
|
322
|
+
updateEnumTypeTooltip();
|
|
323
|
+
|
|
294
324
|
inputs.library.root.style.setProperty('display', 'none');
|
|
295
325
|
inputs.trivialNameCol.addOptions(trivialNameSampleDiv);
|
|
296
326
|
|
|
327
|
+
// Wire up monomer cell double-click to open selection dialog
|
|
328
|
+
inputs.placeholders.onMonomerCellEdit = async (position: number, currentMonomers: string[]) => {
|
|
329
|
+
const mol = inputs.macromolecule.molValue;
|
|
330
|
+
if (position < 0 || position >= mol.atoms.length)
|
|
331
|
+
return null;
|
|
332
|
+
const atom = mol.atoms[position];
|
|
333
|
+
const helmType: HelmType = atom.biotype()!;
|
|
334
|
+
const polymerType = helmTypeToPolymerType(helmType);
|
|
335
|
+
return showMonomerSelectionDialog(monomerLib, polymerType, currentMonomers);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Wire up breadth monomer cell double-click to open selection dialog
|
|
339
|
+
inputs.placeholdersBreadth.onMonomerCellEdit = async (
|
|
340
|
+
start: number, _end: number, currentMonomers: string[],
|
|
341
|
+
) => {
|
|
342
|
+
const mol = inputs.macromolecule.molValue;
|
|
343
|
+
if (start < 0 || start >= mol.atoms.length)
|
|
344
|
+
return null;
|
|
345
|
+
const atom = mol.atoms[start];
|
|
346
|
+
const helmType: HelmType = atom.biotype()!;
|
|
347
|
+
const polymerType = helmTypeToPolymerType(helmType);
|
|
348
|
+
return showMonomerSelectionDialog(monomerLib, polymerType, currentMonomers);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// === VALIDATORS ===
|
|
352
|
+
// Validates placeholder positions and monomers based on the current enumeration mode.
|
|
297
353
|
let placeholdersValidity: string | null = null;
|
|
298
354
|
inputs.placeholders.addValidator((value: string): string | null => {
|
|
355
|
+
placeholdersValidity = null;
|
|
299
356
|
const errors: string[] = [];
|
|
357
|
+
setTimeout(() => { updateWarnings(); }, 100);
|
|
300
358
|
try {
|
|
301
359
|
// for library, ony one placeholder is allowed
|
|
302
360
|
if (inputs.enumeratorType.value === PolyToolEnumeratorTypes.Library) {
|
|
303
|
-
setTimeout(() => { updateWarnings(); }, 10);
|
|
304
361
|
const pcs = inputs.placeholders.placeholdersValue;
|
|
305
362
|
if (pcs.length > 1)
|
|
306
|
-
|
|
363
|
+
placeholdersValidity = 'Only one placeholder is allowed for Library mode';
|
|
307
364
|
if (pcs.length === 1) {
|
|
308
365
|
if (pcs[0].position == null)
|
|
309
|
-
|
|
366
|
+
placeholdersValidity = 'Position is required for Library mode';
|
|
310
367
|
if (pcs[0].position > inputs.macromolecule.molValue.atoms.length)
|
|
311
|
-
|
|
368
|
+
placeholdersValidity = `There is no monomer at position ${pcs[0].position + 1}.`;
|
|
369
|
+
}
|
|
370
|
+
return placeholdersValidity;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Parallel mode: all placeholders must have the same monomer count
|
|
374
|
+
if (inputs.enumeratorType.value === PolyToolEnumeratorTypes.Parallel) {
|
|
375
|
+
const pcs = inputs.placeholders.placeholdersValue;
|
|
376
|
+
const nonEmpty = pcs.filter((ph) => ph.monomers.length > 0);
|
|
377
|
+
if (nonEmpty.length > 1) {
|
|
378
|
+
const firstCount = nonEmpty[0].monomers.length;
|
|
379
|
+
const mismatch = nonEmpty.find((ph) => ph.monomers.length !== firstCount);
|
|
380
|
+
if (mismatch) {
|
|
381
|
+
placeholdersValidity = `Parallel mode requires all positions to have the same number of monomers. ` +
|
|
382
|
+
`Position ${nonEmpty[0].position + 1} has ${firstCount}, ` +
|
|
383
|
+
`but position ${mismatch.position + 1} has ${mismatch.monomers.length}.`;
|
|
384
|
+
}
|
|
312
385
|
}
|
|
313
|
-
return null;
|
|
314
386
|
}
|
|
315
387
|
|
|
316
388
|
if (dataRole !== PolyToolDataRole.macromolecule)
|
|
@@ -348,7 +420,7 @@ async function getPolyToolEnumerateDialog(
|
|
|
348
420
|
.join('\n');
|
|
349
421
|
if (Object.keys(byTypeStr).length > 0)
|
|
350
422
|
errors.push(`Placeholders contain missed monomers: ${byTypeStr}`);
|
|
351
|
-
placeholdersValidity = errors.length > 0 ? errors.join('\n') :
|
|
423
|
+
placeholdersValidity = errors.length > 0 ? errors.join('\n') : placeholdersValidity;
|
|
352
424
|
} catch (err: any) {
|
|
353
425
|
const [errMsg, errStack] = defaultErrorHandler(err, false);
|
|
354
426
|
placeholdersValidity = errMsg;
|
|
@@ -435,8 +507,14 @@ async function getPolyToolEnumerateDialog(
|
|
|
435
507
|
defaultErrorHandler(err, false);
|
|
436
508
|
}
|
|
437
509
|
prevEnumerationType = inputs.enumeratorType.value;
|
|
510
|
+
updateEnumTypeTooltip();
|
|
511
|
+
// Re-validate placeholders (Parallel mode has different constraints than Single/Matrix)
|
|
512
|
+
inputs.placeholders.fireChanged();
|
|
438
513
|
});
|
|
439
514
|
|
|
515
|
+
// === MOLECULE INTERACTION EVENT HANDLERS ===
|
|
516
|
+
|
|
517
|
+
// Mouse move: show tooltip of substitute monomers at hovered position
|
|
440
518
|
subs.push(inputs.macromolecule.onMouseMove.subscribe((e: MouseEvent) => {
|
|
441
519
|
try {
|
|
442
520
|
_package.logger.debug(`${logPrefix}, placeholdersInput.onMouseMove()`);
|
|
@@ -461,6 +539,7 @@ async function getPolyToolEnumerateDialog(
|
|
|
461
539
|
defaultErrorHandler(err, false);
|
|
462
540
|
}
|
|
463
541
|
}));
|
|
542
|
+
// Click on molecule: add clicked atom position to the placeholders grid
|
|
464
543
|
subs.push(inputs.macromolecule.onClick.subscribe((e: MouseEvent) => {
|
|
465
544
|
try {
|
|
466
545
|
_package.logger.debug(`${logPrefix}, placeholdersInput.onClick()`);
|
|
@@ -503,6 +582,9 @@ async function getPolyToolEnumerateDialog(
|
|
|
503
582
|
inputs.macromolecule.root.style.setProperty('min-width', '250px', 'important');
|
|
504
583
|
// inputs.macromolecule.root.style.setProperty('max-height', '300px', 'important');
|
|
505
584
|
|
|
585
|
+
// === VIEW UPDATE HELPERS ===
|
|
586
|
+
|
|
587
|
+
// Highlights atoms in the molecule editor that have placeholders defined
|
|
506
588
|
const updateViewMol = () => {
|
|
507
589
|
const phPosSet = new Set<number>(inputs.placeholders.placeholdersValue.map((ph) => ph.position));
|
|
508
590
|
const mol = inputs.macromolecule.molValue;
|
|
@@ -513,6 +595,7 @@ async function getPolyToolEnumerateDialog(
|
|
|
513
595
|
inputs.macromolecule.redraw();
|
|
514
596
|
};
|
|
515
597
|
|
|
598
|
+
// Shows/hides conversion rules UI based on toAtomicLevel setting and data role
|
|
516
599
|
const updateViewRules = () => {
|
|
517
600
|
if (inputs.toAtomicLevel.value && dataRole === PolyToolDataRole.template) {
|
|
518
601
|
inputs.generateHelm.root.style.removeProperty('display');
|
|
@@ -528,7 +611,7 @@ async function getPolyToolEnumerateDialog(
|
|
|
528
611
|
if (resizeInputs)
|
|
529
612
|
resizeInputs();
|
|
530
613
|
};
|
|
531
|
-
|
|
614
|
+
let dialogOKButton: HTMLButtonElement | null | undefined = null;
|
|
532
615
|
const updateWarnings = () => {
|
|
533
616
|
const warnings = placeholdersValidity;
|
|
534
617
|
// const iw = inputs.warnings;
|
|
@@ -540,6 +623,8 @@ async function getPolyToolEnumerateDialog(
|
|
|
540
623
|
|
|
541
624
|
w.innerText = warnings;
|
|
542
625
|
w.style.removeProperty('display');
|
|
626
|
+
if (dialogOKButton)
|
|
627
|
+
dialogOKButton.disabled = true;
|
|
543
628
|
} else {
|
|
544
629
|
// iw.value = ''; // <- breaks dialog resize
|
|
545
630
|
// iw.enabled = false;
|
|
@@ -547,6 +632,8 @@ async function getPolyToolEnumerateDialog(
|
|
|
547
632
|
|
|
548
633
|
w.innerText = '';
|
|
549
634
|
w.style.setProperty('display', 'none');
|
|
635
|
+
if (dialogOKButton)
|
|
636
|
+
dialogOKButton.disabled = false;
|
|
550
637
|
}
|
|
551
638
|
//resizeInputs();
|
|
552
639
|
};
|
|
@@ -572,6 +659,8 @@ async function getPolyToolEnumerateDialog(
|
|
|
572
659
|
fillForCurrentCell(seqValue, dataRole, cell);
|
|
573
660
|
updateViewRules();
|
|
574
661
|
|
|
662
|
+
// === EXECUTION (OK button handler) ===
|
|
663
|
+
// Pre-flight validates inputs, resolves Library mode, builds params, and runs enumeration.
|
|
575
664
|
const exec = async (): Promise<void> => {
|
|
576
665
|
try {
|
|
577
666
|
const srcHelm = inputs.macromolecule.stringValue;
|
|
@@ -600,6 +689,7 @@ async function getPolyToolEnumerateDialog(
|
|
|
600
689
|
await getHelmHelper(); // initializes JSDraw and org
|
|
601
690
|
|
|
602
691
|
const placeHoldersValue = inputs.placeholders.placeholdersValue;
|
|
692
|
+
// Library mode: load all monomers from the selected library and convert to Single mode
|
|
603
693
|
let enumerationType = inputs.enumeratorType.value;
|
|
604
694
|
if (enumerationType === PolyToolEnumeratorTypes.Library) {
|
|
605
695
|
if (placeHoldersValue.length !== 1) {
|
|
@@ -622,12 +712,33 @@ async function getPolyToolEnumerateDialog(
|
|
|
622
712
|
enumerationType = PolyToolEnumeratorTypes.Single;
|
|
623
713
|
}
|
|
624
714
|
|
|
715
|
+
if (enumerationType === PolyToolEnumeratorTypes.Parallel) {
|
|
716
|
+
const nonEmpty = placeHoldersValue.filter((ph) => ph.monomers.length > 0);
|
|
717
|
+
if (nonEmpty.length > 1) {
|
|
718
|
+
const firstCount = nonEmpty[0].monomers.length;
|
|
719
|
+
if (nonEmpty.some((ph) => ph.monomers.length !== firstCount)) {
|
|
720
|
+
grok.shell.warning('Parallel mode requires all positions to have the same number of monomers');
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
625
726
|
const params: PolyToolEnumeratorParams = {
|
|
626
727
|
placeholders: placeHoldersValue,
|
|
627
728
|
type: enumerationType,
|
|
628
729
|
breadthPlaceholders: inputs.placeholdersBreadth.placeholdersBreadthValue,
|
|
629
730
|
keepOriginal: inputs.keepOriginal.value,
|
|
630
731
|
};
|
|
732
|
+
|
|
733
|
+
if (cell?.column?.temp?.[SeqTemps.notationProvider] &&
|
|
734
|
+
!(cell.column.temp[SeqTemps.notationProvider] instanceof CyclizedNotationProvider)) {
|
|
735
|
+
const notationProvider = cell.column.temp[SeqTemps.notationProvider] as INotationProvider;
|
|
736
|
+
if ((notationProvider.constructor as typeof NotationProviderBase).implementsFromHelm) {
|
|
737
|
+
const cons = notationProvider.constructor as typeof NotationProviderBase;
|
|
738
|
+
params.fromHelmNotation = {notationName: cons.notationName, convert: (helm) => cons.convertFromHelm(helm, {})};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
631
742
|
const toAtomicLevelV: boolean = inputs.toAtomicLevel.value;
|
|
632
743
|
const enumeratorResDf = await polyToolEnumerateSeq(srcHelm, dataRole, srcId, params,
|
|
633
744
|
toAtomicLevelV ? {
|
|
@@ -644,6 +755,8 @@ async function getPolyToolEnumerateDialog(
|
|
|
644
755
|
}
|
|
645
756
|
};
|
|
646
757
|
|
|
758
|
+
// === DIALOG CONSTRUCTION AND LAYOUT ===
|
|
759
|
+
// Layout: macromolecule editor on top, two-column (placeholders | breadth), two-column (options | rules)
|
|
647
760
|
const dialog = ui.dialog({title: PT_UI_DIALOG_ENUMERATION, showFooter: true})
|
|
648
761
|
.add(inputs.macromolecule.root)
|
|
649
762
|
.add(ui.divH([
|
|
@@ -668,14 +781,20 @@ async function getPolyToolEnumerateDialog(
|
|
|
668
781
|
{style: {width: '50%'}})],
|
|
669
782
|
{style: {width: '100%'}}))
|
|
670
783
|
.add(warningsTextDiv)
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
784
|
+
.onOK(() => {
|
|
785
|
+
if (placeholdersValidity) {
|
|
786
|
+
updateWarnings();
|
|
787
|
+
grok.shell.warning('Please fix validation errors before running enumeration');
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
exec();
|
|
791
|
+
});
|
|
792
|
+
// .onOK(() => { exec(); });
|
|
793
|
+
dialogOKButton = dialog.getButton('OK') as HTMLButtonElement;
|
|
676
794
|
subs.push(dialog.onClose.subscribe(() => {
|
|
677
795
|
destroy();
|
|
678
796
|
}));
|
|
797
|
+
// Dialog history: serialization/deserialization for persisting dialog state across sessions
|
|
679
798
|
dialog.history(
|
|
680
799
|
/* getInput */ (): PolyToolEnumerateHelmSerialized => {
|
|
681
800
|
return {
|
|
@@ -707,6 +826,10 @@ async function getPolyToolEnumerateDialog(
|
|
|
707
826
|
inputs.highlightMonomers.value = x.highlightMonomers ?? false;
|
|
708
827
|
ruleInputs.setActive(x.rules);
|
|
709
828
|
inputs.library.value = x.library;
|
|
829
|
+
setTimeout(() => {
|
|
830
|
+
inputs.placeholders.invalidateGrid();
|
|
831
|
+
inputs.placeholdersBreadth.invalidateGrid();
|
|
832
|
+
}, 100);
|
|
710
833
|
});
|
|
711
834
|
return dialog;
|
|
712
835
|
} catch (err: any) {
|
|
@@ -715,10 +838,16 @@ async function getPolyToolEnumerateDialog(
|
|
|
715
838
|
}
|
|
716
839
|
}
|
|
717
840
|
|
|
718
|
-
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
841
|
+
/** Executes enumeration and post-processes results into a DataFrame.
|
|
842
|
+
* Handles both macromolecule and template data roles, with optional
|
|
843
|
+
* atomic-level conversion (chirality, highlighting, rules).
|
|
844
|
+
*
|
|
845
|
+
* @param srcHelm Source HELM string to enumerate
|
|
846
|
+
* @param dataRole Whether the source is a {@link PolyToolDataRole.template} or {@link PolyToolDataRole.macromolecule}
|
|
847
|
+
* @param srcId Optional trivial name column info for generating IDs
|
|
848
|
+
* @param params Enumeration parameters (type, placeholders, keepOriginal)
|
|
849
|
+
* @param toAtomicLevel Post-processing options, or false to skip
|
|
850
|
+
*/
|
|
722
851
|
export async function polyToolEnumerateSeq(
|
|
723
852
|
srcHelm: string, dataRole: PolyToolDataRole, srcId: { value: string, colName: string } | null,
|
|
724
853
|
params: PolyToolEnumeratorParams,
|
|
@@ -731,7 +860,10 @@ export async function polyToolEnumerateSeq(
|
|
|
731
860
|
const rdKitModule = await getRdKitModule();
|
|
732
861
|
const monomerLib = libHelper.getMonomerLib(); // TODO: Get monomer lib from src SeqValueBase
|
|
733
862
|
|
|
863
|
+
// Core enumeration: produces [helm, id] pairs
|
|
734
864
|
const resList = doPolyToolEnumerateHelm(srcHelm, srcId?.value ?? '', params);
|
|
865
|
+
|
|
866
|
+
// Create result column based on data role (macromolecule uses HELM directly, template converts via Chain)
|
|
735
867
|
let enumCol: DG.Column<string>;
|
|
736
868
|
switch (dataRole) {
|
|
737
869
|
case PolyToolDataRole.macromolecule: {
|
|
@@ -754,11 +886,19 @@ export async function polyToolEnumerateSeq(
|
|
|
754
886
|
}
|
|
755
887
|
}
|
|
756
888
|
const enumeratorResDf = DG.DataFrame.fromColumns([enumCol]);
|
|
889
|
+
|
|
890
|
+
if (dataRole === PolyToolDataRole.macromolecule && params.fromHelmNotation) {
|
|
891
|
+
const c = DG.Column.fromType(DG.COLUMN_TYPE.STRING, `${params.fromHelmNotation.notationName}(${enumCol.name})`, enumeratorResDf.rowCount)
|
|
892
|
+
.init((rowIdx: number) => enumCol.isNone(rowIdx) ? null : params.fromHelmNotation!.convert(enumCol.get(rowIdx)!));
|
|
893
|
+
enumeratorResDf.columns.add(c);
|
|
894
|
+
}
|
|
895
|
+
|
|
757
896
|
await grok.data.detectSemanticTypes(enumeratorResDf);
|
|
758
897
|
if (dataRole == PolyToolDataRole.template)
|
|
759
898
|
PackageFunctions.applyNotationProviderForCyclized(enumCol, '-');
|
|
760
899
|
|
|
761
900
|
|
|
901
|
+
// Optional post-processing: convert to atomic level with chirality/highlighting
|
|
762
902
|
if (toAtomicLevel) {
|
|
763
903
|
let resHelmCol: DG.Column<string>;
|
|
764
904
|
if (dataRole === PolyToolDataRole.macromolecule) {
|
|
@@ -775,6 +915,7 @@ export async function polyToolEnumerateSeq(
|
|
|
775
915
|
}
|
|
776
916
|
}
|
|
777
917
|
|
|
918
|
+
// Add trivial name ID column if the source had one
|
|
778
919
|
if (srcId) {
|
|
779
920
|
const enumIdCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, srcId.colName, resList.length)
|
|
780
921
|
.init((rowIdx: number) => resList[rowIdx][1]);
|
|
@@ -61,6 +61,39 @@ function getPtEnumeratorMatrix(m: HelmMol, placeholders: PolyToolPlaceholder[]):
|
|
|
61
61
|
return resMolList;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** Parallel (zip) enumeration: the i-th result takes the i-th monomer from each placeholder position.
|
|
65
|
+
* All placeholders must have the same number of monomers (validated upstream).
|
|
66
|
+
* With K positions and N monomers each, produces exactly N results. */
|
|
67
|
+
function getPtEnumeratorParallel(m: HelmMol, placeholders: PolyToolPlaceholder[]): HelmMol[] {
|
|
68
|
+
if (placeholders.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
|
|
71
|
+
const monomerCount = placeholders[0].monomers.length;
|
|
72
|
+
for (const ph of placeholders) {
|
|
73
|
+
if (ph.monomers.length !== monomerCount)
|
|
74
|
+
throw new Error(`Parallel enumeration requires all positions to have the same number of monomers`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const resMolList: HelmMol[] = new Array<HelmMol>(monomerCount);
|
|
78
|
+
for (let i = 0; i < monomerCount; i++) {
|
|
79
|
+
const resM = m.clone() as HelmMol;
|
|
80
|
+
const nameParts: string[] = [];
|
|
81
|
+
for (const ph of placeholders) {
|
|
82
|
+
const pos = ph.position;
|
|
83
|
+
const newSymbol = ph.monomers[i];
|
|
84
|
+
const oldSymbol = resM.atoms[pos].elem;
|
|
85
|
+
resM.atoms[pos].elem = newSymbol;
|
|
86
|
+
|
|
87
|
+
const idOldSymbol = oldSymbol?.length > 1 ? `[${oldSymbol}]` : oldSymbol;
|
|
88
|
+
const idNewSymbol = newSymbol?.length > 1 ? `[${newSymbol}]` : newSymbol;
|
|
89
|
+
nameParts.push(`${idOldSymbol}${pos + 1}${idNewSymbol}`);
|
|
90
|
+
}
|
|
91
|
+
resM.name = `${m.name}-${nameParts.join('-')}`;
|
|
92
|
+
resMolList[i] = resM;
|
|
93
|
+
}
|
|
94
|
+
return resMolList;
|
|
95
|
+
}
|
|
96
|
+
|
|
64
97
|
function getPtEnumeratorBreadth(m: HelmMol, placeholdersBreadth: PolyToolBreadthPlaceholder[]): HelmMol[] {
|
|
65
98
|
if (placeholdersBreadth.length == 0)
|
|
66
99
|
return [];
|
|
@@ -90,6 +123,10 @@ export function doPolyToolEnumerateHelm(
|
|
|
90
123
|
resMolList = getPtEnumeratorSingle(molHandler.m, params.placeholders);
|
|
91
124
|
break;
|
|
92
125
|
}
|
|
126
|
+
case PolyToolEnumeratorTypes.Parallel: {
|
|
127
|
+
resMolList = getPtEnumeratorParallel(molHandler.m, params.placeholders);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
93
130
|
case PolyToolEnumeratorTypes.Matrix: {
|
|
94
131
|
resMolList = getPtEnumeratorMatrix(molHandler.m, params.placeholders);
|
|
95
132
|
break;
|