@datagrok/hit-triage 1.3.2 → 1.3.4
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/CHANGELOG.md +9 -0
- package/README_HD.md +13 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +1 -1
- package/src/app/accordeons/new-hit-design-template-accordeon.ts +1 -0
- package/src/app/consts.ts +13 -5
- package/src/app/hit-design-app.ts +63 -13
- package/src/app/hit-design-views/info-view.ts +44 -6
- package/src/app/hit-design-views/submit-view.ts +41 -5
- package/src/app/hit-design-views/tiles-view.ts +44 -6
- package/src/app/hit-design-views/utils.css +8 -0
- package/src/app/pepti-hit-app.ts +3 -3
- package/src/app/pepti-hits-views/info-view.ts +1 -1
- package/src/app/types.ts +1 -1
- package/src/app/utils.ts +82 -2
package/package.json
CHANGED
package/src/app/consts.ts
CHANGED
|
@@ -17,17 +17,17 @@ export const HTScriptPrefix = 'HTScript';
|
|
|
17
17
|
export const HTQueryPrefix = 'HTQuery';
|
|
18
18
|
export const ComputeQueryMolColName = 'molecules';
|
|
19
19
|
export const i18n = {
|
|
20
|
-
startNewCampaign: 'New
|
|
21
|
-
createNewCampaign: 'New
|
|
20
|
+
startNewCampaign: 'New Campaign',
|
|
21
|
+
createNewCampaign: 'New Campaign',
|
|
22
22
|
dataSourceFunction: 'Source',
|
|
23
|
-
createNewTemplate: 'New
|
|
23
|
+
createNewTemplate: 'New Template',
|
|
24
24
|
StartCampaign: 'Start',
|
|
25
25
|
createTemplate: 'Create',
|
|
26
26
|
createCampaign: 'Create',
|
|
27
27
|
download: 'Download',
|
|
28
28
|
cancel: 'Cancel',
|
|
29
|
-
continueCampaigns: 'Continue
|
|
30
|
-
createNewCampaignHeader: 'New
|
|
29
|
+
continueCampaigns: 'Continue Campaign',
|
|
30
|
+
createNewCampaignHeader: 'New Campaign',
|
|
31
31
|
selectTemplate: 'Template',
|
|
32
32
|
} as const;
|
|
33
33
|
|
|
@@ -36,3 +36,11 @@ export const funcTypeNames = {
|
|
|
36
36
|
function: 'function-package',
|
|
37
37
|
query: 'data-query',
|
|
38
38
|
} as const;
|
|
39
|
+
|
|
40
|
+
export const HDCampaignsGroupingLSKey = 'HDCampaignsGrouping';
|
|
41
|
+
|
|
42
|
+
export enum CampaignGroupingType {
|
|
43
|
+
None = 'None',
|
|
44
|
+
Template = 'Template',
|
|
45
|
+
Status = 'Status',
|
|
46
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import * as grok from 'datagrok-api/grok';
|
|
2
3
|
import * as ui from 'datagrok-api/ui';
|
|
3
4
|
import * as DG from 'datagrok-api/dg';
|
|
4
|
-
import {AppName, HitDesignCampaign, HitDesignTemplate,
|
|
5
|
+
import {AppName, HitDesignCampaign, HitDesignTemplate, IFunctionArgs} from './types';
|
|
5
6
|
import {HitDesignInfoView} from './hit-design-views/info-view';
|
|
6
7
|
import {CampaignIdKey, CampaignJsonName, CampaignTableName,
|
|
7
8
|
HTQueryPrefix, HTScriptPrefix, HitDesignCampaignIdKey,
|
|
@@ -35,6 +36,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
35
36
|
protected currentDesignViewId?: string;
|
|
36
37
|
public mainView: DG.ViewBase;
|
|
37
38
|
protected get version() {return this._campaign?.version ?? 0;};
|
|
39
|
+
public existingStatuses: string[] = [];
|
|
38
40
|
constructor(c: DG.FuncCall, an: AppName = 'Hit Design',
|
|
39
41
|
infoViewConstructor: (app: HitDesignApp) => HitDesignInfoView = (app) => new HitDesignInfoView(app)) {
|
|
40
42
|
super(c, an);
|
|
@@ -72,6 +74,48 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
72
74
|
}));
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
public get stages() {
|
|
78
|
+
return this.campaign?.template?.stages ?? this.template?.stages ?? [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public async setStages(st: string[]) {
|
|
82
|
+
if (!this.campaign || !this.campaign.template || !this.template) {
|
|
83
|
+
grok.shell.error('Campaign or template is not set');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!st?.length) {
|
|
87
|
+
grok.shell.error('Removing all stages is not allowed');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const stageCol = this.dataFrame?.col(TileCategoriesColName);
|
|
91
|
+
if (!stageCol) {
|
|
92
|
+
grok.shell.error('No stage column found');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const removedStages: string[] = [];
|
|
96
|
+
//make sure there is no duplication
|
|
97
|
+
const stageSet = new Set(st);
|
|
98
|
+
const uniqueStages = [...stageSet];
|
|
99
|
+
const dfLen = this.dataFrame!.rowCount;
|
|
100
|
+
const stageCats = stageCol.categories;
|
|
101
|
+
const stageIndexes = stageCol.getRawData() as Int32Array;
|
|
102
|
+
for (let i = 0; i < dfLen; i++) {
|
|
103
|
+
const stage = stageCats[stageIndexes[i]];
|
|
104
|
+
if (!stageSet.has(stage)) {
|
|
105
|
+
stageCol.set(i, st[0], false);
|
|
106
|
+
removedStages.push(stage);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (removedStages.length > 0)
|
|
111
|
+
grok.shell.warning(`Some stages were removed: (${removedStages.join(', ')}). Corresponding rows were set to stage "${st[0]}"`);
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
this.campaign.template.stages = uniqueStages;
|
|
115
|
+
this.template.stages = uniqueStages;
|
|
116
|
+
await this.saveCampaign(true);
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
public async setTemplate(template: T, campaignId?: string) {
|
|
76
120
|
if (!campaignId) {
|
|
77
121
|
this._designView?.dataFrame && grok.shell.closeTable(this._designView.dataFrame);
|
|
@@ -183,7 +227,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
183
227
|
this.dataFrame!.col(col.name)!.set(newValueIdx, col.get(0), false);
|
|
184
228
|
}
|
|
185
229
|
this.dataFrame!.fireValuesChanged();
|
|
186
|
-
this.saveCampaign(
|
|
230
|
+
this.saveCampaign(false);
|
|
187
231
|
}
|
|
188
232
|
|
|
189
233
|
protected initDesignViewRibbons(view: DG.TableView, subs: Subscription[]) {
|
|
@@ -275,7 +319,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
275
319
|
this.dataFrame!.fireValuesChanged();
|
|
276
320
|
} finally {
|
|
277
321
|
ui.setUpdateIndicator(view.grid.root, false);
|
|
278
|
-
this.saveCampaign(
|
|
322
|
+
this.saveCampaign(false);
|
|
279
323
|
}
|
|
280
324
|
}, () => null, this.campaign?.template!, true);
|
|
281
325
|
};
|
|
@@ -285,7 +329,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
285
329
|
const permissionsButton = ui.iconFA('share', async () => {
|
|
286
330
|
await (new PermissionsDialog(this.campaign?.permissions)).show((res) => {
|
|
287
331
|
this.campaign!.permissions = res;
|
|
288
|
-
this.saveCampaign(
|
|
332
|
+
this.saveCampaign(true);
|
|
289
333
|
});
|
|
290
334
|
}, 'Edit campaign permissions');
|
|
291
335
|
const tilesButton = ui.bigButton('Progress tracker', () => {
|
|
@@ -297,15 +341,21 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
297
341
|
if (dialogContent) {
|
|
298
342
|
const dlg = ui.dialog('Submit');
|
|
299
343
|
dlg.add(dialogContent);
|
|
300
|
-
dlg.addButton('Save', ()=>{
|
|
301
|
-
|
|
344
|
+
dlg.addButton('Save', () => {
|
|
345
|
+
this._campaign!.status = this._submitView!.getStatus();
|
|
346
|
+
this.saveCampaign();
|
|
347
|
+
dlg.close();
|
|
348
|
+
});
|
|
349
|
+
if (this.template?.submit?.fName && this.template?.submit?.package && DG.Func.find({name: this.template.submit.fName, package: this.template.submit.package})?.length > 0)
|
|
350
|
+
dlg.addButton('Submit', ()=>{this._submitView?.submit(); dlg.close();});
|
|
302
351
|
dlg.show();
|
|
303
352
|
}
|
|
304
353
|
});
|
|
305
354
|
submitButton.classList.add('hit-design-submit-button');
|
|
306
355
|
const ribbonButtons: HTMLElement[] = [submitButton];
|
|
307
|
-
if (this.
|
|
356
|
+
if (this.stages.length > 0)
|
|
308
357
|
ribbonButtons.unshift(tilesButton);
|
|
358
|
+
// only initialize campaign template if its not exsitent yet
|
|
309
359
|
if (this.campaign && this.template && !this.campaign.template)
|
|
310
360
|
this.campaign.template = this.template;
|
|
311
361
|
|
|
@@ -358,11 +408,11 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
358
408
|
subs.push(this.dataFrame!.onRowsAdded.pipe(filter(() => !this.isJoining))
|
|
359
409
|
.subscribe(() => { // TODO, insertion of rows in the middle
|
|
360
410
|
try {
|
|
361
|
-
if (this.
|
|
411
|
+
if (this.stages.length > 0) {
|
|
362
412
|
for (let i = 0; i < this.dataFrame!.rowCount; i++) {
|
|
363
413
|
const colVal = this.dataFrame!.col(TileCategoriesColName)!.get(i);
|
|
364
414
|
if (!colVal || colVal === '' || this.dataFrame!.col(TileCategoriesColName)?.isNone(i))
|
|
365
|
-
this.dataFrame!.set(TileCategoriesColName, i, this.
|
|
415
|
+
this.dataFrame!.set(TileCategoriesColName, i, this.stages[0]);
|
|
366
416
|
}
|
|
367
417
|
}
|
|
368
418
|
let lastAddedCell: DG.GridCell | null = null;
|
|
@@ -432,7 +482,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
432
482
|
view?.grid && subs.push(view.grid.onCellValueEdited.subscribe(async (gc) => {
|
|
433
483
|
try {
|
|
434
484
|
if (gc.tableColumn?.name === TileCategoriesColName) {
|
|
435
|
-
await this.saveCampaign(
|
|
485
|
+
await this.saveCampaign(false);
|
|
436
486
|
return;
|
|
437
487
|
}
|
|
438
488
|
if (gc.tableColumn?.name !== this.molColName)
|
|
@@ -566,7 +616,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
566
616
|
|
|
567
617
|
if (this._campaign)
|
|
568
618
|
this._campaign!.savePath = this._filePath;
|
|
569
|
-
await this.saveCampaign(
|
|
619
|
+
await this.saveCampaign(true);
|
|
570
620
|
ui.empty(pathDiv);
|
|
571
621
|
const folderPath = getFolderPath();
|
|
572
622
|
link = ui.link(folderPath,
|
|
@@ -603,7 +653,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
603
653
|
};
|
|
604
654
|
}
|
|
605
655
|
|
|
606
|
-
async saveCampaign(
|
|
656
|
+
async saveCampaign(notify = true): Promise<HitDesignCampaign> {
|
|
607
657
|
const campaignId = this.campaignId!;
|
|
608
658
|
const templateName = this.template!.name;
|
|
609
659
|
const enrichedDf = this.dataFrame!;
|
|
@@ -621,7 +671,7 @@ export class HitDesignApp<T extends HitDesignTemplate = HitDesignTemplate> exten
|
|
|
621
671
|
const campaign: HitDesignCampaign = {
|
|
622
672
|
name: campaignName,
|
|
623
673
|
templateName,
|
|
624
|
-
status:
|
|
674
|
+
status: this.campaign?.status ?? 'In Progress',
|
|
625
675
|
createDate: this.campaign?.createDate ?? toFormatedDateString(new Date()),
|
|
626
676
|
campaignFields: this.campaign?.campaignFields ?? this.campaignProps,
|
|
627
677
|
columnSemTypes,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import * as grok from 'datagrok-api/grok';
|
|
2
3
|
import * as ui from 'datagrok-api/ui';
|
|
3
4
|
import * as DG from 'datagrok-api/dg';
|
|
@@ -5,10 +6,13 @@ import {u2} from '@datagrok-libraries/utils/src/u2';
|
|
|
5
6
|
import {HitDesignApp} from '../hit-design-app';
|
|
6
7
|
import {_package} from '../../package';
|
|
7
8
|
import $ from 'cash-dom';
|
|
8
|
-
import {CampaignJsonName, HitDesignCampaignIdKey, i18n} from '../consts';
|
|
9
|
+
import {CampaignGroupingType, CampaignJsonName, HitDesignCampaignIdKey, i18n} from '../consts';
|
|
9
10
|
import {HitDesignCampaign, HitDesignTemplate} from '../types';
|
|
10
11
|
import {addBreadCrumbsToRibbons, checkEditPermissions,
|
|
11
|
-
checkViewPermissions,
|
|
12
|
+
checkViewPermissions, getGroupedCampaigns, getSavedCampaignsGrouping,
|
|
13
|
+
loadCampaigns, modifyUrl, popRibbonPannels,
|
|
14
|
+
processGroupingTable,
|
|
15
|
+
setSavedCampaignsGrouping} from '../utils';
|
|
12
16
|
import {newHitDesignCampaignAccordeon} from '../accordeons/new-hit-design-campaign-accordeon';
|
|
13
17
|
import {newHitDesignTemplateAccordeon} from '../accordeons/new-hit-design-template-accordeon';
|
|
14
18
|
import {HitBaseView} from '../base-view';
|
|
@@ -17,6 +21,7 @@ import {defaultPermissions, PermissionsDialog} from '../dialogs/permissions-dial
|
|
|
17
21
|
export class HitDesignInfoView
|
|
18
22
|
<T extends HitDesignTemplate = HitDesignTemplate, K extends HitDesignApp = HitDesignApp>
|
|
19
23
|
extends HitBaseView<T, K> {
|
|
24
|
+
currentSorting: string = 'None';
|
|
20
25
|
constructor(app: K) {
|
|
21
26
|
super(app);
|
|
22
27
|
this.name = 'Hit Design';
|
|
@@ -48,6 +53,7 @@ export class HitDesignInfoView
|
|
|
48
53
|
ui.setUpdateIndicator(this.root, true);
|
|
49
54
|
try {
|
|
50
55
|
const continueCampaignsHeader = ui.h1(i18n.continueCampaigns);
|
|
56
|
+
|
|
51
57
|
const createNewCampaignHeader = ui.h1(i18n.createNewCampaignHeader, {style: {marginLeft: '10px'}});
|
|
52
58
|
const appHeader = this.getAppHeader();
|
|
53
59
|
|
|
@@ -56,10 +62,38 @@ export class HitDesignInfoView
|
|
|
56
62
|
const contentDiv = ui.div([templatesDiv, campaignAccordionDiv], 'ui-form');
|
|
57
63
|
|
|
58
64
|
const campaignsTable = await this.getCampaignsTable();
|
|
65
|
+
const tableRoot = ui.div([campaignsTable], {style: {position: 'relative'}});
|
|
66
|
+
|
|
67
|
+
const sortIcon = ui.iconFA('sort', () => {
|
|
68
|
+
const menu = DG.Menu.popup();
|
|
69
|
+
Object.values(CampaignGroupingType).forEach((i) => {
|
|
70
|
+
menu.item(i, async () => {
|
|
71
|
+
setSavedCampaignsGrouping(i as CampaignGroupingType);
|
|
72
|
+
ui.setUpdateIndicator(tableRoot, true);
|
|
73
|
+
try {
|
|
74
|
+
const t = await this.getCampaignsTable();
|
|
75
|
+
ui.setUpdateIndicator(tableRoot, false);
|
|
76
|
+
ui.empty(tableRoot);
|
|
77
|
+
tableRoot.appendChild(t);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
grok.shell.error('Failed to update campaigns table');
|
|
80
|
+
console.error(e);
|
|
81
|
+
} finally {
|
|
82
|
+
ui.setUpdateIndicator(tableRoot, false);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
menu.show({element: sortingHeader, x: 100, y: sortingHeader.offsetTop + 30});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
sortIcon.style.marginBottom = '12px';
|
|
89
|
+
sortIcon.style.marginLeft = '5px';
|
|
90
|
+
sortIcon.style.fontSize = '15px';
|
|
91
|
+
ui.tooltip.bind(sortIcon, () => `Group Campaigns. Current: ${this.currentSorting}`);
|
|
92
|
+
const sortingHeader = ui.divH([continueCampaignsHeader, sortIcon], {style: {alignItems: 'center'}});
|
|
59
93
|
$(this.root).empty();
|
|
60
94
|
this.root.appendChild(ui.div([
|
|
61
|
-
ui.divV([appHeader,
|
|
62
|
-
|
|
95
|
+
ui.divV([appHeader, sortingHeader], {style: {marginLeft: '10px'}}),
|
|
96
|
+
tableRoot,
|
|
63
97
|
createNewCampaignHeader,
|
|
64
98
|
contentDiv,
|
|
65
99
|
]));
|
|
@@ -178,7 +212,10 @@ export class HitDesignInfoView
|
|
|
178
212
|
|
|
179
213
|
private async getCampaignsTable() {
|
|
180
214
|
const campaignNamesMap = await loadCampaigns(this.app.appName, this.deletedCampaigns);
|
|
181
|
-
|
|
215
|
+
const grouppingMode = getSavedCampaignsGrouping();
|
|
216
|
+
const grouppedCampaigns = getGroupedCampaigns<HitDesignCampaign>(Object.values(campaignNamesMap), grouppingMode);
|
|
217
|
+
this.currentSorting = grouppingMode;
|
|
218
|
+
this.app.existingStatuses = Array.from(new Set(Object.values(campaignNamesMap).map((c) => c.status).filter((s) => !!s)));
|
|
182
219
|
const deleteAndShareCampaignIcons = (info: HitDesignCampaign) => {
|
|
183
220
|
const deleteIcon = ui.icons.delete(async () => {
|
|
184
221
|
ui.dialog('Delete campaign')
|
|
@@ -226,6 +263,7 @@ export class HitDesignInfoView
|
|
|
226
263
|
['Name', 'Created', 'Molecules', 'Status', '']);
|
|
227
264
|
table.style.color = 'var(--grey-5)';
|
|
228
265
|
table.style.marginLeft = '24px';
|
|
266
|
+
processGroupingTable(table, grouppedCampaigns);
|
|
229
267
|
return table;
|
|
230
268
|
}
|
|
231
269
|
|
|
@@ -239,7 +277,7 @@ export class HitDesignInfoView
|
|
|
239
277
|
this.app.dataFrame = camp.df;
|
|
240
278
|
await this.app.setTemplate(template);
|
|
241
279
|
this.app.campaignProps = camp.campaignProps;
|
|
242
|
-
await this.app.saveCampaign(
|
|
280
|
+
await this.app.saveCampaign(false);
|
|
243
281
|
if (template.layoutViewState && this.app.campaign)
|
|
244
282
|
this.app.campaign.layout = template.layoutViewState;
|
|
245
283
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import * as ui from 'datagrok-api/ui';
|
|
2
3
|
import * as grok from 'datagrok-api/grok';
|
|
3
4
|
import * as DG from 'datagrok-api/dg';
|
|
@@ -7,19 +8,54 @@ import {HitDesignTemplate} from '../types';
|
|
|
7
8
|
import {HitBaseView} from '../base-view';
|
|
8
9
|
|
|
9
10
|
export class HitDesignSubmitView extends HitBaseView<HitDesignTemplate, HitDesignApp> {
|
|
11
|
+
private statusInput: DG.InputBase<string | undefined>;
|
|
12
|
+
private statusSuggestionsMenu: DG.Menu;
|
|
13
|
+
content: HTMLDivElement;
|
|
10
14
|
constructor(app: HitDesignApp) {
|
|
11
15
|
super(app);
|
|
12
16
|
this.name = 'Submit';
|
|
17
|
+
this.statusInput = ui.input.string('Status', {value: this.app.campaign?.status, nullable: false});
|
|
18
|
+
this.statusSuggestionsMenu = DG.Menu.popup();
|
|
19
|
+
this.statusInput.root.style.marginLeft = '12px';
|
|
20
|
+
this.content = ui.div();
|
|
21
|
+
|
|
22
|
+
this.statusInput.onChanged.subscribe(() => {
|
|
23
|
+
this.statusSuggestionsMenu.clear();
|
|
24
|
+
const status = (this.statusInput.value ?? '').toLowerCase();
|
|
25
|
+
// eslint-disable-next-line max-len
|
|
26
|
+
const similarStatuses = this.app.existingStatuses.filter((s) => s?.toLowerCase()?.includes(status) && s?.toLowerCase() !== status).filter((_, i) => i < 5);
|
|
27
|
+
if (similarStatuses.length) {
|
|
28
|
+
similarStatuses.forEach((s) => {
|
|
29
|
+
this.statusSuggestionsMenu.item(s, () => {
|
|
30
|
+
this.statusInput.value = s;
|
|
31
|
+
this.statusSuggestionsMenu.root.remove();
|
|
32
|
+
this.statusInput.root.focus();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
if (this.content.parentElement?.parentElement) {
|
|
36
|
+
const xOffset = this.statusInput.root.offsetLeft + (this.statusInput.input?.offsetLeft ?? 0);
|
|
37
|
+
const yOffset = this.statusInput.root.offsetTop + this.statusInput.root.offsetHeight + this.content.parentElement.offsetTop;
|
|
38
|
+
this.statusSuggestionsMenu.show({element: this.content.parentElement.parentElement!, x: xOffset, y: yOffset});
|
|
39
|
+
}
|
|
40
|
+
} else
|
|
41
|
+
this.statusSuggestionsMenu.root.remove();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public getStatus() {
|
|
46
|
+
return this.statusInput.value ?? this.app.campaign?.status ?? 'No Status';
|
|
13
47
|
}
|
|
14
48
|
|
|
15
49
|
render(): HTMLDivElement {
|
|
16
|
-
|
|
50
|
+
this.statusInput.value = this.app.campaign?.status ?? '';
|
|
51
|
+
ui.empty(this.content);
|
|
17
52
|
|
|
18
|
-
|
|
53
|
+
this.content = ui.divV([
|
|
19
54
|
ui.h1('Summary'),
|
|
20
55
|
ui.div([ui.tableFromMap(this.app.getSummary())]),
|
|
56
|
+
this.statusInput.root,
|
|
21
57
|
]);
|
|
22
|
-
return content;
|
|
58
|
+
return this.content;
|
|
23
59
|
}
|
|
24
60
|
|
|
25
61
|
onActivated(): void {
|
|
@@ -37,8 +73,8 @@ export class HitDesignSubmitView extends HitBaseView<HitDesignTemplate, HitDesig
|
|
|
37
73
|
}
|
|
38
74
|
const filteredDf = DG.DataFrame.fromCsv(this.app.dataFrame!.toCsv({filteredRowsOnly: true}));
|
|
39
75
|
await submitFn.apply({df: filteredDf, molecules: this.app.molColName});
|
|
40
|
-
this.app.campaign
|
|
41
|
-
this.app.saveCampaign(
|
|
76
|
+
this.app.campaign!.status = this.getStatus();
|
|
77
|
+
this.app.saveCampaign();
|
|
42
78
|
grok.shell.info('Submitted successfully.');
|
|
43
79
|
}
|
|
44
80
|
}
|
|
@@ -5,6 +5,7 @@ import {HitDesignApp} from '../hit-design-app';
|
|
|
5
5
|
import {_package} from '../../package';
|
|
6
6
|
import {TileCategoriesColName} from '../consts';
|
|
7
7
|
import './utils.css';
|
|
8
|
+
import {getTileCategoryEditor} from '../accordeons/new-hit-design-template-accordeon';
|
|
8
9
|
|
|
9
10
|
export function getTilesViewDialog(app: HitDesignApp, getTableView: () => DG.TableView | null) {
|
|
10
11
|
const tilesViewerSketchStateString = app.campaign?.tilesViewerFormSketch;
|
|
@@ -32,7 +33,8 @@ export function getTilesViewDialog(app: HitDesignApp, getTableView: () => DG.Tab
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const tileOpts = {lanesColumnName: TileCategoriesColName,
|
|
36
|
+
const tileOpts = {lanesColumnName: TileCategoriesColName,
|
|
37
|
+
lanes: app.stages,
|
|
36
38
|
...((sketchState?.elementStates?.length ?? 0) > 0 ? {sketchState} : {})};
|
|
37
39
|
|
|
38
40
|
const tv = getTableView();
|
|
@@ -59,15 +61,50 @@ export function getTilesViewDialog(app: HitDesignApp, getTableView: () => DG.Tab
|
|
|
59
61
|
v.detach();
|
|
60
62
|
}
|
|
61
63
|
v = tv.addViewer(DG.VIEWER.TILE_VIEWER,
|
|
62
|
-
{lanesColumnName: TileCategoriesColName, lanes: app.
|
|
64
|
+
{lanesColumnName: TileCategoriesColName, lanes: app.stages});
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
if (!v) {
|
|
66
68
|
grok.shell.error('Failed to create tiles viewer. check the console for more details.');
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
69
|
-
modal.add(v.root);
|
|
70
71
|
|
|
72
|
+
let stageEditorDialog: DG.Dialog | null = null;
|
|
73
|
+
const closeViewer = () => {// it can be already closed by the time we get here
|
|
74
|
+
try {
|
|
75
|
+
v.detach();
|
|
76
|
+
v.close();
|
|
77
|
+
} catch (e) {
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
modal.add(v.root);
|
|
81
|
+
modal.addButton('Modify Stages', () => {
|
|
82
|
+
stageEditorDialog?.close();
|
|
83
|
+
const stageEditor = getTileCategoryEditor(app.stages);
|
|
84
|
+
stageEditorDialog = ui.dialog('Modify Stages')
|
|
85
|
+
.add(stageEditor.fieldsDiv)
|
|
86
|
+
.onOK(async () => {
|
|
87
|
+
closeViewer(); // so that its not included in the layout.
|
|
88
|
+
ui.setUpdateIndicator(modal.root, true);
|
|
89
|
+
try {
|
|
90
|
+
await app.setStages(stageEditor.getFields());
|
|
91
|
+
ui.setUpdateIndicator(modal.root, false);
|
|
92
|
+
modal.close();
|
|
93
|
+
await new Promise<void>((r) => setTimeout(() => {
|
|
94
|
+
r();
|
|
95
|
+
getTilesViewDialog(app, getTableView);
|
|
96
|
+
}, 100));
|
|
97
|
+
} catch (e) {
|
|
98
|
+
grok.shell.error('Failed to update stages. check the console for more details.');
|
|
99
|
+
console.error('Failed to update stages', e);
|
|
100
|
+
} finally {
|
|
101
|
+
if (modal?.root && document.contains(modal.root))
|
|
102
|
+
modal.close();
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.show();
|
|
106
|
+
stageEditorDialog.root.classList.add('hit-design-stage-editing-dialog');
|
|
107
|
+
}, 0);
|
|
71
108
|
// from this modal, only way to go to another view is through edit form.
|
|
72
109
|
// when this happens, we should not destroy the viewer,
|
|
73
110
|
//but just hide it so that when we come back, we can show it again.
|
|
@@ -85,6 +122,7 @@ export function getTilesViewDialog(app: HitDesignApp, getTableView: () => DG.Tab
|
|
|
85
122
|
const closeSub = modal.onClose.subscribe(() => {
|
|
86
123
|
closeSub.unsubscribe();
|
|
87
124
|
viewChangeSub.unsubscribe();
|
|
125
|
+
stageEditorDialog?.close();
|
|
88
126
|
// save the sketch state
|
|
89
127
|
try {
|
|
90
128
|
const sketchState = v.props.sketchState;
|
|
@@ -97,13 +135,13 @@ export function getTilesViewDialog(app: HitDesignApp, getTableView: () => DG.Tab
|
|
|
97
135
|
grok.shell.error('Failed to save sketch state. check the console for more details.');
|
|
98
136
|
console.error('Failed to save sketch state', e);
|
|
99
137
|
}
|
|
100
|
-
|
|
101
|
-
v.close();
|
|
138
|
+
closeViewer();
|
|
102
139
|
});
|
|
103
140
|
|
|
104
141
|
modal.showModal(true);
|
|
105
142
|
|
|
106
|
-
|
|
143
|
+
modal.getButton('CANCEL')?.remove();
|
|
144
|
+
modal.onOK(() => {});
|
|
107
145
|
// remove the modal background
|
|
108
146
|
document.querySelector('.d4-modal-background')?.remove();
|
|
109
147
|
}
|
package/src/app/pepti-hit-app.ts
CHANGED
|
@@ -80,11 +80,11 @@ export class PeptiHitApp extends HitDesignApp<PeptiHitTemplate> {
|
|
|
80
80
|
subs.push(this.dataFrame!.onRowsAdded.pipe(filter(() => !this.isJoining))
|
|
81
81
|
.subscribe(() => { // TODO, insertion of rows in the middle
|
|
82
82
|
try {
|
|
83
|
-
if (this.
|
|
83
|
+
if (this.stages.length > 0) {
|
|
84
84
|
for (let i = 0; i < this.dataFrame!.rowCount; i++) {
|
|
85
85
|
const colVal = this.dataFrame!.col(TileCategoriesColName)!.get(i);
|
|
86
86
|
if (!colVal || colVal === '' || this.dataFrame!.col(TileCategoriesColName)?.isNone(i))
|
|
87
|
-
this.dataFrame!.set(TileCategoriesColName, i, this.
|
|
87
|
+
this.dataFrame!.set(TileCategoriesColName, i, this.stages[0]);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
let lastAddedCell: DG.GridCell | null = null;
|
|
@@ -154,7 +154,7 @@ export class PeptiHitApp extends HitDesignApp<PeptiHitTemplate> {
|
|
|
154
154
|
view?.grid && subs.push(view.grid.onCellValueEdited.subscribe(async (gc) => {
|
|
155
155
|
try {
|
|
156
156
|
if (gc.tableColumn?.name === TileCategoriesColName) {
|
|
157
|
-
await this.saveCampaign(
|
|
157
|
+
await this.saveCampaign(false);
|
|
158
158
|
return;
|
|
159
159
|
}
|
|
160
160
|
if (gc.tableColumn?.name !== this.helmColName)
|
|
@@ -22,7 +22,7 @@ export class PeptiHitInfoView extends HitDesignInfoView<PeptiHitTemplate, PeptiH
|
|
|
22
22
|
this.app.dataFrame = camp.df;
|
|
23
23
|
await this.app.setTemplate(template);
|
|
24
24
|
this.app.campaignProps = camp.campaignProps;
|
|
25
|
-
await this.app.saveCampaign(
|
|
25
|
+
await this.app.saveCampaign(false);
|
|
26
26
|
if (template.layoutViewState && this.app.campaign)
|
|
27
27
|
this.app.campaign.layout = template.layoutViewState;
|
|
28
28
|
});
|
package/src/app/types.ts
CHANGED
package/src/app/utils.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import * as grok from 'datagrok-api/grok';
|
|
2
3
|
import * as ui from 'datagrok-api/ui';
|
|
3
4
|
import * as DG from 'datagrok-api/dg';
|
|
4
5
|
import {Subscription} from 'rxjs';
|
|
5
|
-
import {CampaignJsonName, ComputeQueryMolColName} from './consts';
|
|
6
|
-
import {AppName, CampaignsType, TriagePermissions} from './types';
|
|
6
|
+
import {CampaignGroupingType, CampaignJsonName, ComputeQueryMolColName, HDCampaignsGroupingLSKey} from './consts';
|
|
7
|
+
import {AppName, CampaignsType, HitDesignCampaign, HitTriageCampaign, TriagePermissions} from './types';
|
|
7
8
|
import {_package} from '../package';
|
|
8
9
|
|
|
9
10
|
export const toFormatedDateString = (d: Date): string => {
|
|
@@ -164,3 +165,82 @@ export async function checkEditPermissions(authorId: string, permissions: Triage
|
|
|
164
165
|
export async function checkViewPermissions(authorId: string, permissions: TriagePermissions): Promise<boolean> {
|
|
165
166
|
return checkPermissions(authorId, Array.from(new Set([...permissions.view, ...permissions.edit])));
|
|
166
167
|
}
|
|
168
|
+
|
|
169
|
+
export function getLocalStorageValue<T = string>(key: string): T | null {
|
|
170
|
+
return localStorage.getItem(key) as unknown as T;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function setLocalStorageValue(key: string, value: string) {
|
|
174
|
+
localStorage.setItem(key, value as unknown as string);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function getSavedCampaignsGrouping(): CampaignGroupingType {
|
|
178
|
+
return getLocalStorageValue<CampaignGroupingType>(HDCampaignsGroupingLSKey) ?? CampaignGroupingType.None;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function setSavedCampaignsGrouping(value: CampaignGroupingType) {
|
|
182
|
+
setLocalStorageValue(HDCampaignsGroupingLSKey, value);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const getGroupingKey = <T extends HitDesignCampaign | HitTriageCampaign = HitDesignCampaign>(grouping: CampaignGroupingType, campaign: T): string => {
|
|
186
|
+
switch (grouping) {
|
|
187
|
+
case CampaignGroupingType.Template:
|
|
188
|
+
return campaign.template?.key ?? campaign.templateName;
|
|
189
|
+
case CampaignGroupingType.Status:
|
|
190
|
+
return campaign.status;
|
|
191
|
+
default:
|
|
192
|
+
return '';
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export function getGroupedCampaigns<T extends HitDesignCampaign | HitTriageCampaign = HitDesignCampaign>(campaigns: T[], grouping: CampaignGroupingType):
|
|
197
|
+
{[key: string]: T[]} {
|
|
198
|
+
if (grouping === CampaignGroupingType.None)
|
|
199
|
+
return {'': campaigns};
|
|
200
|
+
const groupedCampaigns: {[key: string]: T[]} = {};
|
|
201
|
+
for (const campaign of campaigns) {
|
|
202
|
+
const key = getGroupingKey(grouping, campaign);
|
|
203
|
+
if (!groupedCampaigns[key])
|
|
204
|
+
groupedCampaigns[key] = [];
|
|
205
|
+
groupedCampaigns[key].push(campaign);
|
|
206
|
+
}
|
|
207
|
+
return groupedCampaigns;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function processGroupingTable<T extends HitDesignCampaign | HitTriageCampaign = HitDesignCampaign>(table: HTMLTableElement, groupedCampaigns: {[key: string]: T[]}, numCols = 6) {
|
|
211
|
+
table.classList.add('hit-design-groupped-campaigns-table');
|
|
212
|
+
const keys = Object.keys(groupedCampaigns);
|
|
213
|
+
if (keys.length < 2)
|
|
214
|
+
return table;
|
|
215
|
+
const body = table.getElementsByTagName('tbody')[0];
|
|
216
|
+
const rows = Array.from(table.getElementsByTagName('tr')).filter((row) => !row.classList.contains('header'));
|
|
217
|
+
let curRow = 0;
|
|
218
|
+
|
|
219
|
+
const setState = (expanded: boolean, start: number, end: number) => {
|
|
220
|
+
for (let i = start; i < end; i++)
|
|
221
|
+
rows[i]?.style && (rows[i].style.display = expanded ? 'table-row' : 'none');
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
for (const key of keys) {
|
|
226
|
+
const row = rows[curRow];
|
|
227
|
+
if (!row)
|
|
228
|
+
break;
|
|
229
|
+
const l = groupedCampaigns[key].length;
|
|
230
|
+
const startRow = curRow;
|
|
231
|
+
const endRow = curRow + l;
|
|
232
|
+
const acc = ui.accordion(`Hit-Design-campaigns-group-${key}`);
|
|
233
|
+
const pane = acc.addPane(key, () => {return ui.div();}, undefined, undefined, false);
|
|
234
|
+
pane.root.style.marginLeft = '-24px';
|
|
235
|
+
pane.root.onclick = () => {
|
|
236
|
+
setState(pane.expanded, startRow, endRow);
|
|
237
|
+
};
|
|
238
|
+
setState(pane.expanded, startRow, endRow);
|
|
239
|
+
const newRow = body.insertRow(0);
|
|
240
|
+
const newCell = newRow.insertCell(0);
|
|
241
|
+
newCell.appendChild(pane.root);
|
|
242
|
+
newCell.colSpan = numCols;
|
|
243
|
+
body.insertBefore(newRow, row);
|
|
244
|
+
curRow += l;
|
|
245
|
+
}
|
|
246
|
+
}
|