@datagrok/hit-triage 1.3.7 → 1.3.8

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/hit-triage",
3
3
  "friendlyName": "HitTriage",
4
- "version": "1.3.7",
4
+ "version": "1.3.8",
5
5
  "author": {
6
6
  "name": "Davit Rizhinashvili",
7
7
  "email": "drizhinashvili@datagrok.ai"
@@ -10,6 +10,7 @@ import {getNewVid} from '../utils/calculate-single-cell';
10
10
  type NewHitDesignCampaignRes = {
11
11
  df: DG.DataFrame,
12
12
  campaignProps: {[key: string]: any}
13
+ name: string,
13
14
  }
14
15
 
15
16
  type HitDesignCampaignAccordeon = {
@@ -50,12 +51,15 @@ export function newHitDesignCampaignAccordeon(template: HitDesignTemplate, pepti
50
51
  {name: field.name, type: CampaignFieldTypes[field.type as keyof typeof CampaignFieldTypes],
51
52
  nullable: !field.required, ...(field.semtype ? {semType: field.semtype} : {})}));
52
53
  const campaignPropsObject: {[key: string]: any} = {};
54
+ const campaignNameInput = ui.input.string('Campaing Name', {tooltipText: 'New campaign name. If empty, campaign code will be used.'});
53
55
  const campaignPropsForm = ui.input.form(campaignPropsObject, campaignProps);
56
+ campaignPropsForm.prepend(campaignNameInput.root);
54
57
  campaignPropsForm.classList.remove('ui-form');
55
58
  const form = ui.div([
56
59
  ...(template.campaignFields?.length > 0 ? []: []),
57
60
  campaignPropsForm,
58
61
  ]);
62
+
59
63
  const buttonsDiv = ui.buttonsInput([]); // div for create and cancel buttons
60
64
  form.appendChild(buttonsDiv);
61
65
  const okPromise = new Promise<NewHitDesignCampaignRes>((resolve) => {
@@ -69,7 +73,7 @@ export function newHitDesignCampaignAccordeon(template: HitDesignTemplate, pepti
69
73
  }
70
74
  }
71
75
  }
72
- resolve({df, campaignProps: campaignPropsObject});
76
+ resolve({df, campaignProps: campaignPropsObject, name: campaignNameInput.value});
73
77
  });
74
78
  buttonsDiv.appendChild(startCampaignButton);
75
79
  });
@@ -10,7 +10,7 @@ import {CampaignIdKey, CampaignJsonName, CampaignTableName,
10
10
  import {calculateColumns, calculateSingleCellValues, getNewVid} from './utils/calculate-single-cell';
11
11
  import '../../css/hit-triage.css';
12
12
  import {_package} from '../package';
13
- import {addBreadCrumbsToRibbons, checkRibbonsHaveSubmit, modifyUrl, toFormatedDateString} from './utils';
13
+ import {addBreadCrumbsToRibbons, checkRibbonsHaveSubmit, editableTableField, modifyUrl, toFormatedDateString} from './utils';
14
14
  import {HitDesignSubmitView} from './hit-design-views/submit-view';
15
15
  import {getTilesViewDialog} from './hit-design-views/tiles-view';
16
16
  import {HitAppBase} from './hit-app-base';
@@ -174,6 +174,11 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
174
174
 
175
175
  get designViewName(): string {return this._designViewName;}
176
176
 
177
+ set designViewName(name: string) {
178
+ this._designViewName = name;
179
+ this._designView && (this._designView.name = name);
180
+ }
181
+
177
182
  get molColName() {
178
183
  return this._molColName ??= this.dataFrame?.columns.bySemType(DG.SEMTYPE.MOLECULE)?.name ?? HitDesignMolColName;
179
184
  }
@@ -337,7 +342,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
337
342
  getTilesViewDialog(this, () => this._designView ?? null);
338
343
  });
339
344
 
