@jbrowse/plugin-linear-genome-view 5.0.0-beta.17 → 5.0.0-beta.19

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 (46) hide show
  1. package/esm/LinearGenomeView/afterAttach.js +6 -4
  2. package/esm/LinearGenomeView/components/ImportForm.js +2 -4
  3. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +4 -2
  4. package/esm/LinearGenomeView/components/SearchBox.js +1 -9
  5. package/esm/LinearGenomeView/components/SearchResultsDialog.js +1 -1
  6. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
  7. package/esm/LinearGenomeView/components/SearchResultsTable.js +23 -5
  8. package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
  9. package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
  10. package/esm/LinearGenomeView/components/TrackContainer.js +3 -4
  11. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
  12. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
  13. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +2 -3
  14. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +1 -1
  15. package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +1 -1
  16. package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +1 -1
  17. package/esm/LinearGenomeView/index.d.ts +1 -1
  18. package/esm/LinearGenomeView/index.js +2 -0
  19. package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -9
  20. package/esm/LinearGenomeView/keyboardHandler.js +58 -34
  21. package/esm/LinearGenomeView/menuItems.js +3 -2
  22. package/esm/LinearGenomeView/model.d.ts +489 -52
  23. package/esm/LinearGenomeView/model.js +125 -45
  24. package/esm/LinearGenomeView/rExport/exportLinearView.js +4 -5
  25. package/esm/LinearGenomeView/rExport/rScript.d.ts +26 -1
  26. package/esm/LinearGenomeView/rExport/rScript.js +27 -6
  27. package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
  28. package/esm/LinearGenomeView/showFiles.d.ts +2 -0
  29. package/esm/LinearGenomeView/showFiles.js +19 -0
  30. package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
  31. package/esm/LinearGenomeView/showRegionsWithUndo.js +1 -1
  32. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +3 -1
  33. package/esm/LinearGenomeView/util.d.ts +1 -1
  34. package/esm/LinearGenomeView/util.js +1 -1
  35. package/esm/TrackModifiers/index.d.ts +80 -0
  36. package/esm/TrackModifiers/index.js +212 -0
  37. package/esm/displayKitTests/perRegionTestEnv.d.ts +10 -4
  38. package/esm/index.d.ts +3 -1
  39. package/esm/index.js +1 -2
  40. package/esm/searchUtils.d.ts +14 -2
  41. package/esm/searchUtils.js +56 -36
  42. package/package.json +10 -9
  43. package/esm/FeatureTrack/configSchema.d.ts +0 -217
  44. package/esm/FeatureTrack/configSchema.js +0 -55
  45. package/esm/FeatureTrack/index.d.ts +0 -2
  46. package/esm/FeatureTrack/index.js +0 -13
@@ -2,12 +2,14 @@ import { canonicalLocString } from '@jbrowse/core/TextSearch/places';
2
2
  import { getConf, readConfObject } from '@jbrowse/core/configuration';
3
3
  import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models';
4
4
  import { exportViewSvg } from '@jbrowse/core/svg/exportViewSvg';
5
- import { assembleLocString, clamp, getBpDisplayStr, getNotificationSink, getSession, isSessionModelWithWidgets, localStorageGetBoolean, localStorageGetItem, localStorageSetBoolean, localStorageSetItem, springAnimate, sum, } from '@jbrowse/core/util';
5
+ import { highlightListMenuItems } from '@jbrowse/core/ui/menuItems';
6
+ import { assembleLocString, clamp, getBpDisplayStr, getEnv, getNotificationSink, getSession, isSessionModelWithWidgets, localStorageGetBoolean, localStorageGetItem, localStorageSetBoolean, localStorageSetItem, springAnimate, sum, } from '@jbrowse/core/util';
6
7
  import { bpToLinearBp, bpToPx, computeMoveToLayout, createOverviewLayout, getLayoutHighlightCoords, getOverviewRegionPxSpan, moveTo, pxToBp, } from '@jbrowse/core/util/Base1DUtils';
7
8
  import { scaleBandHeights } from '@jbrowse/core/util/bandHeight';
8
9
  import { wholeBaseRegions } from '@jbrowse/core/util/blockTypes';
9
10
  import calculateDynamicBlocks from '@jbrowse/core/util/calculateDynamicBlocks';
10
11
  import calculateStaticBlocks from '@jbrowse/core/util/calculateStaticBlocks';
