@datagrok/hit-triage 1.2.1 → 1.3.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.
@@ -1,10 +1,10 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
- import {HitDesignCampaign, HitDesignTemplate, HitTriageCampaignStatus, IFunctionArgs} from './types';
4
+ import {AppName, HitDesignCampaign, HitDesignTemplate, HitTriageCampaignStatus, IFunctionArgs} from './types';
5
5
  import {HitDesignInfoView} from './hit-design-views/info-view';
6
6
  import {CampaignIdKey, CampaignJsonName, CampaignTableName,
7
- EmptyStageCellValue, HTQueryPrefix, HTScriptPrefix, HitDesignCampaignIdKey,
7
+ HTQueryPrefix, HTScriptPrefix, HitDesignCampaignIdKey,
8
8
  HitDesignMolColName, TileCategoriesColName, ViDColName, i18n} from './consts';
9
9
  import {calculateColumns, calculateSingleCellValues, getNewVid} from './utils/calculate-single-cell';
10
10
  import '../../css/hit-triage.css';
@@ -19,41 +19,39 @@ import {Subscription} from 'rxjs';
19
19
  import {filter} from 'rxjs/operators';
20
20
  import {defaultPermissions, PermissionsDialog} from './dialogs/permissions-dialog';
21
21
 
22
- export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
22
+ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> extends HitAppBase<T> {
23
23
  multiView: DG.MultiView;
24
-
25
- private _infoView: HitDesignInfoView;
24
+ protected _infoView: HitDesignInfoView;
26
25
  get infoView(): HitDesignInfoView {return this._infoView;}
27
- private _designView?: DG.TableView;
26
+ protected _designView?: DG.TableView;
28
27
  public _submitView?: HitDesignSubmitView;
29
- private _designViewName = 'Design';
30
- private _filePath = 'System.AppData/HitTriage/Hit Design/campaigns';
31
- private _campaignId?: string;
32
- private _dfName?: string;
33
- private _molColName: string = HitDesignMolColName;
34
- private _campaign?: HitDesignCampaign;
28
+ protected _designViewName = 'Design';
29
+ protected _filePath = `System.AppData/HitTriage/${this.appName}/campaigns`;
30
+ protected _campaignId?: string;
31
+ protected _molColName: string = HitDesignMolColName;
32
+ protected _campaign?: HitDesignCampaign;
35
33
  public campaignProps: {[key: string]: any} = {};
36
- private processedValues: string[] = [];
37
- private _extraStageColsCount = 0;
38
34
 
39
- private currentDesignViewId?: string;
35
+ protected currentDesignViewId?: string;
40
36
  public mainView: DG.ViewBase;
41
- private get version() {return this._campaign?.version ?? 0;};
42
-
43
- constructor(c: DG.FuncCall) {
44
- super(c);
45
- this._infoView = new HitDesignInfoView(this);
37
+ protected get version() {return this._campaign?.version ?? 0;};
38
+ constructor(c: DG.FuncCall, an: AppName = 'Hit Design',
39
+ infoViewConstructor: (app: HitDesignApp) => HitDesignInfoView = (app) => new HitDesignInfoView(app)) {
40
+ super(c, an);
41
+ this._infoView = infoViewConstructor(this);
46
42
  this.multiView = new DG.MultiView({viewFactories: {[this._infoView.name]: () => this._infoView}});
47
43
  this.multiView.tabs.onTabChanged.subscribe((_) => {
48
44
  if (this.multiView.currentView instanceof HitBaseView)
49
- (this.multiView.currentView as HitBaseView<HitDesignTemplate, HitDesignApp>).onActivated();
45
+ (this.multiView.currentView as HitBaseView<T, typeof this>).onActivated();
50
46
  });
51
47
  this.multiView.parentCall = c;
52
48
 
53
49
  this.mainView = this.multiView;
54
- //this.mainView = grok.shell.addView(this.multiView);
50
+ this._initViewChangeSub();
51
+ }
55
52
 
56
- grok.events.onCurrentViewChanged.subscribe(async () => {
53
+ _initViewChangeSub() {
54
+ this.multiView.subs.push(grok.events.onCurrentViewChanged.subscribe(async () => {
57
55
  try {
58
56
  if (grok.shell.v?.name === this.currentDesignViewId) {
59
57
  grok.shell.windows.showHelp = false;
@@ -61,7 +59,7 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
61
59
  this.setBaseUrl();
62
60
  modifyUrl(CampaignIdKey, this._campaignId ?? this._campaign?.name ?? '');
63
61
 
64
- const {sub} = addBreadCrumbsToRibbons(grok.shell.v, 'Hit Design', grok.shell.v?.name, () => {
62
+ const {sub} = addBreadCrumbsToRibbons(grok.shell.v, this.appName, grok.shell.v?.name, () => {
65
63
  grok.shell.v = this.mainView;
66
64
  this._designView?.close();
67
65
  this._infoView.init();
@@ -71,18 +69,18 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
71
69
  } catch (e) {
72
70
  console.error(e);
73
71
  }
74
- });
72
+ }));
75
73
  }
76
74
 
77
- public async setTemplate(template: HitDesignTemplate, campaignId?: string) {
75
+ public async setTemplate(template: T, campaignId?: string) {
78
76
  if (!campaignId) {
79
77
  this._designView?.dataFrame && grok.shell.closeTable(this._designView.dataFrame);
80
78
  this._designView = undefined;
81
- campaignId = await this.getNewCampaignName('Hit Design/campaigns', template.key);
79
+ campaignId = await this.getNewCampaignName(`${this.appName}/campaigns`, template.key);
82
80
  modifyUrl(HitDesignCampaignIdKey, campaignId);
83
- this._filePath = `System.AppData/HitTriage/Hit Design/campaigns/${campaignId}/${CampaignTableName}`;
81
+ this._filePath = `System.AppData/HitTriage/${this.appName}/campaigns/${campaignId}/${CampaignTableName}`;
84
82
  } else {
85
- const fileLoc = 'System.AppData/HitTriage/Hit Design/campaigns';
83
+ const fileLoc = `System.AppData/HitTriage/${this.appName}/campaigns`;
86
84
  this._filePath = this.campaign?.savePath ?? `${fileLoc}/${campaignId}/${CampaignTableName}`;
87
85
  this.dataFrame = await grok.dapi.files.readCsv(this._filePath);
88
86
  if (this._campaign?.columnSemTypes) {
@@ -108,7 +106,6 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
108
106
  }
109
107
  await this.dataFrame.meta.detectSemanticTypes();
110
108
  this._molColName = this.dataFrame.columns.bySemType(DG.SEMTYPE.MOLECULE)?.name ?? HitDesignMolColName;
111
- this._dfName = this.dataFrame.name ??= campaignId;
112
109
  this._campaignId = campaignId;
113
110
  this.template = template;
114
111
 
@@ -118,8 +115,6 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
118
115
  await this.setCanEdit(this.campaign);
119
116
  else
120
117
  this.hasEditPermission = true; // if the campaign is new, obviously the user can edit it
121
-
122
- this._extraStageColsCount = this.dataFrame!.rowCount - this.dataFrame.filter.trueCount;
123
118
  const designV = this.designView;
124
119
  this.currentDesignViewId = designV.name;
125
120
  this.setBaseUrl();
@@ -169,34 +164,171 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
169
164
  this.cacheDuplicateVIDs();
170
165
  return this._duplicateVidCache;
171
166
  }
172
- private getDesignView(): DG.TableView {
173
- const subs: Subscription[] = [];
174
- const isNew = this.dataFrame!.col(this.molColName)?.toList().every((m) => !m && m === '');
175
- const view = grok.shell.addTableView(this.dataFrame!);
176
- this._designViewName = this.campaign?.name ?? this._designViewName;
177
- view.name = this._designViewName;
178
- this.processedValues = this.dataFrame!.getCol(this.molColName).toList();
179
167
 
180
- const performSingleCellCalculations = async (newValueIdx: number, newValue?: string) => {
181
- const computeObj = this.template!.compute;
182
- if (!newValue || newValue === '')
183
- return;
168
+ async performSingleCellCalculations(newValueIdx: number, newValue?: string) {
169
+ const computeObj = this.template!.compute;
170
+ if (!newValue || newValue === '')
171
+ return;
184
172
 
185
- const calcDf =
173
+ const calcDf =
186
174
  await calculateSingleCellValues(
187
175
  newValue, computeObj.descriptors.args, computeObj.functions, computeObj.scripts, computeObj.queries);
188
176
 
189
- for (const col of calcDf.columns.toList()) {
190
- if (col.name === HitDesignMolColName) continue;
191
- if (!this.dataFrame!.columns.contains(col.name)) {
192
- const newCol = this.dataFrame!.columns.addNew(col.name, col.type);
193
- newCol.semType = col.semType;
194
- }
195
- this.dataFrame!.col(col.name)!.set(newValueIdx, col.get(0), false);
177
+ for (const col of calcDf.columns.toList()) {
178
+ if (col.name === HitDesignMolColName) continue;
179
+ if (!this.dataFrame!.columns.contains(col.name)) {
180
+ const newCol = this.dataFrame!.columns.addNew(col.name, col.type);
181
+ newCol.semType = col.semType;
196
182
  }
183
+ this.dataFrame!.col(col.name)!.set(newValueIdx, col.get(0), false);
184
+ }
197
185
  this.dataFrame!.fireValuesChanged();
198
186
  this.saveCampaign(undefined, false);
199
- };
187
+ }
188
+
189
+ protected initDesignViewRibbons(view: DG.TableView, subs: Subscription[]) {
190
+ const onRemoveSub = grok.events.onViewRemoved.subscribe((v) => {
191
+ if (v.id === view?.id) {
192
+ subs.forEach((s) => s.unsubscribe());
193
+ onRemoveSub.unsubscribe();
194
+ }
195
+ });
196
+ const ribbons = view?.getRibbonPanels();
197
+ if (ribbons) {
198
+ const hasSubmit = checkRibbonsHaveSubmit(ribbons);
199
+ if (!hasSubmit) {
200
+ const getComputeDialog = async () => {
201
+ chemFunctionsDialog(this, async (resultMap) => {
202
+ const oldDescriptors = this.template!.compute.descriptors.args;
203
+ const oldFunctions = this.template!.compute.functions;
204
+ const oldScripts = this.template!.compute.scripts ?? [];
205
+ const oldQueries = this.template!.compute.queries ?? [];
206
+ const newDescriptors = resultMap.descriptors;
207
+ const newComputeObj = {
208
+ descriptors: {
209
+ enabled: !!resultMap?.descriptors?.length,
210
+ args: resultMap?.descriptors ?? [],
211
+ },
212
+ functions: Object.entries(resultMap?.externals ?? {}).map(([funcName, args]) => {
213
+ const splitFunc = funcName.split(':');
214
+ return ({
215
+ name: splitFunc[1],
216
+ package: splitFunc[0],
217
+ args: args,
218
+ });
219
+ }),
220
+ scripts: Object.entries(resultMap?.scripts ?? {})
221
+ .filter(([name, _]) => name.startsWith(HTScriptPrefix) && name.split(':').length === 3)
222
+ .map(([scriptId, args]) => {
223
+ const scriptNameParts = scriptId.split(':');
224
+ return ({
225
+ name: scriptNameParts[1] ?? '',
226
+ id: scriptNameParts[2] ?? '',
227
+ args: args,
228
+ });
229
+ }),
230
+ queries: Object.entries(resultMap?.queries ?? {})
231
+ .filter(([name, _]) => name.startsWith(HTQueryPrefix) && name.split(':').length === 3)
232
+ .map(([queryName, args]) => {
233
+ const queryNameParts = queryName.split(':');
234
+ return ({
235
+ name: queryNameParts[1] ?? '',
236
+ id: queryNameParts[2] ?? '',
237
+ args: args,
238
+ });
239
+ }),
240
+ };
241
+ this.template!.compute = newComputeObj;
242
+ this.campaign!.template = this.template;
243
+ const uncalculatedDescriptors = newDescriptors.filter((d) => !oldDescriptors.includes(d));
244
+
245
+ const newFunctions: {[_: string]: IFunctionArgs} = {};
246
+ Object.entries(resultMap?.externals ?? {})
247
+ .filter(([funcName, args]) => {
248
+ const oldFunc = oldFunctions.find((f) => `${f.package}:${f.name}` === funcName);
249
+ if (!oldFunc)
250
+ return true;
251
+ return !Object.entries(args).every(([key, value]) => oldFunc.args[key] === value);
252
+ }).forEach(([funcName, args]) => {newFunctions[funcName] = args;});
253
+
254
+ const newScripts: {[_: string]: IFunctionArgs} = {};
255
+ Object.entries(resultMap?.scripts ?? {})
256
+ .filter(([scriptName, args]) => {
257
+ const oldScript = oldScripts.find((f) => `${HTScriptPrefix}:${f.name}:${f.id}` === scriptName);
258
+ if (!oldScript)
259
+ return true;
260
+ return !Object.entries(args).every(([key, value]) => oldScript.args[key] === value);
261
+ }).forEach(([scriptName, args]) => {newScripts[scriptName] = args;});
262
+
263
+ const newQueries: {[_: string]: IFunctionArgs} = {};
264
+ Object.entries(resultMap?.queries ?? {})
265
+ .filter(([queryName, args]) => {
266
+ const oldQuery = oldQueries.find((f) => `${HTQueryPrefix}:${f.name}:${f.id}` === queryName);
267
+ if (!oldQuery)
268
+ return true;
269
+ return !Object.entries(args).every(([key, value]) => oldQuery.args[key] === value);
270
+ }).forEach(([queryName, args]) => {newQueries[queryName] = args;});
271
+ ui.setUpdateIndicator(view.grid.root, true);
272
+ try {
273
+ await calculateColumns({descriptors: uncalculatedDescriptors, externals: newFunctions,
274
+ scripts: newScripts, queries: newQueries}, this.dataFrame!, this.molColName!);
275
+ this.dataFrame!.fireValuesChanged();
276
+ } finally {
277
+ ui.setUpdateIndicator(view.grid.root, false);
278
+ this.saveCampaign(undefined, false);
279
+ }
280
+ }, () => null, this.campaign?.template!, true);
281
+ };
282
+
283
+ const calculateRibbon = ui.iconFA('wrench', getComputeDialog, 'Calculate additional properties');
284
+ const addNewRowButton = ui.icons.add(() => {this.dataFrame?.rows.addNew(null, true);}, 'Add new row');
285
+ const permissionsButton = ui.iconFA('share', async () => {
286
+ await (new PermissionsDialog(this.campaign?.permissions)).show((res) => {
287
+ this.campaign!.permissions = res;
288
+ this.saveCampaign(undefined, true);
289
+ });
290
+ }, 'Edit campaign permissions');
291
+ const tilesButton = ui.bigButton('Progress tracker', () => {
292
+ getTilesViewDialog(this, () => this._designView ?? null);
293
+ });
294
+
295
+ const submitButton = ui.bigButton('Submit', () => {
296
+ const dialogContent = this._submitView?.render();
297
+ if (dialogContent) {
298
+ const dlg = ui.dialog('Submit');
299
+ dlg.add(dialogContent);
300
+ dlg.addButton('Save', ()=>{this.saveCampaign(); dlg.close();});
301
+ dlg.addButton('Submit', ()=>{this._submitView?.submit(); dlg.close();});
302
+ dlg.show();
303
+ }
304
+ });
305
+ submitButton.classList.add('hit-design-submit-button');
306
+ const ribbonButtons: HTMLElement[] = [submitButton];
307
+ if (this.template?.stages?.length ?? 0 > 0)
308
+ ribbonButtons.unshift(tilesButton);
309
+ if (this.campaign && this.template && !this.campaign.template)
310
+ this.campaign.template = this.template;
311
+
312
+ if (this.hasEditPermission)
313
+ ribbonButtons.unshift(permissionsButton);
314
+ ribbonButtons.unshift(calculateRibbon);
315
+ ribbonButtons.unshift(addNewRowButton);
316
+
317
+
318
+ ribbons.push(ribbonButtons);
319
+ // remove project save button from the ribbon
320
+ view.setRibbonPanels(ribbons);
321
+ }
322
+ }
323
+ return view;
324
+ }
325
+
326
+ protected getDesignView(): DG.TableView {
327
+ const subs: Subscription[] = [];
328
+ const isNew = this.dataFrame!.col(this.molColName)?.toList().every((m) => !m && m === '');
329
+ const view = grok.shell.addTableView(this.dataFrame!);
330
+ this._designViewName = this.campaign?.name ?? this._designViewName;
331
+ view.name = this._designViewName;
200
332
 
201
333
  view._onAdded();
202
334
  const layoutViewState = this._campaign?.layout ?? this.template?.layoutViewState;
@@ -216,7 +348,6 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
216
348
  subs.push(this.dataFrame!.onRowsAdded.pipe(filter(() => !this.isJoining))
217
349
  .subscribe(() => { // TODO, insertion of rows in the middle
218
350
  try {
219
- this.processedValues = this.dataFrame!.getCol(this.molColName).toList();
220
351
  if (this.template!.stages?.length > 0) {
221
352
  for (let i = 0; i < this.dataFrame!.rowCount; i++) {
222
353
  const colVal = this.dataFrame!.col(TileCategoriesColName)!.get(i);
@@ -240,13 +371,6 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
240
371
  //const lastCell = view.grid.cell(this.molColName, this.dataFrame!.rowCount - 1);
241
372
  //view.grid.onCellValueEdited
242
373
  }));
243
- this.dataFrame && subs.push(this.dataFrame?.onRowsRemoved.subscribe(() => {
244
- try {
245
- this.processedValues = this.dataFrame!.getCol(this.molColName).toList();
246
- } catch (e) {
247
- console.error(e);
248
- }
249
- }));
250
374
  subs.push(grok.events.onContextMenu.subscribe((args) => {
251
375
  try {
252
376
  const viewer: DG.Viewer = args?.args?.context;
@@ -266,18 +390,10 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
266
390
  });
267
391
  menu.item('Duplicate molecule', () => {
268
392
  try {
269
- this.dataFrame!.rows.addNew(null, true);
270
- let lastCell: DG.GridCell | null = null;
271
- for (let i = 0; i < this.dataFrame!.rowCount; i++) {
272
- const cell = view.grid.cell(this.molColName, i);
273
- if (!cell)
274
- continue;
275
- if (cell.cell.value === '' || cell.cell.value === null)
276
- lastCell = cell;
277
- }
278
- if (lastCell)
279
- lastCell.cell.value = args?.args?.item?.cell?.value ?? '';
280
- // grok.functions.call('Chem:editMoleculeCell', {cell: lastCell});
393
+ const row = this.dataFrame!.rows.addNew(null, true);
394
+ const cell = row.get(this.molColName);
395
+ if (cell != null && row.idx > -1)
396
+ this.dataFrame!.cell(row.idx, this.molColName).value = args?.args?.item?.cell?.value ?? '';
281
397
  } catch (e) {
282
398
  console.error(e);
283
399
  }
@@ -288,7 +404,7 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
288
404
  if (cellValue && (cellIndex ?? -1) > -1) {
289
405
  menu.item('Re-Run Calculations', async () => {
290
406
  try {
291
- await performSingleCellCalculations(cellIndex, cellValue);
407
+ await this.performSingleCellCalculations(cellIndex, cellValue);
292
408
  } catch (e) {
293
409
  console.error(e);
294
410
  }
@@ -352,7 +468,7 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
352
468
 
353
469
  this.dataFrame!.col(ViDColName)!.set(newValueIdx, newVid, false);
354
470
 
355
- performSingleCellCalculations(newValueIdx, newValue);
471
+ this.performSingleCellCalculations(newValueIdx, newValue);
356
472
  } catch (e) {
357
473
  console.error(e);
358
474
  }
@@ -373,141 +489,7 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
373
489
  }
374
490
  } catch (e) {}
375
491
  }));
376
-
377
-
378
- const onRemoveSub = grok.events.onViewRemoved.subscribe((v) => {
379
- if (v.id === view?.id) {
380
- subs.forEach((s) => s.unsubscribe());
381
- onRemoveSub.unsubscribe();
382
- }
383
- });
384
- const ribbons = view?.getRibbonPanels();
385
- if (ribbons) {
386
- const hasSubmit = checkRibbonsHaveSubmit(ribbons);
387
- if (!hasSubmit) {
388
- const getComputeDialog = async () => {
389
- chemFunctionsDialog(this, async (resultMap) => {
390
- const oldDescriptors = this.template!.compute.descriptors.args;
391
- const oldFunctions = this.template!.compute.functions;
392
- const oldScripts = this.template!.compute.scripts ?? [];
393
- const oldQueries = this.template!.compute.queries ?? [];
394
- const newDescriptors = resultMap.descriptors;
395
- const newComputeObj = {
396
- descriptors: {
397
- enabled: !!resultMap?.descriptors?.length,
398
- args: resultMap?.descriptors ?? [],
399
- },
400
- functions: Object.entries(resultMap?.externals ?? {}).map(([funcName, args]) => {
401
- const splitFunc = funcName.split(':');
402
- return ({
403
- name: splitFunc[1],
404
- package: splitFunc[0],
405
- args: args,
406
- });
407
- }),
408
- scripts: Object.entries(resultMap?.scripts ?? {})
409
- .filter(([name, _]) => name.startsWith(HTScriptPrefix) && name.split(':').length === 3)
410
- .map(([scriptId, args]) => {
411
- const scriptNameParts = scriptId.split(':');
412
- return ({
413
- name: scriptNameParts[1] ?? '',
414
- id: scriptNameParts[2] ?? '',
415
- args: args,
416
- });
417
- }),
418
- queries: Object.entries(resultMap?.queries ?? {})
419
- .filter(([name, _]) => name.startsWith(HTQueryPrefix) && name.split(':').length === 3)
420
- .map(([queryName, args]) => {
421
- const queryNameParts = queryName.split(':');
422
- return ({
423
- name: queryNameParts[1] ?? '',
424
- id: queryNameParts[2] ?? '',
425
- args: args,
426
- });
427
- }),
428
- };
429
- this.template!.compute = newComputeObj;
430
- this.campaign!.template = this.template;
431
- const uncalculatedDescriptors = newDescriptors.filter((d) => !oldDescriptors.includes(d));
432
-
433
- const newFunctions: {[_: string]: IFunctionArgs} = {};
434
- Object.entries(resultMap?.externals ?? {})
435
- .filter(([funcName, args]) => {
436
- const oldFunc = oldFunctions.find((f) => `${f.package}:${f.name}` === funcName);
437
- if (!oldFunc)
438
- return true;
439
- return !Object.entries(args).every(([key, value]) => oldFunc.args[key] === value);
440
- }).forEach(([funcName, args]) => {newFunctions[funcName] = args;});
441
-
442
- const newScripts: {[_: string]: IFunctionArgs} = {};
443
- Object.entries(resultMap?.scripts ?? {})
444
- .filter(([scriptName, args]) => {
445
- const oldScript = oldScripts.find((f) => `${HTScriptPrefix}:${f.name}:${f.id}` === scriptName);
446
- if (!oldScript)
447
- return true;
448
- return !Object.entries(args).every(([key, value]) => oldScript.args[key] === value);
449
- }).forEach(([scriptName, args]) => {newScripts[scriptName] = args;});
450
-
451
- const newQueries: {[_: string]: IFunctionArgs} = {};
452
- Object.entries(resultMap?.queries ?? {})
453
- .filter(([queryName, args]) => {
454
- const oldQuery = oldQueries.find((f) => `${HTQueryPrefix}:${f.name}:${f.id}` === queryName);
455
- if (!oldQuery)
456
- return true;
457
- return !Object.entries(args).every(([key, value]) => oldQuery.args[key] === value);
458
- }).forEach(([queryName, args]) => {newQueries[queryName] = args;});
459
- ui.setUpdateIndicator(view.grid.root, true);
460
- try {
461
- await calculateColumns({descriptors: uncalculatedDescriptors, externals: newFunctions,
462
- scripts: newScripts, queries: newQueries}, this.dataFrame!, this.molColName!);
463
- this.dataFrame!.fireValuesChanged();
464
- } finally {
465
- ui.setUpdateIndicator(view.grid.root, false);
466
- this.saveCampaign(undefined, false);
467
- }
468
- }, () => null, this.campaign?.template!, true);
469
- };
470
-
471
- const calculateRibbon = ui.iconFA('wrench', getComputeDialog, 'Calculate additional properties');
472
- const addNewRowButton = ui.icons.add(() => {this.dataFrame?.rows.addNew(null, true);}, 'Add new row');
473
- const permissionsButton = ui.iconFA('share', async () => {
474
- await (new PermissionsDialog(this.campaign?.permissions)).show((res) => {
475
- this.campaign!.permissions = res;
476
- this.saveCampaign(undefined, true);
477
- });
478
- }, 'Edit campaign permissions');
479
- const tilesButton = ui.bigButton('Progress tracker', () => {
480
- getTilesViewDialog(this, () => this._designView ?? null);
481
- });
482
-
483
- const submitButton = ui.bigButton('Submit', () => {
484
- const dialogContent = this._submitView?.render();
485
- if (dialogContent) {
486
- const dlg = ui.dialog('Submit');
487
- dlg.add(dialogContent);
488
- dlg.addButton('Save', ()=>{this.saveCampaign(); dlg.close();});
489
- dlg.addButton('Submit', ()=>{this._submitView?.submit(); dlg.close();});
490
- dlg.show();
491
- }
492
- });
493
- submitButton.classList.add('hit-design-submit-button');
494
- const ribbonButtons: HTMLElement[] = [submitButton];
495
- if (this.template?.stages?.length ?? 0 > 0)
496
- ribbonButtons.unshift(tilesButton);
497
- if (this.campaign && this.template && !this.campaign.template)
498
- this.campaign.template = this.template;
499
-
500
- if (this.hasEditPermission)
501
- ribbonButtons.unshift(permissionsButton);
502
- ribbonButtons.unshift(calculateRibbon);
503
- ribbonButtons.unshift(addNewRowButton);
504
-
505
-
506
- ribbons.push(ribbonButtons);
507
- // remove project save button from the ribbon
508
- view.setRibbonPanels(ribbons);
509
- }
510
- }
492
+ this.initDesignViewRibbons(view, subs);
511
493
  view.parentCall = this.parentCall;
512
494
  return view;
513
495
  }
@@ -605,7 +587,7 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
605
587
  'Template': this.template?.name ?? 'Molecules',
606
588
  'File path': getPathEditor(),
607
589
  ...campaignProps,
608
- 'Number of molecules': (this.dataFrame!.rowCount - this._extraStageColsCount).toString(),
590
+ 'Number of molecules': (this.dataFrame!.rowCount).toString(),
609
591
  'Enrichment methods': [this.template!.compute.descriptors.enabled ? 'descriptors' : '',
610
592
  ...this.template!.compute.functions.map((func) => func.name)].filter((f) => f && f.trim() !== '').join(', '),
611
593
  };
@@ -633,7 +615,7 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
633
615
  createDate: this.campaign?.createDate ?? toFormatedDateString(new Date()),
634
616
  campaignFields: this.campaign?.campaignFields ?? this.campaignProps,
635
617
  columnSemTypes,
636
- rowCount: enrichedDf.col(ViDColName)?.toList().filter((s) => s !== EmptyStageCellValue).length ?? 0,
618
+ rowCount: enrichedDf.rowCount,
637
619
  filteredRowCount: enrichedDf.filter.trueCount,
638
620
  savePath: this._filePath,
639
621
  columnTypes: colTypeMap,
@@ -646,7 +628,7 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
646
628
  grok.shell.error('You do not have permission to modify this campaign');
647
629
  return campaign;
648
630
  }
649
- const campaignPath = `Hit Design/campaigns/${campaignId}/${CampaignJsonName}`;
631
+ const campaignPath = `${this.appName}/campaigns/${campaignId}/${CampaignJsonName}`;
650
632
  // check if someone already saved the campaign
651
633
  let resDf = enrichedDf;
652
634
 
@@ -669,7 +651,6 @@ export class HitDesignApp extends HitAppBase<HitDesignTemplate> {
669
651
  const csvDf = DG.DataFrame.fromColumns(
670
652
  resDf.columns.toList().filter((col) => !col.name.startsWith('~')),
671
653
  ).toCsv();
672
- //await _package.files.writeAsText(`Hit Design/campaigns/${campaignId}/${CampaignTableName}`, csvDf);
673
654
  await grok.dapi.files.writeAsText(this._filePath, csvDf);
674
655
  const newLayout = this._designView!.saveLayout();
675
656
  if (!newLayout)