340
- const submitButton = ui.bigButton('Submit', () => {
345
+ const submitButton = ui.bigButton('Submit...', () => {
341
346
  const dialogContent = this._submitView?.render();
342
347
  if (dialogContent) {
343
348
  const dlg = ui.dialog('Submit');
@@ -385,10 +390,11 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
385
390
  }
386
391
 
387
392
  protected getDesignView(): DG.TableView {
393
+ this._designView && this._designView.close();
388
394
  const subs: Subscription[] = [];
389
395
  const isNew = this.dataFrame!.col(this.molColName)?.toList().every((m) => !m && m === '');
390
396
  const view = grok.shell.addTableView(this.dataFrame!);
391
- this._designViewName = this.campaign?.name ?? this._designViewName;
397
+ this._designViewName = this.campaign?.friendlyName ?? this.campaign?.name ?? this._designViewName;
392
398
  view.name = this._designViewName;
393
399
 
394
400
  view._onAdded();
@@ -635,6 +641,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
635
641
  newPathInput.addOptions(cancelButton);
636
642
  newPathInput.addOptions(saveButton);
637
643
  pathDiv.appendChild(newPathInput.root);
644
+ newPathInput.input.focus();
638
645
  }, 'Edit file path');
639
646
  editIcon.style.marginLeft = '5px';
640
647
  const folderPath = getFolderPath();
@@ -644,7 +651,21 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
644
651
  return pathDiv;
645
652
  };
646
653
 
654
+ const campaignName = this.campaign?.friendlyName ?? this.campaign?.name ?? this.campaignId!;
655
+ const campaignNameField = editableTableField(ui.divText(campaignName), {
656
+ tooltip: 'Edit Campaign Name',
657
+ nullable: false,
658
+ onOk: async (a) => {
659
+ this.campaign!.friendlyName = a!;
660
+ await this.saveCampaign(true);
661
+ },
662
+ validator: async (a) => !!a?.trim() ? null : 'Campaign name can not be empty',
663
+ });
664
+
665
+
647
666
  return {
667
+ 'Name': campaignNameField,
668
+ 'Code': this.campaignId ?? this._campaign?.name,
648
669
  'Template': this.template?.name ?? 'Molecules',
649
670
  'File Path': getPathEditor(),
650
671
  ...(this.campaign?.authorUserFriendlyName ? {'Author': this.campaign.authorUserFriendlyName} : {}),
@@ -657,7 +678,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
657
678
  };
658
679
  }
659
680
 
660
- async saveCampaign(notify = true, isCreating = false): Promise<HitDesignCampaign> {
681
+ async saveCampaign(notify = true, isCreating = false, customProps?: Partial<HitDesignCampaign>): Promise<HitDesignCampaign> {
661
682
  const campaignId = this.campaignId!;
662
683
  const templateName = this.template!.name;
663
684
  const enrichedDf = this.dataFrame!;
@@ -689,6 +710,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
689
710
  const authorName = authorUserId ? this.campaign?.authorUserFriendlyName ?? (await grok.dapi.users.find(authorUserId))?.friendlyName : undefined;
690
711
  const campaign: HitDesignCampaign = {
691
712
  name: campaignName,
713
+ friendlyName: this.campaign?.friendlyName ?? customProps?.friendlyName ?? campaignName,
692
714
  templateName,
693
715
  status: this.campaign?.status ?? 'In Progress',
694
716
  createDate: this.campaign?.createDate ?? toFormatedDateString(new Date()),
@@ -745,6 +767,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
745
767
  notify && grok.shell.info('Campaign saved successfully.');
746
768
  !notify && isCreating && grok.shell.info('Campaign created successfully.');
747
769
  this.campaign = campaign;
770
+ this.designViewName = campaign.friendlyName ?? campaign.name;
748
771
  return campaign;
749
772
  }
750
773
  }
@@ -255,7 +255,8 @@ export class HitDesignInfoView
255
255
  };
256
256
 
257
257
  const table = ui.table(Object.values(grouppedCampaigns).flat(), (info) =>
258
- ([ui.link(info.name, () => this.setCampaign(info.name), 'Continue Campaign', ''),
258
+ ([ui.link(info.friendlyName ?? info.name, () => this.setCampaign(info.name), 'Continue Campaign', ''),
259
+ info.name,
259
260
  info.createDate,
260
261
  info.authorUserFriendlyName ?? '',
261
262
  info.lastModifiedUserName ?? '',
@@ -263,7 +264,7 @@ export class HitDesignInfoView
263
264
  info.status,
264
265
  ...(deleteAndShareCampaignIcons(info)),
265
266
  ]),
266
- ['Name', 'Created', 'Author', 'Last Modified by', 'Molecules', 'Status', '', '']);
267
+ ['Name', 'Code', 'Created', 'Author', 'Last Modified by', 'Molecules', 'Status', '', '']);
267
268
  table.style.color = 'var(--grey-5)';
268
269
  table.style.marginLeft = '24px';
269
270
  processGroupingTable(table, grouppedCampaigns);
@@ -280,7 +281,8 @@ export class HitDesignInfoView
280
281
  this.app.dataFrame = camp.df;
281
282
  await this.app.setTemplate(template);
282
283
  this.app.campaignProps = camp.campaignProps;
283
- await this.app.saveCampaign(false, true);
284
+ const campaignName = !!camp.name?.trim() ? camp.name?.trim() : undefined;
285
+ await this.app.saveCampaign(false, true, {friendlyName: campaignName});
284
286
  if (template.layoutViewState && this.app.campaign)
285
287
  this.app.campaign.layout = template.layoutViewState;
286
288
  });
@@ -48,7 +48,7 @@ export class PeptiHitApp extends HitDesignApp<PeptiHitTemplate> {
48
48
  }
49
49
  }
50
50
 