12
+ import { coerceHighlight } from '@jbrowse/core/util/highlights';
11
13
  import { hideTrackGeneric, launchToggleTrackGeneric, launchTrackGeneric, showTrackGeneric, toggleTrackGeneric, } from '@jbrowse/core/util/tracks';
12
14
  import { ElementId } from '@jbrowse/core/util/types/mst';
13
15
  import { highlightsOnAssemblies } from '@jbrowse/core/util/viewHighlights';
@@ -17,7 +19,7 @@ import { MIN_DISPLAY_HEIGHT } from '@jbrowse/display-kit/const';
17
19
  import { contentRightEdgePx } from '@jbrowse/display-kit/regionHost';
18
20
  import { cast, getParent, getSnapshot, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
19
21
  import { observable, when } from 'mobx';
20
- import { handleSelectedRegion, isOpenInView, navToOption, } from '../searchUtils.js';
22
+ import { fetchResults, handleSelectedRegion, isOpenInView, navToOption, } from '../searchUtils.js';
21
23
  import { doAfterAttach } from './afterAttach.js';
22
24
  import { linearGenomeViewAxisConfigSchema } from './axisConfigSchema.js';
23
25
  import { getCytobands, shouldSwapTracks } from './components/util.js';
@@ -27,7 +29,43 @@ import { setupKeyboardHandler } from './keyboardHandler.js';
27
29
  import { lgvLaunchKeys } from './launchKeys.js';
28
30
  import { buildHighlightsSubMenuItems, buildMenuItems, buildRubberBandMenuItems, buildRubberbandClickMenuItems, } from './menuItems.js';
29
31
  import { sharedScaleContainerOf } from './sharedScaleContainer.js';
32
+ import { showFiles } from './showFiles.js';
30
33
  import { calculateVisibleLocStrings, cytobandLabelGutterWidth, expandRegion, fitAllRegionsWindow, generateLocations, getGridlineTicks, getPaddingSpans, getScalebarLabels, getScalebarRefNameLabels, regionsOrientation, setDisplayedRegionsKeepingCenter, } from './util.js';
34
+ function searchHitsOf(model, results, query, assemblyName, land) {
35
+ const session = getSession(model);
36
+ const assembly = session.assemblyManager.get(assemblyName);
37
+ const ordered = [...results].sort((a, b) => Number(isOpenInView(b, model)) - Number(isOpenInView(a, model)));
38
+ return {
39
+ query,
40
+ assemblyName,
41
+ rows: ordered.map(result => {
42
+ const location = result.getLocation() ?? '';
43
+ const trackId = result.getTrackId();
44
+ return {
45
+ id: result.getId(),
46
+ label: result.getLabel(),
47
+ location: assembly && location
48
+ ? canonicalLocString(location, assembly)
49
+ : location,
50
+ trackName: trackId ? (session.getTrackById(trackId)?.name ?? '') : '',
51
+ };
52
+ }),
53
+ pick: async (id) => {
54
+ const picked = ordered.find(result => result.getId() === id);
55
+ try {
56
+ if (picked) {
57
+ await land(picked);
58
+ }
59
+ }
60
+ catch (e) {
61
+ console.error(e);
62
+ if (isAlive(model)) {
63
+ getNotificationSink(model).notifyError(`${e}`, e);
64
+ }
65
+ }
66
+ },
67
+ };
68
+ }
31
69
  /**
32
70
  * Calculate the offsetPx needed to center content within a viewport.
33
71
  * Returns a negative offset when content is smaller than viewport (padding on left).
@@ -260,11 +298,11 @@ export function stateModelFactory(pluginManager) {
260
298
  hideNoTracksActive: types.stripDefault(types.boolean, false),
261
299
  /**
262
300
  * #property
263
- * vestigial: the hierarchical selector is the only one that exists, so
264
- * this value is ignored. Retained because saved sessions and share
265
- * links persist it.
301
+ * ignored: the hierarchical selector is the only one. Declared because
302
+ * saved sessions and share links carry it.
303
+ * @deprecated
266
304
  */
267
- trackSelectorType: types.stripDefault(types.enumeration(['hierarchical']), 'hierarchical'),
305
+ trackSelectorType: types.stripDefault(types.string, 'hierarchical'),
268
306
  /**
269
307
  * #property
270
308
  * show the "center line". A personal preference: never in a session
@@ -388,6 +426,13 @@ export function stateModelFactory(pluginManager) {
388
426
  // displays that lay out against it; `setWidth` seeds the true one at
389
427
  // the first measure.
390
428
  coarseBpPerPx: 1,
429
+ /**
430
+ * #volatile
431
+ * the scroll offset the coarse blocks were laid out at, in px at
432
+ * `coarseBpPerPx`: the settled viewport's left edge, which the first
433
+ * coarse block is not while the view is scrolled before the first region
434
+ */
435
+ coarseOffsetPx: 0,
391
436
  /**
392
437
  * #volatile
393
438
  */
@@ -1139,6 +1184,7 @@ export function stateModelFactory(pluginManager) {
1139
1184
  // The debounced coarse autorun is 500ms out; a restored snapshot's
1140
1185
  // consumers would lay out and fetch at 1 bp/px until then.
1141
1186
  self.coarseBpPerPx = self.bpPerPx;
1187
+ self.coarseOffsetPx = self.offsetPx;
1142
1188
  }
1143
1189
  },
