@datagrok/bio 2.23.2 → 2.25.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 (63) hide show
  1. package/.eslintrc.json +0 -2
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +1 -1
  4. package/dist/455.js.map +1 -1
  5. package/dist/package-test.js +3 -3
  6. package/dist/package-test.js.map +1 -1
  7. package/dist/package.js +2 -2
  8. package/dist/package.js.map +1 -1
  9. package/files/samples/HELM_CHEMS.csv +11 -0
  10. package/package.json +2 -2
  11. package/src/analysis/sequence-space.ts +1 -1
  12. package/src/demo/bio03-atomic-level.ts +1 -1
  13. package/src/package-types.ts +2 -2
  14. package/src/package.ts +10 -4
  15. package/src/tests/activity-cliffs-tests.ts +2 -2
  16. package/src/tests/monomer-libraries-tests.ts +9 -7
  17. package/src/tests/renderers-monomer-placer-tests.ts +1 -1
  18. package/src/tests/scoring.ts +1 -1
  19. package/src/tests/seq-handler-get-helm-tests.ts +1 -1
  20. package/src/tests/splitters-test.ts +2 -2
  21. package/src/tests/substructure-filters-tests.ts +11 -11
  22. package/src/tests/to-atomic-level-tests.ts +2 -2
  23. package/src/tests/to-atomic-level-ui-tests.ts +13 -14
  24. package/src/utils/cell-renderer.ts +1 -1
  25. package/src/utils/get-region.ts +2 -2
  26. package/src/utils/helm-to-molfile/converter/const.ts +0 -1
  27. package/src/utils/helm-to-molfile/converter/converter.ts +4 -4
  28. package/src/utils/helm-to-molfile/converter/helm.ts +14 -6
  29. package/src/utils/helm-to-molfile/converter/mol-bonds.ts +2 -2
  30. package/src/utils/helm-to-molfile/converter/mol-wrapper.ts +2 -2
  31. package/src/utils/helm-to-molfile/converter/monomer-wrapper.ts +1 -1
  32. package/src/utils/helm-to-molfile/converter/polymer.ts +1 -1
  33. package/src/utils/helm-to-molfile/converter/r-group-handler.ts +2 -2
  34. package/src/utils/helm-to-molfile/utils.ts +3 -2
  35. package/src/utils/monomer-cell-renderer-base.ts +1 -2
  36. package/src/utils/monomer-lib/consts.ts +1 -6
  37. package/src/utils/monomer-lib/lib-manager.ts +239 -112
  38. package/src/utils/monomer-lib/library-file-manager/file-validator.ts +1 -1
  39. package/src/utils/monomer-lib/library-file-manager/monomers-lib-provider.ts +378 -0
  40. package/src/utils/monomer-lib/library-file-manager/ui.ts +120 -81
  41. package/src/utils/monomer-lib/monomer-colors.ts +37 -39
  42. package/src/utils/monomer-lib/monomer-lib-base.ts +34 -7
  43. package/src/utils/monomer-lib/monomer-lib.ts +7 -33
  44. package/src/utils/monomer-lib/monomer-manager/duplicate-monomer-manager.ts +3 -3
  45. package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +91 -82
  46. package/src/utils/monomer-lib/smiles2Monomer.ts +128 -0
  47. package/src/utils/monomer-lib/web-editor-monomer-dummy.ts +15 -1
  48. package/src/utils/monomer-lib/web-editor-monomer-of-library.ts +2 -1
  49. package/src/utils/multiple-sequence-alignment-ui.ts +1 -1
  50. package/src/utils/multiple-sequence-alignment.ts +1 -1
  51. package/src/utils/seq-helper/seq-handler.ts +26 -13
  52. package/src/utils/seq-helper/seq-helper.ts +1 -2
  53. package/src/utils/sequence-to-mol.ts +1 -1
  54. package/src/utils/ui-utils.ts +1 -1
  55. package/src/viewers/web-logo-viewer.ts +20 -9
  56. package/src/widgets/composition-analysis-widget.ts +1 -1
  57. package/src/widgets/sequence-scrolling-widget.ts +1 -2
  58. package/test-console-output-1.log +670 -721
  59. package/test-record-1.mp4 +0 -0
  60. package/src/utils/monomer-lib/library-file-manager/custom-monomer-lib-handlers.ts +0 -41
  61. package/src/utils/monomer-lib/library-file-manager/event-manager.ts +0 -93
  62. package/src/utils/monomer-lib/library-file-manager/file-manager.ts +0 -317
  63. package/src/utils/monomer-lib/monomer-set.ts +0 -61
@@ -6,19 +6,21 @@ import * as DG from 'datagrok-api/dg';
6
6
 
7
7
  import $ from 'cash-dom';
8
8
  import {Subject} from 'rxjs';
9
- import './style.css';
10
9
 
11
10
  import {
12
11
  getUserLibSettings, setUserLibSettings
13
12
  } from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
14
13
  import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