51
- protected getDesignView(): DG.TableView {
51
+ protected override getDesignView(): DG.TableView {
52
52
  const subs: Subscription[] = [];
53
53
  const isNew = this.dataFrame!.col(this.helmColName)?.toList().every((m) => !m && m === '');
54
54
  const helmCol = this.dataFrame!.col(this.helmColName);
package/src/app/types.ts CHANGED
@@ -104,6 +104,7 @@ export type HitTriageCampaignStatus = string;
104
104
 
105
105
  export type HitTriageCampaign = {
106
106
  name: string,
107
+ friendlyName?: string,
107
108
  templateName: string,
108
109
  template?: HitDesignTemplate,
109
110
  savePath?: string,
package/src/app/utils.ts CHANGED
@@ -215,7 +215,7 @@ export function getGroupedCampaigns<T extends HitDesignCampaign | HitTriageCampa
215
215
  return groupedCampaigns;
216
216
  }
217
217
 
218
- export function processGroupingTable<T extends HitDesignCampaign | HitTriageCampaign = HitDesignCampaign>(table: HTMLTableElement, groupedCampaigns: {[key: string]: T[]}, numCols = 8) {
218
+ export function processGroupingTable<T extends HitDesignCampaign | HitTriageCampaign = HitDesignCampaign>(table: HTMLTableElement, groupedCampaigns: {[key: string]: T[]}, numCols = 9) {
219
219
  table.classList.add('hit-design-groupped-campaigns-table');
220
220
  const keys = Object.keys(groupedCampaigns);
221
221
  if (keys.length < 2)
@@ -252,3 +252,73 @@ export function processGroupingTable<T extends HitDesignCampaign | HitTriageCamp
252
252
  curRow += l;
253
253
  }
254
254
  }
255
+
256
+ export type EditableFieldOptions = {
257
+ onChange?: (val?: string | null) => void,
258
+ onOk?: (val?: string | null) => Promise<void>,
259
+ validator?: (val?: string | null) => Promise<string | null>,
260
+ onCancel?: () => void,
261
+ afterEditTextContent?: (val: string) => string,
262
+ beforeEditTextContent?: (val: string) => string,
263
+ nullable?: boolean,
264
+ tooltip?: string,
265
+ }
266
+
267
+ export function editableTableField(field: HTMLElement, options?: EditableFieldOptions) {
268
+ const editIcon = ui.icons.edit(() => {
269
+ let tooltipMsg = options?.tooltip ?? field.textContent ?? '';
270
+ const beforeEditTextContent = options?.beforeEditTextContent ?? ((val) => val);
271
+ const afterEditTextContent = options?.afterEditTextContent ?? ((val) => val);
272
+ const input = ui.input.string('smth', {value: beforeEditTextContent(field.textContent ?? ''), onValueChanged: async () => {
273
+ const vr = await internalValidator();
274
+ setTimeout(() => {
275
+ if (vr) {
276
+ input.input.classList.add('d4-invalid');
277
+ tooltipMsg = vr;
278
+ return;
279
+ } else {
280
+ input.input.classList.remove('d4-invalid');
281
+ tooltipMsg = options?.tooltip ?? field.textContent ?? '';
282
+ }
283
+ }, 100);
284
+ options?.onChange?.(input.value);
285
+ }});
286
+ ui.tooltip.bind(input.input, () => tooltipMsg);
287
+ const labelElement = input.root.getElementsByTagName('label').item(0);
288
+ if (labelElement)
289
+ labelElement.remove();
290
+ input.root.style.width = '100%';
291
+ const internalValidator = async () => {
292
+ const initialRes = !!input.value?.trim() ? null : !!options?.nullable ? null :'Field cannot be empty';
293
+ return initialRes ?? (options?.validator ? await options.validator(input.value) : null);
294
+ };
295
+
296
+ const saveButton = ui.button('Save', async () => {
297
+ const vr = await internalValidator();
298
+ if (vr) {
299
+ grok.shell.error(vr);
300
+ return;
301
+ }
302
+ await options?.onOk?.(input.value);
303
+ field.textContent = afterEditTextContent(input.value) ?? '';
304
+ ui.empty(container);
305
+ container.appendChild(field);
306
+ container.appendChild(editIcon);
307
+ });
308
+ const cancelButton = ui.button('Cancel', () => {
309
+ ui.empty(container);
310
+ container.appendChild(field);
311
+ container.appendChild(editIcon);
312
+ options?.onCancel?.();
313
+ });
314
+
315
+ ui.empty(container);
316
+ input.addOptions(cancelButton);
317
+ input.addOptions(saveButton);
318
+ container.appendChild(input.root);
319
+ input.input.focus();
320
+ }, options?.tooltip ?? 'Edit');
321
+ const container = ui.divH([field, editIcon], {style: {display: 'flex', alignItems: 'center'}});
322
+ editIcon.style.marginLeft = '5px';
323
+ return container;
324
+ }