@datagrok/bio 2.14.2 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/css/monomer-manager.css +66 -0
  3. package/detectors.js +7 -2
  4. package/dist/111.js +1 -1
  5. package/dist/111.js.map +1 -1
  6. package/dist/234.js +1 -1
  7. package/dist/234.js.map +1 -1
  8. package/dist/242.js.map +1 -1
  9. package/dist/603.js +1 -1
  10. package/dist/603.js.map +1 -1
  11. package/dist/682.js +1 -1
  12. package/dist/682.js.map +1 -1
  13. package/dist/705.js +1 -1
  14. package/dist/705.js.map +1 -1
  15. package/dist/778.js +1 -1
  16. package/dist/778.js.map +1 -1
  17. package/dist/793.js +1 -1
  18. package/dist/793.js.map +1 -1
  19. package/dist/801.js +2 -0
  20. package/dist/801.js.map +1 -0
  21. package/dist/950.js +1 -1
  22. package/dist/950.js.map +1 -1
  23. package/dist/980.js +2 -0
  24. package/dist/980.js.map +1 -0
  25. package/dist/package-test.js +6 -6
  26. package/dist/package-test.js.map +1 -1
  27. package/dist/package.js +5 -5
  28. package/dist/package.js.map +1 -1
  29. package/files/monomer-libraries/polytool-lib.json +48 -0
  30. package/files/monomer-libraries/sample-lib-Aca-colored.json +2 -2
  31. package/package.json +20 -12
  32. package/src/analysis/sequence-space.ts +2 -1
  33. package/src/demo/bio05-helm-msa-sequence-space.ts +1 -1
  34. package/src/package-test.ts +3 -1
  35. package/src/package-types.ts +9 -1
  36. package/src/package.ts +77 -33
  37. package/src/seq_align.ts +1 -1
  38. package/src/substructure-search/substructure-search.ts +2 -2
  39. package/src/tests/WebLogo-project-tests.ts +3 -4
  40. package/src/tests/activity-cliffs-tests.ts +5 -18
  41. package/src/tests/detectors-benchmark-tests.ts +24 -9
  42. package/src/tests/mm-distance-tests.ts +4 -3
  43. package/src/tests/monomer-libraries-tests.ts +3 -3
  44. package/src/tests/seq-handler-get-helm-tests.ts +88 -0
  45. package/src/tests/sequence-space-test.ts +4 -3
  46. package/src/tests/to-atomic-level-tests.ts +2 -0
  47. package/src/tests/to-atomic-level-ui-tests.ts +74 -0
  48. package/src/utils/cell-renderer.ts +3 -0
  49. package/src/utils/convert.ts +2 -2
  50. package/src/utils/cyclized.ts +20 -1
  51. package/src/utils/dimerized.ts +12 -0
  52. package/src/utils/get-region-func-editor.ts +1 -1
  53. package/src/utils/helm-to-molfile/converter/converter.ts +58 -30
  54. package/src/utils/helm-to-molfile/converter/mol-atoms.ts +2 -0
  55. package/src/utils/helm-to-molfile/converter/mol-bonds.ts +2 -0
  56. package/src/utils/helm-to-molfile/converter/mol-wrapper.ts +5 -1
  57. package/src/utils/helm-to-molfile/converter/monomer-wrapper.ts +7 -3
  58. package/src/utils/helm-to-molfile/converter/polymer.ts +21 -6
  59. package/src/utils/helm-to-molfile/converter/types.ts +11 -0
  60. package/src/utils/helm-to-molfile/utils.ts +11 -15
  61. package/src/utils/monomer-lib/lib-manager.ts +15 -1
  62. package/src/utils/monomer-lib/library-file-manager/file-manager.ts +1 -1
  63. package/src/utils/monomer-lib/library-file-manager/file-validator.ts +8 -0
  64. package/src/utils/monomer-lib/library-file-manager/ui.ts +150 -3
  65. package/src/utils/monomer-lib/monomer-lib.ts +59 -21
  66. package/src/utils/monomer-lib/monomer-manager/duplicate-monomer-manager.ts +155 -0
  67. package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +924 -0
  68. package/src/utils/multiple-sequence-alignment-ui.ts +3 -3
  69. package/src/utils/seq-helper/index.ts +1 -0
  70. package/src/utils/seq-helper/seq-helper.ts +131 -0
  71. package/src/utils/sequence-to-mol.ts +47 -18
  72. package/src/widgets/bio-substructure-filter.ts +9 -7
  73. package/src/widgets/package-settings-editor-widget.ts +6 -6
  74. package/src/widgets/representations.ts +12 -12
  75. package/dist/449.js +0 -2
  76. package/dist/449.js.map +0 -1
  77. /package/src/tests/{seq-handler-get-region.ts → seq-handler-get-region-tests.ts} +0 -0
