@datagrok/bio 2.11.29 → 2.11.30
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 +12 -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/files/tests/to-atomic-level-msa-fasta-input.csv +10 -0
- package/files/tests/to-atomic-level-msa-fasta-output.csv +2178 -0
- package/package.json +8 -8
- package/src/package.ts +11 -9
- package/src/tests/msa-tests.ts +2 -2
- package/src/tests/pepsea-tests.ts +1 -1
- package/src/tests/renderers-test.ts +16 -16
- package/src/tests/splitters-test.ts +7 -12
- package/src/tests/substructure-filters-tests.ts +277 -55
- package/src/tests/to-atomic-level-tests.ts +32 -22
- package/src/tests/utils/sequences-generators.ts +6 -3
- package/src/tests/utils.ts +6 -6
- package/src/utils/docker.ts +36 -0
- package/src/viewers/vd-regions-viewer.ts +4 -0
- package/src/viewers/web-logo-viewer.ts +4 -0
- package/src/widgets/bio-substructure-filter-helm.ts +168 -0
- package/src/widgets/bio-substructure-filter-types.ts +131 -0
- package/src/widgets/bio-substructure-filter.ts +215 -175
- package/src/widgets/composition-analysis-widget.ts +1 -1
|
@@ -9,43 +9,73 @@ import * as DG from 'datagrok-api/dg';
|
|
|
9
9
|
import * as grok from 'datagrok-api/grok';
|
|
10
10
|
|
|
11
11
|
import wu from 'wu';
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
12
|
+
import $ from 'cash-dom';
|
|
13
|
+
import {fromEvent, Observable, Subject, Subscription, Unsubscribable} from 'rxjs';
|
|
14
14
|
|
|
15
15
|
import {TAGS as bioTAGS, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
16
|
-
import {errInfo
|
|
17
|
-
import {delay} from '@datagrok-libraries/utils/src/test';
|
|
18
|
-
import {
|
|
16
|
+
import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
|
|
17
|
+
import {delay, testEvent} from '@datagrok-libraries/utils/src/test';
|
|
18
|
+
import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
19
|
+
import {IHelmWebEditor, IWebEditorApp} from '@datagrok-libraries/bio/src/helm/types';
|
|
20
|
+
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
21
|
+
import {IRenderer} from '@datagrok-libraries/bio/src/types/renderer';
|
|
22
|
+
import {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
|
|
23
|
+
import {PromiseSyncer} from '@datagrok-libraries/bio/src/utils/syncer';
|
|
19
24
|
|
|
20
25
|
import {helmSubstructureSearch, linearSubstructureSearch} from '../substructure-search/substructure-search';
|
|
21
26
|
import {updateDivInnerHTML} from '../utils/ui-utils';
|
|
27
|
+
import {BioFilterBase, BioFilterProps, IBioFilter, IFilterProps} from './bio-substructure-filter-types';
|
|
28
|
+
import {HelmBioFilter} from './bio-substructure-filter-helm';
|
|
22
29
|
|
|
23
30
|
import {_package} from '../package';
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
const FILTER_SYNC_EVENT: string = 'bio-substructure-filter';
|
|
33
|
+
|
|
34
|
+
class FilterState {
|
|
35
|
+
constructor(
|
|
36
|
+
public readonly props: IFilterProps,
|
|
37
|
+
public readonly filterId: number,
|
|
38
|
+
public readonly dataFrameId: string,
|
|
39
|
+
public readonly columnName: string,
|
|
40
|
+
public readonly bitset: DG.BitSet | null,
|
|
41
|
+
) {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class SeparatorFilterProps extends BioFilterProps {
|
|
45
|
+
constructor(
|
|
46
|
+
substructure: string,
|
|
47
|
+
public separator?: string
|
|
48
|
+
) {
|
|
49
|
+
super(substructure);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class BioSubstructureFilter extends DG.Filter implements IRenderer {
|
|
54
|
+
bioFilter: IBioFilter | null = null;
|
|
27
55
|
bitset: DG.BitSet | null = null;
|
|
28
|
-
loader: HTMLDivElement
|
|
29
|
-
onBioFilterChangedSubs?: Subscription;
|
|
56
|
+
readonly loader: HTMLDivElement;
|
|
30
57
|
notation: string | undefined = undefined;
|
|
31
58
|
|
|
59
|
+
readonly logger: ILogger;
|
|
60
|
+
readonly filterSyncer: PromiseSyncer;
|
|
61
|
+
|
|
32
62
|
get calculating(): boolean { return this.loader.style.display == 'initial'; }
|
|
33
63
|
|
|
34
64
|
set calculating(value: boolean) { this.loader.style.display = value ? 'initial' : 'none'; }
|
|
35
65
|
|
|
36
66
|
get filterSummary(): string {
|
|
37
|
-
return this.bioFilter!.
|
|
67
|
+
return this.bioFilter!.filterSummary;
|
|
38
68
|
}
|
|
39
69
|
|
|
40
70
|
get isFiltering(): boolean {
|
|
41
|
-
return super.isFiltering && this.bioFilter
|
|
71
|
+
return super.isFiltering && (this.bioFilter?.isFiltering ?? false);
|
|
42
72
|
}
|
|
43
73
|
|
|
44
74
|
get isReadyToApplyFilter(): boolean {
|
|
45
75
|
return !this.calculating && this.bitset != null;
|
|
46
76
|
}
|
|
47
77
|
|
|
48
|
-
get
|
|
78
|
+
get debounceTime(): number {
|
|
49
79
|
if (this.column == null)
|
|
50
80
|
return 1000;
|
|
51
81
|
const length = this.column.length;
|
|
@@ -54,44 +84,85 @@ export class BioSubstructureFilter extends DG.Filter {
|
|
|
54
84
|
const msecMax = 1000;
|
|
55
85
|
if (length < minLength) return 0;
|
|
56
86
|
if (length > maxLength) return msecMax;
|
|
57
|
-
|
|
87
|
+
const res = Math.floor(msecMax * ((length - minLength) / (maxLength - minLength)));
|
|
88
|
+
return res;
|
|
58
89
|
}
|
|
59
90
|
|
|
60
|
-
//column name setter overload
|
|
61
|
-
|
|
62
91
|
constructor() {
|
|
63
92
|
super();
|
|
64
93
|
this.root = ui.divV([]);
|
|
94
|
+
this.loader = ui.loader();
|
|
65
95
|
this.calculating = false;
|
|
96
|
+
this.filterSyncer = new PromiseSyncer(this.logger = _package.logger);
|
|
97
|
+
|
|
98
|
+
return new Proxy(this, {
|
|
99
|
+
set(target: any, key, value) {
|
|
100
|
+
if (key === 'column') {
|
|
101
|
+
const k = 42;
|
|
102
|
+
}
|
|
103
|
+
target[key] = value;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
66
107
|
}
|
|
67
108
|
|
|
109
|
+
private static filterCounter: number = -1;
|
|
110
|
+
private readonly filterId: number = ++BioSubstructureFilter.filterCounter;
|
|
111
|
+
|
|
112
|
+
private filterToLog(): string { return `BioSubstructureFilter<${this.filterId}>`; }
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
private viewSubs: Unsubscribable[] = [];
|
|
116
|
+
|
|
68
117
|
attach(dataFrame: DG.DataFrame): void {
|
|
69
|
-
super.attach(
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
118
|
+
const superAttach = super.attach.bind(this);
|
|
119
|
+
const logPrefix = `${this.filterToLog()}.attach()`;
|
|
120
|
+
this.filterSyncer.sync(logPrefix, async () => {
|
|
121
|
+
superAttach(dataFrame);
|
|
122
|
+
this.column = dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
|
|
123
|
+
const uh = UnitsHandler.getOrCreate(this.column!);
|
|
124
|
+
this.columnName ??= this.column?.name;
|
|
125
|
+
this.notation ??= this.column?.getTag(DG.TAGS.UNITS);
|
|
126
|
+
|
|
127
|
+
this.bioFilter = this.notation === NOTATION.FASTA ?
|
|
128
|
+
new FastaBioFilter() : this.notation === NOTATION.SEPARATOR ?
|
|
129
|
+
new SeparatorBioFilter(this.column!.getTag(bioTAGS.separator)) : new HelmBioFilter();
|
|
130
|
+
this.root.appendChild(this.bioFilter!.filterPanel);
|
|
131
|
+
this.root.appendChild(this.loader);
|
|
132
|
+
await this.bioFilter.attach(); // may await waitForElementInDom
|
|
133
|
+
|
|
134
|
+
this.viewSubs.push(DG.debounce(this.bioFilter!.onChanged, this.debounceTime)
|
|
135
|
+
.subscribe(this.bioFilterOnChangedDebounced.bind(this)));
|
|
136
|
+
this.viewSubs.push(grok.events.onResetFilterRequest
|
|
137
|
+
.subscribe((_value: any) => { this.bioFilter?.resetFilter(); }));
|
|
138
|
+
this.viewSubs.push(grok.events.onCustomEvent(FILTER_SYNC_EVENT)
|
|
139
|
+
.subscribe(this.filterOnSync.bind(this)));
|
|
140
|
+
});
|
|
88
141
|
}
|
|
89
142
|
|
|
90
143
|
detach() {
|
|
91
|
-
|
|
92
|
-
|
|
144
|
+
const superDetach = super.detach.bind(this);
|
|
145
|
+
const logPrefix = `${this.filterToLog()}.detach()`;
|
|
146
|
+
this.filterSyncer.sync(logPrefix, async () => {
|
|
147
|
+
for (const sub of this.viewSubs) sub.unsubscribe();
|
|
148
|
+
this.viewSubs = [];
|
|
149
|
+
superDetach(); // requests this.isFiltering
|
|
150
|
+
if (this.bioFilter) this.bioFilter.detach();
|
|
151
|
+
this.bioFilter = null;
|
|
152
|
+
});
|
|
93
153
|
}
|
|
94
154
|
|
|
155
|
+
// -- Sync -
|
|
156
|
+
|
|
157
|
+
private filterOnSync(state: FilterState): void {
|
|
158
|
+
if (state.filterId === this.filterId) return;
|
|
159
|
+
if (state.dataFrameId !== this.dataFrame!.id || state.columnName !== this.columnName) return;
|
|
160
|
+
|
|
161
|
+
this.bioFilter!.props = state.props;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// -- Layout --
|
|
165
|
+
|
|
95
166
|
applyFilter(): void {
|
|
96
167
|
if (this.bitset && !this.isDetached)
|
|
97
168
|
this.dataFrame?.filter.and(this.bitset);
|
|
@@ -102,7 +173,7 @@ export class BioSubstructureFilter extends DG.Filter {
|
|
|
102
173
|
*/
|
|
103
174
|
saveState(): any {
|
|
104
175
|
const state = super.saveState();
|
|
105
|
-
state.
|
|
176
|
+
state.props = this.bioFilter!.props.save();
|
|
106
177
|
return state;
|
|
107
178
|
}
|
|
108
179
|
|
|
@@ -111,105 +182,165 @@ export class BioSubstructureFilter extends DG.Filter {
|
|
|
111
182
|
*/
|
|
112
183
|
applyState(state: any): void {
|
|
113
184
|
super.applyState(state); //column, columnName
|
|
114
|
-
if (state.
|
|
115
|
-
this.bioFilter!.
|
|
185
|
+
if (state.props)
|
|
186
|
+
this.bioFilter!.props.apply(state.props);
|
|
187
|
+
|
|
188
|
+
// if (state.bioSubstructure) {
|
|
189
|
+
// // (async () => { await this.bioFilterOnChangedDebounced(); })();
|
|
190
|
+
// this.bioFilter!.substructure = state.bioSubstructure;
|
|
191
|
+
// }
|
|
192
|
+
}
|
|
116
193
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
194
|
+
private fireFilterSync(): void {
|
|
195
|
+
const logPrefix = `${this.filterToLog()}.fireFilterSync()`;
|
|
196
|
+
_package.logger.debug(`${logPrefix}, ` +
|
|
197
|
+
`bioFilter = ${!!this.bioFilter ? this.bioFilter.constructor.name : 'null'}` +
|
|
198
|
+
(!!this.bioFilter ? `, props = ${JSON.stringify(this.bioFilter!.props.save())}` : ''));
|
|
199
|
+
|
|
200
|
+
grok.events.fireCustomEvent(FILTER_SYNC_EVENT, new FilterState(
|
|
201
|
+
this.bioFilter!.props, this.filterId, this.dataFrame!.id, this.columnName!, this.bitset));
|
|
120
202
|
}
|
|
121
203
|
|
|
204
|
+
// -- Handle events
|
|
205
|
+
|
|
122
206
|
/**
|
|
123
207
|
* Performs the actual filtering
|
|
124
208
|
* When the results are ready, triggers `rows.requestFilter`, which in turn triggers `applyFilter`
|
|
125
209
|
* that would simply apply the bitset synchronously.
|
|
126
210
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
211
|
+
bioFilterOnChangedDebounced(): void {
|
|
212
|
+
if (!this.dataFrame) return; // Debounced event can be handled postponed
|
|
213
|
+
const logPrefix = `${this.filterToLog()}.bioFilterOnChangedDebounced()`;
|
|
214
|
+
_package.logger.debug(`${logPrefix}, start, ` +
|
|
215
|
+
`isFiltering = ${this.isFiltering}, ` +
|
|
216
|
+
`props = ${JSON.stringify(this.bioFilter!.props.save())}`);
|
|
217
|
+
|
|
129
218
|
if (!this.isFiltering) {
|
|
130
219
|
this.bitset = null;
|
|
131
|
-
this.dataFrame
|
|
132
|
-
|
|
133
|
-
|
|
220
|
+
this.dataFrame!.rows.requestFilter();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// some other filter is already filtering for the exact same thing
|
|
225
|
+
if (wu(this.dataFrame!.rows.filters).has(`${this.columnName}: ${this.filterSummary}`))
|
|
134
226
|
return;
|
|
135
|
-
|
|
227
|
+
|
|
228
|
+
this.filterSyncer.sync(logPrefix, async () => {
|
|
136
229
|
this.calculating = true;
|
|
137
230
|
try {
|
|
231
|
+
_package.logger.debug(`${logPrefix}, before substructureSearch`);
|
|
138
232
|
this.bitset = await this.bioFilter?.substructureSearch(this.column!)!;
|
|
233
|
+
_package.logger.debug(`${logPrefix}, after substructureSearch`);
|
|
139
234
|
this.calculating = false;
|
|
235
|
+
this.fireFilterSync();
|
|
140
236
|
this.dataFrame?.rows.requestFilter();
|
|
141
237
|
} finally {
|
|
142
238
|
this.calculating = false;
|
|
239
|
+
_package.logger.debug(`${logPrefix}, end`);
|
|
143
240
|
}
|
|
144
|
-
}
|
|
241
|
+
});
|
|
145
242
|
}
|
|
146
|
-
}
|
|
147
243
|
|
|
148
|
-
|
|
149
|
-
onChanged: Subject<any> = new Subject<any>();
|
|
244
|
+
// -- IRenderer --
|
|
150
245
|
|
|
151
|
-
|
|
152
|
-
return new HTMLElement();
|
|
153
|
-
}
|
|
246
|
+
private _onRendered = new Subject<void>();
|
|
154
247
|
|
|
155
|
-
|
|
156
|
-
abstract set substructure(s: string);
|
|
248
|
+
get onRendered(): Observable<void> { return this._onRendered; }
|
|
157
249
|
|
|
158
|
-
|
|
159
|
-
|
|
250
|
+
invalidate(caller?: string): void {
|
|
251
|
+
const logPrefix = `${this.filterToLog()}.invalidate(${caller ? ` <- ${caller} ` : ''})`;
|
|
252
|
+
this.filterSyncer.sync(logPrefix, async () => { this._onRendered.next(); });
|
|
160
253
|
}
|
|
161
254
|
|
|
162
|
-
|
|
255
|
+
async awaitRendered(timeout: number = 10000): Promise<void> {
|
|
256
|
+
const callLog = `awaitRendered( ${timeout} )`;
|
|
257
|
+
const logPrefix = `${this.filterToLog()}.${callLog}`;
|
|
258
|
+
await delay(0);
|
|
259
|
+
await testEvent(this.onRendered, () => {
|
|
260
|
+
this.logger.debug(`${logPrefix}, ` + '_onRendered event caught');
|
|
261
|
+
}, () => {
|
|
262
|
+
this.invalidate(callLog);
|
|
263
|
+
}, timeout, `${logPrefix} ${timeout} timeout`);
|
|
163
264
|
|
|
164
|
-
|
|
265
|
+
// Rethrow stored syncer error (for test purposes)
|
|
266
|
+
const viewErrors = this.filterSyncer.resetErrors();
|
|
267
|
+
if (viewErrors.length > 0) throw viewErrors[0];
|
|
268
|
+
}
|
|
165
269
|
}
|
|
166
270
|
|
|
167
|
-
class
|
|
271
|
+
export class FastaBioFilter extends BioFilterBase<BioFilterProps> {
|
|
272
|
+
readonly emptyProps = new BioFilterProps('');
|
|
273
|
+
|
|
168
274
|
readonly substructureInput: DG.InputBase<string>;
|
|
169
275
|
|
|
276
|
+
get type(): string { return 'FastaBioFilter'; }
|
|
277
|
+
|
|
170
278
|
constructor() {
|
|
171
279
|
super();
|
|
172
280
|
|
|
173
281
|
this.substructureInput = ui.stringInput('', '', () => {
|
|
174
|
-
this.
|
|
282
|
+
this.props.substructure = this.substructureInput.value;
|
|
283
|
+
if (!this._propsChanging) this.onChanged.next();
|
|
175
284
|
}, {placeholder: 'Substructure'});
|
|
176
285
|
}
|
|
177
286
|
|
|
178
|
-
|
|
179
|
-
|
|
287
|
+
public applyProps() {
|
|
288
|
+
this.substructureInput.value = this.props.substructure;
|
|
180
289
|
}
|
|
181
290
|
|
|
182
|
-
get
|
|
183
|
-
return this.substructureInput.
|
|
291
|
+
get filterPanel() {
|
|
292
|
+
return this.substructureInput.root;
|
|
184
293
|
}
|
|
185
294
|
|
|
186
|
-
|
|
187
|
-
this.substructureInput.value = s;
|
|
188
|
-
}
|
|
295
|
+
get isFiltering(): boolean { return this.substructureInput.value !== ''; }
|
|
189
296
|
|
|
190
297
|
async substructureSearch(column: DG.Column): Promise<DG.BitSet | null> {
|
|
191
|
-
return linearSubstructureSearch(this.substructure, column);
|
|
298
|
+
return linearSubstructureSearch(this.props.substructure, column);
|
|
192
299
|
}
|
|
193
300
|
|
|
194
|
-
|
|
195
|
-
this.substructureInput.value = '';
|
|
196
|
-
}
|
|
301
|
+
async attach(): Promise<void> {}
|
|
197
302
|
|
|
198
|
-
detach(): void {
|
|
303
|
+
async detach(): Promise<void> {
|
|
304
|
+
await super.detach();
|
|
305
|
+
}
|
|
199
306
|
}
|
|
200
307
|
|
|
201
|
-
export class
|
|
308
|
+
export class SeparatorBioFilter extends BioFilterBase<SeparatorFilterProps> {
|
|
309
|
+
readonly emptyProps = new SeparatorFilterProps('');
|
|
310
|
+
|
|
311
|
+
readonly substructureInput: DG.InputBase<string>;
|
|
202
312
|
readonly separatorInput: DG.InputBase<string>;
|
|
203
313
|
colSeparator = '';
|
|
204
314
|
|
|
205
|
-
|
|
315
|
+
get type(): string { return 'SeparatorBioFilter'; }
|
|
316
|
+
|
|
317
|
+
constructor(colSeparator: string) {
|
|
206
318
|
super();
|
|
207
319
|
|
|
208
|
-
this.
|
|
209
|
-
this.
|
|
320
|
+
this.substructureInput = ui.stringInput('', '', () => {
|
|
321
|
+
this.props.substructure = this.substructureInput.value;
|
|
322
|
+
if (!this._propsChanging) this.onChanged.next();
|
|
323
|
+
}, {placeholder: 'Substructure'});
|
|
324
|
+
this.separatorInput = ui.stringInput('', this.colSeparator = colSeparator, () => {
|
|
325
|
+
this.props.separator = !!this.separatorInput.value ? this.separatorInput.value : undefined;
|
|
326
|
+
if (!this._propsChanging) this.onChanged.next();
|
|
210
327
|
}, {placeholder: 'Separator'});
|
|
211
|
-
|
|
212
|
-
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
applyProps(): void {
|
|
331
|
+
this.substructureInput.value = this.props.substructure;
|
|
332
|
+
this.separatorInput.value = this.props.separator ?? this.colSeparator;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
get filterSummary(): string {
|
|
336
|
+
const sep: string = this.props.separator ? this.props.separator : this.colSeparator;
|
|
337
|
+
return `${this.props.substructure}, {sep}`;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
get isFiltering(): boolean { return this.props.substructure !== ''; };
|
|
341
|
+
|
|
342
|
+
resetFilter(): void {
|
|
343
|
+
this.props = new SeparatorFilterProps('');
|
|
213
344
|
}
|
|
214
345
|
|
|
215
346
|
get filterPanel() {
|
|
@@ -233,100 +364,9 @@ export class SeparatorFilter extends FastaFilter {
|
|
|
233
364
|
return linearSubstructureSearch(this.substructure, column, this.colSeparator);
|
|
234
365
|
}
|
|
235
366
|
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export class HelmFilter extends BioFilterBase {
|
|
240
|
-
helmEditor: IHelmWebEditor;
|
|
241
|
-
_filterPanel = ui.div('', {style: {cursor: 'pointer'}});
|
|
242
|
-
helmSubstructure = '';
|
|
243
|
-
|
|
244
|
-
constructor() {
|
|
245
|
-
super();
|
|
246
|
-
this.init();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
viewSubs: Unsubscribable[] = [];
|
|
250
|
-
|
|
251
|
-
async init() {
|
|
252
|
-
this.helmEditor = await grok.functions.call('Helm:helmWebEditor');
|
|
253
|
-
await ui.tools.waitForElementInDom(this._filterPanel);
|
|
254
|
-
this.updateFilterPanel();
|
|
255
|
-
let editorDiv: HTMLDivElement | undefined;
|
|
256
|
-
let webEditor: any | undefined;
|
|
257
|
-
// TODO: Unsubscribe 'click' and 'sizeChanged'
|
|
258
|
-
this.viewSubs.push(fromEvent(this._filterPanel, 'click').subscribe(() => {
|
|
259
|
-
({editorDiv, webEditor} = this.helmEditor.createWebEditor(this.helmSubstructure));
|
|
260
|
-
const dlg = ui.dialog({showHeader: false, showFooter: true})
|
|
261
|
-
.add(editorDiv)
|
|
262
|
-
.onOK(() => {
|
|
263
|
-
const helmString = webEditor.canvas.getHelm(true)
|
|
264
|
-
.replace(/<\/span>/g, '').replace(/<span style='background:#bbf;'>/g, '');
|
|
265
|
-
this.helmSubstructure = helmString;
|
|
266
|
-
this.updateFilterPanel(this.substructure);
|
|
267
|
-
setTimeout(() => { this.onChanged.next(); }, 10);
|
|
268
|
-
}).show({modal: true, fullScreen: true});
|
|
269
|
-
const onCloseSub = dlg.onClose.subscribe(() => {
|
|
270
|
-
onCloseSub.unsubscribe();
|
|
271
|
-
editorDiv = undefined;
|
|
272
|
-
webEditor = undefined;
|
|
273
|
-
});
|
|
274
|
-
}));
|
|
275
|
-
this.viewSubs.push(ui.onSizeChanged(this._filterPanel).subscribe((_) => {
|
|
276
|
-
try {
|
|
277
|
-
if (!!webEditor) {
|
|
278
|
-
const helmString = webEditor.canvas.getHelm(true)
|
|
279
|
-
.replace(/<\/span>/g, '').replace(/<span style='background:#bbf;'>/g, '');
|
|
280
|
-
this.updateFilterPanel(helmString);
|
|
281
|
-
}
|
|
282
|
-
} catch (err: any) {
|
|
283
|
-
const [errMsg, errStack] = errInfo(err);
|
|
284
|
-
_package.logger.error(errMsg, undefined, errStack);
|
|
285
|
-
}
|
|
286
|
-
}));
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
get filterPanel() {
|
|
290
|
-
return this._filterPanel;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
get substructure() {
|
|
294
|
-
return this.helmSubstructure;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
set substructure(s: string) {
|
|
298
|
-
this.helmEditor.editor.setHelm(s);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
updateFilterPanel(helmString?: string) {
|
|
302
|
-
const width = this._filterPanel.parentElement!.clientWidth < 100 ? 100 :
|
|
303
|
-
this._filterPanel.parentElement!.clientWidth;
|
|
304
|
-
const height = width / 2;
|
|
305
|
-
if (!helmString) {
|
|
306
|
-
const editDiv = ui.divText('Click to edit', 'helm-substructure-filter');
|
|
307
|
-
updateDivInnerHTML(this._filterPanel, editDiv);
|
|
308
|
-
} else {
|
|
309
|
-
updateDivInnerHTML(this._filterPanel, this.helmEditor.host);
|
|
310
|
-
this.helmEditor.editor.setHelm(helmString);
|
|
311
|
-
this.helmEditor.resizeEditor(width, height);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async substructureSearch(column: DG.Column): Promise<DG.BitSet | null> {
|
|
316
|
-
ui.setUpdateIndicator(this._filterPanel, true);
|
|
317
|
-
await delay(10);
|
|
318
|
-
const res = await helmSubstructureSearch(this.substructure, column);
|
|
319
|
-
ui.setUpdateIndicator(this._filterPanel, false);
|
|
320
|
-
return res;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
resetFilter(): void {
|
|
324
|
-
console.debug('Bio: HelmFilter.resetFilter()');
|
|
325
|
-
this.helmSubstructure = '';
|
|
326
|
-
this.updateFilterPanel(this.substructure);
|
|
327
|
-
}
|
|
367
|
+
async attach(): Promise<void> {}
|
|
328
368
|
|
|
329
|
-
detach(): void {
|
|
330
|
-
|
|
369
|
+
async detach(): Promise<void> {
|
|
370
|
+
await super.detach();
|
|
331
371
|
}
|
|
332
372
|
}
|
|
@@ -11,7 +11,7 @@ import '../../css/composition-analysis.css';
|
|
|
11
11
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
export function getCompositionAnalysisWidget(val: DG.SemanticValue) {
|
|
14
|
+
export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
|
|
15
15
|
const host = ui.div();
|
|
16
16
|
host.classList.add('macromolecule-cell-comp-analysis-host');
|
|
17
17
|
const alphabet = val.cell.column.tags[bioTAGS.alphabet];
|