1144
1190
  /**
@@ -1264,40 +1310,11 @@ export function stateModelFactory(pluginManager) {
1264
1310
  model: self,
1265
1311
  assemblyName,
1266
1312
  })) {
1267
- const session = getSession(self);
1268
- const assembly = session.assemblyManager.get(assemblyName);
1269
- const ordered = [...searchResults].sort((a, b) => Number(isOpenInView(b, self)) -
1270
- Number(isOpenInView(a, self)));
1313
+ const hits = searchHitsOf(self, searchResults, searchQuery, assemblyName, onPick);
1271
1314
  const picker = {
1272
- query: searchQuery,
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
- }),
1315
+ ...hits,
1288
1316
  pick: async (id) => {
1289
- const picked = ordered.find(result => result.getId() === id);
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
- }
1317
+ await hits.pick(id);
1301
1318
  if (isAlive(self) && self.searchPicker === picker) {
1302
1319
  self.closeSearchPicker();
1303
1320
  }
@@ -1305,6 +1322,29 @@ export function stateModelFactory(pluginManager) {
1305
1322
  };
1306
1323
  self.searchPicker = picker;
1307
1324
  },
1325
+ /**
1326
+ * #action
1327
+ * Search the view's assembly by name, for a host listing the hits
1328
+ * itself. A row's `pick` lands as the search box does: it navigates
1329
+ * there and shows the track the hit's index came from.
1330
+ */
1331
+ async search(query, opts) {
1332
+ const assemblyName = self.assemblyNames[0] ?? self.launchAssemblyName;
1333
+ if (!assemblyName) {
1334
+ throw new Error('the view has no assembly to search yet');
1335
+ }
1336
+ const results = await fetchResults({
1337
+ session: getSession(self),
1338
+ queryString: query,
1339
+ assemblyName,
1340
+ signal: opts?.signal,
1341
+ });
1342
+ return searchHitsOf(self, results, query, assemblyName, result => navToOption({
1343
+ option: result,
1344
+ model: self,
1345
+ assemblyName,
1346
+ }));
1347
+ },
1308
1348
  /**
1309
1349
  * #action
1310
1350
  */
@@ -1337,6 +1377,34 @@ export function stateModelFactory(pluginManager) {
1337
1377
  async launchTrack(trackId, initialSnapshot = {}, displayInitialSnapshot = {}, inlineConf) {
1338
1378
  return launchTrackGeneric(self, trackId, initialSnapshot, displayInitialSnapshot, inlineConf);
1339
1379
  },
1380
+ /**
1381
+ * #action
1382
+ * Replace the highlights `highlights` reads, each written as the launch
1383
+ * spec's `highlight` writes one: a locstring, or an object whose
1384
+ * `assemblyName` defaults to the view's. Throws on one that names no
1385
+ * region, writing none.
1386
+ */
1387
+ setHighlights(highlights) {
1388
+ const session = getSession(self);
1389
+ const assemblyName = self.assemblyNames[0] ?? self.launchAssemblyName ?? '';
1390
+ session.setHighlights(highlights.map(h => {
1391
+ const highlight = coerceHighlight(h, assemblyName, (ref, asm) => session.assemblyManager.isValidRefName(ref, asm ?? assemblyName));
1392
+ if (!highlight) {
1393
+ throw new Error(`not a region to highlight: ${JSON.stringify(h)}`);
1394
+ }
1395
+ return highlight;
1396
+ }));
1397
+ },
1398
+ /**
1399
+ * #action
1400
+ * Show files from the user's disk as session tracks: one per data file,
1401
+ * beside the index among them that names it, its type guessed from its
1402
+ * name. Adds nothing when one file has no known type. Resolves the new
1403
+ * tracks' ids.
1404
+ */
1405
+ async showFiles(files) {
1406
+ return showFiles(self, files);
1407
+ },
1340
1408
  }))
