@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.
@@ -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 extends HitBaseView<HitDesignTemplate, HitDesignApp> {
18
- constructor(app: HitDesignApp) {
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
- async init(presetTemplate?: HitDesignTemplate) {
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 = u2.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
- private async startNewCampaign(
68
- containerDiv: HTMLElement, templateInputDiv: HTMLElement, presetTemplate?: HitDesignTemplate,
73
+ protected async startNewCampaign(
74
+ containerDiv: HTMLElement, templateInputDiv: HTMLElement, presetTemplate?: T,
69
75
  ) {
70
- const templates = (await _package.files.list('Hit Design/templates'))
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: HitDesignTemplate | null = null;
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: HitDesignTemplate = presetTemplate && presetTemplate.name === templateName ? presetTemplate :
84
- JSON.parse(await _package.files.readAsText('Hit Design/templates/' + templateName + '.json'));
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(`Hit Design/templates/${prevValue}.json`);
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
- private async checkCampaign(campId?: string) {
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(`Hit Design/campaigns/${campaignId}/${CampaignJsonName}`))
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(`Hit Design/campaigns/${campaignId}/${CampaignJsonName}`));
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: HitDesignTemplate = campaign.template ?? JSON.parse(
161
- await _package.files.readAsText(`Hit Design/templates/${campaign.templateName}.json`),
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('Hit Design', this.deletedCampaigns);
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('Hit Design', info.name);
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
- `Hit Design/campaigns/${info.name}/${CampaignJsonName}`, JSON.stringify(info));
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
- private async getNewCampaignAccordeon(template: HitDesignTemplate) {
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?: HitDesignTemplate) {
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, 'Hit Design', i18n.createNewTemplate, async () => {
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).onSelected.subscribe(async (_) => {
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