@datagrok/helm 3.0.4 → 3.0.6
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 +11 -0
- package/dist/489.js +1 -1
- package/dist/489.js.map +1 -1
- 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/package.json +11 -2
- package/src/constants.ts +12 -0
- package/src/package-api.ts +8 -0
- package/src/package-test.ts +1 -0
- package/src/package-utils.ts +37 -0
- package/src/package.g.ts +9 -0
- package/src/package.ts +20 -0
- package/src/tests/helm-input-tests.ts +31 -0
- package/src/tests/wrap-width-tests.ts +132 -0
- package/src/utils/wrap-width.ts +18 -0
- package/src/widgets/helm-input.ts +4 -1
- package/src/widgets/wrap-width-widget.ts +73 -0
- package/test-console-output-1.log +138 -104
- package/test-playwright-report.json +20 -20
- package/test-record-1.mp4 +0 -0
package/package.json
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/helm",
|
|
3
3
|
"friendlyName": "Helm",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.6",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Davit Rizhinashvili",
|
|
7
7
|
"email": "drizhinashvili@datagrok.ai"
|
|
8
8
|
},
|
|
9
9
|
"description": "Provides support for HELM notation (importing, detecting, rendering, conversion).",
|
|
10
|
+
"properties": [
|
|
11
|
+
{
|
|
12
|
+
"name": "MonomersPerRow",
|
|
13
|
+
"description": "Number of monomers per row before a HELM structure wraps to the next line. Counts nucleotides for RNA/DNA.",
|
|
14
|
+
"propertyType": "int",
|
|
15
|
+
"defaultValue": "20",
|
|
16
|
+
"nullable": false
|
|
17
|
+
}
|
|
18
|
+
],
|
|
10
19
|
"sources": [
|
|
11
20
|
"css/helm.css"
|
|
12
21
|
],
|
|
13
22
|
"dependencies": {
|
|
14
23
|
"@datagrok-libraries/bio": "^6.0.2",
|
|
15
24
|
"@datagrok-libraries/chem-meta": "^1.2.9",
|
|
16
|
-
"@datagrok-libraries/hwe": "^1.0.
|
|
25
|
+
"@datagrok-libraries/hwe": "^1.0.5",
|
|
17
26
|
"@datagrok-libraries/test": "^1.3.1",
|
|
18
27
|
"@datagrok-libraries/utils": "^4.6.9",
|
|
19
28
|
"cash-dom": "^8.1.1",
|
package/src/constants.ts
CHANGED
|
@@ -24,3 +24,15 @@ export const SDF_MONOMER_NAME = 'MonomerName';
|
|
|
24
24
|
export const enum TAGS {
|
|
25
25
|
cellRendererRenderError = '.cell-renderer.render.error',
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/** Names of package properties declared in the `properties` section of `package.json`. */
|
|
29
|
+
export const enum HelmPackagePropertiesNames {
|
|
30
|
+
MonomersPerRow = 'MonomersPerRow',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Fallback for `MonomersPerRow` when the package property is missing or
|
|
35
|
+
* unparseable (a package loaded before its settings, an old install). Kept in
|
|
36
|
+
* step with the `defaultValue` in package.json and with hwe's own default.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_MONOMERS_PER_ROW = 20;
|
package/src/package-api.ts
CHANGED
|
@@ -23,6 +23,14 @@ export namespace funcs {
|
|
|
23
23
|
return await grok.functions.call('Helm:EditMoleculeCell', { cell });
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {DG.Column} col
|
|
28
|
+
* semType: Macromolecule
|
|
29
|
+
*/
|
|
30
|
+
export async function helmPanel(col: DG.Column ): Promise<any> {
|
|
31
|
+
return await grok.functions.call('Helm:HelmPanel', { col });
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* Adds editor
|
|
28
36
|
* @param {any} mol
|
package/src/package-test.ts
CHANGED
|
@@ -16,6 +16,7 @@ import './tests/helm-helper-tests';
|
|
|
16
16
|
import './tests/helm-substructure-filter';
|
|
17
17
|
import './tests/helm-activity-cliffs';
|
|
18
18
|
import './tests/to-atomic-level-ui-non-linear';
|
|
19
|
+
import './tests/wrap-width-tests';
|
|
19
20
|
|
|
20
21
|
export const _package = new DG.Package();
|
|
21
22
|
export {tests};
|
package/src/package-utils.ts
CHANGED
|
@@ -12,6 +12,11 @@ import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
|
12
12
|
|
|
13
13
|
import * as DG from 'datagrok-api/dg';
|
|
14
14
|
|
|
15
|
+
import {setMonomersPerRow} from '@datagrok-libraries/hwe';
|
|
16
|
+
|
|
17
|
+
import {HelmPackagePropertiesNames} from './constants';
|
|
18
|
+
import {resolveMonomersPerRow} from './utils/wrap-width';
|
|
19
|
+
|
|
15
20
|
import {_package} from './package';
|
|
16
21
|
|
|
17
22
|
// hwe migration (Phase 7): the legacy Dojo / JSDraw2 / HELMWebEditor loader and
|
|
@@ -52,6 +57,38 @@ export class HelmPackage extends DG.Package {
|
|
|
52
57
|
|
|
53
58
|
private _initialized: boolean = false;
|
|
54
59
|
|
|
60
|
+
// -- Wrapping (line-break) width --
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The package-wide default number of monomers per row, from the
|
|
64
|
+
* `MonomersPerRow` package property (see the `properties` section of
|
|
65
|
+
* package.json). This is the DEFAULT: the column widget in `helmPanel` can
|
|
66
|
+
* override it for the session without touching this value.
|
|
67
|
+
*
|
|
68
|
+
* Falls back to {@link DEFAULT_MONOMERS_PER_ROW} when settings are not
|
|
69
|
+
* loaded yet or hold something unparseable.
|
|
70
|
+
*/
|
|
71
|
+
public get defaultMonomersPerRow(): number {
|
|
72
|
+
let raw: unknown;
|
|
73
|
+
try {
|
|
74
|
+
raw = this.settings?.[HelmPackagePropertiesNames.MonomersPerRow];
|
|
75
|
+
} catch {
|
|
76
|
+
// `settings` reaches into the platform; a package instance that is not
|
|
77
|
+
// registered yet (or the standalone test bundle's copy) has nothing to
|
|
78
|
+
// reach into. Fall back rather than break every render.
|
|
79
|
+
raw = undefined;
|
|
80
|
+
}
|
|
81
|
+
return resolveMonomersPerRow(raw);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Push {@link defaultMonomersPerRow} into hwe's process-wide layout setting,
|
|
86
|
+
* which every renderer / editor / adapter reads at layout time.
|
|
87
|
+
*/
|
|
88
|
+
public applyDefaultMonomersPerRow(): void {
|
|
89
|
+
setMonomersPerRow(this.defaultMonomersPerRow);
|
|
90
|
+
}
|
|
91
|
+
|
|
55
92
|
/** Requires Bio initialized (monomer library). */
|
|
56
93
|
completeInit(helmHelper: IHelmHelper, libHelper: IMonomerLibHelper): void {
|
|
57
94
|
this._helmHelper = helmHelper;
|
package/src/package.g.ts
CHANGED
|
@@ -25,6 +25,15 @@ export function editMoleculeCell(cell: any) : void {
|
|
|
25
25
|
PackageFunctions.editMoleculeCell(cell);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
//name: HELM Renderer
|
|
29
|
+
//tags: widgets, panel
|
|
30
|
+
//input: column col { semType: Macromolecule; units: helm }
|
|
31
|
+
//output: widget result
|
|
32
|
+
//meta.role: widgets,panel
|
|
33
|
+
export async function helmPanel(col: DG.Column) : Promise<any> {
|
|
34
|
+
return await PackageFunctions.helmPanel(col);
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
//name: Edit Helm...
|
|
29
38
|
//description: Adds editor
|
|
30
39
|
//input: semantic_value mol { semType: Macromolecule }
|
package/src/package.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {HelmInputBase, IHelmHelper, IHelmInputInitOptions} from '@datagrok-libra
|
|
|
15
15
|
import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
16
16
|
|
|
17
17
|
import {getPropertiesWidget} from './widgets/properties-widget';
|
|
18
|
+
import {getWrapWidthWidget} from './widgets/wrap-width-widget';
|
|
18
19
|
import {HelmGridCellRenderer, HelmGridCellRendererBack} from './utils/helm-grid-cell-renderer';
|
|
19
20
|
import {HelmPackage} from './package-utils';
|
|
20
21
|
import {HelmHelper} from './helm-helper';
|
|
@@ -70,6 +71,11 @@ async function initHelmInt(): Promise<void> {
|
|
|
70
71
|
const libHelper = await getMonomerLibHelper();
|
|
71
72
|
const helmHelper: IHelmHelper = new HelmHelper(seqHelper, _package.logger, rdKitModule);
|
|
72
73
|
_package.completeInit(helmHelper, libHelper);
|
|
74
|
+
// Seed hwe's process-wide row-wrap width from the `MonomersPerRow` package
|
|
75
|
+
// property. Must run before the first cell renders; the column widget in
|
|
76
|
+
// `helmPanel` can then override it for the session without changing this
|
|
77
|
+
// default.
|
|
78
|
+
_package.applyDefaultMonomersPerRow();
|
|
73
79
|
} catch (err: any) {
|
|
74
80
|
const [errMsg, errStack] = errInfo(err);
|
|
75
81
|
grok.shell.error(`Package \'Helm\' init error:\n${errMsg}`);
|
|
@@ -151,6 +157,20 @@ export class PackageFunctions {
|
|
|
151
157
|
checkMonomersAndOpenWebEditor(cell, undefined, undefined);
|
|
152
158
|
}
|
|
153
159
|
|
|
160
|
+
@grok.decorators.panel({
|
|
161
|
+
name: 'HELM Renderer',
|
|
162
|
+
meta: {role: 'widgets'},
|
|
163
|
+
tags: ['widgets', 'panel'],
|
|
164
|
+
})
|
|
165
|
+
static async helmPanel(
|
|
166
|
+
@grok.decorators.param({'options': {'semType': 'Macromolecule', 'units': 'helm'}}) col: DG.Column): Promise<DG.Widget> {
|
|
167
|
+
if (col.meta.units !== NOTATION.HELM)
|
|
168
|
+
return new DG.Widget(ui.divText('These settings apply only to HELM columns'));
|
|
169
|
+
// Session-scoped row-wrap width. The package-wide default lives in the
|
|
170
|
+
// `MonomersPerRow` package property and is not touched here.
|
|
171
|
+
return getWrapWidthWidget();
|
|
172
|
+
}
|
|
173
|
+
|
|
154
174
|
|
|
155
175
|
@grok.decorators.func({
|
|
156
176
|
'meta': {'action': 'Edit Helm...'},
|
|
@@ -133,6 +133,37 @@ category('HelmInput', () => {
|
|
|
133
133
|
}
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
test('editor dialog OK updates the value and fires onChanged', async () => {
|
|
137
|
+
// Regression: the editor dialog's OK used to update only the drawing
|
|
138
|
+
// (`viewer.editor.setHelm`) — `stringValue` stayed stale (often empty) and
|
|
139
|
+
// `onChanged` never fired, so a consumer wired to the input (e.g. a Flow
|
|
140
|
+
// Helm Input node) read back an empty value after sketching.
|
|
141
|
+
const helmValue = 'PEPTIDE1{[meY].A.G.T}$$$$';
|
|
142
|
+
const helmInput = (await ui.input.helmAsync('Macromolecule')) as HelmInput;
|
|
143
|
+
let changed = 0;
|
|
144
|
+
const changedSub = helmInput.onChanged.subscribe(() => changed++);
|
|
145
|
+
const hostDlg = ui.dialog('Helm Editor OK Test').add(helmInput).show();
|
|
146
|
+
try {
|
|
147
|
+
await delay(300);
|
|
148
|
+
// The broken state: the drawing holds a molecule the value never saw.
|
|
149
|
+
helmInput.viewer.editor.setHelm(helmValue);
|
|
150
|
+
expect(helmInput.stringValue, '', 'precondition: the value is still empty');
|
|
151
|
+
helmInput.showEditorDialog();
|
|
152
|
+
await delay(2000); // the full web editor app builds asynchronously
|
|
153
|
+
const editorDlg = DG.Dialog.getOpenDialogs().find((d) => d.root !== hostDlg.root);
|
|
154
|
+
expect(editorDlg != null, true, 'the editor dialog opened');
|
|
155
|
+
editorDlg!.getButton('OK').click();
|
|
156
|
+
await delay(300);
|
|
157
|
+
const v = helmInput.stringValue;
|
|
158
|
+
expect(v.startsWith('PEPTIDE1{') && v.includes('meY'), true,
|
|
159
|
+
`OK propagates the sketched HELM into the value (was "${v}")`);
|
|
160
|
+
expect(changed > 0, true, 'onChanged fired so listeners see the edit');
|
|
161
|
+
} finally {
|
|
162
|
+
changedSub.unsubscribe();
|
|
163
|
+
hostDlg.close();
|
|
164
|
+
}
|
|
165
|
+
}, {timeout: 60000});
|
|
166
|
+
|
|
136
167
|
test('tooltip', async () => {
|
|
137
168
|
await _testTooltipOnHelmInput();
|
|
138
169
|
}, {timeout: 300000});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
|
+
|
|
3
|
+
import {after, before, category, expect, test} from '@datagrok-libraries/test/src/test';
|
|
4
|
+
import {getWrapSettings, setMonomersPerRow} from '@datagrok-libraries/hwe';
|
|
5
|
+
|
|
6
|
+
import {initHelmMainPackage} from './utils';
|
|
7
|
+
import {getWrapWidthWidget} from '../widgets/wrap-width-widget';
|
|
8
|
+
import {resolveMonomersPerRow} from '../utils/wrap-width';
|
|
9
|
+
import {DEFAULT_MONOMERS_PER_ROW} from '../constants';
|
|
10
|
+
|
|
11
|
+
// The int input inside the widget produced by `getWrapWidthWidget`.
|
|
12
|
+
function widgetInput(widget: DG.Widget): HTMLInputElement {
|
|
13
|
+
const input = widget.root.querySelector('input') as HTMLInputElement | null;
|
|
14
|
+
if (input === null) throw new Error('wrap-width widget: no input element');
|
|
15
|
+
return input;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Set the input the way a user would, so the widget's change handler runs.
|
|
19
|
+
function typeValue(input: HTMLInputElement, value: number): void {
|
|
20
|
+
input.value = String(value);
|
|
21
|
+
input.dispatchEvent(new Event('input', {bubbles: true}));
|
|
22
|
+
input.dispatchEvent(new Event('change', {bubbles: true}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
category('WrapWidth: package property', () => {
|
|
26
|
+
test('reads an int property', async () => {
|
|
27
|
+
expect(resolveMonomersPerRow(30), 30);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('reads a string defaultValue straight from package.json', async () => {
|
|
31
|
+
// An `int` property that has not round-tripped through the server arrives
|
|
32
|
+
// as the string written in package.json.
|
|
33
|
+
expect(resolveMonomersPerRow('30'), 30);
|
|
34
|
+
expect(resolveMonomersPerRow('20'), 20);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('falls back when settings are missing or unusable', async () => {
|
|
38
|
+
// A NaN reaching the layout would produce a drawing with no coordinates,
|
|
39
|
+
// so every unusable value must land on the default instead.
|
|
40
|
+
for (const raw of [undefined, null, '', 'abc', Number.NaN, 0, -5, {}])
|
|
41
|
+
expect(resolveMonomersPerRow(raw), DEFAULT_MONOMERS_PER_ROW);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('default matches the value declared in package.json', async () => {
|
|
45
|
+
expect(DEFAULT_MONOMERS_PER_ROW, 20);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
category('WrapWidth: session widget', () => {
|
|
50
|
+
// The wrap width is process-wide state inside hwe; put it back afterwards so
|
|
51
|
+
// a failure here cannot change how the rest of the suite renders.
|
|
52
|
+
let restore: number;
|
|
53
|
+
|
|
54
|
+
before(async () => {
|
|
55
|
+
await initHelmMainPackage();
|
|
56
|
+
restore = getWrapSettings().monomersPerRow;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
after(async () => {
|
|
60
|
+
setMonomersPerRow(restore);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('opens showing the current session value', async () => {
|
|
64
|
+
setMonomersPerRow(14);
|
|
65
|
+
const widget = getWrapWidthWidget();
|
|
66
|
+
try {
|
|
67
|
+
expect(Number(widgetInput(widget).value), 14);
|
|
68
|
+
} finally {
|
|
69
|
+
widget.detach();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('editing the input changes the session value', async () => {
|
|
74
|
+
setMonomersPerRow(DEFAULT_MONOMERS_PER_ROW);
|
|
75
|
+
const widget = getWrapWidthWidget();
|
|
76
|
+
try {
|
|
77
|
+
typeValue(widgetInput(widget), 8);
|
|
78
|
+
expect(getWrapSettings().monomersPerRow, 8);
|
|
79
|
+
} finally {
|
|
80
|
+
widget.detach();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('editing does NOT change the package default', async () => {
|
|
85
|
+
// This is the whole point of the split: the package property stays the
|
|
86
|
+
// value every new session starts from.
|
|
87
|
+
const widget = getWrapWidthWidget();
|
|
88
|
+
try {
|
|
89
|
+
typeValue(widgetInput(widget), 5);
|
|
90
|
+
expect(getWrapSettings().monomersPerRow, 5);
|
|
91
|
+
expect(resolveMonomersPerRow('20'), 20);
|
|
92
|
+
expect(DEFAULT_MONOMERS_PER_ROW, 20);
|
|
93
|
+
} finally {
|
|
94
|
+
widget.detach();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('reflects a change made elsewhere', async () => {
|
|
99
|
+
const widget = getWrapWidthWidget();
|
|
100
|
+
try {
|
|
101
|
+
setMonomersPerRow(13);
|
|
102
|
+
expect(Number(widgetInput(widget).value), 13);
|
|
103
|
+
} finally {
|
|
104
|
+
widget.detach();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('stops tracking after detach', async () => {
|
|
109
|
+
const widget = getWrapWidthWidget();
|
|
110
|
+
const input = widgetInput(widget);
|
|
111
|
+
typeValue(input, 9);
|
|
112
|
+
widget.detach();
|
|
113
|
+
setMonomersPerRow(17);
|
|
114
|
+
// The subscription goes through `widget.sub`, so `detach` unsubscribes it.
|
|
115
|
+
expect(Number(input.value), 9);
|
|
116
|
+
expect(getWrapSettings().monomersPerRow, 17);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('a rejected value leaves the session value alone', async () => {
|
|
120
|
+
setMonomersPerRow(11);
|
|
121
|
+
const widget = getWrapWidthWidget();
|
|
122
|
+
try {
|
|
123
|
+
const input = widgetInput(widget);
|
|
124
|
+
input.value = '';
|
|
125
|
+
input.dispatchEvent(new Event('input', {bubbles: true}));
|
|
126
|
+
input.dispatchEvent(new Event('change', {bubbles: true}));
|
|
127
|
+
expect(getWrapSettings().monomersPerRow, 11);
|
|
128
|
+
} finally {
|
|
129
|
+
widget.detach();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {DEFAULT_MONOMERS_PER_ROW} from '../constants';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Read the `MonomersPerRow` package property into a usable row width.
|
|
5
|
+
*
|
|
6
|
+
* Datagrok hands an `int` property back as a number once it has round-tripped
|
|
7
|
+
* through the server, but a `defaultValue` straight out of package.json arrives
|
|
8
|
+
* as the string written there, and a package whose settings have not loaded yet
|
|
9
|
+
* gives `undefined`. All three have to end up at a sane integer — a `NaN` here
|
|
10
|
+
* would reach the layout and produce a drawing with no coordinates.
|
|
11
|
+
*
|
|
12
|
+
* @param {unknown} raw The raw property value.
|
|
13
|
+
* @return {number} A row width of at least 1, or {@link DEFAULT_MONOMERS_PER_ROW}.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveMonomersPerRow(raw: unknown): number {
|
|
16
|
+
const parsed = typeof raw === 'number' ? raw : Number.parseInt(String(raw ?? ''), 10);
|
|
17
|
+
return Number.isFinite(parsed) && parsed >= 1 ? Math.round(parsed) : DEFAULT_MONOMERS_PER_ROW;
|
|
18
|
+
}
|
|
@@ -265,7 +265,9 @@ export class HelmInput extends HelmInputBase {
|
|
|
265
265
|
try {
|
|
266
266
|
const webEditorValue = webEditorApp!.canvas!.getHelm(true)
|
|
267
267
|
.replace(/<\/span>/g, '').replace(/<span style='background:#bbf;'>/g, '');
|
|
268
|
-
|
|
268
|
+
// Through setStringValue, not just editor.setHelm — updating only the drawing
|
|
269
|
+
// left `value`/`stringValue` stale (a Flow Helm Input read back an empty value).
|
|
270
|
+
this.setStringValue(webEditorValue);
|
|
269
271
|
this.helmString = webEditorValue;
|
|
270
272
|
|
|
271
273
|
const selection = webEditorApp!.canvas!.helm!.jsd.m.atoms;
|
|
@@ -277,6 +279,7 @@ export class HelmInput extends HelmInputBase {
|
|
|
277
279
|
this.viewer.editor.m.atoms[i].highlighted = selection[i].selected;
|
|
278
280
|
}
|
|
279
281
|
this.viewer.editor.redraw();
|
|
282
|
+
this.fireChanged();
|
|
280
283
|
} catch (err: any) {
|
|
281
284
|
defaultErrorHandler(err);
|
|
282
285
|
} finally {
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
|
|
5
|
+
import {Subscription} from 'rxjs';
|
|
6
|
+
|
|
7
|
+
// The wrapping width is a process-wide setting inside hwe, read at layout time
|
|
8
|
+
// by every HelmService / editor / adapter (see @datagrok-libraries/hwe API.md
|
|
9
|
+
// §4.3). That is what lets one input here change both the grid cells and the
|
|
10
|
+
// editor without hunting down the several service instances the package owns.
|
|
11
|
+
import {getWrapSettings, setMonomersPerRow, onWrapSettingsChanged} from '@datagrok-libraries/hwe';
|
|
12
|
+
|
|
13
|
+
import {_package} from '../package';
|
|
14
|
+
|
|
15
|
+
/** Widest row the input allows. Well past any readable structure. */
|
|
16
|
+
const MAX_MONOMERS_PER_ROW = 100;
|
|
17
|
+
|
|
18
|
+
/** Repaint every open grid so HELM cells re-render at the new width. */
|
|
19
|
+
function invalidateHelmGrids(): void {
|
|
20
|
+
// hwe's own layout / render caches key on a setting version and have already
|
|
21
|
+
// missed by the time we get here, so a plain repaint is enough.
|
|
22
|
+
for (const view of grok.shell.tableViews) {
|
|
23
|
+
try {
|
|
24
|
+
view.grid?.invalidate();
|
|
25
|
+
} catch (_err) {
|
|
26
|
+
// A view can be closing while we iterate; a failed repaint is not worth
|
|
27
|
+
// failing the whole widget over.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Session-scoped control over where HELM structures break into a new line.
|
|
34
|
+
*
|
|
35
|
+
* Deliberately does NOT write the package property: the `MonomersPerRow`
|
|
36
|
+
* package setting stays the default that every session starts from, and this
|
|
37
|
+
* widget adjusts the current session only. "Reset" puts the session back onto
|
|
38
|
+
* that default rather than clearing it.
|
|
39
|
+
*
|
|
40
|
+
* @return {DG.Widget} The row-width editor.
|
|
41
|
+
*/
|
|
42
|
+
export function getWrapWidthWidget(): DG.Widget {
|
|
43
|
+
const input = ui.input.int('Monomers per row', {
|
|
44
|
+
value: getWrapSettings().monomersPerRow,
|
|
45
|
+
min: 1,
|
|
46
|
+
max: MAX_MONOMERS_PER_ROW,
|
|
47
|
+
tooltipText: 'Number of monomers drawn per row before the structure wraps. ' +
|
|
48
|
+
'Counts nucleotides for RNA/DNA. Applies to this session only. The default comes from the Helm package property',
|
|
49
|
+
onValueChanged: (value: number | null) => {
|
|
50
|
+
if (value === null || !Number.isFinite(value)) return;
|
|
51
|
+
setMonomersPerRow(value);
|
|
52
|
+
invalidateHelmGrids();
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const packageDefault = _package.defaultMonomersPerRow;
|
|
57
|
+
const resetLink = ui.link('Reset to package default', () => {
|
|
58
|
+
setMonomersPerRow(packageDefault);
|
|
59
|
+
invalidateHelmGrids();
|
|
60
|
+
}, `Back to ${packageDefault} — the MonomersPerRow package property`, {style: {marginTop: '4px'}});
|
|
61
|
+
|
|
62
|
+
const widget = new DG.Widget(ui.divV([input.root, resetLink]));
|
|
63
|
+
|
|
64
|
+
// Keep the input honest if the setting is changed from anywhere else (the
|
|
65
|
+
// editor, another widget, a script). `setMonomersPerRow` clamps and rounds,
|
|
66
|
+
// so this is also how the input reflects a value it did not produce itself.
|
|
67
|
+
const off = onWrapSettingsChanged((settings) => {
|
|
68
|
+
if (input.value !== settings.monomersPerRow) input.value = settings.monomersPerRow;
|
|
69
|
+
});
|
|
70
|
+
widget.sub(new Subscription(off));
|
|
71
|
+
|
|
72
|
+
return widget;
|
|
73
|
+
}
|