@jbrowse/product-core 4.3.0 → 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/esm/RootModel/BaseRootModel.d.ts +629 -304
- package/esm/RootModel/BaseRootModel.js +139 -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 +68 -0
- package/esm/RootModel/PreferencesConfig.js +76 -0
- package/esm/RootModel/createConfigModel.d.ts +240 -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 +312 -11
- package/esm/Session/BaseSession.js +458 -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 +1281 -1128
- package/esm/Session/MultipleViews.js +396 -64
- package/esm/Session/Preferences.d.ts +1667 -0
- package/esm/Session/Preferences.js +59 -0
- package/esm/Session/ReferenceManagement.d.ts +28 -3
- package/esm/Session/ReferenceManagement.js +56 -41
- package/esm/Session/SessionTracks.d.ts +1277 -1138
- package/esm/Session/SessionTracks.js +540 -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 +1171 -1117
- package/esm/Session/Tracks.js +183 -20
- package/esm/Session/assertSessionModel.d.ts +21 -0
- package/esm/Session/assertSessionModel.js +1 -0
- package/esm/Session/developerMode.d.ts +7 -0
- package/esm/Session/developerMode.js +43 -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/readdedTrackConf.d.ts +8 -0
- package/esm/Session/readdedTrackConf.js +25 -0
- package/esm/Session/shareableSnapshot.d.ts +77 -0
- package/esm/Session/shareableSnapshot.js +196 -0
- package/esm/Session/temporaryAssemblyTracks.d.ts +24 -0
- package/esm/Session/temporaryAssemblyTracks.js +37 -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 +90 -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 +41 -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 +98 -16
- 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 +19 -0
- package/esm/useEngineLifecycle.js +44 -0
- package/package.json +12 -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,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrates old session and config snapshots to be compatible with the current
|
|
3
|
+
* display type registrations. Handles display types that were removed or
|
|
4
|
+
* renamed in the v4 rendering rearchitecture.
|
|
5
|
+
*
|
|
6
|
+
* Remapped display types (see `displayTypeMap` for the band settings each one
|
|
7
|
+
* carries over):
|
|
8
|
+
* LinearPileupDisplay → LinearAlignmentsDisplay
|
|
9
|
+
* LinearSNPCoverageDisplay → LinearAlignmentsDisplay
|
|
10
|
+
* LinearReadArcsDisplay → LinearAlignmentsDisplay
|
|
11
|
+
* LinearReadCloudDisplay → LinearAlignmentsDisplay
|
|
12
|
+
* LinearFeatureDisplay → LinearBasicDisplay
|
|
13
|
+
*
|
|
14
|
+
* Also lifts the per-instance alignments settings a v4.3.0 session persisted
|
|
15
|
+
* (`colorBySetting`, `filterBySetting`, `jexlFilters`, `trackMaxHeight`,
|
|
16
|
+
* `hideMismatchesSetting`) into the config where they now live as slots — off
|
|
17
|
+
* the nested `LinearAlignmentsDisplay` container's `PileupDisplay` /
|
|
18
|
+
* `SNPCoverageDisplay` sub-nodes and off a flat old display alike, see
|
|
19
|
+
* `extractAlignmentsInstanceSettings` — and routes the legacy per-instance
|
|
20
|
+
* `heightPreConfig` display-height prop onto the `height` config slot — see
|
|
21
|
+
* `extractInstanceHeight`.
|
|
22
|
+
*/
|
|
23
|
+
// Each old display drew one band; the unified LinearAlignmentsDisplay draws all
|
|
24
|
+
// of them, so a bare type rename turns a coverage-only track into coverage +
|
|
25
|
+
// pileup and an arcs track into a plain pileup. `settings` restores the old
|
|
26
|
+
// intent through the config slots that now express it, and is applied only where
|
|
27
|
+
// the snapshot doesn't already carry the key, so an explicit author value wins.
|
|
28
|
+
const displayTypeMap = {
|
|
29
|
+
LinearPileupDisplay: {
|
|
30
|
+
type: 'LinearAlignmentsDisplay',
|
|
31
|
+
settings: { showCoverage: false },
|
|
32
|
+
},
|
|
33
|
+
LinearSNPCoverageDisplay: {
|
|
34
|
+
type: 'LinearAlignmentsDisplay',
|
|
35
|
+
// coverageHeight + height together, else the band keeps its 45px default
|
|
36
|
+
// under the 250px display height and leaves 200px blank.
|
|
37
|
+
settings: { showPileup: false, coverageHeight: 100, height: 100 },
|
|
38
|
+
},
|
|
39
|
+
LinearReadArcsDisplay: {
|
|
40
|
+
type: 'LinearAlignmentsDisplay',
|
|
41
|
+
settings: {
|
|
42
|
+
showPileup: false,
|
|
43
|
+
showCoverage: false,
|
|
44
|
+
readConnections: 'arc',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
LinearReadCloudDisplay: {
|
|
48
|
+
type: 'LinearAlignmentsDisplay',
|
|
49
|
+
settings: {
|
|
50
|
+
showPileup: false,
|
|
51
|
+
showCoverage: false,
|
|
52
|
+
readConnections: 'cloud',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
LinearFeatureDisplay: { type: 'LinearBasicDisplay' },
|
|
56
|
+
};
|
|
57
|
+
// The pre-4.x LinearAlignmentsDisplay was a container whose per-instance
|
|
58
|
+
// track-menu settings lived on nested `PileupDisplay` / `SNPCoverageDisplay`
|
|
59
|
+
// sub-nodes. Those settings are now config slots on the flat
|
|
60
|
+
// LinearAlignmentsDisplay, so the sub-nodes are dead on load — MST drops them
|
|
61
|
+
// and `colorBy`/`filterBy` silently revert to their config default (e.g. a
|
|
62
|
+
// modifications/methylation session opens colored `normal`). We pull them off
|
|
63
|
+
// the instance here and route them into the config.
|
|
64
|
+
const NESTED_ALIGNMENTS_SUBNODES = ['PileupDisplay', 'SNPCoverageDisplay'];
|
|
65
|
+
// Persisted key -> the slot it now lives in, and how to carry its value across.
|
|
66
|
+
//
|
|
67
|
+
// The `*Setting` names are the ones a real saved session carries, and getting
|
|
68
|
+
// them wrong is why this migration never fired: SharedLinearPileupDisplayMixin
|
|
69
|
+
// (v4.3.0) declared the props as `colorBySetting`/`filterBySetting`, its
|
|
70
|
+
// `preProcessSnapshot` renamed an incoming bare `colorBy` to `colorBySetting`,
|
|
71
|
+
// and its `postProcessSnapshot` wrote that name back out — so no session
|
|
72
|
+
// snapshot JBrowse ever produced holds the bare form this used to look for. The
|
|
73
|
+
// bare names stay accepted because a hand-written snapshot may use them.
|
|
74
|
+
//
|
|
75
|
+
// `hideSmallIndelsSetting` and `hideLargeIndelsSetting` are deliberately absent:
|
|
76
|
+
// those two toggles have no slot today, the feature having been removed rather
|
|
77
|
+
// than moved, so there is nowhere to carry them.
|
|
78
|
+
const MIGRATED_INSTANCE_SLOTS = {
|
|
79
|
+
colorBy: { slot: 'colorBy' },
|
|
80
|
+
colorBySetting: { slot: 'colorBy' },
|
|
81
|
+
filterBy: { slot: 'filterBy' },
|
|
82
|
+
filterBySetting: { slot: 'filterBy' },
|
|
83
|
+
jexlFilters: { slot: 'jexlFilters' },
|
|
84
|
+
trackMaxHeight: { slot: 'maxHeight' },
|
|
85
|
+
hideMismatchesSetting: {
|
|
86
|
+
slot: 'showMismatches',
|
|
87
|
+
convert: value => !value,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Every display-instance key a session snapshot may still carry and have
|
|
92
|
+
* honored, keyed by the display type it applies to — `*` for the ones any
|
|
93
|
+
* display gets. A snapshot carrying one of these loads correctly, so `jbrowse
|
|
94
|
+
* validate` reports it as stale rather than dead; that command reads this map
|
|
95
|
+
* (through the generated config manifest) rather than keeping a copy that could
|
|
96
|
+
* drift.
|
|
97
|
+
*
|
|
98
|
+
* Not every entry is lifted *here*. The two multi-sample variant displays lift
|
|
99
|
+
* their own `jexlFilters` in the model's `preProcessSnapshot`, because the value
|
|
100
|
+
* stays on the instance (under `jexlFiltersSetting`) rather than moving to a
|
|
101
|
+
* config slot, which is the only shape this module handles. What decides
|
|
102
|
+
* membership is whether the key still works, not which file makes it work.
|
|
103
|
+
*
|
|
104
|
+
* Keyed rather than flat because the same name means different things on
|
|
105
|
+
* different displays: `jexlFilters` is lifted for the alignments display and,
|
|
106
|
+
* differently, for the two variant ones, while on a LinearBasicDisplay — whose
|
|
107
|
+
* own prop has always been `jexlFiltersSetting` — it is simply dead, and a flat
|
|
108
|
+
* list would call that one supported.
|
|
109
|
+
*/
|
|
110
|
+
export const MIGRATED_DISPLAY_INSTANCE_KEYS = {
|
|
111
|
+
'*': ['heightPreConfig'],
|
|
112
|
+
LinearAlignmentsDisplay: [
|
|
113
|
+
...Object.keys(MIGRATED_INSTANCE_SLOTS),
|
|
114
|
+
...NESTED_ALIGNMENTS_SUBNODES,
|
|
115
|
+
],
|
|
116
|
+
LinearMultiSampleVariantDisplay: ['jexlFilters'],
|
|
117
|
+
LinearMultiSampleVariantMatrixDisplay: ['jexlFilters'],
|
|
118
|
+
};
|
|
119
|
+
// Read the migrated settings off one node (a nested sub-node, or an old flat
|
|
120
|
+
// display's own snapshot), keyed by the slot they now belong to. A key present
|
|
121
|
+
// under both its bare and its `*Setting` spelling resolves to whichever comes
|
|
122
|
+
// last in MIGRATED_INSTANCE_SLOTS, which is the `*Setting` one — the spelling
|
|
123
|
+
// the display was actually writing.
|
|
124
|
+
function migratedSettingsOf(source) {
|
|
125
|
+
const settings = {};
|
|
126
|
+
for (const [key, { slot, convert }] of Object.entries(MIGRATED_INSTANCE_SLOTS)) {
|
|
127
|
+
if (source[key] !== undefined) {
|
|
128
|
+
settings[slot] = convert ? convert(source[key]) : source[key];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return settings;
|
|
132
|
+
}
|
|
133
|
+
function isObject(v) {
|
|
134
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
135
|
+
}
|
|
136
|
+
// The standalone `methylation` colorBy scheme was removed; it is now the
|
|
137
|
+
// `modifications` scheme with `fillUnmarked` set. It is NOT rewritten here: the
|
|
138
|
+
// alignments model normalizes it at read time (`normalizeColorBy`, applied in
|
|
139
|
+
// its `colorBy` getter), which covers every persistence path — instance slot,
|
|
140
|
+
// session-wide promoted default, and config-file display default — so session
|
|
141
|
+
// migration deliberately leaves a `methylation` colorBy untouched.
|
|
142
|
+
function migrateDisplayType(display, trackConfigId, collected) {
|
|
143
|
+
const entry = displayTypeMap[display.type];
|
|
144
|
+
if (!entry) {
|
|
145
|
+
return display;
|
|
146
|
+
}
|
|
147
|
+
const missing = Object.fromEntries(Object.entries(entry.settings ?? {}).filter(([k]) => display[k] === undefined));
|
|
148
|
+
// Every one of these is a config slot on the flat display, and a session
|
|
149
|
+
// instance declares only `type` and `configuration` — so merging them here
|
|
150
|
+
// would hand MST properties it drops on load, and the compensation would be
|
|
151
|
+
// gone exactly where it is needed most: a v4 session on an admin config, which
|
|
152
|
+
// is the commonest way anyone has one of these displays at all. Route them the
|
|
153
|
+
// way extractInstanceHeight routes its own, into the config the instance
|
|
154
|
+
// points at. A *config* display node has no `configuration` reference and
|
|
155
|
+
// keeps them inline, where they are the live slots.
|
|
156
|
+
const displayId = display.configuration;
|
|
157
|
+
if (trackConfigId && typeof displayId === 'string') {
|
|
158
|
+
if (Object.keys(missing).length > 0) {
|
|
159
|
+
collected.push({
|
|
160
|
+
trackConfigId,
|
|
161
|
+
displayId,
|
|
162
|
+
displayType: entry.type,
|
|
163
|
+
settings: missing,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return { ...display, type: entry.type };
|
|
167
|
+
}
|
|
168
|
+
return { ...display, ...missing, type: entry.type };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Pull the per-instance alignments settings off an old display into `collected`
|
|
172
|
+
* and return the display with the dead keys stripped. The caller routes
|
|
173
|
+
* `collected` into the config (see `applyExtractedSettings`); the settings can't
|
|
174
|
+
* stay on the instance because they're config slots now.
|
|
175
|
+
*
|
|
176
|
+
* Two shapes, because v4.3.0 sessions hold both:
|
|
177
|
+
*
|
|
178
|
+
* - the nested `LinearAlignmentsDisplay` container, whose settings sat on
|
|
179
|
+
* `PileupDisplay` / `SNPCoverageDisplay` sub-nodes
|
|
180
|
+
* - a flat `LinearPileupDisplay` / `LinearReadArcsDisplay` /
|
|
181
|
+
* `LinearReadCloudDisplay` / `LinearSNPCoverageDisplay`, which carried them
|
|
182
|
+
* directly — `migrateDisplayType` has already renamed it by the time this
|
|
183
|
+
* runs, so both arrive here as a LinearAlignmentsDisplay
|
|
184
|
+
*
|
|
185
|
+
* The display's own keys are read only when it carries a `configuration`
|
|
186
|
+
* reference, i.e. only for a session instance. A *config* display node reaches
|
|
187
|
+
* this same function through `migrateConfigSnapshot`, and there `colorBy` and
|
|
188
|
+
* friends are the live slots — lifting them would strip a working config.
|
|
189
|
+
*/
|
|
190
|
+
function extractAlignmentsInstanceSettings(display, trackConfigId, collected) {
|
|
191
|
+
if (display.type !== 'LinearAlignmentsDisplay') {
|
|
192
|
+
return display;
|
|
193
|
+
}
|
|
194
|
+
const subNode = NESTED_ALIGNMENTS_SUBNODES.map(k => display[k]).find(isObject);
|
|
195
|
+
// The instance's `configuration` string is the display config id the settings
|
|
196
|
+
// must merge onto (`${trackId}-LinearAlignmentsDisplay`). Skip routing if it's
|
|
197
|
+
// inline or the track id is unknown — nothing to key the merge on.
|
|
198
|
+
const displayId = display.configuration;
|
|
199
|
+
const isInstance = typeof displayId === 'string';
|
|
200
|
+
const ownSettings = isInstance ? migratedSettingsOf(display) : {};
|
|
201
|
+
const settings = {
|
|
202
|
+
...(subNode ? migratedSettingsOf(subNode) : {}),
|
|
203
|
+
...ownSettings,
|
|
204
|
+
};
|
|
205
|
+
if (!subNode && Object.keys(settings).length === 0) {
|
|
206
|
+
return display;
|
|
207
|
+
}
|
|
208
|
+
if (trackConfigId && isInstance && Object.keys(settings).length > 0) {
|
|
209
|
+
collected.push({
|
|
210
|
+
trackConfigId,
|
|
211
|
+
displayId,
|
|
212
|
+
displayType: 'LinearAlignmentsDisplay',
|
|
213
|
+
settings,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
const { PileupDisplay, SNPCoverageDisplay, ...rest } = display;
|
|
217
|
+
if (isInstance) {
|
|
218
|
+
// Every one of these is dead on a display instance now, routed or not — MST
|
|
219
|
+
// would drop them on load anyway, and leaving them makes a migrated snapshot
|
|
220
|
+
// still look like it holds settings it does not.
|
|
221
|
+
for (const key of Object.keys(MIGRATED_INSTANCE_SLOTS)) {
|
|
222
|
+
delete rest[key];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return rest;
|
|
226
|
+
}
|
|
227
|
+
// Pre-v4 the display height lived in a per-instance `heightPreConfig` MST
|
|
228
|
+
// prop (`height = heightPreConfig ?? config-height`); the drag-resize handle
|
|
229
|
+
// wrote it. That prop is gone — the height is now the `height` config slot only
|
|
230
|
+
// — so an old session's `heightPreConfig` is silently dropped on load and the
|
|
231
|
+
// track snaps to the config default (e.g. the alignments 250px). Route it onto
|
|
232
|
+
// the display's `height` slot so saved/shared sessions keep their heights, and
|
|
233
|
+
// strip the dead prop. When a track shows in two panels (a synteny view's two
|
|
234
|
+
// LGVs share one display config), the last panel's value wins — both then render
|
|
235
|
+
// at one height, which is the config-slot model's intent.
|
|
236
|
+
function extractInstanceHeight(display, trackConfigId, collected) {
|
|
237
|
+
if (typeof display.heightPreConfig !== 'number') {
|
|
238
|
+
return display;
|
|
239
|
+
}
|
|
240
|
+
const displayId = display.configuration;
|
|
241
|
+
if (trackConfigId && typeof displayId === 'string') {
|
|
242
|
+
collected.push({
|
|
243
|
+
trackConfigId,
|
|
244
|
+
displayId,
|
|
245
|
+
displayType: typeof display.type === 'string' ? display.type : undefined,
|
|
246
|
+
settings: { height: display.heightPreConfig },
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const { heightPreConfig, ...rest } = display;
|
|
250
|
+
return rest;
|
|
251
|
+
}
|
|
252
|
+
function migrateDisplaySnapshot(display, trackConfigId, collected) {
|
|
253
|
+
return extractInstanceHeight(extractAlignmentsInstanceSettings(migrateDisplayType(display, trackConfigId, collected), trackConfigId, collected), trackConfigId, collected);
|
|
254
|
+
}
|
|
255
|
+
function migrateDisplaysArray(displays, trackConfigId, collected) {
|
|
256
|
+
let changed = false;
|
|
257
|
+
const result = displays.map(d => {
|
|
258
|
+
if (isObject(d) && 'type' in d) {
|
|
259
|
+
const migrated = migrateDisplaySnapshot(d, trackConfigId, collected);
|
|
260
|
+
if (migrated !== d) {
|
|
261
|
+
changed = true;
|
|
262
|
+
}
|
|
263
|
+
return migrated;
|
|
264
|
+
}
|
|
265
|
+
return d;
|
|
266
|
+
});
|
|
267
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
268
|
+
return changed ? result : displays;
|
|
269
|
+
}
|
|
270
|
+
function migrateTrackSnapshot(track, collected) {
|
|
271
|
+
if (!isObject(track)) {
|
|
272
|
+
return track;
|
|
273
|
+
}
|
|
274
|
+
let changed = false;
|
|
275
|
+
const result = { ...track };
|
|
276
|
+
const displays = track.displays;
|
|
277
|
+
if (Array.isArray(displays)) {
|
|
278
|
+
const trackConfigId = typeof track.configuration === 'string' ? track.configuration : undefined;
|
|
279
|
+
const newDisplays = migrateDisplaysArray(displays, trackConfigId, collected);
|
|
280
|
+
if (newDisplays !== displays) {
|
|
281
|
+
changed = true;
|
|
282
|
+
result.displays = newDisplays;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return changed ? result : track;
|
|
286
|
+
}
|
|
287
|
+
function migrateViewSnapshot(view, collected) {
|
|
288
|
+
let changed = false;
|
|
289
|
+
const result = { ...view };
|
|
290
|
+
const tracks = view.tracks;
|
|
291
|
+
if (Array.isArray(tracks)) {
|
|
292
|
+
const newTracks = tracks.map(t => migrateTrackSnapshot(t, collected));
|
|
293
|
+
if (newTracks.some((t, i) => t !== tracks[i])) {
|
|
294
|
+
changed = true;
|
|
295
|
+
result.tracks = newTracks;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Handle nested views (e.g. LinearSyntenyView has sub-views)
|
|
299
|
+
const subViews = view.views;
|
|
300
|
+
if (Array.isArray(subViews)) {
|
|
301
|
+
const newSubViews = subViews.map(sv => isObject(sv) ? migrateViewSnapshot(sv, collected) : sv);
|
|
302
|
+
if (newSubViews.some((v, i) => v !== subViews[i])) {
|
|
303
|
+
changed = true;
|
|
304
|
+
result.views = newSubViews;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return changed ? result : view;
|
|
308
|
+
}
|
|
309
|
+
// Merge `settings` onto the display config carrying `displayId` inside `track`
|
|
310
|
+
// (an in-session track config). Returns a new track if it changed. The config
|
|
311
|
+
// slots win over whatever the old config held.
|
|
312
|
+
function mergeSettingsIntoTrackConfig(track, displayId, displayType, settings) {
|
|
313
|
+
const displays = track.displays;
|
|
314
|
+
const list = Array.isArray(displays) ? displays : [];
|
|
315
|
+
const hasMatch = list.some(d => isObject(d) && d.displayId === displayId);
|
|
316
|
+
const newDisplays = hasMatch
|
|
317
|
+
? list.map(d => isObject(d) && d.displayId === displayId ? { ...d, ...settings } : d)
|
|
318
|
+
: [
|
|
319
|
+
...list,
|
|
320
|
+
{
|
|
321
|
+
type: displayType ?? 'LinearAlignmentsDisplay',
|
|
322
|
+
displayId,
|
|
323
|
+
...settings,
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
return { ...track, displays: newDisplays };
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Route each setting lifted off a nested alignments instance into the config it
|
|
330
|
+
* now lives in: a user-added `sessionTracks` entry is edited in place (the
|
|
331
|
+
* config is embedded there), while an admin config track — whose base lives in
|
|
332
|
+
* the reloaded config file, out of reach here — gets a `trackConfigDeltas`
|
|
333
|
+
* entry keyed by trackId (see product-core/src/Session/CLAUDE.md).
|
|
334
|
+
*/
|
|
335
|
+
function applyExtractedSettings(snapshot, collected) {
|
|
336
|
+
if (collected.length === 0) {
|
|
337
|
+
return snapshot;
|
|
338
|
+
}
|
|
339
|
+
const sessionTracks = Array.isArray(snapshot.sessionTracks)
|
|
340
|
+
? [...snapshot.sessionTracks]
|
|
341
|
+
: [];
|
|
342
|
+
const sessionTrackIndex = new Map(sessionTracks.map((t, i) => [isObject(t) ? t.trackId : undefined, i]));
|
|
343
|
+
const deltas = isObject(snapshot.trackConfigDeltas)
|
|
344
|
+
? { ...snapshot.trackConfigDeltas }
|
|
345
|
+
: {};
|
|
346
|
+
let sessionTracksChanged = false;
|
|
347
|
+
let deltasChanged = false;
|
|
348
|
+
for (const { trackConfigId, displayId, displayType, settings } of collected) {
|
|
349
|
+
const idx = sessionTrackIndex.get(trackConfigId);
|
|
350
|
+
if (idx !== undefined && isObject(sessionTracks[idx])) {
|
|
351
|
+
sessionTracks[idx] = mergeSettingsIntoTrackConfig(sessionTracks[idx], displayId, displayType, settings);
|
|
352
|
+
sessionTracksChanged = true;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
const existing = isObject(deltas[trackConfigId])
|
|
356
|
+
? deltas[trackConfigId]
|
|
357
|
+
: { trackId: trackConfigId };
|
|
358
|
+
deltas[trackConfigId] = mergeSettingsIntoTrackConfig(existing, displayId, displayType, settings);
|
|
359
|
+
deltasChanged = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
...snapshot,
|
|
364
|
+
...(sessionTracksChanged ? { sessionTracks } : {}),
|
|
365
|
+
...(deltasChanged ? { trackConfigDeltas: deltas } : {}),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Walks a session snapshot and remaps old display types to their new names.
|
|
370
|
+
* Handles displays nested inside views, tracks, and sessionTracks.
|
|
371
|
+
*/
|
|
372
|
+
export function migrateSessionSnapshot(snapshot) {
|
|
373
|
+
let changed = false;
|
|
374
|
+
const result = { ...snapshot };
|
|
375
|
+
// Per-instance alignments settings lifted off old nested displays in views,
|
|
376
|
+
// routed into the config after the walk (they can't stay on the instance).
|
|
377
|
+
const collected = [];
|
|
378
|
+
const views = snapshot.views;
|
|
379
|
+
if (Array.isArray(views)) {
|
|
380
|
+
const newViews = views.map(view => isObject(view) ? migrateViewSnapshot(view, collected) : view);
|
|
381
|
+
if (newViews.some((v, i) => v !== views[i])) {
|
|
382
|
+
changed = true;
|
|
383
|
+
result.views = newViews;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const sessionTracks = snapshot.sessionTracks;
|
|
387
|
+
if (Array.isArray(sessionTracks)) {
|
|
388
|
+
const newTracks = sessionTracks.map(t => migrateTrackSnapshot(t, collected));
|
|
389
|
+
if (newTracks.some((t, i) => t !== sessionTracks[i])) {
|
|
390
|
+
changed = true;
|
|
391
|
+
result.sessionTracks = newTracks;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// trackConfigDeltas is a `trackId → partial track config` map (see
|
|
395
|
+
// SessionTracks.ts). A delta carries a `displays` array like a track, so run
|
|
396
|
+
// each through the same track migrator; migrateTrackSnapshot is a no-op for a
|
|
397
|
+
// delta that carries no stale display type.
|
|
398
|
+
const deltas = snapshot.trackConfigDeltas;
|
|
399
|
+
if (deltas && typeof deltas === 'object') {
|
|
400
|
+
let deltasChanged = false;
|
|
401
|
+
const newDeltas = {};
|
|
402
|
+
for (const [trackId, delta] of Object.entries(deltas)) {
|
|
403
|
+
const migrated = migrateTrackSnapshot(delta, collected);
|
|
404
|
+
if (migrated !== delta) {
|
|
405
|
+
deltasChanged = true;
|
|
406
|
+
}
|
|
407
|
+
newDeltas[trackId] = migrated;
|
|
408
|
+
}
|
|
409
|
+
if (deltasChanged) {
|
|
410
|
+
changed = true;
|
|
411
|
+
result.trackConfigDeltas = newDeltas;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// applyExtractedSettings returns its input by identity when nothing was
|
|
415
|
+
// collected, so this preserves the unchanged-by-identity contract.
|
|
416
|
+
return applyExtractedSettings(changed ? result : snapshot, collected);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Walks a config snapshot and remaps old display types in track definitions.
|
|
420
|
+
* Config tracks have displays in their configuration (not in views).
|
|
421
|
+
*/
|
|
422
|
+
export function migrateConfigSnapshot(snapshot) {
|
|
423
|
+
const tracks = snapshot.tracks;
|
|
424
|
+
if (!Array.isArray(tracks)) {
|
|
425
|
+
return snapshot;
|
|
426
|
+
}
|
|
427
|
+
const newTracks = tracks.map(t => migrateTrackSnapshot(t, []));
|
|
428
|
+
if (newTracks.every((t, i) => t === tracks[i])) {
|
|
429
|
+
return snapshot;
|
|
430
|
+
}
|
|
431
|
+
return { ...snapshot, tracks: newTracks };
|
|
432
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AbstractSessionModel } from '@jbrowse/core/util';
|
|
2
|
+
/**
|
|
3
|
+
* A serialized session. Open-shaped on purpose: the concrete shape is the
|
|
4
|
+
* session model's `SnapshotIn`, which grows with every plugin a host loads.
|
|
5
|
+
*/
|
|
6
|
+
export interface SessionSnapshot {
|
|
7
|
+
name: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Serialize a live session into a compact, URL-safe string for a query param or
|
|
12
|
+
* hash fragment. Deflated then base64url-encoded, so it survives a URL intact
|
|
13
|
+
* and stays far smaller than raw JSON.
|
|
14
|
+
*
|
|
15
|
+
* Not a plain `getSnapshot`: display settings a user is *inheriting* from a
|
|
16
|
+
* promoted display-type default live in their own browser, never in the
|
|
17
|
+
* session, so a raw snapshot renders differently for whoever opens the link.
|
|
18
|
+
* `getShareableSessionSnapshot` flattens that cascade into the snapshot first.
|
|
19
|
+
*
|
|
20
|
+
* The session travels but the config does not — the receiving app supplies its
|
|
21
|
+
* own assembly/tracks. Put the result somewhere the server never sees (the hash
|
|
22
|
+
* fragment) if it may be long: a query string can exceed the request-line limit
|
|
23
|
+
* and get a 414, which is why jbrowse-web moved its own there.
|
|
24
|
+
*
|
|
25
|
+
* Carries `ENCODED_PREFIX`, the same `?session=encoded-…` value jbrowse-web's
|
|
26
|
+
* SessionLoader decodes, so a link built by an embedded product opens in
|
|
27
|
+
* jbrowse.org/jb2 and vice versa. The format is `encodeSessionParam`'s `long`
|
|
28
|
+
* mode; sessionUrl.test.ts asserts the two stay byte-identical.
|
|
29
|
+
*/
|
|
30
|
+
export declare function encodeSessionToUrl(session: AbstractSessionModel): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Inverse of {@link encodeSessionToUrl}. Accepts the value with or without the
|
|
33
|
+
* `encoded-` prefix, so a raw `?session=` value from either app passes straight
|
|
34
|
+
* through.
|
|
35
|
+
*
|
|
36
|
+
* Throws on anything that isn't a decodable session — a truncated link, or a
|
|
37
|
+
* `share-`/`spec-` param an embedded product doesn't handle — so a host can
|
|
38
|
+
* fall back to its own declarative launch state instead of opening a
|
|
39
|
+
* half-built session.
|
|
40
|
+
*/
|
|
41
|
+
export declare function decodeSessionFromUrl(encoded: string): Promise<SessionSnapshot>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ENCODED_PREFIX, fromUrlSafeB64, toUrlSafeB64, } from '@jbrowse/core/util';
|
|
2
|
+
import { getShareableSessionSnapshot } from './Session/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Serialize a live session into a compact, URL-safe string for a query param or
|
|
5
|
+
* hash fragment. Deflated then base64url-encoded, so it survives a URL intact
|
|
6
|
+
* and stays far smaller than raw JSON.
|
|
7
|
+
*
|
|
8
|
+
* Not a plain `getSnapshot`: display settings a user is *inheriting* from a
|
|
9
|
+
* promoted display-type default live in their own browser, never in the
|
|
10
|
+
* session, so a raw snapshot renders differently for whoever opens the link.
|
|
11
|
+
* `getShareableSessionSnapshot` flattens that cascade into the snapshot first.
|
|
12
|
+
*
|
|
13
|
+
* The session travels but the config does not — the receiving app supplies its
|
|
14
|
+
* own assembly/tracks. Put the result somewhere the server never sees (the hash
|
|
15
|
+
* fragment) if it may be long: a query string can exceed the request-line limit
|
|
16
|
+
* and get a 414, which is why jbrowse-web moved its own there.
|
|
17
|
+
*
|
|
18
|
+
* Carries `ENCODED_PREFIX`, the same `?session=encoded-…` value jbrowse-web's
|
|
19
|
+
* SessionLoader decodes, so a link built by an embedded product opens in
|
|
20
|
+
* jbrowse.org/jb2 and vice versa. The format is `encodeSessionParam`'s `long`
|
|
21
|
+
* mode; sessionUrl.test.ts asserts the two stay byte-identical.
|
|
22
|
+
*/
|
|
23
|
+
export async function encodeSessionToUrl(session) {
|
|
24
|
+
const snap = getShareableSessionSnapshot(session);
|
|
25
|
+
return `${ENCODED_PREFIX}${await toUrlSafeB64(JSON.stringify(snap))}`;
|
|
26
|
+
}
|
|
27
|
+
function isSessionSnapshot(value) {
|
|
28
|
+
return (typeof value === 'object' &&
|
|
29
|
+
value !== null &&
|
|
30
|
+
!Array.isArray(value) &&
|
|
31
|
+
'name' in value &&
|
|
32
|
+
typeof value.name === 'string');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Inverse of {@link encodeSessionToUrl}. Accepts the value with or without the
|
|
36
|
+
* `encoded-` prefix, so a raw `?session=` value from either app passes straight
|
|
37
|
+
* through.
|
|
38
|
+
*
|
|
39
|
+
* Throws on anything that isn't a decodable session — a truncated link, or a
|
|
40
|
+
* `share-`/`spec-` param an embedded product doesn't handle — so a host can
|
|
41
|
+
* fall back to its own declarative launch state instead of opening a
|
|
42
|
+
* half-built session.
|
|
43
|
+
*/
|
|
44
|
+
export async function decodeSessionFromUrl(encoded) {
|
|
45
|
+
const b64 = encoded.startsWith(ENCODED_PREFIX)
|
|
46
|
+
? encoded.slice(ENCODED_PREFIX.length)
|
|
47
|
+
: encoded;
|
|
48
|
+
const parsed = JSON.parse(await fromUrlSafeB64(b64));
|
|
49
|
+
if (!isSessionSnapshot(parsed)) {
|
|
50
|
+
throw new Error('not a session snapshot: no "name"');
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { PluginDefinition } from '@jbrowse/core/pluginDefinitions';
|
|
2
|
+
import type { IAnyStateTreeNode, IAnyType } from '@jbrowse/mobx-state-tree';
|
|
3
|
+
export interface DroppedSessionNode {
|
|
4
|
+
type?: string;
|
|
5
|
+
configuration?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function filterSessionInPlace(node: IAnyStateTreeNode, type: IAnyType, dropped?: DroppedSessionNode[]): DroppedSessionNode[];
|
|
8
|
+
export interface NonPortableLocation {
|
|
9
|
+
locationType: string;
|
|
10
|
+
name: string;
|
|
11
|
+
trackId?: string;
|
|
12
|
+
trackName?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface WebPortabilityReport {
|
|
15
|
+
nonPortable: NonPortableLocation[];
|
|
16
|
+
portable: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function analyzeWebPortability(snapshot: unknown): WebPortabilityReport;
|
|
19
|
+
export interface TrackSnapshot {
|
|
20
|
+
trackId: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface WebExportInput {
|
|
24
|
+
assemblies?: AssemblySnapshot[];
|
|
25
|
+
tracks?: TrackSnapshot[];
|
|
26
|
+
plugins?: PluginDefinition[];
|
|
27
|
+
connections?: unknown[];
|
|
28
|
+
configuration?: {
|
|
29
|
+
sourceConfigUrl?: string;
|
|
30
|
+
webExportUrl?: string;
|
|
31
|
+
};
|
|
32
|
+
defaultSession?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export interface AssemblySnapshot {
|
|
35
|
+
name: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
export interface HostedBaseConfig {
|
|
39
|
+
assemblies?: AssemblySnapshot[];
|
|
40
|
+
tracks?: TrackSnapshot[];
|
|
41
|
+
internetAccounts?: {
|
|
42
|
+
internetAccountId?: string;
|
|
43
|
+
}[];
|
|
44
|
+
plugins?: PluginDefinition[];
|
|
45
|
+
connections?: unknown[];
|
|
46
|
+
configuration?: {
|
|
47
|
+
shareURL?: string;
|
|
48
|
+
};
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
export interface WebExportPlan {
|
|
52
|
+
strategy: 'hostedConfigBase' | 'selfContained';
|
|
53
|
+
configUrl?: string;
|
|
54
|
+
webBaseUrl: string;
|
|
55
|
+
session: Record<string, unknown>;
|
|
56
|
+
droppedTracks: string[];
|
|
57
|
+
blockingFiles: string[];
|
|
58
|
+
selfContainedReason?: SelfContainedReason;
|
|
59
|
+
droppedTextIndexes: string[];
|
|
60
|
+
revertedAssemblies: string[];
|
|
61
|
+
unavailableAccounts: string[];
|
|
62
|
+
}
|
|
63
|
+
export type SelfContainedReason = 'noSourceConfig' | 'baseUnreachable' | 'assembliesNotInBase';
|
|
64
|
+
export declare function planWebExport(snapshot: WebExportInput, baseConfig?: HostedBaseConfig): WebExportPlan;
|
|
65
|
+
export declare const DEFAULT_WEB_BASE_URL = "https://jbrowse.org/code/jb2/latest/";
|
|
66
|
+
export declare function buildWebExportUrl(plan: Pick<WebExportPlan, 'webBaseUrl' | 'configUrl'>, sessionParam: string, options?: {
|
|
67
|
+
password?: string;
|
|
68
|
+
exportedFrom?: string;
|
|
69
|
+
}): string;
|