1341
1409
  .views(self => ({
1342
1410
  /**
@@ -2167,6 +2235,9 @@ export function stateModelFactory(pluginManager) {
2167
2235
  self.coarseTotalBp = blocks.totalBp;
2168
2236
  self.coarseBpPerPx = bpPerPx;
2169
2237
  }
2238
+ // on every call, since a scroll through the padding before the first
2239
+ // region moves the viewport and leaves the blocks the same
2240
+ self.coarseOffsetPx = self.offsetPx;
2170
2241
  },
2171
2242
  }))
2172
2243
  .actions(self => ({
@@ -2177,7 +2248,7 @@ export function stateModelFactory(pluginManager) {
2177
2248
  *
2178
2249
  * The coarse blocks are a 500ms throttle, and every consumer trades
2179
2250
  * freshness for not recomputing per animation frame — the location box,
2180
- * canvas's on-screen feature set, and the two per-bp scans behind
2251
+ * the feature display's on-screen feature set, and the two per-bp scans behind
2181
2252
  * `settledDynamicBlocks`. That trade is only sound while the answer is a
2182
2253
  * few frames old. **A jump makes it unrelated instead**, because there is
2183
2254
  * nothing to coalesce: the viewport left behind is not an approximation of
@@ -2638,16 +2709,26 @@ export function stateModelFactory(pluginManager) {
2638
2709
  getSession(self).addHighlight(region);
2639
2710
  }
2640
2711
  },
2712
+ /**
2713
+ * #action
2714
+ * navigates to a highlight, zoomed out a little for context. Goes through
2715
+ * a locstring so it reaches a refName the view is not displaying
2716
+ */
2717
+ navToHighlight(highlight) {
2718
+ const { assemblyName, ...region } = highlight;
2719
+ return self.navToLocString(assembleLocString(region), assemblyName, 0.2);
2720
+ },
2721
+ }))
2722
+ .actions(self => ({
2641
2723
  /**
2642
2724
  * #action
2643
2725
  * navigates to the most recently added highlight on this view's
2644
- * assemblies, zoomed out a little for context
2726
+ * assemblies
2645
2727
  */
2646
2728
  async navigateNewestHighlight() {
2647
2729
  const newest = self.highlights.at(-1);
2648
2730
  if (newest) {
2649
- const { assemblyName, ...region } = newest;
2650
- await self.navToLocString(assembleLocString(region), assemblyName, 0.2);
2731
+ await self.navToHighlight(newest);
2651
2732
  }
2652
2733
  else {
2653
2734
  getSession(self).notify('There are no highlights to navigate to.', 'info');
@@ -2758,11 +2839,10 @@ export function stateModelFactory(pluginManager) {
2758
2839
  },
2759
2840
  /**
2760
2841
  * #method
2761
- * returns menu items for a highlight context menu. plugins can extend
2762
- * this via Core-extendPluggableElement to add items
2842
+ * a highlight chip's menu rows after Dismiss; extend this to add one
2763
2843
  */
2764
2844
  highlightMenuItems(_highlight) {
2765
- return [];
2845
+ return highlightListMenuItems(getSession(self), getEnv(self).pluginManager);
2766
2846
  },
2767
2847
  }))
2768
2848
  .actions(self => {
@@ -2,9 +2,6 @@ import { fullConfSnapshot, readConfObject } from '@jbrowse/core/configuration';
2
2
  import { getSession } from '@jbrowse/core/util';
3
3
  import { getSnapshot } from '@jbrowse/mobx-state-tree';
4
4
  import { exportTracksToR } from './rScript.js';
5
- function trackConf(track) {
6
- return track.configuration;
7
- }
8
5
  /** The regions on screen, each displayed region cut to what the view shows of it. */
9
6
  function viewRegions(view) {
10
7
  const blocks = view.dynamicBlocks.contentBlocks;
@@ -52,10 +49,10 @@ function tracksOf(view) {
52
49
  const { heightMode, height } = fullConfSnapshot(display.configuration);
53
50
  tracks.push({
54
51
  trackId: track.configuration.trackId,
55
- trackName: readConfObject(trackConf(track), 'name') ||
52
+ trackName: readConfObject(track.configuration, 'name') ||
56
53
  track.configuration.trackId,
57
54
  displayType: display.type,
58
- adapter: readConfObject(trackConf(track), 'adapter'),
55
+ adapter: readConfObject(track.configuration, 'adapter'),
59
56
  display: written,
60
57
  heightMode,
61
58
  height,
@@ -71,6 +68,8 @@ export function exportLinearViewToR(view, out) {
71
68
  regions: viewRegions(view),
72
69
  assembly: assemblyOf(view),
73
70
  bpPerPx: view.bpPerPx,
71
+ colorByFrame: view.colorByFrame,
72
+ showAminoAcids: view.showAminoAcids,
74
73
  out,
75
74
  notes: view.displayedRegions.some(r => r.reversed)
76
75
  ? ['view: shown right to left, where the figure runs left to right']
@@ -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
@@ -54,13 +75,17 @@ export declare function locusOf(refName: string, start: number, end: number): st
54
75
  * header instead of stopping the figure: a script that quietly differs from
55
76
  * its view says so.
56
77
  */
57
- export declare function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, bpPerPx, out, notes: viewNotes, }: {
78
+ export declare function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, bpPerPx, colorByFrame, showAminoAcids, out, notes: viewNotes, }: {
58
79
  tracks: readonly RExportTrack[];
59
80
  regions: readonly Region[];
60
81
  /** The view's assembly config as written, which names its sequence and its contigs' aliases. */
61
82
  assembly?: Record<string, unknown>;
62
83
  /** The view's zoom, which decides what a feature or quantitative display shows. */
63
84
  bpPerPx?: number;
85
+ /** The view's "CDS reading frame colors", which a feature track takes. */
86
+ colorByFrame?: boolean;
87
+ /** The view's "Amino acids when zoomed in", which a feature track takes. */
88
+ showAminoAcids?: boolean;
64
89
  out?: string;
65
90
  /** What the caller knows of the view that the figure does not show. */
66
91
  notes?: readonly string[];
@@ -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: {
@@ -93,9 +93,15 @@ const RESERVED = new Set([
93
93
  'Inf',
94
94
  'NaN',
95
95
  'NA',
96
+ 'NA_integer_',
97
+ 'NA_real_',
98
+ 'NA_complex_',
99
+ 'NA_character_',
96
100
  ]);
97
101
  function isIdentifier(s) {
98
- return /^[A-Za-z.][A-Za-z0-9._]*$/.test(s) && !RESERVED.has(s);
102
+ return (/^([A-Za-z]|\.(?!\d))[A-Za-z0-9._]*$/.test(s) &&
103
+ !/^(\.\.\.|\.\.\d+)$/.test(s) &&
104
+ !RESERVED.has(s));
99
105
  }
100
106
  /**
101
107
  * A config value as the R literal ggjbrowse hands the engine: a `list` per
@@ -216,7 +222,7 @@ function fileArgument(adapters, adapter) {
216
222
  * Each sequence adapter `jbrowse_assembly()` builds from a path, by the
217
223
  * extension ggjbrowse reads it off, and the index each takes beside it.
218
224
  */
219
- const SEQUENCE_ADAPTERS = {
225
+ export const SEQUENCE_ADAPTERS = {
220
226
  TwoBitAdapter: { slot: 'twoBitLocation', ext: /\.2bit$/, beside: {} },
221
227
  BgzipFastaAdapter: {
222
228
  slot: 'fastaLocation',
@@ -328,9 +334,19 @@ function trackNames(tracks) {
328
334
  return name;
329
335
  });
330
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
+ }
331
347
  /** What the figure cannot show of a track whose settings ggjbrowse reads. */
332
348
  function settingNotes(track) {
333
- const color = track.display.color;
349
+ const { color } = track.display.encoding ?? {};
334
350
  return [
335
351
  ...(color?.field === 'mateRefName'
336
352
  ? [
@@ -346,7 +362,7 @@ function settingNotes(track) {
346
362
  * header instead of stopping the figure: a script that quietly differs from
347
363
  * its view says so.
348
364
  */
349
- export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, bpPerPx, out = 'figure.png', notes: viewNotes = [], }) {
365
+ export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, bpPerPx, colorByFrame = false, showAminoAcids = true, out = 'figure.png', notes: viewNotes = [], }) {
350
366
  const notes = [...viewNotes];
351
367
  const assembly = assemblySnapshot && assemblyArgument(assemblySnapshot);
352
368
  if (assembly && 'skipped' in assembly) {
@@ -360,6 +376,7 @@ export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, b
360
376
  const names = trackNames(tracks);
361
377
  const reads = [];
362
378
  const drawn = [];
379
+ const bands = [];
363
380
  let alignments;
364
381
  tracks.forEach((track, i) => {
365
382
  const reader = READERS[track.displayType];
@@ -381,10 +398,13 @@ export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, b
381
398
  }
382
399
  : track.display;
383
400
  const width = viewPx !== undefined;
401
+ const geneTrack = track.displayType === 'LinearBasicDisplay';
384
402
  const args = [
385
403
  ...(Object.keys(display).length ? [`display = ${rValue(display)}`] : []),
386
404
  ...(width ? ['width = width'] : []),
387
405
  ...(assemblyValue ? ['assembly = assembly'] : []),
406
+ ...(geneTrack && colorByFrame ? ['color_by_cds = TRUE'] : []),
407
+ ...(geneTrack && !showAminoAcids ? ['amino_acids = FALSE'] : []),
388
408
  ...(source.index ? [`index = ${rStr(source.index)}`] : []),
389
409
  ];
390
410
  const name = names[i];
@@ -397,6 +417,7 @@ export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, b
397
417
  ` ${['locus', ...args].join(',\n ')})`,
398
418
  ].join('\n'));
399
419
  drawn.push(name);
420
+ bands.push(stackArgument(track.trackName, name));
400
421
  if (reader.call === 'jbrowse_alignments') {
401
422
  alignments ??= name;
402
423
  }
@@ -456,7 +477,7 @@ export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, b
456
477
  '',
457
478
  reads.join('\n\n'),
458
479
  '',
459
- `fig <- jbrowse_stack(${drawn.join(', ')})`,
480
+ `fig <- jbrowse_stack(${bands.join(',\n ')})`,
460
481
  'print(fig)',
461
482
  `ggplot2::ggsave(${rStr(out)}, fig, width = jbrowse_width(fig), height = jbrowse_height(fig))`,
462
483
  '',
@@ -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
  *
@@ -6,7 +6,7 @@ import { transaction } from 'mobx';
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
  *
@@ -39,7 +39,9 @@ export async function renderToSvg(model, opts) {
39
39
  textHeight,
40
40
  trackLabels,
41
41
  });
42
- notifySkippedSvgTracks(session, skippedTracks);
42
+ if (opts.announceSkipped !== false) {
43
+ notifySkippedSvgTracks(session, skippedTracks);
44
+ }
43
45
  // read after the displays' waits, and handed to SVGHeader rather than read
44
46
  // again, so the reserved `tracksTop` and the drawn header are one layout
45
47
  const { width, effectiveShowCytobands } = model;
@@ -25,7 +25,7 @@ interface CenterKeepingView {
25
25
  * A caller building a view SNAPSHOT does: it has no model to call
26
26
  * `fitAllRegions()` on, and naming `bpPerPx`/`offsetPx` instead is dropped
27
27
  * whenever the snapshot also carries a `windowWidthBp` — which one copied off an
28
- * existing view always does. plugin-canvas's collapsed-intron launch is that
28
+ * existing view always does. plugin-features's collapsed-intron launch is that
29
29
  * caller. A live view calls `fitAllRegions()`.
30
30
  *
31
31
  * `width` enters only through the zoom-in floor, which bites for a region set
@@ -18,7 +18,7 @@ import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
18
18
  * A caller building a view SNAPSHOT does: it has no model to call
19
19
  * `fitAllRegions()` on, and naming `bpPerPx`/`offsetPx` instead is dropped
20
20
  * whenever the snapshot also carries a `windowWidthBp` — which one copied off an
21
- * existing view always does. plugin-canvas's collapsed-intron launch is that
21
+ * existing view always does. plugin-features's collapsed-intron launch is that
22
22
  * caller. A live view calls `fitAllRegions()`.
23
23
  *
24
24
  * `width` enters only through the zoom-in floor, which bites for a region set
@@ -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 {};