@jbrowse/plugin-linear-genome-view 5.0.0-beta.2 → 5.0.0-beta.20
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/LinearGenomeView/afterAttach.d.ts +22 -5
- package/esm/LinearGenomeView/afterAttach.js +129 -106
- package/esm/LinearGenomeView/axisConfigSchema.d.ts +18 -0
- package/esm/LinearGenomeView/axisConfigSchema.js +22 -0
- package/esm/LinearGenomeView/components/CenterLine.js +10 -5
- package/esm/LinearGenomeView/components/ConnectedHoverHighlight.js +2 -3
- package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +9 -4
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +45 -8
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
- package/esm/LinearGenomeView/components/Header.js +11 -1
- package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
- package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
- package/esm/LinearGenomeView/components/HeaderPanControls.js +6 -3
- package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +6 -35
- package/esm/LinearGenomeView/components/Highlight.js +27 -11
- package/esm/LinearGenomeView/components/HighlightBand.d.ts +11 -2
- package/esm/LinearGenomeView/components/HighlightBand.js +32 -13
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -3
- package/esm/LinearGenomeView/components/HighlightChip.js +13 -8
- package/esm/LinearGenomeView/components/HighlightLabelDialog.d.ts +6 -0
- package/esm/LinearGenomeView/components/HighlightLabelDialog.js +18 -0
- package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
- package/esm/LinearGenomeView/components/ImportForm.js +7 -9
- package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +13 -14
- package/esm/LinearGenomeView/components/MiniControls.js +11 -5
- package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
- package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +6 -5
- package/esm/LinearGenomeView/components/OverviewRubberband.js +25 -48
- package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
- package/esm/LinearGenomeView/components/PaddingBlocks.js +3 -8
- package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +74 -0
- package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
- package/esm/LinearGenomeView/components/Rubberband.js +3 -2
- package/esm/LinearGenomeView/components/SVGHighlightBand.d.ts +10 -1
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +16 -5
- package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
- package/esm/LinearGenomeView/components/Scalebar.js +8 -5
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +51 -36
- package/esm/LinearGenomeView/components/SearchBox.js +10 -16
- package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
- package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
- package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -8
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
- package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +4 -7
- package/esm/LinearGenomeView/components/SearchResultsTable.js +25 -53
- package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
- package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
- package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
- package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
- package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
- package/esm/LinearGenomeView/components/TrackContainer.js +63 -10
- package/esm/LinearGenomeView/components/TrackLabel.js +14 -4
- package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
- package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +4 -5
- package/esm/LinearGenomeView/components/TrackLabelMenu.js +11 -14
- package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
- package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +6 -4
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +12 -24
- package/esm/LinearGenomeView/components/TracksContainer.js +11 -23
- package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
- package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
- package/esm/LinearGenomeView/components/fetchSequence.js +15 -7
- package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
- package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
- package/esm/LinearGenomeView/components/useRangeSelect.js +26 -61
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
- package/esm/LinearGenomeView/components/useSideScroll.d.ts +17 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +156 -102
- package/esm/LinearGenomeView/components/util.d.ts +4 -0
- package/esm/LinearGenomeView/components/util.js +4 -2
- package/esm/LinearGenomeView/consts.d.ts +2 -0
- package/esm/LinearGenomeView/consts.js +3 -1
- package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
- package/esm/LinearGenomeView/containingLgv.js +23 -0
- package/esm/LinearGenomeView/headerFit.d.ts +4 -2
- package/esm/LinearGenomeView/headerFit.js +20 -7
- package/esm/LinearGenomeView/index.d.ts +2 -7
- package/esm/LinearGenomeView/index.js +5 -7
- package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -7
- package/esm/LinearGenomeView/keyboardHandler.js +61 -23
- package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
- package/esm/LinearGenomeView/launchKeys.js +11 -6
- package/esm/LinearGenomeView/lazyDialogs.d.ts +16 -3
- package/esm/LinearGenomeView/lazyDialogs.js +1 -0
- package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
- package/esm/LinearGenomeView/linkedViewSync.js +27 -49
- package/esm/LinearGenomeView/menuItems.d.ts +16 -18
- package/esm/LinearGenomeView/menuItems.js +264 -164
- package/esm/LinearGenomeView/model.d.ts +1192 -560
- package/esm/LinearGenomeView/model.js +805 -590
- package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +17 -0
- package/esm/LinearGenomeView/rExport/exportLinearView.js +117 -0
- package/esm/LinearGenomeView/rExport/rScript.d.ts +110 -0
- package/esm/LinearGenomeView/rExport/rScript.js +524 -0
- package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
- package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
- package/esm/LinearGenomeView/showFiles.d.ts +2 -0
- package/esm/LinearGenomeView/showFiles.js +19 -0
- package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +13 -5
- package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
- package/esm/LinearGenomeView/stickyChrome.js +5 -9
- package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.d.ts +3 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +9 -9
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.d.ts +3 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +5 -5
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +13 -18
- package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
- package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
- package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
- package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +26 -0
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +21 -13
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -5
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +7 -6
- package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +2 -3
- package/esm/LinearGenomeView/svgcomponents/SVGView.js +17 -3
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -20
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +74 -77
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +28 -12
- package/esm/LinearGenomeView/svgcomponents/util.js +41 -29
- package/esm/LinearGenomeView/types.d.ts +13 -10
- package/esm/LinearGenomeView/types.js +0 -1
- package/esm/LinearGenomeView/util.d.ts +103 -46
- package/esm/LinearGenomeView/util.js +222 -170
- package/esm/MultiLevelRubberband/MultiLevelRubberband.js +9 -6
- package/esm/MultiLevelRubberband/index.d.ts +2 -0
- package/esm/MultiLevelRubberband/index.js +2 -0
- package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
- package/esm/MultiLevelRubberband/menuItems.js +52 -0
- package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
- package/esm/MultiLevelRubberband/rowLabel.js +27 -0
- package/esm/MultiLevelRubberband/types.d.ts +1 -0
- package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
- package/esm/MultiLevelRubberband/useRangeSelect.js +45 -59
- package/esm/TrackModifiers/index.d.ts +83 -0
- package/esm/TrackModifiers/index.js +231 -0
- package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -68
- package/esm/displayKitTests/perRegionTestEnv.js +30 -26
- package/esm/index.d.ts +21 -99
- package/esm/index.js +17 -38
- package/esm/searchUtils.d.ts +26 -10
- package/esm/searchUtils.js +137 -137
- package/esm/shared/RubberbandSpan.d.ts +3 -2
- package/esm/shared/RubberbandSpan.js +6 -3
- package/esm/shared/coordLabels.d.ts +11 -4
- package/esm/shared/coordLabels.js +20 -5
- package/esm/shared/useWindowDrag.d.ts +18 -0
- package/esm/shared/useWindowDrag.js +47 -0
- package/package.json +12 -11
- package/esm/FeatureTrack/configSchema.d.ts +0 -124
- package/esm/FeatureTrack/configSchema.js +0 -54
- package/esm/FeatureTrack/index.d.ts +0 -2
- package/esm/FeatureTrack/index.js +0 -13
- package/esm/LinearGenomeView/initKeys.d.ts +0 -12
- package/esm/LinearGenomeView/initKeys.js +0 -68
- package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
- package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
- package/esm/lazyPluginExports.d.ts +0 -23
- package/esm/lazyPluginExports.js +0 -36
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { expandAssemblySequence, expandSidecarShorthand, refNameAliasAdapterType, } from '@jbrowse/core/assemblyManager/expandAssemblyConfigShorthand';
|
|
2
|
+
import { resolveUri } from '@jbrowse/core/util';
|
|
3
|
+
const VCF_ADAPTERS = {
|
|
4
|
+
VcfTabixAdapter: { slot: 'vcfGzLocation', ext: /\.vcf\.gz$/, index: '.tbi' },
|
|
5
|
+
VcfAdapter: { slot: 'vcfLocation', ext: /\.vcf$/ },
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Each display ggjbrowse reads, by the reader that reads it, and the adapters
|
|
9
|
+
* it opens: the slot holding the file, the extension the reader knows it by,
|
|
10
|
+
* and the suffix of the index the reader looks for beside it.
|
|
11
|
+
*/
|
|
12
|
+
export const READERS = {
|
|
13
|
+
LinearBasicDisplay: {
|
|
14
|
+
call: 'jbrowse_features',
|
|
15
|
+
adapters: {
|
|
16
|
+
Gff3TabixAdapter: {
|
|
17
|
+
slot: 'gffGzLocation',
|
|
18
|
+
ext: /\.gff3?\.gz$/,
|
|
19
|
+
index: '.tbi',
|
|
20
|
+
},
|
|
21
|
+
Gff3Adapter: { slot: 'gffLocation', ext: /\.gff3?$/ },
|
|
22
|
+
GtfTabixAdapter: {
|
|
23
|
+
slot: 'gtfGzLocation',
|
|
24
|
+
ext: /\.gtf\.gz$/,
|
|
25
|
+
index: '.tbi',
|
|
26
|
+
},
|
|
27
|
+
GtfAdapter: { slot: 'gtfLocation', ext: /\.gtf$/ },
|
|
28
|
+
BedTabixAdapter: {
|
|
29
|
+
slot: 'bedGzLocation',
|
|
30
|
+
ext: /\.bed\.gz$/,
|
|
31
|
+
index: '.tbi',
|
|
32
|
+
},
|
|
33
|
+
BedAdapter: { slot: 'bedLocation', ext: /\.bed$/ },
|
|
34
|
+
BigBedAdapter: { slot: 'bigBedLocation', ext: /\.(bb|bigbed)$/ },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
LinearVariantDisplay: {
|
|
38
|
+
call: 'jbrowse_variants',
|
|
39
|
+
recordsOnlyCall: 'jbrowse_features',
|
|
40
|
+
adapters: VCF_ADAPTERS,
|
|
41
|
+
},
|
|
42
|
+
LinearWiggleDisplay: {
|
|
43
|
+
call: 'jbrowse_wiggle',
|
|
44
|
+
adapters: {
|
|
45
|
+
BigWigAdapter: { slot: 'bigWigLocation', ext: /$/ },
|
|
46
|
+
MultiWiggleAdapter: {},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
LinearAlignmentsDisplay: {
|
|
50
|
+
call: 'jbrowse_alignments',
|
|
51
|
+
adapters: {
|
|
52
|
+
BamAdapter: { slot: 'bamLocation', ext: /(?<!\.cram)$/, index: '.bai' },
|
|
53
|
+
CramAdapter: { slot: 'cramLocation', ext: /\.cram$/, index: '.crai' },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The ggjbrowse release the script is tested against: its floor, and the tag
|
|
59
|
+
* its install line pins. The R export workflow installs this tag on a push.
|
|
60
|
+
*/
|
|
61
|
+
export const GGJBROWSE_VERSION = '0.11.0';
|
|
62
|
+
const INSTALL = `remotes::install_github("GMOD/ggjbrowse@v${GGJBROWSE_VERSION}")`;
|
|
63
|
+
function isSet(value) {
|
|
64
|
+
return !(value === undefined ||
|
|
65
|
+
value === null ||
|
|
66
|
+
value === '' ||
|
|
67
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
68
|
+
(typeof value === 'object' &&
|
|
69
|
+
!Array.isArray(value) &&
|
|
70
|
+
Object.keys(value).length === 0));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A resolved config writes an unset slot as `''`, `[]` or `{}`, which a reader
|
|
74
|
+
* would take for a setting. Keeps `false` and `0`, which are values.
|
|
75
|
+
*/
|
|
76
|
+
export function withoutUnset(value) {
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
return value.map(withoutUnset);
|
|
79
|
+
}
|
|
80
|
+
if (value && typeof value === 'object') {
|
|
81
|
+
return Object.fromEntries(Object.entries(value)
|
|
82
|
+
.map(([k, v]) => [k, withoutUnset(v)])
|
|
83
|
+
.filter(([, v]) => isSet(v)));
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
export function rStr(s) {
|
|
88
|
+
return JSON.stringify(s);
|
|
89
|
+
}
|
|
90
|
+
const RESERVED = new Set([
|
|
91
|
+
'if',
|
|
92
|
+
'else',
|
|
93
|
+
'repeat',
|
|
94
|
+
'while',
|
|
95
|
+
'function',
|
|
96
|
+
'for',
|
|
97
|
+
'next',
|
|
98
|
+
'break',
|
|
99
|
+
'in',
|
|
100
|
+
'TRUE',
|
|
101
|
+
'FALSE',
|
|
102
|
+
'NULL',
|
|
103
|
+
'Inf',
|
|
104
|
+
'NaN',
|
|
105
|
+
'NA',
|
|
106
|
+
'NA_integer_',
|
|
107
|
+
'NA_real_',
|
|
108
|
+
'NA_complex_',
|
|
109
|
+
'NA_character_',
|
|
110
|
+
]);
|
|
111
|
+
function isIdentifier(s) {
|
|
112
|
+
return (/^([A-Za-z]|\.(?!\d))[A-Za-z0-9._]*$/.test(s) &&
|
|
113
|
+
!/^(\.\.\.|\.\.\d+)$/.test(s) &&
|
|
114
|
+
!RESERVED.has(s));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A config value as the R literal ggjbrowse hands the engine: a `list` per
|
|
118
|
+
* object, `c()` per vector of scalars, `list()` per vector of objects and per
|
|
119
|
+
* empty vector, which jsonlite would otherwise write as `{}`.
|
|
120
|
+
*/
|
|
121
|
+
export function rValue(value) {
|
|
122
|
+
if (value === null || value === undefined) {
|
|
123
|
+
return 'NULL';
|
|
124
|
+
}
|
|
125
|
+
if (typeof value === 'string') {
|
|
126
|
+
return rStr(value);
|
|
127
|
+
}
|
|
128
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
129
|
+
return typeof value === 'number' ? String(value) : value ? 'TRUE' : 'FALSE';
|
|
130
|
+
}
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
const items = value.map(rValue).join(', ');
|
|
133
|
+
return value.length && value.every(v => typeof v !== 'object')
|
|
134
|
+
? `c(${items})`
|
|
135
|
+
: `list(${items})`;
|
|
136
|
+
}
|
|
137
|
+
return `list(${Object.entries(value)
|
|
138
|
+
.map(([k, v]) => `${isIdentifier(k) ? k : rStr(k)} = ${rValue(v)}`)
|
|
139
|
+
.join(', ')})`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The path or URL a JBrowse file location points at, as R opens it: a `uri`
|
|
143
|
+
* resolved against its `baseUri` as the browser's fetch resolved it, a
|
|
144
|
+
* `file://` URL as its path, or a `localPath`.
|
|
145
|
+
*/
|
|
146
|
+
export function locationPath(location) {
|
|
147
|
+
const loc = location;
|
|
148
|
+
if (!loc?.uri) {
|
|
149
|
+
return loc?.localPath;
|
|
150
|
+
}
|
|
151
|
+
const resolved = resolveUri({ uri: loc.uri, baseUri: loc.baseUri });
|
|
152
|
+
return resolved.startsWith('file://')
|
|
153
|
+
? decodeURIComponent(new URL(resolved).pathname)
|
|
154
|
+
: resolved;
|
|
155
|
+
}
|
|
156
|
+
function isLocation(v) {
|
|
157
|
+
return (typeof v === 'object' &&
|
|
158
|
+
v !== null &&
|
|
159
|
+
!Array.isArray(v) &&
|
|
160
|
+
(typeof v.uri === 'string' ||
|
|
161
|
+
typeof v.localPath === 'string'));
|
|
162
|
+
}
|
|
163
|
+
/** An adapter config with each location a path or URL R opens, each path also pushed onto `uris`. */
|
|
164
|
+
function openable(value, uris) {
|
|
165
|
+
if (isLocation(value)) {
|
|
166
|
+
const uri = locationPath(value);
|
|
167
|
+
if (uri) {
|
|
168
|
+
uris.push(uri);
|
|
169
|
+
}
|
|
170
|
+
return { uri };
|
|
171
|
+
}
|
|
172
|
+
if (Array.isArray(value)) {
|
|
173
|
+
return value.map(v => openable(v, uris));
|
|
174
|
+
}
|
|
175
|
+
if (value && typeof value === 'object') {
|
|
176
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, openable(v, uris)]));
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The file argument of a track's reader: its path where ggjbrowse would choose
|
|
182
|
+
* the track's adapter for it from the extension, and the adapter config itself
|
|
183
|
+
* where the adapter carries a setting or the name hides its format, so every
|
|
184
|
+
* setting reaches the engine as the browser has it.
|
|
185
|
+
*/
|
|
186
|
+
function fileArgument(adapters, adapter) {
|
|
187
|
+
const type = String(adapter.type);
|
|
188
|
+
const known = adapters[type];
|
|
189
|
+
if (!known) {
|
|
190
|
+
return { skipped: `${type} has no R reader for this display` };
|
|
191
|
+
}
|
|
192
|
+
const uris = [];
|
|
193
|
+
const located = withoutUnset(openable(adapter, uris));
|
|
194
|
+
if (uris.length === 0) {
|
|
195
|
+
return { skipped: `${type} names no file` };
|
|
196
|
+
}
|
|
197
|
+
if (uris.some(u => u.startsWith('blob:'))) {
|
|
198
|
+
return {
|
|
199
|
+
skipped: 'it reads a file opened in the browser, which R cannot reach',
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const file = known.slot ? locationPath(adapter[known.slot]) : undefined;
|
|
203
|
+
const index = adapter.index;
|
|
204
|
+
const indexPath = locationPath(index?.location) ?? locationPath(adapter.craiLocation);
|
|
205
|
+
const plainSlots = new Set(['type', 'index', 'craiLocation', known.slot]);
|
|
206
|
+
const plain = file !== undefined &&
|
|
207
|
+
known.ext?.test(file.replace(/\?.*$/, '').toLowerCase()) &&
|
|
208
|
+
Object.keys(located).every(k => plainSlots.has(k)) &&
|
|
209
|
+
Object.keys(index ?? {}).every(k => k === 'location' || k === 'indexType') &&
|
|
210
|
+
(index?.indexType === 'CSI') === !!indexPath?.endsWith('.csi');
|
|
211
|
+
return plain
|
|
212
|
+
? {
|
|
213
|
+
file: rStr(file),
|
|
214
|
+
index: indexPath && indexPath !== `${file}${known.index}`
|
|
215
|
+
? indexPath
|
|
216
|
+
: undefined,
|
|
217
|
+
uris,
|
|
218
|
+
}
|
|
219
|
+
: { file: rValue(located), uris };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Each sequence adapter `jbrowse_assembly()` builds from a path, by the
|
|
223
|
+
* extension ggjbrowse reads it off, and the index each takes beside it.
|
|
224
|
+
*/
|
|
225
|
+
export const SEQUENCE_ADAPTERS = {
|
|
226
|
+
TwoBitAdapter: { slot: 'twoBitLocation', ext: /\.2bit$/, beside: {} },
|
|
227
|
+
BgzipFastaAdapter: {
|
|
228
|
+
slot: 'fastaLocation',
|
|
229
|
+
ext: /\.gz$/,
|
|
230
|
+
beside: { faiLocation: '.fai', gziLocation: '.gzi' },
|
|
231
|
+
},
|
|
232
|
+
IndexedFastaAdapter: {
|
|
233
|
+
slot: 'fastaLocation',
|
|
234
|
+
ext: /^(?!.*\.(2bit|gz)$)/,
|
|
235
|
+
beside: { faiLocation: '.fai' },
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
/** The path of a sequence adapter `jbrowse_assembly()` rebuilds from that path alone. */
|
|
239
|
+
function plainSequence(adapter) {
|
|
240
|
+
const { type, uri, baseUri, ...slots } = adapter;
|
|
241
|
+
if (type === undefined && typeof uri === 'string') {
|
|
242
|
+
return Object.keys(slots).length
|
|
243
|
+
? undefined
|
|
244
|
+
: locationPath({ uri, baseUri });
|
|
245
|
+
}
|
|
246
|
+
const known = SEQUENCE_ADAPTERS[String(type)];
|
|
247
|
+
const file = known && uri === undefined && locationPath(slots[known.slot]);
|
|
248
|
+
return file &&
|
|
249
|
+
known.ext.test(file.replace(/\?.*$/, '').toLowerCase()) &&
|
|
250
|
+
Object.entries(slots).every(([k, v]) => k === known.slot ||
|
|
251
|
+
(known.beside[k] !== undefined &&
|
|
252
|
+
locationPath(v) === `${file}${known.beside[k]}`))
|
|
253
|
+
? file
|
|
254
|
+
: undefined;
|
|
255
|
+
}
|
|
256
|
+
/** The path of an alias adapter ggjbrowse reads as that path alone. */
|
|
257
|
+
function plainAliases(adapter) {
|
|
258
|
+
const { type, uri, baseUri, location, ...rest } = adapter;
|
|
259
|
+
const file = typeof uri === 'string'
|
|
260
|
+
? locationPath({ uri, baseUri })
|
|
261
|
+
: locationPath(location);
|
|
262
|
+
return file &&
|
|
263
|
+
Object.keys(rest).length === 0 &&
|
|
264
|
+
type === refNameAliasAdapterType(file)
|
|
265
|
+
? file
|
|
266
|
+
: undefined;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The view's assembly as the R value every reader takes: `jbrowse_assembly()`
|
|
270
|
+
* of its sequence and alias files where ggjbrowse builds the same config from
|
|
271
|
+
* their paths, and otherwise the config itself, its locations paths R opens.
|
|
272
|
+
*/
|
|
273
|
+
export function assemblyArgument(snapshot) {
|
|
274
|
+
const config = expandAssemblySequence(snapshot);
|
|
275
|
+
const name = String(config.name);
|
|
276
|
+
const sequence = withoutUnset(config.sequence?.adapter);
|
|
277
|
+
if (!sequence) {
|
|
278
|
+
return { skipped: 'the assembly names no sequence' };
|
|
279
|
+
}
|
|
280
|
+
const aliasConfig = config.refNameAliases;
|
|
281
|
+
const aliases = aliasConfig
|
|
282
|
+
? withoutUnset(expandSidecarShorthand(aliasConfig, refNameAliasAdapterType).adapter)
|
|
283
|
+
: undefined;
|
|
284
|
+
const uris = [];
|
|
285
|
+
const located = {
|
|
286
|
+
name,
|
|
287
|
+
sequence: { adapter: openable(sequence, uris) },
|
|
288
|
+
...(aliases
|
|
289
|
+
? { refNameAliases: { adapter: openable(aliases, uris) } }
|
|
290
|
+
: {}),
|
|
291
|
+
};
|
|
292
|
+
if (uris.some(u => u.startsWith('blob:'))) {
|
|
293
|
+
return {
|
|
294
|
+
skipped: 'the assembly reads a file opened in the browser, which R cannot reach',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const sequenceFile = plainSequence(sequence);
|
|
298
|
+
const aliasFile = aliases && plainAliases(aliases);
|
|
299
|
+
return sequenceFile && (!aliases || aliasFile)
|
|
300
|
+
? {
|
|
301
|
+
value: `jbrowse_assembly(${[
|
|
302
|
+
rStr(sequenceFile),
|
|
303
|
+
...(aliasFile ? [rStr(aliasFile)] : []),
|
|
304
|
+
`name = ${rStr(name)}`,
|
|
305
|
+
].join(', ')})`,
|
|
306
|
+
}
|
|
307
|
+
: { value: rValue(located) };
|
|
308
|
+
}
|
|
309
|
+
/** A region as JBrowse's location box writes it, 1-based and inclusive. */
|
|
310
|
+
export function locusOf(refName, start, end) {
|
|
311
|
+
const n = (x) => Math.round(x).toLocaleString('en-US');
|
|
312
|
+
return `${refName}:${n(start + 1)}-${n(end)}`;
|
|
313
|
+
}
|
|
314
|
+
function lociLiteral(loci) {
|
|
315
|
+
return loci.length === 1
|
|
316
|
+
? rStr(loci[0])
|
|
317
|
+
: `c(${loci.map(l => rStr(l)).join(', ')})`;
|
|
318
|
+
}
|
|
319
|
+
/** An R-safe name per track id, unique within the script. */
|
|
320
|
+
function trackNames(tracks) {
|
|
321
|
+
const taken = new Set(['locus', 'width', 'assembly', 'fig']);
|
|
322
|
+
return tracks.map(track => {
|
|
323
|
+
let base = track.trackId
|
|
324
|
+
.replaceAll(/[^a-zA-Z0-9]/g, '_')
|
|
325
|
+
.replace(/^(?=[\d_])/, 'x');
|
|
326
|
+
if (!isIdentifier(base)) {
|
|
327
|
+
base = `${base}_track`;
|
|
328
|
+
}
|
|
329
|
+
let name = base;
|
|
330
|
+
for (let n = 2; taken.has(name); n++) {
|
|
331
|
+
name = `${base}_${n}`;
|
|
332
|
+
}
|
|
333
|
+
taken.add(name);
|
|
334
|
+
return name;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* A track's `jbrowse_stack()` argument, named as the view labels it so its
|
|
339
|
+
* band is titled. A name `width` would set the stack's own `width`, so that
|
|
340
|
+
* one goes in a list, whose names title its tracks too.
|
|
341
|
+
*/
|
|
342
|
+
function stackArgument(title, name) {
|
|
343
|
+
return title === 'width'
|
|
344
|
+
? `list(width = ${name})`
|
|
345
|
+
: `${isIdentifier(title) ? title : rStr(title)} = ${name}`;
|
|
346
|
+
}
|
|
347
|
+
/** What the figure cannot show of a track whose settings ggjbrowse reads. */
|
|
348
|
+
// The variant display's record settings, under `records`, as the feature
|
|
349
|
+
// track's top-level spelling `jbrowse_features` reads; the genotype rows'
|
|
350
|
+
// settings stay behind.
|
|
351
|
+
const RECORD_SETTINGS = [
|
|
352
|
+
'displayMode',
|
|
353
|
+
'mark',
|
|
354
|
+
'labels',
|
|
355
|
+
'facet',
|
|
356
|
+
'maxFeatureScreenDensity',
|
|
357
|
+
];
|
|
358
|
+
const SHARED_SETTINGS = [
|
|
359
|
+
'filter',
|
|
360
|
+
'encoding',
|
|
361
|
+
'legend',
|
|
362
|
+
'heightMode',
|
|
363
|
+
'growMaxHeight',
|
|
364
|
+
];
|
|
365
|
+
function isRecord(x) {
|
|
366
|
+
return typeof x === 'object' && x !== null && !Array.isArray(x);
|
|
367
|
+
}
|
|
368
|
+
function setEntries(source, keys) {
|
|
369
|
+
return keys.flatMap(k => {
|
|
370
|
+
const entry = [k, source[k]];
|
|
371
|
+
return entry[1] === undefined ? [] : [entry];
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function recordsAsFeatureDisplay(display) {
|
|
375
|
+
const records = isRecord(display.records) ? display.records : {};
|
|
376
|
+
const tooltip = {
|
|
377
|
+
...(isRecord(display.tooltip) ? display.tooltip : {}),
|
|
378
|
+
...(isRecord(records.tooltip) ? records.tooltip : {}),
|
|
379
|
+
};
|
|
380
|
+
return Object.fromEntries([
|
|
381
|
+
...setEntries(display, SHARED_SETTINGS),
|
|
382
|
+
...setEntries(records, RECORD_SETTINGS),
|
|
383
|
+
...setEntries({ tooltip }, Object.keys(tooltip).length > 0 ? ['tooltip'] : []),
|
|
384
|
+
]);
|
|
385
|
+
}
|
|
386
|
+
function settingNotes(track) {
|
|
387
|
+
const { color } = track.display.encoding ?? {};
|
|
388
|
+
return [
|
|
389
|
+
...(color?.field === 'mateRefName'
|
|
390
|
+
? [
|
|
391
|
+
'color: mateRefName colors each contig by its hash, where the browser orders them by the assembly',
|
|
392
|
+
]
|
|
393
|
+
: []),
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* One R script for a view's tracks over its regions: a ggjbrowse reader per
|
|
398
|
+
* track, reading each as its display does, then `jbrowse_stack()` drawing
|
|
399
|
+
* them as the view stacks them. A track ggjbrowse cannot read lands in the
|
|
400
|
+
* header instead of stopping the figure: a script that quietly differs from
|
|
401
|
+
* its view says so.
|
|
402
|
+
*/
|
|
403
|
+
export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, bpPerPx, colorByFrame = false, showAminoAcids = true, out = 'figure.png', notes: viewNotes = [], }) {
|
|
404
|
+
const notes = [...viewNotes];
|
|
405
|
+
const assembly = assemblySnapshot && assemblyArgument(assemblySnapshot);
|
|
406
|
+
if (assembly && 'skipped' in assembly) {
|
|
407
|
+
notes.push(`assembly: ${assembly.skipped}`);
|
|
408
|
+
}
|
|
409
|
+
const assemblyValue = assembly && 'value' in assembly ? assembly.value : undefined;
|
|
410
|
+
const loci = regions.map(r => locusOf(r.refName, r.start, r.end));
|
|
411
|
+
const totalBp = regions.reduce((sum, r) => sum + r.end - r.start, 0);
|
|
412
|
+
const viewPx = Math.round(totalBp / bpPerPx);
|
|
413
|
+
const names = trackNames(tracks);
|
|
414
|
+
const reads = [];
|
|
415
|
+
const drawn = [];
|
|
416
|
+
const bands = [];
|
|
417
|
+
let alignments;
|
|
418
|
+
tracks.forEach((track, i) => {
|
|
419
|
+
const known = READERS[track.displayType];
|
|
420
|
+
if (!known) {
|
|
421
|
+
notes.push(`${track.trackName}: ${track.displayType} has no R reader in ggjbrowse yet`);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const recordsCall = track.recordsOnly ? known.recordsOnlyCall : undefined;
|
|
425
|
+
const asRecords = recordsCall !== undefined;
|
|
426
|
+
const reader = {
|
|
427
|
+
adapters: known.adapters,
|
|
428
|
+
call: recordsCall ?? known.call,
|
|
429
|
+
};
|
|
430
|
+
const source = fileArgument(reader.adapters, track.adapter);
|
|
431
|
+
if ('skipped' in source) {
|
|
432
|
+
notes.push(`${track.trackName}: ${source.skipped}`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const { heightMode, height } = track;
|
|
436
|
+
const written = asRecords
|
|
437
|
+
? recordsAsFeatureDisplay(track.display)
|
|
438
|
+
: track.display;
|
|
439
|
+
const display = reader.call === 'jbrowse_features'
|
|
440
|
+
? {
|
|
441
|
+
...written,
|
|
442
|
+
...(heightMode === undefined ? {} : { heightMode }),
|
|
443
|
+
...(height === undefined ? {} : { height }),
|
|
444
|
+
}
|
|
445
|
+
: written;
|
|
446
|
+
const geneTrack = track.displayType === 'LinearBasicDisplay';
|
|
447
|
+
const args = [
|
|
448
|
+
...(Object.keys(display).length ? [`display = ${rValue(display)}`] : []),
|
|
449
|
+
'width = width',
|
|
450
|
+
...(assemblyValue ? ['assembly = assembly'] : []),
|
|
451
|
+
...(geneTrack && colorByFrame ? ['color_by_cds = TRUE'] : []),
|
|
452
|
+
...(geneTrack && !showAminoAcids ? ['amino_acids = FALSE'] : []),
|
|
453
|
+
...(source.index ? [`index = ${rStr(source.index)}`] : []),
|
|
454
|
+
];
|
|
455
|
+
const name = names[i];
|
|
456
|
+
reads.push([
|
|
457
|
+
`# ${track.trackName}`,
|
|
458
|
+
`${name} <- ${reader.call}(${source.file},`,
|
|
459
|
+
` ${['locus', ...args].join(',\n ')})`,
|
|
460
|
+
].join('\n'));
|
|
461
|
+
drawn.push(name);
|
|
462
|
+
bands.push(stackArgument(track.trackName, name));
|
|
463
|
+
if (reader.call === 'jbrowse_alignments') {
|
|
464
|
+
alignments ??= name;
|
|
465
|
+
}
|
|
466
|
+
notes.push(...[
|
|
467
|
+
...(source.uris.some(u => u.includes('://') && u.includes('?'))
|
|
468
|
+
? [
|
|
469
|
+
'source: a URL carries its query string, credential included, so the script is not one to share',
|
|
470
|
+
]
|
|
471
|
+
: []),
|
|
472
|
+
...settingNotes(track),
|
|
473
|
+
].map(n => `${track.trackName}: ${n}`));
|
|
474
|
+
});
|
|
475
|
+
if (drawn.length === 0) {
|
|
476
|
+
return { drawn: 0, script: undefined, notes };
|
|
477
|
+
}
|
|
478
|
+
const script = [
|
|
479
|
+
"# Generated by JBrowse 2: the view's tracks, read by ggjbrowse, JBrowse's",
|
|
480
|
+
'# own engine in R, and drawn as the view stacks them.',
|
|
481
|
+
`# Install ggjbrowse with ${INSTALL}`,
|
|
482
|
+
'#',
|
|
483
|
+
'# Each reader returns the tables its track draws; jbrowse_stack() draws',
|
|
484
|
+
'# them. Restyle every band with &, or draw one alone and add to it:',
|
|
485
|
+
'# fig & ggplot2::theme_classic()',
|
|
486
|
+
`# ${alignments ? `autoplot(${alignments}, "pileup")` : `autoplot(${drawn[0]})`} + ggplot2::theme_classic()`,
|
|
487
|
+
...(notes.length
|
|
488
|
+
? [
|
|
489
|
+
'#',
|
|
490
|
+
'# What this figure does not show:',
|
|
491
|
+
...notes.map(n => `# - ${n}`),
|
|
492
|
+
]
|
|
493
|
+
: []),
|
|
494
|
+
'',
|
|
495
|
+
'if (',
|
|
496
|
+
' !requireNamespace("ggjbrowse", quietly = TRUE) ||',
|
|
497
|
+
` packageVersion("ggjbrowse") < ${rStr(GGJBROWSE_VERSION)}`,
|
|
498
|
+
') {',
|
|
499
|
+
` stop("this script needs ggjbrowse ${GGJBROWSE_VERSION} or later: ${INSTALL.replaceAll('"', '\\"')}")`,
|
|
500
|
+
'}',
|
|
501
|
+
'library(ggjbrowse)',
|
|
502
|
+
'',
|
|
503
|
+
`locus <- ${lociLiteral(loci)}`,
|
|
504
|
+
...(assemblyValue
|
|
505
|
+
? [
|
|
506
|
+
"# The view's assembly: the reference every reader reads against, and",
|
|
507
|
+
'# the aliases that name each contig as each file spells it.',
|
|
508
|
+
`assembly <- ${assemblyValue}`,
|
|
509
|
+
]
|
|
510
|
+
: []),
|
|
511
|
+
"# The view's pixels across its regions. A smaller number lays the tracks",
|
|
512
|
+
'# out as a narrower browser window would, for a narrower figure with',
|
|
513
|
+
'# fewer labels at the same size.',
|
|
514
|
+
`width <- ${viewPx}`,
|
|
515
|
+
'',
|
|
516
|
+
reads.join('\n\n'),
|
|
517
|
+
'',
|
|
518
|
+
`fig <- jbrowse_stack(${bands.join(',\n ')})`,
|
|
519
|
+
'print(fig)',
|
|
520
|
+
`ggplot2::ggsave(${rStr(out)}, fig, width = jbrowse_width(fig), height = jbrowse_height(fig))`,
|
|
521
|
+
'',
|
|
522
|
+
].join('\n');
|
|
523
|
+
return { drawn: drawn.length, script, notes };
|
|
524
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
|
3
3
|
* A container's zoom-out ceiling for the rows it stacks. Unanswered is its own
|
|
4
4
|
* state rather than a zero: a row that has not been laid out yet cannot supply
|
|
5
5
|
* a fit, and reading that as "no shared ceiling" is a different claim. See
|
|
6
|
-
* `
|
|
6
|
+
* `LinearSyntenyView/sharedFit.ts`.
|
|
7
7
|
*/
|
|
8
8
|
export type SharedFit = {
|
|
9
9
|
answered: false;
|
|
@@ -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
|
+
}
|
|
@@ -6,7 +6,7 @@ import type { Region } from '@jbrowse/core/util/types';
|
|
|
6
6
|
*
|
|
7
7
|
* This is the "navigate the view you are looking at" half of a launcher, as
|
|
8
8
|
* against `session.addView`, which opens a second one. Two features do it —
|
|
9
|
-
* plugin-
|
|
9
|
+
* plugin-features's collapsed introns and plugin-alignments' "view mate region" —
|
|
10
10
|
* and both had their own copy of the same fifteen lines, including the same two
|
|
11
11
|
* decisions worth stating once:
|
|
12
12
|
*
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { getNotificationSink } from '@jbrowse/core/util';
|
|
2
|
+
import { transaction } from 'mobx';
|
|
2
3
|
/**
|
|
3
4
|
* Replace a view's displayed regions with a set someone asked for, frame them,
|
|
4
5
|
* and offer an Undo that puts the view back exactly as it was.
|
|
5
6
|
*
|
|
6
7
|
* This is the "navigate the view you are looking at" half of a launcher, as
|
|
7
8
|
* against `session.addView`, which opens a second one. Two features do it —
|
|
8
|
-
* plugin-
|
|
9
|
+
* plugin-features's collapsed introns and plugin-alignments' "view mate region" —
|
|
9
10
|
* and both had their own copy of the same fifteen lines, including the same two
|
|
10
11
|
* decisions worth stating once:
|
|
11
12
|
*
|
|
@@ -32,13 +33,20 @@ export function showRegionsWithUndo({ view, regions, message, alsoUndo, }) {
|
|
|
32
33
|
windowWidthBp: view.windowWidthBp,
|
|
33
34
|
windowStartBp: view.windowStartBp,
|
|
34
35
|
};
|
|
35
|
-
|
|
36
|
+
// two root actions in one transaction: `fitAllRegions` reads as the reader's
|
|
37
|
+
// own gesture to a synteny follow, which `showRegions` does not
|
|
38
|
+
transaction(() => {
|
|
39
|
+
view.setDisplayedRegions(regions);
|
|
40
|
+
view.fitAllRegions();
|
|
41
|
+
});
|
|
36
42
|
getNotificationSink(view).notify(message, 'info', {
|
|
37
43
|
name: 'Undo',
|
|
38
44
|
onClick: () => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
transaction(() => {
|
|
46
|
+
view.setDisplayedRegions(previous.displayedRegions);
|
|
47
|
+
view.setWindow(previous.windowWidthBp, previous.windowStartBp);
|
|
48
|
+
alsoUndo?.();
|
|
49
|
+
});
|
|
42
50
|
},
|
|
43
51
|
});
|
|
44
52
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where the sticky boxes below the view's chrome pin, as CSS rather than as a
|
|
3
|
-
* pixel sum.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* minimum-height, since pinning them to their constants clipped their content
|
|
9
|
-
* at a larger root font size, and one of them is briefly absent during a slow
|
|
10
|
-
* load. Anything positioning against them in the DOM reads what they publish
|
|
11
|
-
* and keeps the constant only as the fallback for the first frame.
|
|
3
|
+
* pixel sum. Both boxes above are minimum-height, since pinning them to their
|
|
4
|
+
* constants clipped their content at a larger root font size, and one of them
|
|
5
|
+
* is briefly absent during a slow load, so a sum of their constants is only
|
|
6
|
+
* nominal. Anything positioning against them reads what they publish and keeps
|
|
7
|
+
* the constant only as the fallback for the first frame.
|
|
12
8
|
*/
|
|
13
9
|
export declare function stickyChromeTops({ stickyViewHeaders, headerHeight, }: {
|
|
14
10
|
stickyViewHeaders: boolean;
|
|
@@ -3,15 +3,11 @@ import { LGV_HEADER_HEIGHT_VAR, VIEW_HEADER_HEIGHT_VAR, } from '@jbrowse/core/ut
|
|
|
3
3
|
import { SCALE_BAR_HEIGHT } from './consts.js';
|
|
4
4
|
/**
|
|
5
5
|
* Where the sticky boxes below the view's chrome pin, as CSS rather than as a
|
|
6
|
-
* pixel sum.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* minimum-height, since pinning them to their constants clipped their content
|
|
12
|
-
* at a larger root font size, and one of them is briefly absent during a slow
|
|
13
|
-
* load. Anything positioning against them in the DOM reads what they publish
|
|
14
|
-
* and keeps the constant only as the fallback for the first frame.
|
|
6
|
+
* pixel sum. Both boxes above are minimum-height, since pinning them to their
|
|
7
|
+
* constants clipped their content at a larger root font size, and one of them
|
|
8
|
+
* is briefly absent during a slow load, so a sum of their constants is only
|
|
9
|
+
* nominal. Anything positioning against them reads what they publish and keeps
|
|
10
|
+
* the constant only as the fallback for the first frame.
|
|
15
11
|
*/
|
|
16
12
|
export function stickyChromeTops({ stickyViewHeaders, headerHeight, }) {
|
|
17
13
|
// `scalebar` is undefined rather than 0 when the chrome does not pin: its
|