@datagrok/bio 2.11.30 → 2.11.33

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 (53) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/36.js +1 -1
  3. package/dist/36.js.map +1 -1
  4. package/dist/42.js +1 -1
  5. package/dist/42.js.map +1 -1
  6. package/dist/590.js +2 -0
  7. package/dist/590.js.map +1 -0
  8. package/dist/709.js +1 -2
  9. package/dist/709.js.map +1 -1
  10. package/dist/79.js.map +1 -1
  11. package/dist/895.js +3 -0
  12. package/dist/895.js.map +1 -0
  13. package/dist/package-test.js +8 -1
  14. package/dist/package-test.js.LICENSE.txt +1 -0
  15. package/dist/package-test.js.map +1 -1
  16. package/dist/package.js +8 -1
  17. package/dist/package.js.LICENSE.txt +1 -0
  18. package/dist/package.js.map +1 -1
  19. package/files/{data → monomer-libraries}/HELMCoreLibrary.json +594 -594
  20. package/files/tests/libraries/HELMmonomerSchema.json +96 -0
  21. package/package.json +12 -10
  22. package/scripts/sequence_generator.md +48 -0
  23. package/scripts/sequence_generator.py +515 -256
  24. package/src/package-test.ts +4 -0
  25. package/src/package.ts +26 -24
  26. package/src/tests/WebLogo-layout-tests.ts +37 -0
  27. package/src/tests/WebLogo-positions-test.ts +5 -0
  28. package/src/tests/WebLogo-project-tests.ts +63 -0
  29. package/src/tests/activity-cliffs-tests.ts +3 -2
  30. package/src/tests/monomer-libraries-tests.ts +7 -4
  31. package/src/tests/scoring.ts +3 -2
  32. package/src/tests/substructure-filters-tests.ts +3 -2
  33. package/src/tests/to-atomic-level-tests.ts +3 -2
  34. package/src/utils/helm-to-molfile.ts +3 -3
  35. package/src/utils/monomer-lib/lib-manager.ts +116 -0
  36. package/src/utils/monomer-lib/library-file-manager/consts.ts +1 -0
  37. package/src/utils/monomer-lib/library-file-manager/custom-monomer-lib-handlers.ts +80 -0
  38. package/src/utils/monomer-lib/library-file-manager/event-manager.ts +58 -0
  39. package/src/utils/monomer-lib/library-file-manager/file-manager.ts +187 -0
  40. package/src/utils/monomer-lib/library-file-manager/file-validator.ts +56 -0
  41. package/src/utils/monomer-lib/library-file-manager/style.css +8 -0
  42. package/src/utils/monomer-lib/library-file-manager/ui.ts +224 -0
  43. package/src/utils/monomer-lib/monomer-lib.ts +114 -0
  44. package/src/utils/poly-tool/const.ts +28 -0
  45. package/src/utils/poly-tool/monomer-lib-handler.ts +115 -0
  46. package/src/utils/poly-tool/types.ts +6 -0
  47. package/src/utils/poly-tool/ui.ts +2 -2
  48. package/src/viewers/vd-regions-viewer.ts +5 -4
  49. package/src/viewers/web-logo-viewer.ts +6 -5
  50. package/src/widgets/bio-substructure-filter.ts +4 -1
  51. package/files/libraries/HELMCoreLibrary.json +0 -18218
  52. package/src/utils/monomer-lib.ts +0 -305
  53. /package/dist/{709.js.LICENSE.txt → 895.js.LICENSE.txt} +0 -0
