@jbrowse/product-core 4.3.0 → 5.0.0-beta.1
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/esm/RootModel/BaseRootModel.d.ts +629 -304
- package/esm/RootModel/BaseRootModel.js +138 -14
- package/esm/RootModel/HierarchicalConfig.d.ts +57 -21
- package/esm/RootModel/HierarchicalConfig.js +36 -0
- package/esm/RootModel/InternetAccounts.d.ts +18 -1
- package/esm/RootModel/InternetAccounts.js +120 -22
- package/esm/RootModel/PreferencesConfig.d.ts +53 -0
- package/esm/RootModel/PreferencesConfig.js +61 -0
- package/esm/RootModel/createConfigModel.d.ts +232 -0
- package/esm/RootModel/createConfigModel.js +56 -0
- package/esm/RootModel/index.d.ts +10 -5
- package/esm/RootModel/index.js +10 -5
- package/esm/RootModel/menuItems.d.ts +24 -0
- package/esm/RootModel/menuItems.js +143 -0
- package/esm/Session/BaseSession.d.ts +310 -11
- package/esm/Session/BaseSession.js +448 -14
- package/esm/Session/Connections.d.ts +114 -8
- package/esm/Session/Connections.js +195 -74
- package/esm/Session/DrawerWidgets.d.ts +115 -12
- package/esm/Session/DrawerWidgets.js +161 -42
- package/esm/Session/MultipleViews.d.ts +1266 -1128
- package/esm/Session/MultipleViews.js +357 -66
- package/esm/Session/Preferences.d.ts +1665 -0
- package/esm/Session/Preferences.js +54 -0
- package/esm/Session/ReferenceManagement.d.ts +28 -3
- package/esm/Session/ReferenceManagement.js +56 -41
- package/esm/Session/SessionTracks.d.ts +1275 -1138
- package/esm/Session/SessionTracks.js +532 -35
- package/esm/Session/Themes.d.ts +87 -2
- package/esm/Session/Themes.js +156 -23
- package/esm/Session/TrackMenu.d.ts +112 -0
- package/esm/Session/TrackMenu.js +200 -0
- package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
- package/esm/Session/TrackMenuSessionMixin.js +41 -0
- package/esm/Session/Tracks.d.ts +1169 -1117
- package/esm/Session/Tracks.js +160 -20
- package/esm/Session/assertSessionModel.d.ts +21 -0
- package/esm/Session/assertSessionModel.js +1 -0
- package/esm/Session/finalizeSession.d.ts +16 -0
- package/esm/Session/finalizeSession.js +29 -0
- package/esm/Session/index.d.ts +22 -10
- package/esm/Session/index.js +13 -10
- package/esm/Session/shareableSnapshot.d.ts +77 -0
- package/esm/Session/shareableSnapshot.js +196 -0
- package/esm/Session/temporaryAssemblyTracks.d.ts +23 -0
- package/esm/Session/temporaryAssemblyTracks.js +38 -0
- package/esm/assertExtends.d.ts +55 -0
- package/esm/assertExtends.js +1 -0
- package/esm/controllerTracks.d.ts +56 -0
- package/esm/controllerTracks.js +77 -0
- package/esm/destroyViewState.d.ts +21 -0
- package/esm/destroyViewState.js +29 -0
- package/esm/index.d.ts +31 -4
- package/esm/index.js +31 -4
- package/esm/loadPlugins.d.ts +33 -0
- package/esm/loadPlugins.js +38 -0
- package/esm/localFiles.d.ts +74 -0
- package/esm/localFiles.js +156 -0
- package/esm/observeSession.d.ts +56 -0
- package/esm/observeSession.js +91 -0
- package/esm/pluginInput.d.ts +32 -0
- package/esm/pluginInput.js +19 -0
- package/esm/pruneUnbuildableNodes.d.ts +109 -0
- package/esm/pruneUnbuildableNodes.js +568 -0
- package/esm/resolveAssemblies.d.ts +44 -0
- package/esm/resolveAssemblies.js +55 -0
- package/esm/rpcWorker.d.ts +26 -0
- package/esm/rpcWorker.js +96 -15
- package/esm/scheduleDetachedDestroy.d.ts +18 -0
- package/esm/scheduleDetachedDestroy.js +24 -0
- package/esm/sessionMigrations/index.d.ts +53 -0
- package/esm/sessionMigrations/index.js +432 -0
- package/esm/sessionUrl.d.ts +41 -0
- package/esm/sessionUrl.js +53 -0
- package/esm/sessionUtils.d.ts +69 -0
- package/esm/sessionUtils.js +613 -0
- package/esm/siblingCast.d.ts +14 -0
- package/esm/siblingCast.js +16 -0
- package/esm/snapshotUtils.d.ts +8 -0
- package/esm/snapshotUtils.js +12 -0
- package/esm/ui/AboutDialog.d.ts +3 -6
- package/esm/ui/AboutDialog.js +4 -4
- package/esm/ui/AboutDialogContents.d.ts +2 -6
- package/esm/ui/AboutDialogContents.js +34 -13
- package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
- package/esm/ui/AssemblyInfoPanel.js +69 -0
- package/esm/ui/DisplayDefaultsSection.d.ts +36 -0
- package/esm/ui/DisplayDefaultsSection.js +65 -0
- package/esm/ui/Drawer.d.ts +21 -0
- package/esm/ui/Drawer.js +60 -0
- package/esm/ui/DrawerControls.d.ts +5 -0
- package/esm/ui/DrawerControls.js +28 -0
- package/esm/ui/DrawerHeader.d.ts +6 -0
- package/esm/ui/DrawerHeader.js +36 -0
- package/esm/ui/DrawerWidget.d.ts +5 -0
- package/esm/ui/DrawerWidget.js +12 -0
- package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
- package/esm/ui/DrawerWidgetSelector.js +40 -0
- package/esm/ui/FileInfoPanel.d.ts +3 -6
- package/esm/ui/FileInfoPanel.js +35 -26
- package/esm/ui/HeaderButtons.d.ts +7 -1
- package/esm/ui/HeaderButtons.js +23 -18
- package/esm/ui/ModalWidget.d.ts +12 -0
- package/esm/ui/ModalWidget.js +33 -0
- package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
- package/esm/ui/ModalWidgetAppBar.js +9 -0
- package/esm/ui/PreferencesDialog.d.ts +56 -0
- package/esm/ui/PreferencesDialog.js +145 -0
- package/esm/ui/PreferencesResetDialog.d.ts +8 -0
- package/esm/ui/PreferencesResetDialog.js +19 -0
- package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
- package/esm/ui/RefNameAliasesDialog.js +65 -0
- package/esm/ui/RefNameInfoDialog.d.ts +3 -6
- package/esm/ui/RefNameInfoDialog.js +57 -63
- package/esm/ui/WidgetBody.d.ts +18 -0
- package/esm/ui/WidgetBody.js +22 -0
- package/esm/ui/WidgetHeading.d.ts +7 -0
- package/esm/ui/WidgetHeading.js +7 -0
- package/esm/ui/drawerLayout.d.ts +18 -0
- package/esm/ui/drawerLayout.js +22 -0
- package/esm/ui/index.d.ts +13 -1
- package/esm/ui/index.js +12 -1
- package/esm/ui/util.d.ts +37 -4
- package/esm/ui/util.js +35 -38
- package/esm/useEngineLifecycle.d.ts +13 -0
- package/esm/useEngineLifecycle.js +22 -0
- package/package.json +11 -15
- package/esm/RootModel/FormatAbout.d.ts +0 -12
- package/esm/RootModel/FormatAbout.js +0 -15
- package/esm/RootModel/FormatDetails.d.ts +0 -24
- package/esm/RootModel/FormatDetails.js +0 -27
- package/esm/Session/DialogQueue.d.ts +0 -17
- package/esm/Session/DialogQueue.js +0 -31
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { storeBlobLocation } from '@jbrowse/core/util';
|
|
2
|
+
/**
|
|
3
|
+
* Register in-memory bytes as browser-local files JBrowse can read, returning
|
|
4
|
+
* the `name -> BlobLocation` map {@link resolveLocalFileUris} substitutes with.
|
|
5
|
+
*
|
|
6
|
+
* This is the escape from "the data has to be on a web server first". A
|
|
7
|
+
* `BlobLocation` opens as a `BlobFile`, which serves byte ranges out of
|
|
8
|
+
* `Blob.slice()` — so a bgzipped+tabixed file, a BAM with its `.bai`, or a
|
|
9
|
+
* bigWig is *seeked into* exactly as if it were remote, and only the bytes the
|
|
10
|
+
* current view needs are ever touched. That is the whole point of doing this
|
|
11
|
+
* rather than inlining features into a config: a 200k-feature table is ~20MB of
|
|
12
|
+
* JSON to serialize, ship, and hold in memory, while the same data as a tabix
|
|
13
|
+
* file is a few MB the view reads a few KB of at a time.
|
|
14
|
+
*
|
|
15
|
+
* For hosts that hold their data in a process rather than at a URL — a notebook
|
|
16
|
+
* kernel, an R session, a desktop app — this is the way in, and it needs no web
|
|
17
|
+
* server, no CORS, and no temporary public bucket.
|
|
18
|
+
*/
|
|
19
|
+
export function registerLocalFiles(files) {
|
|
20
|
+
return Object.fromEntries(Object.entries(files).map(([name, data]) => [name, register(name, data)]));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fold newly-offered files into a controller's registered map, registering only
|
|
24
|
+
* the names it has not seen. Registering is what mints the blobIds, so a host
|
|
25
|
+
* re-stating its whole file set on every update — which is what a declarative
|
|
26
|
+
* host does — pays for each file once rather than once per update.
|
|
27
|
+
*
|
|
28
|
+
* Only ever adds: a name already registered keeps its existing blob, because a
|
|
29
|
+
* live track config points at that blobId and swapping it underneath would
|
|
30
|
+
* leave the track reading bytes nothing else references.
|
|
31
|
+
*/
|
|
32
|
+
export function mergeLocalFiles(current, files) {
|
|
33
|
+
const fresh = Object.fromEntries(Object.entries(files).filter(([name]) => !current[name]));
|
|
34
|
+
return { ...current, ...registerLocalFiles(fresh) };
|
|
35
|
+
}
|
|
36
|
+
// Registering the same bytes twice used to mint a second blobId and leave the
|
|
37
|
+
// first in core's process-global blobMap forever. Nothing collects that map, so
|
|
38
|
+
// a host that hands the same `localFiles` to a new controller each time it
|
|
39
|
+
// rebuilds — which is how a host swaps genome or track set, now that the
|
|
40
|
+
// controller has no setters that rebuild internally — grew it without bound.
|
|
41
|
+
//
|
|
42
|
+
// Keyed on the caller's own object, which is the only identity available: bytes
|
|
43
|
+
// are not comparable cheaply, and two Uint8Arrays over the same buffer are
|
|
44
|
+
// legitimately different files. A WeakMap so a host dropping its reference to
|
|
45
|
+
// the data lets the entry go, and per name inside it because the same bytes can
|
|
46
|
+
// legitimately be registered under two names and a BlobLocation carries one.
|
|
47
|
+
const registered = new WeakMap();
|
|
48
|
+
function register(name, data) {
|
|
49
|
+
const byName = registered.get(data) ?? new Map();
|
|
50
|
+
const seen = byName.get(name);
|
|
51
|
+
if (seen) {
|
|
52
|
+
return seen;
|
|
53
|
+
}
|
|
54
|
+
const location = storeBlobLocation({
|
|
55
|
+
blob: data instanceof File ? data : new File([data], name),
|
|
56
|
+
});
|
|
57
|
+
byName.set(name, location);
|
|
58
|
+
registered.set(data, byName);
|
|
59
|
+
return location;
|
|
60
|
+
}
|
|
61
|
+
// A location node is `{ uri }` and at most the two keys that travel with it.
|
|
62
|
+
// Anything else carrying a `uri` is a shorthand adapter (`{ type, uri }`), and
|
|
63
|
+
// telling the two apart is what stops resolveLocalFileUris replacing a whole
|
|
64
|
+
// adapter with a bare location — see its comment.
|
|
65
|
+
const LOCATION_KEYS = new Set(['uri', 'baseUri', 'locationType']);
|
|
66
|
+
function isLocationNode(record) {
|
|
67
|
+
return Object.keys(record).every(key => LOCATION_KEYS.has(key));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Expand every shorthand adapter snapshot (`{ type, uri }`) in a config to the
|
|
71
|
+
* canonical location keys its config schema declares, using that adapter type's
|
|
72
|
+
* own `normalizeSnapshot` — the hook that exists so "downstream code can read
|
|
73
|
+
* location keys without knowing each shorthand".
|
|
74
|
+
*
|
|
75
|
+
* {@link resolveLocalFileUris} has to run on the expanded form, and this is the
|
|
76
|
+
* difference between `localFiles` working and silently doing nothing: the
|
|
77
|
+
* shorthand puts `uri` on the *adapter*, so substituting there would replace
|
|
78
|
+
* `{ type: 'BamAdapter', uri: 'x.bam' }` with a bare BlobLocation and take the
|
|
79
|
+
* adapter's type with it. Expanding first moves the name onto `bamLocation`,
|
|
80
|
+
* where it is a location node and substitution is what it looks like. The
|
|
81
|
+
* expansion also derives the conventional index sibling (`.bai`/`.tbi`/`.crai`)
|
|
82
|
+
* from the same string, so registering `x.bam` and `x.bam.bai` is enough and no
|
|
83
|
+
* caller has to know which adapter wanted which file.
|
|
84
|
+
*
|
|
85
|
+
* Idempotent, and a no-op for an already-canonical config or an adapter type
|
|
86
|
+
* that declares no shorthand.
|
|
87
|
+
*/
|
|
88
|
+
export function normalizeAdapterSnapshots(config, pluginManager) {
|
|
89
|
+
if (Array.isArray(config)) {
|
|
90
|
+
return config.map(item => normalizeAdapterSnapshots(item, pluginManager));
|
|
91
|
+
}
|
|
92
|
+
if (typeof config === 'object' && config !== null) {
|
|
93
|
+
const record = config;
|
|
94
|
+
// `type` names a track, a display and an adapter alike, so this asks
|
|
95
|
+
// whether the name is an adapter rather than demanding that it is —
|
|
96
|
+
// getAdapterType throws on a miss, and 'AlignmentsTrack' is a miss
|
|
97
|
+
const normalize = typeof record.type === 'string' &&
|
|
98
|
+
pluginManager.hasAdapterType(record.type)
|
|
99
|
+
? pluginManager.getAdapterType(record.type).normalizeSnapshot
|
|
100
|
+
: undefined;
|
|
101
|
+
const normalized = normalize?.(record) ?? record;
|
|
102
|
+
// Every normalizeSnapshot returns its argument unchanged when there was no
|
|
103
|
+
// shorthand to expand, so identity is the signal that `uri` was consumed —
|
|
104
|
+
// and it then has to GO. The same function runs again as the config
|
|
105
|
+
// schema's preProcessSnapshot when MST builds the tree, and a surviving
|
|
106
|
+
// `uri` makes that second pass rebuild the location from the string,
|
|
107
|
+
// overwriting the BlobLocation just substituted into it. That failure is
|
|
108
|
+
// invisible: the track ends up pointing at a relative URL named
|
|
109
|
+
// `volvox-sorted.bam`, which 404s against the host page's own origin.
|
|
110
|
+
const expanded = normalized === record
|
|
111
|
+
? record
|
|
112
|
+
: Object.fromEntries(Object.entries(normalized).filter(([key]) => key !== 'uri'));
|
|
113
|
+
return Object.fromEntries(Object.entries(expanded).map(([key, value]) => [
|
|
114
|
+
key,
|
|
115
|
+
normalizeAdapterSnapshots(value, pluginManager),
|
|
116
|
+
]));
|
|
117
|
+
}
|
|
118
|
+
return config;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Rewrite every `{ uri: <a registered name> }` in a track config to that file's
|
|
122
|
+
* `BlobLocation`, in place of nothing else — unregistered URIs are left alone,
|
|
123
|
+
* so a config can mix local and remote files freely.
|
|
124
|
+
*
|
|
125
|
+
* Runs on a config whose locations are already canonical, which is what
|
|
126
|
+
* `guessTrackConf` (for a loose track spec) and {@link normalizeAdapterSnapshots}
|
|
127
|
+
* (for a written-out one) each produce. So a host registers `peaks.bed.gz` and
|
|
128
|
+
* `peaks.bed.gz.tbi` under their plain names, the config names both under
|
|
129
|
+
* `bedGzLocation` and `index.location`, and this swaps both for blobs — index
|
|
130
|
+
* and all, with no host-side knowledge of which adapter wanted which sibling.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveLocalFileUris(config, locations) {
|
|
133
|
+
if (Array.isArray(config)) {
|
|
134
|
+
return config.map(item => resolveLocalFileUris(item, locations));
|
|
135
|
+
}
|
|
136
|
+
if (typeof config === 'object' && config !== null) {
|
|
137
|
+
const record = config;
|
|
138
|
+
const uri = record.uri;
|
|
139
|
+
// the whole location node is replaced: a BlobLocation carries blobId and
|
|
140
|
+
// its own locationType, and leaving the old uri alongside would make it
|
|
141
|
+
// fail isBlobLocation's discrimination. Only a *location* node, though — a
|
|
142
|
+
// shorthand adapter also has a `uri`, and replacing that node discards the
|
|
143
|
+
// adapter's type and every sibling key, which the config schema then papers
|
|
144
|
+
// over with its `/path/to/my.bam` default: a track that loads nothing, with
|
|
145
|
+
// nothing logged. normalizeAdapterSnapshots is what turns the shorthand
|
|
146
|
+
// into a location node so this substitutes it.
|
|
147
|
+
if (typeof uri === 'string' && locations[uri] && isLocationNode(record)) {
|
|
148
|
+
return locations[uri];
|
|
149
|
+
}
|
|
150
|
+
return Object.fromEntries(Object.entries(record).map(([key, value]) => [
|
|
151
|
+
key,
|
|
152
|
+
resolveLocalFileUris(value, locations),
|
|
153
|
+
]));
|
|
154
|
+
}
|
|
155
|
+
return config;
|
|
156
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SessionSnapshot } from './sessionUrl.ts';
|
|
2
|
+
type OpenTracks = {
|
|
3
|
+
configuration: {
|
|
4
|
+
trackId: string;
|
|
5
|
+
};
|
|
6
|
+
}[];
|
|
7
|
+
interface ObservedView {
|
|
8
|
+
id?: string;
|
|
9
|
+
coarseVisibleLocStrings?: string;
|
|
10
|
+
views?: {
|
|
11
|
+
coarseVisibleLocStrings?: string;
|
|
12
|
+
}[];
|
|
13
|
+
tracks?: OpenTracks;
|
|
14
|
+
levels?: {
|
|
15
|
+
tracks: OpenTracks;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
interface ObservedSession {
|
|
19
|
+
name: string;
|
|
20
|
+
views: ObservedView[];
|
|
21
|
+
selection?: unknown;
|
|
22
|
+
}
|
|
23
|
+
interface ObservedRoot {
|
|
24
|
+
session?: ObservedSession;
|
|
25
|
+
}
|
|
26
|
+
/** where one view is looking: a locstring, or one per panel for a comparative view */
|
|
27
|
+
export type ViewLocation = string | string[] | undefined;
|
|
28
|
+
export interface SessionObservers {
|
|
29
|
+
/** fires with every view's visible region when the layout settles */
|
|
30
|
+
onLocationChange?: (locations: ViewLocation[]) => void;
|
|
31
|
+
/** fires with the serialized feature when one is selected in any view */
|
|
32
|
+
onFeatureSelect?: (feature: unknown) => void;
|
|
33
|
+
/**
|
|
34
|
+
* fires with a plain-JSON snapshot of the layout the user built, in the shape
|
|
35
|
+
* the `session` option takes — so "save this arrangement" is storing this
|
|
36
|
+
* value and reopening it is passing it back
|
|
37
|
+
*/
|
|
38
|
+
onSessionChange?: (session: SessionSnapshot) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The live session as a plain JSON snapshot, ready to hand straight back as a
|
|
42
|
+
* product's `session` option (or its controller's `setSession`). The
|
|
43
|
+
* uncompressed twin of `encodeSession`, for hosts that move JSON rather than
|
|
44
|
+
* URLs — a notebook kernel, an R session, a "save this layout" button.
|
|
45
|
+
*
|
|
46
|
+
* Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't:
|
|
47
|
+
* display settings a user is *inheriting* from a promoted display-type default
|
|
48
|
+
* live in their own browser, never in the session, so a raw snapshot replays
|
|
49
|
+
* differently for whoever opens it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getSessionSnapshot(session: ObservedSession): SessionSnapshot;
|
|
52
|
+
/**
|
|
53
|
+
* Wire the read-backs a host asked for, returning one disposer for all of them.
|
|
54
|
+
*/
|
|
55
|
+
export declare function observeSession(root: ObservedRoot, { onLocationChange, onFeatureSelect, onSessionChange }: SessionObservers): () => void;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { isFeature } from '@jbrowse/core/util';
|
|
2
|
+
import { autorun, untracked } from 'mobx';
|
|
3
|
+
import { getShareableSessionSnapshot } from './Session/shareableSnapshot.js';
|
|
4
|
+
/**
|
|
5
|
+
* The live session as a plain JSON snapshot, ready to hand straight back as a
|
|
6
|
+
* product's `session` option (or its controller's `setSession`). The
|
|
7
|
+
* uncompressed twin of `encodeSession`, for hosts that move JSON rather than
|
|
8
|
+
* URLs — a notebook kernel, an R session, a "save this layout" button.
|
|
9
|
+
*
|
|
10
|
+
* Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't:
|
|
11
|
+
* display settings a user is *inheriting* from a promoted display-type default
|
|
12
|
+
* live in their own browser, never in the session, so a raw snapshot replays
|
|
13
|
+
* differently for whoever opens it.
|
|
14
|
+
*/
|
|
15
|
+
export function getSessionSnapshot(session) {
|
|
16
|
+
// `name` is restated rather than the whole thing cast: the bake returns an
|
|
17
|
+
// open record, but the model guarantees the session has one
|
|
18
|
+
return {
|
|
19
|
+
...getShareableSessionSnapshot(session),
|
|
20
|
+
name: session.name,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// A linear view reports its visible region as one coarseVisibleLocStrings
|
|
24
|
+
// string; a comparative view (synteny/dotplot) has no single string, so report
|
|
25
|
+
// one entry per panel instead.
|
|
26
|
+
function viewLocation(view) {
|
|
27
|
+
return typeof view.coarseVisibleLocStrings === 'string'
|
|
28
|
+
? view.coarseVisibleLocStrings || undefined
|
|
29
|
+
: view.views
|
|
30
|
+
?.map(v => v.coarseVisibleLocStrings)
|
|
31
|
+
.filter(loc => loc !== undefined);
|
|
32
|
+
}
|
|
33
|
+
// Every trackId a view has open. A synteny view keeps its tracks per level
|
|
34
|
+
// rather than in one flat list, so reading `tracks` alone reports none of them —
|
|
35
|
+
// and the read-back then never fired when a synteny track was opened.
|
|
36
|
+
function viewTracks(view) {
|
|
37
|
+
return (view.tracks ?? view.levels?.flatMap(l => l.tracks) ?? [])
|
|
38
|
+
.map(t => t.configuration.trackId)
|
|
39
|
+
.join(',');
|
|
40
|
+
}
|
|
41
|
+
// The coarse signal the read-backs ride on: which views exist, what each has
|
|
42
|
+
// open, and where each is looking. Deliberately NOT the whole snapshot —
|
|
43
|
+
// depending on that would pull in offsetPx, and a snapshot is kilobytes, so a
|
|
44
|
+
// drag would push one per pointer event across the host's wire.
|
|
45
|
+
// coarseVisibleLocStrings is JBrowse's own debounced location, so this settles
|
|
46
|
+
// after a pan rather than firing during it.
|
|
47
|
+
//
|
|
48
|
+
// A plain function, not an action: actions run untracked, and computing this is
|
|
49
|
+
// precisely what subscribes the autorun below.
|
|
50
|
+
function layoutSignal(views) {
|
|
51
|
+
return views
|
|
52
|
+
.map(v => `${v.id}:${JSON.stringify(viewLocation(v))}:${viewTracks(v)}`)
|
|
53
|
+
.join('|');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Wire the read-backs a host asked for, returning one disposer for all of them.
|
|
57
|
+
*/
|
|
58
|
+
export function observeSession(root, { onLocationChange, onFeatureSelect, onSessionChange }) {
|
|
59
|
+
const disposers = [];
|
|
60
|
+
// `session` is read inside each autorun and never captured: setSession
|
|
61
|
+
// replaces the whole node (`self.session = cast(...)` in BaseRootModel), so a
|
|
62
|
+
// captured one is a dead MST node the moment a session is restored, and the
|
|
63
|
+
// read-back would go silent for the rest of the product's life.
|
|
64
|
+
if (onLocationChange ?? onSessionChange) {
|
|
65
|
+
disposers.push(autorun(() => {
|
|
66
|
+
const { session } = root;
|
|
67
|
+
if (!session) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// the only dependency this autorun should have
|
|
71
|
+
layoutSignal(session.views);
|
|
72
|
+
onLocationChange?.(session.views.map(viewLocation));
|
|
73
|
+
// untracked, so reading the snapshot adds no dependencies of its own
|
|
74
|
+
// eslint-disable-next-line no-restricted-syntax -- effect input: the callback consumes the snapshot, the views are the trigger
|
|
75
|
+
onSessionChange?.(untracked(() => getSessionSnapshot(session)));
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
if (onFeatureSelect) {
|
|
79
|
+
disposers.push(autorun(() => {
|
|
80
|
+
const selection = root.session?.selection;
|
|
81
|
+
if (isFeature(selection)) {
|
|
82
|
+
onFeatureSelect(selection.toJSON());
|
|
83
|
+
}
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
return () => {
|
|
87
|
+
for (const dispose of disposers) {
|
|
88
|
+
dispose();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PluginConstructor } from '@jbrowse/core/Plugin';
|
|
2
|
+
import type { PluginRecord } from '@jbrowse/core/PluginLoader';
|
|
3
|
+
/**
|
|
4
|
+
* A plugin an embedded product's caller can register: either the class itself,
|
|
5
|
+
* or the `{ plugin, definition }` record `loadPlugins` returns.
|
|
6
|
+
*
|
|
7
|
+
* Prefer passing the record straight through. The `definition` is what lets the
|
|
8
|
+
* RPC worker load the same plugin on its side — hand it only the class and the
|
|
9
|
+
* plugin exists on the main thread but not in the worker, so anything it
|
|
10
|
+
* contributes that runs there (an adapter, most commonly) fails to resolve.
|
|
11
|
+
*/
|
|
12
|
+
export type PluginInput = PluginConstructor | PluginRecord;
|
|
13
|
+
/**
|
|
14
|
+
* Turn caller-supplied plugins into `PluginManager` load records, keeping each
|
|
15
|
+
* runtime plugin's `definition` when it has one.
|
|
16
|
+
*
|
|
17
|
+
* That definition is the whole point: `PluginManager.addPlugin` only records
|
|
18
|
+
* `runtimePluginDefinitions` for records that carry one, and `RpcManager` ships
|
|
19
|
+
* that list to the RPC worker as its boot config so the worker can fetch the
|
|
20
|
+
* same plugin. Dropping it leaves the plugin main-thread-only, and the failure
|
|
21
|
+
* surfaces far away — as an unknown adapter/track type inside the worker.
|
|
22
|
+
*
|
|
23
|
+
* Shared by every embedded product rather than restated, because getting it
|
|
24
|
+
* wrong is invisible until someone combines a runtime plugin with a worker.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toPluginLoadRecord(p: PluginInput): {
|
|
27
|
+
plugin: import("@jbrowse/core/Plugin").default;
|
|
28
|
+
definition?: undefined;
|
|
29
|
+
} | {
|
|
30
|
+
plugin: import("@jbrowse/core/Plugin").default;
|
|
31
|
+
definition: import("@jbrowse/core/pluginDefinitions").PluginDefinition;
|
|
32
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn caller-supplied plugins into `PluginManager` load records, keeping each
|
|
3
|
+
* runtime plugin's `definition` when it has one.
|
|
4
|
+
*
|
|
5
|
+
* That definition is the whole point: `PluginManager.addPlugin` only records
|
|
6
|
+
* `runtimePluginDefinitions` for records that carry one, and `RpcManager` ships
|
|
7
|
+
* that list to the RPC worker as its boot config so the worker can fetch the
|
|
8
|
+
* same plugin. Dropping it leaves the plugin main-thread-only, and the failure
|
|
9
|
+
* surfaces far away — as an unknown adapter/track type inside the worker.
|
|
10
|
+
*
|
|
11
|
+
* Shared by every embedded product rather than restated, because getting it
|
|
12
|
+
* wrong is invisible until someone combines a runtime plugin with a worker.
|
|
13
|
+
*/
|
|
14
|
+
export function toPluginLoadRecord(p) {
|
|
15
|
+
// a plugin class is a function; a loadPlugins record is a plain object
|
|
16
|
+
return typeof p === 'function'
|
|
17
|
+
? { plugin: new p() }
|
|
18
|
+
: { plugin: new p.plugin(), definition: p.definition };
|
|
19
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
+
import type { PluggableElementTypeGroup } from '@jbrowse/core/PluginManager';
|
|
3
|
+
import type { IAnyType } from '@jbrowse/mobx-state-tree';
|
|
4
|
+
/**
|
|
5
|
+
* A session snapshot that arrives from somewhere else — a share link, a desktop
|
|
6
|
+
* "export to web" URL, a session.json — can name a pluggable type this build has
|
|
7
|
+
* no plugin for. Every such collection is `types.map`/`types.array` over a bare
|
|
8
|
+
* `pluggableMstType` union with no dispatcher, so one entry the union cannot
|
|
9
|
+
* match throws `No type is applicable for the union` out of the `cast` in
|
|
10
|
+
* `setSession` — outside the try that `filterSessionInPlace` sits in. The whole
|
|
11
|
+
* session is lost, and jbrowse-web's fallback then advises the recipient to ask
|
|
12
|
+
* for a link made with the Share button, which is often exactly what they were
|
|
13
|
+
* sent.
|
|
14
|
+
*
|
|
15
|
+
* Two routes reach it and neither is exotic. A desktop export carries session
|
|
16
|
+
* state built from desktop's own core plugins — `blat` registers
|
|
17
|
+
* `UcscResultsWidget`, and every BLAT or in-silico PCR search leaves one in the
|
|
18
|
+
* drawer — which jbrowse-web does not have (see
|
|
19
|
+
* `scripts/check-core-plugin-sets.ts` for the exact relation between the
|
|
20
|
+
* products' lists). And a runtime plugin that fails to load is already
|
|
21
|
+
* tolerated: `notifyPluginLoadFailures` says the session opens without it, which
|
|
22
|
+
* was untrue for any session actually using its types.
|
|
23
|
+
*
|
|
24
|
+
* So the node comes out of the tree and the session opens. `dropped` is what the
|
|
25
|
+
* caller tells the user, which is the only part of this a person can act on.
|
|
26
|
+
*
|
|
27
|
+
* **The node is held, not discarded.** Taking it out of the tree is only half
|
|
28
|
+
* the job: jbrowse-web autosaves `getSnapshot(session)` on a 400ms debounce
|
|
29
|
+
* (`rootModel/persistence.ts`), so a recipient who merely opens a shared link
|
|
30
|
+
* without the plugin writes the reduced session back over the stored row — and
|
|
31
|
+
* reshares it that way. The removed nodes ride along under
|
|
32
|
+
* `heldForMissingPlugins` instead, and a build that has the plugin puts them
|
|
33
|
+
* back. Installing the plugin and reloading is then enough to get the session
|
|
34
|
+
* whole again, which is what makes a plugin removable at all.
|
|
35
|
+
*
|
|
36
|
+
* **The session schema is read off the session MST type, not written down here.**
|
|
37
|
+
* One walk descends `(type, snapshot)` in lockstep: at any `types.array` or
|
|
38
|
+
* `types.map` whose element type is one of the `pluggableMstType` unions it
|
|
39
|
+
* admits, holds or restores each entry, and everywhere else it recurses
|
|
40
|
+
* structurally. That is what reaches a synteny view's `levels[].tracks` and the
|
|
41
|
+
* session's `connectionInstances`, which a hand-written list of `views` /
|
|
42
|
+
* `tracks` / `displays` / `widgets` did not, and what will reach the next
|
|
43
|
+
* container someone adds without anyone editing this file.
|
|
44
|
+
*
|
|
45
|
+
* **A node is held only when all three tests fail**: its `type` names no
|
|
46
|
+
* registered element, no element declares it as an `alias`, and
|
|
47
|
+
* the real MST union refuses the snapshot. The last two are what keep a renamed
|
|
48
|
+
* type alive — an element can declare `aliases`, and a model's own
|
|
49
|
+
* `preProcessSnapshot` can rewrite the type literal
|
|
50
|
+
* (`LinearMultiSampleVariantDisplay` does both for the old
|
|
51
|
+
* `MultiLinearVariantDisplay`), neither of which the registry's names show. The
|
|
52
|
+
* first is what keeps this from quietly swallowing a *malformed* snapshot of a
|
|
53
|
+
* type this build does have: that still throws, exactly as before, because
|
|
54
|
+
* holding it would hide a real bug behind a message about plugins.
|
|
55
|
+
*/
|
|
56
|
+
type Group = PluggableElementTypeGroup;
|
|
57
|
+
export interface UnbuildableNode {
|
|
58
|
+
group: Group;
|
|
59
|
+
type: string;
|
|
60
|
+
cascade?: true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One node taken out of the tree, with the anchor that puts it back.
|
|
64
|
+
*
|
|
65
|
+
* The anchor is a parent **id** plus a position, never a path: paths are
|
|
66
|
+
* index-based and every edit the recipient makes shifts them, while a view id
|
|
67
|
+
* and a track id are stable for as long as the container exists. A held node
|
|
68
|
+
* whose parent the recipient has since deleted is not restorable and is not
|
|
69
|
+
* meant to be — the container going is the user saying so.
|
|
70
|
+
*/
|
|
71
|
+
export interface HeldNode {
|
|
72
|
+
group: Group;
|
|
73
|
+
/** map key, for a node that came out of a `types.map` */
|
|
74
|
+
key?: string;
|
|
75
|
+
/**
|
|
76
|
+
* id of the nearest enclosing node that has one — the view a track came out
|
|
77
|
+
* of, the track a display came out of, the synteny level a level's track came
|
|
78
|
+
* out of. Absent for a container on the session itself.
|
|
79
|
+
*/
|
|
80
|
+
parent?: string;
|
|
81
|
+
/**
|
|
82
|
+
* position in the list it came out of, so a restore lands where it was.
|
|
83
|
+
* Absent for a map entry, which `key` places instead.
|
|
84
|
+
*/
|
|
85
|
+
index?: number;
|
|
86
|
+
snapshot: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Takes the session-snapshot nodes whose pluggable type this plugin manager
|
|
90
|
+
* cannot build out of the tree and holds them under `heldForMissingPlugins`,
|
|
91
|
+
* and puts back the ones already held that it now can build — in one walk of
|
|
92
|
+
* the session type, so the two can never disagree about where a node lives.
|
|
93
|
+
* Returns the snapshot unchanged (by identity) when there is nothing to do
|
|
94
|
+
* either way, which is every session this build produced itself.
|
|
95
|
+
*/
|
|
96
|
+
export declare function pruneUnbuildableNodes(snapshot: Record<string, unknown>, pluginManager: PluginManager, sessionModelType: IAnyType): {
|
|
97
|
+
snapshot: Record<string, unknown>;
|
|
98
|
+
dropped: UnbuildableNode[];
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The user-facing summary of one prune, or undefined when nothing was dropped.
|
|
102
|
+
* Names the missing types rather than counting nodes: the plugin a person has to
|
|
103
|
+
* install is the actionable half, and one missing display type can take several
|
|
104
|
+
* nodes with it. Says what a person can do about it, because the nodes are not
|
|
105
|
+
* gone — they ride along under `heldForMissingPlugins` and come back the moment
|
|
106
|
+
* a build that has the plugin opens the session.
|
|
107
|
+
*/
|
|
108
|
+
export declare function describeUnbuildableNodes(dropped: UnbuildableNode[]): string | undefined;
|
|
109
|
+
export {};
|