@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.
Files changed (177) hide show
  1. package/esm/LinearGenomeView/afterAttach.d.ts +22 -5
  2. package/esm/LinearGenomeView/afterAttach.js +129 -106
  3. package/esm/LinearGenomeView/axisConfigSchema.d.ts +18 -0
  4. package/esm/LinearGenomeView/axisConfigSchema.js +22 -0
  5. package/esm/LinearGenomeView/components/CenterLine.js +10 -5
  6. package/esm/LinearGenomeView/components/ConnectedHoverHighlight.js +2 -3
  7. package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
  8. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +9 -4
  9. package/esm/LinearGenomeView/components/ExportSvgDialog.js +45 -8
  10. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  11. package/esm/LinearGenomeView/components/Header.js +11 -1
  12. package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
  13. package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
  14. package/esm/LinearGenomeView/components/HeaderPanControls.js +6 -3
  15. package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
  16. package/esm/LinearGenomeView/components/HeaderZoomControls.js +6 -35
  17. package/esm/LinearGenomeView/components/Highlight.js +27 -11
  18. package/esm/LinearGenomeView/components/HighlightBand.d.ts +11 -2
  19. package/esm/LinearGenomeView/components/HighlightBand.js +32 -13
  20. package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -3
  21. package/esm/LinearGenomeView/components/HighlightChip.js +13 -8
  22. package/esm/LinearGenomeView/components/HighlightLabelDialog.d.ts +6 -0
  23. package/esm/LinearGenomeView/components/HighlightLabelDialog.js +18 -0
  24. package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
  25. package/esm/LinearGenomeView/components/ImportForm.js +7 -9
  26. package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
  27. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +13 -14
  28. package/esm/LinearGenomeView/components/MiniControls.js +11 -5
  29. package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
  30. package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
  31. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +6 -5
  32. package/esm/LinearGenomeView/components/OverviewRubberband.js +25 -48
  33. package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
  34. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
  35. package/esm/LinearGenomeView/components/PaddingBlocks.js +3 -8
  36. package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
  37. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
  38. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
  39. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
  40. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +74 -0
  41. package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
  42. package/esm/LinearGenomeView/components/Rubberband.js +3 -2
  43. package/esm/LinearGenomeView/components/SVGHighlightBand.d.ts +10 -1
  44. package/esm/LinearGenomeView/components/SVGHighlightBand.js +16 -5
  45. package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
  46. package/esm/LinearGenomeView/components/Scalebar.js +8 -5
  47. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +51 -36
  48. package/esm/LinearGenomeView/components/SearchBox.js +10 -16
  49. package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
  50. package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
  51. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -8
  52. package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
  53. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +4 -7
  54. package/esm/LinearGenomeView/components/SearchResultsTable.js +25 -53
  55. package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
  56. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
  57. package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
  58. package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
  59. package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
  60. package/esm/LinearGenomeView/components/TrackContainer.js +63 -10
  61. package/esm/LinearGenomeView/components/TrackLabel.js +14 -4
  62. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  63. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +4 -5
  64. package/esm/LinearGenomeView/components/TrackLabelMenu.js +11 -14
  65. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
  66. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
  67. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +6 -4
  68. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +12 -24
  69. package/esm/LinearGenomeView/components/TracksContainer.js +11 -23
  70. package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
  71. package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
  72. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  73. package/esm/LinearGenomeView/components/fetchSequence.js +15 -7
  74. package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
  75. package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
  76. package/esm/LinearGenomeView/components/useRangeSelect.js +26 -61
  77. package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
  78. package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
  79. package/esm/LinearGenomeView/components/useSideScroll.d.ts +17 -2
  80. package/esm/LinearGenomeView/components/useSideScroll.js +156 -102
  81. package/esm/LinearGenomeView/components/util.d.ts +4 -0
  82. package/esm/LinearGenomeView/components/util.js +4 -2
  83. package/esm/LinearGenomeView/consts.d.ts +2 -0
  84. package/esm/LinearGenomeView/consts.js +3 -1
  85. package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
  86. package/esm/LinearGenomeView/containingLgv.js +23 -0
  87. package/esm/LinearGenomeView/headerFit.d.ts +4 -2
  88. package/esm/LinearGenomeView/headerFit.js +20 -7
  89. package/esm/LinearGenomeView/index.d.ts +2 -7
  90. package/esm/LinearGenomeView/index.js +5 -7
  91. package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -7
  92. package/esm/LinearGenomeView/keyboardHandler.js +61 -23
  93. package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
  94. package/esm/LinearGenomeView/launchKeys.js +11 -6
  95. package/esm/LinearGenomeView/lazyDialogs.d.ts +16 -3
  96. package/esm/LinearGenomeView/lazyDialogs.js +1 -0
  97. package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
  98. package/esm/LinearGenomeView/linkedViewSync.js +27 -49
  99. package/esm/LinearGenomeView/menuItems.d.ts +16 -18
  100. package/esm/LinearGenomeView/menuItems.js +264 -164
  101. package/esm/LinearGenomeView/model.d.ts +1192 -560
  102. package/esm/LinearGenomeView/model.js +805 -590
  103. package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +17 -0
  104. package/esm/LinearGenomeView/rExport/exportLinearView.js +117 -0
  105. package/esm/LinearGenomeView/rExport/rScript.d.ts +110 -0
  106. package/esm/LinearGenomeView/rExport/rScript.js +524 -0
  107. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  108. package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
  109. package/esm/LinearGenomeView/showFiles.d.ts +2 -0
  110. package/esm/LinearGenomeView/showFiles.js +19 -0
  111. package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
  112. package/esm/LinearGenomeView/showRegionsWithUndo.js +13 -5
  113. package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
  114. package/esm/LinearGenomeView/stickyChrome.js +5 -9
  115. package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
  116. package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
  117. package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
  118. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.d.ts +3 -1
  119. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +9 -9
  120. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.d.ts +3 -1
  121. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +5 -5
  122. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +13 -18
  123. package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
  124. package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
  125. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
  126. package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
  127. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +26 -0
  128. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
  129. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +21 -13
  130. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -5
  131. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +7 -6
  132. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +2 -3
  133. package/esm/LinearGenomeView/svgcomponents/SVGView.js +17 -3
  134. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -20
  135. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
  136. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +74 -77
  137. package/esm/LinearGenomeView/svgcomponents/util.d.ts +28 -12
  138. package/esm/LinearGenomeView/svgcomponents/util.js +41 -29
  139. package/esm/LinearGenomeView/types.d.ts +13 -10
  140. package/esm/LinearGenomeView/types.js +0 -1
  141. package/esm/LinearGenomeView/util.d.ts +103 -46
  142. package/esm/LinearGenomeView/util.js +222 -170
  143. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +9 -6
  144. package/esm/MultiLevelRubberband/index.d.ts +2 -0
  145. package/esm/MultiLevelRubberband/index.js +2 -0
  146. package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
  147. package/esm/MultiLevelRubberband/menuItems.js +52 -0
  148. package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
  149. package/esm/MultiLevelRubberband/rowLabel.js +27 -0
  150. package/esm/MultiLevelRubberband/types.d.ts +1 -0
  151. package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
  152. package/esm/MultiLevelRubberband/useRangeSelect.js +45 -59
  153. package/esm/TrackModifiers/index.d.ts +83 -0
  154. package/esm/TrackModifiers/index.js +231 -0
  155. package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -68
  156. package/esm/displayKitTests/perRegionTestEnv.js +30 -26
  157. package/esm/index.d.ts +21 -99
  158. package/esm/index.js +17 -38
  159. package/esm/searchUtils.d.ts +26 -10
  160. package/esm/searchUtils.js +137 -137
  161. package/esm/shared/RubberbandSpan.d.ts +3 -2
  162. package/esm/shared/RubberbandSpan.js +6 -3
  163. package/esm/shared/coordLabels.d.ts +11 -4
  164. package/esm/shared/coordLabels.js +20 -5
  165. package/esm/shared/useWindowDrag.d.ts +18 -0
  166. package/esm/shared/useWindowDrag.js +47 -0
  167. package/package.json +12 -11
  168. package/esm/FeatureTrack/configSchema.d.ts +0 -124
  169. package/esm/FeatureTrack/configSchema.js +0 -54
  170. package/esm/FeatureTrack/index.d.ts +0 -2
  171. package/esm/FeatureTrack/index.js +0 -13
  172. package/esm/LinearGenomeView/initKeys.d.ts +0 -12
  173. package/esm/LinearGenomeView/initKeys.js +0 -68
  174. package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
  175. package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
  176. package/esm/lazyPluginExports.d.ts +0 -23
  177. 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
