@jbrowse/core 5.0.0-beta.2 → 5.0.0-beta.4
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 +70 -40
- package/esm/BaseFeatureWidget/BaseFeatureDetail/FeatureDetails.d.ts +2 -3
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.d.ts +4 -0
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.js +17 -0
- package/esm/BaseFeatureWidget/BaseFeatureDetail/index.js +4 -3
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.d.ts +2 -2
- package/esm/BaseFeatureWidget/index.d.ts +1 -1
- package/esm/BaseFeatureWidget/stateModelFactory.d.ts +8 -1
- package/esm/BaseFeatureWidget/stateModelFactory.js +6 -0
- package/esm/BaseFeatureWidget/types.d.ts +10 -2
- package/esm/PluginManager.d.ts +30 -1
- package/esm/PluginManager.js +107 -70
- package/esm/ReExports/BaseFeatureDetails.js +1 -1
- package/esm/ReExports/MuiDataGridReExports.d.ts +1 -0
- package/esm/ReExports/MuiDataGridReExports.js +2 -2
- package/esm/ReExports/barrelOnlyNames.d.ts +5 -0
- package/esm/ReExports/barrelOnlyNames.js +63 -0
- package/esm/ReExports/modules.js +28 -56
- package/esm/ReExports/publicTracks.d.ts +1 -0
- package/esm/ReExports/publicTracks.js +14 -0
- package/esm/ReExports/publicUi.d.ts +4 -0
- package/esm/ReExports/publicUi.js +33 -0
- package/esm/ReExports/publicUtil.d.ts +1 -1
- package/esm/ReExports/publicUtil.js +8 -4
- package/esm/ReExports/sharedModules.d.ts +1 -1
- package/esm/ReExports/sharedModules.js +8 -1
- package/esm/ReExports/uiStub.d.ts +1 -0
- package/esm/ReExports/uiStub.js +6 -8
- package/esm/ReExports/workerModules.js +9 -4
- package/esm/ReExports/workerNamespaceNames.d.ts +11 -0
- package/esm/ReExports/workerNamespaceNames.js +281 -0
- package/esm/configuration/configurationSchema.js +11 -0
- package/esm/configuration/index.d.ts +2 -2
- package/esm/configuration/index.js +1 -1
- package/esm/configuration/promotableDefaults.d.ts +54 -19
- package/esm/configuration/promotableDefaults.js +108 -45
- package/esm/configuration/promotablePin.d.ts +50 -40
- package/esm/configuration/promotableResolve.d.ts +1 -1
- package/esm/configuration/promotableResolve.js +1 -1
- package/esm/configuration/tabixIndexFields.d.ts +2 -2
- package/esm/configuration/tabixIndexFields.js +2 -2
- package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +4 -12
- package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +12 -0
- package/esm/data_adapters/BaseAdapter/featureDensity.js +15 -0
- package/esm/data_adapters/BaseAdapter/index.d.ts +2 -2
- package/esm/data_adapters/BaseAdapter/index.js +1 -1
- package/esm/data_adapters/BaseAdapter/types.d.ts +2 -8
- package/esm/pluggableElementTypes/AdapterType.d.ts +2 -1
- package/esm/pluggableElementTypes/AdapterType.js +1 -1
- package/esm/pluggableElementTypes/DisplayType.d.ts +3 -10
- package/esm/pluggableElementTypes/DisplayType.js +3 -45
- package/esm/pluggableElementTypes/LazyStateModelElement.d.ts +27 -0
- package/esm/pluggableElementTypes/LazyStateModelElement.js +71 -0
- package/esm/pluggableElementTypes/ViewType.d.ts +3 -10
- package/esm/pluggableElementTypes/ViewType.js +6 -51
- package/esm/pluggableElementTypes/extendElementType.d.ts +1 -2
- package/esm/pluggableElementTypes/extendElementType.js +1 -2
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +3 -3
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +1 -1
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +1 -4
- package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +34 -34
- package/esm/pluggableElementTypes/models/BaseViewModel.js +19 -30
- package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +4 -3
- package/esm/pluggableElementTypes/models/assertDisplayContract.js +2 -2
- package/esm/pluggableElementTypes/models/baseTrackConfig.js +7 -1
- package/esm/ui/BaseExportSvgDialog.d.ts +1 -1
- package/esm/ui/ErrorMessageStackTraceDialog.js +5 -6
- package/esm/ui/ExportSvgDialog.d.ts +1 -1
- package/esm/ui/MenuTypes.d.ts +3 -3
- package/esm/ui/PinAdornment.js +22 -40
- package/esm/ui/ScrollChrome.d.ts +16 -0
- package/esm/ui/ScrollChrome.js +12 -0
- package/esm/ui/ScrollZoomToggle.d.ts +4 -3
- package/esm/ui/ScrollZoomToggle.js +4 -3
- package/esm/ui/SizeSliderRow.js +1 -1
- package/esm/ui/index.d.ts +2 -2
- package/esm/ui/index.js +3 -3
- package/esm/ui/legendMenuItem.d.ts +9 -15
- package/esm/ui/legendMenuItem.js +8 -19
- package/esm/ui/menuItemAdornment.d.ts +2 -3
- package/esm/ui/menuItemAdornment.js +2 -3
- package/esm/ui/menuItems.d.ts +1 -2
- package/esm/ui/menuItems.js +0 -1
- package/esm/ui/palette.d.ts +11 -11
- package/esm/ui/palette.js +11 -11
- package/esm/ui/promotablePinCoverage.d.ts +16 -2
- package/esm/ui/promotablePinCoverage.js +38 -2
- package/esm/ui/theme.js +5 -0
- package/esm/ui/toggleMenuItems.d.ts +13 -6
- package/esm/ui/toggleMenuItems.js +32 -15
- package/esm/util/Base1DUtils.d.ts +4 -2
- package/esm/util/Base1DUtils.js +12 -4
- package/esm/util/SvgCanvas.js +4 -3
- package/esm/util/adapterConfigKey.d.ts +12 -0
- package/esm/util/adapterConfigKey.js +14 -0
- package/esm/util/adapterMetadata.js +2 -1
- package/esm/util/analytics.js +4 -5
- package/esm/util/clampToContig.d.ts +17 -0
- package/esm/util/clampToContig.js +19 -0
- package/esm/util/colorBits.d.ts +2 -1
- package/esm/util/colorBits.js +2 -5
- package/esm/util/createStopTokenRotation.d.ts +8 -7
- package/esm/util/featureTypes.d.ts +24 -0
- package/esm/util/featureTypes.js +35 -0
- package/esm/util/fetchPhases.d.ts +1 -1
- package/esm/util/index.d.ts +4 -2
- package/esm/util/index.js +3 -1
- package/esm/util/installFetch.d.ts +29 -18
- package/esm/util/installFetch.js +17 -17
- package/esm/util/installInitAutorun.d.ts +6 -5
- package/esm/util/installInitAutorun.js +2 -2
- package/esm/util/installPrerequisiteFetch.d.ts +86 -0
- package/esm/util/installPrerequisiteFetch.js +73 -0
- package/esm/util/openFeatureWidget.d.ts +5 -0
- package/esm/util/openFeatureWidget.js +1 -0
- package/esm/util/openViews.d.ts +16 -0
- package/esm/util/openViews.js +22 -0
- package/esm/util/parentWalk.d.ts +1 -1
- package/esm/util/parentWalk.js +1 -1
- package/esm/util/renderToStaticMarkup.js +10 -0
- package/esm/util/scheduleDetachedDestroy.d.ts +18 -0
- package/esm/util/scheduleDetachedDestroy.js +24 -0
- package/esm/util/scrollLatch.d.ts +1 -0
- package/esm/util/scrollLatch.js +8 -0
- package/esm/util/stopToken.d.ts +6 -5
- package/esm/util/trackConfigDelta.d.ts +6 -6
- package/esm/util/trackConfigDelta.js +3 -3
- package/esm/util/types/index.d.ts +25 -20
- package/esm/util/usePanelVirtualScroll.d.ts +6 -2
- package/esm/util/usePanelVirtualScroll.js +6 -2
- package/esm/util/useRowVirtualScroll.d.ts +1 -1
- package/esm/util/useRowVirtualScroll.js +1 -1
- package/esm/util/useVirtualScrollWheel.js +11 -1
- package/esm/util/wheelZoom.d.ts +3 -1
- package/esm/util/wheelZoom.js +39 -14
- package/esm/util/whenViewSettled.d.ts +31 -6
- package/esm/util/whenViewSettled.js +33 -5
- package/esm/util/withLaunchInput.d.ts +47 -2
- package/esm/util/withLaunchInput.js +56 -11
- package/package.json +10 -5
- package/esm/ui/promotableMenuItems.d.ts +0 -19
- package/esm/ui/promotableMenuItems.js +0 -91
|
@@ -7,6 +7,16 @@ import { createRoot } from 'react-dom/client';
|
|
|
7
7
|
// reason to pull react/react-dom/@emotion, and the barrel is imported by
|
|
8
8
|
// worker-side adapter code that never renders.
|
|
9
9
|
//
|
|
10
|
+
// It is not part of the plugin ABI either, and that removal did break two
|
|
11
|
+
// published bundles before anyone noticed: react-msaview's SVG export read it
|
|
12
|
+
// off `@jbrowse/core/util`, and jbrowse-plugin-msaview and -tview bundle
|
|
13
|
+
// react-msaview. Resolved on their side -- react-msaview owns its own copy now
|
|
14
|
+
// (packages/lib/src/renderToStaticMarkup.ts there) -- because a rendering
|
|
15
|
+
// library asking its host for a renderer was the coupling worth removing, and
|
|
16
|
+
// because react-dom is a host external for a plugin anyway, so it costs them a
|
|
17
|
+
// few hundred bytes rather than a duplicate react. sharedModules.purity.test.ts
|
|
18
|
+
// is what keeps this file off the worker's graph now.
|
|
19
|
+
//
|
|
10
20
|
// https://react.dev/reference/react-dom/server/renderToString#removing-rendertostring-from-the-client-code
|
|
11
21
|
export function renderToStaticMarkup(node) {
|
|
12
22
|
const div = document.createElement('div');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
/**
|
|
3
|
+
* Free a tree an action has already let go of — a superseded session, a view
|
|
4
|
+
* taken out of one, a superseded rootModel the React host has detached.
|
|
5
|
+
*
|
|
6
|
+
* A task rather than a microtask: the wait is for React to unmount the
|
|
7
|
+
* components rendering it, which happens on the re-render that the action's own
|
|
8
|
+
* reactions schedule. Deferring is acceptable here, and is not elsewhere,
|
|
9
|
+
* because the node stays alive and detached until it fires — a late read gets a
|
|
10
|
+
* live tree nobody is rendering. ADR-069.
|
|
11
|
+
*
|
|
12
|
+
* The destroy itself is not optional. `beforeDestroy` is a plugin-facing
|
|
13
|
+
* contract (jbrowse-plugin-apollo aborts its in-flight fetches there) and core's
|
|
14
|
+
* `BaseTrackModel` releases the `rpcSessionId` claim that lets
|
|
15
|
+
* `CoreFreeResources` evict a parsed adapter from the worker — so detaching and
|
|
16
|
+
* forgetting would trade a console warning for an unbounded leak.
|
|
17
|
+
*/
|
|
18
|
+
export declare function scheduleDetachedDestroy(node: IAnyStateTreeNode): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { destroy, isAlive } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
/**
|
|
3
|
+
* Free a tree an action has already let go of — a superseded session, a view
|
|
4
|
+
* taken out of one, a superseded rootModel the React host has detached.
|
|
5
|
+
*
|
|
6
|
+
* A task rather than a microtask: the wait is for React to unmount the
|
|
7
|
+
* components rendering it, which happens on the re-render that the action's own
|
|
8
|
+
* reactions schedule. Deferring is acceptable here, and is not elsewhere,
|
|
9
|
+
* because the node stays alive and detached until it fires — a late read gets a
|
|
10
|
+
* live tree nobody is rendering. ADR-069.
|
|
11
|
+
*
|
|
12
|
+
* The destroy itself is not optional. `beforeDestroy` is a plugin-facing
|
|
13
|
+
* contract (jbrowse-plugin-apollo aborts its in-flight fetches there) and core's
|
|
14
|
+
* `BaseTrackModel` releases the `rpcSessionId` claim that lets
|
|
15
|
+
* `CoreFreeResources` evict a parsed adapter from the worker — so detaching and
|
|
16
|
+
* forgetting would trade a console warning for an unbounded leak.
|
|
17
|
+
*/
|
|
18
|
+
export function scheduleDetachedDestroy(node) {
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
if (isAlive(node)) {
|
|
21
|
+
destroy(node);
|
|
22
|
+
}
|
|
23
|
+
}, 0);
|
|
24
|
+
}
|
package/esm/util/scrollLatch.js
CHANGED
|
@@ -47,6 +47,14 @@ export function createScrollLatch(timeoutMs = LATCH_TIMEOUT_MS) {
|
|
|
47
47
|
}
|
|
48
48
|
return null;
|
|
49
49
|
},
|
|
50
|
+
// Whether this panel already owns the gesture `e` belongs to, i.e. it has
|
|
51
|
+
// scrolled during a run of events this one continues. Callers ask before
|
|
52
|
+
// declining an event on their own rules: a gesture that has latched keeps
|
|
53
|
+
// every event it fires, cross-axis momentum included, which is the whole
|
|
54
|
+
// point of latching.
|
|
55
|
+
holds(e) {
|
|
56
|
+
return latched && e.timeStamp - lastEvent < timeoutMs;
|
|
57
|
+
},
|
|
50
58
|
// Forget the gesture state so the next event is treated as a fresh gesture.
|
|
51
59
|
// Called when the pointer leaves the panel: a gesture the browser has
|
|
52
60
|
// latched to this element must not keep the page suppressed once the cursor
|
package/esm/util/stopToken.d.ts
CHANGED
|
@@ -31,11 +31,12 @@ import type { TimeGate } from './timeGate.ts';
|
|
|
31
31
|
* either way over a 2000x BAM cancel burst) and that measurement was sound but
|
|
32
32
|
* scoped: every loop on the alignments path is already chunked by awaits at
|
|
33
33
|
* region granularity, so there was nothing there for an intra-loop probe to
|
|
34
|
-
* interrupt. The counter-example is
|
|
35
|
-
* millions of
|
|
36
|
-
* only thing that can stop the work, and
|
|
37
|
-
* future attempt to delete this needs a
|
|
38
|
-
* await-free workload
|
|
34
|
+
* interrupt. The counter-example is the multi-sample-variant genotype matrix
|
|
35
|
+
* fill (`VariantRPC/getGenotypeMatrix.ts`) — millions of cells with no await
|
|
36
|
+
* anywhere, where this probe is the only thing that can stop the work, and
|
|
37
|
+
* which that bench never exercises. Any future attempt to delete this needs a
|
|
38
|
+
* cancel measurement on an await-free workload, not on a pileup. LD's O(n²)
|
|
39
|
+
* pair fill was the original example and went with the in-browser estimator.
|
|
39
40
|
*
|
|
40
41
|
* ## Which path runs where
|
|
41
42
|
*
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
* empty delta, and `SessionTracks.updateTrackConfiguration` used to read that as
|
|
24
24
|
* "the edit netted back to the base" and revert the track's working copy — which
|
|
25
25
|
* undid a removal on screen 400ms after it landed. Promotable slots made that
|
|
26
|
-
* reachable rather than rare:
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* reachable rather than rare: a slider row's reset unsets its slot, and an
|
|
27
|
+
* admin config is free to set one. That branch now keeps the working copy; see
|
|
28
|
+
* the comment there.
|
|
29
29
|
*
|
|
30
30
|
* `displays` is merged by `displayId` so an edit to one display doesn't pin the
|
|
31
31
|
* others. Nested config objects (e.g. `adapter`) recurse. Any other array (value
|
|
@@ -59,9 +59,9 @@ export declare function mergeTrackConfig(base: Record<string, unknown>, delta: R
|
|
|
59
59
|
export interface TrackConfigChange {
|
|
60
60
|
/**
|
|
61
61
|
* Where the setting lives, and — for a row a UI can revert — its **address**:
|
|
62
|
-
* `
|
|
63
|
-
* into the type and slot it clears. So a producer that
|
|
64
|
-
* heading sets `label` and leaves this alone.
|
|
62
|
+
* product-core's `resetDisplayDefault` parses a promoted display-type
|
|
63
|
+
* default's path back into the type and slot it clears. So a producer that
|
|
64
|
+
* wants a friendlier heading sets `label` and leaves this alone.
|
|
65
65
|
*/
|
|
66
66
|
path: string[];
|
|
67
67
|
from: Json;
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
* empty delta, and `SessionTracks.updateTrackConfiguration` used to read that as
|
|
24
24
|
* "the edit netted back to the base" and revert the track's working copy — which
|
|
25
25
|
* undid a removal on screen 400ms after it landed. Promotable slots made that
|
|
26
|
-
* reachable rather than rare:
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* reachable rather than rare: a slider row's reset unsets its slot, and an
|
|
27
|
+
* admin config is free to set one. That branch now keeps the working copy; see
|
|
28
|
+
* the comment there.
|
|
29
29
|
*
|
|
30
30
|
* `displays` is merged by `displayId` so an edit to one display doesn't pin the
|
|
31
31
|
* others. Nested config objects (e.g. `adapter`) recurse. Any other array (value
|
|
@@ -376,22 +376,30 @@ export interface SessionWithSessionPlugins extends AbstractSessionModel {
|
|
|
376
376
|
removeSessionPlugin: (plugin: PluginDefinition) => void;
|
|
377
377
|
}
|
|
378
378
|
export declare function isSessionWithSessionPlugins(thing: unknown): thing is SessionWithSessionPlugins;
|
|
379
|
+
export interface PermanentPluginsSafeMode {
|
|
380
|
+
reason: 'requested' | 'previousLaunchFailed';
|
|
381
|
+
/** what was loading when the launch that failed did not finish */
|
|
382
|
+
suspects: string[];
|
|
383
|
+
}
|
|
379
384
|
/**
|
|
380
385
|
* A session whose product keeps a plugin list of its own, outside both the
|
|
381
386
|
* config and the session — jbrowse-web's permanent plugins, stored in the
|
|
382
387
|
* browser against the config being viewed, so an install survives a new session
|
|
383
388
|
* and the next visit.
|
|
384
389
|
*
|
|
385
|
-
* The
|
|
386
|
-
*
|
|
387
|
-
* which
|
|
388
|
-
* app reload every plugin change needs anyway.
|
|
390
|
+
* The plugin store is the whole surface: the pin beside an installed plugin
|
|
391
|
+
* writes the list, and an entry the load skipped is a row there with a switch
|
|
392
|
+
* on it, which is what makes the safe mode below something a user can act on.
|
|
389
393
|
*/
|
|
390
394
|
export interface SessionWithPermanentPlugins extends AbstractSessionModel {
|
|
391
|
-
permanentPlugins: PluginDefinition
|
|
392
|
-
|
|
395
|
+
permanentPlugins: (PluginDefinition & {
|
|
396
|
+
disabled?: boolean;
|
|
397
|
+
})[];
|
|
398
|
+
permanentPluginsSafeMode: PermanentPluginsSafeMode | undefined;
|
|
393
399
|
addPermanentPlugin: (plugin: PluginDefinition) => void;
|
|
394
400
|
removePermanentPlugin: (plugin: PluginDefinition) => void;
|
|
401
|
+
setPermanentPluginDisabled: (plugin: PluginDefinition, disabled: boolean) => void;
|
|
402
|
+
reloadWithPermanentPlugins: () => void;
|
|
395
403
|
}
|
|
396
404
|
export declare function isSessionWithPermanentPlugins(thing: unknown): thing is SessionWithPermanentPlugins;
|
|
397
405
|
/** abstract interface for a session that manages a global selection */
|
|
@@ -480,22 +488,19 @@ export interface AbstractViewModel {
|
|
|
480
488
|
*/
|
|
481
489
|
trackContainers?: TrackContainer[];
|
|
482
490
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
491
|
+
* what this view contributes to the census of what is open: the tracks it
|
|
492
|
+
* holds itself, and the views nested directly inside it. `BaseViewModel`
|
|
493
|
+
* answers both with nothing, so a view declares what it owns instead of the
|
|
494
|
+
* base reading properties whose meaning belongs to the view — see
|
|
495
|
+
* `util/openViews.ts`, where the recursion over them lives.
|
|
496
|
+
*
|
|
497
|
+
* Required, like `bodyMounted`, so a stand-in that means to be enumerated
|
|
498
|
+
* says so; the empty default means forgetting under-reports rather than
|
|
499
|
+
* putting something that is not a track in front of the readiness marker.
|
|
488
500
|
*/
|
|
489
501
|
ownTracks: AbstractTrackModel[];
|
|
490
|
-
/**
|
|
491
|
-
|
|
492
|
-
* `tracks` / `trackContainers` / `views` is written once, in `BaseViewModel`;
|
|
493
|
-
* anything needing "everything on screen" asks this and `ownTracks` rather
|
|
494
|
-
* than knowing which property a container keeps its children on.
|
|
495
|
-
*/
|
|
496
|
-
allViews: AbstractViewModel[];
|
|
497
|
-
/** every track open on this view or any view nested inside it */
|
|
498
|
-
allTracks: AbstractTrackModel[];
|
|
502
|
+
/** the views nested directly inside this one — see `ownTracks` */
|
|
503
|
+
ownViews: AbstractViewModel[];
|
|
499
504
|
}
|
|
500
505
|
export declare function isViewModel(thing: unknown): thing is AbstractViewModel;
|
|
501
506
|
type Display = {
|
|
@@ -8,11 +8,15 @@ interface PanelScrollTarget {
|
|
|
8
8
|
* the canvas basic display: a plain wheel scrolls it, `shift` keeps scrolling it
|
|
9
9
|
* once `scrollZoom` has claimed the plain wheel for the view, and ctrl/meta is
|
|
10
10
|
* left to the browser and the view's pinch-zoom. Nothing here resizes anything.
|
|
11
|
+
* A sideways-dominant wheel is the view's pan and is declined
|
|
12
|
+
* (`useVirtualScrollWheel`, which says what latching does with that).
|
|
11
13
|
*
|
|
12
14
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
13
15
|
* DOM overflow container, so nothing scrolls them without this. The latch (inside
|
|
14
|
-
* `applyScroll`) owns `preventDefault` but never `stopPropagation
|
|
15
|
-
*
|
|
16
|
+
* `applyScroll`) owns `preventDefault` but never `stopPropagation`: the event
|
|
17
|
+
* still reaches the LGV, which reads that flag and declines a gesture this panel
|
|
18
|
+
* has claimed, so a diagonal wheel scrolls the reads without also panning the
|
|
19
|
+
* genome sideways.
|
|
16
20
|
*
|
|
17
21
|
* ADR-027 keeps wheel-*intent* dispatch per handler, because the panels do not
|
|
18
22
|
* agree on what a gesture means. This is the second of the two rules that
|
|
@@ -4,11 +4,15 @@ import { useVirtualScrollWheel } from './useVirtualScrollWheel.js';
|
|
|
4
4
|
* the canvas basic display: a plain wheel scrolls it, `shift` keeps scrolling it
|
|
5
5
|
* once `scrollZoom` has claimed the plain wheel for the view, and ctrl/meta is
|
|
6
6
|
* left to the browser and the view's pinch-zoom. Nothing here resizes anything.
|
|
7
|
+
* A sideways-dominant wheel is the view's pan and is declined
|
|
8
|
+
* (`useVirtualScrollWheel`, which says what latching does with that).
|
|
7
9
|
*
|
|
8
10
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
9
11
|
* DOM overflow container, so nothing scrolls them without this. The latch (inside
|
|
10
|
-
* `applyScroll`) owns `preventDefault` but never `stopPropagation
|
|
11
|
-
*
|
|
12
|
+
* `applyScroll`) owns `preventDefault` but never `stopPropagation`: the event
|
|
13
|
+
* still reaches the LGV, which reads that flag and declines a gesture this panel
|
|
14
|
+
* has claimed, so a diagonal wheel scrolls the reads without also panning the
|
|
15
|
+
* genome sideways.
|
|
12
16
|
*
|
|
13
17
|
* ADR-027 keeps wheel-*intent* dispatch per handler, because the panels do not
|
|
14
18
|
* agree on what a gesture means. This is the second of the two rules that
|
|
@@ -10,7 +10,7 @@ interface RowScrollTarget {
|
|
|
10
10
|
* Wheel gestures over a **row-stack** panel — the multi-sample variant displays
|
|
11
11
|
* and MAF: `shift`+wheel resizes the rows (a vertical-zoom that keeps the row
|
|
12
12
|
* under the cursor put), a plain wheel scrolls them, and anything the panel
|
|
13
|
-
* can't consume is left to bubble to the view.
|
|
13
|
+
* can't consume — a sideways swipe included — is left to bubble to the view.
|
|
14
14
|
*
|
|
15
15
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
16
16
|
* DOM overflow container, so nothing scrolls them without this.
|
|
@@ -4,7 +4,7 @@ import { useVirtualScrollWheel } from './useVirtualScrollWheel.js';
|
|
|
4
4
|
* Wheel gestures over a **row-stack** panel — the multi-sample variant displays
|
|
5
5
|
* and MAF: `shift`+wheel resizes the rows (a vertical-zoom that keeps the row
|
|
6
6
|
* under the cursor put), a plain wheel scrolls them, and anything the panel
|
|
7
|
-
* can't consume is left to bubble to the view.
|
|
7
|
+
* can't consume — a sideways swipe included — is left to bubble to the view.
|
|
8
8
|
*
|
|
9
9
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
10
10
|
* DOM overflow container, so nothing scrolls them without this.
|
|
@@ -58,7 +58,17 @@ onWheel) {
|
|
|
58
58
|
// not-yet-committed model read.
|
|
59
59
|
const runningRef = useRef(0);
|
|
60
60
|
const applyScroll = useCallback((e, { scrollTop, viewportHeight, scrollableHeight }, commit) => {
|
|
61
|
-
|
|
61
|
+
// Each handler takes the gesture whose dominant axis is its own: a
|
|
62
|
+
// sideways swipe belongs to the view's pan, and consuming its small
|
|
63
|
+
// vertical component would both drift the panel and — since consuming
|
|
64
|
+
// means preventDefault, which is how the view knows to stay out — kill
|
|
65
|
+
// the pan the user actually asked for. A tie is vertical, matching the
|
|
66
|
+
// view's own zoom-vs-pan tie-break. Raw deltas, because both axes of one
|
|
67
|
+
// event share a deltaMode. `holds` is what keeps this from cutting a
|
|
68
|
+
// gesture in half: once the panel has latched, the sideways momentum
|
|
69
|
+
// trailing a vertical scroll stays with the panel.
|
|
70
|
+
const sideways = Math.abs(e.deltaX) > Math.abs(e.deltaY);
|
|
71
|
+
if (scrollableHeight <= 0 || (sideways && !latch.holds(e))) {
|
|
62
72
|
return;
|
|
63
73
|
}
|
|
64
74
|
// A fresh frame (nothing pending) re-syncs to the model's live scrollTop;
|
package/esm/util/wheelZoom.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface WheelZoomTarget {
|
|
|
17
17
|
views: WheelZoomView[];
|
|
18
18
|
scrollZoom: boolean;
|
|
19
19
|
originElement: () => HTMLElement | undefined;
|
|
20
|
+
hold?: <T>(fn: () => T) => T;
|
|
20
21
|
}
|
|
21
22
|
export interface WheelZoomControllerOptions {
|
|
22
23
|
element: HTMLElement;
|
|
@@ -37,9 +38,10 @@ export interface WheelZoomControllerOptions {
|
|
|
37
38
|
/**
|
|
38
39
|
* The one wheel gesture handler for linear views. Its decision matrix:
|
|
39
40
|
*
|
|
41
|
+
* - a wheel a nested scroller already consumed → declined, it owns the gesture
|
|
40
42
|
* - ctrl/meta+wheel, or (scrollZoom && |dy| >= |dx|) → zoom about the cursor
|
|
41
43
|
* - shift+wheel while scrollZoom is on → declined, we pass
|
|
42
|
-
* - a
|
|
44
|
+
* - a sideways delta, unless one arrives mid-zoom → pan by dx
|
|
43
45
|
*
|
|
44
46
|
* Declining shift is what leaves a nested panel its own scroll
|
|
45
47
|
* (`usePanelVirtualScroll`) — it is not a way back to the page. Browsers turn
|
package/esm/util/wheelZoom.js
CHANGED
|
@@ -89,9 +89,10 @@ export function applyZoomAccum(bpPerPx, zoomAccum, elapsedMs) {
|
|
|
89
89
|
/**
|
|
90
90
|
* The one wheel gesture handler for linear views. Its decision matrix:
|
|
91
91
|
*
|
|
92
|
+
* - a wheel a nested scroller already consumed → declined, it owns the gesture
|
|
92
93
|
* - ctrl/meta+wheel, or (scrollZoom && |dy| >= |dx|) → zoom about the cursor
|
|
93
94
|
* - shift+wheel while scrollZoom is on → declined, we pass
|
|
94
|
-
* - a
|
|
95
|
+
* - a sideways delta, unless one arrives mid-zoom → pan by dx
|
|
95
96
|
*
|
|
96
97
|
* Declining shift is what leaves a nested panel its own scroll
|
|
97
98
|
* (`usePanelVirtualScroll`) — it is not a way back to the page. Browsers turn
|
|
@@ -141,26 +142,41 @@ export function createWheelZoomController({ element, resolveTarget, swallowUnhan
|
|
|
141
142
|
s.originLeft = origin.getBoundingClientRect().left;
|
|
142
143
|
}
|
|
143
144
|
const originLeft = s.originLeft;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
const apply = () => {
|
|
146
|
+
transaction(() => {
|
|
147
|
+
if (s.zoomAccum !== 0 && originLeft !== undefined) {
|
|
148
|
+
const offset = s.lastClientX - originLeft;
|
|
149
|
+
for (const view of target.views) {
|
|
150
|
+
view.zoomTo(applyZoomAccum(view.bpPerPx, s.zoomAccum, elapsed), offset);
|
|
151
|
+
}
|
|
149
152
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
if (s.scrollDelta !== 0) {
|
|
154
|
+
for (const view of target.views) {
|
|
155
|
+
view.horizontalScroll(s.scrollDelta);
|
|
156
|
+
}
|
|
154
157
|
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
if (target.hold) {
|
|
161
|
+
target.hold(apply);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
apply();
|
|
165
|
+
}
|
|
157
166
|
}
|
|
158
167
|
s.zoomAccum = 0;
|
|
159
168
|
s.scrollDelta = 0;
|
|
160
169
|
}
|
|
161
170
|
function onWheel(event) {
|
|
162
171
|
onEvent?.(event);
|
|
163
|
-
|
|
172
|
+
// A nested vertical scroller preventDefaults exactly what it consumed, and
|
|
173
|
+
// that flag is still readable on the way up, so the view declines what a
|
|
174
|
+
// panel is already scrolling: a trackpad emits a small deltaX alongside
|
|
175
|
+
// every vertical swipe, which used to pan the genome sideways under someone
|
|
176
|
+
// who was only scrolling reads. No timer of our own — the scroll latch
|
|
177
|
+
// holds the whole continuous gesture, boundary included, and releasing it
|
|
178
|
+
// is the pause that ends the gesture.
|
|
179
|
+
const gated = event.defaultPrevented || (presence !== undefined && !presence.isOver);
|
|
164
180
|
const target = gated ? undefined : resolveTarget(event);
|
|
165
181
|
if (target && !(event.shiftKey && target.scrollZoom)) {
|
|
166
182
|
// a gap longer than the frame-elapsed clamp means this event starts a new
|
|
@@ -216,7 +232,16 @@ export function createWheelZoomController({ element, resolveTarget, swallowUnhan
|
|
|
216
232
|
Math.abs(deltaX) > Math.abs(2 * deltaY)) {
|
|
217
233
|
event.preventDefault();
|
|
218
234
|
}
|
|
219
|
-
|
|
235
|
+
// Pan on a gesture that is actually sideways. A trackpad puts a small
|
|
236
|
+
// deltaX on every vertical swipe, and panning on it drifted the genome
|
|
237
|
+
// under anyone scrolling a track — including where no panel handler
|
|
238
|
+
// reports the scroll for us to decline, as with the natively-scrolling
|
|
239
|
+
// pinned-tracks block. Ties go vertical, the same way the zoom test
|
|
240
|
+
// above resolves them. With scrollZoom on this changes nothing: a
|
|
241
|
+
// vertical-dominant wheel was already taken as a zoom.
|
|
242
|
+
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
243
|
+
s.scrollDelta = accumulateScroll(s.scrollDelta, deltaX);
|
|
244
|
+
}
|
|
220
245
|
}
|
|
221
246
|
frame.schedule(flush);
|
|
222
247
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export interface SettleableView {
|
|
2
|
+
initialized: boolean;
|
|
3
|
+
error: unknown;
|
|
4
|
+
pendingLaunch?: unknown;
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
7
|
* Wait until a view has either initialized — launch blob consumed and all — or
|
|
3
8
|
* failed, and report which.
|
|
@@ -28,13 +33,33 @@
|
|
|
28
33
|
*
|
|
29
34
|
* No time bound, deliberately: `(initialized && launch consumed) || error` is a
|
|
30
35
|
* terminal set, so a slow-but-healthy remote assembly is waited out rather than
|
|
31
|
-
* guessed at.
|
|
36
|
+
* guessed at. `escape` is how a caller adds the one term that is not the view's
|
|
37
|
+
* own — a session-level render failure for an export, `superseded` for a view
|
|
38
|
+
* an `installInitAutorun` apply is waiting on — which is what keeps a caller
|
|
39
|
+
* from re-spelling the whole predicate around it.
|
|
40
|
+
*
|
|
41
|
+
* **Not for the view whose `apply` you are inside.** `applyInitOnce` clears the
|
|
42
|
+
* launch blob only after awaiting `apply`, so `pendingLaunch` is held for the
|
|
43
|
+
* whole of it and this can end only on `escape` or `error`, never on success.
|
|
44
|
+
* A wait on `self` stays hand-rolled; `reference/VIEW_INIT.md` has the shape.
|
|
32
45
|
*
|
|
33
46
|
* @returns true if the view initialized (and consumed any launch), false if it
|
|
34
47
|
* settled on an error
|
|
35
48
|
*/
|
|
36
|
-
export declare function whenViewSettled(view:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
export declare function whenViewSettled(view: SettleableView, escape?: () => boolean): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* The same wait over several views at once — a synteny view's genome rows. An
|
|
52
|
+
* SVG export of a split view keeps its own per-panel loop instead: the split
|
|
53
|
+
* view's `initialized` folds in `views.every(v => v.initialized)` but not each
|
|
54
|
+
* panel's own `pendingLaunch`, so that loop is waiting on a different question
|
|
55
|
+
* by the time it runs.
|
|
56
|
+
*
|
|
57
|
+
* One predicate over `every`, never `Promise.all` of the single form: `when`
|
|
58
|
+
* returns a cancellable promise, so racing N of them against an escape leaves
|
|
59
|
+
* N-1 live reactions behind on the path that escapes. This is one reaction with
|
|
60
|
+
* nothing to cancel.
|
|
61
|
+
*
|
|
62
|
+
* @returns true if every view initialized, false if any settled on an error or
|
|
63
|
+
* the escape fired
|
|
64
|
+
*/
|
|
65
|
+
export declare function whenViewsSettled(views: SettleableView[], escape?: () => boolean): Promise<boolean>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { when } from 'mobx';
|
|
2
|
+
const consumed = (view) => view.initialized && view.pendingLaunch === undefined;
|
|
3
|
+
const done = (view) => consumed(view) || !!view.error;
|
|
2
4
|
/**
|
|
3
5
|
* Wait until a view has either initialized — launch blob consumed and all — or
|
|
4
6
|
* failed, and report which.
|
|
@@ -29,13 +31,39 @@ import { when } from 'mobx';
|
|
|
29
31
|
*
|
|
30
32
|
* No time bound, deliberately: `(initialized && launch consumed) || error` is a
|
|
31
33
|
* terminal set, so a slow-but-healthy remote assembly is waited out rather than
|
|
32
|
-
* guessed at.
|
|
34
|
+
* guessed at. `escape` is how a caller adds the one term that is not the view's
|
|
35
|
+
* own — a session-level render failure for an export, `superseded` for a view
|
|
36
|
+
* an `installInitAutorun` apply is waiting on — which is what keeps a caller
|
|
37
|
+
* from re-spelling the whole predicate around it.
|
|
38
|
+
*
|
|
39
|
+
* **Not for the view whose `apply` you are inside.** `applyInitOnce` clears the
|
|
40
|
+
* launch blob only after awaiting `apply`, so `pendingLaunch` is held for the
|
|
41
|
+
* whole of it and this can end only on `escape` or `error`, never on success.
|
|
42
|
+
* A wait on `self` stays hand-rolled; `reference/VIEW_INIT.md` has the shape.
|
|
33
43
|
*
|
|
34
44
|
* @returns true if the view initialized (and consumed any launch), false if it
|
|
35
45
|
* settled on an error
|
|
36
46
|
*/
|
|
37
|
-
export async function whenViewSettled(view) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
export async function whenViewSettled(view, escape = () => false) {
|
|
48
|
+
await when(() => escape() || done(view));
|
|
49
|
+
return consumed(view);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The same wait over several views at once — a synteny view's genome rows. An
|
|
53
|
+
* SVG export of a split view keeps its own per-panel loop instead: the split
|
|
54
|
+
* view's `initialized` folds in `views.every(v => v.initialized)` but not each
|
|
55
|
+
* panel's own `pendingLaunch`, so that loop is waiting on a different question
|
|
56
|
+
* by the time it runs.
|
|
57
|
+
*
|
|
58
|
+
* One predicate over `every`, never `Promise.all` of the single form: `when`
|
|
59
|
+
* returns a cancellable promise, so racing N of them against an escape leaves
|
|
60
|
+
* N-1 live reactions behind on the path that escapes. This is one reaction with
|
|
61
|
+
* nothing to cancel.
|
|
62
|
+
*
|
|
63
|
+
* @returns true if every view initialized, false if any settled on an error or
|
|
64
|
+
* the escape fired
|
|
65
|
+
*/
|
|
66
|
+
export async function whenViewsSettled(views, escape = () => false) {
|
|
67
|
+
await when(() => escape() || views.every(done));
|
|
68
|
+
return views.every(consumed);
|
|
41
69
|
}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
export { legacyInitMessage, unknownKeysMessage } from './unknownSnapshotKeys.ts';
|
|
2
|
-
import type { IAnyModelType, IModelType, SnapshotIn } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import type { IAnyModelType, IModelType, SnapshotIn, SnapshotOut } from '@jbrowse/mobx-state-tree';
|
|
3
|
+
/**
|
|
4
|
+
* The slice of a PluginManager the partition reads: which view type a
|
|
5
|
+
* snapshot's `type` names, and that type's state model as REGISTERED — the
|
|
6
|
+
* one `extendViewType` may have composed more properties onto after the
|
|
7
|
+
* factory wrapped its own.
|
|
8
|
+
*/
|
|
9
|
+
export interface ViewTypeLookup {
|
|
10
|
+
viewTypes: {
|
|
11
|
+
has: (name: string) => boolean;
|
|
12
|
+
};
|
|
13
|
+
getViewType: (name: string) => {
|
|
14
|
+
isStateModelLoaded: boolean;
|
|
15
|
+
stateModel: IAnyModelType;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface LaunchInputOptions<M extends IAnyModelType> {
|
|
19
|
+
/** the PluginManager; without it the partition sees only the wrapped model */
|
|
20
|
+
registry?: ViewTypeLookup;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a snapshot shows the launch applied — rows built, regions set. The
|
|
23
|
+
* blob persists only until then: a snapshot taken mid-load (an autosave
|
|
24
|
+
* firing before the launch autorun navigates) has to rebuild the view rather
|
|
25
|
+
* than drop it to the import form, and a materialized view has nothing left
|
|
26
|
+
* to rebuild. `installInitAutorun` keys its failure policy off the same line,
|
|
27
|
+
* read from the live node. `Partial` because a `stripDefault` property is
|
|
28
|
+
* absent from the snapshot when empty, whatever its declared type says.
|
|
29
|
+
*/
|
|
30
|
+
materialized: (snap: Partial<SnapshotOut<M>>) => boolean;
|
|
31
|
+
}
|
|
3
32
|
/**
|
|
4
33
|
* How a launch key's value is told apart from the state property of the same
|
|
5
34
|
* name. A key whose name collides with nothing is `launch`; the rest name the
|
|
@@ -93,6 +122,17 @@ export type LaunchInputModel<M extends IAnyModelType, Commands, PassThrough exte
|
|
|
93
122
|
export declare function defineLaunchKeys<Commands>(): <const P extends readonly string[] = readonly never[]>(keys: Record<keyof Commands, LaunchKeySpec>, { passThrough }?: {
|
|
94
123
|
passThrough?: P;
|
|
95
124
|
}) => LaunchKeyRegistration<Commands, P[number]>;
|
|
125
|
+
/**
|
|
126
|
+
* The part of a launch spec that belongs on the snapshot: everything but the
|
|
127
|
+
* launch keys, and the identity and `launch` keys no spec may set. For a caller
|
|
128
|
+
* that applies the launch itself and builds the view from a snapshot — a
|
|
129
|
+
* synteny row, whose parent navigates it and opens its tracks — so what it
|
|
130
|
+
* hands MST is settings only, and the view's own partition sorts those into
|
|
131
|
+
* properties and reported typos.
|
|
132
|
+
*/
|
|
133
|
+
export declare function snapshotSettings(spec: Record<string, unknown>, registration: LaunchKeyRegistration<unknown, string> | undefined): {
|
|
134
|
+
[k: string]: unknown;
|
|
135
|
+
};
|
|
96
136
|
/**
|
|
97
137
|
* Accept a view's settings written directly on the view object, and move the
|
|
98
138
|
* ones a launcher has to resolve into the internal `launch` property.
|
|
@@ -104,6 +144,11 @@ export declare function defineLaunchKeys<Commands>(): <const P extends readonly
|
|
|
104
144
|
* are about to be rejected. `afterAttach` — reached only by a snapshot that won
|
|
105
145
|
* — reports what the partition captured.
|
|
106
146
|
*
|
|
147
|
+
* `registry` is the PluginManager. With it the partition classifies against
|
|
148
|
+
* the view type as registered, `extendViewType`'s added properties included;
|
|
149
|
+
* without it, against the model as wrapped here. `materialized` draws the line
|
|
150
|
+
* past which the blob is no longer persisted; see `LaunchInputOptions`.
|
|
151
|
+
*
|
|
107
152
|
* ORDER: MST runs preprocessors in the reverse of the order they were added, so
|
|
108
153
|
* this belongs on the chain BEFORE a view's own legacy-key preprocessor, where
|
|
109
154
|
* it partitions the snapshot MST finally consumes rather than a key that remap
|
|
@@ -120,4 +165,4 @@ export declare function defineLaunchKeys<Commands>(): <const P extends readonly
|
|
|
120
165
|
* without a word, and `ViewType.stateModel` and every `.properties`
|
|
121
166
|
* introspection site break with it.
|
|
122
167
|
*/
|
|
123
|
-
export declare function withLaunchInput<M extends IAnyModelType, Commands, PassThrough extends string>(model: M, registration: LaunchKeyRegistration<Commands, PassThrough>): LaunchInputModel<M, Commands, PassThrough>;
|
|
168
|
+
export declare function withLaunchInput<M extends IAnyModelType, Commands, PassThrough extends string>(model: M, registration: LaunchKeyRegistration<Commands, PassThrough>, { registry, materialized }: LaunchInputOptions<M>): LaunchInputModel<M, Commands, PassThrough>;
|