@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.
- package/CHANGELOG.md +4 -0
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/files/Hit Design/campaigns/DMT1-1/campaign.json +69 -1
- package/files/Hit Design/campaigns/DMT2-1/campaign.json +84 -1
- package/files/PeptiHit/campaigns/PHD-1/campaign.json +1 -0
- package/files/PeptiHit/campaigns/PHD-1/enriched_table.csv +10 -0
- package/files/PeptiHit/campaigns/PHD-2/campaign.json +1 -0
- package/files/PeptiHit/campaigns/PHD-2/enriched_table.csv +82 -0
- package/files/PeptiHit/templates/Peptide hits Demo.json +1 -0
- package/images/icons/pepti-hit-icon.png +0 -0
- package/package.json +4 -4
- package/src/app/accordeons/layout-input.ts +2 -2
- package/src/app/accordeons/new-hit-design-campaign-accordeon.ts +12 -2
- package/src/app/accordeons/new-hit-design-template-accordeon.ts +12 -11
- package/src/app/accordeons/new-template-accordeon.ts +6 -9
- package/src/app/base-view.ts +2 -2
- package/src/app/consts.ts +1 -1
- package/src/app/dialogs/functions-dialog.ts +3 -3
- package/src/app/hit-app-base.ts +6 -3
- package/src/app/hit-design-app.ts +194 -213
- package/src/app/hit-design-views/info-view.ts +39 -33
- package/src/app/hit-design-views/tiles-view.ts +6 -0
- package/src/app/hit-triage-app.ts +1 -1
- package/src/app/pepti-hit-app.ts +221 -0
- package/src/app/pepti-hits-views/info-view.ts +49 -0
- package/src/app/types.ts +5 -1
- package/src/package.ts +47 -22
|
@@ -14,8 +14,10 @@ import {newHitDesignTemplateAccordeon} from '../accordeons/new-hit-design-templa
|
|
|
14
14
|
import {HitBaseView} from '../base-view';
|
|
15
15
|
import {defaultPermissions, PermissionsDialog} from '../dialogs/permissions-dialog';
|
|
16
16
|
|
|
17
|
-
export class HitDesignInfoView
|
|
18
|
-
|
|
17
|
+
export class HitDesignInfoView
|
|
18
|
+
<T extends HitDesignTemplate = HitDesignTemplate, K extends HitDesignApp = HitDesignApp>
|
|
19
|
+
extends HitBaseView<T, K> {
|
|
20
|
+
constructor(app: K) {
|
|
19
21
|
super(app);
|
|
20
22
|
this.name = 'Hit Design';
|
|
21
23
|
grok.shell.windows.showHelp = true;
|
|
@@ -28,22 +30,26 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
28
30
|
grok.shell.windows.help.showHelp(_package.webRoot + 'README_HD.md');
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
getAppHeader() {
|
|
34
|
+
return u2.appHeader({
|
|
35
|
+
iconPath: _package.webRoot + '/images/icons/hit-design-icon.png',
|
|
36
|
+
learnMoreUrl: 'https://github.com/datagrok-ai/public/blob/master/packages/HitTriage/README_HD.md',
|
|
37
|
+
description:
|
|
38
|
+
'- Configure your own workflow using the template editor\n' +
|
|
39
|
+
'- Sketch molecules in the molecular spreadsheet\n' +
|
|
40
|
+
'- Annotate and share ideas with the team\n' +
|
|
41
|
+
'- Calculate different molecular properties\n' +
|
|
42
|
+
'- Save campaigns and continue from where you left off\n' +
|
|
43
|
+
'- Submit final selection to the function of your choice',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async init(presetTemplate?: T) {
|
|
32
48
|
ui.setUpdateIndicator(this.root, true);
|
|
33
49
|
try {
|
|
34
50
|
const continueCampaignsHeader = ui.h1(i18n.continueCampaigns);
|
|
35
51
|
const createNewCampaignHeader = ui.h1(i18n.createNewCampaignHeader, {style: {marginLeft: '10px'}});
|
|
36
|
-
const appHeader =
|
|
37
|
-
iconPath: _package.webRoot + '/images/icons/hit-design-icon.png',
|
|
38
|
-
learnMoreUrl: 'https://github.com/datagrok-ai/public/blob/master/packages/HitTriage/README_HD.md',
|
|
39
|
-
description:
|
|
40
|
-
'- Configure your own workflow using the template editor\n' +
|
|
41
|
-
'- Sketch molecules in the molecular spreadsheet\n' +
|
|
42
|
-
'- Annotate and share ideas with the team\n' +
|
|
43
|
-
'- Calculate different molecular properties\n' +
|
|
44
|
-
'- Save campaigns and continue from where you left off\n' +
|
|
45
|
-
'- Submit final selection to the function of your choice',
|
|
46
|
-
});
|
|
52
|
+
const appHeader = this.getAppHeader();
|
|
47
53
|
|
|
48
54
|
const campaignAccordionDiv = ui.div();
|
|
49
55
|
const templatesDiv = ui.divH([]);
|
|
@@ -64,24 +70,24 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
containerDiv: HTMLElement, templateInputDiv: HTMLElement, presetTemplate?:
|
|
73
|
+
protected async startNewCampaign(
|
|
74
|
+
containerDiv: HTMLElement, templateInputDiv: HTMLElement, presetTemplate?: T,
|
|
69
75
|
) {
|
|
70
|
-
const templates = (await _package.files.list(
|
|
76
|
+
const templates = (await _package.files.list(`${this.app.appName}/templates`))
|
|
71
77
|
.filter((file) => file.name.endsWith('.json'))
|
|
72
78
|
.map((file) => file.name.slice(0, -5));
|
|
73
79
|
// if the template is just created and saved, it may not be in the list of templates
|
|
74
80
|
if (presetTemplate && !templates.includes(presetTemplate.name))
|
|
75
81
|
templates.push(presetTemplate.name);
|
|
76
82
|
|
|
77
|
-
let selectedTemplate:
|
|
83
|
+
let selectedTemplate: T | null = null;
|
|
78
84
|
let templateChangeFlag = true;
|
|
79
85
|
const onTemmplateChange = async () => {
|
|
80
86
|
if (!templateChangeFlag)
|
|
81
87
|
return;
|
|
82
88
|
const templateName = templatesInput.value;
|
|
83
|
-
const template:
|
|
84
|
-
JSON.parse(await _package.files.readAsText(
|
|
89
|
+
const template: T = presetTemplate && presetTemplate.name === templateName ? presetTemplate :
|
|
90
|
+
JSON.parse(await _package.files.readAsText(`${this.app.appName}/templates/${templateName}.json`));
|
|
85
91
|
selectedTemplate = template;
|
|
86
92
|
const newCampaignAccordeon = await this.getNewCampaignAccordeon(template);
|
|
87
93
|
$(containerDiv).empty();
|
|
@@ -110,7 +116,7 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
110
116
|
.onOK(async () => {
|
|
111
117
|
try {
|
|
112
118
|
const prevValue = templatesInput.value;
|
|
113
|
-
await _package.files.delete(
|
|
119
|
+
await _package.files.delete(`${this.app.appName}/templates/${prevValue}.json`);
|
|
114
120
|
templateChangeFlag = false;
|
|
115
121
|
templatesInput.items = templatesInput.items.filter((item) => item !== prevValue);
|
|
116
122
|
templatesInput.value = templatesInput.items[0];
|
|
@@ -135,17 +141,17 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
|
|
138
|
-
|
|
144
|
+
protected async checkCampaign(campId?: string) {
|
|
139
145
|
const url = location.search;
|
|
140
146
|
const urlParams = new URLSearchParams(url);
|
|
141
147
|
if (!urlParams.has(HitDesignCampaignIdKey) && !campId)
|
|
142
148
|
return;
|
|
143
149
|
const campaignId = campId ?? urlParams.get(HitDesignCampaignIdKey);
|
|
144
150
|
// check if such campaign exists
|
|
145
|
-
if (!await _package.files.exists(
|
|
151
|
+
if (!await _package.files.exists(`${this.app.appName}/campaigns/${campaignId}/${CampaignJsonName}`))
|
|
146
152
|
return;
|
|
147
153
|
const campaign: HitDesignCampaign =
|
|
148
|
-
JSON.parse(await _package.files.readAsText(
|
|
154
|
+
JSON.parse(await _package.files.readAsText(`${this.app.appName}/campaigns/${campaignId}/${CampaignJsonName}`));
|
|
149
155
|
if (campaign) {
|
|
150
156
|
// in case if the link was opened and user has no permissions to view the campaign
|
|
151
157
|
if (campaign.authorUserId && campaign.permissions &&
|
|
@@ -157,8 +163,8 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
157
163
|
this.app.campaign = campaign;
|
|
158
164
|
}
|
|
159
165
|
// Load the template and modify it
|
|
160
|
-
const template:
|
|
161
|
-
await _package.files.readAsText(
|
|
166
|
+
const template: T = campaign.template ?? JSON.parse(
|
|
167
|
+
await _package.files.readAsText(`${this.app.appName}/templates/${campaign.templateName}.json`),
|
|
162
168
|
);
|
|
163
169
|
// modify the template with path to the campaign's precalculated table
|
|
164
170
|
await this.app.setTemplate(template, campaignId!);
|
|
@@ -171,14 +177,14 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
private async getCampaignsTable() {
|
|
174
|
-
const campaignNamesMap = await loadCampaigns(
|
|
180
|
+
const campaignNamesMap = await loadCampaigns(this.app.appName, this.deletedCampaigns);
|
|
175
181
|
|
|
176
182
|
const deleteAndShareCampaignIcons = (info: HitDesignCampaign) => {
|
|
177
183
|
const deleteIcon = ui.icons.delete(async () => {
|
|
178
184
|
ui.dialog('Delete campaign')
|
|
179
185
|
.add(ui.divText(`Are you sure you want to delete campaign ${info.name}?`))
|
|
180
186
|
.onOK(async () => {
|
|
181
|
-
await this.deleteCampaign(
|
|
187
|
+
await this.deleteCampaign(this.app.appName, info.name);
|
|
182
188
|
this.deletedCampaigns.push(info.name);
|
|
183
189
|
await this.init();
|
|
184
190
|
})
|
|
@@ -190,7 +196,7 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
190
196
|
info.permissions = res;
|
|
191
197
|
info.authorUserId ??= grok.shell.user.id;
|
|
192
198
|
await _package.files.writeAsText(
|
|
193
|
-
|
|
199
|
+
`${this.app.appName}/campaigns/${info.name}/${CampaignJsonName}`, JSON.stringify(info));
|
|
194
200
|
grok.shell.info('Permissions updated for campaign ' + info.name);
|
|
195
201
|
} catch (e) {
|
|
196
202
|
grok.shell.error('Failed to update permissions for campaign ' + info.name);
|
|
@@ -227,7 +233,7 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
227
233
|
const campaign = await this.checkCampaign(campaignName);
|
|
228
234
|
this.app.campaign = campaign;
|
|
229
235
|
}
|
|
230
|
-
|
|
236
|
+
async getNewCampaignAccordeon(template: T) {
|
|
231
237
|
const {root, promise, cancelPromise} = newHitDesignCampaignAccordeon(template);
|
|
232
238
|
promise.then(async (camp) => {
|
|
233
239
|
this.app.dataFrame = camp.df;
|
|
@@ -244,7 +250,7 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
244
250
|
return root;
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
private async createNewTemplate(preset?:
|
|
253
|
+
private async createNewTemplate(preset?: T) {
|
|
248
254
|
ui.setUpdateIndicator(this.root, true);
|
|
249
255
|
try {
|
|
250
256
|
const newTemplateAccordeon = await newHitDesignTemplateAccordeon(this.app, preset);
|
|
@@ -259,13 +265,13 @@ export class HitDesignInfoView extends HitBaseView<HitDesignTemplate, HitDesignA
|
|
|
259
265
|
newView.parentCall = this.app.parentCall;
|
|
260
266
|
grok.shell.addView(newView);
|
|
261
267
|
newView.path = new URL(this.app.baseUrl).pathname + '/new-template';
|
|
262
|
-
const {sub} = addBreadCrumbsToRibbons(newView,
|
|
268
|
+
const {sub} = addBreadCrumbsToRibbons(newView, this.app.appName, i18n.createNewTemplate, async () => {
|
|
263
269
|
grok.shell.v = curView;
|
|
264
270
|
newView.close();
|
|
265
271
|
});
|
|
266
272
|
//containerDiv.appendChild(newTemplateAccordeon.root);
|
|
267
273
|
newTemplateAccordeon.template.then(async (t) => {
|
|
268
|
-
await this.init(t);
|
|
274
|
+
await this.init(t as any);
|
|
269
275
|
newView.close();
|
|
270
276
|
popRibbonPannels(newView);
|
|
271
277
|
grok.shell.v = curView;
|
|
@@ -23,6 +23,12 @@ export function getTilesViewDialog(app: HitDesignApp, getTableView: () => DG.Tab
|
|
|
23
23
|
sketchState.elementStates = sketchState.elementStates.filter((elementState: any) =>
|
|
24
24
|
elementState?.viewerSettings?.column && app.dataFrame!.col(elementState.viewerSettings.column),
|
|
25
25
|
);
|
|
26
|
+
// similarly, because the table name can be duplicated or changed,
|
|
27
|
+
// we need to make sure that table name in all viewer settings corresponds to actual table name
|
|
28
|
+
sketchState.elementStates.forEach((elState: any) => {
|
|
29
|
+
if (elState?.viewerSettings?.table)
|
|
30
|
+
elState.viewerSettings.table = app.dataFrame!.name;
|
|
31
|
+
});
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -40,7 +40,7 @@ export class HitTriageApp extends HitAppBase<HitTriageTemplate> {
|
|
|
40
40
|
private _pickViewPromise?: Promise<void> | null = null;
|
|
41
41
|
|
|
42
42
|
constructor(c: DG.FuncCall) {
|
|
43
|
-
super(c);
|
|
43
|
+
super(c, 'Hit Triage');
|
|
44
44
|
this._infoView = new InfoView(this);
|
|
45
45
|
this.multiView = new DG.MultiView({viewFactories: {[this._infoView.name]: () => this._infoView}});
|
|
46
46
|
this.multiView.tabs.onTabChanged.subscribe((_) => {
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
import {PeptiHitTemplate} from './types';
|
|
5
|
+
import {PeptiHitHelmColName, TileCategoriesColName, ViDColName} from './consts';
|
|
6
|
+
import {getNewVid} from './utils/calculate-single-cell';
|
|
7
|
+
import '../../css/hit-triage.css';
|
|
8
|
+
import {_package} from '../package';
|
|
9
|
+
import {Subscription} from 'rxjs';
|
|
10
|
+
import {filter} from 'rxjs/operators';
|
|
11
|
+
import {HitDesignApp} from './hit-design-app';
|
|
12
|
+
import {PeptiHitInfoView} from './pepti-hits-views/info-view';
|
|
13
|
+
|
|
14
|
+
export class PeptiHitApp extends HitDesignApp<PeptiHitTemplate> {
|
|
15
|
+
_helmColName: string = PeptiHitHelmColName;
|
|
16
|
+
constructor(c: DG.FuncCall) {
|
|
17
|
+
super(c, 'PeptiHit', (app) => new PeptiHitInfoView(app as PeptiHitApp));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async setTemplate(template: PeptiHitTemplate, campaignId?: string): Promise<void> {
|
|
21
|
+
await super.setTemplate(template, campaignId);
|
|
22
|
+
this._helmColName = this.dataFrame!.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)?.name ?? PeptiHitHelmColName;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get helmColName(): string {
|
|
26
|
+
return this._helmColName ??
|
|
27
|
+
this.dataFrame!.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)?.name;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async performSingleCellCalculations(cellIndex: number, cellValue?: string) {
|
|
31
|
+
try {
|
|
32
|
+
if (!cellValue)
|
|
33
|
+
throw new Error('No cell value provided');
|
|
34
|
+
const col = DG.Column.fromStrings(this.helmColName, [cellValue]);
|
|
35
|
+
const table = DG.DataFrame.fromColumns([col]);
|
|
36
|
+
col.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
37
|
+
col.setTag('units', 'helm');
|
|
38
|
+
col.setTag('.alphabetIsMultichar', 'true');
|
|
39
|
+
await grok.functions.call('Bio:toAtomicLevel', {table: table, seqCol: col, nonlinear: true});
|
|
40
|
+
const molCol = table.columns.names().find((c) => c.toLowerCase() !== this.helmColName.toLowerCase());
|
|
41
|
+
const mol = molCol ? table.col(molCol)?.get(0) : null;
|
|
42
|
+
if (!mol)
|
|
43
|
+
throw new Error('Failed to convert sequence to atomic level');
|
|
44
|
+
this.dataFrame!.set(this.molColName, cellIndex, mol);
|
|
45
|
+
await super.performSingleCellCalculations(cellIndex, mol);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected getDesignView(): DG.TableView {
|
|
52
|
+
const subs: Subscription[] = [];
|
|
53
|
+
const isNew = this.dataFrame!.col(this.helmColName)?.toList().every((m) => !m && m === '');
|
|
54
|
+
const helmCol = this.dataFrame!.col(this.helmColName);
|
|
55
|
+
if (!helmCol)
|
|
56
|
+
throw new Error('No helm column found');
|
|
57
|
+
helmCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
58
|
+
helmCol.setTag('units', 'helm');
|
|
59
|
+
helmCol.setTag('.alphabetIsMultichar', 'true');
|
|
60
|
+
helmCol.setTag('cell.renderer', 'helm');
|
|
61
|
+
|
|
62
|
+
const view = grok.shell.addTableView(this.dataFrame!);
|
|
63
|
+
this._designViewName = this.campaign?.name ?? this._designViewName;
|
|
64
|
+
view.name = this._designViewName;
|
|
65
|
+
view._onAdded();
|
|
66
|
+
const layoutViewState = this._campaign?.layout ?? this.template?.layoutViewState;
|
|
67
|
+
if (layoutViewState) {
|
|
68
|
+
try {
|
|
69
|
+
const layout = DG.ViewLayout.fromViewState(layoutViewState);
|
|
70
|
+
view.loadLayout(layout);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
grok.shell.error('Failed to apply layout. Falling back to default layout.');
|
|
73
|
+
console.error(e);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isNew)
|
|
78
|
+
grok.functions.call('Helm:editMoleculeCell', {cell: view.grid.cell(this.helmColName, 0)});
|
|
79
|
+
|
|
80
|
+
subs.push(this.dataFrame!.onRowsAdded.pipe(filter(() => !this.isJoining))
|
|
81
|
+
.subscribe(() => { // TODO, insertion of rows in the middle
|
|
82
|
+
try {
|
|
83
|
+
if (this.template!.stages?.length > 0) {
|
|
84
|
+
for (let i = 0; i < this.dataFrame!.rowCount; i++) {
|
|
85
|
+
const colVal = this.dataFrame!.col(TileCategoriesColName)!.get(i);
|
|
86
|
+
if (!colVal || colVal === '' || this.dataFrame!.col(TileCategoriesColName)?.isNone(i))
|
|
87
|
+
this.dataFrame!.set(TileCategoriesColName, i, this.template!.stages[0]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let lastAddedCell: DG.GridCell | null = null;
|
|
91
|
+
for (let i = 0; i < this.dataFrame!.rowCount; i++) {
|
|
92
|
+
const cell = view.grid.cell(this.helmColName, i);
|
|
93
|
+
if (!cell)
|
|
94
|
+
continue;
|
|
95
|
+
if (cell.cell.value === '' || cell.cell.value === null)
|
|
96
|
+
lastAddedCell = cell;
|
|
97
|
+
}
|
|
98
|
+
if (lastAddedCell)
|
|
99
|
+
grok.functions.call('Helm:editMoleculeCell', {cell: lastAddedCell});
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.error(e);
|
|
102
|
+
}
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
subs.push(grok.events.onContextMenu.subscribe((args) => {
|
|
106
|
+
try {
|
|
107
|
+
const viewer: DG.Viewer = args?.args?.context;
|
|
108
|
+
if (!viewer)
|
|
109
|
+
return;
|
|
110
|
+
if (viewer?.type !== DG.VIEWER.GRID)
|
|
111
|
+
return;
|
|
112
|
+
if (!viewer.tableView || viewer.tableView.id !== view.id)
|
|
113
|
+
return;
|
|
114
|
+
if (args?.args?.item?.tableColumn?.name !== this.helmColName || !args?.args?.item?.isTableCell)
|
|
115
|
+
return;
|
|
116
|
+
const menu: DG.Menu = args?.args?.menu;
|
|
117
|
+
if (!menu)
|
|
118
|
+
return;
|
|
119
|
+
menu.item('Add new row', () => {
|
|
120
|
+
this.dataFrame!.rows.addNew(null, true);
|
|
121
|
+
});
|
|
122
|
+
menu.item('Duplicate Sequence', () => {
|
|
123
|
+
try {
|
|
124
|
+
const row = this.dataFrame!.rows.addNew(null, true);
|
|
125
|
+
const cell = row.get(this.helmColName);
|
|
126
|
+
if (cell != null && row.idx > -1)
|
|
127
|
+
this.dataFrame!.cell(row.idx, this.helmColName).value = args?.args?.item?.cell?.value ?? '';
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error(e);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const cellIndex = args?.args?.item?.tableRowIndex;
|
|
134
|
+
const cellValue = args?.args?.item?.cell?.value;
|
|
135
|
+
if (cellValue && (cellIndex ?? -1) > -1) {
|
|
136
|
+
menu.item('Re-Run Calculations', async () => {
|
|
137
|
+
try {
|
|
138
|
+
await this.performSingleCellCalculations(cellIndex, cellValue);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error(e);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch (e: any) {
|
|
145
|
+
grok.log.error(e);
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
if (!view?.grid) {
|
|
150
|
+
grok.shell.error('Applied layout created view without grid. Resetting layout.');
|
|
151
|
+
view.resetLayout();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
view?.grid && subs.push(view.grid.onCellValueEdited.subscribe(async (gc) => {
|
|
155
|
+
try {
|
|
156
|
+
if (gc.tableColumn?.name === TileCategoriesColName) {
|
|
157
|
+
await this.saveCampaign(undefined, false);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (gc.tableColumn?.name !== this.helmColName)
|
|
161
|
+
return;
|
|
162
|
+
const newValue = gc.cell.value;
|
|
163
|
+
const newValueIdx = gc.tableRowIndex!;
|
|
164
|
+
let newVid = this.dataFrame!.col(ViDColName)?.get(newValueIdx);
|
|
165
|
+
let foundMatch = false;
|
|
166
|
+
// try to find existing sequence
|
|
167
|
+
if (newValue) {
|
|
168
|
+
try {
|
|
169
|
+
const canonicals = gc.tableColumn.toList();
|
|
170
|
+
const canonicalNewValue = newValue;
|
|
171
|
+
if (canonicals?.length === this.dataFrame!.rowCount) {
|
|
172
|
+
for (let i = 0; i < canonicals.length; i++) {
|
|
173
|
+
if (canonicals[i] === canonicalNewValue &&
|
|
174
|
+
i !== newValueIdx && this.dataFrame!.col(ViDColName)?.get(i)) {
|
|
175
|
+
newVid = this.dataFrame!.col(ViDColName)?.get(i);
|
|
176
|
+
foundMatch = true;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
console.error(e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// if the vid was duplicated, generate a new one
|
|
186
|
+
if (this.duplicateVidCache && !foundMatch &&
|
|
187
|
+
this.duplicateVidCache.valueCounts[this.duplicateVidCache.indexes[newValueIdx]] > 1)
|
|
188
|
+
newVid = null;
|
|
189
|
+
|
|
190
|
+
if (!newVid || newVid === '')
|
|
191
|
+
newVid = getNewVid(this.dataFrame!.col(ViDColName)!);
|
|
192
|
+
|
|
193
|
+
this.dataFrame!.col(ViDColName)!.set(newValueIdx, newVid, false);
|
|
194
|
+
|
|
195
|
+
this.performSingleCellCalculations(newValueIdx, newValue);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
console.error(e);
|
|
198
|
+
}
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
view?.grid && subs.push(view.grid.onCellRender.subscribe((args) => {
|
|
202
|
+
try {
|
|
203
|
+
// color duplicate vid values
|
|
204
|
+
const cell = args.cell;
|
|
205
|
+
if (!cell || !cell.isTableCell || !cell.tableColumn || !this.duplicateVidCache ||
|
|
206
|
+
cell.tableColumn.name !== ViDColName || (cell.tableRowIndex ?? -1) < 0)
|
|
207
|
+
return;
|
|
208
|
+
|
|
209
|
+
if (this.duplicateVidCache.valueCounts[this.duplicateVidCache.indexes[cell.tableRowIndex!]] > 1) {
|
|
210
|
+
args.cell.style.backColor =
|
|
211
|
+
DG.Color.setAlpha(DG.Color.getCategoricalColor(this.duplicateVidCache.indexes[cell.tableRowIndex!])
|
|
212
|
+
, 150);
|
|
213
|
+
}
|
|
214
|
+
} catch (e) {}
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
this.initDesignViewRibbons(view, subs);
|
|
218
|
+
view.parentCall = this.parentCall;
|
|
219
|
+
return view;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
import {u2} from '@datagrok-libraries/utils/src/u2';
|
|
5
|
+
import {_package} from '../../package';
|
|
6
|
+
import {PeptiHitTemplate} from '../types';
|
|
7
|
+
import {newHitDesignCampaignAccordeon} from '../accordeons/new-hit-design-campaign-accordeon';
|
|
8
|
+
import {HitDesignInfoView} from '../hit-design-views/info-view';
|
|
9
|
+
import {PeptiHitApp} from '../pepti-hit-app';
|
|
10
|
+
|
|
11
|
+
export class PeptiHitInfoView extends HitDesignInfoView<PeptiHitTemplate, PeptiHitApp> {
|
|
12
|
+
constructor(app: PeptiHitApp) {
|
|
13
|
+
super(app);
|
|
14
|
+
this.name = 'PeptiHit';
|
|
15
|
+
grok.shell.windows.showHelp = true;
|
|
16
|
+
grok.shell.windows.help.showHelp(_package.webRoot + 'README_HD.md');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override async getNewCampaignAccordeon(template: PeptiHitTemplate) {
|
|
20
|
+
const {root, promise, cancelPromise} = newHitDesignCampaignAccordeon(template, true);
|
|
21
|
+
promise.then(async (camp) => {
|
|
22
|
+
this.app.dataFrame = camp.df;
|
|
23
|
+
await this.app.setTemplate(template);
|
|
24
|
+
this.app.campaignProps = camp.campaignProps;
|
|
25
|
+
await this.app.saveCampaign(undefined, false);
|
|
26
|
+
if (template.layoutViewState && this.app.campaign)
|
|
27
|
+
this.app.campaign.layout = template.layoutViewState;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
cancelPromise.then(() => {
|
|
31
|
+
this.init();
|
|
32
|
+
});
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override getAppHeader() {
|
|
37
|
+
return u2.appHeader({
|
|
38
|
+
iconPath: _package.webRoot + '/images/icons/pepti-hit-icon.png',
|
|
39
|
+
learnMoreUrl: 'https://github.com/datagrok-ai/public/blob/master/packages/HitTriage/README_HD.md',
|
|
40
|
+
description:
|
|
41
|
+
'- Configure your own workflow using the template editor\n' +
|
|
42
|
+
'- Sketch Helm sequences in the spreadsheet\n' +
|
|
43
|
+
'- Annotate and share ideas with the team\n' +
|
|
44
|
+
'- Convert to molecular form and calculate different properties\n' +
|
|
45
|
+
'- Save campaigns and continue from where you left off\n' +
|
|
46
|
+
'- Submit final selection to the function of your choice',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/app/types.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
2
|
|
|
3
|
-
export type AppName = 'Hit Triage' | 'Hit Design';
|
|
3
|
+
export type AppName = 'Hit Triage' | 'Hit Design' | 'PeptiHit';
|
|
4
4
|
|
|
5
5
|
export type CampaignsType = {
|
|
6
6
|
'Hit Triage': HitTriageCampaign,
|
|
7
7
|
'Hit Design': HitDesignCampaign,
|
|
8
|
+
'PeptiHit': HitDesignCampaign,
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export type IDescriptorTree = {
|
|
@@ -145,6 +146,9 @@ export type HitDesignTemplate = Omit<HitTriageTemplate, 'dataSourceType' | 'quer
|
|
|
145
146
|
|
|
146
147
|
export type HitDesignCampaign = Omit<HitTriageCampaign, 'filters' | 'ingest'> & {tilesViewerFormSketch?: string};
|
|
147
148
|
|
|
149
|
+
// todo: probably add some more stuff
|
|
150
|
+
export type PeptiHitTemplate = HitDesignTemplate & {toAtomiLevelProps?: {[key: string]: any}}
|
|
151
|
+
|
|
148
152
|
export type ComputeFunctions = {
|
|
149
153
|
functions: DG.Func[],
|
|
150
154
|
scripts: DG.Script[],
|
package/src/package.ts
CHANGED
|
@@ -7,30 +7,12 @@ import {HitDesignApp} from './app/hit-design-app';
|
|
|
7
7
|
import {GasteigerPngRenderer} from './pngRenderers';
|
|
8
8
|
import {loadCampaigns} from './app/utils';
|
|
9
9
|
import {AppName} from './app';
|
|
10
|
+
import {PeptiHitApp} from './app/pepti-hit-app';
|
|
11
|
+
import {PeptiHitHelmColName} from './app/consts';
|
|
10
12
|
// import {loadCampaigns} from './app/utils';
|
|
11
13
|
|
|
12
14
|
export const _package = new DG.Package();
|
|
13
15
|
|
|
14
|
-
//input: dynamic treeNode
|
|
15
|
-
//input: view browseView
|
|
16
|
-
export async function hitTriageAppTreeBrowser(treeNode: DG.TreeViewGroup, browseView: any) {// TODO: DG.BrowseView
|
|
17
|
-
await hitAppTB(treeNode, browseView, 'Hit Triage');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
//tags: app
|
|
21
|
-
//name: Hit Triage
|
|
22
|
-
//output: view v
|
|
23
|
-
export async function hitTriageApp(): Promise<DG.ViewBase> {
|
|
24
|
-
const c = grok.functions.getCurrentCall();
|
|
25
|
-
return new HitTriageApp(c).multiView;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
//input: dynamic treeNode
|
|
29
|
-
//input: view browseView
|
|
30
|
-
export async function hitDesignAppTreeBrowser(treeNode: DG.TreeViewGroup, browseView: any) {// TODO: DG.BrowseView
|
|
31
|
-
await hitAppTB(treeNode, browseView, 'Hit Design');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
16
|
async function hitAppTB(treeNode: DG.TreeViewGroup, browseView: any, name: AppName) {// TODO: DG.BrowseView
|
|
35
17
|
const camps = await loadCampaigns(name, []);
|
|
36
18
|
|
|
@@ -38,7 +20,8 @@ async function hitAppTB(treeNode: DG.TreeViewGroup, browseView: any, name: AppNa
|
|
|
38
20
|
const savePath = 'ingest' in camp ? camp.ingest.query : camp.savePath;
|
|
39
21
|
if (!savePath || await grok.dapi.files.exists(savePath) === false)
|
|
40
22
|
continue;
|
|
41
|
-
treeNode.item(camp.name)
|
|
23
|
+
const node = treeNode.item(camp.name);
|
|
24
|
+
node.onSelected.subscribe(async (_) => {
|
|
42
25
|
try {
|
|
43
26
|
const df = await grok.dapi.files.readCsv(savePath);
|
|
44
27
|
if (!df)
|
|
@@ -47,8 +30,14 @@ async function hitAppTB(treeNode: DG.TreeViewGroup, browseView: any, name: AppNa
|
|
|
47
30
|
if (semtypeInfo) {
|
|
48
31
|
for (const [colName, semType] of Object.entries(semtypeInfo)) {
|
|
49
32
|
const col = df.columns.byName(colName);
|
|
50
|
-
if (col)
|
|
33
|
+
if (col) {
|
|
51
34
|
col.semType = semType;
|
|
35
|
+
if (semType === DG.SEMTYPE.MACROMOLECULE && colName === PeptiHitHelmColName) {
|
|
36
|
+
col.setTag('units', 'helm');
|
|
37
|
+
col.setTag('.alphabetIsMultichar', 'true');
|
|
38
|
+
col.setTag('cell.renderer', 'helm');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
52
41
|
}
|
|
53
42
|
}
|
|
54
43
|
const tv = DG.TableView.create(df, false);
|
|
@@ -63,6 +52,32 @@ async function hitAppTB(treeNode: DG.TreeViewGroup, browseView: any, name: AppNa
|
|
|
63
52
|
}
|
|
64
53
|
}
|
|
65
54
|
|
|
55
|
+
//input: dynamic treeNode
|
|
56
|
+
//input: view browseView
|
|
57
|
+
export async function hitTriageAppTreeBrowser(treeNode: DG.TreeViewGroup, browseView: any) {// TODO: DG.BrowseView
|
|
58
|
+
await hitAppTB(treeNode, browseView, 'Hit Triage');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//input: dynamic treeNode
|
|
62
|
+
//input: view browseView
|
|
63
|
+
export async function hitDesignAppTreeBrowser(treeNode: DG.TreeViewGroup, browseView: any) {// TODO: DG.BrowseView
|
|
64
|
+
await hitAppTB(treeNode, browseView, 'Hit Design');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//input: dynamic treeNode
|
|
68
|
+
//input: view browseView
|
|
69
|
+
export async function peptiHitAppTreeBrowser(treeNode: DG.TreeViewGroup, browseView: any) {// TODO: DG.BrowseView
|
|
70
|
+
await hitAppTB(treeNode, browseView, 'PeptiHit');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//tags: app
|
|
74
|
+
//name: Hit Triage
|
|
75
|
+
//output: view v
|
|
76
|
+
export async function hitTriageApp(): Promise<DG.ViewBase> {
|
|
77
|
+
const c = grok.functions.getCurrentCall();
|
|
78
|
+
return new HitTriageApp(c).multiView;
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
//tags: app
|
|
67
82
|
//name: Hit Design
|
|
68
83
|
//meta.icon: images/icons/hit-design-icon.png
|
|
@@ -72,6 +87,16 @@ export async function hitDesignApp(): Promise<DG.ViewBase> {
|
|
|
72
87
|
return new HitDesignApp(c).multiView;
|
|
73
88
|
}
|
|
74
89
|
|
|
90
|
+
//tags: app
|
|
91
|
+
//name: PeptiHit
|
|
92
|
+
//meta.icon: images/icons/pepti-hit-icon.png
|
|
93
|
+
//output: view v
|
|
94
|
+
export async function peptiHitApp(): Promise<DG.ViewBase> {
|
|
95
|
+
const c = grok.functions.getCurrentCall();
|
|
96
|
+
await grok.functions.call('Bio:initBio', {});
|
|
97
|
+
return new PeptiHitApp(c).multiView;
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
//name: Demo Molecules 100
|
|
76
101
|
//tags: HitTriageDataSource
|
|
77
102
|
//output: dataframe result
|