@@ -0,0 +1,187 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
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 {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
7
+ import {LIB_PATH} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
8
+ import {MonomerLib} from '../monomer-lib';
9
+ import {
10
+ HELM_REQUIRED_FIELD as REQ,
11
+ } from '@datagrok-libraries/bio/src/utils/const';
12
+ import {JSONSchemaType} from 'ajv';
13
+ import {HELM_JSON_SCHEMA_PATH} from './consts';
14
+ import {MonomerLibFileEventManager} from './event-manager';
15
+
16
+ import {MonomerLibFileValidator} from './file-validator';
17
+ import {MonomerLibManager} from '../lib-manager';
18
+
19
+ /** Singleton for adding, validation and reading of monomer library files.
20
+ * All files **must** be aligned to the HELM standard before adding. */
21
+ export class MonomerLibFileManager {
22
+ private constructor(
23
+ private libraryFileValidator: MonomerLibFileValidator,
24
+ private libraryEventManager: MonomerLibFileEventManager,
25
+ ) {
26
+ this.libraryEventManager.updateValidLibraryFileListRequested$.subscribe(async () => {
27
+ await this.updateValidLibraryList();
28
+ });
29
+ }
30
+
31
+ private static instance: MonomerLibFileManager | undefined;
32
+
33
+ static async getInstance(
34
+ libraryEventManager: MonomerLibFileEventManager,
35
+ ): Promise<MonomerLibFileManager> {
36
+ if (MonomerLibFileManager.instance === undefined) {
37
+ const helmSchemaRaw = await grok.dapi.files.readAsText(HELM_JSON_SCHEMA_PATH);
38
+ const helmSchema = JSON.parse(helmSchemaRaw) as JSONSchemaType<any>;
39
+
40
+ const fileValidator = new MonomerLibFileValidator(helmSchema);
41
+ MonomerLibFileManager.instance = new MonomerLibFileManager(fileValidator, libraryEventManager);
42
+ }
43
+
44
+ return MonomerLibFileManager.instance;
45
+ }
46
+
47
+ /** Add standard .json monomer library */
48
+ async addLibraryFile(fileContent: string, fileName: string): Promise<void> {
49
+ try {
50
+ if (await this.libraryFileExists(fileName)) {
51
+ grok.shell.error(`File ${fileName} already exists`);
52
+ return;
53
+ }
54
+
55
+ await this.validateAgainstHELM(fileContent, fileName);
56
+ await grok.dapi.files.writeAsText(LIB_PATH + `${fileName}`, fileContent);
57
+ await this.updateValidLibraryList();
58
+ const fileExists = await grok.dapi.files.exists(LIB_PATH + `${fileName}`);
59
+ if (!fileExists)
60
+ grok.shell.error(`Failed to add ${fileName} library`);
61
+ else
62
+ grok.shell.info(`Added ${fileName} HELM library`);
63
+ } catch (e) {
64
+ console.error(e);
65
+ grok.shell.error(`Failed to add ${fileName} library`);
66
+ }
67
+ }
68
+
69
+ async deleteLibraryFile(fileName: string): Promise<void> {
70
+ try {
71
+ await grok.dapi.files.delete(LIB_PATH + `${fileName}`);
72
+ await this.updateValidLibraryList();
73
+ grok.shell.info(`Deleted ${fileName} library`);
74
+ } catch (e) {
75
+ console.error(e);
76
+ const fileExists = await grok.dapi.files.exists(LIB_PATH + `${fileName}`);
77
+ if (fileExists)
78
+ grok.shell.error(`Failed to delete ${fileName} library`);
79
+ else
80
+ grok.shell.warning(`File ${fileName} already deleted, refresh the list`);
81
+ }
82
+ }
83
+
84
+ async loadLibraryFromFile(path: string, fileName: string): Promise<IMonomerLib> {
85
+ let rawLibData: any[] = [];
86
+ const fileSource = new DG.FileSource(path);
87
+ const file = await fileSource.readAsText(fileName);
88
+ rawLibData = JSON.parse(file);
89
+ const monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } } = {};
90
+ const polymerTypes: string[] = [];
91
+ rawLibData.forEach((monomer) => {
92
+ if (!polymerTypes.includes(monomer[REQ.POLYMER_TYPE])) {
93
+ monomers[monomer[REQ.POLYMER_TYPE]] = {};
94
+ polymerTypes.push(monomer[REQ.POLYMER_TYPE]);
95
+ }
96
+ monomers[monomer[REQ.POLYMER_TYPE]][monomer[REQ.SYMBOL]] = monomer as Monomer;
97
+ });
98
+
99
+ return new MonomerLib(monomers);
100
+ }
101
+
102
+ getValidLibraryPaths(): string[] {
103
+ return this.libraryEventManager.getValidFilesPathList();
104
+ }
105
+
106
+ private async libraryFileExists(fileName: string): Promise<boolean> {
107
+ return await grok.dapi.files.exists(LIB_PATH + `${fileName}`);
108
+ }
109
+
110
+ private async updateValidLibraryList(): Promise<void> {
111
+ const invalidFiles = [] as string[];
112
+ // console.log(`files before validation:`, this.libraryEventManager.getValidFilesPathList());
113
+ const filePaths = await this.getFilePathsAtDefaultLocation();
114
+
115
+ if (!this.fileListHasChanged(filePaths))
116
+ return;
117
+
118
+ for (const path of filePaths) {
119
+ if (!path.endsWith('.json')) {
120
+ invalidFiles.push(path);
121
+ continue;
122
+ }
123
+
124
+ const fileContent = await grok.dapi.files.readAsText(LIB_PATH + `${path}`);
125
+ if (!this.isValidHELMLibrary(fileContent, path))
126
+ invalidFiles.push(path);
127
+ }
128
+
129
+ const validLibraryPaths = filePaths.filter((path) => !invalidFiles.includes(path));
130
+
131
+ if (this.fileListHasChanged(validLibraryPaths)) {
132
+ this.libraryEventManager.changeValidFilesPathList(validLibraryPaths);
133
+ MonomerLibManager.instance.loadLibraries(true);
134
+ }
135
+ // console.log(`files after validation:`, this.libraryEventManager.getValidFilesPathList());
136
+
137
+ if (validLibraryPaths.some((el) => !el.endsWith('.json')))
138
+ console.warn(`Wrong validation: ${validLibraryPaths}`);
139
+
140
+ if (invalidFiles.length > 0) {
141
+ const message = `Invalid monomer library files in ${LIB_PATH}` +
142
+ `, consider fixing or removing them: ${invalidFiles.join(', ')}`;
143
+
144
+ console.warn(message);
145
+ grok.shell.warning(message);
146
+ }
147
+ }
148
+
149
+ private fileListHasChanged(newList: string[]): boolean {
150
+ const currentList = this.libraryEventManager.getValidFilesPathList();
151
+ return newList.length !== currentList.length || newList.some((el, i) => el !== currentList[i]);
152
+ }
153
+
154
+ private async validateAgainstHELM(fileContent: string, fileName: string): Promise<void> {
155
+ const isValid = this.isValidHELMLibrary(fileContent, fileName);
156
+ if (!isValid)
157
+ throw new Error(`File ${fileName} does not satisfy HELM standard`);
158
+ }
159
+
160
+ private isValidHELMLibrary(fileContent: string, fileName: string): boolean {
161
+ return this.libraryFileValidator.validateFile(fileContent, fileName);
162
+ }
163
+
164
+ /** Get relative paths for files in LIB_PATH */
165
+ private async getFilePathsAtDefaultLocation(): Promise<string[]> {
166
+ const list = await grok.dapi.files.list(LIB_PATH);
167
+ const paths = list.map((fileInfo) => {
168
+ return fileInfo.fullPath;
169
+ });
170
+
171
+ // console.log(`retreived paths:`, paths);
172
+
173
+ // WARNING: an extra sanity check,
174
+ // caused by unexpected behavior of grok.dapi.files.list() when it returns non-existent paths
175
+ const existingPaths = [] as string[];
176
+ for (const path of paths) {
177
+ const exists = await grok.dapi.files.exists(path);
178
+ if (exists)
179
+ existingPaths.push(path);
180
+ }
181
+
182
+ return existingPaths.map((path) => {
183
+ // Get relative path (to LIB_PATH)
184
+ return path.substring(LIB_PATH.length);
185
+ });
186
+ }
187
+ }
@@ -0,0 +1,56 @@
1
+ import {JSONSchemaType, ValidateFunction} from 'ajv';
2
+ import Ajv2020 from 'ajv/dist/2020';
3
+ import addErrors from 'ajv-errors';
4
+
5
+ export class MonomerLibFileValidator {
6
+ private validateMonomerSchema: ValidateFunction<any>;
7
+
8
+ constructor(private helmMonomerSchema: JSONSchemaType<any>) {
9
+ const ajv = new Ajv2020({allErrors: true, strictTuples: false});
10
+ addErrors(ajv);
11
+ this.validateMonomerSchema = ajv.compile(this.helmMonomerSchema);
12
+ }
13
+
14
+ validateFile(fileContent: string, fileName: string): boolean {
15
+ const jsonContent = this.parseJson(fileContent);
16
+ if (jsonContent === null)
17
+ return false;
18
+
19
+ if (!Array.isArray(jsonContent)) {
20
+ console.warn(
21
+ 'Bio: Monomer Library File Validator: Invalid JSON format: The file must contain an array of monomers.'
22
+ );
23
+ return false;
24
+ }
25
+
26
+ return this.validateJsonContent(jsonContent, fileName);
27
+ }
28
+
29
+ private parseJson(fileContent: string): any[] | null {
30
+ try {
31
+ return JSON.parse(fileContent);
32
+ } catch (e) {
33
+ console.error('Bio: Monomer Library File Validator: Invalid JSON format:', e);
34
+ return null;
35
+ }
36
+ }
37
+
38
+ private validateJsonContent(jsonContent: any[], fileName: string): boolean {
39
+ let isValid = true;
40
+ for (const monomer of jsonContent) {
41
+ isValid = this.validateMonomerSchema(monomer);
42
+ if (!isValid) {
43
+ console.warn(
44
+ `Bio: Monomer Library File Validator:\nfile${fileName}\n monomer violating JSON schema:`,
45
+ monomer,
46
+ '\nError reason: ',
47
+ this.validateMonomerSchema.errors,
48
+ `\nThere may be other errors in ${fileName} since the validation is stopped after the first error.`,
49
+ ' Please, verify that the monomer library file satisfies the JSON schema'
50
+ );
51
+ break;
52
+ }
53
+ }
54
+ return isValid;
55
+ }
56
+ }
@@ -0,0 +1,8 @@
1
+ .monomer-lib-controls-form {
2
+ margin-left: auto;
3
+ margin-right: auto;
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: center;
7
+ width: 100%;
8
+ }
@@ -0,0 +1,224 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
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 $ from 'cash-dom';
7
+ import * as rxjs from 'rxjs';
8
+ import './style.css';
9
+
10
+ import {
11
+ getUserLibSettings, setUserLibSettings
12
+ } from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
13
+ import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
14
+ import {MonomerLibManager} from '../lib-manager';
15
+
16
+ import {MonomerLibFileManager} from './file-manager';
17
+ import {MonomerLibFileEventManager} from './event-manager';
18
+
19
+ export async function showManageLibrariesDialog(): Promise<void> {
20
+ await DialogWrapper.showDialog();
21
+ }
22
+
23
+ export async function getMonomerLibraryManagerLink(): Promise<DG.Widget> {
24
+ const link = ui.label('Manage monomer libraries');
25
+ $(link).addClass('d4-link-action');
26
+ link.onclick = async () => await showManageLibrariesDialog();
27
+ return new DG.Widget(
28
+ link
29
+ );
30
+ }
31
+
32
+ class MonomerLibraryManagerWidget {
33
+ private constructor(
34
+ private eventManager: MonomerLibFileEventManager
35
+ ) { }
36
+
37
+ private static _instance: MonomerLibraryManagerWidget;
38
+
39
+ static async getContent(eventManager: MonomerLibFileEventManager): Promise<DG.Widget> {
40
+ if (!MonomerLibraryManagerWidget._instance)
41
+ MonomerLibraryManagerWidget._instance = new MonomerLibraryManagerWidget(eventManager);
42
+
43
+ if (!MonomerLibraryManagerWidget._instance.widget)
44
+ MonomerLibraryManagerWidget._instance.widget = await MonomerLibraryManagerWidget._instance.createWidget();
45
+
46
+ return MonomerLibraryManagerWidget._instance.widget;
47
+ }
48
+
49
+ private monomerLibFileManager: MonomerLibFileManager;
50
+ private widget: DG.Widget | undefined;
51
+
52
+ private async createWidget() {
53
+ this.monomerLibFileManager = await MonomerLibFileManager.getInstance(this.eventManager);
54
+ const content = await this.getWidgetContent();
55
+ this.eventManager.addLibraryFileRequested$.subscribe(
56
+ async () => await this.promptToAddLibraryFiles()
57
+ );
58
+ return new DG.Widget(content);
59
+ }
60
+
61
+ private async getWidgetContent(): Promise<HTMLElement> {
62
+ this.monomerLibFileManager = await MonomerLibFileManager.getInstance(this.eventManager);
63
+ const libControlsForm = await LibraryControlsManager.createControlsForm(this.eventManager);
64
+ $(libControlsForm).addClass('monomer-lib-controls-form');
65
+ const widgetContent = ui.divV([libControlsForm]);
66
+ return widgetContent;
67
+ }
68
+
69
+ private async promptToAddLibraryFiles(): Promise<void> {
70
+ DG.Utils.openFile({
71
+ accept: '.json',
72
+ open: async (selectedFile) => {
73
+ const content = await selectedFile.text();
74
+ const name = selectedFile.name;
75
+ const progressIndicator = DG.TaskBarProgressIndicator.create(`Adding ${name} as a monomer library`);
76
+ try {
77
+ await this.monomerLibFileManager.addLibraryFile(content, name);
78
+ // this.eventManager.updateLibrarySelectionStatus(name, true);
79
+ } catch (e) {
80
+ grok.shell.error(`File ${name} is not a valid monomer library, verify it is aligned to HELM JSON schema.`);
81
+ } finally {
82
+ progressIndicator.close();
83
+ }
84
+ },
85
+ });
86
+ }
87
+ }
88
+
89
+ class LibraryControlsManager {
90
+ private constructor(private eventManager: MonomerLibFileEventManager) {
91
+ this.eventManager.updateUIControlsRequested$.subscribe(
92
+ async () => await this.updateControlsForm()
93
+ );
94
+ this.eventManager.librarySelectionRequested$.subscribe(
95
+ async ([fileName, isSelected]) => await this.updateLibrarySelectionStatus(isSelected, fileName)
96
+ );
97
+ }
98
+ private monomerLibFileManager: MonomerLibFileManager;
99
+ private userLibSettings: UserLibSettings;
100
+
101
+ static async createControlsForm(eventManager: MonomerLibFileEventManager): Promise<HTMLElement> {
102
+ const manager = new LibraryControlsManager(eventManager);
103
+ await manager.initialize();
104
+
105
+ return await manager._createControlsForm();
106
+ }
107
+
108
+ private async _createControlsForm(): Promise<HTMLElement> {
109
+ this.monomerLibFileManager = await MonomerLibFileManager.getInstance(this.eventManager);
110
+ const libraryControls = await this.createLibraryControls();
111
+ const inputsForm = ui.form(libraryControls);
112
+ $(inputsForm).addClass('monomer-lib-controls-form');
113
+
114
+ return inputsForm;
115
+ }
116
+
117
+ private async initialize(): Promise<void> {
118
+ this.userLibSettings = await getUserLibSettings();
119
+ };
120
+
121
+ private async updateControlsForm(): Promise<void> {
122
+ const updatedForm = await this._createControlsForm();
123
+ $('.monomer-lib-controls-form').replaceWith(updatedForm);
124
+ }
125
+
126
+ private async createLibraryControls(): Promise<DG.InputBase<boolean | null>[]> {
127
+ const fileManager = await MonomerLibFileManager.getInstance(this.eventManager);
128
+ const libFileNameList: string[] = fileManager.getValidLibraryPaths();
129
+ return libFileNameList.map((libFileName) => this.createLibInput(libFileName));
130
+ }
131
+
132
+ private createLibInput(libFileName: string): DG.InputBase<boolean | null> {
133
+ const isMonomerLibrarySelected = !this.userLibSettings.exclude.includes(libFileName);
134
+ const libInput = ui.boolInput(
135
+ libFileName,
136
+ isMonomerLibrarySelected,
137
+ (isSelected: boolean) => this.eventManager.updateLibrarySelectionStatus(libFileName, isSelected)
138
+ );
139
+ ui.tooltip.bind(libInput.root, `Include monomers from ${libFileName}`);
140
+ const deleteIcon = ui.iconFA('trash-alt', () => this.promptForLibraryDeletion(libFileName));
141
+ ui.tooltip.bind(deleteIcon, `Delete ${libFileName}`);
142
+ libInput.addOptions(deleteIcon);
143
+ return libInput;
144
+ }
145
+
146
+ private async updateLibrarySelectionStatus(
147
+ isMonomerLibrarySelected: boolean,
148
+ libFileName: string
149
+ ): Promise<void> {
150
+ this.updateLibrarySettings(isMonomerLibrarySelected, libFileName);
151
+ await setUserLibSettings(this.userLibSettings);
152
+ await MonomerLibManager.instance.loadLibraries(true);
153
+ grok.shell.info('Monomer library user settings saved');
154
+ }
155
+
156
+ private updateLibrarySettings(
157
+ isLibrarySelected: boolean | null,
158
+ libFileName: string,
159
+ ): void {
160
+ if (isLibrarySelected) {
161
+ // Remove selected library from exclusion list
162
+ this.userLibSettings.exclude = this.userLibSettings.exclude.filter((libName) => libName !== libFileName);
163
+ } else if (!this.userLibSettings.exclude.includes(libFileName)) {
164
+ // Add unselected library to exclusion list
165
+ this.userLibSettings.exclude.push(libFileName);
166
+ }
167
+ }
168
+
169
+ private promptForLibraryDeletion(fileName: string): void {
170
+ const dialog = ui.dialog('Warning');
171
+ dialog.add(ui.divText(`Delete file ${fileName}?`))
172
+ .onOK(async () => {
173
+ try {
174
+ const progressIndicator = DG.TaskBarProgressIndicator.create(`Deleting ${fileName} library`);
175
+ this.updateLibrarySelectionStatus(false, fileName);
176
+ await this.monomerLibFileManager.deleteLibraryFile(fileName);
177
+ progressIndicator.close();
178
+ } catch (e) {
179
+ console.error(e);
180
+ grok.shell.error(`Failed to delete ${fileName} library`);
181
+ }
182
+ })
183
+ .showModal(false);
184
+ }
185
+ }
186
+
187
+ class DialogWrapper {
188
+ private constructor() { }
189
+
190
+ private static _instance: DialogWrapper;
191
+ private dialog?: DG.Dialog;
192
+ private closeDialogSubject$ = new rxjs.Subject<void>();
193
+
194
+ static async showDialog(): Promise<void> {
195
+ if (!DialogWrapper._instance) {
196
+ DialogWrapper._instance = new DialogWrapper();
197
+ DialogWrapper._instance.closeDialogSubject$.subscribe(
198
+ () => { DialogWrapper._instance.dialog = undefined; }
199
+ );
200
+ }
201
+
202
+ if (!DialogWrapper._instance.dialog)
203
+ DialogWrapper._instance.dialog = await DialogWrapper._instance.getDialog();
204
+
205
+ DialogWrapper._instance.dialog.show();
206
+ }
207
+
208
+ private async getDialog(): Promise<DG.Dialog> {
209
+ const eventManager = MonomerLibFileEventManager.getInstance();
210
+ const widget = await MonomerLibraryManagerWidget.getContent(eventManager);
211
+ const dialog = ui.dialog('Manage monomer libraries');
212
+ $(dialog.root).css('width', '350px');
213
+ dialog.clear();
214
+ dialog.addButton(
215
+ 'Add',
216
+ () => eventManager.addLibraryFile(),
217
+ undefined,
218
+ 'Upload new HELM monomer library'
219
+ );
220
+ dialog.add(widget);
221
+ dialog.onClose.subscribe(() => this.closeDialogSubject$.next());
222
+ return dialog;
223
+ }
224
+ }
@@ -0,0 +1,114 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
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 {Observable, Subject} from 'rxjs';
7
+
8
+ import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
9
+ import {MolfileHandler} from '@datagrok-libraries/chem-meta/src/parsing-utils/molfile-handler';
10
+
11
+ import {_package} from '../../package';
12
+
13
+ /** Wrapper for monomers obtained from different sources. For managing monomere
14
+ * libraries, use MolfileHandler class instead */
15
+ export class MonomerLib implements IMonomerLib {
16
+ public readonly error: string | undefined;
17
+
18
+ private _monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } } = {};
19
+ private _onChanged = new Subject<any>();
20
+
21
+ constructor(monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } }, error?: string) {
22
+ this._monomers = monomers;
23
+ this.error = error;
24
+ }
25
+
26
+ getMonomer(polymerType: string, monomerSymbol: string): Monomer | null {
27
+ if (polymerType in this._monomers! && monomerSymbol in this._monomers![polymerType])
28
+ return this._monomers![polymerType][monomerSymbol];
29
+ else
30
+ return null;
31
+ }
32
+
33
+ getPolymerTypes(): string[] {
34
+ return Object.keys(this._monomers);
35
+ }
36
+
37
+ getMonomerMolsByPolymerType(polymerType: string): { [monomerSymbol: string]: string } {
38
+ const res: { [monomerSymbol: string]: string } = {};
39
+
40
+ Object.keys(this._monomers[polymerType] ?? {}).forEach((monomerSymbol) => {
41
+ res[monomerSymbol] = this._monomers[polymerType][monomerSymbol].molfile;
42
+ });
43
+
44
+ return res;
45
+ }
46
+
47
+ getMonomerSymbolsByType(polymerType: string): string[] {
48
+ return Object.keys(this._monomers[polymerType]);
49
+ }
50
+
51
+ /** Get a list of monomers with specified element attached to specified
52
+ * R-group
53
+ * WARNING: RGroup numbering starts from 1, not 0*/
54
+ getMonomerSymbolsByRGroup(rGroupNumber: number, polymerType: string, element?: string): string[] {
55
+ const monomerSymbols = this.getMonomerSymbolsByType(polymerType);
56
+ let monomers = monomerSymbols.map((sym) => this.getMonomer(polymerType, sym));
57
+ monomers = monomers.filter((el) => el !== null);
58
+ if (monomers.length === 0)
59
+ return [];
60
+
61
+ function findAllIndices<T>(arr: T[], element: T): number[] {
62
+ return arr.map((value, index) => (value === element ? index : -1))
63
+ .filter((index) => index !== -1);
64
+ }
65
+
66
+ monomers = monomers.filter((monomer) => {
67
+ if (!monomer?.rgroups)
68
+ return false;
69
+ let criterion = monomer?.rgroups.length >= rGroupNumber;
70
+ const molfileHandler = MolfileHandler.getInstance(monomer.molfile);
71
+ const rGroupIndices = findAllIndices(molfileHandler.atomTypes, 'R#');
72
+ criterion &&= true;
73
+ return criterion;
74
+ });
75
+ return monomers.map((monomer) => monomer?.symbol!);
76
+ }
77
+
78
+ get onChanged(): Observable<any> {
79
+ return this._onChanged;
80
+ }
81
+
82
+ private _updateInt(lib: IMonomerLib): void {
83
+ const typesNew = lib.getPolymerTypes();
84
+ const types = this.getPolymerTypes();
85
+
86
+ typesNew.forEach((type) => {
87
+ //could possibly rewrite -> TODO: check duplicated monomer symbol
88
+
89
+ if (!types.includes(type))
90
+ this._monomers![type] = {};
91
+
92
+ const monomers = lib.getMonomerSymbolsByType(type);
93
+ monomers.forEach((monomerSymbol) => {
94
+ this._monomers[type][monomerSymbol] = lib.getMonomer(type, monomerSymbol)!;
95
+ });
96
+ });
97
+ }
98
+
99
+ public update(lib: IMonomerLib): void {
100
+ this._updateInt(lib);
101
+ this._onChanged.next();
102
+ }
103
+
104
+ public updateLibs(libList: IMonomerLib[], reload: boolean = false): void {
105
+ if (reload) this._monomers = {};
106
+ for (const lib of libList) if (!lib.error) this._updateInt(lib);
107
+ this._onChanged.next();
108
+ }
109
+
110
+ public clear(): void {
111
+ this._monomers = {};
112
+ this._onChanged.next();
113
+ }
114
+ }
@@ -1,3 +1,5 @@
1
+ import {HELM_REQUIRED_FIELD} from '@datagrok-libraries/bio/src/utils/const';
2
+
1
3
  export const enum HELM_WRAPPER {
2
4
  LEFT = 'PEPTIDE1{',
3
5
  RIGHT = '}$$$$',
@@ -14,3 +16,29 @@ export const enum CYCLIZATION_TYPE {
14
16
  R3 = 'R3-R3',
15
17
  }
16
18
 
19
+ export const helmFieldsToPolyToolInputFields = {
20
+ [HELM_REQUIRED_FIELD.SYMBOL]: 'Short Name',
21
+ [HELM_REQUIRED_FIELD.NAME]: 'Medium Name',
22
+ [HELM_REQUIRED_FIELD.SMILES]: 'SMILES',
23
+ };
24
+
25
+ export const R_GROUP_BLOCK_DUMMY = [
26
+ {
27
+ 'capGroupSMILES': '[*:1][H]',
28
+ 'alternateId': 'R1-H',
29
+ 'capGroupName': 'H',
30
+ 'label': 'R1'
31
+ },
32
+ {
33
+ 'capGroupSMILES': 'O[*:2]',
34
+ 'alternateId': 'R2-OH',
35
+ 'capGroupName': 'OH',
36
+ 'label': 'R2'
37
+ },
38
+ {
39
+ 'capGroupSMILES': '[*:3][H]',
40
+ 'alternateId': 'R3-H',
41
+ 'capGroupName': 'H',
42
+ 'label': 'R3'
43
+ }
44
+ ];