@datagrok/bio 2.17.4 → 2.17.6

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.
Binary file
@@ -0,0 +1,6 @@
1
+ model,mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
2
+ MAINTAINED,21.0,6,160,110,3.90,2.620,16.46,0,1,4,4
3
+ WORSENED,21.0,6,160,110,3.90,2.875,17.02,0,1,4,4
4
+ MAINTAINED,22.8,4,108,93,3.85,2.320,18.61,1,1,4,1
5
+ IMPROVED,21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
6
+ WORSENED,18.7,8,360,175,3.15,3.440,17.02,0,0,3,2
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Leonid Stolbov",
6
6
  "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.17.4",
8
+ "version": "2.17.6",
9
9
  "description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
10
10
  "repository": {
11
11
  "type": "git",
package/src/package.ts CHANGED
@@ -85,7 +85,7 @@ export async function getMonomerLibHelper(): Promise<IMonomerLibHelper> {
85
85
  return await MonomerLibManager.getInstance();
86
86
  }
87
87
 
88
- export let hydrophobPalette: SeqPaletteCustom | null = null;
88
+ //export let hydrophobPalette: SeqPaletteCustom | null = null;
89
89
 
90
90
  export class SeqPaletteCustom implements SeqPalette {
91
91
  private readonly _palette: { [m: string]: string };
@@ -128,39 +128,42 @@ async function initBioInt() {
128
128
  libSettings.explicit = [];
129
129
  await setUserLibSettings(libSettings);
130
130
  }
131
- libHelper.awaitLoaded(Infinity).then(() => {
132
- // Do not wait for monomers and sets loaded
133
- return Promise.all([libHelper.loadMonomerLib(), libHelper.loadMonomerSets()]);
134
- });
131
+ await libHelper.awaitLoaded(Infinity);
132
+ if (!libHelper.initialLoadCompleted)
133
+ await libHelper.loadMonomerLib();
134
+ // Do not wait for monomers and sets loaded
135
+ libHelper.loadMonomerSets();
135
136
  const monomerLib = libHelper.getMonomerLib();
136
137
  const monomerSets = libHelper.getMonomerSets();
137
138
  // finally log
138
139
  const t2: number = window.performance.now();
139
140
  _package.logger.debug(`${logPrefix}, loading ET: ${t2 - t1} ms`);
140
141
 
141
- const monomers: string[] = [];
142
- const logPs: number[] = [];
142
+ // const monomers: string[] = [];
143
+ // const logPs: number[] = [];
143
144
 
144
145
  const seqHelper = new SeqHelper(libHelper, rdKitModule);
145
146
  _package.completeInit(seqHelper, monomerLib, monomerSets, rdKitModule);
146
- const series = monomerLib!.getMonomerMolsByPolymerType('PEPTIDE')!;
147
- Object.keys(series).forEach((symbol) => {
148
- monomers.push(symbol);
149
- const block = series[symbol].replaceAll('#R', 'O ');
150
- const mol = rdKitModule.get_mol(block);
151
- const logP = JSON.parse(mol.get_descriptors()).CrippenClogP;
152
- logPs.push(logP);
153
- mol?.delete();
154
- });
155
147
 
156
- const sum = logPs.reduce((a, b) => a + b, 0);
157
- const avg = (sum / logPs.length) || 0;
148
+ // NB! do not delete the code below. not used now but in future we might use hydrophobicity palette
149
+ // const series = monomerLib!.getMonomerMolsByPolymerType('PEPTIDE')!;
150
+ // Object.keys(series).forEach((symbol) => {
151
+ // monomers.push(symbol);
152
+ // const block = series[symbol].replaceAll('#R', 'O ');
153
+ // const mol = rdKitModule.get_mol(block);
154
+ // const logP = JSON.parse(mol.get_descriptors()).CrippenClogP;
155
+ // logPs.push(logP);
156
+ // mol?.delete();
157
+ // });
158
+
159
+ // const sum = logPs.reduce((a, b) => a + b, 0);
160
+ // const avg = (sum / logPs.length) || 0;
158
161
 
159
- const palette: { [monomer: string]: string } = {};
160
- for (let i = 0; i < monomers.length; i++)
161
- palette[monomers[i]] = logPs[i] < avg ? '#4682B4' : '#DC143C';
162
+ // const palette: { [monomer: string]: string } = {};
163
+ // for (let i = 0; i < monomers.length; i++)
164
+ // palette[monomers[i]] = logPs[i] < avg ? '#4682B4' : '#DC143C';
162
165
 
163
- hydrophobPalette = new SeqPaletteCustom(palette);
166
+ // hydrophobPalette = new SeqPaletteCustom(palette);
164
167
 
165
168
  _package.logger.debug(`${logPrefix}, end`);
166
169
  }
@@ -961,19 +964,36 @@ export async function manageMonomersView() {
961
964
  await monomerManager.getViewRoot();
962
965
  }
963
966
 
964
- //name: Manage Monomers
967
+ //name: Monomers
965
968
  //tags: app
966
- //meta.browsePath: Peptides | Monomers
967
- export async function manageMonomersApp() {
968
- const monomerManager = await MonomerManager.getInstance();
969
- await monomerManager.getViewRoot();
970
- }
969
+ //meta.browsePath: Peptides
970
+ //meta.icon: files/icons/monomers.png
971
+ //output: view v
972
+ export async function manageLibrariesApp(): Promise<DG.View> {
973
+ return await showManageLibrariesView(false);
974
+ }
975
+
976
+ //name: Monomer Manager Tree Browser
977
+ //input: dynamic treeNode
978
+ //input: view browseView
979
+ export async function manageLibrariesAppTreeBrowser(treeNode: DG.TreeViewGroup, browseView: DG.BrowseView) {
980
+ const libraries = (await (await MonomerLibManager.getInstance()).getFileManager()).getValidLibraryPaths();
981
+ libraries.forEach((libName) => {
982
+ const libNode = treeNode.item(libName);
983
+ // eslint-disable-next-line rxjs/no-ignored-subscription, rxjs/no-async-subscribe
984
+ libNode.onSelected.subscribe(async () => {
985
+ const monomerManager = await MonomerManager.getNewInstance();
986
+ browseView.preview = await monomerManager.getViewRoot(libName, false);
987
+ });
971
988
 
972
- //name: Manage Libraries
973
- //tags: app
974
- //meta.browsePath: Peptides | Monomers
975
- export async function manageLibrariesApp(): Promise<void> {
976
- await showManageLibrariesView();
989
+ libNode.root.addEventListener('dblclick', async (e) => {
990
+ e.preventDefault();
991
+ e.stopImmediatePropagation();
992
+ const monomerManager = await MonomerManager.getInstance();
993
+ await monomerManager.getViewRoot(libName, true);
994
+ monomerManager.resetCurrentRowFollowing();
995
+ });
996
+ });
977
997
  }
978
998
 
979
999
  //name: saveAsFasta
@@ -1,3 +1,5 @@
1
+ /* eslint-disable max-lines */
2
+ /* eslint-disable max-lines-per-function */
1
3
  import * as grok from 'datagrok-api/grok';
2
4
  import * as ui from 'datagrok-api/ui';
3
5
  import * as DG from 'datagrok-api/dg';
@@ -271,6 +273,7 @@ MWRSWY-CKHPMWRSWY-CKHP`;
271
273
  testSpgi = 'testSpgi',
272
274
  testSpgi100 = 'testSpgi100',
273
275
  testUrl = 'testUrl',
276
+ fastaNegativeWords = 'fasta_negative_words'
274
277
  }
275
278
 
276
279
  const samples: { [key: string]: string } = {
@@ -295,6 +298,7 @@ MWRSWY-CKHPMWRSWY-CKHP`;
295
298
  [Samples.testSpgi100]: 'System:AppData/Bio/tests/testSpgi100.csv',
296
299
  [Samples.testSpgi]: 'System:AppData/Bio/tests/SPGI-derived.csv',
297
300
  [Samples.testUrl]: 'System:AppData/Bio/tests/testUrl.csv',
301
+ [Samples.fastaNegativeWords]: 'System:AppData/Bio/tests/fasta_negative_words.csv',
298
302
  };
299
303
 
300
304
  const _samplesDfs: { [key: string]: Promise<DG.DataFrame> } = {};
@@ -505,6 +509,10 @@ MWRSWY-CKHPMWRSWY-CKHP`;
505
509
  test('samplesTestUrl', async () => {
506
510
  await _testDf(readSamples(Samples.testUrl), {} /* no positive */, seqHelper);
507
511
  });
512
+
513
+ test('samplesFastaNegativeWords', async () => {
514
+ await _testDf(readSamples(Samples.fastaNegativeWords), {} /* no positive */, seqHelper);
515
+ });
508
516
  });
509
517
 
510
518
  export async function _testNegList(list: string[]): Promise<void> {
@@ -30,7 +30,8 @@ declare const window: MonomerLibWindowType;
30
30
  export class MonomerLibManager implements IMonomerLibHelper {
31
31
  private readonly _monomerLib = new MonomerLib({}, 'MAIN');
32
32
  private readonly _monomerSets = new MonomerSet('MAIN', []);
33
-
33
+ private _initialLoadCompleted: boolean = false;
34
+ public get initialLoadCompleted(): boolean { return this._initialLoadCompleted; }
34
35
  private _eventManager: MonomerLibFileEventManager;
35
36
 
36
37
  public get eventManager(): IMonomerLibFileEventManager { return this._eventManager; }
@@ -85,7 +86,8 @@ export class MonomerLibManager implements IMonomerLibHelper {
85
86
  return this._monomerSets;
86
87
  }
87
88
 
88
- /** Object containing symbols for each type of polymer where duplicate monomers are found in different libs (based on symbol as key) */
89
+ /** Object containing symbols for each type of polymer where duplicate monomers
90
+ * are found in different libs (based on symbol as key) */
89
91
  get duplicateMonomers() {
90
92
  return this._monomerLib.duplicateMonomers;
91
93
  }
@@ -107,6 +109,7 @@ export class MonomerLibManager implements IMonomerLibHelper {
107
109
  this._fileManagerPromise = (async () => {
108
110
  const fileManager: MonomerLibFileManager =
109
111
  await MonomerLibFileManager.create(this, this._eventManager, this.logger);
112
+ await fileManager.initializedPromise;
110
113
  return fileManager;
111
114
  })();
112
115
  }
@@ -158,6 +161,7 @@ export class MonomerLibManager implements IMonomerLibHelper {
158
161
  });
159
162
  })),]);
160
163
  this._monomerLib.updateLibs(libs, reload);
164
+ this._initialLoadCompleted = true;
161
165
  } catch (err: any) {
162
166
  // WARNING: This function is not allowed to throw any exception,
163
167
  // because it will prevent further handling monomer library settings
@@ -28,6 +28,7 @@ import {_package} from '../../../package';
28
28
  * All files **must** be aligned to the HELM standard before adding. */
29
29
  export class MonomerLibFileManager implements IMonomerLibFileManager {
30
30
  public filesPromise: Promise<void> = Promise.resolve();
31
+ public initializedPromise: Promise<void> = Promise.resolve();
31
32
 
32
33
  private constructor(
33
34
  private readonly fileValidator: MonomerLibFileValidator,
@@ -35,8 +36,19 @@ export class MonomerLibFileManager implements IMonomerLibFileManager {
35
36
  public readonly eventManager: MonomerLibFileEventManager,
36
37
  private readonly logger: ILogger,
37
38
  ) {
39
+ // these both are behavioral subjects, i.e. they emit their value when subscribed.
40
+ // initial creation/request from bio package on awaiting files promise makes no sense,
41
+ // until the subscription fires first time
42
+ let resolveFilesPromise: () => void;
43
+ let initialized = false;
44
+ this.initializedPromise =
45
+ Promise.race([DG.delay(1000), new Promise<void>((resolve) => resolveFilesPromise = resolve)]);
38
46
  const _libSub = this.eventManager.updateValidLibraryFileListRequested$.subscribe(() => {
39
47
  this.updateValidLibList().then(() => {});
48
+ if (!initialized) {
49
+ initialized = true;
50
+ resolveFilesPromise();
51
+ }
40
52
  });
41
53
  const _setSub = this.eventManager.updateValidSetFileListRequested$.subscribe(() => {
42
54
  this.updateValidSetList().then(() => {});
@@ -181,7 +193,7 @@ export class MonomerLibFileManager implements IMonomerLibFileManager {
181
193
 
182
194
  if (this.libListHasChanged(validLibPathList)) {
183
195
  this.eventManager.changeValidLibPathList(validLibPathList);
184
- this.libHelper.loadMonomerLib(true);
196
+ await this.libHelper.loadMonomerLib(true);
185
197
  }
186
198
  // console.log(`files after validation:`, this.libraryEventManager.getValidFilesPathList());
187
199
 
@@ -24,8 +24,8 @@ export async function showManageLibrariesDialog(): Promise<void> {
24
24
  await DialogWrapper.showDialog();
25
25
  }
26
26
 
27
- export async function showManageLibrariesView() {
28
- await LibManagerView.showView();
27
+ export async function showManageLibrariesView(addView = true) {
28
+ return await LibManagerView.showView(addView);
29
29
  }
30
30
 
31
31
  export async function getMonomerLibraryManagerLink(): Promise<DG.Widget> {
@@ -68,8 +68,9 @@ class MonomerLibraryManagerWidget {
68
68
  private async createWidget() {
69
69
  const content = await this.getWidgetContent();
70
70
  const monomerLibHelper = await getMonomerLibHelper();
71
+ // eslint-disable-next-line rxjs/no-ignored-subscription
71
72
  monomerLibHelper.eventManager.addLibraryFileRequested$.subscribe(
72
- async () => await this.promptToAddLibraryFiles()
73
+ () => this.promptToAddLibraryFiles()
73
74
  );
74
75
  return new DG.Widget(content);
75
76
  }
@@ -109,11 +110,13 @@ class LibraryControlsManager {
109
110
  private fileManager: IMonomerLibFileManager,
110
111
  private readonly userLibSettings: UserLibSettings,
111
112
  ) {
113
+ // eslint-disable-next-line rxjs/no-ignored-subscription
112
114
  this.fileManager.eventManager.updateUIControlsRequested$.subscribe(() => {
113
115
  this.updateControlsForm();
114
116
  });
115
- this.fileManager.eventManager.librarySelectionRequested$.subscribe(async ([fileName, isSelected]) => {
116
- await this.updateLibrarySelectionStatus(isSelected, fileName);
117
+ // eslint-disable-next-line rxjs/no-ignored-subscription
118
+ this.fileManager.eventManager.librarySelectionRequested$.subscribe(([fileName, isSelected]) => {
119
+ this.updateLibrarySelectionStatus(isSelected, fileName);
117
120
  });
118
121
  }
119
122
 
@@ -222,6 +225,7 @@ class DialogWrapper {
222
225
  static async showDialog(): Promise<void> {
223
226
  if (!DialogWrapper._instance) {
224
227
  DialogWrapper._instance = new DialogWrapper();
228
+ // eslint-disable-next-line rxjs/no-ignored-subscription
225
229
  DialogWrapper._instance.closeDialogSubject$.subscribe(
226
230
  () => { DialogWrapper._instance.dialog = undefined; }
227
231
  );
@@ -251,7 +255,10 @@ class DialogWrapper {
251
255
  'Upload new HELM monomer library'
252
256
  );
253
257
  dialog.add(widget);
254
- dialog.onClose.subscribe(() => this.closeDialogSubject$.next());
258
+ const sub = dialog.onClose.subscribe(() => {
259
+ this.closeDialogSubject$.next();
260
+ sub.unsubscribe();
261
+ });
255
262
  return dialog;
256
263
  }
257
264
  }
@@ -259,10 +266,11 @@ class DialogWrapper {
259
266
  class LibManagerView {
260
267
  private constructor() {};
261
268
  private static _instance: LibManagerView;
269
+ static viewName = 'Manage Monomer Libraries';
262
270
  private _view: DG.View;
263
271
  private _duplicateManager: DuplicateMonomerManager;
264
272
  private libManager: MonomerLibManager;
265
- private async getView() {
273
+ private async getView(addView = true) {
266
274
  const eventManager = MonomerLibFileEventManager.getInstance();
267
275
  const widget = (await MonomerLibraryManagerWidget.getInstance()).widget;
268
276
  const addButton = ui.bigButton('Add',
@@ -275,7 +283,10 @@ class LibManagerView {
275
283
  this._duplicateManager.root],
276
284
  {style: {width: '100%', height: '100%'}},
277
285
  true);
278
- this._view = grok.shell.newView('Manage Monomer Libraries', [v]);
286
+ this._view = DG.View.fromRoot(v);
287
+ this._view.name = LibManagerView.viewName;
288
+ if (addView)
289
+ grok.shell.addView(this._view);
279
290
 
280
291
  ui.tools.waitForElementInDom(v).then(() => {
281
292
  setTimeout(() => {
@@ -300,24 +311,32 @@ class LibManagerView {
300
311
  }
301
312
  }));
302
313
  });
314
+ return this._view;
303
315
  //grok.shell.dockManager.dock(this._duplicateManager.root, DG.DOCK_TYPE.RIGHT, null, '', 0.4);
304
316
  }
305
317
 
306
- static async showView() {
318
+ static async showView(addView = true) {
307
319
  if (!LibManagerView._instance)
308
320
  LibManagerView._instance = new LibManagerView();
309
321
  if (!LibManagerView._instance._duplicateManager)
310
322
  LibManagerView._instance._duplicateManager = await DuplicateMonomerManager.getInstance();
311
323
  if (!LibManagerView._instance.libManager)
312
324
  LibManagerView._instance.libManager = await MonomerLibManager.getInstance();
313
- if (LibManagerView._instance._view &&
325
+ if (addView && LibManagerView._instance._view &&
314
326
  Array.from(grok.shell.views).find((v) => v.id && v.id === LibManagerView._instance._view.id)) {
315
327
  grok.shell.v = LibManagerView._instance._view;
316
328
  await LibManagerView._instance._duplicateManager.refresh();
317
- return;
329
+ return LibManagerView._instance._view;
318
330
  }
319
- LibManagerView._instance.getView();
331
+ // something can conflict with browse view, so need to make sure that we close all existing views
332
+ LibManagerView.closeExistingViews();
333
+ return LibManagerView._instance.getView(addView);
320
334
  }
335
+
336
+ private static closeExistingViews() {
337
+ Array.from(grok.shell.views).filter((v) => v.name === LibManagerView.viewName).forEach((v) => v.close());
338
+ }
339
+
321
340
  async mergeSelectedLibs() {
322
341
  const libraryExistsError = 'Library with this name already exists';
323
342
  const libManager = await MonomerLibManager.getInstance();
@@ -51,9 +51,14 @@ export const MONOMER_DF_COLUMNS = {
51
51
 
52
52
 
53
53
  export class MonomerManager implements IMonomerManager {
54
- private adjustColWidths() {
54
+ private adjustTable() {
55
+ if (this.tv?.dataFrame) {
56
+ grok.data.detectSemanticTypes(this.tv.dataFrame);
57
+ this.tv.dataFrame.meta.detectSemanticTypes();
58
+ }
55
59
  setTimeout(() => {
56
60
  if (this.tv?.grid) {
61
+ this.tv!.grid.props.allowEdit = false;
57
62
  this.tv!.grid.col(MONOMER_DF_COLUMN_NAMES.NAME)!.width = 100;
58
63
  this.tv!.grid.col(MONOMER_DF_COLUMN_NAMES.SYMBOL)!.width = 70;
59
64
  }
@@ -75,7 +80,7 @@ export class MonomerManager implements IMonomerManager {
75
80
  const df = await this.getMonomersDf(this.libInput.value!);
76
81
  if (this.tv?.dataFrame) {
77
82
  this.tv.dataFrame = df;
78
- this.adjustColWidths();
83
+ this.adjustTable();
79
84
  if (scrollToRowSymbol != undefined) {
80
85
  setTimeout(() => {
81
86
  const col = df.col(MONOMER_DF_COLUMN_NAMES.SYMBOL)!;
@@ -99,6 +104,13 @@ export class MonomerManager implements IMonomerManager {
99
104
  return this.instance;
100
105
  }
101
106
 
107
+ public static async getNewInstance(): Promise<MonomerManager> {
108
+ const monManager = await MonomerLibManager.getInstance();
109
+ await monManager.awaitLoaded();
110
+ await monManager.loadLibrariesPromise;
111
+ return new MonomerManager(monManager);
112
+ }
113
+
102
114
  async createNewMonomerLib(libName: string, _monomers: Monomer[]): Promise<void> {
103
115
  this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, true);
104
116
  try {
@@ -168,11 +180,11 @@ export class MonomerManager implements IMonomerManager {
168
180
  return this._newMonomerForm;
169
181
  }
170
182
 
171
- private async getMonomersTableView(fileName?: string): Promise<DG.TableView> {
183
+ private async getMonomersTableView(fileName?: string, addView = true): Promise<DG.TableView> {
172
184
  const df = await this.getMonomersDf(fileName);
173
- this.tv = DG.TableView.create(df, true);
174
- //const f = tv.filters();
175
- this.adjustColWidths();
185
+ this.tv = DG.TableView.create(df, addView);
186
+
187
+ this.adjustTable();
176
188
  this.tv.subs.push(
177
189
  grok.events.onContextMenu.subscribe(({args}) => {
178
190
  if (!args || !args.menu || !args.context || args.context.type !== DG.VIEWER.GRID || !args.context.tableView ||
@@ -205,21 +217,28 @@ export class MonomerManager implements IMonomerManager {
205
217
  return this.tv;
206
218
  }
207
219
 
220
+ private static closeAllMonomerManagers() {
221
+ Array.from(grok.shell.tableViews ?? []).filter((v) => v.name === MonomerManager.VIEW_NAME).forEach((v) => v.close());
222
+ }
223
+
208
224
  private findActiveManagerView() {
209
225
  if (!this.tv)
210
226
  return null;
211
227
  const tv = Array.from(grok.shell.tableViews ?? []).find((tv) => tv.id === this.tv!.id);
212
228
  if (tv)
213
229
  grok.shell.v = tv;
230
+ else
231
+ MonomerManager.closeAllMonomerManagers();
232
+
214
233
  return tv ?? null;
215
234
  }
216
235
 
217
236
  private _skipLibInputOnchange: boolean = false;
218
237
 
219
- async getViewRoot(libName?: string) {
238
+ async getViewRoot(libName?: string, addView = true) {
220
239
  const availableMonLibs = (await this.monomerLibManamger.getFileManager()).getValidLibraryPaths();
221
240
  this._newMonomerForm.molSketcher.resize();
222
- if ((this.tv = this.findActiveManagerView()) && (libName ?? this.libInput.value)) {
241
+ if (addView && (this.tv = this.findActiveManagerView()) && (libName ?? this.libInput.value)) {
223
242
  // get monomer library list
224
243
  try {
225
244
  this._skipLibInputOnchange = true;
@@ -233,12 +252,12 @@ export class MonomerManager implements IMonomerManager {
233
252
  }
234
253
  const df = await this.getMonomersDf(libName);
235
254
  this.tv.dataFrame = df;
236
- this.adjustColWidths();
255
+ this.adjustTable();
237
256
  return this.tv;
238
257
  }
239
258
 
240
259
  libName ??= availableMonLibs[0];
241
- this.tv = await this.getMonomersTableView(libName);
260
+ this.tv = await this.getMonomersTableView(libName, addView);
242
261
 
243
262
  // remove project save button and download from ribbons
244
263
  let ribbons = this.tv.getRibbonPanels();
@@ -300,7 +319,7 @@ export class MonomerManager implements IMonomerManager {
300
319
  if (this._skipLibInputOnchange) return;
301
320
  const df = await this.getMonomersDf(this.libInput.value!);
302
321
  this.tv!.dataFrame = df;
303
- this.adjustColWidths();
322
+ this.adjustTable();
304
323
  } catch (e) {
305
324
  console.error(e);
306
325
  }
@@ -308,7 +327,9 @@ export class MonomerManager implements IMonomerManager {
308
327
  this.libInput.addOptions(ui.icons.add(() => { this.createNewLibDialog(); }, 'Create new monomer library...'));
309
328
  const monForm = this._newMonomerForm.form;
310
329
  monForm.prepend(this.libInput.root);
311
- this.tv.dockManager.dock(monForm, DG.DOCK_TYPE.LEFT, null, undefined, 0.4);
330
+ ui.tools.waitForElementInDom(this.tv.root).then(() => {
331
+ this.tv!.dockManager.dock(monForm, DG.DOCK_TYPE.LEFT, null, undefined, 0.4);
332
+ });
312
333
  return this.tv;
313
334
  }
314
335
 
@@ -403,6 +424,10 @@ export class MonomerManager implements IMonomerManager {
403
424
  this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, false);
404
425
  }
405
426
  }
427
+
428
+ public resetCurrentRowFollowing() {
429
+ this._newMonomerForm.molChanged = false;
430
+ }
406
431
  }
407
432
 
408
433
  // some monomers might be in form of cap groups in place of r-groups (with supplied rgroups info), this function will convert them to r-groups
@@ -474,7 +499,7 @@ function getCaseInvariantValue<T>(obj: { [key: string]: T }, key: string): T | u
474
499
 
475
500
  // some r groups for some monomers can lack smiles, or something else :D this function will try to fix that
476
501
  function resolveRGroupInfo(rgps: RGroup[]): RGroup[] {
477
- return rgps.map((rg) => {
502
+ return (rgps.map((rg) => {
478
503
  const cp = assignObjectCaseInvariant(RGROUP_FIELDS, rg);
479
504
  const smi = getCaseInvariantValue(cp, HELM_RGROUP_FIELDS.CAP_GROUP_SMILES_UPPERCASE);
480
505
  const altId = getCaseInvariantValue(cp, HELM_RGROUP_FIELDS.ALTERNATE_ID);
@@ -498,7 +523,7 @@ function resolveRGroupInfo(rgps: RGroup[]): RGroup[] {
498
523
  cp[HELM_RGROUP_FIELDS.ALTERNATE_ID] = `${label}-${capName}`;
499
524
  }
500
525
  return cp;
501
- }) as RGroup[];
526
+ }) as RGroup[]).sort((a, b) => a.label?.localeCompare(b.label ?? '') ?? 0);
502
527
  }
503
528
 
504
529
 
@@ -522,6 +547,7 @@ class MonomerForm implements INewMonomerForm {
522
547
  rgroupsGridRoot: HTMLElement;
523
548
  private _molChanged: boolean = false;
524
549
  get molChanged() { return this._molChanged; }
550
+ set molChanged(v: boolean) { this._molChanged = v; }
525
551
  private saveValidationResult?: string | null = null;
526
552
  private triggerMolChange: boolean = true; // makes sure that change is not triggered by copying the molecule from grid
527
553
  inputsTabControl: DG.TabControl;
@@ -607,7 +633,7 @@ class MonomerForm implements INewMonomerForm {
607
633
  } as unknown as RGroup;
608
634
  });
609
635
  // if (this.rgroupsGrid.items.length !== rGroupItems.length)
610
- this.rgroupsGrid.items = rGroupItems.sort((a, b) => a.label.localeCompare(b.label));
636
+ this.rgroupsGrid.items = rGroupItems.sort((a, b) => a.label?.localeCompare(b.label ?? '') ?? 0);
611
637
  this.rgroupsGrid.render();
612
638
  this.rgroupsGridRoot.style.display = 'flex';
613
639
  const mostSimilar = await mostSimilarNaturalAnalog(capSmiles(smiles, rGroupItems), this.polymerTypeInput.value ?? '');