@jbrowse/core 5.0.0-beta.1 → 5.0.0-beta.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.
- package/README.md +42 -3
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/test_data/NCDN.d.ts +2 -2
- package/esm/PluginLoader.d.ts +1 -0
- package/esm/PluginLoader.js +52 -8
- package/esm/PluginManager.d.ts +70 -17
- package/esm/PluginManager.js +151 -20
- package/esm/ReExports/modules.js +3 -53
- package/esm/ReExports/publicUtil.d.ts +1 -1
- package/esm/ReExports/publicUtil.js +1 -1
- package/esm/ReExports/sharedModules.d.ts +96 -0
- package/esm/ReExports/sharedModules.js +59 -0
- package/esm/ReExports/uiStub.d.ts +1 -0
- package/esm/ReExports/uiStub.js +13 -0
- package/esm/ReExports/workerModules.d.ts +2 -0
- package/esm/ReExports/workerModules.js +10 -0
- package/esm/configuration/configurationSchema.d.ts +5 -38
- package/esm/configuration/configurationSchema.js +53 -40
- package/esm/configuration/promotableDefaults.js +12 -6
- package/esm/configuration/promotableResolve.d.ts +1 -1
- package/esm/configuration/promotableResolve.js +1 -1
- package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.d.ts +12 -0
- package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +39 -0
- package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +29 -0
- package/esm/data_adapters/BaseAdapter/featureDensity.js +17 -0
- package/esm/data_adapters/BaseAdapter/index.d.ts +1 -0
- package/esm/data_adapters/BaseAdapter/index.js +1 -0
- package/esm/data_adapters/BaseAdapter/stats.d.ts +33 -1
- package/esm/data_adapters/BaseAdapter/stats.js +52 -13
- package/esm/data_adapters/BaseAdapter/types.d.ts +1 -0
- package/esm/pluggableElementTypes/DisplayType.d.ts +9 -10
- package/esm/pluggableElementTypes/DisplayType.js +43 -8
- package/esm/pluggableElementTypes/ViewType.d.ts +25 -2
- package/esm/pluggableElementTypes/ViewType.js +75 -2
- package/esm/pluggableElementTypes/extendElementType.js +8 -3
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +41 -0
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +74 -1
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +21 -0
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +37 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +49 -0
- package/esm/pluggableElementTypes/models/BaseViewModel.js +39 -0
- package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +10 -7
- package/esm/pluggableElementTypes/models/assertDisplayContract.js +23 -30
- package/esm/rpc/RpcRegistry.d.ts +10 -0
- package/esm/rpc/byteBudget.d.ts +9 -0
- package/esm/rpc/coreRpcMethods.d.ts +1 -0
- package/esm/rpc/coreRpcMethods.js +1 -0
- package/esm/rpc/methods/CoreGetFeatureDensity.d.ts +11 -0
- package/esm/rpc/methods/CoreGetFeatureDensity.js +22 -0
- package/esm/svg/svgReady.d.ts +1 -0
- package/esm/ui/CascadingMenu.js +30 -8
- package/esm/ui/LoadingOverlay.d.ts +14 -0
- package/esm/ui/LoadingOverlay.js +1 -1
- package/esm/ui/MenuTypes.d.ts +11 -2
- package/esm/ui/MenuTypes.js +3 -0
- package/esm/ui/PaletteContext.d.ts +11 -4
- package/esm/ui/PaletteContext.js +34 -5
- package/esm/ui/PinAdornment.js +12 -6
- package/esm/ui/index.d.ts +1 -1
- package/esm/ui/index.js +1 -1
- package/esm/ui/launchViewMenu.js +7 -1
- package/esm/ui/legendMenuItem.d.ts +5 -5
- package/esm/ui/legendMenuItem.js +5 -5
- package/esm/ui/makeSizeMenu.js +1 -1
- package/esm/ui/menuItems.d.ts +1 -1
- package/esm/ui/menuItems.js +1 -1
- package/esm/ui/promotableMenuItems.js +8 -6
- package/esm/ui/promotablePinCoverage.js +2 -1
- package/esm/ui/toggleMenuItems.d.ts +1 -3
- package/esm/util/addAndShowTrack.d.ts +1 -1
- package/esm/util/addAndShowTrack.js +5 -1
- package/esm/util/addTrackFromWidget.js +2 -2
- package/esm/util/cacheBudgets.d.ts +22 -19
- package/esm/util/cacheBudgets.js +22 -19
- package/esm/util/createStopTokenRotation.d.ts +1 -0
- package/esm/util/createStopTokenRotation.js +7 -27
- package/esm/util/fetchContext.d.ts +2 -2
- package/esm/util/fetchContext.js +2 -2
- package/esm/util/fetchPhases.d.ts +9 -9
- package/esm/util/hooks.d.ts +8 -0
- package/esm/util/hooks.js +27 -0
- package/esm/util/index.d.ts +2 -3
- package/esm/util/index.js +2 -3
- package/esm/util/installFetch.d.ts +49 -23
- package/esm/util/installFetch.js +49 -21
- package/esm/util/installInitAutorun.d.ts +8 -8
- package/esm/util/installInitAutorun.js +15 -15
- package/esm/util/lazyAfterAttach.d.ts +13 -0
- package/esm/util/lazyAfterAttach.js +31 -0
- package/esm/util/legendCandidates.d.ts +50 -0
- package/esm/util/legendCandidates.js +87 -0
- package/esm/util/mstUtils.d.ts +12 -1
- package/esm/util/mstUtils.js +20 -2
- package/esm/util/pluginStore.d.ts +1 -0
- package/esm/util/pluginStore.js +8 -1
- package/esm/util/range.d.ts +13 -9
- package/esm/util/range.js +17 -8
- package/esm/util/rxjs.d.ts +16 -0
- package/esm/util/rxjs.js +49 -0
- package/esm/util/tabix.d.ts +10 -4
- package/esm/util/tabix.js +11 -5
- package/esm/util/tracks.d.ts +48 -0
- package/esm/util/tracks.js +192 -66
- package/esm/util/types/index.d.ts +48 -5
- package/esm/util/types/index.js +3 -0
- package/esm/util/unknownSnapshotKeys.d.ts +36 -0
- package/esm/util/unknownSnapshotKeys.js +69 -0
- package/esm/util/viewStatus.d.ts +23 -0
- package/esm/util/viewStatus.js +17 -0
- package/esm/util/whenViewSettled.d.ts +17 -4
- package/esm/util/whenViewSettled.js +19 -6
- package/esm/util/withLaunchInput.d.ts +123 -0
- package/esm/util/withLaunchInput.js +228 -0
- package/package.json +12 -9
- package/esm/util/applyInitSettings.d.ts +0 -79
- package/esm/util/applyInitSettings.js +0 -91
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hasParent, isAlive, types } from '@jbrowse/mobx-state-tree';
|
|
2
|
-
import { getConf } from '../../configuration/index.js';
|
|
2
|
+
import { getConf, isConfigurationSlot, preProcessSlotValues, } from '../../configuration/index.js';
|
|
3
3
|
import { getContainingTrack, getEnv, statusFraction, statusMessageText, } from '../../util/index.js';
|
|
4
4
|
import { ElementId } from '../../util/types/mst.js';
|
|
5
5
|
/**
|
|
@@ -210,6 +210,79 @@ function stateModelFactory() {
|
|
|
210
210
|
* base display reload does nothing, see specialized displays for details
|
|
211
211
|
*/
|
|
212
212
|
reload() { },
|
|
213
|
+
/**
|
|
214
|
+
* #action
|
|
215
|
+
* Apply a bag of display settings to the LIVE display, and report what
|
|
216
|
+
* landed. Each key runs through the display config schema's
|
|
217
|
+
* `preProcessSnapshot` (shorthand expansions, legacy-key migrations —
|
|
218
|
+
* the same lowering a session spec's inline track keys get in
|
|
219
|
+
* `showTrackGeneric`), then writes the matching config slot. Keys that
|
|
220
|
+
* are not slots come back in `unapplied` rather than vanishing: the
|
|
221
|
+
* settings vocabulary's historical failure mode is the silently dropped
|
|
222
|
+
* key.
|
|
223
|
+
*
|
|
224
|
+
* `allowSetters` additionally routes a non-slot key to a conventionally
|
|
225
|
+
* named single-argument `set<Key>` action. Opt-in, never the default:
|
|
226
|
+
* the declarative surfaces (session specs, share links, embeds) feed
|
|
227
|
+
* this whole bags of untyped JSON, and a blanket fallback would let
|
|
228
|
+
* them reach internal setters (`setError`, `setScrollTop`, ...) and
|
|
229
|
+
* call multi-argument setters with one argument. A caller that wants a
|
|
230
|
+
* specific action can also simply call it.
|
|
231
|
+
*
|
|
232
|
+
* A key whose write THREW is reported separately, in `failed` — it is the
|
|
233
|
+
* only one of the three that means the caller got something wrong, and
|
|
234
|
+
* the only one worth a notification. `unapplied` is not: at the
|
|
235
|
+
* `showTrackGeneric` call site it also collects keys that surface
|
|
236
|
+
* consumed itself (`type`) and MST display props the display snapshot
|
|
237
|
+
* already applied (`resolution`), so treating it as "dropped" would
|
|
238
|
+
* report a correct call as broken.
|
|
239
|
+
*
|
|
240
|
+
* Per-key errors do not abort the rest of the bag — a caller
|
|
241
|
+
* mid-`showTrack` has already pushed the track, and one rejected value
|
|
242
|
+
* must not strand a half-configured track.
|
|
243
|
+
*/
|
|
244
|
+
applyDisplaySettings(settings, options) {
|
|
245
|
+
// configuration is the reference every concrete display adds at
|
|
246
|
+
// instantiation (see DisplayModel below); the base model composes
|
|
247
|
+
// before it exists, hence the cast rather than a prop
|
|
248
|
+
const { configuration } = self;
|
|
249
|
+
const applied = [];
|
|
250
|
+
const unapplied = [];
|
|
251
|
+
const failed = [];
|
|
252
|
+
const slots = preProcessSlotValues(configuration, settings);
|
|
253
|
+
for (const [key, value] of Object.entries(slots)) {
|
|
254
|
+
if (!key || key === 'type') {
|
|
255
|
+
unapplied.push(key ? 'type (switch the display type instead)' : '(empty key)');
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
if (isConfigurationSlot(configuration, key)) {
|
|
260
|
+
// the key arrives from runtime JSON; setConf's slot name is a
|
|
261
|
+
// compile-time type
|
|
262
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
263
|
+
configuration.setSlot(key, value);
|
|
264
|
+
applied.push(key);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const setter = self[`set${key[0].toUpperCase()}${key.slice(1)}`];
|
|
268
|
+
if (typeof setter !== 'function') {
|
|
269
|
+
unapplied.push(key);
|
|
270
|
+
}
|
|
271
|
+
else if (options?.allowSetters) {
|
|
272
|
+
;
|
|
273
|
+
setter(value);
|
|
274
|
+
applied.push(`${key} (via setter)`);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
unapplied.push(`${key} (not a config slot; a set${key[0].toUpperCase()}${key.slice(1)} action exists — call it, or pass { allowSetters: true })`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
failed.push({ key, error: `${e}` });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { applied, unapplied, failed };
|
|
285
|
+
},
|
|
213
286
|
}));
|
|
214
287
|
}
|
|
215
288
|
export const BaseDisplay = stateModelFactory();
|
|
@@ -144,6 +144,27 @@ export declare function createBaseTrackModel(pm: PluginManager, trackType: strin
|
|
|
144
144
|
* #action
|
|
145
145
|
*/
|
|
146
146
|
setResizing(flag: boolean): void;
|
|
147
|
+
/**
|
|
148
|
+
* #action
|
|
149
|
+
* `applyDisplaySettings` on the display being drawn (`activeDisplay`,
|
|
150
|
+
* which a shown track always has — this is not meaningful on a bare
|
|
151
|
+
* config node) — the track-level entry for "restyle this track in
|
|
152
|
+
* place" (a session spec's inline keys, an agent's settings bag). Only
|
|
153
|
+
* that one display: settings vocabularies are per display type, so
|
|
154
|
+
* broadcasting one bag across a track's other displays would mis-route
|
|
155
|
+
* keys; address a non-active display directly if that is what you mean.
|
|
156
|
+
* See BaseDisplayModel for the routing and the `allowSetters` opt-in.
|
|
157
|
+
*/
|
|
158
|
+
applyDisplaySettings(settings: Record<string, unknown>, options?: {
|
|
159
|
+
allowSetters?: boolean;
|
|
160
|
+
}): {
|
|
161
|
+
applied: string[];
|
|
162
|
+
unapplied: string[];
|
|
163
|
+
failed: {
|
|
164
|
+
key: string;
|
|
165
|
+
error: string;
|
|
166
|
+
}[];
|
|
167
|
+
};
|
|
147
168
|
/**
|
|
148
169
|
* #action
|
|
149
170
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lazy } from 'react';
|
|
2
|
-
import { addDisposer, getSnapshot, types } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import { addDisposer, getSnapshot, isAlive, types, } from '@jbrowse/mobx-state-tree';
|
|
3
3
|
import Save from '@mui/icons-material/Save';
|
|
4
4
|
import { compareStructural, reaction } from 'mobx';
|
|
5
5
|
import { ConfigurationReference, getConf } from '../../configuration/index.js';
|
|
@@ -251,6 +251,20 @@ export function createBaseTrackModel(pm, trackType, baseTrackConfig) {
|
|
|
251
251
|
setResizing(flag) {
|
|
252
252
|
self.resizing = flag;
|
|
253
253
|
},
|
|
254
|
+
/**
|
|
255
|
+
* #action
|
|
256
|
+
* `applyDisplaySettings` on the display being drawn (`activeDisplay`,
|
|
257
|
+
* which a shown track always has — this is not meaningful on a bare
|
|
258
|
+
* config node) — the track-level entry for "restyle this track in
|
|
259
|
+
* place" (a session spec's inline keys, an agent's settings bag). Only
|
|
260
|
+
* that one display: settings vocabularies are per display type, so
|
|
261
|
+
* broadcasting one bag across a track's other displays would mis-route
|
|
262
|
+
* keys; address a non-active display directly if that is what you mean.
|
|
263
|
+
* See BaseDisplayModel for the routing and the `allowSetters` opt-in.
|
|
264
|
+
*/
|
|
265
|
+
applyDisplaySettings(settings, options) {
|
|
266
|
+
return self.activeDisplay.applyDisplaySettings(settings, options);
|
|
267
|
+
},
|
|
254
268
|
/**
|
|
255
269
|
* #action
|
|
256
270
|
*/
|
|
@@ -261,6 +275,12 @@ export function createBaseTrackModel(pm, trackType, baseTrackConfig) {
|
|
|
261
275
|
}
|
|
262
276
|
const displays = self.configuration.displays;
|
|
263
277
|
const displayConf = getDisplayConf(displays, newDisplayId);
|
|
278
|
+
// same interception showTrackGeneric makes: a registered-but-unloaded
|
|
279
|
+
// display would otherwise fail as an opaque union mismatch below
|
|
280
|
+
const displayRecord = pm.resolveDisplayTypeRecord(displayConf.type);
|
|
281
|
+
if (displayRecord && !displayRecord.isStateModelLoaded) {
|
|
282
|
+
throw new Error(`state model for display type ${displayConf.type} is not loaded yet — await getDisplayType('${displayConf.type}').loadStateModel() first`);
|
|
283
|
+
}
|
|
264
284
|
self.displays[idx] = {
|
|
265
285
|
...initialSnapshot,
|
|
266
286
|
type: displayConf.type,
|
|
@@ -434,9 +454,24 @@ export function createBaseTrackModel(pm, trackType, baseTrackConfig) {
|
|
|
434
454
|
// being replaced, so leaving the menu up would keep a
|
|
435
455
|
// list of items built against a destroyed MST node
|
|
436
456
|
keepMenuOpen: false,
|
|
457
|
+
// the chosen display's state model may still be a dynamic
|
|
458
|
+
// import away, so load it before swapping — replaceDisplay
|
|
459
|
+
// itself stays a plain sync action
|
|
437
460
|
onClick: () => {
|
|
438
461
|
if (d.displayId !== shownId) {
|
|
439
|
-
|
|
462
|
+
void (async () => {
|
|
463
|
+
try {
|
|
464
|
+
await pm
|
|
465
|
+
.resolveDisplayTypeRecord(d.type)
|
|
466
|
+
?.loadStateModel();
|
|
467
|
+
if (isAlive(self)) {
|
|
468
|
+
self.replaceDisplay(shownId, d.displayId, self.activeDisplay.getPortableSettings?.(d.displayId) ?? {});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
getSession(self).notifyError(`${e}`, e);
|
|
473
|
+
}
|
|
474
|
+
})();
|
|
440
475
|
}
|
|
441
476
|
},
|
|
442
477
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MenuItem } from '../../ui/index.ts';
|
|
2
|
+
import type { AbstractTrackModel, AbstractViewModel } from '../../util/types/index.ts';
|
|
2
3
|
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
3
4
|
/**
|
|
4
5
|
* #stateModel BaseViewModel
|
|
@@ -66,6 +67,30 @@ declare const BaseViewModel: import("@jbrowse/mobx-state-tree").IModelType<{
|
|
|
66
67
|
* #method
|
|
67
68
|
*/
|
|
68
69
|
menuItems(): MenuItem[];
|
|
70
|
+
/**
|
|
71
|
+
* #getter
|
|
72
|
+
* Every track open on this view itself: its own `tracks` array plus any
|
|
73
|
+
* track containers it owns instead (the synteny view keeps one list per
|
|
74
|
+
* band on `trackContainers` and its own `tracks` empty). Tracks on nested
|
|
75
|
+
* views are `allTracks`'s, not this getter's.
|
|
76
|
+
*/
|
|
77
|
+
readonly ownTracks: AbstractTrackModel[];
|
|
78
|
+
/**
|
|
79
|
+
* #getter
|
|
80
|
+
* This view and every view nested inside it, to any depth — a synteny
|
|
81
|
+
* stack's genome rows, a breakpoint split view's panels. Each view answers
|
|
82
|
+
* for its own children, so a consumer never walks the nesting itself:
|
|
83
|
+
* before this getter, four consumers each carried a copy of the walk and
|
|
84
|
+
* two of them had drifted (one read `levels`, one read `trackContainers`,
|
|
85
|
+
* and each was blind to the other's spelling).
|
|
86
|
+
*/
|
|
87
|
+
readonly allViews: AbstractViewModel[];
|
|
88
|
+
} & {
|
|
89
|
+
/**
|
|
90
|
+
* #getter
|
|
91
|
+
* Every track open on this view or any view nested inside it.
|
|
92
|
+
*/
|
|
93
|
+
readonly allTracks: AbstractTrackModel[];
|
|
69
94
|
} & {
|
|
70
95
|
/**
|
|
71
96
|
* #action
|
|
@@ -172,6 +197,30 @@ export declare const BaseViewModelWithDisplayedRegions: import("@jbrowse/mobx-st
|
|
|
172
197
|
* #method
|
|
173
198
|
*/
|
|
174
199
|
menuItems(): MenuItem[];
|
|
200
|
+
/**
|
|
201
|
+
* #getter
|
|
202
|
+
* Every track open on this view itself: its own `tracks` array plus any
|
|
203
|
+
* track containers it owns instead (the synteny view keeps one list per
|
|
204
|
+
* band on `trackContainers` and its own `tracks` empty). Tracks on nested
|
|
205
|
+
* views are `allTracks`'s, not this getter's.
|
|
206
|
+
*/
|
|
207
|
+
readonly ownTracks: AbstractTrackModel[];
|
|
208
|
+
/**
|
|
209
|
+
* #getter
|
|
210
|
+
* This view and every view nested inside it, to any depth — a synteny
|
|
211
|
+
* stack's genome rows, a breakpoint split view's panels. Each view answers
|
|
212
|
+
* for its own children, so a consumer never walks the nesting itself:
|
|
213
|
+
* before this getter, four consumers each carried a copy of the walk and
|
|
214
|
+
* two of them had drifted (one read `levels`, one read `trackContainers`,
|
|
215
|
+
* and each was blind to the other's spelling).
|
|
216
|
+
*/
|
|
217
|
+
readonly allViews: AbstractViewModel[];
|
|
218
|
+
} & {
|
|
219
|
+
/**
|
|
220
|
+
* #getter
|
|
221
|
+
* Every track open on this view or any view nested inside it.
|
|
222
|
+
*/
|
|
223
|
+
readonly allTracks: AbstractTrackModel[];
|
|
175
224
|
} & {
|
|
176
225
|
/**
|
|
177
226
|
* #action
|
|
@@ -94,6 +94,45 @@ const BaseViewModel = types
|
|
|
94
94
|
menuItems() {
|
|
95
95
|
return [];
|
|
96
96
|
},
|
|
97
|
+
/**
|
|
98
|
+
* #getter
|
|
99
|
+
* Every track open on this view itself: its own `tracks` array plus any
|
|
100
|
+
* track containers it owns instead (the synteny view keeps one list per
|
|
101
|
+
* band on `trackContainers` and its own `tracks` empty). Tracks on nested
|
|
102
|
+
* views are `allTracks`'s, not this getter's.
|
|
103
|
+
*/
|
|
104
|
+
get ownTracks() {
|
|
105
|
+
const s = self;
|
|
106
|
+
return [
|
|
107
|
+
...(s.tracks ?? []),
|
|
108
|
+
...(s.trackContainers ?? []).flatMap(c => c.tracks ?? []),
|
|
109
|
+
];
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* #getter
|
|
113
|
+
* This view and every view nested inside it, to any depth — a synteny
|
|
114
|
+
* stack's genome rows, a breakpoint split view's panels. Each view answers
|
|
115
|
+
* for its own children, so a consumer never walks the nesting itself:
|
|
116
|
+
* before this getter, four consumers each carried a copy of the walk and
|
|
117
|
+
* two of them had drifted (one read `levels`, one read `trackContainers`,
|
|
118
|
+
* and each was blind to the other's spelling).
|
|
119
|
+
*/
|
|
120
|
+
get allViews() {
|
|
121
|
+
const s = self;
|
|
122
|
+
return [
|
|
123
|
+
self,
|
|
124
|
+
...(s.views ?? []).flatMap(v => v.allViews ?? []),
|
|
125
|
+
];
|
|
126
|
+
},
|
|
127
|
+
}))
|
|
128
|
+
.views(self => ({
|
|
129
|
+
/**
|
|
130
|
+
* #getter
|
|
131
|
+
* Every track open on this view or any view nested inside it.
|
|
132
|
+
*/
|
|
133
|
+
get allTracks() {
|
|
134
|
+
return self.allViews.flatMap(v => v.ownTracks);
|
|
135
|
+
},
|
|
97
136
|
}))
|
|
98
137
|
.actions(self => ({
|
|
99
138
|
/**
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Checks that a display's fetch foundation attached it exactly once — the one
|
|
4
|
+
* ordering contract here that is a *state* rather than a declaration, so no
|
|
5
|
+
* selector can reach it. Costs a `WeakSet` lookup, so it runs in a production
|
|
6
|
+
* build too and reports through `contractReports`, which is silent until
|
|
7
|
+
* something arms it.
|
|
6
8
|
*
|
|
7
9
|
* Called once per display from **whichever fetch foundation installed its
|
|
8
10
|
* autoruns** — `MultiRegionDisplayMixin`'s `afterAttach` for the per-region
|
|
@@ -69,14 +71,15 @@ interface RetryContractHost {
|
|
|
69
71
|
*/
|
|
70
72
|
viewportEmpty?: boolean;
|
|
71
73
|
}
|
|
72
|
-
/** Called by `FetchMixin.runFetch`.
|
|
74
|
+
/** Called by `FetchMixin.runFetch`. */
|
|
73
75
|
export declare function noteFetchStarted(self: object): void;
|
|
74
76
|
/** Reads and clears the flag above, so each check starts from a known state. */
|
|
75
77
|
export declare function takeFetchStarted(self: object): boolean;
|
|
76
78
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
79
|
+
* Checks that a display's `reload()` can actually reach a fetch — the retry
|
|
80
|
+
* contract, which `DisplayErrorBar` depends on and which no type can state.
|
|
81
|
+
* Costs an `untracked` read per autorun run against a fetch, so it runs in a
|
|
82
|
+
* production build too and reports through `contractReports`.
|
|
80
83
|
*
|
|
81
84
|
* **The button is the contract.** `DisplayErrorBar`'s only action is
|
|
82
85
|
* `model.reload()`, so every state that can raise the error bar has to be one
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { getMembers } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import { reportContractViolation } from '@jbrowse/render-core/contractReports';
|
|
2
3
|
import { untracked } from 'mobx';
|
|
3
4
|
// Nodes whose fetch foundation has already installed its autoruns. Keyed on the
|
|
4
5
|
// node rather than the model type: every display of a type would otherwise
|
|
5
6
|
// report from the second instance onward.
|
|
6
7
|
const attached = new WeakSet();
|
|
7
8
|
function report(message) {
|
|
8
|
-
|
|
9
|
-
// the session loader as an invalid track and the display is silently dropped
|
|
10
|
-
// — which would hide the very contract violation this is reporting.
|
|
11
|
-
console.error(`[jbrowse display contract] ${message}`);
|
|
9
|
+
reportContractViolation('display', message);
|
|
12
10
|
}
|
|
13
11
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Checks that a display's fetch foundation attached it exactly once — the one
|
|
13
|
+
* ordering contract here that is a *state* rather than a declaration, so no
|
|
14
|
+
* selector can reach it. Costs a `WeakSet` lookup, so it runs in a production
|
|
15
|
+
* build too and reports through `contractReports`, which is silent until
|
|
16
|
+
* something arms it.
|
|
17
17
|
*
|
|
18
18
|
* Called once per display from **whichever fetch foundation installed its
|
|
19
19
|
* autoruns** — `MultiRegionDisplayMixin`'s `afterAttach` for the per-region
|
|
@@ -46,19 +46,17 @@ function report(message) {
|
|
|
46
46
|
* points nowhere near the cause.
|
|
47
47
|
*/
|
|
48
48
|
export function assertDisplayContract(self, installedBy = "the per-region fetch foundation's afterAttach") {
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
attached.add(self);
|
|
61
|
-
}
|
|
49
|
+
if (attached.has(self)) {
|
|
50
|
+
report(`${getMembers(self).name}: ${installedBy} ran twice on one display, ` +
|
|
51
|
+
`so its fetch autoruns are installed twice (double fetches, double ` +
|
|
52
|
+
`clears). Our MST fork auto-chains lifecycle hooks, so the usual ` +
|
|
53
|
+
`cause is a superAfterAttach() call in this display's afterAttach — ` +
|
|
54
|
+
`delete it. Otherwise this display composes two fetch foundations, ` +
|
|
55
|
+
`or calls an installer it already gets from a mixin. ` +
|
|
56
|
+
`See agent-docs/ARCHITECTURE.md §"What not to do".`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
attached.add(self);
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
// Displays whose fetch has started since the last time the caller looked, and
|
|
@@ -85,11 +83,8 @@ export function assertDisplayContract(self, installedBy = "the per-region fetch
|
|
|
85
83
|
// on the display with the liveliest one.
|
|
86
84
|
const fetchStarted = new WeakSet();
|
|
87
85
|
const ledgers = new WeakMap();
|
|
88
|
-
/** Called by `FetchMixin.runFetch`.
|
|
86
|
+
/** Called by `FetchMixin.runFetch`. */
|
|
89
87
|
export function noteFetchStarted(self) {
|
|
90
|
-
if (process.env.NODE_ENV === 'production') {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
88
|
fetchStarted.add(self);
|
|
94
89
|
ledgers.get(self)?.('fetched');
|
|
95
90
|
}
|
|
@@ -98,9 +93,10 @@ export function takeFetchStarted(self) {
|
|
|
98
93
|
return fetchStarted.delete(self);
|
|
99
94
|
}
|
|
100
95
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
96
|
+
* Checks that a display's `reload()` can actually reach a fetch — the retry
|
|
97
|
+
* contract, which `DisplayErrorBar` depends on and which no type can state.
|
|
98
|
+
* Costs an `untracked` read per autorun run against a fetch, so it runs in a
|
|
99
|
+
* production build too and reports through `contractReports`.
|
|
104
100
|
*
|
|
105
101
|
* **The button is the contract.** `DisplayErrorBar`'s only action is
|
|
106
102
|
* `model.reload()`, so every state that can raise the error bar has to be one
|
|
@@ -195,9 +191,6 @@ export function takeFetchStarted(self) {
|
|
|
195
191
|
* is worse than the bug being checked for.
|
|
196
192
|
*/
|
|
197
193
|
export function makeRetryContractCheck(self) {
|
|
198
|
-
if (process.env.NODE_ENV === 'production') {
|
|
199
|
-
return () => { };
|
|
200
|
-
}
|
|
201
194
|
// eslint-disable-next-line no-restricted-syntax -- instrumentation
|
|
202
195
|
let lastCounter = untracked(() => self.reloadCounter);
|
|
203
196
|
function noteRetryContractOutcome(outcome) {
|
package/esm/rpc/RpcRegistry.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FeatureDensity } from '../data_adapters/BaseAdapter/featureDensity.ts';
|
|
1
2
|
import type { StatusCallback } from '../util/progress.ts';
|
|
2
3
|
import type { UnwrapRpcResult } from '../util/rpc.ts';
|
|
3
4
|
import type { Feature, SimpleFeatureSerialized } from '../util/simpleFeature.ts';
|
|
@@ -52,6 +53,15 @@ export interface RpcRegistry {
|
|
|
52
53
|
};
|
|
53
54
|
return: number | undefined;
|
|
54
55
|
};
|
|
56
|
+
CoreGetFeatureDensity: {
|
|
57
|
+
args: {
|
|
58
|
+
adapterConfig: Record<string, unknown>;
|
|
59
|
+
regions: RegionLike[];
|
|
60
|
+
bpPerPx: number;
|
|
61
|
+
headers?: Record<string, string>;
|
|
62
|
+
};
|
|
63
|
+
return: FeatureDensity[] | undefined;
|
|
64
|
+
};
|
|
55
65
|
CoreGetInfo: {
|
|
56
66
|
args: {
|
|
57
67
|
adapterConfig: Record<string, unknown>;
|
package/esm/rpc/byteBudget.d.ts
CHANGED
|
@@ -6,6 +6,15 @@ import type { AugmentedRegion } from '../util/types/data.ts';
|
|
|
6
6
|
* budget, or the save dialog's bound on the whole download.
|
|
7
7
|
*/
|
|
8
8
|
export type ByteEstimateScope = 'largestRegion' | 'wholeRequest';
|
|
9
|
+
/**
|
|
10
|
+
* The one spelling of a gated feature RPC's budget argument: the number the
|
|
11
|
+
* worker measures against, undefined when the gate declined to act and nothing
|
|
12
|
+
* is measured. A call-site argument, never part of `rpcProps`, because it
|
|
13
|
+
* swings at 20kb and on force-load.
|
|
14
|
+
*/
|
|
15
|
+
export interface GatedFetchArgs {
|
|
16
|
+
byteLimit?: number;
|
|
17
|
+
}
|
|
9
18
|
export interface ByteMeasurableAdapter {
|
|
10
19
|
getRegionByteSize: (regions: AugmentedRegion[], opts?: {
|
|
11
20
|
stopToken?: StopToken;
|
|
@@ -5,5 +5,6 @@ export { default as CoreGetFeatures } from './methods/CoreGetFeatures.ts';
|
|
|
5
5
|
export { default as CoreGetSequence } from './methods/CoreGetSequence.ts';
|
|
6
6
|
export { default as CoreFreeResources } from './methods/CoreFreeResources.ts';
|
|
7
7
|
export { default as CoreGetRegionByteEstimate } from './methods/CoreGetRegionByteEstimate.ts';
|
|
8
|
+
export { default as CoreGetFeatureDensity } from './methods/CoreGetFeatureDensity.ts';
|
|
8
9
|
export { default as CoreGetRegions } from './methods/CoreGetRegions.ts';
|
|
9
10
|
export { default as CoreGetExportData } from './methods/CoreGetExportData.ts';
|
|
@@ -5,5 +5,6 @@ export { default as CoreGetFeatures } from './methods/CoreGetFeatures.js';
|
|
|
5
5
|
export { default as CoreGetSequence } from './methods/CoreGetSequence.js';
|
|
6
6
|
export { default as CoreFreeResources } from './methods/CoreFreeResources.js';
|
|
7
7
|
export { default as CoreGetRegionByteEstimate } from './methods/CoreGetRegionByteEstimate.js';
|
|
8
|
+
export { default as CoreGetFeatureDensity } from './methods/CoreGetFeatureDensity.js';
|
|
8
9
|
export { default as CoreGetRegions } from './methods/CoreGetRegions.js';
|
|
9
10
|
export { default as CoreGetExportData } from './methods/CoreGetExportData.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import RpcMethodTypeWithRenameRegions from '../../pluggableElementTypes/RpcMethodTypeWithRenameRegions.ts';
|
|
2
|
+
import type { RpcExecuteArgs } from '../RpcRegistry.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The density tier's read: features per bin at the view's bp/px for regions the
|
|
5
|
+
* region-too-large gate refused. Answers `undefined` for an adapter with no
|
|
6
|
+
* density source, and the display keeps the banner.
|
|
7
|
+
*/
|
|
8
|
+
export default class CoreGetFeatureDensity extends RpcMethodTypeWithRenameRegions<'CoreGetFeatureDensity'> {
|
|
9
|
+
name: 'CoreGetFeatureDensity';
|
|
10
|
+
execute(args: RpcExecuteArgs<'CoreGetFeatureDensity'>): Promise<import("../../data_adapters/BaseAdapter/featureDensity.ts").FeatureDensity[] | undefined>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isFeatureAdapter } from '../../data_adapters/BaseAdapter/index.js';
|
|
2
|
+
import { getAdapter } from '../../data_adapters/dataAdapterCache.js';
|
|
3
|
+
import RpcMethodTypeWithRenameRegions from '../../pluggableElementTypes/RpcMethodTypeWithRenameRegions.js';
|
|
4
|
+
/**
|
|
5
|
+
* The density tier's read: features per bin at the view's bp/px for regions the
|
|
6
|
+
* region-too-large gate refused. Answers `undefined` for an adapter with no
|
|
7
|
+
* density source, and the display keeps the banner.
|
|
8
|
+
*/
|
|
9
|
+
export default class CoreGetFeatureDensity extends RpcMethodTypeWithRenameRegions {
|
|
10
|
+
name = 'CoreGetFeatureDensity';
|
|
11
|
+
async execute(args) {
|
|
12
|
+
const { adapterConfig, sessionId, regions, bpPerPx, stopToken } = args;
|
|
13
|
+
const { dataAdapter } = await getAdapter(this.pluginManager, sessionId, adapterConfig);
|
|
14
|
+
return isFeatureAdapter(dataAdapter)
|
|
15
|
+
? dataAdapter.getFeatureDensity(regions, {
|
|
16
|
+
bpPerPx,
|
|
17
|
+
stopToken,
|
|
18
|
+
statusCallback: args.statusCallback,
|
|
19
|
+
})
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/esm/svg/svgReady.d.ts
CHANGED
|
@@ -94,6 +94,7 @@ export declare function awaitSvgReady(model: Pick<SvgExportable, 'svgReady' | 'e
|
|
|
94
94
|
export declare function awaitViewInitialized(view: {
|
|
95
95
|
initialized: boolean;
|
|
96
96
|
error: unknown;
|
|
97
|
+
pendingLaunch?: unknown;
|
|
97
98
|
}): Promise<void>;
|
|
98
99
|
/**
|
|
99
100
|
* How the export answers "a track failed": by failing. A track whose data won't
|
package/esm/ui/CascadingMenu.js
CHANGED
|
@@ -6,7 +6,7 @@ import { makeStyles } from '../util/tss-react/index.js';
|
|
|
6
6
|
import { useEventCallback } from '../util/useEventCallback.js';
|
|
7
7
|
import HoverMenu from './HoverMenu.js';
|
|
8
8
|
import { MenuItemTrailing } from './MenuItemTrailing.js';
|
|
9
|
-
import { staysOpenOnClick } from './MenuTypes.js';
|
|
9
|
+
import { resolveSubMenu, staysOpenOnClick } from './MenuTypes.js';
|
|
10
10
|
import { hasMenuItemAdornment } from './menuItemAdornment.js';
|
|
11
11
|
import { isAimedAtPanel } from './submenuAim.js';
|
|
12
12
|
// Compact section headers so tall menus (e.g. a display's flat "Show..."
|
|
@@ -24,6 +24,12 @@ const useStyles = makeStyles()(theme => ({
|
|
|
24
24
|
borderTop: `1px solid ${theme.palette.divider}`,
|
|
25
25
|
marginTop: 4,
|
|
26
26
|
},
|
|
27
|
+
// a leading subHeader has no rows above it to divide from, so the rule and
|
|
28
|
+
// its space would float against the menu's top edge
|
|
29
|
+
leadingSubHeader: {
|
|
30
|
+
borderTopWidth: 0,
|
|
31
|
+
marginTop: 0,
|
|
32
|
+
},
|
|
27
33
|
}));
|
|
28
34
|
const CascadingMenuContext = createContext(undefined);
|
|
29
35
|
function useCascadingMenu() {
|
|
@@ -312,17 +318,36 @@ function CascadingSubmenu({ item, itemKey, inset, columns, hover, onNavigateBack
|
|
|
312
318
|
// up
|
|
313
319
|
paperRef: isOpen ? hover.panelRef : undefined, onClose: () => {
|
|
314
320
|
hover.setSubmenu(undefined);
|
|
315
|
-
}, children: _jsx(
|
|
321
|
+
}, children: _jsx(SubMenuList, { item: item, onNavigateBack: () => {
|
|
316
322
|
hover.setSubmenu(undefined);
|
|
317
323
|
anchorEl?.focus();
|
|
318
324
|
} }) })] }));
|
|
319
325
|
}
|
|
326
|
+
// Resolves the rows inside the panel, which MUI mounts only while it is open:
|
|
327
|
+
// a function-form `subMenu` is not called by listing the parent menu. An
|
|
328
|
+
// observer so the observables that builder reads re-render the open panel, the
|
|
329
|
+
// way an array built in the root observer's render already did.
|
|
330
|
+
const SubMenuList = observer(function SubMenuList({ item, onNavigateBack, }) {
|
|
331
|
+
return (_jsx(CascadingMenuList, { menuItems: resolveSubMenu(item), onNavigateBack: onNavigateBack }));
|
|
332
|
+
});
|
|
320
333
|
// One clickable menu row: label (with optional leading icon) plus its trailing
|
|
321
334
|
// value/help/adornment decorations. The menu-wide `columns` flags let every row
|
|
322
335
|
// reserve matching decoration slots so the columns line up down the menu.
|
|
323
336
|
function CascadingMenuItem({ item, inset, columns, onNavigateBack, onHover, }) {
|
|
324
337
|
const { closeAfterItemClick, onMenuItemClick, onCloseRoot } = useCascadingMenu();
|
|
325
|
-
return (_jsx(DisabledTooltip, { item: item, children: _jsxs(MenuItem, { "data-testid": makeTestId('menuitem', item.label), disabled: item.disabled,
|
|
338
|
+
return (_jsx(DisabledTooltip, { item: item, children: _jsxs(MenuItem, { "data-testid": makeTestId('menuitem', item.label), disabled: item.disabled,
|
|
339
|
+
// A checkbox or radio row states its own value. The glyph
|
|
340
|
+
// `MenuItemEndDecoration` swaps is an `aria-hidden` icon, so without
|
|
341
|
+
// these a screen reader reads "Curved lines" identically whether the
|
|
342
|
+
// setting is on or off, and a test has no way to ask but the MUI icon's
|
|
343
|
+
// internal testid.
|
|
344
|
+
role: item.type === 'checkbox'
|
|
345
|
+
? 'menuitemcheckbox'
|
|
346
|
+
: item.type === 'radio'
|
|
347
|
+
? 'menuitemradio'
|
|
348
|
+
: undefined, "aria-checked": item.type === 'checkbox' || item.type === 'radio'
|
|
349
|
+
? item.checked
|
|
350
|
+
: undefined, onClick: () => {
|
|
326
351
|
// onCloseRoot runs before the callback, so item.onClick must NOT read
|
|
327
352
|
// model state that closing clears (e.g. a right-click menu's ephemeral
|
|
328
353
|
// hit/context fields): capture that state when the menu items are
|
|
@@ -339,7 +364,7 @@ function CascadingMenuItem({ item, inset, columns, onNavigateBack, onHover, }) {
|
|
|
339
364
|
}, children: [_jsx(MenuItemLeadingIcon, { Icon: item.icon }), _jsx(ListItemText, { primary: item.label, secondary: item.subLabel, inset: inset }), _jsx(MenuItemTrailing, { item: item, columns: columns })] }) }));
|
|
340
365
|
}
|
|
341
366
|
function CascadingMenuList({ menuItems, onNavigateBack, }) {
|
|
342
|
-
const { classes } = useStyles();
|
|
367
|
+
const { classes, cx } = useStyles();
|
|
343
368
|
const hover = useSubmenuHover();
|
|
344
369
|
// every row a pointer can rest on asks for the open panel to go away; only a
|
|
345
370
|
// row that owns a submenu, or the panel itself, keeps one up. Whether the ask
|
|
@@ -360,10 +385,7 @@ function CascadingMenuList({ menuItems, onNavigateBack, }) {
|
|
|
360
385
|
return _jsx(Divider, { component: "li" }, `divider-${idx}`);
|
|
361
386
|
}
|
|
362
387
|
if (item.type === 'subHeader') {
|
|
363
|
-
return (_jsx(ListSubheader, { className: classes.subHeader, onMouseEnter: closeOnHover,
|
|
364
|
-
// a leading subHeader has no rows above it to divide from, so drop
|
|
365
|
-
// the divider rule that would otherwise float at the menu's top edge
|
|
366
|
-
sx: idx === 0 ? { borderTop: 'none', marginTop: 0 } : undefined, children: item.label }, `subHeader-${item.label}`));
|
|
388
|
+
return (_jsx(ListSubheader, { className: cx(classes.subHeader, idx === 0 && classes.leadingSubHeader), onMouseEnter: closeOnHover, children: item.label }, `subHeader-${item.label}`));
|
|
367
389
|
}
|
|
368
390
|
if (item.type === 'custom') {
|
|
369
391
|
return (_jsx(CustomMenuRow, { item: item, onHover: closeOnHover }, `custom-${item.label}`));
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns false, then flips true once `active` has stayed true continuously for
|
|
3
|
+
* `delayMs`. Each activation gets a fresh delay, and the flag drops the moment
|
|
4
|
+
* `active` does.
|
|
5
|
+
*
|
|
6
|
+
* Keyed on a count of RISING edges rather than on `active` itself, so the
|
|
7
|
+
* falling edge does no work at all. A display refetching through a wheel zoom
|
|
8
|
+
* flips `active` several times a second — it finishes a fetch, waits out the
|
|
9
|
+
* debounce, starts the next — and an effect depending on `active` tore its timer
|
|
10
|
+
* down and built a new one on each of those, twice over, for a scrim that by
|
|
11
|
+
* construction never appears. Deriving the flag from a comparison instead means
|
|
12
|
+
* one `setTimeout` per loading pulse, which is the floor for these semantics.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useDelayedFlag(active: boolean, delayMs: number): boolean;
|
|
1
15
|
export default function LoadingOverlay({ statusMessage, progress, isVisible, immediate, canceled, onCancel, onRetry, }: {
|
|
2
16
|
statusMessage?: string;
|
|
3
17
|
progress?: number;
|
package/esm/ui/LoadingOverlay.js
CHANGED
|
@@ -27,7 +27,7 @@ const flashDelayMs = 250;
|
|
|
27
27
|
* construction never appears. Deriving the flag from a comparison instead means
|
|
28
28
|
* one `setTimeout` per loading pulse, which is the floor for these semantics.
|
|
29
29
|
*/
|
|
30
|
-
function useDelayedFlag(active, delayMs) {
|
|
30
|
+
export function useDelayedFlag(active, delayMs) {
|
|
31
31
|
const [previous, setPrevious] = useState(active);
|
|
32
32
|
const [activation, setActivation] = useState(0);
|
|
33
33
|
if (previous !== active) {
|