@jbrowse/plugin-linear-genome-view 5.0.0-beta.17 → 5.0.0-beta.18
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/FeatureTrack/configSchema.d.ts +3 -1
- package/esm/FeatureTrack/configSchema.js +3 -1
- package/esm/LinearGenomeView/afterAttach.js +6 -4
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +1 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +1 -1
- package/esm/LinearGenomeView/model.d.ts +77 -7
- package/esm/LinearGenomeView/model.js +105 -33
- package/esm/LinearGenomeView/rExport/rScript.d.ts +21 -0
- package/esm/LinearGenomeView/rExport/rScript.js +3 -3
- package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
- package/esm/LinearGenomeView/showFiles.d.ts +2 -0
- package/esm/LinearGenomeView/showFiles.js +19 -0
- package/esm/TrackModifiers/index.d.ts +80 -0
- package/esm/TrackModifiers/index.js +212 -0
- package/esm/index.d.ts +3 -1
- package/esm/index.js +1 -0
- package/package.json +9 -8
|
@@ -37,7 +37,9 @@ import type PluginManager from '@jbrowse/core/PluginManager';
|
|
|
37
37
|
* type: 'Gff3TabixAdapter',
|
|
38
38
|
* uri: 'https://example.com/genes.sorted.gff.gz',
|
|
39
39
|
* },
|
|
40
|
-
* displayDefaults: {
|
|
40
|
+
* displayDefaults: {
|
|
41
|
+
* encoding: { color: { value: "jexl:feature.type=='CDS'?'red':'blue'" } },
|
|
42
|
+
* },
|
|
41
43
|
* }
|
|
42
44
|
* ```
|
|
43
45
|
*/
|
|
@@ -38,7 +38,9 @@ import { createBaseTrackConfig } from '@jbrowse/core/pluggableElementTypes';
|
|
|
38
38
|
* type: 'Gff3TabixAdapter',
|
|
39
39
|
* uri: 'https://example.com/genes.sorted.gff.gz',
|
|
40
40
|
* },
|
|
41
|
-
* displayDefaults: {
|
|
41
|
+
* displayDefaults: {
|
|
42
|
+
* encoding: { color: { value: "jexl:feature.type=='CDS'?'red':'blue'" } },
|
|
43
|
+
* },
|
|
42
44
|
* }
|
|
43
45
|
* ```
|
|
44
46
|
*/
|
|
@@ -2,8 +2,10 @@ import { getSession, isSessionModelWithWidgets, resolveNamedRegions, } from '@jb
|
|
|
2
2
|
import { coerceHighlight } from '@jbrowse/core/util/highlights';
|
|
3
3
|
import { installInitAutorun } from '@jbrowse/core/util/installInitAutorun';
|
|
4
4
|
import { normalizeTrackInit, warmTrackDisplayGeneric, } from '@jbrowse/core/util/tracks';
|
|
5
|
+
import { requestedDisplayType } from '@jbrowse/launch-args/displaySettings';
|
|
5
6
|
import { addDisposer } from '@jbrowse/mobx-state-tree';
|
|
6
7
|
import { autorun, untracked, when } from 'mobx';
|
|
8
|
+
import { launchTrackInit } from '../TrackModifiers/index.js';
|
|
7
9
|
import { SearchResultsNotFoundError } from '../searchUtils.js';
|
|
8
10
|
// `init` is a frozen blob that often comes from hand-authored JSON, where a
|
|
9
11
|
// lone entry gets written bare (`tracks: 'genes'`). A string is iterable, so
|
|
@@ -127,8 +129,7 @@ async function navigateInit(self, session, init) {
|
|
|
127
129
|
// display's state model first when it is registered lazily
|
|
128
130
|
async function showInitTracks(self, init) {
|
|
129
131
|
for (const t of asArray(init.tracks)) {
|
|
130
|
-
|
|
131
|
-
await self.launchTrack(trackId, trackSnapshot, displaySnapshot);
|
|
132
|
+
await launchTrackInit(self, t);
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
/**
|
|
@@ -219,8 +220,9 @@ function setupInitWarmup(self) {
|
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
for (const t of tracks) {
|
|
222
|
-
const { trackId, displaySnapshot } = normalizeTrackInit(t);
|
|
223
|
-
|
|
223
|
+
const { trackId, displaySnapshot, modifiers } = normalizeTrackInit(t);
|
|
224
|
+
const type = requestedDisplayType(modifiers);
|
|
225
|
+
warmTrackDisplayGeneric(self, trackId, type ? { ...displaySnapshot, type } : displaySnapshot, assembly);
|
|
224
226
|
}
|
|
225
227
|
});
|
|
226
228
|
}, { name: 'LGVInitWarmup' }));
|
|
@@ -102,7 +102,9 @@ const LinearGenomeViewContainer = observer(function LinearGenomeViewContainer({
|
|
|
102
102
|
.flatMap(t => t.displays)
|
|
103
103
|
.map(d => d.hoveredFeature)
|
|
104
104
|
.find(Boolean);
|
|
105
|
-
|
|
105
|
+
// `viewId` lets a view paired with one linear view ignore another
|
|
106
|
+
// showing the same locus
|
|
107
|
+
session.setHovered({ hoverPosition, hoverFeature, viewId: model.id });
|
|
106
108
|
}, children: [_jsx(NavigationAnnouncer, { model: model }), _jsxs("div", { ref: headerRef, "data-testid": `lgv-header-${model.id}`, className: classes.header, style: { position: stickyViewHeaders ? 'sticky' : undefined }, children: [_jsx(Header, { model: model }), hideHeader ? _jsx(MiniControls, { model: model }) : null] }), _jsx("div", { ref: tracksRef, children: model.scalebarOnly ? (_jsx(Rubberband, { model: model })) : (_jsx(TracksContainer, { model: model, children: !tracks.length ? (_jsx(NoTracksActiveButton, { model: model })) : (_jsxs(_Fragment, { children: [pinnedTracks.length ? (_jsx(Paper, { elevation: 2, "data-testid": "pinned-tracks", className: classes.pinnedTracks, style: {
|
|
107
109
|
top: stickyTops.pinnedTracks,
|
|
108
110
|
// the scroll port this sticks in, not the window: a
|
|
@@ -22,7 +22,7 @@ export type SearchBoxPrefs = ReturnType<typeof useSearchBoxPrefs>;
|
|
|
22
22
|
/**
|
|
23
23
|
* The menu rows that drive those prefs. "Stacked" rather than "Vertical": the
|
|
24
24
|
* two headers name the same state and had drifted apart on it once already.
|
|
25
|
-
* A caller spreading the rows into a "Show
|
|
25
|
+
* A caller spreading the rows into a "Show" submenu passes `'Search boxes'`
|
|
26
26
|
* as the toggle's label.
|
|
27
27
|
*/
|
|
28
28
|
export declare function searchBoxMenuItems({ showSearchBoxes, setShowSearchBoxes, sideBySide, setSideBySide, }: SearchBoxPrefs, toggleLabel?: string): MenuItem[];
|
|
@@ -22,7 +22,7 @@ export function useSearchBoxPrefs(prefix, numViews) {
|
|
|
22
22
|
/**
|
|
23
23
|
* The menu rows that drive those prefs. "Stacked" rather than "Vertical": the
|
|
24
24
|
* two headers name the same state and had drifted apart on it once already.
|
|
25
|
-
* A caller spreading the rows into a "Show
|
|
25
|
+
* A caller spreading the rows into a "Show" submenu passes `'Search boxes'`
|
|
26
26
|
* as the toggle's label.
|
|
27
27
|
*/
|
|
28
28
|
export function searchBoxMenuItems({ showSearchBoxes, setShowSearchBoxes, sideBySide, setSideBySide, }, toggleLabel = 'Show search boxes') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BpOffset, ExportSvgOptions, HighlightType, InitState, NavLocation, ViewTrackLabelMode, VolatileGuide } from './types.ts';
|
|
1
|
+
import type { BpOffset, ExportSvgOptions, HighlightInput, HighlightType, InitState, NavLocation, ViewTrackLabelMode, VolatileGuide } from './types.ts';
|
|
2
2
|
import type { RegionEdit } from './util.ts';
|
|
3
3
|
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
4
4
|
import type BaseResult from '@jbrowse/core/TextSearch/BaseResults';
|
|
@@ -20,18 +20,24 @@ export interface SearchPickerRow {
|
|
|
20
20
|
trackName: string;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* a dialog, and `SearchPicker` in `@jbrowse/display-ui/embed` a plain list.
|
|
23
|
+
* A name search's hits as a host lists them: what `view.search` returns, and
|
|
24
|
+
* what the picker holds.
|
|
26
25
|
*/
|
|
27
|
-
export interface
|
|
26
|
+
export interface SearchHits {
|
|
28
27
|
query: string;
|
|
29
28
|
assemblyName: string;
|
|
30
|
-
/** one row per
|
|
29
|
+
/** one row per hit, hits in a track the view already shows first */
|
|
31
30
|
rows: SearchPickerRow[];
|
|
32
|
-
/**
|
|
31
|
+
/** lands on the row's place and reports a failure */
|
|
33
32
|
pick: (id: string) => Promise<unknown>;
|
|
34
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The hits of a name search that point at more than one place, waiting for the
|
|
36
|
+
* user to choose; its `pick` also closes the picker. A host draws them however
|
|
37
|
+
* it likes: the Material view shows a dialog, and `SearchPicker` in
|
|
38
|
+
* `@jbrowse/display-ui/embed` a plain list.
|
|
39
|
+
*/
|
|
40
|
+
export type SearchPickerState = SearchHits;
|
|
35
41
|
export type { PaddingSpan } from './util.ts';
|
|
36
42
|
export interface HighlightRegion {
|
|
37
43
|
assemblyName?: string;
|
|
@@ -172,6 +178,13 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
172
178
|
* #volatile
|
|
173
179
|
*/
|
|
174
180
|
coarseBpPerPx: number;
|
|
181
|
+
/**
|
|
182
|
+
* #volatile
|
|
183
|
+
* the scroll offset the coarse blocks were laid out at, in px at
|
|
184
|
+
* `coarseBpPerPx`: the settled viewport's left edge, which the first
|
|
185
|
+
* coarse block is not while the view is scrolled before the first region
|
|
186
|
+
*/
|
|
187
|
+
coarseOffsetPx: number;
|
|
175
188
|
/**
|
|
176
189
|
* #volatile
|
|
177
190
|
*/
|
|
@@ -967,6 +980,15 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
967
980
|
* there and shows the track its index came from
|
|
968
981
|
*/
|
|
969
982
|
setSearchResults(searchResults: BaseResult[], searchQuery: string, assemblyName: string, onPick?: (result: BaseResult) => Promise<unknown>): void;
|
|
983
|
+
/**
|
|
984
|
+
* #action
|
|
985
|
+
* Search the view's assembly by name, for a host listing the hits
|
|
986
|
+
* itself. A row's `pick` lands as the search box does: it navigates
|
|
987
|
+
* there and shows the track the hit's index came from.
|
|
988
|
+
*/
|
|
989
|
+
search(query: string, opts?: {
|
|
990
|
+
signal?: AbortSignal;
|
|
991
|
+
}): Promise<SearchHits>;
|
|
970
992
|
/**
|
|
971
993
|
* #action
|
|
972
994
|
*/
|
|
@@ -988,6 +1010,22 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
988
1010
|
* loaded: loads it, then shows
|
|
989
1011
|
*/
|
|
990
1012
|
launchTrack(trackId: string, initialSnapshot?: any, displayInitialSnapshot?: any, inlineConf?: Record<string, unknown>): Promise<any>;
|
|
1013
|
+
/**
|
|
1014
|
+
* #action
|
|
1015
|
+
* Replace the highlights `highlights` reads, each written as the launch
|
|
1016
|
+
* spec's `highlight` writes one: a locstring, or an object whose
|
|
1017
|
+
* `assemblyName` defaults to the view's. Throws on one that names no
|
|
1018
|
+
* region, writing none.
|
|
1019
|
+
*/
|
|
1020
|
+
setHighlights(highlights: readonly (string | HighlightInput)[]): void;
|
|
1021
|
+
/**
|
|
1022
|
+
* #action
|
|
1023
|
+
* Show files from the user's disk as session tracks: one per data file,
|
|
1024
|
+
* beside the index among them that names it, its type guessed from its
|
|
1025
|
+
* name. Adds nothing when one file has no known type. Resolves the new
|
|
1026
|
+
* tracks' ids.
|
|
1027
|
+
*/
|
|
1028
|
+
showFiles(files: readonly File[]): Promise<string[]>;
|
|
991
1029
|
} & {
|
|
992
1030
|
/**
|
|
993
1031
|
* #method
|
|
@@ -1911,6 +1949,13 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
1911
1949
|
* #volatile
|
|
1912
1950
|
*/
|
|
1913
1951
|
coarseBpPerPx: number;
|
|
1952
|
+
/**
|
|
1953
|
+
* #volatile
|
|
1954
|
+
* the scroll offset the coarse blocks were laid out at, in px at
|
|
1955
|
+
* `coarseBpPerPx`: the settled viewport's left edge, which the first
|
|
1956
|
+
* coarse block is not while the view is scrolled before the first region
|
|
1957
|
+
*/
|
|
1958
|
+
coarseOffsetPx: number;
|
|
1914
1959
|
/**
|
|
1915
1960
|
* #volatile
|
|
1916
1961
|
*/
|
|
@@ -2706,6 +2751,15 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
2706
2751
|
* there and shows the track its index came from
|
|
2707
2752
|
*/
|
|
2708
2753
|
setSearchResults(searchResults: BaseResult[], searchQuery: string, assemblyName: string, onPick?: (result: BaseResult) => Promise<unknown>): void;
|
|
2754
|
+
/**
|
|
2755
|
+
* #action
|
|
2756
|
+
* Search the view's assembly by name, for a host listing the hits
|
|
2757
|
+
* itself. A row's `pick` lands as the search box does: it navigates
|
|
2758
|
+
* there and shows the track the hit's index came from.
|
|
2759
|
+
*/
|
|
2760
|
+
search(query: string, opts?: {
|
|
2761
|
+
signal?: AbortSignal;
|
|
2762
|
+
}): Promise<SearchHits>;
|
|
2709
2763
|
/**
|
|
2710
2764
|
* #action
|
|
2711
2765
|
*/
|
|
@@ -2727,6 +2781,22 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
2727
2781
|
* loaded: loads it, then shows
|
|
2728
2782
|
*/
|
|
2729
2783
|
launchTrack(trackId: string, initialSnapshot?: any, displayInitialSnapshot?: any, inlineConf?: Record<string, unknown>): Promise<any>;
|
|
2784
|
+
/**
|
|
2785
|
+
* #action
|
|
2786
|
+
* Replace the highlights `highlights` reads, each written as the launch
|
|
2787
|
+
* spec's `highlight` writes one: a locstring, or an object whose
|
|
2788
|
+
* `assemblyName` defaults to the view's. Throws on one that names no
|
|
2789
|
+
* region, writing none.
|
|
2790
|
+
*/
|
|
2791
|
+
setHighlights(highlights: readonly (string | HighlightInput)[]): void;
|
|
2792
|
+
/**
|
|
2793
|
+
* #action
|
|
2794
|
+
* Show files from the user's disk as session tracks: one per data file,
|
|
2795
|
+
* beside the index among them that names it, its type guessed from its
|
|
2796
|
+
* name. Adds nothing when one file has no known type. Resolves the new
|
|
2797
|
+
* tracks' ids.
|
|
2798
|
+
*/
|
|
2799
|
+
showFiles(files: readonly File[]): Promise<string[]>;
|
|
2730
2800
|
} & {
|
|
2731
2801
|
/**
|
|
2732
2802
|
* #method
|
|
@@ -8,6 +8,7 @@ import { scaleBandHeights } from '@jbrowse/core/util/bandHeight';
|
|
|
8
8
|
import { wholeBaseRegions } from '@jbrowse/core/util/blockTypes';
|
|
9
9
|
import calculateDynamicBlocks from '@jbrowse/core/util/calculateDynamicBlocks';
|
|
10
10
|
import calculateStaticBlocks from '@jbrowse/core/util/calculateStaticBlocks';
|
|
11
|
+
import { coerceHighlight } from '@jbrowse/core/util/highlights';
|
|
11
12
|
import { hideTrackGeneric, launchToggleTrackGeneric, launchTrackGeneric, showTrackGeneric, toggleTrackGeneric, } from '@jbrowse/core/util/tracks';
|
|
12
13
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
13
14
|
import { highlightsOnAssemblies } from '@jbrowse/core/util/viewHighlights';
|
|
@@ -17,7 +18,7 @@ import { MIN_DISPLAY_HEIGHT } from '@jbrowse/display-kit/const';
|
|
|
17
18
|
import { contentRightEdgePx } from '@jbrowse/display-kit/regionHost';
|
|
18
19
|
import { cast, getParent, getSnapshot, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
|
|
19
20
|
import { observable, when } from 'mobx';
|
|
20
|
-
import { handleSelectedRegion, isOpenInView, navToOption, } from '../searchUtils.js';
|
|
21
|
+
import { fetchResults, handleSelectedRegion, isOpenInView, navToOption, } from '../searchUtils.js';
|
|
21
22
|
import { doAfterAttach } from './afterAttach.js';
|
|
22
23
|
import { linearGenomeViewAxisConfigSchema } from './axisConfigSchema.js';
|
|
23
24
|
import { getCytobands, shouldSwapTracks } from './components/util.js';
|
|
@@ -27,7 +28,43 @@ import { setupKeyboardHandler } from './keyboardHandler.js';
|
|
|
27
28
|
import { lgvLaunchKeys } from './launchKeys.js';
|
|
28
29
|
import { buildHighlightsSubMenuItems, buildMenuItems, buildRubberBandMenuItems, buildRubberbandClickMenuItems, } from './menuItems.js';
|
|
29
30
|
import { sharedScaleContainerOf } from './sharedScaleContainer.js';
|
|
31
|
+
import { showFiles } from './showFiles.js';
|
|
30
32
|
import { calculateVisibleLocStrings, cytobandLabelGutterWidth, expandRegion, fitAllRegionsWindow, generateLocations, getGridlineTicks, getPaddingSpans, getScalebarLabels, getScalebarRefNameLabels, regionsOrientation, setDisplayedRegionsKeepingCenter, } from './util.js';
|
|
33
|
+
function searchHitsOf(model, results, query, assemblyName, land) {
|
|
34
|
+
const session = getSession(model);
|
|
35
|
+
const assembly = session.assemblyManager.get(assemblyName);
|
|
36
|
+
const ordered = [...results].sort((a, b) => Number(isOpenInView(b, model)) - Number(isOpenInView(a, model)));
|
|
37
|
+
return {
|
|
38
|
+
query,
|
|
39
|
+
assemblyName,
|
|
40
|
+
rows: ordered.map(result => {
|
|
41
|
+
const location = result.getLocation() ?? '';
|
|
42
|
+
const trackId = result.getTrackId();
|
|
43
|
+
return {
|
|
44
|
+
id: result.getId(),
|
|
45
|
+
label: result.getLabel(),
|
|
46
|
+
location: assembly && location
|
|
47
|
+
? canonicalLocString(location, assembly)
|
|
48
|
+
: location,
|
|
49
|
+
trackName: trackId ? (session.getTrackById(trackId)?.name ?? '') : '',
|
|
50
|
+
};
|
|
51
|
+
}),
|
|
52
|
+
pick: async (id) => {
|
|
53
|
+
const picked = ordered.find(result => result.getId() === id);
|
|
54
|
+
try {
|
|
55
|
+
if (picked) {
|
|
56
|
+
await land(picked);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
console.error(e);
|
|
61
|
+
if (isAlive(model)) {
|
|
62
|
+
getNotificationSink(model).notifyError(`${e}`, e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
31
68
|
/**
|
|
32
69
|
* Calculate the offsetPx needed to center content within a viewport.
|
|
33
70
|
* Returns a negative offset when content is smaller than viewport (padding on left).
|
|
@@ -388,6 +425,13 @@ export function stateModelFactory(pluginManager) {
|
|
|
388
425
|
// displays that lay out against it; `setWidth` seeds the true one at
|
|
389
426
|
// the first measure.
|
|
390
427
|
coarseBpPerPx: 1,
|
|
428
|
+
/**
|
|
429
|
+
* #volatile
|
|
430
|
+
* the scroll offset the coarse blocks were laid out at, in px at
|
|
431
|
+
* `coarseBpPerPx`: the settled viewport's left edge, which the first
|
|
432
|
+
* coarse block is not while the view is scrolled before the first region
|
|
433
|
+
*/
|
|
434
|
+
coarseOffsetPx: 0,
|
|
391
435
|
/**
|
|
392
436
|
* #volatile
|
|
393
437
|
*/
|
|
@@ -1139,6 +1183,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
1139
1183
|
// The debounced coarse autorun is 500ms out; a restored snapshot's
|
|
1140
1184
|
// consumers would lay out and fetch at 1 bp/px until then.
|
|
1141
1185
|
self.coarseBpPerPx = self.bpPerPx;
|
|
1186
|
+
self.coarseOffsetPx = self.offsetPx;
|
|
1142
1187
|
}
|
|
1143
1188
|
},
|
|
1144
1189
|
/**
|
|
@@ -1264,40 +1309,11 @@ export function stateModelFactory(pluginManager) {
|
|
|
1264
1309
|
model: self,
|
|
1265
1310
|
assemblyName,
|
|
1266
1311
|
})) {
|
|
1267
|
-
const
|
|
1268
|
-
const assembly = session.assemblyManager.get(assemblyName);
|
|
1269
|
-
const ordered = [...searchResults].sort((a, b) => Number(isOpenInView(b, self)) -
|
|
1270
|
-
Number(isOpenInView(a, self)));
|
|
1312
|
+
const hits = searchHitsOf(self, searchResults, searchQuery, assemblyName, onPick);
|
|
1271
1313
|
const picker = {
|
|
1272
|
-
|
|
1273
|
-
assemblyName,
|
|
1274
|
-
rows: ordered.map(result => {
|
|
1275
|
-
const location = result.getLocation() ?? '';
|
|
1276
|
-
const trackId = result.getTrackId();
|
|
1277
|
-
return {
|
|
1278
|
-
id: result.getId(),
|
|
1279
|
-
label: result.getLabel(),
|
|
1280
|
-
location: assembly && location
|
|
1281
|
-
? canonicalLocString(location, assembly)
|
|
1282
|
-
: location,
|
|
1283
|
-
trackName: trackId
|
|
1284
|
-
? (session.getTrackById(trackId)?.name ?? '')
|
|
1285
|
-
: '',
|
|
1286
|
-
};
|
|
1287
|
-
}),
|
|
1314
|
+
...hits,
|
|
1288
1315
|
pick: async (id) => {
|
|
1289
|
-
|
|
1290
|
-
try {
|
|
1291
|
-
if (picked) {
|
|
1292
|
-
await onPick(picked);
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
catch (e) {
|
|
1296
|
-
console.error(e);
|
|
1297
|
-
if (isAlive(self)) {
|
|
1298
|
-
getNotificationSink(self).notifyError(`${e}`, e);
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1316
|
+
await hits.pick(id);
|
|
1301
1317
|
if (isAlive(self) && self.searchPicker === picker) {
|
|
1302
1318
|
self.closeSearchPicker();
|
|
1303
1319
|
}
|
|
@@ -1305,6 +1321,31 @@ export function stateModelFactory(pluginManager) {
|
|
|
1305
1321
|
};
|
|
1306
1322
|
self.searchPicker = picker;
|
|
1307
1323
|
},
|
|
1324
|
+
/**
|
|
1325
|
+
* #action
|
|
1326
|
+
* Search the view's assembly by name, for a host listing the hits
|
|
1327
|
+
* itself. A row's `pick` lands as the search box does: it navigates
|
|
1328
|
+
* there and shows the track the hit's index came from.
|
|
1329
|
+
*/
|
|
1330
|
+
async search(query, opts) {
|
|
1331
|
+
const assemblyName = self.assemblyNames[0] ?? self.launchAssemblyName;
|
|
1332
|
+
if (!assemblyName) {
|
|
1333
|
+
throw new Error('the view has no assembly to search yet');
|
|
1334
|
+
}
|
|
1335
|
+
const { assemblyManager, textSearchManager } = getSession(self);
|
|
1336
|
+
const results = await fetchResults({
|
|
1337
|
+
queryString: query,
|
|
1338
|
+
assemblyName,
|
|
1339
|
+
textSearchManager,
|
|
1340
|
+
assembly: await assemblyManager.waitForAssembly(assemblyName),
|
|
1341
|
+
signal: opts?.signal,
|
|
1342
|
+
});
|
|
1343
|
+
return searchHitsOf(self, results, query, assemblyName, result => navToOption({
|
|
1344
|
+
option: result,
|
|
1345
|
+
model: self,
|
|
1346
|
+
assemblyName,
|
|
1347
|
+
}));
|
|
1348
|
+
},
|
|
1308
1349
|
/**
|
|
1309
1350
|
* #action
|
|
1310
1351
|
*/
|
|
@@ -1337,6 +1378,34 @@ export function stateModelFactory(pluginManager) {
|
|
|
1337
1378
|
async launchTrack(trackId, initialSnapshot = {}, displayInitialSnapshot = {}, inlineConf) {
|
|
1338
1379
|
return launchTrackGeneric(self, trackId, initialSnapshot, displayInitialSnapshot, inlineConf);
|
|
1339
1380
|
},
|
|
1381
|
+
/**
|
|
1382
|
+
* #action
|
|
1383
|
+
* Replace the highlights `highlights` reads, each written as the launch
|
|
1384
|
+
* spec's `highlight` writes one: a locstring, or an object whose
|
|
1385
|
+
* `assemblyName` defaults to the view's. Throws on one that names no
|
|
1386
|
+
* region, writing none.
|
|
1387
|
+
*/
|
|
1388
|
+
setHighlights(highlights) {
|
|
1389
|
+
const session = getSession(self);
|
|
1390
|
+
const assemblyName = self.assemblyNames[0] ?? self.launchAssemblyName ?? '';
|
|
1391
|
+
session.setHighlights(highlights.map(h => {
|
|
1392
|
+
const highlight = coerceHighlight(h, assemblyName, (ref, asm) => session.assemblyManager.isValidRefName(ref, asm ?? assemblyName));
|
|
1393
|
+
if (!highlight) {
|
|
1394
|
+
throw new Error(`not a region to highlight: ${JSON.stringify(h)}`);
|
|
1395
|
+
}
|
|
1396
|
+
return highlight;
|
|
1397
|
+
}));
|
|
1398
|
+
},
|
|
1399
|
+
/**
|
|
1400
|
+
* #action
|
|
1401
|
+
* Show files from the user's disk as session tracks: one per data file,
|
|
1402
|
+
* beside the index among them that names it, its type guessed from its
|
|
1403
|
+
* name. Adds nothing when one file has no known type. Resolves the new
|
|
1404
|
+
* tracks' ids.
|
|
1405
|
+
*/
|
|
1406
|
+
async showFiles(files) {
|
|
1407
|
+
return showFiles(self, files);
|
|
1408
|
+
},
|
|
1340
1409
|
}))
|
|
1341
1410
|
.views(self => ({
|
|
1342
1411
|
/**
|
|
@@ -2167,6 +2236,9 @@ export function stateModelFactory(pluginManager) {
|
|
|
2167
2236
|
self.coarseTotalBp = blocks.totalBp;
|
|
2168
2237
|
self.coarseBpPerPx = bpPerPx;
|
|
2169
2238
|
}
|
|
2239
|
+
// on every call, since a scroll through the padding before the first
|
|
2240
|
+
// region moves the viewport and leaves the blocks the same
|
|
2241
|
+
self.coarseOffsetPx = self.offsetPx;
|
|
2170
2242
|
},
|
|
2171
2243
|
}))
|
|
2172
2244
|
.actions(self => ({
|
|
@@ -15,6 +15,18 @@ export interface RExportTrack {
|
|
|
15
15
|
heightMode?: unknown;
|
|
16
16
|
height?: unknown;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Each display ggjbrowse reads, by the reader that reads it, and the adapters
|
|
20
|
+
* it opens (the slot holding the file and the extension the reader knows it
|
|
21
|
+
* by).
|
|
22
|
+
*/
|
|
23
|
+
export declare const READERS: Record<string, {
|
|
24
|
+
call: string;
|
|
25
|
+
adapters: Record<string, {
|
|
26
|
+
slot?: string;
|
|
27
|
+
ext?: RegExp;
|
|
28
|
+
}>;
|
|
29
|
+
}>;
|
|
18
30
|
/** The ggjbrowse release whose readers and `jbrowse_stack` the script calls. */
|
|
19
31
|
export declare const GGJBROWSE_VERSION = "0.10.0";
|
|
20
32
|
/**
|
|
@@ -35,6 +47,15 @@ export declare function rValue(value: unknown): string;
|
|
|
35
47
|
* `file://` URL as its path, or a `localPath`.
|
|
36
48
|
*/
|
|
37
49
|
export declare function locationPath(location: unknown): string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Each sequence adapter `jbrowse_assembly()` builds from a path, by the
|
|
52
|
+
* extension ggjbrowse reads it off, and the index each takes beside it.
|
|
53
|
+
*/
|
|
54
|
+
export declare const SEQUENCE_ADAPTERS: Record<string, {
|
|
55
|
+
slot: string;
|
|
56
|
+
ext: RegExp;
|
|
57
|
+
beside: Record<string, string>;
|
|
58
|
+
}>;
|
|
38
59
|
/**
|
|
39
60
|
* The view's assembly as the R value every reader takes: `jbrowse_assembly()`
|
|
40
61
|
* of its sequence and alias files where ggjbrowse builds the same config from
|
|
@@ -5,7 +5,7 @@ import { resolveUri } from '@jbrowse/core/util';
|
|
|
5
5
|
* it opens (the slot holding the file and the extension the reader knows it
|
|
6
6
|
* by).
|
|
7
7
|
*/
|
|
8
|
-
const READERS = {
|
|
8
|
+
export const READERS = {
|
|
9
9
|
LinearBasicDisplay: {
|
|
10
10
|
call: 'jbrowse_features',
|
|
11
11
|
adapters: {
|
|
@@ -216,7 +216,7 @@ function fileArgument(adapters, adapter) {
|
|
|
216
216
|
* Each sequence adapter `jbrowse_assembly()` builds from a path, by the
|
|
217
217
|
* extension ggjbrowse reads it off, and the index each takes beside it.
|
|
218
218
|
*/
|
|
219
|
-
const SEQUENCE_ADAPTERS = {
|
|
219
|
+
export const SEQUENCE_ADAPTERS = {
|
|
220
220
|
TwoBitAdapter: { slot: 'twoBitLocation', ext: /\.2bit$/, beside: {} },
|
|
221
221
|
BgzipFastaAdapter: {
|
|
222
222
|
slot: 'fastaLocation',
|
|
@@ -330,7 +330,7 @@ function trackNames(tracks) {
|
|
|
330
330
|
}
|
|
331
331
|
/** What the figure cannot show of a track whose settings ggjbrowse reads. */
|
|
332
332
|
function settingNotes(track) {
|
|
333
|
-
const color = track.display.
|
|
333
|
+
const { color } = track.display.encoding ?? {};
|
|
334
334
|
return [
|
|
335
335
|
...(color?.field === 'mateRefName'
|
|
336
336
|
? [
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findParent } from '@jbrowse/mobx-state-tree';
|
|
2
2
|
export function isSharedScaleContainer(thing) {
|
|
3
3
|
return typeof thing === 'object' && thing !== null && 'sharedFit' in thing;
|
|
4
4
|
}
|
|
5
|
-
// Walks rather than using findParentThatIs because having no such container is
|
|
6
|
-
// the ordinary standalone case here, not the error that helper reports.
|
|
7
5
|
export function sharedScaleContainerOf(node) {
|
|
8
|
-
|
|
9
|
-
let found;
|
|
10
|
-
while (!found && hasParent(current) && isAlive(current)) {
|
|
11
|
-
current = getParent(current);
|
|
12
|
-
if (isSharedScaleContainer(current)) {
|
|
13
|
-
found = current;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return found;
|
|
6
|
+
return findParent(node, isSharedScaleContainer);
|
|
17
7
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { fileToLocation, getEnv, getSession, isSessionWithAddSessionTrack, } from '@jbrowse/core/util';
|
|
2
|
+
import { pairIndexFiles } from '@jbrowse/core/util/indexCandidates';
|
|
3
|
+
import { guessTrackConfForLocation } from '@jbrowse/core/util/tracks';
|
|
4
|
+
// Every type is guessed before any track is added, so a selection holding one
|
|
5
|
+
// file of no known type adds nothing rather than half of itself.
|
|
6
|
+
export async function showFiles(view, files) {
|
|
7
|
+
const session = getSession(view);
|
|
8
|
+
if (!isSessionWithAddSessionTrack(session)) {
|
|
9
|
+
throw new Error('this session does not take new tracks');
|
|
10
|
+
}
|
|
11
|
+
const { pluginManager } = getEnv(view);
|
|
12
|
+
const assemblyName = view.assemblyNames[0] ?? view.launchAssemblyName;
|
|
13
|
+
const confs = pairIndexFiles(files).map(({ data, index }) => guessTrackConfForLocation(fileToLocation(data), index && fileToLocation(index), pluginManager, assemblyName));
|
|
14
|
+
for (const conf of confs) {
|
|
15
|
+
session.addSessionTrackConf(conf);
|
|
16
|
+
}
|
|
17
|
+
await Promise.all(confs.map(conf => view.launchTrack(conf.trackId)));
|
|
18
|
+
return confs.map(conf => conf.trackId);
|
|
19
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { FieldPredicate } from '@jbrowse/core/util/fieldPredicate';
|
|
2
|
+
import type { TrackInit } from '@jbrowse/core/util/tracks';
|
|
3
|
+
import type { DisplaySettings } from '@jbrowse/launch-args/displaySettings';
|
|
4
|
+
import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
5
|
+
/** The linear view a command line's tracks open in, as these helpers read it. */
|
|
6
|
+
export interface ModifierView {
|
|
7
|
+
centerLineInfo?: {
|
|
8
|
+
refName?: string;
|
|
9
|
+
offset: number;
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
reversed?: boolean;
|
|
13
|
+
oob?: boolean;
|
|
14
|
+
};
|
|
15
|
+
launchTrack: (trackId: string, trackSnapshot?: object, displaySnapshot?: object) => Promise<{
|
|
16
|
+
displays: unknown[];
|
|
17
|
+
} | undefined>;
|
|
18
|
+
showTrack: (trackId: string, trackSnapshot?: object, displaySnapshot?: object) => unknown;
|
|
19
|
+
tracks: {
|
|
20
|
+
configuration: {
|
|
21
|
+
trackId: string;
|
|
22
|
+
};
|
|
23
|
+
displays: unknown[];
|
|
24
|
+
activeDisplay: {
|
|
25
|
+
configuration: Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `displayTypeOf` for a track the session holds, undefined for one it does
|
|
31
|
+
* not; `displayType` is the display the caller opens it as when no
|
|
32
|
+
* `display:` modifier names one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function openedDisplayType(session: {
|
|
35
|
+
getTrackById: (trackId: string) => unknown;
|
|
36
|
+
}, trackId: string, modifiers: string[], displayType?: string): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Write the `filter` of `trackId`'s `settings` onto its display open in
|
|
39
|
+
* `view`, by field over the configured filter, as the track menu would.
|
|
40
|
+
* Returns the display's filter as written, which reopens the track as drawn,
|
|
41
|
+
* or undefined when the settings write no filter.
|
|
42
|
+
*/
|
|
43
|
+
export declare function writeFilter(view: Pick<ModifierView, 'tracks' | 'showTrack'>, trackId: string, settings: DisplaySettings): FieldPredicate[] | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated `writeFilter`, which every display category takes. This one
|
|
46
|
+
* writes an alignments `display`'s filter only.
|
|
47
|
+
*/
|
|
48
|
+
export declare function writeReadFilter(display: unknown, displayType: string | undefined, settings: DisplaySettings): FieldPredicate[] | undefined;
|
|
49
|
+
interface LaunchOptions {
|
|
50
|
+
/** the track entry's own display settings, which the modifiers write over */
|
|
51
|
+
displaySnapshot?: Record<string, unknown>;
|
|
52
|
+
trackSnapshot?: Record<string, unknown>;
|
|
53
|
+
/** changes the settings after the center-line sort resolves, before the open */
|
|
54
|
+
adjust?: (settings: DisplaySettings) => void;
|
|
55
|
+
warn?: (message: string) => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Open `trackId` in `view` with its command-line modifiers applied, and return
|
|
59
|
+
* the session-spec track entry that reopens it as drawn, or undefined when the
|
|
60
|
+
* open fails, which `launchTrack` reports to the session.
|
|
61
|
+
*/
|
|
62
|
+
export declare function launchTrackWithModifiers(view: ModifierView, trackId: string, displayType: string | undefined, modifiers: string[], { displaySnapshot, trackSnapshot, adjust, warn, }?: LaunchOptions): Promise<{
|
|
63
|
+
trackId: string;
|
|
64
|
+
filter?: FieldPredicate[] | undefined;
|
|
65
|
+
} | undefined>;
|
|
66
|
+
/**
|
|
67
|
+
* Open a session spec's track entry in a navigated `view`, its `modifiers`
|
|
68
|
+
* written over its display settings. When a modifier or the open fails, the
|
|
69
|
+
* session's snackbar says why and the track opens with its own settings.
|
|
70
|
+
*/
|
|
71
|
+
export declare function launchTrackInit(view: ModifierView & IAnyStateTreeNode, t: TrackInit): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Write a track's `memberWrites` onto what its display in `view` already has,
|
|
74
|
+
* after every other modifier, so a member write keeps the rest of the setting
|
|
75
|
+
* whatever order the command line gives them in. A display replaces a color
|
|
76
|
+
* or facet object whole, which is why these cannot ride in on the launch
|
|
77
|
+
* snapshot.
|
|
78
|
+
*/
|
|
79
|
+
export declare function writeMembers(view: Pick<ModifierView, 'tracks' | 'showTrack'>, trackId: string, members: string[]): Record<string, unknown>;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { mergeConfSettings } from '@jbrowse/core/configuration';
|
|
2
|
+
import { getEnv, getSession } from '@jbrowse/core/util';
|
|
3
|
+
import { basePaintedAt } from '@jbrowse/core/util/Base1DUtils';
|
|
4
|
+
import { predicatesOf, testsSomething, withFields, } from '@jbrowse/core/util/fieldPredicate';
|
|
5
|
+
import { normalizeTrackInit, trackDisplayType } from '@jbrowse/core/util/tracks';
|
|
6
|
+
import { buildDisplaySnapshot, displayCategory, memberPatch, memberWrites, mergeSettings, requestedDisplayType, } from '@jbrowse/launch-args/displaySettings';
|
|
7
|
+
import { getSnapshot, getType, isArrayType, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
|
|
8
|
+
/**
|
|
9
|
+
* The display a linear view opens `conf` as, read off the display picker so it
|
|
10
|
+
* follows `display:` and the adapter rather than a list of track types.
|
|
11
|
+
*/
|
|
12
|
+
function displayTypeOf(pluginManager, conf, modifiers, displayType) {
|
|
13
|
+
return trackDisplayType(pluginManager, conf, 'LinearGenomeView', requestedDisplayType(modifiers) ?? displayType);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `displayTypeOf` for a track the session holds, undefined for one it does
|
|
17
|
+
* not; `displayType` is the display the caller opens it as when no
|
|
18
|
+
* `display:` modifier names one.
|
|
19
|
+
*/
|
|
20
|
+
export function openedDisplayType(session, trackId, modifiers, displayType) {
|
|
21
|
+
const conf = session.getTrackById(trackId);
|
|
22
|
+
return conf
|
|
23
|
+
? displayTypeOf(getEnv(session).pluginManager, (isStateTreeNode(conf) ? getSnapshot(conf) : conf), modifiers, displayType)
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
const consoleWarn = (message) => {
|
|
27
|
+
console.warn(`Warning: ${message}`);
|
|
28
|
+
};
|
|
29
|
+
function modifierLaunch(displayType, modifiers, warn = consoleWarn) {
|
|
30
|
+
return {
|
|
31
|
+
displayType,
|
|
32
|
+
settings: buildDisplaySnapshot(displayType, modifiers, warn),
|
|
33
|
+
members: memberWrites(displayType, modifiers),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// the entries grouped by field, as `setFilterFields` takes them: a field whose
|
|
37
|
+
// entries are all bare is `[]`, which clears it, defaults included
|
|
38
|
+
function filterPatch(entries) {
|
|
39
|
+
const patch = {};
|
|
40
|
+
for (const p of entries) {
|
|
41
|
+
patch[p.field] = [
|
|
42
|
+
...(patch[p.field] ?? []),
|
|
43
|
+
...(testsSomething(p) ? [p] : []),
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
return patch;
|
|
47
|
+
}
|
|
48
|
+
// the display snapshot a track opens with. `filter` stays out of it:
|
|
49
|
+
// `writeFilter` writes it once the track is open, since the list in a
|
|
50
|
+
// snapshot would replace the configured one
|
|
51
|
+
function launchSnapshot({ settings }) {
|
|
52
|
+
const { snap, displayType } = settings;
|
|
53
|
+
const { filter: _filter, ...unfiltered } = snap;
|
|
54
|
+
return {
|
|
55
|
+
...unfiltered,
|
|
56
|
+
...(displayType ? { type: displayType } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Write the `filter` of `trackId`'s `settings` onto its display open in
|
|
61
|
+
* `view`, by field over the configured filter, as the track menu would.
|
|
62
|
+
* Returns the display's filter as written, which reopens the track as drawn,
|
|
63
|
+
* or undefined when the settings write no filter.
|
|
64
|
+
*/
|
|
65
|
+
export function writeFilter(view, trackId, settings) {
|
|
66
|
+
const { filter } = settings.snap;
|
|
67
|
+
const track = view.tracks.find(t => t.configuration.trackId === trackId);
|
|
68
|
+
if (!filter || !track) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const patch = filterPatch(filter);
|
|
72
|
+
const display = track.displays[0];
|
|
73
|
+
if (display?.setFilterFields) {
|
|
74
|
+
display.setFilterFields(patch);
|
|
75
|
+
return display.writtenFilter;
|
|
76
|
+
}
|
|
77
|
+
const written = withFields(predicatesOf(track.activeDisplay.configuration.filter), patch);
|
|
78
|
+
view.showTrack(trackId, {}, { filter: written });
|
|
79
|
+
return written;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated `writeFilter`, which every display category takes. This one
|
|
83
|
+
* writes an alignments `display`'s filter only.
|
|
84
|
+
*/
|
|
85
|
+
export function writeReadFilter(display, displayType, settings) {
|
|
86
|
+
const { filter } = settings.snap;
|
|
87
|
+
if (displayCategory(displayType) !== 'alignments' || !filter) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const filterDisplay = display;
|
|
91
|
+
filterDisplay?.setFilterFields?.(filterPatch(filter));
|
|
92
|
+
return filterDisplay?.writtenFilter;
|
|
93
|
+
}
|
|
94
|
+
// the pivot is the base under the view's center, which a view has only once
|
|
95
|
+
// it shows a region
|
|
96
|
+
function centerSort(view, trackId, field, warn) {
|
|
97
|
+
const center = view.centerLineInfo;
|
|
98
|
+
if (center && !center.oob) {
|
|
99
|
+
return {
|
|
100
|
+
field,
|
|
101
|
+
pos: basePaintedAt(center, center.offset),
|
|
102
|
+
refName: center.refName ?? '',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
warn(`sort:${field} on "${trackId}" ignored — the view has no center position to sort at (pass --loc)`);
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// The modifiers' settings written over the entry's own as a settings bag
|
|
111
|
+
// writes them (`mergeConfSettings`): a namespace such as `pileup` member by
|
|
112
|
+
// member, a bare value lifted first, and a channel such as `encoding.color`
|
|
113
|
+
// whole, so a `color:` modifier is not mixed into the entry's color by field.
|
|
114
|
+
// Plain JSON where no registered display type says which is which.
|
|
115
|
+
function overEntry(view, displayType, entry, over) {
|
|
116
|
+
const pluginManager = isStateTreeNode(view)
|
|
117
|
+
? getEnv(view).pluginManager
|
|
118
|
+
: undefined;
|
|
119
|
+
return displayType && pluginManager?.displayTypes.has(displayType)
|
|
120
|
+
? mergeConfSettings(pluginManager.getDisplayType(displayType).configSchema, entry, over)
|
|
121
|
+
: mergeSettings(structuredClone(entry), over);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Open `trackId` in `view` with its command-line modifiers applied, and return
|
|
125
|
+
* the session-spec track entry that reopens it as drawn, or undefined when the
|
|
126
|
+
* open fails, which `launchTrack` reports to the session.
|
|
127
|
+
*/
|
|
128
|
+
export async function launchTrackWithModifiers(view, trackId, displayType, modifiers, { displaySnapshot = {}, trackSnapshot = {}, adjust, warn = consoleWarn, } = {}) {
|
|
129
|
+
const launch = modifierLaunch(displayType, modifiers, warn);
|
|
130
|
+
const { snap, sort } = launch.settings;
|
|
131
|
+
const at = sort && centerSort(view, trackId, sort.field, warn);
|
|
132
|
+
if (at) {
|
|
133
|
+
snap.sort = at;
|
|
134
|
+
}
|
|
135
|
+
adjust?.(launch.settings);
|
|
136
|
+
const launched = overEntry(view, displayType, displaySnapshot, launchSnapshot(launch));
|
|
137
|
+
const opened = await view.launchTrack(trackId, trackSnapshot, launched);
|
|
138
|
+
if (!opened) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const drawnFilter = writeFilter(view, trackId, launch.settings);
|
|
142
|
+
const members = writeMembers(view, trackId, launch.members);
|
|
143
|
+
return {
|
|
144
|
+
trackId,
|
|
145
|
+
...launched,
|
|
146
|
+
...(drawnFilter ? { filter: drawnFilter } : {}),
|
|
147
|
+
...members,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Open a session spec's track entry in a navigated `view`, its `modifiers`
|
|
152
|
+
* written over its display settings. When a modifier or the open fails, the
|
|
153
|
+
* session's snackbar says why and the track opens with its own settings.
|
|
154
|
+
*/
|
|
155
|
+
export async function launchTrackInit(view, t) {
|
|
156
|
+
const { trackId, trackSnapshot, displaySnapshot, modifiers } = normalizeTrackInit(t);
|
|
157
|
+
if (modifiers.length === 0) {
|
|
158
|
+
await view.launchTrack(trackId, trackSnapshot, displaySnapshot);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const session = getSession(view);
|
|
162
|
+
const { type } = displaySnapshot;
|
|
163
|
+
const notes = [];
|
|
164
|
+
try {
|
|
165
|
+
await launchTrackWithModifiers(view, trackId, openedDisplayType(session, trackId, modifiers, typeof type === 'string' ? type : undefined), modifiers, {
|
|
166
|
+
trackSnapshot,
|
|
167
|
+
displaySnapshot,
|
|
168
|
+
warn: message => notes.push(message),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
console.error(e);
|
|
173
|
+
session.notifyError(`"${trackId}" ${modifiers.join(' ')}: ${e}`, e);
|
|
174
|
+
}
|
|
175
|
+
// launchTrack reports its own failure, such as a display:
|
|
176
|
+
// the track cannot open as
|
|
177
|
+
if (!view.tracks.some(t => t.configuration.trackId === trackId)) {
|
|
178
|
+
await view.launchTrack(trackId, trackSnapshot, displaySnapshot);
|
|
179
|
+
}
|
|
180
|
+
if (notes.length) {
|
|
181
|
+
session.notify(notes.join('; '), 'warning');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Write a track's `memberWrites` onto what its display in `view` already has,
|
|
186
|
+
* after every other modifier, so a member write keeps the rest of the setting
|
|
187
|
+
* whatever order the command line gives them in. A display replaces a color
|
|
188
|
+
* or facet object whole, which is why these cannot ride in on the launch
|
|
189
|
+
* snapshot.
|
|
190
|
+
*/
|
|
191
|
+
export function writeMembers(view, trackId, members) {
|
|
192
|
+
if (members.length === 0) {
|
|
193
|
+
return {};
|
|
194
|
+
}
|
|
195
|
+
const track = view.tracks.find(t => t.configuration.trackId === trackId);
|
|
196
|
+
if (!track) {
|
|
197
|
+
throw new Error(`"${trackId}" is not open, so ${members.join(' ')} has nothing to write to`);
|
|
198
|
+
}
|
|
199
|
+
const { configuration } = track.activeDisplay;
|
|
200
|
+
const configured = getSnapshot(configuration);
|
|
201
|
+
const patch = memberPatch(members, root => {
|
|
202
|
+
// a list at a default the snapshot strips, Manhattan's marks, still seeds
|
|
203
|
+
// the write, so `marks.0.mark=bar` edits the mark drawn
|
|
204
|
+
const member = configuration[root];
|
|
205
|
+
return (configured[root] ??
|
|
206
|
+
(isStateTreeNode(member) && isArrayType(getType(member))
|
|
207
|
+
? getSnapshot(member)
|
|
208
|
+
: undefined));
|
|
209
|
+
});
|
|
210
|
+
view.showTrack(trackId, {}, patch);
|
|
211
|
+
return patch;
|
|
212
|
+
}
|
package/esm/index.d.ts
CHANGED
|
@@ -29,12 +29,14 @@ export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/compo
|
|
|
29
29
|
export type { SearchBoxPrefs } from './LinearGenomeView/components/useSearchBoxPrefs.ts';
|
|
30
30
|
export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
|
|
31
31
|
export { applyInitHighlights } from './LinearGenomeView/afterAttach.ts';
|
|
32
|
+
export { launchTrackInit, launchTrackWithModifiers, openedDisplayType, writeFilter, writeMembers, writeReadFilter, } from './TrackModifiers/index.ts';
|
|
33
|
+
export type { ModifierView } from './TrackModifiers/index.ts';
|
|
32
34
|
export { MultiLevelRubberband, multiLevelRowClickMenuItems, multiLevelRowMenuItems, rowLabels, } from './MultiLevelRubberband/index.ts';
|
|
33
35
|
export { fetchResults, SearchResultsNotFoundError } from './searchUtils.ts';
|
|
34
36
|
export type { LaunchLinearGenomeViewArgs } from './LaunchLinearGenomeView/index.ts';
|
|
35
37
|
export type { BpOffset, ExportSvgOptions, HighlightInput, HighlightType, InitState, LinearGenomeViewLaunchProps, NavLocation, TrackLabelMode, ViewTrackLabelMode, VolatileGuide, } from './LinearGenomeView/types.ts';
|
|
36
38
|
export type { ScalebarRefNameLabel } from './LinearGenomeView/util.ts';
|
|
37
|
-
export type { HighlightRegion, SearchPickerRow, SearchPickerState, } from './LinearGenomeView/model.ts';
|
|
39
|
+
export type { HighlightRegion, SearchHits, SearchPickerRow, SearchPickerState, } from './LinearGenomeView/model.ts';
|
|
38
40
|
export type { RegionsOrientation } from './LinearGenomeView/util.ts';
|
|
39
41
|
export type { RegionEdit } from './LinearGenomeView/util.ts';
|
|
40
42
|
export { makeTicks } from './LinearGenomeView/util.ts';
|
package/esm/index.js
CHANGED
|
@@ -71,6 +71,7 @@ export { default as HeaderSearchBoxRow } from './LinearGenomeView/components/Hea
|
|
|
71
71
|
export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/components/useSearchBoxPrefs.js';
|
|
72
72
|
export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
|
|
73
73
|
export { applyInitHighlights } from './LinearGenomeView/afterAttach.js';
|
|
74
|
+
export { launchTrackInit, launchTrackWithModifiers, openedDisplayType, writeFilter, writeMembers, writeReadFilter, } from './TrackModifiers/index.js';
|
|
74
75
|
export { MultiLevelRubberband, multiLevelRowClickMenuItems, multiLevelRowMenuItems, rowLabels, } from './MultiLevelRubberband/index.js';
|
|
75
76
|
export { fetchResults, SearchResultsNotFoundError } from './searchUtils.js';
|
|
76
77
|
// The scalebar's grid, published because it is a contract other views draw
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-linear-genome-view",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 linear genome view",
|
|
6
6
|
"keywords": [
|
|
@@ -21,15 +21,16 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@jbrowse/mobx-state-tree": "^6.
|
|
24
|
+
"@jbrowse/mobx-state-tree": "^6.10.0",
|
|
25
25
|
"@mui/icons-material": "^9.5.0",
|
|
26
26
|
"@mui/material": "^9.5.0",
|
|
27
|
-
"mobx": "^7.
|
|
27
|
+
"mobx": "^7.1.1",
|
|
28
28
|
"mobx-react": "^10.0.2",
|
|
29
|
-
"@jbrowse/
|
|
30
|
-
"@jbrowse/display-
|
|
31
|
-
"@jbrowse/
|
|
32
|
-
"@jbrowse/
|
|
29
|
+
"@jbrowse/display-ui": "^5.0.0-beta.18",
|
|
30
|
+
"@jbrowse/display-kit": "^5.0.0-beta.18",
|
|
31
|
+
"@jbrowse/launch-args": "^5.0.0-beta.18",
|
|
32
|
+
"@jbrowse/render-core": "^5.0.0-beta.18",
|
|
33
|
+
"@jbrowse/core": "^5.0.0-beta.18"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"react": ">=19.0.0",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
},
|
|
41
42
|
"sideEffects": false,
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"@jbrowse/display-test-utils": "^5.0.0-beta.
|
|
44
|
+
"@jbrowse/display-test-utils": "^5.0.0-beta.18"
|
|
44
45
|
},
|
|
45
46
|
"scripts": {
|
|
46
47
|
"prebuild": "pnpm clean",
|