@jbrowse/plugin-linear-genome-view 5.0.0-beta.18 → 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 (37) hide show
  1. package/esm/LinearGenomeView/components/ImportForm.js +2 -4
  2. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +1 -1
  3. package/esm/LinearGenomeView/components/SearchBox.js +1 -9
  4. package/esm/LinearGenomeView/components/SearchResultsDialog.js +1 -1
  5. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
  6. package/esm/LinearGenomeView/components/SearchResultsTable.js +23 -5
  7. package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
  8. package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
  9. package/esm/LinearGenomeView/components/TrackContainer.js +3 -4
  10. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
  11. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
  12. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +2 -3
  13. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +1 -1
  14. package/esm/LinearGenomeView/index.d.ts +1 -1
  15. package/esm/LinearGenomeView/index.js +2 -0
  16. package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -9
  17. package/esm/LinearGenomeView/keyboardHandler.js +58 -34
  18. package/esm/LinearGenomeView/menuItems.js +3 -2
  19. package/esm/LinearGenomeView/model.d.ts +412 -45
  20. package/esm/LinearGenomeView/model.js +23 -15
  21. package/esm/LinearGenomeView/rExport/exportLinearView.js +4 -5
  22. package/esm/LinearGenomeView/rExport/rScript.d.ts +5 -1
  23. package/esm/LinearGenomeView/rExport/rScript.js +24 -3
  24. package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
  25. package/esm/LinearGenomeView/showRegionsWithUndo.js +1 -1
  26. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +3 -1
  27. package/esm/LinearGenomeView/util.d.ts +1 -1
  28. package/esm/LinearGenomeView/util.js +1 -1
  29. package/esm/displayKitTests/perRegionTestEnv.d.ts +10 -4
  30. package/esm/index.js +0 -2
  31. package/esm/searchUtils.d.ts +14 -2
  32. package/esm/searchUtils.js +56 -36
  33. package/package.json +9 -9
  34. package/esm/FeatureTrack/configSchema.d.ts +0 -219
  35. package/esm/FeatureTrack/configSchema.js +0 -57
  36. package/esm/FeatureTrack/index.d.ts +0 -2
  37. package/esm/FeatureTrack/index.js +0 -13
@@ -2,7 +2,8 @@ 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';
@@ -297,11 +298,11 @@ export function stateModelFactory(pluginManager) {
297
298
  hideNoTracksActive: types.stripDefault(types.boolean, false),
298
299
  /**
299
300
  * #property
300
- * vestigial: the hierarchical selector is the only one that exists, so
301
- * this value is ignored. Retained because saved sessions and share
302
- * links persist it.
301
+ * ignored: the hierarchical selector is the only one. Declared because
302
+ * saved sessions and share links carry it.
303
+ * @deprecated
303
304
  */
304
- trackSelectorType: types.stripDefault(types.enumeration(['hierarchical']), 'hierarchical'),
305
+ trackSelectorType: types.stripDefault(types.string, 'hierarchical'),
305
306
  /**
306
307
  * #property
307
308
  * show the "center line". A personal preference: never in a session
@@ -1332,12 +1333,10 @@ export function stateModelFactory(pluginManager) {
1332
1333
  if (!assemblyName) {
1333
1334
  throw new Error('the view has no assembly to search yet');
1334
1335
  }
1335
- const { assemblyManager, textSearchManager } = getSession(self);
1336
1336
  const results = await fetchResults({
1337
+ session: getSession(self),
1337
1338
  queryString: query,
1338
1339
  assemblyName,
1339
- textSearchManager,
1340
- assembly: await assemblyManager.waitForAssembly(assemblyName),
1341
1340
  signal: opts?.signal,
1342
1341
  });
1343
1342
  return searchHitsOf(self, results, query, assemblyName, result => navToOption({
@@ -2249,7 +2248,7 @@ export function stateModelFactory(pluginManager) {
2249
2248
  *
2250
2249
  * The coarse blocks are a 500ms throttle, and every consumer trades
2251
2250
  * freshness for not recomputing per animation frame — the location box,
2252
- * 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
2253
2252
  * `settledDynamicBlocks`. That trade is only sound while the answer is a
2254
2253
  * few frames old. **A jump makes it unrelated instead**, because there is
2255
2254
  * nothing to coalesce: the viewport left behind is not an approximation of
@@ -2710,16 +2709,26 @@ export function stateModelFactory(pluginManager) {
2710
2709
  getSession(self).addHighlight(region);
2711
2710
  }
2712
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 => ({
2713
2723
  /**
2714
2724
  * #action
2715
2725
  * navigates to the most recently added highlight on this view's
2716
- * assemblies, zoomed out a little for context
2726
+ * assemblies
2717
2727
  */
2718
2728
  async navigateNewestHighlight() {
2719
2729
  const newest = self.highlights.at(-1);
2720
2730
  if (newest) {
2721
- const { assemblyName, ...region } = newest;
2722
- await self.navToLocString(assembleLocString(region), assemblyName, 0.2);
2731
+ await self.navToHighlight(newest);
2723
2732
  }
2724
2733
  else {
2725
2734
  getSession(self).notify('There are no highlights to navigate to.', 'info');
@@ -2830,11 +2839,10 @@ export function stateModelFactory(pluginManager) {
2830
2839
  },
2831
2840
  /**
2832
2841
  * #method
2833
- * returns menu items for a highlight context menu. plugins can extend
2834
- * this via Core-extendPluggableElement to add items
2842
+ * a highlight chip's menu rows after Dismiss; extend this to add one
2835
2843
  */
2836
2844
  highlightMenuItems(_highlight) {
2837
- return [];
2845
+ return highlightListMenuItems(getSession(self), getEnv(self).pluginManager);
2838
2846
  },
2839
2847
  }))
