@jbrowse/plugin-linear-genome-view 5.0.0-beta.1 → 5.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/LaunchLinearGenomeView/index.js +2 -13
- package/esm/LinearGenomeView/afterAttach.js +16 -18
- package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
- package/esm/LinearGenomeView/components/PaddingBlocks.js +11 -1
- package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +1 -1
- package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
- package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
- package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
- package/esm/LinearGenomeView/components/searchModes.js +1 -1
- package/esm/LinearGenomeView/index.d.ts +1 -0
- package/esm/LinearGenomeView/index.js +8 -1
- package/esm/LinearGenomeView/initKeys.d.ts +0 -1
- package/esm/LinearGenomeView/initKeys.js +9 -45
- package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
- package/esm/LinearGenomeView/launchKeys.js +23 -0
- package/esm/LinearGenomeView/lazyDialogs.d.ts +1 -1
- package/esm/LinearGenomeView/linkedViewSync.d.ts +1 -1
- package/esm/LinearGenomeView/linkedViewSync.js +6 -0
- package/esm/LinearGenomeView/model.d.ts +1734 -22
- package/esm/LinearGenomeView/model.js +121 -57
- package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +2 -2
- package/esm/LinearGenomeView/types.d.ts +1 -1
- package/esm/displayKitTests/perRegionTestEnv.d.ts +51 -28
- package/esm/displayKitTests/perRegionTestEnv.js +0 -3
- package/esm/index.d.ts +14 -2
- package/esm/index.js +1 -2
- package/esm/searchUtils.d.ts +6 -3
- package/esm/searchUtils.js +37 -11
- package/package.json +8 -8
- package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
- package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
|
@@ -8,9 +8,11 @@ import { wholeBaseRegions } from '@jbrowse/core/util/blockTypes';
|
|
|
8
8
|
import calculateDynamicBlocks from '@jbrowse/core/util/calculateDynamicBlocks';
|
|
9
9
|
import calculateStaticBlocks from '@jbrowse/core/util/calculateStaticBlocks';
|
|
10
10
|
import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
|
|
11
|
-
import { hideTrackGeneric, showTrackGeneric, toggleTrackGeneric, } from '@jbrowse/core/util/tracks';
|
|
11
|
+
import { hideTrackGeneric, launchToggleTrackGeneric, launchTrackGeneric, showTrackGeneric, toggleTrackGeneric, } from '@jbrowse/core/util/tracks';
|
|
12
12
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
13
|
-
import { computeViewStatus } from '@jbrowse/core/util/viewStatus';
|
|
13
|
+
import { assemblyErrorMessage, computeViewStatus, } from '@jbrowse/core/util/viewStatus';
|
|
14
|
+
import { pendingLaunch, withLaunchInput, } from '@jbrowse/core/util/withLaunchInput';
|
|
15
|
+
import { contentRightEdgePx } from '@jbrowse/display-kit/regionHost';
|
|
14
16
|
import { cast, getParent, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
|
|
15
17
|
import { when } from 'mobx';
|
|
16
18
|
import { handleSelectedRegion } from '../searchUtils.js';
|
|
@@ -19,11 +21,16 @@ import { shouldSwapTracks } from './components/util.js';
|
|
|
19
21
|
import { HEADER_BAR_HEIGHT, HEADER_OVERVIEW_HEIGHT, MIN_BP_PER_PX, MINIMIZED_TRACK_HEIGHT, RESIZE_HANDLE_HEIGHT, SCALE_BAR_HEIGHT, SHOW_ALL_REGIONS_FILL, TRACK_OUTLINE_BORDER, TRACK_TOP_GAP, } from './consts.js';
|
|
20
22
|
import { planFlight } from './flyTo.js';
|
|
21
23
|
import { setupKeyboardHandler } from './keyboardHandler.js';
|
|
24
|
+
import { lgvLaunchKeys } from './launchKeys.js';
|
|
22
25
|
import { buildMenuItems, buildRubberBandMenuItems, buildRubberbandClickMenuItems, } from './menuItems.js';
|
|
23
26
|
import { sharedScaleContainerOf } from './sharedScaleContainer.js';
|
|
24
27
|
import { calculateVisibleLocStrings, cytobandLabelGutterWidth, expandRegion, fitAllRegionsWindow, generateLocations, getScalebarRefNameLabels, regionsOrientation, groupContiguousBlocks, labelFitsInBlock, makeBlockTicks, runRefNameLabelPx, tickLabelWidth, } from './util.js';
|
|
25
28
|
// lazies
|
|
26
29
|
const SearchResultsDialog = lazy(() => import('./components/SearchResultsDialog.js'));
|
|
30
|
+
// The one array `paddingSpans` returns when a view has nothing to mask, so that
|
|
31
|
+
// computed's value repeats by identity. Frozen because every consumer maps over
|
|
32
|
+
// it and a caller that sorted in place would do so for the whole session.
|
|
33
|
+
const NO_PADDING_SPANS = Object.freeze([]);
|
|
27
34
|
/**
|
|
28
35
|
* Calculate the offsetPx needed to center content within a viewport.
|
|
29
36
|
* Returns a negative offset when content is smaller than viewport (padding on left).
|
|
@@ -141,30 +148,28 @@ function isTrackSelectorVisible(self) {
|
|
|
141
148
|
* #category view
|
|
142
149
|
*
|
|
143
150
|
* #example
|
|
144
|
-
* A `LinearGenomeView` is what you hand-author under `defaultSession.views
|
|
145
|
-
*
|
|
151
|
+
* A `LinearGenomeView` is what you hand-author under `defaultSession.views`, and
|
|
152
|
+
* every setting goes directly on the view object. `assembly`/`loc` fill in
|
|
153
|
+
* `displayedRegions`/`bpPerPx`/`offsetPx` for you:
|
|
146
154
|
* ```js
|
|
147
155
|
* defaultSession: {
|
|
148
156
|
* name: 'My session',
|
|
149
157
|
* views: [
|
|
150
158
|
* {
|
|
151
159
|
* type: 'LinearGenomeView',
|
|
152
|
-
* // plain persisted props sit alongside init, not inside it
|
|
153
160
|
* colorByCDS: true,
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* tracks: ['genes', 'alignments'],
|
|
158
|
-
* },
|
|
161
|
+
* assembly: 'hg38',
|
|
162
|
+
* loc: 'chr1:1,000,000-1,100,000',
|
|
163
|
+
* tracks: ['genes', 'alignments'],
|
|
159
164
|
* },
|
|
160
165
|
* ],
|
|
161
166
|
* }
|
|
162
167
|
* ```
|
|
163
|
-
* `
|
|
164
|
-
*
|
|
165
|
-
* `colorByCDS`, `showAminoAcids`, `showCenterLine`, `trackLabels
|
|
166
|
-
* `showHighlightChips` are
|
|
167
|
-
*
|
|
168
|
+
* `assembly`, `loc`, `tracks`, `tracklist`, `nav` and `highlight` need
|
|
169
|
+
* on-attach resolution and are captured into the `launch` property below;
|
|
170
|
+
* `colorByCDS`, `showAminoAcids`, `showCenterLine`, `trackLabels` and
|
|
171
|
+
* `showHighlightChips` are plain view props MST restores natively. Both are
|
|
172
|
+
* written the same way.
|
|
168
173
|
* At runtime the same model is driven imperatively — every property and action
|
|
169
174
|
* below is reachable on `viewState.session.views[0]`:
|
|
170
175
|
* ```js
|
|
@@ -175,7 +180,7 @@ function isTrackSelectorVisible(self) {
|
|
|
175
180
|
* ```
|
|
176
181
|
*/
|
|
177
182
|
export function stateModelFactory(pluginManager) {
|
|
178
|
-
|
|
183
|
+
const model = types
|
|
179
184
|
.compose('LinearGenomeView', BaseViewModel, HighlightsMixin(), types.model({
|
|
180
185
|
/**
|
|
181
186
|
* #property
|
|
@@ -321,20 +326,14 @@ export function stateModelFactory(pluginManager) {
|
|
|
321
326
|
scalebarOnly: types.stripDefault(types.boolean, false),
|
|
322
327
|
/**
|
|
323
328
|
* #property
|
|
324
|
-
* transient
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
* "loc": "chr1:1,000,000-2,000,000",
|
|
333
|
-
* "tracks": ["genes", "variants"]
|
|
334
|
-
* }
|
|
335
|
-
* ```
|
|
336
|
-
*/
|
|
337
|
-
init: types.frozen(),
|
|
329
|
+
* transient launch state: the settings written on the view object that
|
|
330
|
+
* need resolving before they can be view state — an assembly, a
|
|
331
|
+
* location, track recipes, highlights. `preProcessSnapshot` moves them
|
|
332
|
+
* here off the snapshot, the afterAttach autorun applies them and
|
|
333
|
+
* clears this, so a saved session never retains it. Not written by
|
|
334
|
+
* hand: author every setting directly on the view.
|
|
335
|
+
*/
|
|
336
|
+
launch: types.frozen(),
|
|
338
337
|
}))
|
|
339
338
|
.volatile(self => {
|
|
340
339
|
// typed locals so `unknown`/`Record` aren't narrowed to `undefined`/`{}`; inline
|
|
@@ -413,6 +412,14 @@ export function stateModelFactory(pluginManager) {
|
|
|
413
412
|
// ahead of every other getter so that the ~300 reads of `self.bpPerPx` /
|
|
414
413
|
// `self.offsetPx` across the codebase go on resolving by name. Only the
|
|
415
414
|
// writes had to move, and they are six actions below.
|
|
415
|
+
/**
|
|
416
|
+
* #getter
|
|
417
|
+
* the launch state that still has something to apply — the gate every
|
|
418
|
+
* loading and error path below reads.
|
|
419
|
+
*/
|
|
420
|
+
get pendingLaunch() {
|
|
421
|
+
return pendingLaunch(self.launch);
|
|
422
|
+
},
|
|
416
423
|
/**
|
|
417
424
|
* #getter
|
|
418
425
|
* corresponds roughly to the zoom level, base-pairs per pixel.
|
|
@@ -555,10 +562,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
555
562
|
*/
|
|
556
563
|
get assemblyErrors() {
|
|
557
564
|
const { assemblyManager } = getSession(self);
|
|
558
|
-
return self.assemblyNames
|
|
559
|
-
.map(name => assemblyManager.get(name)?.error)
|
|
560
|
-
.filter(e => !!e)
|
|
561
|
-
.join(', ');
|
|
565
|
+
return assemblyErrorMessage(assemblyManager, self.assemblyNames);
|
|
562
566
|
},
|
|
563
567
|
/**
|
|
564
568
|
* #getter
|
|
@@ -575,8 +579,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
575
579
|
* and error checks resolve it directly through here.
|
|
576
580
|
*/
|
|
577
581
|
get initAssembly() {
|
|
578
|
-
return self.
|
|
579
|
-
? getSession(self).assemblyManager.get(self.
|
|
582
|
+
return self.pendingLaunch
|
|
583
|
+
? getSession(self).assemblyManager.get(self.pendingLaunch.assembly)
|
|
580
584
|
: undefined;
|
|
581
585
|
},
|
|
582
586
|
/**
|
|
@@ -587,7 +591,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
587
591
|
return false;
|
|
588
592
|
}
|
|
589
593
|
// if init is set, wait for that assembly to have regions loaded
|
|
590
|
-
if (self.
|
|
594
|
+
if (self.pendingLaunch) {
|
|
591
595
|
const asm = this.initAssembly;
|
|
592
596
|
return !!(asm?.initialized && asm.regions);
|
|
593
597
|
}
|
|
@@ -606,7 +610,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
606
610
|
* displayed assemblies that hasn't finished loading.
|
|
607
611
|
*/
|
|
608
612
|
get loadingAssembly() {
|
|
609
|
-
return self.
|
|
613
|
+
return self.pendingLaunch
|
|
610
614
|
? this.initAssembly
|
|
611
615
|
: getSession(self).assemblyManager.loadingAssembly(self.assemblyNames);
|
|
612
616
|
},
|
|
@@ -646,7 +650,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
646
650
|
* #getter
|
|
647
651
|
*/
|
|
648
652
|
get hasSomethingToShow() {
|
|
649
|
-
return this.hasDisplayedRegions || !!self.
|
|
653
|
+
return this.hasDisplayedRegions || !!self.pendingLaunch;
|
|
650
654
|
},
|
|
651
655
|
/**
|
|
652
656
|
* #getter
|
|
@@ -656,7 +660,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
656
660
|
* would mount over empty regions and pxToBp/hover would throw.
|
|
657
661
|
*
|
|
658
662
|
* **Not dotplot's or synteny's `initPending`**, which is the bare
|
|
659
|
-
* `!!self.
|
|
663
|
+
* `!!self.pendingLaunch` — "an init blob has not been applied" — and which those
|
|
660
664
|
* views read from their `settled` gate rather than from `showLoading`.
|
|
661
665
|
* This one is narrower on purpose: it closes only the window where there
|
|
662
666
|
* is nothing on screen. Once navigation has produced regions the view is
|
|
@@ -666,7 +670,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
666
670
|
* theirs.
|
|
667
671
|
*/
|
|
668
672
|
get awaitingInitNavigation() {
|
|
669
|
-
return !!self.
|
|
673
|
+
return !!self.pendingLaunch && !this.hasDisplayedRegions;
|
|
670
674
|
},
|
|
671
675
|
/**
|
|
672
676
|
* #getter
|
|
@@ -935,17 +939,17 @@ export function stateModelFactory(pluginManager) {
|
|
|
935
939
|
return this.assembliesNotFound;
|
|
936
940
|
}
|
|
937
941
|
// Check init assembly for errors (displayedRegions may be empty during init)
|
|
938
|
-
if (self.
|
|
942
|
+
if (self.pendingLaunch) {
|
|
939
943
|
// `assembly` is required by InitState, but init is a frozen blob and
|
|
940
944
|
// hand-authored JSON is what fills it, so the type is not a guarantee.
|
|
941
945
|
// Naming the authoring mistake beats the downstream symptom, which is
|
|
942
946
|
// the literal string "Assembly undefined not found".
|
|
943
|
-
if (!self.
|
|
947
|
+
if (!self.pendingLaunch.assembly) {
|
|
944
948
|
return 'LinearGenomeView init needs an "assembly"';
|
|
945
949
|
}
|
|
946
950
|
const asm = this.initAssembly;
|
|
947
951
|
if (!asm) {
|
|
948
|
-
return `Assembly ${self.
|
|
952
|
+
return `Assembly ${self.pendingLaunch.assembly} not found`;
|
|
949
953
|
}
|
|
950
954
|
if (asm.error) {
|
|
951
955
|
return asm.error;
|
|
@@ -1221,6 +1225,16 @@ export function stateModelFactory(pluginManager) {
|
|
|
1221
1225
|
hideTrack(trackId) {
|
|
1222
1226
|
return hideTrackGeneric(self, trackId);
|
|
1223
1227
|
},
|
|
1228
|
+
}))
|
|
1229
|
+
.actions(self => ({
|
|
1230
|
+
/**
|
|
1231
|
+
* #action
|
|
1232
|
+
* showTrack for a track whose display state model may be lazily
|
|
1233
|
+
* loaded: loads it, then shows
|
|
1234
|
+
*/
|
|
1235
|
+
async launchTrack(trackId, initialSnapshot = {}, displayInitialSnapshot = {}, inlineConf) {
|
|
1236
|
+
return launchTrackGeneric(self, trackId, initialSnapshot, displayInitialSnapshot, inlineConf);
|
|
1237
|
+
},
|
|
1224
1238
|
}))
|
|
1225
1239
|
.actions(self => ({
|
|
1226
1240
|
/**
|
|
@@ -1289,6 +1303,13 @@ export function stateModelFactory(pluginManager) {
|
|
|
1289
1303
|
toggleTrack(trackId) {
|
|
1290
1304
|
return toggleTrackGeneric(self, trackId);
|
|
1291
1305
|
},
|
|
1306
|
+
/**
|
|
1307
|
+
* #action
|
|
1308
|
+
* toggleTrack with launchTrack's loading behavior
|
|
1309
|
+
*/
|
|
1310
|
+
async launchToggleTrack(trackId) {
|
|
1311
|
+
return launchToggleTrackGeneric(self, trackId);
|
|
1312
|
+
},
|
|
1292
1313
|
/**
|
|
1293
1314
|
* #action
|
|
1294
1315
|
*/
|
|
@@ -1402,8 +1423,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
1402
1423
|
/**
|
|
1403
1424
|
* #action
|
|
1404
1425
|
*/
|
|
1405
|
-
|
|
1406
|
-
self.
|
|
1426
|
+
setLaunch(launch) {
|
|
1427
|
+
self.launch = launch;
|
|
1407
1428
|
},
|
|
1408
1429
|
/**
|
|
1409
1430
|
* #method
|
|
@@ -1866,6 +1887,11 @@ export function stateModelFactory(pluginManager) {
|
|
|
1866
1887
|
* The seam is the one that must survive: regions lay out contiguously,
|
|
1867
1888
|
* so it is all that separates two of them.
|
|
1868
1889
|
*/
|
|
1890
|
+
// Annotated with the shape spelled out rather than as `PaddingSpan`,
|
|
1891
|
+
// because the API doc's type column is where a host drawing its own
|
|
1892
|
+
// chrome reads the field names, and a named type prints as its name.
|
|
1893
|
+
// `spans` and NO_PADDING_SPANS are both `PaddingSpan[]`, so the two
|
|
1894
|
+
// cannot drift without failing here.
|
|
1869
1895
|
get paddingSpans() {
|
|
1870
1896
|
const { blocks, offsetPx: firstBlockOffset } = this.staticBlocks;
|
|
1871
1897
|
const spans = [];
|
|
@@ -1892,7 +1918,14 @@ export function stateModelFactory(pluginManager) {
|
|
|
1892
1918
|
});
|
|
1893
1919
|
}
|
|
1894
1920
|
}
|
|
1895
|
-
|
|
1921
|
+
// One shared array when there is nothing to draw, so the computed's
|
|
1922
|
+
// value repeats and MobX's `===` stops the chain here. This is the
|
|
1923
|
+
// COMMON case, not an edge one: a view sitting inside one contig has
|
|
1924
|
+
// no region seam, no elision and no boundary, and that is where a
|
|
1925
|
+
// reader spends nearly all of a session. A fresh `[]` each frame
|
|
1926
|
+
// instead re-rendered a PaddingBlocks per track plus one for the
|
|
1927
|
+
// container, every frame of every gesture, to draw nothing.
|
|
1928
|
+
return spans.length > 0 ? spans : NO_PADDING_SPANS;
|
|
1896
1929
|
},
|
|
1897
1930
|
/**
|
|
1898
1931
|
* #getter
|
|
@@ -1962,6 +1995,32 @@ export function stateModelFactory(pluginManager) {
|
|
|
1962
1995
|
};
|
|
1963
1996
|
});
|
|
1964
1997
|
},
|
|
1998
|
+
/**
|
|
1999
|
+
* #getter
|
|
2000
|
+
* Right edge (px, viewport-relative) of the on-screen content, clamped
|
|
2001
|
+
* to the track — where a right-pinned overlay such as a wiggle's colour
|
|
2002
|
+
* or score legend belongs, rather than out in the empty gutter the
|
|
2003
|
+
* regions can leave at whole-genome zoom. `@jbrowse/display-kit`'s
|
|
2004
|
+
* `contentRightEdgePx` states the rule; an SVG export applies the same
|
|
2005
|
+
* one against the export's canvas width instead of this track's.
|
|
2006
|
+
*
|
|
2007
|
+
* Published as a SCALAR on purpose. `visibleRegions` rebuilds a fresh
|
|
2008
|
+
* array of fresh objects on every pan and zoom frame, so a component
|
|
2009
|
+
* that derived this number itself re-rendered on every frame of every
|
|
2010
|
+
* gesture — to produce, whenever content fills the track, the unchanged
|
|
2011
|
+
* `trackWidthPx`. Reading the scalar lets MobX's `===` stop the chain at
|
|
2012
|
+
* this computed instead. Measured over 20 zoom frames: the wiggle
|
|
2013
|
+
* family's three body instances fell from 66 renders to 7.
|
|
2014
|
+
*/
|
|
2015
|
+
get contentRightEdgePx() {
|
|
2016
|
+
// The `trackWidthPx` rule sends a DISPLAY to `model.canvasWidthPx`
|
|
2017
|
+
// so it cannot pick the wrong one of four view getters. This is the
|
|
2018
|
+
// view, publishing its own geometry, and `canvasWidthPx` is defined
|
|
2019
|
+
// as exactly this host's `trackWidthPx` (MultiRegionDisplayMixin) —
|
|
2020
|
+
// the same source of truth, read from the side that owns it.
|
|
2021
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
2022
|
+
return contentRightEdgePx(this.visibleRegions, self.trackWidthPx);
|
|
2023
|
+
},
|
|
1965
2024
|
/**
|
|
1966
2025
|
* #getter
|
|
1967
2026
|
* visibleRegions expanded by a half-screen buffer on each side,
|
|
@@ -2386,13 +2445,15 @@ export function stateModelFactory(pluginManager) {
|
|
|
2386
2445
|
* navigating to the locstring
|
|
2387
2446
|
* @param grow - optional multiplier to expand the region by (e.g., 0.2
|
|
2388
2447
|
* adds 20% padding on each side)
|
|
2448
|
+
* @param opts - `showHitTrack: false` keeps a text-search hit from also
|
|
2449
|
+
* showing the track its index was built from
|
|
2389
2450
|
* @returns whether the view moved. A multi-hit search raises the picker
|
|
2390
2451
|
* instead of navigating, and both resolve — see `handleSelectedRegion`.
|
|
2391
2452
|
*/
|
|
2392
2453
|
// annotated, not inferred: the same type cycle `showTrack` names. Left to
|
|
2393
2454
|
// infer, the `boolean` reaches this model's type through `self` and 180
|
|
2394
2455
|
// unrelated files lose their parameter types to it.
|
|
2395
|
-
async navToLocString(input, optAssemblyName, grow) {
|
|
2456
|
+
async navToLocString(input, optAssemblyName, grow, opts) {
|
|
2396
2457
|
const { assemblyNames } = self;
|
|
2397
2458
|
const session = getSession(self);
|
|
2398
2459
|
const { assemblyManager } = session;
|
|
@@ -2407,6 +2468,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
2407
2468
|
input,
|
|
2408
2469
|
assemblyName,
|
|
2409
2470
|
grow,
|
|
2471
|
+
showHitTrack: opts?.showHitTrack,
|
|
2410
2472
|
model: self,
|
|
2411
2473
|
});
|
|
2412
2474
|
},
|
|
@@ -2697,7 +2759,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
2697
2759
|
afterAttach() {
|
|
2698
2760
|
doAfterAttach(self);
|
|
2699
2761
|
},
|
|
2700
|
-
}))
|
|
2762
|
+
}));
|
|
2763
|
+
return withLaunchInput(model, lgvLaunchKeys)
|
|
2701
2764
|
.preProcessSnapshot((snap) => {
|
|
2702
2765
|
if (!snap) {
|
|
2703
2766
|
return snap;
|
|
@@ -2739,23 +2802,24 @@ export function stateModelFactory(pluginManager) {
|
|
|
2739
2802
|
};
|
|
2740
2803
|
})
|
|
2741
2804
|
.postProcessSnapshot(snap => {
|
|
2742
|
-
//
|
|
2805
|
+
// launch is transient, never persisted. showCenterLine is
|
|
2743
2806
|
// purely a localStorage-backed preference (see setupLocalStorageAutorun
|
|
2744
2807
|
// in afterAttach.ts) and is never persisted into session snapshots. The
|
|
2745
2808
|
// remaining fields are also localStorage-backed, but still persist when
|
|
2746
2809
|
// non-default: their strip baseline is the universal default (hardcoded
|
|
2747
2810
|
// here), not the localStorage-derived creation default, so a
|
|
2748
2811
|
// localStorage-set value stays portable across browsers.
|
|
2749
|
-
const {
|
|
2812
|
+
const { launch, showCenterLine, showCytobands, trackLabels, colorByCDS, showAminoAcids, showTrackOutlines, ...rest } = snap;
|
|
2750
2813
|
return {
|
|
2751
2814
|
...rest,
|
|
2752
|
-
// keep
|
|
2753
|
-
// launch autorun navigates (e.g. autosave firing
|
|
2754
|
-
// rebuild the view instead of dropping to the
|
|
2755
|
-
// displayedRegions is stripDefault, so it's absent (not
|
|
2756
|
-
// the optional chain is runtime-necessary despite the
|
|
2815
|
+
// keep the launch state until displayedRegions exist, so a snapshot
|
|
2816
|
+
// taken before the launch autorun navigates (e.g. autosave firing
|
|
2817
|
+
// mid-load) can still rebuild the view instead of dropping to the
|
|
2818
|
+
// import form. displayedRegions is stripDefault, so it's absent (not
|
|
2819
|
+
// []) when empty — the optional chain is runtime-necessary despite the
|
|
2820
|
+
// non-nullish type.
|
|
2757
2821
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
2758
|
-
...(
|
|
2822
|
+
...(launch && !snap.displayedRegions?.length ? { launch } : {}),
|
|
2759
2823
|
...(!showCytobands ? { showCytobands } : {}),
|
|
2760
2824
|
...(trackLabels ? { trackLabels } : {}),
|
|
2761
2825
|
...(colorByCDS ? { colorByCDS } : {}),
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { svgNodeId } from '@jbrowse/core/svg/svgId';
|
|
3
|
+
import { contractReportsOn, reportContractViolation, } from '@jbrowse/render-core/contractReports';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* No-ops in production.
|
|
5
|
+
* Checks that a live SVG figure is being used the way it can work.
|
|
6
6
|
*
|
|
7
7
|
* `useViewSvgFigure` publishes a figure whose two halves are drawn at different
|
|
8
8
|
* moments and only agree by construction, and both ways of breaking that are
|
|
9
9
|
* invisible in the output: the drawing is still a plausible picture of
|
|
10
10
|
* somewhere, just not of one moment or not with its own clips. So the run
|
|
11
11
|
* reports instead, through the `[jbrowse <family> contract]` channel every
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* check in the tree uses — `config/jest/contractGate.js` fails the test that
|
|
13
|
+
* collects one, and ARCHITECTURAL_LIMITS.md §"Ordering is the contract" is the
|
|
14
|
+
* register. `figure` rather than `svg-figure` because the gate's family pattern
|
|
15
|
+
* is `\w+`, which a hyphen is not.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Reported and never thrown, as the rest of that family: a figure is chrome a
|
|
18
|
+
* host renders beside its own, and taking the host's tree down over a drawing
|
|
19
|
+
* is worse than the drawing being wrong.
|
|
20
20
|
*/
|
|
21
21
|
function report(message) {
|
|
22
|
-
|
|
22
|
+
reportContractViolation('figure', message);
|
|
23
23
|
}
|
|
24
24
|
// Live figures per view, keyed the way their SVG ids are. A count rather than a
|
|
25
25
|
// flag: the report belongs on the second mount, and the first has to survive the
|
|
@@ -43,9 +43,6 @@ const figuresPerView = new Map();
|
|
|
43
43
|
*/
|
|
44
44
|
export function useOneFigurePerView(view) {
|
|
45
45
|
useEffect(() => {
|
|
46
|
-
if (process.env.NODE_ENV === 'production') {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
46
|
// captured, not re-derived on cleanup: by then the view may have left the
|
|
50
47
|
// tree, and `svgNodeId` walks it
|
|
51
48
|
const key = svgNodeId(view);
|
|
@@ -99,7 +96,10 @@ export function useFrozenFigureContract(ref, snapshot) {
|
|
|
99
96
|
rendered.current = snapshot;
|
|
100
97
|
useEffect(() => {
|
|
101
98
|
const el = ref.current;
|
|
102
|
-
|
|
99
|
+
// the one check here with a cost of its own — a `MutationObserver` over the
|
|
100
|
+
// whole figure subtree — so unlike its neighbour it asks whether the
|
|
101
|
+
// channel is armed rather than running and reporting into it
|
|
102
|
+
if (!contractReportsOn() || !el) {
|
|
103
103
|
return undefined;
|
|
104
104
|
}
|
|
105
105
|
const observer = new MutationObserver(() => {
|
|
@@ -101,8 +101,8 @@ async function renderFigure(view, opts) {
|
|
|
101
101
|
*/
|
|
102
102
|
const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
|
|
103
103
|
const { width, height, margin, theme } = snapshot;
|
|
104
|
-
//
|
|
105
|
-
//
|
|
104
|
+
// it watches for the one thing this component cannot enforce: an observer
|
|
105
|
+
// INSIDE the tree, which the memo above is not between
|
|
106
106
|
const ref = useRef(null);
|
|
107
107
|
useFrozenFigureContract(ref, snapshot);
|
|
108
108
|
return (_jsx(ThemeProvider, { theme: createJBrowseTheme(theme), children: _jsx(StyleThemeProvider, { theme: resolveStyleTheme({ configTheme: theme }), children: _jsx("svg", { ref: ref, width: width, height: height, viewBox: `0 0 ${width} ${height}`,
|
|
@@ -41,4 +41,4 @@ export interface InitState {
|
|
|
41
41
|
nav?: boolean;
|
|
42
42
|
highlight?: (string | HighlightType)[];
|
|
43
43
|
}
|
|
44
|
-
export type LinearGenomeViewLaunchProps = Partial<Omit<SnapshotIn<LinearGenomeViewStateModel>, keyof InitState | 'id' | 'type' | '
|
|
44
|
+
export type LinearGenomeViewLaunchProps = Partial<Omit<SnapshotIn<LinearGenomeViewStateModel>, keyof InitState | 'id' | 'type' | 'launch'>>;
|