@datagrok/helm 2.3.4 → 2.4.2

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.
@@ -3,19 +3,20 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import $ from 'cash-dom';
6
- import {fromEvent, Unsubscribable} from 'rxjs';
6
+ import {fromEvent, Observable, Unsubscribable} from 'rxjs';
7
7
 
8
8
  import {IMonomerLib} from '@datagrok-libraries/bio/src/types/index';
9
- import {IHelmHelper, IInputInitOptions} from '@datagrok-libraries/bio/src/helm/helm-helper';
10
- import {HelmMol, IHelmWebEditor} from '@datagrok-libraries/bio/src/helm/types';
9
+ import {HelmInputBase, IHelmHelper, IHelmInputInitOptions} from '@datagrok-libraries/bio/src/helm/helm-helper';
10
+ import {HelmAtom, HelmMol, HelmString, IHelmWebEditor} from '@datagrok-libraries/bio/src/helm/types';
11
11
 
12
12
  import {defaultErrorHandler} from '../utils/err-info';
13
- import {getHoveredMonomerFromEditorMol} from '../utils/get-hovered';
13
+ import {getHoveredMonomerFromEditorMol, getSeqMonomerFromHelmAtom} from '../utils/get-hovered';
14
14
 
15
- import {_package} from '../package';
15
+ import {MonomerNumberingTypes} from '@datagrok-libraries/bio/src/helm/consts';
16
16
 
17
+ import {_package} from '../package';
17
18
 
