@datagrok/bio 2.25.14 → 2.25.15

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.
@@ -0,0 +1,27 @@
1
+ {
2
+ "monomerSymbols": [
3
+ "A",
4
+ "C",
5
+ "D",
6
+ "E",
7
+ "F",
8
+ "G",
9
+ "H",
10
+ "I",
11
+ "K",
12
+ "L",
13
+ "M",
14
+ "N",
15
+ "P",
16
+ "Q",
17
+ "R",
18
+ "S",
19
+ "T",
20
+ "V",
21
+ "W",
22
+ "Y"
23
+ ],
24
+ "description": "Canonical amino acids collection",
25
+ "updatedBy": "rizhi",
26
+ "updatedOn": "2026-01-01T12:00:00Z"
27
+ }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Davit Rizhinashvili",
6
6
  "email": "drizhinashvili@datagrok.ai"
7
7
  },
8
- "version": "2.25.14",
8
+ "version": "2.25.15",
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",
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "dependencies": {
46
46
  "@biowasm/aioli": "^3.1.0",
47
- "@datagrok-libraries/bio": "^5.61.6",
47
+ "@datagrok-libraries/bio": "^5.62.0",
48
48
  "@datagrok-libraries/chem-meta": "^1.2.9",
49
49
  "@datagrok-libraries/math": "^1.2.6",
50
50
  "@datagrok-libraries/ml": "^6.10.9",
@@ -5,7 +5,7 @@ import * as ui from 'datagrok-api/ui';
5
5
  import * as DG from 'datagrok-api/dg';
6
6
 
7
7
  import {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
8
- import {DEFAULT_FILES_LIB_PROVIDER_NAME, findProviderWithLibraryName, IMonomerLib, IMonomerSet} from '@datagrok-libraries/bio/src/types/monomer-library';
8
+ import {DEFAULT_FILES_LIB_PROVIDER_NAME, findProviderWithLibraryName, IMonomerLib, IMonomerSet, MonomerCollection} from '@datagrok-libraries/bio/src/types/monomer-library';
9
9
  import {
10
10
  getUserLibSettings, setUserLibSettings,
11
11
  } from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
@@ -19,6 +19,7 @@ import {_package} from '../../package';
19
19
  import {IMonomerLibHelper, IMonomerLibProvider} from '@datagrok-libraries/bio/src/types/monomer-library';
20
20
  import {merge, Observable, Subject} from 'rxjs';
21
21
  import {MonomerLibFromFilesProvider} from './library-file-manager/monomers-lib-provider';
22
+ const MONOMER_COLLECTION_STORAGE_PATH = 'System:AppData/Bio/monomer-collections/';
22
23
 
23
24
  type MonomerLibWindowType = Window & { $monomerLibHelperPromise?: Promise<MonomerLibManager> };
24
25
  declare const window: MonomerLibWindowType;
@@ -409,6 +410,39 @@ export class MonomerLibManager implements IMonomerLibHelper {
409
410
  }
410
411
  }
411
412
 
413
+ async listMonomerCollections(): Promise<string[]> {
414
+ // these are provider less functions. coleections will be in files storage in txt format
415
+ const collections = (await grok.dapi.files.list(MONOMER_COLLECTION_STORAGE_PATH))
416
+ .filter((file) => file.extension === 'json' || file.name.endsWith('.json'));
417
+ return collections.map((file) => file.name);
418
+ }
419
+
420
+ async deleteMonomerCollection(collectionName: string): Promise<void> {
421
+ if (!collectionName.endsWith('.json'))
422
+ collectionName += '.json';
423
+ if (await grok.dapi.files.exists(MONOMER_COLLECTION_STORAGE_PATH + collectionName))
424
+ await grok.dapi.files.delete(MONOMER_COLLECTION_STORAGE_PATH + collectionName);
425
+ }
426
+
427
+ async readMonomerCollection(collectionName: string): Promise<MonomerCollection> {
428
+ if (!collectionName.endsWith('.json'))
429
+ collectionName += '.json';
430
+ const file = await grok.dapi.files.readAsText(MONOMER_COLLECTION_STORAGE_PATH + collectionName);
431
+ return JSON.parse(file) as MonomerCollection;
432
+ }
433
+
434
+ async addOrUpdateMonomerCollection(collectionName: string, monomerSymbols: string[], desc?: string): Promise<void> {
435
+ if (!collectionName.endsWith('.json'))
436
+ collectionName += '.json';
437
+ const content = JSON.stringify({
438
+ description: desc,
439
+ monomerSymbols: monomerSymbols,
440
+ updatedBy: DG.User.current().login,
441
+ updatedOn: new Date().toISOString(),
442
+ } satisfies MonomerCollection, null, 2);
443
+ await grok.dapi.files.writeAsText(MONOMER_COLLECTION_STORAGE_PATH + collectionName, content);
444
+ }
445
+
412
446
  // -- Instance singleton --
413
447
  public static async getInstance(): Promise<MonomerLibManager> {
414
448
  let res = window.$monomerLibHelperPromise;
@@ -255,6 +255,58 @@ export class MonomerManager implements IMonomerManager {
255
255
  }
256
256
  }
257
257
 
258
+ async createNewMonomersCollectionDialog(monomerSymbols: string[]) {
259
+ const existingCollections = (await this.monomerLibManamger.listMonomerCollections()).map((name) => name.toLowerCase());
260
+ const nameInput = ui.input.string('Collection Name', {tooltipText: 'Name of the monomer collection, should be unique', placeholder: 'Enter collection name', nullable: false});
261
+ const descriptionInput = ui.input.string('Description', {tooltipText: 'Description of the monomer collection', placeholder: 'Enter collection description', nullable: true});
262
+ const d = ui.dialog('Create New Monomer Collection')
263
+ .add(nameInput)
264
+ .add(descriptionInput)
265
+ .addButton('Add', async () => {
266
+ if (!nameInput.value || !nameInput.value.trim()) {
267
+ grok.shell.warning('Collection name cannot be empty');
268
+ return;
269
+ }
270
+ const saveAction = async (symbols: string[]) => {
271
+ await this.monomerLibManamger.addOrUpdateMonomerCollection(nameInput.value!, symbols, descriptionInput.value ?? undefined);
272
+ grok.shell.info(`Collection ${nameInput.value} saved successfully`);
273
+ };
274
+ if (existingCollections.includes(nameInput.value!.toLowerCase()) || existingCollections.includes(nameInput.value!.toLowerCase() + '.json')) {
275
+ const confD = ui.dialog('Collection already exists')
276
+ .add(ui.divText(`A collection with the name ${nameInput.value} already exists. Do you want to merge or overwrite it?`));
277
+ confD.addButton('Merge', async () => {
278
+ const existingCollection = await this.monomerLibManamger.readMonomerCollection(nameInput.value!);
279
+ const mergedSymbols = Array.from(new Set([...(existingCollection.monomerSymbols ?? []), ...monomerSymbols]));
280
+ try {
281
+ await saveAction(mergedSymbols);
282
+ } catch (e) {
283
+ grok.shell.error('Error merging monomer collection');
284
+ console.error(e);
285
+ }
286
+ confD.close();
287
+ });
288
+ confD.addButton('Overwrite', async () => {
289
+ try {
290
+ await saveAction(monomerSymbols);
291
+ } catch (e) {
292
+ grok.shell.error('Error overwriting monomer collection');
293
+ console.error(e);
294
+ }
295
+ confD.close();
296
+ });
297
+ confD.show();
298
+ } else {
299
+ try {
300
+ await saveAction(monomerSymbols);
301
+ } catch (e) {
302
+ grok.shell.error('Error creating monomer collection');
303
+ console.error(e);
304
+ }
305
+ }
306
+ d.close();
307
+ }).show();
308
+ }
309
+
258
310
  async createNewLibDialog(monomers?: Monomer[]) {
259
311
  const monomerLibs = await this.monomerLibManamger.getAvaliableLibraryNames();
260
312
  const libNameInput = ui.input.string('Library Name', {
@@ -321,26 +373,36 @@ export class MonomerManager implements IMonomerManager {
321
373
  args.context.tableView.id !== (this.tv!.id ?? '') || !args.item || !args.item.isTableCell || (args.item.tableRowIndex ?? -1) < 0)
322
374
  return;
323
375
  const rowIdx = args.item.tableRowIndex;
324
- args.menu.item('Edit Monomer', async () => {
376
+ const menu = args.menu as DG.Menu;
377
+ menu.item('Edit Monomer', async () => {
325
378
  await this.editMonomer(this.tv!.dataFrame.rows.get(rowIdx));
326
379
  });
327
380
 
328
- args.menu.item('Fix all monomers', () => {
381
+ menu.item('Fix all monomers', () => {
329
382
  this.fixAllMonomers();
330
383
  });
331
384
  if (this.tv!.dataFrame.selection.trueCount > 0) {
332
- args.menu.item('Remove Selected Monomers', async () => {
385
+ const group = menu.group('Selected Monomers');
386
+ group.item('Remove', async () => {
333
387
  const monomers = await Promise.all(Array.from(this.tv!.dataFrame.selection.getSelectedIndexes())
334
388
  .map((r) => monomerFromDfRow(this.tv!.dataFrame.rows.get(r))));
335
389
  this._newMonomerForm.removeMonomers(monomers, this.libInput.value!);
336
390
  });
337
- args.menu.item('Selection To New Library', async () => {
391
+ group.item('Create Library', async () => {
338
392
  const monomers = await Promise.all(Array.from(this.tv!.dataFrame.selection.getSelectedIndexes())
339
393
  .map((r) => monomerFromDfRow(this.tv!.dataFrame.rows.get(r))));
340
394
  this.createNewLibDialog(monomers);
341
395
  });
396
+ group.item('Create Collection', async () => {
397
+ const monomerSymbols = Array.from(this.tv!.dataFrame.selection.getSelectedIndexes())
398
+ .map((r) => this.tv!.dataFrame.col(MONOMER_DF_COLUMN_NAMES.SYMBOL)!.get(r) as string)
399
+ .filter((s): s is string => !!s && s.trim().length > 0);
400
+ if (monomerSymbols.length === 0)
401
+ return grok.shell.warning('No valid monomer symbols found in selection');
402
+ this.createNewMonomersCollectionDialog(monomerSymbols);
403
+ });
342
404
  } else {
343
- args.menu.item('Remove Monomer', async () => {
405
+ menu.item('Remove Monomer', async () => {
344
406
  const monomer = await monomerFromDfRow(this.tv!.dataFrame.rows.get(rowIdx));
345
407
  this._newMonomerForm.removeMonomers([monomer], this.libInput.value!);
346
408
  });