@@ -63,7 +63,7 @@ export class MonomerLibFileManager implements IMonomerLibFileManager {
63
63
  }
64
64
 
65
65
  /** Add standard .json monomer library */
66
- async addLibraryFile(fileContent: string, fileName: string): Promise<void> {
66
+ async addLibraryFile(fileContent: string, fileName: string, reload = true): Promise<void> {
67
67
  try {
68
68
  const alreadyFileExists = await grok.dapi.files.exists(LIB_PATH + `${fileName}`);
69
69
  if (alreadyFileExists) {
@@ -45,6 +45,7 @@ export class MonomerLibFileValidator {
45
45
 
46
46
  private validateJsonContent(jsonContent: any[], fileName: string): boolean {
47
47
  let isValid = true;
48
+ const existingMonomerSymbols = new Set<string>();
48
49
  for (const monomer of jsonContent) {
49
50
  const name = monomer[REQ.SYMBOL] ?? monomer[REQ.ID] ?? monomer[REQ.NAME] ?? NA_CODE;
50
51
  isValid = this.validateMonomerSchema(monomer);
@@ -59,6 +60,13 @@ export class MonomerLibFileValidator {
59
60
  );
60
61
  break;
61
62
  }
63
+ const key = `${(monomer[REQ.POLYMER_TYPE] ?? '')}-${name}`;
64
+ if (existingMonomerSymbols.has(key)) {
65
+ console.warn(`Bio: Monomer Library File Validator file ${fileName}, monomer '${name}' is duplicated.`,
66
+ 'Please, verify that the monomer library file does not contain duplicated monomer symbols.'
67
+ );
68
+ }
69
+ existingMonomerSymbols.add(key);
62
70
  }
63
71
  return isValid;
64
72
  }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  /* Do not change these import lines to match external modules in webpack configuration */
2
3
  import * as grok from 'datagrok-api/grok';
3
4
  import * as ui from 'datagrok-api/ui';
@@ -15,11 +16,18 @@ import {getMonomerLibHelper, IMonomerLibFileManager} from '@datagrok-libraries/b
15
16
 
16
17
  import {MonomerLibFileEventManager} from './event-manager';
17
18
  import {_package} from '../../../package';
19
+ import {MonomerManager} from '../monomer-manager/monomer-manager';
20
+ import {DuplicateMonomerManager} from '../monomer-manager/duplicate-monomer-manager';
21
+ import {MonomerLibManager} from '../lib-manager';
18
22
 
19
23
  export async function showManageLibrariesDialog(): Promise<void> {
20
24
  await DialogWrapper.showDialog();
21
25
  }
22
26
 
27
+ export async function showManageLibrariesView() {
28
+ await LibManagerView.showView();
29
+ }
30
+
23
31
  export async function getMonomerLibraryManagerLink(): Promise<DG.Widget> {
24
32
  const link = ui.label('Manage monomer libraries');
25
33
  $(link).addClass('d4-link-action');
@@ -52,6 +60,11 @@ class MonomerLibraryManagerWidget {
52
60
  return MonomerLibraryManagerWidget.instancePromise;
53
61
  }
54
62
 
63
+ static async reloadWidget(): Promise<void> {
64
+ const instance = await MonomerLibraryManagerWidget.getInstance();
65
+ instance._widget = await instance.createWidget();
66
+ }
67
+
55
68
  private async createWidget() {
56
69
  const content = await this.getWidgetContent();
57
70
  const monomerLibHelper = await getMonomerLibHelper();
@@ -125,7 +138,7 @@ class LibraryControlsManager {
125
138
  return inputsForm;
126
139
  }
127
140
 
128
- private updateControlsForm(): void {
141
+ public updateControlsForm(): void {
129
142
  const updatedForm = this._createControlsForm();
130
143
  $('.monomer-lib-controls-form').replaceWith(updatedForm);
131
144
  }
@@ -139,12 +152,16 @@ class LibraryControlsManager {
139
152
  const logPrefix = `${this.toLog()}.createLibInput()`;
140
153
  _package.logger.debug(`${logPrefix}, libFileName = '${libFileName}', start`);
141
154
  const isMonomerLibrarySelected = !this.userLibSettings.exclude.includes(libFileName);
142
- const libInput = ui.input.bool(libFileName, {value: isMonomerLibrarySelected, onValueChanged: (input) => {
143
- this.fileManager.eventManager.updateLibrarySelectionStatus(libFileName, input.value);
155
+ const libInput = ui.input.bool(libFileName, {value: isMonomerLibrarySelected, onValueChanged: () => {
156
+ this.fileManager.eventManager.updateLibrarySelectionStatus(libFileName, libInput.value);
144
157
  }});
145
158
  ui.tooltip.bind(libInput.root, `Include monomers from ${libFileName}`);
146
159
  const deleteIcon = ui.iconFA('trash-alt', () => this.promptForLibraryDeletion(libFileName));
160
+ const editIcon = ui.icons.edit(async () => {
161
+ grok.shell.v = await (await MonomerManager.getInstance()).getViewRoot(libFileName);
162
+ }, 'Edit monomer library');
147
163
  ui.tooltip.bind(deleteIcon, `Delete ${libFileName}`);
164
+ libInput.addOptions(editIcon);
148
165
  libInput.addOptions(deleteIcon);
149
166
  _package.logger.debug(`${logPrefix}, libFileName = '${libFileName}', end`);
150
167
  return libInput;
@@ -235,3 +252,133 @@ class DialogWrapper {
235
252
  return dialog;
236
253
  }
237
254
  }
255
+
256
+ class LibManagerView {
257
+ private constructor() {};
258
+ private static _instance: LibManagerView;
259
+ private _view: DG.View;
260
+ private _duplicateManager: DuplicateMonomerManager;
261
+ private libManager: MonomerLibManager;
262
+ private async getView() {
263
+ const eventManager = MonomerLibFileEventManager.getInstance();
264
+ const widget = (await MonomerLibraryManagerWidget.getInstance()).widget;
265
+ const addButton = ui.bigButton('Add',
266
+ () => eventManager.addLibraryFile(), 'Upload new HELM monomer library');
267
+ const mergeButton =
268
+ ui.bigButton('Merge', () => { this.mergeSelectedLibs(); }, 'Merge selected libraries into one');
269
+
270
+ const v = ui.splitH(
271
+ [ui.divV([widget.root, ui.buttonsInput([addButton, mergeButton])], {classes: 'ui-form'}),
272
+ this._duplicateManager.root],
273
+ {style: {width: '100%', height: '100%'}},
274
+ true);
275
+ this._view = grok.shell.newView('Manage Monomer Libraries', [v]);
276
+
277
+ ui.tools.waitForElementInDom(v).then(() => {
278
+ setTimeout(() => {
279
+ const children = Array.from(v.children as HTMLCollectionOf<HTMLElement>)
280
+ .filter((el) => el.classList.contains('ui-box'));
281
+ if (children.length !== 2)
282
+ return;
283
+ const [left, right] = children;
284
+ const combinedWidth = left.getBoundingClientRect().width + right.getBoundingClientRect().width;
285
+ const leftWidth = combinedWidth * 0.3;
286
+ left.style.width = `${leftWidth}px`;
287
+ const rightWidth = combinedWidth - leftWidth;
288
+ right.style.width = `${rightWidth}px`;
289
+ }, 100);
290
+ this._view.subs.push(grok.events.onCurrentViewChanged.subscribe(async () => {
291
+ try {
292
+ const inst = LibManagerView._instance;
293
+ if (inst && inst._view && 'id' in grok.shell.v && grok.shell.v.id === inst._view.id)
294
+ inst._duplicateManager?.refresh();
295
+ } catch (e) {
296
+ console.error(e);
297
+ }
298
+ }));
299
+ });
300
+ //grok.shell.dockManager.dock(this._duplicateManager.root, DG.DOCK_TYPE.RIGHT, null, '', 0.4);
301
+ }
302
+
303
+ static async showView() {
304
+ if (!LibManagerView._instance)
305
+ LibManagerView._instance = new LibManagerView();
306
+ if (!LibManagerView._instance._duplicateManager)
307
+ LibManagerView._instance._duplicateManager = await DuplicateMonomerManager.getInstance();
308
+ if (!LibManagerView._instance.libManager)
309
+ LibManagerView._instance.libManager = await MonomerLibManager.getInstance();
310
+ if (LibManagerView._instance._view &&
311
+ Array.from(grok.shell.views).find((v) => v.id && v.id === LibManagerView._instance._view.id)) {
312
+ grok.shell.v = LibManagerView._instance._view;
313
+ await LibManagerView._instance._duplicateManager.refresh();
314
+ return;
315
+ }
316
+ LibManagerView._instance.getView();
317
+ }
318
+ async mergeSelectedLibs() {
319
+ const libraryExistsError = 'Library with this name already exists';
320
+ const libManager = await MonomerLibManager.getInstance();
321
+ await libManager.awaitLoaded();
322
+ await libManager.loadLibrariesPromise;
323
+ if (!libManager.duplicatesHandled) {
324
+ grok.shell.warning(`Selected libraries contain repeating symbols with different monomers.
325
+ Please choose the correct monomer for each symbol using duplicate monomomer manager.`);
326
+ return;
327
+ }
328
+ const libJSON = libManager.getBioLib().toJSON();
329
+ const dialog = ui.dialog('Merge selected libraries');
330
+ const newFileNameInput = ui.input.string('Library Name', {
331
+ placeholder: 'Enter new library name',
332
+ nullable: false,
333
+ onValueChanged: () => {
334
+ const res = validateInput(newFileNameInput.value);
335
+ dialog.getButton('Download')?.classList?.toggle('d4-disabled', !!res && res !== libraryExistsError);
336
+ dialog.getButton('Save')?.classList?.toggle('d4-disabled', !!res);
337
+ }
338
+ });
339
+ const validLibPaths = (await this.libManager.getFileManager()).getValidLibraryPaths();
340
+ newFileNameInput.addValidator(validateInput);
341
+ function getFileNameInputValue() {
342
+ let fileName = newFileNameInput.value;
343
+ if (!fileName.endsWith('.json'))
344
+ fileName += '.json';
345
+ return fileName;
346
+ };
347
+
348
+ function validateInput(v: string) {
349
+ if (!v || !v.trim()) return 'Library name cannot be empty';
350
+ if ((v.endsWith('.json') && validLibPaths.includes(v)) || validLibPaths.includes(v + '.json'))
351
+ return libraryExistsError;
352
+ return null;
353
+ }
354
+ dialog
355
+ .add(newFileNameInput)
356
+ .add(ui.divText(`Total monomers: ${libJSON.length}`))
357
+ .addButton('Download', () => { DG.Utils.download(getFileNameInputValue(), JSON.stringify(libJSON)); })
358
+ .addButton('Save', async () => {
359
+ dialog.close();
360
+ const fileName = getFileNameInputValue();
361
+ const content = JSON.stringify(libJSON);
362
+ const fileManager = await this.libManager.getFileManager();
363
+ this._view && ui.setUpdateIndicator(this._view.root, true);
364
+ try {
365
+ await fileManager.addLibraryFile(content, fileName, false); // we will reload after updating settings
366
+ const settings = await getUserLibSettings();
367
+ settings.exclude = validLibPaths; // exclude all previous libraries
368
+ await setUserLibSettings(settings);
369
+ await this.libManager.loadLibraries(true);
370
+ await MonomerLibraryManagerWidget.reloadWidget();
371
+ } catch (e) {
372
+ grok.shell.error(`Failed to save library ${fileName}. see console for details.`);
373
+ console.error(e);
374
+ } finally {
375
+ this._view && ui.setUpdateIndicator(this._view.root, false);
376
+ }
377
+ })
378
+ .show();
379
+ dialog.getButton('Download')?.classList?.add('d4-disabled');
380
+ dialog.getButton('Save')?.classList?.add('d4-disabled');
381
+ }
382
+ }
383
+
384
+
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  /* Do not change these import lines to match external modules in webpack configuration */
2
3
  import * as grok from 'datagrok-api/grok';
3
4
  import * as ui from 'datagrok-api/ui';
@@ -18,12 +19,21 @@ import {helmTypeToPolymerType} from '@datagrok-libraries/bio/src/monomer-works/m
18
19
  import '../../../css/cell-renderer.css';
19
20
 
20
21
  import {_package} from '../../package';
22
+ import {getUserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
23
+ import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
21
24
 
22
25
  /** Wrapper for monomers obtained from different sources. For managing monomere
23
26
  * libraries, use MolfileHandler class instead */
24
27
  export class MonomerLib implements IMonomerLib {
25
28
  private _monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } } = {};
26
29
  private _onChanged = new Subject<any>();
30
+ private _duplicateMonomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer[] } } = {};
31
+ public get duplicateMonomers(): { [polymerType: string]: { [monomerSymbol: string]: Monomer[] } } {
32
+ return this._duplicateMonomers;
33
+ }
34
+ private _duplicatesHandled = true;
35
+ public get duplicatesHandled() { return this._duplicatesHandled; }
36
+ private duplicatesNotified: boolean = false;
27
37
 
28
38
  constructor(
29
39
  monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } },
@@ -37,6 +47,15 @@ export class MonomerLib implements IMonomerLib {
37
47
  }
38
48
  }
39
49
 
50
+ toJSON(): Monomer[] {
51
+ const resJSON: Monomer[] = [];
52
+ for (const set of Object.values(this._monomers)) {
53
+ for (const m of Object.values(set))
54
+ resJSON.push({...m, lib: undefined, wem: undefined});
55
+ }
56
+ return resJSON;
57
+ }
58
+
40
59
  /** Creates missing {@link Monomer} */
41
60
  addMissingMonomer(polymerType: PolymerType, monomerSymbol: string): Monomer {
42
61
  let mSet = this._monomers[polymerType];
@@ -92,7 +111,7 @@ export class MonomerLib implements IMonomerLib {
92
111
  if (!polymerType) {
93
112
  _package.logger.warning(`${logPrefix} symbol '${argMonomerSymbol}', polymerType not specified.`);
94
113
  // Assume any polymer type
95
- for (const [polymerType, dict] of Object.entries(this._monomers)) {
114
+ for (const [_polymerType, dict] of Object.entries(this._monomers)) {
96
115
  res = dict[monomerSymbol];
97
116
  if (res) break;
98
117
  }
@@ -138,7 +157,7 @@ export class MonomerLib implements IMonomerLib {
138
157
  /** Get a list of monomers with specified element attached to specified
139
158
  * R-group
140
159
  * WARNING: RGroup numbering starts from 1, not 0*/
141
- getMonomerSymbolsByRGroup(rGroupNumber: number, polymerType: PolymerType, element?: string): string[] {
160
+ getMonomerSymbolsByRGroup(rGroupNumber: number, polymerType: PolymerType, _element?: string): string[] {
142
161
  const monomerSymbols = this.getMonomerSymbolsByType(polymerType);
143
162
  let monomers = monomerSymbols.map((sym) => this.getMonomer(polymerType, sym));
144
163
  monomers = monomers.filter((el) => el !== null);
@@ -155,7 +174,7 @@ export class MonomerLib implements IMonomerLib {
155
174
  return false;
156
175
  let criterion = monomer?.rgroups.length >= rGroupNumber;
157
176
  const molfileHandler = MolfileHandler.getInstance(monomer.molfile);
158
- const rGroupIndices = findAllIndices(molfileHandler.atomTypes, 'R#');
177
+ const _rGroupIndices = findAllIndices(molfileHandler.atomTypes, 'R#');
159
178
  criterion &&= true;
160
179
  return criterion;
161
180
  });
@@ -178,6 +197,11 @@ export class MonomerLib implements IMonomerLib {
178
197
 
179
198
  const monomers = lib.getMonomerSymbolsByType(type);
180
199
  monomers.forEach((monomerSymbol) => {
200
+ if (this._monomers[type][monomerSymbol]) {
201
+ this._duplicateMonomers[type] ??= {};
202
+ this._duplicateMonomers[type][monomerSymbol] ??= [this._monomers[type][monomerSymbol]];
203
+ this._duplicateMonomers[type][monomerSymbol].push(lib.getMonomer(type, monomerSymbol)!);
204
+ }
181
205
  this._monomers[type][monomerSymbol] = lib.getMonomer(type, monomerSymbol)!;
182
206
  });
183
207
  });
@@ -191,11 +215,40 @@ export class MonomerLib implements IMonomerLib {
191
215
  public updateLibs(libList: IMonomerLib[], reload: boolean = false): void {
192
216
  if (reload)
193
217
  this._monomers = {};
218
+ this._duplicateMonomers = {}; // Reset duplicates
194
219
  for (const lib of libList)
195
220
  if (!lib.error) this._updateLibInt(lib);
221
+ if (Object.entries(this.duplicateMonomers).length > 0) {
222
+ getUserLibSettings().then((settings) => {
223
+ this.assignDuplicatePreferances(settings);
224
+ });
225
+ } else
226
+ this._duplicatesHandled = true;
227
+
196
228
  this._onChanged.next();
197
229
  }
198
230
 
231
+ /** Checks wether all duplicated monomers have set preferences in user settings. overwrites those which have. */
232
+ assignDuplicatePreferances(userSettings: UserLibSettings): boolean {
233
+ let res = true;
234
+ for (const polymerType in this.duplicateMonomers) {
235
+ for (const monomerSymbol in this.duplicateMonomers[polymerType]) {
236
+ if (!userSettings.duplicateMonomerPreferences?.[polymerType]?.[monomerSymbol])
237
+ res = false;
238
+ else {
239
+ const source = userSettings.duplicateMonomerPreferences[polymerType][monomerSymbol];
240
+ const monomer = this.duplicateMonomers[polymerType][monomerSymbol].find((m) => m.lib?.source === source);
241
+ if (!monomer)
242
+ res = false;
243
+ else
244
+ this._monomers[polymerType][monomerSymbol] = monomer;
245
+ }
246
+ }
247
+ }
248
+ this._duplicatesHandled = res;
249
+ return res;
250
+ }
251
+
199
252
  public clear(): void {
200
253
  this._monomers = {};
201
254
  this._onChanged.next();
@@ -250,7 +303,7 @@ export class MonomerLib implements IMonomerLib {
250
303
  if (monomer) {
251
304
  // Symbol & Name
252
305
  const symbol = monomer[REQ.SYMBOL];
253
- const name = monomer[REQ.NAME];
306
+ const _name = monomer[REQ.NAME];
254
307
  res.append(ui.divH([
255
308
  ui.div([symbol], {style: {fontWeight: 'bolder', textWrap: 'nowrap', marginRight: '6px'}}),
256
309
  ui.div([monomer.name])
@@ -259,10 +312,9 @@ export class MonomerLib implements IMonomerLib {
259
312
  // Structure
260
313
  const chemOptions = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
261
314
  let structureEl: HTMLElement;
262
- if (monomer.molfile) {
263
- //
315
+ if (monomer.molfile)
264
316
  structureEl = grok.chem.svgMol(monomer.molfile, undefined, undefined, chemOptions);
265
- } else if (monomer.smiles) {
317
+ else if (monomer.smiles) {
266
318
  structureEl = ui.divV([
267
319
  grok.chem.svgMol(monomer.smiles, undefined, undefined, chemOptions),
268
320
  ui.divText('from smiles', {style: {fontSize: 'smaller'}}),
@@ -276,20 +328,6 @@ export class MonomerLib implements IMonomerLib {
276
328
 
277
329
  // Source
278
330
  res.append(ui.divText(monomer.lib?.source ?? 'Missed in libraries'));
279
-
280
- // const label = (s: string) => {
281
- // return ui.label(s /* span ? */, {classes: 'ui-input-label'});
282
- // };
283
- // res.append(ui.div([
284
- // label('Name'),
285
- // ui.divText(monomer.name, {classes: 'ui-input-text'})
286
- // ], {classes: 'ui-input-root'}));
287
- //
288
- //
289
- // res.append(ui.div([
290
- // label('Source'),
291
- // ui.divText(monomer.lib?.source ?? 'unknown', {classes: 'ui-input-text'}),
292
- // ], {classes: 'ui-input-root'}));
293
331
  } else {
294
332
  res.append(ui.divV([
295
333
  ui.divText(`Monomer '${monomerSymbol}' of type '${polymerType}' not found.`),
@@ -0,0 +1,155 @@
1
+ /* eslint-disable max-len */
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
+ import {Monomer} from '@datagrok-libraries/bio/src/types';
7
+ import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
8
+ import {getUserLibSettings, setUserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
9
+ import '../../../../css/monomer-manager.css';
10
+ import {MonomerLibManager} from '../lib-manager';
11
+
12
+ class MonomerCard {
13
+ root: HTMLElement = ui.divV([], {classes: 'monomer-card-root'});
14
+
15
+ private _selected: boolean = false;
16
+ get selected(): boolean { return this._selected; }
17
+ set selected(value: boolean) {
18
+ this._selected = value;
19
+ this.root.style.border = value ? '2px solid var(--green-2)' : '2px solid var(--grey-2)';
20
+ }
21
+
22
+ constructor(public monomer: Monomer) {}
23
+
24
+ render() {
25
+ ui.empty(this.root);
26
+ const monomerMolSvg = this.monomer.smiles && grok.chem.checkSmiles(this.monomer.smiles) ?
27
+ grok.chem.drawMolecule(this.monomer.smiles, 200, 200) : grok.chem.drawMolecule(this.monomer.molfile ?? '', 200, 200);
28
+ this.root.appendChild(monomerMolSvg);
29
+ const monomerName =
30
+ ui.divH([ui.divText('Monomer Name: '), ui.divText(this.monomer.name)], {classes: 'monomer-card-info-row'});
31
+
32
+ this.root.appendChild(monomerName);
33
+ ui.tooltip.bind(monomerName, this.monomer.name);
34
+ if (this.monomer.lib?.source) {
35
+ const monomerSource =
36
+ ui.divH([ui.divText('Source: '), ui.divText(this.monomer.lib.source)], {classes: 'monomer-card-info-row'});
37
+ this.root.appendChild(monomerSource);
38
+ ui.tooltip.bind(monomerSource, this.monomer.lib.source);
39
+ }
40
+ const monomerType = ui.divH([ui.divText('Polymer Type: '), ui.divText(this.monomer.polymerType)], {classes: 'monomer-card-info-row'});
41
+ this.root.appendChild(monomerType);
42
+ ui.tooltip.bind(monomerType, this.monomer.polymerType);
43
+
44
+ ui.tooltip.bind(this.root, 'Select Monomer');
45
+ }
46
+ }
47
+
48
+ class DuplicateSymbolRow {
49
+ root: HTMLElement = ui.divH([],
50
+ {style: {
51
+ alignItems: 'center',
52
+ width: '100%',
53
+ overflow: 'hidden',
54
+ visibility: 'visible',
55
+ }, classes: 'duplicate-monomer-symbol-row'}
56
+ );
57
+ monomerCards: MonomerCard[];
58
+ constructor(
59
+ public monomerSymbol: string, private monomers: Monomer[],
60
+ selectedMonomer: Monomer | null, onMonomerSelected: (monomer: Monomer) => void) {
61
+ this.monomerCards = monomers.map((monomer) => new MonomerCard(monomer));
62
+ const monomerGallery = ui.divH([], {style: {overflowX: 'auto', width: '100%'}});
63
+ const monomerSymbolDiv = ui.h1(monomerSymbol, {style: {lineHeight: '2em', fontSize: '1.5em', marginRight: '20px', width: '100px', overflow: 'hidden', textOverflow: 'elipsis'}});
64
+ ui.tooltip.bind(monomerSymbolDiv, monomerSymbol);
65
+ this.root.appendChild(monomerSymbolDiv);
66
+ this.root.appendChild(monomerGallery);
67
+ this.monomerCards.forEach((card) => {
68
+ card.root.onclick = () => {
69
+ this.monomerCards.forEach((c) => c.selected = false);
70
+ card.selected = true;
71
+ onMonomerSelected(card.monomer);
72
+ };
73
+ if (selectedMonomer && card.monomer === selectedMonomer)
74
+ card.selected = true;
75
+ monomerGallery.appendChild(card.root);
76
+ });
77
+ }
78
+
79
+ render() {
80
+ this.monomerCards.forEach((card) => card.render());
81
+ }
82
+ }
83
+
84
+ export class DuplicateMonomerManager {
85
+ monomerCardRows: DuplicateSymbolRow[] = [];
86
+ private saveSettingsPromise: Promise<void> = Promise.resolve();
87
+ private searchInput: DG.InputBase<string>;
88
+ private _root: HTMLElement;
89
+ private monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer[] } };
90
+ private settings: UserLibSettings;
91
+ private filteredMonomerRows: DuplicateSymbolRow[] = [];
92
+ private static _instance: DuplicateMonomerManager;
93
+ private vv: DG.VirtualView;
94
+
95
+ static async getInstance() {
96
+ if (!DuplicateMonomerManager._instance) {
97
+ DuplicateMonomerManager._instance = new DuplicateMonomerManager();
98
+ await DuplicateMonomerManager._instance.refresh();
99
+ const libManager = await MonomerLibManager.getInstance();
100
+ libManager.getMonomerLib().onChanged.subscribe(async () => await DuplicateMonomerManager._instance.refresh());
101
+ }
102
+ DuplicateMonomerManager._instance.refresh();
103
+ return DuplicateMonomerManager._instance;
104
+ }
105
+
106
+ public async refresh() {
107
+ this.settings = await getUserLibSettings();
108
+ const libManager = await MonomerLibManager.getInstance();
109
+ await libManager.awaitLoaded();
110
+ await libManager.loadLibrariesPromise;
111
+ this.monomers = libManager.duplicateMonomers;
112
+ this.monomerCardRows = [];
113
+ for (const polymerType in this.monomers) {
114
+ for (const monomerSymbol in this.monomers[polymerType]) {
115
+ const selectedMonomerSource = this.settings.duplicateMonomerPreferences?.[polymerType]?.[monomerSymbol] ?? null;
116
+ const selectedMonomer = selectedMonomerSource ? this.monomers[polymerType][monomerSymbol].find((monomer) => monomer.lib?.source === selectedMonomerSource) ?? null : null;
117
+
118
+ this.monomerCardRows.push(new DuplicateSymbolRow(monomerSymbol, this.monomers[polymerType][monomerSymbol], selectedMonomer,
119
+ async (monomer) => {
120
+ this.saveSettingsPromise = this.saveSettingsPromise.then(async () => {
121
+ if (!monomer.lib?.source)
122
+ return;
123
+ this.settings.duplicateMonomerPreferences = this.settings.duplicateMonomerPreferences ?? {};
124
+ this.settings.duplicateMonomerPreferences[polymerType] = this.settings.duplicateMonomerPreferences[polymerType] ?? {};
125
+ this.settings.duplicateMonomerPreferences[polymerType][monomerSymbol] = monomer.lib.source;
126
+ await setUserLibSettings(this.settings);
127
+ grok.shell.info(`Monomer '${monomer.name}' from source '${monomer.lib.source}' selected for symbol '${monomerSymbol}'.`);
128
+ libManager.assignDuplicatePreferances(this.settings);
129
+ });
130
+ }));
131
+ }
132
+ }
133
+ this.filteredMonomerRows = this.monomerCardRows;
134
+ if (!this.vv) {
135
+ this.vv = ui.virtualView(this.monomerCardRows.length, (i) => { this.monomerCardRows[i].render(); return this.monomerCardRows[i].root; });
136
+ this.vv.root.classList.add('duplicate-monomers-virtual-view');
137
+ this.searchInput = ui.input.string('Search', {placeholder: 'Monomer Symbol', value: '', onValueChanged: () => search(this.searchInput.value)});
138
+ this.searchInput.root.style.justifyContent = 'center';
139
+ this.searchInput.input.style.width = '200px';
140
+ this._root = ui.divV([ui.h1('Manage Duplicate Monomer Symbols', {style: {textAlign: 'center'}}),
141
+ this.searchInput.root, ui.divV([this.vv.root], {style: {overflowY: 'auto', height: '100%'}})], {style: {height: '100%'}});
142
+
143
+ const search = (query: string) => {
144
+ this.filteredMonomerRows = this.monomerCardRows.filter((row) => row.monomerSymbol.toLowerCase().includes(query.toLowerCase()));
145
+ this.vv.setData(this.filteredMonomerRows.length, (i) => { this.filteredMonomerRows[i].render(); return this.filteredMonomerRows[i].root; });
146
+ };
147
+ } else
148
+ this.vv.setData(this.filteredMonomerRows.length, (i) => { this.filteredMonomerRows[i].render(); return this.filteredMonomerRows[i].root; } );
149
+ }
150
+
151
+ private constructor() {}
152
+ get root() {
153
+ return this._root;
154
+ }
155
+ }