15
- import {getMonomerLibHelper, IMonomerLibFileManager} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
14
+ import {findProviderWithLibraryName,
15
+ getMonomerLibHelper, IMonomerLibHelper,
16
+ IMonomerLibProvider} from '@datagrok-libraries/bio/src/types/monomer-library';
16
17
 
17
- import {MonomerLibFileEventManager} from './event-manager';
18
18
  import {_package} from '../../../package';
19
19
  import {MonomerManager} from '../monomer-manager/monomer-manager';
20
20
  import {DuplicateMonomerManager} from '../monomer-manager/duplicate-monomer-manager';
21
21
  import {MonomerLibManager} from '../lib-manager';
22
+ // @ts-ignore
23
+ import './style.css';
22
24
 
23
25
  export async function showManageLibrariesDialog(): Promise<void> {
24
26
  await DialogWrapper.showDialog();
@@ -38,8 +40,6 @@ export async function getMonomerLibraryManagerLink(): Promise<DG.Widget> {
38
40
  }
39
41
 
40
42
  class MonomerLibraryManagerWidget {
41
- private _fileManager: IMonomerLibFileManager;
42
-
43
43
  private _widget: DG.Widget;
44
44
  public get widget(): DG.Widget { return this._widget; }
45
45
 
@@ -47,12 +47,14 @@ class MonomerLibraryManagerWidget {
47
47
 
48
48
  private static instancePromise?: Promise<MonomerLibraryManagerWidget>;
49
49
 
50
+ private libHelper: IMonomerLibHelper;
51
+
50
52
  static async getInstance(): Promise<MonomerLibraryManagerWidget> {
51
- if (MonomerLibraryManagerWidget.instancePromise === undefined) {
53
+ if (MonomerLibraryManagerWidget.instancePromise == undefined) {
52
54
  MonomerLibraryManagerWidget.instancePromise = (async () => {
53
55
  const instance = new MonomerLibraryManagerWidget();
54
56
  const libHelper = await getMonomerLibHelper();
55
- instance._fileManager = await libHelper.getFileManager();
57
+ instance.libHelper = libHelper;
56
58
  instance._widget = await instance.createWidget();
57
59
  return instance;
58
60
  })();
@@ -69,7 +71,7 @@ class MonomerLibraryManagerWidget {
69
71
  const content = await this.getWidgetContent();
70
72
  const monomerLibHelper = await getMonomerLibHelper();
71
73
  // eslint-disable-next-line rxjs/no-ignored-subscription
72
- monomerLibHelper.eventManager.addLibraryFileRequested$.subscribe(
74
+ monomerLibHelper.fileUploadRequested.subscribe(
73
75
  () => this.promptToAddLibraryFiles()
74
76
  );
75
77
  return new DG.Widget(content);
@@ -89,17 +91,38 @@ class MonomerLibraryManagerWidget {
89
91
  DG.Utils.openFile({
90
92
  accept: '.json',
91
93
  open: async (selectedFile) => {
92
- const content = await selectedFile.text();
93
- const name = selectedFile.name;
94
- const progressIndicator = DG.TaskBarProgressIndicator.create(`Adding ${name} as a monomer library`);
95
- try {
96
- await this._fileManager.addLibraryFile(content, name);
94
+ const doAdd = async (provider: IMonomerLibProvider) => {
95
+ const content = await selectedFile.text();
96
+ const name = selectedFile.name;
97
+ const progressIndicator = DG.TaskBarProgressIndicator.create(`Adding ${name} as a monomer library`);
98
+ try {
99
+ await provider.addOrUpdateLibraryString(name, content);
97
100
  // this.eventManager.updateLibrarySelectionStatus(name, true);
98
- } catch (e) {
99
- grok.shell.error(`File ${name} is not a valid monomer library, verify it is aligned to HELM JSON schema.`);
100
- } finally {
101
- progressIndicator.close();
101
+ } catch (e) {
102
+ grok.shell.error(`File ${name} is not a valid monomer library, verify it is aligned to HELM JSON schema.`);
103
+ } finally {
104
+ progressIndicator.close();
105
+ }
106
+ };
107
+ const providers = await this.libHelper.getProviders();
108
+ if (providers.length === 0) {
109
+ grok.shell.error('No monomer library providers available to add the library.');
110
+ return;
102
111
  }
112
+ if (providers.length === 1) {
113
+ await doAdd(providers[0]);
114
+ return;
115
+ }
116
+ const dialog = ui.dialog('Select storage for new monomer library');
117
+ const providersInput =
118
+ ui.input.choice('Storage', {items: providers.map((p) => p.name), value: providers[0].name,
119
+ nullable: false, tooltipText: 'Storage provider for new monomer library'});
120
+ dialog
121
+ .add(providersInput)
122
+ .onOK(async () => {
123
+ const provider = providers.find((p) => p.name === providersInput.value)!; // should not be null
124
+ await doAdd(provider);
125
+ });
103
126
  },
104
127
  });
105
128
  }
@@ -107,16 +130,11 @@ class MonomerLibraryManagerWidget {
107
130
 
108
131
  class LibraryControlsManager {
109
132
  private constructor(
110
- private fileManager: IMonomerLibFileManager,
133
+ private readonly libHelper: IMonomerLibHelper,
111
134
  private readonly userLibSettings: UserLibSettings,
112
135
  ) {
113
- // eslint-disable-next-line rxjs/no-ignored-subscription
114
- this.fileManager.eventManager.updateUIControlsRequested$.subscribe(() => {
115
- this.updateControlsForm();
116
- });
117
- // eslint-disable-next-line rxjs/no-ignored-subscription
118
- this.fileManager.eventManager.librarySelectionRequested$.subscribe(([fileName, isSelected]) => {
119
- this.updateLibrarySelectionStatus(isSelected, fileName);
136
+ this.libHelper.providersDataChanged.subscribe(async () => {
137
+ await this.updateControlsForm();
120
138
  });
121
139
  }
122
140
 
@@ -127,30 +145,28 @@ class LibraryControlsManager {
127
145
  static async createControlsForm(): Promise<HTMLElement> {
128
146
  const logPrefix = 'LibraryControlsForm.createControlsForm()';
129
147
  _package.logger.debug(`${logPrefix}, start`);
130
- const [fileManager, userLibSettings] = await Promise.all([
131
- getMonomerLibHelper().then((libHelper) => libHelper.getFileManager()),
132
- await getUserLibSettings(),
133
- ]);
134
- const manager = new LibraryControlsManager(fileManager, userLibSettings);
148
+ const userLibSettings = await getUserLibSettings();
149
+ const libHelper = await getMonomerLibHelper();
150
+ const manager = new LibraryControlsManager(libHelper, userLibSettings);
135
151
 
136
152
  return manager._createControlsForm();
137
153
  }
138
154
 
139
- private _createControlsForm(): HTMLElement {
140
- const libraryControls = this.createLibraryControls();
155
+ private async _createControlsForm(): Promise<HTMLElement> {
156
+ const libraryControls = await this.createLibraryControls();
141
157
  const inputsForm = ui.wideForm(libraryControls, undefined);
142
158
  $(inputsForm).addClass('monomer-lib-controls-form');
143
159
 
144
160
  return inputsForm;
145
161
  }
146
162
 
147
- public updateControlsForm(): void {
148
- const updatedForm = this._createControlsForm();
163
+ public async updateControlsForm(): Promise<void> {
164
+ const updatedForm = await this._createControlsForm();
149
165
  $('.monomer-lib-controls-form').replaceWith(updatedForm);
150
166
  }
151
167
 
152
- private createLibraryControls(): DG.InputBase<boolean | null>[] {
153
- const libFileNameList: string[] = this.fileManager.getValidLibraryPaths();
168
+ private async createLibraryControls(): Promise<DG.InputBase<boolean | null>[]> {
169
+ const libFileNameList: string[] = await this.libHelper.getAvaliableLibraryNames();
154
170
  return libFileNameList.map((libFileName) => this.createLibInput(libFileName));
155
171
  }
156
172
 
@@ -159,7 +175,7 @@ class LibraryControlsManager {
159
175
  _package.logger.debug(`${logPrefix}, libFileName = '${libFileName}', start`);
160
176
  const isMonomerLibrarySelected = !this.userLibSettings.exclude.includes(libFileName);
161
177
  const libInput = ui.input.bool(libFileName, {value: isMonomerLibrarySelected, onValueChanged: () => {
162
- this.fileManager.eventManager.updateLibrarySelectionStatus(libFileName, libInput.value);
178
+ updateLibrarySelectionStatus(libInput.value, libFileName);
163
179
  }});
164
180
  ui.tooltip.bind(libInput.root, `Include monomers from ${libFileName}`);
165
181
  const deleteIcon = ui.iconFA('trash-alt', () => this.promptForLibraryDeletion(libFileName));
@@ -173,38 +189,19 @@ class LibraryControlsManager {
173
189
  return libInput;
174
190
  }
175
191
 
176
- private async updateLibrarySelectionStatus(
177
- isMonomerLibrarySelected: boolean,
178
- libFileName: string
179
- ): Promise<void> {
180
- this.updateLibrarySettings(isMonomerLibrarySelected, libFileName);
181
- await setUserLibSettings(this.userLibSettings);
182
- const monomerLibHelper = await getMonomerLibHelper();
183
- await monomerLibHelper.loadMonomerLib(true);
184
- grok.shell.info('Monomer library user settings saved');
185
- }
186
-
187
- private updateLibrarySettings(
188
- isLibrarySelected: boolean | null,
189
- libFileName: string,
190
- ): void {
191
- if (isLibrarySelected) {
192
- // Remove selected library from exclusion list
193
- this.userLibSettings.exclude = this.userLibSettings.exclude.filter((libName) => libName !== libFileName);
194
- } else if (!this.userLibSettings.exclude.includes(libFileName)) {
195
- // Add unselected library to exclusion list
196
- this.userLibSettings.exclude.push(libFileName);
197
- }
198
- }
199
192
 
200
193
  private promptForLibraryDeletion(fileName: string): void {
201
194
  const dialog = ui.dialog('Warning');
202
- dialog.add(ui.divText(`Delete file ${fileName}?`))
195
+ dialog.add(ui.divText(`Are you sure you want to delete library "${fileName}"?`))
203
196
  .onOK(async () => {
204
197
  try {
205
198
  const progressIndicator = DG.TaskBarProgressIndicator.create(`Deleting ${fileName} library`);
206
- await this.updateLibrarySelectionStatus(false, fileName);
207
- await this.fileManager.deleteLibraryFile(fileName);
199
+ await updateLibrarySelectionStatus(false, fileName);
200
+ const provider = await findProviderWithLibraryName(await this.libHelper.getProviders(), fileName);
201
+ if (!provider)
202
+ throw new Error(`Cannot find provider for library ${fileName}`);
203
+ await provider.deleteLibrary(fileName);
204
+ // await this.fileManager.deleteLibraryFile(fileName);
208
205
  progressIndicator.close();
209
206
  } catch (e) {
210
207
  console.error(e);
@@ -215,6 +212,33 @@ class LibraryControlsManager {
215
212
  }
216
213
  }
217
214
 
215
+ async function updateLibrarySelectionStatus(
216
+ isMonomerLibrarySelected: boolean,
217
+ libFileName: string
218
+ ): Promise<void> {
219
+ const userLibSettings = await getUserLibSettings();
220
+ updateLibrarySettings(userLibSettings, isMonomerLibrarySelected, libFileName);
221
+ await setUserLibSettings(userLibSettings);
222
+ const monomerLibHelper = await getMonomerLibHelper();
223
+ await monomerLibHelper.loadMonomerLib(true);
224
+ grok.shell.info('Monomer library user settings saved');
225
+ monomerLibHelper.notifyLibrarySelectionChanged();
226
+ }
227
+
228
+ function updateLibrarySettings(
229
+ userLibSettings: UserLibSettings,
230
+ isLibrarySelected: boolean | null,
231
+ libFileName: string,
232
+ ): void {
233
+ if (isLibrarySelected) {
234
+ // Remove selected library from exclusion list
235
+ userLibSettings.exclude = userLibSettings.exclude.filter((libName) => libName !== libFileName);
236
+ } else if (!userLibSettings.exclude.includes(libFileName)) {
237
+ // Add unselected library to exclusion list
238
+ userLibSettings.exclude.push(libFileName);
239
+ }
240
+ }
241
+
218
242
  class DialogWrapper {
219
243
  private constructor() { }
220
244
 
@@ -238,8 +262,8 @@ class DialogWrapper {
238
262
  }
239
263
 
240
264
  private async getDialog(): Promise<DG.Dialog> {
241
- const eventManager = MonomerLibFileEventManager.getInstance();
242
265
  const widget = (await MonomerLibraryManagerWidget.getInstance()).widget;
266
+ const libHelper = await getMonomerLibHelper();
243
267
  const dialog = ui.dialog(
244
268
  {
245
269
  title: 'Manage monomer libraries',
@@ -250,7 +274,7 @@ class DialogWrapper {
250
274
  dialog.clear();
251
275
  dialog.addButton(
252
276
  'Add',
253
- () => eventManager.addLibraryFile(),
277
+ () => libHelper.requestFileUpload(),
254
278
  undefined,
255
279
  'Upload new HELM monomer library'
256
280
  );
@@ -264,17 +288,17 @@ class DialogWrapper {
264
288
  }
265
289
 
266
290
  class LibManagerView {
267
- private constructor() {};
291
+ private constructor(
292
+ ) {};
268
293
  private static _instance: LibManagerView;
269
294
  static viewName = 'Manage Monomer Libraries';
270
295
  private _view: DG.View;
271
296
  private _duplicateManager: DuplicateMonomerManager;
272
297
  private libManager: MonomerLibManager;
273
298
  private async getView(addView = true) {
274
- const eventManager = MonomerLibFileEventManager.getInstance();
275
299
  const widget = (await MonomerLibraryManagerWidget.getInstance()).widget;
276
300
  const addButton = ui.bigButton('Add',
277
- () => eventManager.addLibraryFile(), 'Upload new HELM monomer library');
301
+ () => this.libManager.requestFileUpload(), 'Upload new HELM monomer library');
278
302
  const mergeButton =
279
303
  ui.bigButton('Merge', () => { this.mergeSelectedLibs(); }, 'Merge selected libraries into one');
280
304
 
@@ -349,7 +373,6 @@ class LibManagerView {
349
373
  const libraryExistsError = 'Library with this name already exists';
350
374
  const libManager = await MonomerLibManager.getInstance();
351
375
  await libManager.awaitLoaded();
352
- await libManager.loadLibrariesPromise;
353
376
  if (!libManager.duplicatesHandled) {
354
377
  grok.shell.warning(`Selected libraries contain repeating symbols with different monomers.
355
378
  Please choose the correct monomer for each symbol using duplicate monomomer manager.`);
@@ -366,37 +389,53 @@ class LibManagerView {
366
389
  dialog.getButton('Save')?.classList?.toggle('d4-disabled', !!res);
367
390
  }
368
391
  });
369
- const validLibPaths = (await this.libManager.getFileManager()).getValidLibraryPaths();
392
+ const validLibPaths = await this.libManager.getAvaliableLibraryNames();
370
393
  newFileNameInput.addValidator(validateInput);
371
- function getFileNameInputValue() {
372
- let fileName = newFileNameInput.value;
373
- if (!fileName.endsWith('.json'))
374
- fileName += '.json';
375
- return fileName;
376
- };
377
394
 
378
395
  function validateInput(v: string) {
379
396
  if (!v || !v.trim()) return 'Library name cannot be empty';
380
- if ((v.endsWith('.json') && validLibPaths.includes(v)) || validLibPaths.includes(v + '.json'))
397
+ if (validLibPaths.includes(v) || validLibPaths.includes(v + '.json'))
381
398
  return libraryExistsError;
382
399
  return null;
383
400
  }
401
+ const providers = await this.libManager.getProviders();
402
+ if (providers.length === 0) {
403
+ grok.shell.error('No monomer library providers available to save the merged library.');
404
+ return; // I mean, this should not happen, but ....
405
+ }
406
+ const providersInput = ui.input.choice('Storage', {items: providers.map((p) => p.name), value: providers[0].name,
407
+ nullable: false, tooltipText: 'Storage provider for saving new monomer library'});
408
+
409
+ const getFileNameInputValue = () => {
410
+ let fileName = newFileNameInput.value.trim();
411
+ if (!fileName)
412
+ fileName = 'New';
413
+ if (!fileName.toLowerCase().endsWith('.json'))
414
+ fileName += '.json';
415
+ return fileName;
416
+ };
384
417
  dialog
418
+ .add(providersInput)
385
419
  .add(newFileNameInput)
386
420
  .add(ui.divText(`Total monomers: ${libJSON.length}`))
387
421
  .addButton('Download', () => { DG.Utils.download(getFileNameInputValue(), JSON.stringify(libJSON)); })
388
422
  .addButton('Save', async () => {
423
+ if (!newFileNameInput.value || !newFileNameInput.value.trim() || !providersInput.value) {
424
+ providersInput.validate();
425
+ newFileNameInput.validate(); // this will force showing validation error
426
+ return;
427
+ }
389
428
  dialog.close();
390
- const fileName = getFileNameInputValue();
429
+ const fileName = newFileNameInput.value!;
391
430
  const content = JSON.stringify(libJSON);
392
- const fileManager = await this.libManager.getFileManager();
393
431
  this._view && ui.setUpdateIndicator(this._view.root, true);
394
432
  try {
395
- await fileManager.addLibraryFile(content, fileName, false); // we will reload after updating settings
433
+ const provider = providers.find((p) => p.name === providersInput.value)!; // should not be null
434
+ await provider.addOrUpdateLibraryString(fileName, content); // we will reload after updating settings
396
435
  const settings = await getUserLibSettings();
397
436
  settings.exclude = validLibPaths; // exclude all previous libraries
398
437
  await setUserLibSettings(settings);
399
- await this.libManager.loadLibraries(true);
438
+ await this.libManager.loadMonomerLib(true);
400
439
  await MonomerLibraryManagerWidget.reloadWidget();
401
440
  } catch (e) {
402
441
  grok.shell.error(`Failed to save library ${fileName}. see console for details.`);
@@ -1,6 +1,4 @@
1
1
  import {HelmTypes} from '@datagrok-libraries/bio/src/helm/consts';
2
- import {Helm} from '../helm-to-molfile/converter/helm';
3
- import {HelmType} from '@datagrok-libraries/bio/src/helm/types';
4
2
 
5
3
  /**
6
4
  * MonomerColors class from HelmWebEditor for natural monomers
@@ -8,64 +6,64 @@ import {HelmType} from '@datagrok-libraries/bio/src/helm/types';
8
6
  export const naturalMonomerColors = {
9
7
  [HelmTypes.BASE]: {
10
8
  // Chromatogram palette // HELMWebEditor monomerColors
11
- A: "#20E040", // "#A0A0FF",
12
- G: "#040404", // "#FF7070",
13
- T: "#FF8080", // "#A0FFA0",
14
- C: "#2060FF", // "#FF8C4B",
15
- U: "#FF8080", // "#FF8080"
9
+ A: '#20E040', // "#A0A0FF",
10
+ G: '#040404', // "#FF7070",
11
+ T: '#FF8080', // "#A0FFA0",
12
+ C: '#2060FF', // "#FF8C4B",
13
+ U: '#FF8080', // "#FF8080"
16
14
  },
17
15
 
18
16
  [HelmTypes.NUCLEOTIDE]: {
19
17
  // Chromatogram palette // HELMWebEditor monomerColors
20
- A: "#20E040", // "#A0A0FF",
21
- G: "#040404", // "#FF7070",
22
- T: "#FF8080", // "#A0FFA0",
23
- C: "#2060FF", // "#FF8C4B",
24
- U: "#FF8080", // "#FF8080"
18
+ A: '#20E040', // "#A0A0FF",
19
+ G: '#040404', // "#FF7070",
20
+ T: '#FF8080', // "#A0FFA0",
21
+ C: '#2060FF', // "#FF8C4B",
22
+ U: '#FF8080', // "#FF8080"
25
23
  },
26
24
 
27
25
  [HelmTypes.LINKER]: {
28
- P: "#9aa5e1",
29
- p: "#9aa5e1"
26
+ P: '#9aa5e1',
27
+ p: '#9aa5e1'
30
28
  },
31
29
 
32
30
  [HelmTypes.SUGAR]: {
33
- R: "#7a85c1",
34
- r: "#7a85c1",
31
+ R: '#7a85c1',
32
+ r: '#7a85c1',
35
33
  // TODO: deoxyribose
36
34
  },
37
35
 
38
36
  [HelmTypes.AA]: {
39
37
  // GrokGroups palette // HELMWebEditor monomerColors
40
- A: "rgb(44,160,44)", // "#C8C8C8",
41
- R: "rgb(23,190,207)", // "#145AFF",
42
- N: "rgb(235,137,70)", // "#00DCDC",
43
- D: "rgb(31,119,180)", // "#E60A0A",
44
- C: "rgb(188,189,34)", // "#E6E600",
45
- E: "rgb(31, 120, 150)", // "#00DCDC",
46
- Q: "rgb(205, 111, 71)", // "#E60A0A",
47
- G: "rgb(214,39,40)", // "#EBEBEB",
48
- H: "rgb(158,218,229)", // "#8282D2",
49
- I: "rgb(23,103,57)", // "#0F820F",
50
- L: "rgb(30,110,96)", // "#0F820F",
51
- K: "rgb(108, 218, 229)", //"#145AFF",
52
- M: "rgb(60,131,95)", // "#E6E600",
53
- F: "rgb(24,110,79)", // "#3232AA",
54
- P: "rgb(255,152,150)", // "#DC9682",
55
- S: "rgb(255,187,120)", // "#FA9600",
56
- T: "rgb(245,167,100)", // "#FA9600",
57
- W: "rgb(182, 223, 138)", // "#B45AB4",
58
- Y: "rgb(152,223,138)", // "#3232AA",
59
- V: "rgb(74,160,74)", // "#0F820F",
38
+ A: 'rgb(44,160,44)', // "#C8C8C8",
39
+ R: 'rgb(23,190,207)', // "#145AFF",
40
+ N: 'rgb(235,137,70)', // "#00DCDC",
41
+ D: 'rgb(31,119,180)', // "#E60A0A",
42
+ C: 'rgb(188,189,34)', // "#E6E600",
43
+ E: 'rgb(31, 120, 150)', // "#00DCDC",
44
+ Q: 'rgb(205, 111, 71)', // "#E60A0A",
45
+ G: 'rgb(214,39,40)', // "#EBEBEB",
46
+ H: 'rgb(158,218,229)', // "#8282D2",
47
+ I: 'rgb(23,103,57)', // "#0F820F",
48
+ L: 'rgb(30,110,96)', // "#0F820F",
49
+ K: 'rgb(108, 218, 229)', //"#145AFF",
50
+ M: 'rgb(60,131,95)', // "#E6E600",
51
+ F: 'rgb(24,110,79)', // "#3232AA",
52
+ P: 'rgb(255,152,150)', // "#DC9682",
53
+ S: 'rgb(255,187,120)', // "#FA9600",
54
+ T: 'rgb(245,167,100)', // "#FA9600",
55
+ W: 'rgb(182, 223, 138)', // "#B45AB4",
56
+ Y: 'rgb(152,223,138)', // "#3232AA",
57
+ V: 'rgb(74,160,74)', // "#0F820F",
60
58
  },
61
59
 
62
60
  [HelmTypes.CHEM]: {
63
- R: "#eeeeee",
61
+ R: '#eeeeee',
64
62
  },
65
63
 
66
64
  [HelmTypes.BLOB]: {
67
- B: "#999999",
68
- G: "#e2e2e2"
65
+ B: '#999999',
66
+ G: '#e2e2e2'
69
67
  }
70
68
  };
71
69
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-len */
1
2
  /* eslint-disable max-lines */
2
3
  import * as grok from 'datagrok-api/grok';
3
4
  import * as ui from 'datagrok-api/ui';
@@ -6,7 +7,7 @@ import * as DG from 'datagrok-api/dg';
6
7
  import wu from 'wu';
7
8
  import {Observable, Subject} from 'rxjs';
8
9
 
9
- import {IMonomerLibBase, Monomer, RGroup} from '@datagrok-libraries/bio/src/types/index';
10
+ import {IMonomerLibBase, Monomer, RGroup} from '@datagrok-libraries/bio/src/types/monomer-library';
10
11
  import {HelmAtom, HelmType, IMonomerColors,
11
12
  IWebEditorMonomer, MonomerType, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
12
13
  import {getMonomerHandleArgs} from '@datagrok-libraries/bio/src/helm/helm-helper';
@@ -18,11 +19,14 @@ import {GAP_SYMBOL, GapOriginals, NOTATION} from '@datagrok-libraries/bio/src/ut
18
19
  import {Vector} from '@datagrok-libraries/utils/src/type-declarations';
19
20
  import {vectorAdd, vectorDotProduct, vectorLength} from '@datagrok-libraries/utils/src/vector-operations';
20
21
 
21
- import {AmbiguousWebEditorMonomer, GapWebEditorMonomer, MissingWebEditorMonomer} from './web-editor-monomer-dummy';
22
+ import {AmbiguousWebEditorMonomer, GapWebEditorMonomer, MissingWebEditorMonomer, SmilesWebEditorMonomer} from './web-editor-monomer-dummy';
22
23
  import {LibraryWebEditorMonomer} from './web-editor-monomer-of-library';
23
24
  import {naturalMonomerColors} from './monomer-colors';
24
25
 
25
26
  import {_package} from '../../package';
27
+ import {MonomerLibData} from '@datagrok-libraries/bio/src/types/monomer-library';
28
+ import {smiles2Monomer} from './smiles2Monomer';
29
+ import {polymerTypeToHelmType} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
26
30
 
27
31
  const monomerRe = /[\w()]+/;
28
32
  //** Do not mess with monomer symbol with parenthesis enclosed in square brackets */
@@ -34,8 +38,6 @@ const drawMoleculeCall = (s: string) => {
34
38
  return canvas;
35
39
  };
36
40
 
37
- export type MonomerLibDataType = { [polymerType: string]: { [monomerSymbol: string]: Monomer } };
38
-
39
41
  const whiteColorV = new Vector([255.0, 255.0, 255.0]);
40
42
  const blackColorV = new Vector([0.0, 0.0, 0.0]);
41
43
  const maxTextColorVLen = vectorLength(whiteColorV) * 0.7;
@@ -50,7 +52,7 @@ export class MonomerLibBase implements IMonomerLibBase {
50
52
 
51
53
 
52
54
  constructor(
53
- protected _monomers: MonomerLibDataType,
55
+ protected _monomers: MonomerLibData,
54
56
  public readonly source: string,
55
57
  ) {
56
58
  this._isEmpty = !this._monomers || Object.keys(this._monomers).length === 0 ||
@@ -62,9 +64,15 @@ export class MonomerLibBase implements IMonomerLibBase {
62
64
  }
63
65
 
64
66
  getMonomerSymbolsByType(polymerType: PolymerType): string[] {
65
- return Object.keys(this._monomers[polymerType]);
67
+ const res = Object.keys(this._monomers[polymerType]);
68
+ if (this._smilesMonomerCache[polymerType])
69
+ res.push(...Object.keys(this._smilesMonomerCache[polymerType]));
70
+ return res;
66
71
  }
67
72
 
73
+ // smiles to symbol Mapping cache
74
+ private _smilesMonomerCache: {[polymerType: string]: {[smiles: string]: string}} = {};
75
+
68
76
  /** Creates missing {@link Monomer} */
69
77
  addMissingMonomer(polymerType: PolymerType, monomerSymbol: string): Monomer {
70
78
  let mSet = this._monomers[polymerType];
@@ -79,6 +87,22 @@ export class MonomerLibBase implements IMonomerLibBase {
79
87
  else if (polymerType === PolymerTypes.RNA && monomerSymbol === 'N')
80
88
  monomerName = 'Any';
81
89
 
90
+ // test if it is smiles
91
+ // check if the missing monomer symbol is a valid SMILES string
92
+ const smilesMonomer = smiles2Monomer(monomerSymbol, polymerType);
93
+ if (smilesMonomer) {
94
+ this._smilesMonomerCache[polymerType] = this._smilesMonomerCache[polymerType] ?? {};
95
+ const smSet = this._smilesMonomerCache[polymerType];
96
+ const symbol = Object.keys(smSet).length + 1;
97
+ smSet[monomerSymbol] = `#${polymerType[0]}${symbol}`; // e.g. #P1, #R2, #C3, #B4
98
+ const m: Monomer = {...smilesMonomer, symbol: smSet[monomerSymbol]};
99
+ // note, the ID becomes #<index> for smiles based monomers, and as the smiles, original smiles is passed (which is in monomerSymbol), to avoid key duplication
100
+ const wem = new SmilesWebEditorMonomer(polymerTypeToHelmType(polymerType), m.symbol, monomerSymbol, `SMILES Monomer ${m.symbol}`, m.rgroups.map((rg) => rg[RGP.LABEL]));
101
+ m.wem = wem;
102
+ mSet[m.symbol] = m;
103
+ return m;
104
+ }
105
+
82
106
  const m = mSet[monomerSymbol] = {
83
107
  [REQ.SYMBOL]: monomerSymbol,
84
108
  [REQ.NAME]: monomerName,
@@ -125,8 +149,11 @@ export class MonomerLibBase implements IMonomerLibBase {
125
149
  if (res) break;
126
150
  }
127
151
  } else {
152
+ // Check smiles cache, modify with mapped symbol
153
+ if (this._smilesMonomerCache[polymerType]?.[monomerSymbol])
154
+ monomerSymbol = this._smilesMonomerCache[polymerType][monomerSymbol];
128
155
  const dict = this._monomers[polymerType];
129
- res = dict ? dict[monomerSymbol] : null;
156
+ res = dict?.[monomerSymbol] ?? null;
130
157
  }
131
158
  return res;
132
159
  }
@@ -12,16 +12,17 @@ import {
12
12
  MonomerSetType, PolymerType,
13
13
  } from '@datagrok-libraries/bio/src/helm/types';
14
14
  import {IMonomerLibBase, IMonomerLib, Monomer,
15
- MonomerLibData, MonomerLibSummaryType} from '@datagrok-libraries/bio/src/types';
15
+ MonomerLibData, MonomerLibSummaryType} from '@datagrok-libraries/bio/src/types/monomer-library';
16
16
  import {MolfileHandler} from '@datagrok-libraries/chem-meta/src/parsing-utils/molfile-handler';
17
17
  import {helmTypeToPolymerType} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
18
18
  import {getUserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
19
19
  import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
20
20
 
21
- import {MonomerLibBase, MonomerLibDataType} from './monomer-lib-base';
21
+ import {MonomerLibBase} from './monomer-lib-base';
22
22
 
23
23
  import {_package} from '../../package';
24
24
 
25
+ //@ts-ignore
25
26
  import '../../../css/cell-renderer.css';
26
27
 
27
28
  /** Wrapper for monomers obtained from different sources. For managing monomere
@@ -38,7 +39,7 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
38
39
  private duplicatesNotified: boolean = false;
39
40
 
40
41
  constructor(
41
- monomers: MonomerLibDataType,
42
+ monomers: MonomerLibData,
42
43
  source: string,
43
44
  public readonly error: string | undefined = undefined,
44
45
  ) {
@@ -54,32 +55,6 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
54
55
  return resJSON;
55
56
  }
56
57
 
57
- getMonomer(polymerType: PolymerType | null, argMonomerSymbol: string): Monomer | null {
58
- const logPrefix = `Bio: MonomerLib.getMonomer()`;
59
- // Adjust RNA's 'R' for ribose to 'r' and 'P' for phosphate to 'p' for case-sensitive monomer names.
60
- // There are uppercase 'R' and 'P' at RNA samples in test data 'helm2.csv' but lowercase in HELMCoreLibrary.json
61
- let monomerSymbol = argMonomerSymbol;
62
- if (polymerType == 'RNA' && monomerSymbol == 'R')
63
- monomerSymbol = 'r';
64
- if (polymerType == 'RNA' && monomerSymbol == 'P')
65
- monomerSymbol = 'p';
66
-
67
- let res: Monomer | null = null;
68
-
69
- if (!polymerType) {
70
- _package.logger.warning(`${logPrefix} symbol '${argMonomerSymbol}', polymerType not specified.`);
71
- // Assume any polymer type
72
- for (const [_polymerType, dict] of Object.entries(this._monomers)) {
73
- res = dict[monomerSymbol];
74
- if (res) break;
75
- }
76
- } else {
77
- const dict = this._monomers[polymerType];
78
- res = dict?.[monomerSymbol] ?? null;
79
- }
80
- return res;
81
- }
82
-
83
58
  private _monomerSets: { [biotype: string /*HelmType*/]: MonomerSetType } | null = null;
84
59
 
85
60
  getMonomerSet(biotype: HelmType): MonomerSetType | null {
@@ -158,7 +133,7 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
158
133
  this._isEmpty = this.isEmpty && lib.isEmpty;
159
134
  }
160
135
 
161
- public updateLibs(libList: IMonomerLib[], reload: boolean = false): void {
136
+ public async updateLibs(libList: IMonomerLib[], reload: boolean = false): Promise<void> {
162
137
  if (reload) {
163
138
  this._monomers = {};
164
139
  this._isEmpty = true;
@@ -167,9 +142,8 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
167
142
  for (const lib of libList)
168
143
  if (!lib.error) this._updateLibInt(lib);
169
144
  if (Object.entries(this.duplicateMonomers).length > 0) {
170
- getUserLibSettings().then((settings) => {
171
- this.assignDuplicatePreferences(settings);
172
- });
145
+ const settings = await getUserLibSettings();
146
+ this.assignDuplicatePreferences(settings);
173
147
  } else
174
148
  this._duplicatesHandled = true;
175
149