- * `LinearComparativeView/sharedFit.ts`.
6
+ * `LinearSyntenyView/sharedFit.ts`.
7
7
  */
8
8
  export type SharedFit = {
9
9
  answered: false;
@@ -1,17 +1,7 @@
1
- import { getParent, hasParent, isAlive } from '@jbrowse/mobx-state-tree';
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
- let current = node;
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,2 @@
1
+ import type { LinearGenomeViewModel } from './model.ts';
2
+ export declare function showFiles(view: LinearGenomeViewModel, files: readonly File[]): Promise<string[]>;
@@ -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-canvas's collapsed introns and plugin-alignments' "view mate region" —
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-canvas's collapsed introns and plugin-alignments' "view mate region" —
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
- view.showRegions(regions);
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
- view.setDisplayedRegions(previous.displayedRegions);
40
- view.setWindow(previous.windowWidthBp, previous.windowStartBp);
41
- alsoUndo?.();
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
- * The model's `rubberbandTop` / `pinnedTracksTop` answer the same question in
6
- * numbers, and still have to — `height`, `getTrackYOffset` and the SVG export
7
- * have no DOM to read. But the numbers are nominal. Both boxes above are now
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
- * The model's `rubberbandTop` / `pinnedTracksTop` answer the same question in
9
- * numbers, and still have to — `height`, `getTrackYOffset` and the SVG export
10
- * have no DOM to read. But the numbers are nominal. Both boxes above are now
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