2840
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']
@@ -75,13 +75,17 @@ export declare function locusOf(refName: string, start: number, end: number): st
75
75
  * header instead of stopping the figure: a script that quietly differs from
76
76
  * its view says so.
77
77
  */
78
- 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, }: {
79
79
  tracks: readonly RExportTrack[];
80
80
  regions: readonly Region[];
81
81
  /** The view's assembly config as written, which names its sequence and its contigs' aliases. */
82
82
  assembly?: Record<string, unknown>;
83
83
  /** The view's zoom, which decides what a feature or quantitative display shows. */
84
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;
85
89
  out?: string;
86
90
  /** What the caller knows of the view that the figure does not show. */
87
91
  notes?: readonly string[];
@@ -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
@@ -328,6 +334,16 @@ 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
349
  const { color } = track.display.encoding ?? {};
@@ -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
  '',
@@ -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
@@ -31,7 +31,7 @@ export interface PerRegionTestControl {
31
31
  */
32
32
  estimateBytes: number;
33
33
  estimateCalls: number;
34
- /** the display's own density verdict, canvas's second too-large axis */
34
+ /** the display's own density verdict, the feature display's second too-large axis */
35
35
  densityTooLarge: boolean;
36
36
  /**
37
37
  * What the next fetch stores, so a test can tell the payload a refetch
@@ -45,7 +45,7 @@ export interface PerRegionTestControl {
45
45
  * displays compose this foundation, and every one of them adds a plugin's worth
46
46
  * of behavior on top — so a test that boots one is testing that plugin as much
47
47
  * as the foundation, which is how the foundation's own autoruns came to be
48
- * pinned only by canvas's suite.
48
+ * pinned only by the feature display's suite.
49
49
  */
50
50
  /** Which of the gate's opt-in hooks this display overrides. */
51
51
  export interface GateOptIns {
@@ -266,7 +266,9 @@ declare function makeStateModel(control: PerRegionTestControl, gate: GateOptIns,
266
266
  readonly plotKeys: string[];
267
267
  readonly plot: import("@jbrowse/core/configuration").Plot;
268
268
  readonly configNotices: string[];
269
+ } & {
269
270
  readonly notices: string[];
271
+ } & {
270
272
  readonly cornerNotices: string[];
271
273
  readonly plotExamples: readonly import("@jbrowse/core/configuration").PlotExample[];
272
274
  readonly configDocsUrl: string;
@@ -498,12 +500,12 @@ export declare function createPerRegionTestEnvironment({ measuresBytes, gate, es
498
500
  */
499
501
  estimateBytes?: number;
500
502
  }): {
501
- createDisplay: ({ displayedRegions, displaySnapshot, skipWidth, regionsInSnapshot, }?: {
503
+ createDisplay: (args_0?: {
502
504
  displayedRegions?: import("@jbrowse/core/util").Region[];
503
505
  displaySnapshot?: Record<string, unknown>;
504
506
  skipWidth?: boolean;
505
507
  regionsInSnapshot?: boolean;
506
- }) => {
508
+ } | undefined) => {
507
509
  session: import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<{
508
510
  name: import("@jbrowse/mobx-state-tree").IType<string | undefined, string, string>;
509
511
  view: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").IAnyModelType>;
@@ -804,7 +806,9 @@ export declare function createPerRegionTestEnvironment({ measuresBytes, gate, es
804
806
  readonly plotKeys: string[];
805
807
  readonly plot: import("@jbrowse/core/configuration").Plot;
806
808
  readonly configNotices: string[];
809
+ } & {
807
810
  readonly notices: string[];
811
+ } & {
808
812
  readonly cornerNotices: string[];
809
813
  readonly plotExamples: readonly import("@jbrowse/core/configuration").PlotExample[];
810
814
  readonly configDocsUrl: string;
@@ -1231,7 +1235,9 @@ export declare function createPerRegionTestEnvironment({ measuresBytes, gate, es
1231
1235
  readonly plotKeys: string[];
1232
1236
  readonly plot: import("@jbrowse/core/configuration").Plot;
1233
1237
  readonly configNotices: string[];
1238
+ } & {
1234
1239
  readonly notices: string[];
1240
+ } & {
1235
1241
  readonly cornerNotices: string[];
1236
1242
  readonly plotExamples: readonly import("@jbrowse/core/configuration").PlotExample[];
1237
1243
  readonly configDocsUrl: string;
package/esm/index.js CHANGED
@@ -4,7 +4,6 @@ import { isAbstractMenuManager } from '@jbrowse/core/util';
4
4
  import { VIEW_TRACK_LABEL_MODES } from '@jbrowse/display-kit/types';
5
5
  import { types } from '@jbrowse/mobx-state-tree';
6
6
  import LineStyleIcon from '@mui/icons-material/LineStyle';
7
- import FeatureTrackF from './FeatureTrack/index.js';
8
7
  import LaunchLinearGenomeViewF from './LaunchLinearGenomeView/index.js';
9
8
  import SequenceFeatureHoverHighlightExtensionF from './LinearGenomeView/components/SequenceFeatureHoverHighlightExtension.js';
10
9
  import LinearGenomeViewF from './LinearGenomeView/index.js';
@@ -33,7 +32,6 @@ export default class LinearGenomeViewPlugin extends Plugin {
33
32
  },
34
33
  });
35
34
  install(pluginManager) {
36
- FeatureTrackF(pluginManager);
37
35
  LinearGenomeViewF(pluginManager);
38
36
  LaunchLinearGenomeViewF(pluginManager);
39
37
  SequenceFeatureHoverHighlightExtensionF(pluginManager);
@@ -3,7 +3,7 @@ import type BaseResult from '@jbrowse/core/TextSearch/BaseResults';
3
3
  import type TextSearchManager from '@jbrowse/core/TextSearch/TextSearchManager';
4
4
  import type { Assembly } from '@jbrowse/core/assemblyManager/assembly';
5
5
  import type { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter';
6
- import type { AssemblyHost, NotificationSink, TrackCatalog } from '@jbrowse/core/util';
6
+ import type { AbstractSessionModel, AssemblyHost, NotificationSink, TrackCatalog } from '@jbrowse/core/util';
7
7
  declare module '@jbrowse/core/PluginManager' {
8
8
  interface ExtensionPointRegistry {
9
9
  'LinearGenomeView-searchResultSelected': {
@@ -67,7 +67,19 @@ interface SearchArgs {
67
67
  assembly?: Assembly;
68
68
  signal?: AbortSignal;
69
69
  }
70
- export declare function fetchResults(args: SearchArgs): Promise<BaseResult[]>;
70
+ interface SessionSearchArgs {
71
+ session: Pick<AbstractSessionModel, 'assemblyManager' | 'textSearchManager'>;
72
+ queryString: string;
73
+ assemblyName: string;
74
+ searchType?: SearchType;
75
+ signal?: AbortSignal;
76
+ }
77
+ /**
78
+ * The search box's hits for a query: refNames and text-index results, merged.
79
+ * Pass the session and the function finds the index manager and the assembly
80
+ * itself; passing `textSearchManager` and `assembly` is the older form.
81
+ */
82
+ export declare function fetchResults(args: SessionSearchArgs | SearchArgs): Promise<BaseResult[]>;
71
83
  export declare function nameNotFound(input: string, assemblyName: string, { indexCount, failures }: {
72
84
  indexCount: number;
73
85
  failures: unknown[];
@@ -1,11 +1,11 @@
1
1
  import { RefSequenceResult } from '@jbrowse/core/TextSearch/BaseResults';
2
- import { groupByPlace } from '@jbrowse/core/TextSearch/places';
3
- import { dedupe, getEnv, getNotificationSink, getSession, matchRefNames, MAX_GLOB_REGIONS, UnknownRefNameError, } from '@jbrowse/core/util';
2
+ import { groupByPlace, landsInAssembly } from '@jbrowse/core/TextSearch/places';
3
+ import { dedupe, getEnv, getNotificationSink, getSession, matchRefNames, MAX_GLOB_REGIONS, rankRefNames, UnknownRefNameError, } from '@jbrowse/core/util';
4
4
  import { isAlive } from '@jbrowse/mobx-state-tree';
5
5
  import { parseLocStrings } from './LinearGenomeView/util.js';
6
6
  // Budget for refName suggestions from a query. Deliberately far below the
7
- // autocomplete's ~100-row display cap: refNames are returned ahead of the text
8
- // search hits, so a scaffold-heavy assembly would otherwise fill every row with
7
+ // autocomplete's ~100-row display cap: exact and prefix refName hits are
8
+ // returned ahead of the text search hits, so a scaffold-heavy assembly would otherwise fill every row with
9
9
  // scaffolds and push the gene hits out of the list entirely. Raising this to
10
10
  // match the display cap looks like a consistency fix and is not one.
11
11
  const MAX_REFNAME_HITS = 10;
@@ -274,16 +274,20 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, s
274
274
  }
275
275
  else {
276
276
  // no search hits: still try to resolve the input as a locstring (bare
277
- // refname, "ref start end" triplet, etc). if that also can't find a
278
- // refname AND the input is a single bare token (a plausible gene name),
279
- // reframe the unknown-ref error as a name miss; keep the specific ref
280
- // error for coordinate/multi-part queries
277
+ // refname, "ref start end" triplet, etc). If that also can't find a
278
+ // refname and the input names no sequence and no coordinate — a gene
279
+ // name, or a phrase like "breast cancer" — reframe the unknown-ref error
280
+ // as a name miss; keep the specific ref error for coordinate-shaped input
281
281
  try {
282
282
  await navToLocstrings();
283
283
  return true;
284
284
  }
285
285
  catch (e) {
286
- const isPlainName = !input.includes(':') && !input.includes(' ');
286
+ const isPlainName = !input.includes(':') &&
287
+ input
288
+ .trim()
289
+ .split(/\s+/)
290
+ .every(token => !isRef(token) && !/^\d[\d,]*$/.test(token));
287
291
  if (e instanceof UnknownRefNameError && isPlainName) {
288
292
  throw nameNotFound(input, assemblyName, found);
289
293
  }
@@ -308,17 +312,39 @@ async function searchNames({ queryString, searchType, assemblyName, textSearchMa
308
312
  // not itself kick off the lazy load. A load failure just leaves allRefNames
309
313
  // undefined, so this still returns whatever the text search turned up
310
314
  await assembly?.load().catch(() => { });
311
- const refNameResults = assembly
315
+ const { strong, weak } = assembly
312
316
  ? searchRefNames(assembly, queryString, searchType)
313
- : [];
317
+ : { strong: [], weak: [] };
314
318
  return {
315
- results: dedupe([...refNameResults, ...(report?.results ?? [])], elt => elt.getId()),
319
+ results: dedupe([
320
+ ...strong,
321
+ ...(report?.results ?? []).filter(r => landsInAssembly(r, assembly)),
322
+ ...weak,
323
+ ], elt => elt.getId()),
316
324
  indexCount: report?.indexCount ?? 0,
317
325
  failures: report?.failures ?? [],
318
326
  };
319
327
  }
328
+ /**
329
+ * The search box's hits for a query: refNames and text-index results, merged.
330
+ * Pass the session and the function finds the index manager and the assembly
331
+ * itself; passing `textSearchManager` and `assembly` is the older form.
332
+ */
320
333
  export async function fetchResults(args) {
321
- return (await searchNames(args)).results;
334
+ if ('session' in args) {
335
+ const { session, ...rest } = args;
336
+ const assembly = await session.assemblyManager
337
+ .waitForAssembly(args.assemblyName)
338
+ .catch(() => undefined);
339
+ return (await searchNames({
340
+ ...rest,
341
+ textSearchManager: session.textSearchManager,
342
+ assembly,
343
+ })).results;
344
+ }
345
+ else {
346
+ return (await searchNames(args)).results;
347
+ }
322
348
  }
323
349
  // A miss says when an index could not answer: with none answering it is a
324
350
  // failure, and with some answering it is still a miss, naming what failed
@@ -333,30 +359,24 @@ export function nameNotFound(input, assemblyName, { indexCount, failures }) {
333
359
  })
334
360
  : new SearchResultsNotFoundError(`${miss} (a search index failed: ${failure})`, { cause: failure });
335
361
  }
336
- // Scan assembly refnames for query matches, resolving aliases (e.g. 'contigB')
337
- // to the canonical refname ('ctgB') so the dropdown shows the name that matches
338
- // the FASTA / displayed regions. allRefNames can hold ~10^6 entries, so stop
339
- // once enough unique canonical hits accumulate rather than lowercasing and
340
- // scanning the entire list on every keystroke.
362
+ // The refNames a query names, through the same ranking the dropdown's fallback
363
+ // list uses. A refName the query is only a substring of is the weakest kind of
364
+ // hit, so it goes behind the text-search results rather than ahead of them
341
365
  function searchRefNames(assembly, queryString, searchType) {
342
- const q = queryString.toLowerCase();
343
- // canonical name -> whether any alias of it matched the query exactly, so
344
- // "contigb" is an exact hit on ctgB even though the canonical name is not
345
- // the query. Same collapsing as before, now carrying the flag through it
346
- const canonicalHits = new Map();
347
- for (const ref of assembly.allRefNames ?? []) {
348
- const lower = ref.toLowerCase();
349
- const exact = lower === q;
350
- const isMatch = searchType === 'exact' ? exact : lower.startsWith(q);
351
- if (isMatch) {
352
- const canonical = assembly.getCanonicalRefName2(ref);
353
- canonicalHits.set(canonical, exact || !!canonicalHits.get(canonical));
354
- if (canonicalHits.size >= MAX_REFNAME_HITS) {
355
- break;
356
- }
357
- }
358
- }
359
- return [...canonicalHits].map(([r, exact]) => new RefSequenceResult({ label: r, refName: r, exact }));
366
+ const hits = rankRefNames(assembly, queryString, MAX_REFNAME_HITS)
367
+ .filter(({ tier }) => searchType !== 'exact' || tier === 'exact')
368
+ .map(({ refName, tier }) => ({
369
+ tier,
370
+ result: new RefSequenceResult({
371
+ label: refName,
372
+ refName,
373
+ exact: tier === 'exact',
374
+ }),
375
+ }));
376
+ return {
377
+ strong: hits.filter(h => h.tier !== 'substring').map(h => h.result),
378
+ weak: hits.filter(h => h.tier === 'substring').map(h => h.result),
379
+ };
360
380
  }
361
381
  // splits on the last instance of a character
362
382
  export function splitLast(str, split) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-linear-genome-view",
3
- "version": "5.0.0-beta.18",
3
+ "version": "5.0.0-beta.19",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 linear genome view",
6
6
  "keywords": [
@@ -24,13 +24,13 @@
24
24
  "@jbrowse/mobx-state-tree": "^6.10.0",
25
25
  "@mui/icons-material": "^9.5.0",
26
26
  "@mui/material": "^9.5.0",
27
- "mobx": "^7.1.1",
28
- "mobx-react": "^10.0.2",
29
- "@jbrowse/display-ui": "^5.0.0-beta.18",
30
- "@jbrowse/display-kit": "^5.0.0-beta.18",
31
- "@jbrowse/launch-args": "^5.0.0-beta.18",
32
- "@jbrowse/render-core": "^5.0.0-beta.18",
33
- "@jbrowse/core": "^5.0.0-beta.18"
27
+ "mobx": "^7.1.3",
28
+ "mobx-react": "^10.0.3",
29
+ "@jbrowse/core": "^5.0.0-beta.19",
30
+ "@jbrowse/display-kit": "^5.0.0-beta.19",
31
+ "@jbrowse/launch-args": "^5.0.0-beta.19",
32
+ "@jbrowse/display-ui": "^5.0.0-beta.19",
33
+ "@jbrowse/render-core": "^5.0.0-beta.19"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": ">=19.0.0",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "sideEffects": false,
43
43
  "devDependencies": {
44
- "@jbrowse/display-test-utils": "^5.0.0-beta.18"
44
+ "@jbrowse/display-test-utils": "^5.0.0-beta.19"
45
45
  },
46
46
  "scripts": {
47
47
  "prebuild": "pnpm clean",