18
- export class HelmInput extends DG.JsInputBase<HelmMol> {
19
+ export class HelmInput extends HelmInputBase {
19
20
  /** Input type identifier (such as "Slider" for the slider input). See {@link InputType}. */
20
21
  get inputType(): string {
21
22
  return 'Macromolecule';
@@ -30,12 +31,12 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
30
31
  return this.viewerHost;
31
32
  }
32
33
 
33
- getValue(): HelmMol {
34
- return this.viewer.editor.m;
34
+ getValue(): HelmString {
35
+ return this.viewer.editor.getHelm();
35
36
  }
36
37
 
37
- setValue(value: HelmMol): void {
38
- this.viewer.editor.setMol(value);
38
+ setValue(value: HelmString): void {
39
+ this.viewer.editor.setHelm(value);
39
40
  }
40
41
 
41
42
  getStringValue(): string {
@@ -46,8 +47,24 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
46
47
  this.viewer.editor.setHelm(value);
47
48
  }
48
49
 
49
- viewerHost: HTMLDivElement;
50
- viewer: IHelmWebEditor;
50
+ // -- IHelmInput --
51
+
52
+ get molValue(): HelmMol {
53
+ return this.viewer.editor.m;
54
+ }
55
+
56
+ set molValue(value: HelmMol) {
57
+ this.viewer.editor.setMol(value);
58
+ }
59
+
60
+ get onMouseMove(): Observable<MouseEvent> { return fromEvent<MouseEvent>(this.viewer.host, 'mousemove'); }
61
+
62
+ get onClick(): Observable<MouseEvent> { return fromEvent<MouseEvent>(this.viewer.host, 'click'); }
63
+
64
+ readonly viewerHost: HTMLDivElement;
65
+ readonly viewer: IHelmWebEditor;
66
+ readonly editHintDiv: HTMLDivElement;
67
+
51
68
  helmString: string = '';
52
69
  helmSelection: number[];
53
70
 
@@ -57,6 +74,7 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
57
74
  private readonly helmHelper: IHelmHelper,
58
75
  private readonly monomerLib: IMonomerLib,
59
76
  name?: string,
77
+ public readonly options?: IHelmInputInitOptions,
60
78
  private readonly logger = _package.logger,
61
79
  ) {
62
80
  super();
@@ -67,18 +85,49 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
67
85
  classes: 'ui-input-editor',
68
86
  style: {width: '100%', height: '100%', overflow: 'hidden'},
69
87
  });
70
- this.viewer = this.helmHelper.createHelmWebEditor(this.viewerHost);
88
+ this.viewer = this.helmHelper.createHelmWebEditor(this.viewerHost, {
89
+ monomerNumbering: MonomerNumberingTypes.continuous
90
+ });
91
+
92
+ this.editHintDiv = ui.divH([
93
+ ui.link('Click to edit', () => { this.viewer.host.click(); }, undefined, {}),
94
+ ], {style: {display: 'none', position: 'absolute', top: '0', right: '0'}});
71
95
 
72
96
  this.subs = [];
73
97
  this.subs.push(this.monomerLib.onChanged
74
98
  .subscribe(() => this.viewer.editor.redraw()));
75
- this.subs.push(fromEvent<MouseEvent>(this.viewer.host, 'mousemove')
76
- .subscribe(this.viewerOnMouseMove.bind(this)));
77
- this.subs.push(fromEvent<MouseEvent>(this.viewer.host, 'click')
78
- .subscribe(this.viewerOnClick.bind(this)));
99
+ /* eslint-disable rxjs/no-ignored-subscription */
100
+ fromEvent<MouseEvent>(this.viewer.host, 'mousemove')
101
+ .subscribe(this.viewerOnMouseMove.bind(this));
102
+ fromEvent<MouseEvent>(this.viewer.host, 'mouseenter')
103
+ .subscribe(this.viewerOnMouseEnter.bind(this));
104
+ fromEvent<MouseEvent>(this.viewer.host, 'mouseleave')
105
+ .subscribe(this.viewerOnMouseLeave.bind(this));
106
+ fromEvent<MouseEvent>(this.viewer.host, 'click')
107
+ .subscribe(this.viewerOnClick.bind(this));
108
+ /* eslint-enable rxjs/no-ignored-subscription */
109
+
110
+ this.subs.push(ui.onSizeChanged(this.root).subscribe(() => {
111
+ const rootStyle = window.getComputedStyle(this.root);
112
+ const h = this.root.clientHeight - parseFloat(rootStyle.paddingTop) - parseFloat(rootStyle.paddingBottom);
113
+ this.input.style.height = `${h}px`;
114
+ }));
115
+
116
+ this.subs.push(ui.onSizeChanged(this.input).subscribe(() => {
117
+ const rootStyle = window.getComputedStyle(this.root);
118
+ const h = this.root.clientHeight - parseFloat(rootStyle.paddingTop) - parseFloat(rootStyle.paddingBottom);
119
+ this.input.style.height = `{h}px`;
120
+ const w = this.input.clientWidth;
121
+ this.viewer.editor.setSize(w, h);
122
+ }));
79
123
 
80
124
  this.root.classList.add('ui-input-helm');
81
- this.root.append(this.viewerHost);
125
+ this.root.append(this.viewerHost, this.editHintDiv);
126
+ }
127
+
128
+ detach(): void {
129
+ for (const sub of this.subs)
130
+ sub.unsubscribe();
82
131
  }
83
132
 
84
133
  protected toLog(): string {
@@ -86,51 +135,33 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
86
135
  }
87
136
 
88
137
  /** Inspired by {@link DG.MarkdownInput}, {@link ui._create} */
89
- static create(
90
- helmHelper: IHelmHelper, monomerLib: IMonomerLib, name?: string, options?: IInputInitOptions<HelmMol>
138
+ static create(helmHelper: IHelmHelper, monomerLib: IMonomerLib,
139
+ name?: string, options?: IHelmInputInitOptions
91
140
  ): HelmInput {
92
- const input = new HelmInput(helmHelper, monomerLib, name);
141
+ const input = new HelmInput(helmHelper, monomerLib, name, options);
93
142
  // TODO: Apply options
94
143
  const value: HelmMol | string | undefined = options?.value;
95
144
  if (value !== undefined) {
96
145
  if (value instanceof String || typeof value === 'string')
97
146
  input.stringValue = value as any as string;
147
+ else if (typeof value === 'object' /* && value.T === 'MOL'*/)
148
+ input.molValue = value;
98
149
  else
99
- input.value = value;
150
+ throw new Error(`Unsupported value of type '${typeof value}'.`);
100
151
  }
101
152
 
102
153
  return input;
103
154
  }
104
155
 
105
- // static async init(host?: HTMLElement, cell?: DG.Cell): Promise<HelmInput> {
106
- // const helmHelper = await getHelmHelper();
107
- // const libHelper = await getMonomerLibHelper();
108
- //
109
- // const editor = helmHelper.createHelmWebEditor();
110
- // editor.host.style.width = '270px';
111
- // editor.host.style.height = '150px';
112
- // editor.host.style.paddingLeft = '40px';
113
- //
114
- // cell = cell ?? grok.shell.tv.dataFrame.currentCell;
115
- //
116
- // if (cell.column.semType === DG.SEMTYPE.MACROMOLECULE && cell.column.tags[DG.TAGS.UNITS] === NOTATION.HELM)
117
- // editor.editor.setHelm(cell.value);
118
- // else
119
- // editor.editor.setHelm(PT_HELM_EXAMPLE);
120
- //
121
- // return new HelmInput(helmHelper, editor, libHelper);
122
- // }
123
-
124
- setHelmString(helm: string): void {
125
- this.helmString = helm;
126
- this.viewer.editor.setHelm(helm);
127
- this.helmSelection = [];
156
+ redraw(): void {
157
+ this.viewer.editor.redraw();
128
158
  }
129
159
 
130
- getDiv(): HTMLDivElement {
131
- const title = ui.divText('Macromolecule', {style: {paddingTop: '43px', color: 'var(--grey-4)'}});
132
-
133
- return ui.divH([title, this.viewer.host], {style: {paddingLeft: '48px'}});
160
+ showTooltip(content: HTMLElement | string, a: HelmAtom): void {
161
+ /** Monomer sign offset for tooltip inspired by {@link org.helm.webeditor.drawMonomer()} */
162
+ const so = this.viewer.editor.getDrawOptions().fontsize * 1.2;
163
+ const bcr = this.input.getBoundingClientRect();
164
+ ui.tooltip.show(content, bcr.left + a.p.x + so, bcr.top + a.p.y + so);
134
165
  }
135
166
 
136
167
  // -- Handle events --
@@ -138,6 +169,58 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
138
169
  private viewerOnClick(_event: MouseEvent): void {
139
170
  ui.tooltip.hide();
140
171
 
172
+ if (this.options?.editable != false)
173
+ this.showEditorDialog();
174
+ }
175
+
176
+ private viewerOnMouseMove(event: MouseEvent): void {
177
+ const logPrefix = `${this.toLog()}.viewerOnMouseMove()`;
178
+
179
+ const argsX = event.offsetX;
180
+ const argsY = event.offsetY;
181
+ const mol = this.viewer.editor.m;
182
+ const hoveredAtom = getHoveredMonomerFromEditorMol(argsX, argsY, mol, this.viewer.editor.div.clientHeight);
183
+ if (hoveredAtom) {
184
+ const seqMonomer = getSeqMonomerFromHelmAtom(hoveredAtom);
185
+ const monomerLib = _package.monomerLib;
186
+ const tooltipEl = monomerLib ? monomerLib.getTooltip(seqMonomer.polymerType, seqMonomer.symbol) :
187
+ ui.divText('Monomer library is not available');
188
+ this.showTooltip(tooltipEl, hoveredAtom);
189
+ event.preventDefault();
190
+ event.stopPropagation();
191
+ } else
192
+ ui.tooltip.hide();
193
+
194
+ //this.logger.debug(`${logPrefix}, x = ${event.x}, y = ${event.y}`);
195
+ }
196
+
197
+ private viewerOnMouseEnter(_event: MouseEvent): void {
198
+ //this.logger.debug(`${this.toLog()}.viewerOnMouseEnter()`);
199
+ if (this.options?.editable != false)
200
+ this.showEditHint();
201
+ }
202
+
203
+ private viewerOnMouseLeave(event: MouseEvent): void {
204
+ if (this.editHintDiv.contains(event.relatedTarget as Node)) return; // prevents flickering
205
+
206
+ //this.logger.debug(`${this.toLog()}.viewerOnMouseLeave()`);
207
+ if (this.options?.editable != false)
208
+ this.hideEditHint();
209
+ }
210
+
211
+ // editHint
212
+
213
+ showEditHint(): void {
214
+ this.editHintDiv.style.removeProperty('display');
215
+ }
216
+
217
+ hideEditHint(): void {
218
+ this.editHintDiv.style.display = 'none';
219
+ }
220
+
221
+ // editor dialog
222
+
223
+ showEditorDialog(): void {
141
224
  const webEditorHost = ui.div();
142
225
  const webEditorApp = this.helmHelper.createWebEditorApp(webEditorHost, this.viewer.editor.getHelm());
143
226
 
@@ -146,16 +229,16 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
146
229
  };
147
230
 
148
231
  // Edit the molecule and select monomers to enumerate
149
- const dlg = ui.dialog({showHeader: false, showFooter: true})
232
+ const _editorDialog = ui.dialog({showHeader: false, showFooter: true})
150
233
  .add(webEditorHost!)
151
234
  .onOK(() => {
152
235
  try {
153
- const webEditorValue = webEditorApp.canvas!.getHelm(true)
236
+ const webEditorValue = webEditorApp!.canvas!.getHelm(true)
154
237
  .replace(/<\/span>/g, '').replace(/<span style='background:#bbf;'>/g, '');
155
238
  this.viewer.editor.setHelm(webEditorValue);
156
239
  this.helmString = webEditorValue;
157
240
 
158
- const selection = webEditorApp.canvas!.helm!.jsd.m.atoms;
241
+ const selection = webEditorApp!.canvas!.helm!.jsd.m.atoms;
159
242
  this.helmSelection = [];
160
243
  for (let i = 0; i < selection.length; i++) {
161
244
  if (selection[i].selected)
@@ -175,24 +258,4 @@ export class HelmInput extends DG.JsInputBase<HelmMol> {
175
258
  })
176
259
  .show({modal: true, fullScreen: true});
177
260
  }
178
-
179
- private viewerOnMouseMove(event: MouseEvent): void {
180
- const logPrefix = `${this.toLog()}.viewerOnMouseMove()`;
181
- const dpr = window.devicePixelRatio;
182
-
183
- const argsX = event.offsetX;
184
- const argsY = event.offsetY;
185
- const mol = this.viewer.editor.m;
186
- const seqMonomer = getHoveredMonomerFromEditorMol(argsX, argsY, mol, this.viewer.editor.div.clientHeight);
187
- if (seqMonomer) {
188
- const monomerLib = _package.monomerLib;
189
- const tooltipEl = monomerLib ? monomerLib.getTooltip(seqMonomer.polymerType, seqMonomer.symbol) :
190
- ui.divText('Monomer library is not available');
191
- ui.tooltip.show(tooltipEl, event.x + 16, event.y + 16);
192
- } else {
193
- // Tooltip for missing monomers
194
- ui.tooltip.hide();
195
- }
196
- this.logger.debug(`${logPrefix}, x = ${event.x}, y = ${event.y}`);
197
- }
198
261
  }
@@ -7,10 +7,10 @@ import $ from 'cash-dom';
7
7
  import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
8
8
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
9
9
 
10
- import {JSDraw2HelmModule} from '../types';
10
+ import {JSDraw2Module} from '../types';
11
11
  import {removeGapsFromHelm} from '../utils';
12
12
 
13
- declare const JSDraw2: JSDraw2HelmModule;
13
+ declare const JSDraw2: JSDraw2Module;
14
14
 
15
15
  export class SeqPropertiesError extends Error {
16
16
  constructor(message?: string, options?: ErrorOptions) {
@@ -47,8 +47,8 @@ export function getPropertiesDict(seq: string, sh: SeqHandler): {} {
47
47
  }
48
48
 
49
49
 
50
- export function getPropertiesWidget(value: DG.SemanticValue): DG.Widget {
51
- const sh = SeqHandler.forColumn(value.cell.column);
50
+ export function getPropertiesWidget(value: DG.SemanticValue<string>): DG.Widget {
51
+ const sh = SeqHandler.forColumn(value.cell.column as DG.Column<string>);
52
52
  try {
53
53
  const propDict = getPropertiesDict(value.value, sh);
54
54
  return new DG.Widget(
package/tsconfig.json CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  /* Basic Options */
6
6
  // "incremental": true, /* Enable incremental compilation */
7
- "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8
- "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9
- "lib": ["ES2022", "ES2022.String", "dom"], /* Specify library files to be included in the compilation. */
7
+ "target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8
+ "module": "ES2022", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9
+ "lib": ["ES2022", "ES2022.String", "DOM"], /* Specify library files to be included in the compilation. */
10
10
  "allowJs": true, /* Allow javascript files to be compiled. */
11
11
  // "checkJs": true, /* Report errors in .js files. */
12
12
  // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
package/webpack.config.js CHANGED
@@ -6,7 +6,7 @@ if (mode !== 'production')
6
6
  console.warn(`Building '${packageName}' in '${mode}' mode.`);
7
7
 
8
8
  module.exports = {
9
- //...(mode !== 'production' ? {} : {cache: {type: 'filesystem'}}),
9
+ // ...(mode !== 'production' ? {} : {cache: {type: 'filesystem'}}),
10
10
  cache: {type: 'filesystem'},
11
11
  mode: mode,
12
12
  entry: {
@@ -31,9 +31,9 @@ module.exports = {
31
31
  },
32
32
  module: {
33
33
  rules: [
34
- {test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], exclude: [/node_modules/, /vendor/]},
35
- {test: /\.ts(x?)$/, use: 'ts-loader', exclude: [/node_modules/, /vendor/]},
36
- {test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: [/node_modules/, /vendor/]},
34
+ {test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], exclude: [/node_modules/]},
35
+ {test: /\.ts(x?)$/, use: 'ts-loader', exclude: [/node_modules/]},
36
+ {test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: [/node_modules/]},
37
37
  ],
38
38
  },
39
39
  devtool: 'source-map',