@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
@@ -0,0 +1,212 @@
1
+ import { mergeConfSettings } from '@jbrowse/core/configuration';
2
+ import { getEnv, getSession } from '@jbrowse/core/util';
3
+ import { basePaintedAt } from '@jbrowse/core/util/Base1DUtils';
4
+ import { predicatesOf, testsSomething, withFields, } from '@jbrowse/core/util/fieldPredicate';
5
+ import { normalizeTrackInit, trackDisplayType } from '@jbrowse/core/util/tracks';
6
+ import { buildDisplaySnapshot, displayCategory, memberPatch, memberWrites, mergeSettings, requestedDisplayType, } from '@jbrowse/launch-args/displaySettings';
7
+ import { getSnapshot, getType, isArrayType, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
8
+ /**
9
+ * The display a linear view opens `conf` as, read off the display picker so it
10
+ * follows `display:` and the adapter rather than a list of track types.
11
+ */
12
+ function displayTypeOf(pluginManager, conf, modifiers, displayType) {
13
+ return trackDisplayType(pluginManager, conf, 'LinearGenomeView', requestedDisplayType(modifiers) ?? displayType);
14
+ }
15
+ /**
16
+ * `displayTypeOf` for a track the session holds, undefined for one it does
17
+ * not; `displayType` is the display the caller opens it as when no
18
+ * `display:` modifier names one.
19
+ */
20
+ export function openedDisplayType(session, trackId, modifiers, displayType) {
21
+ const conf = session.getTrackById(trackId);
22
+ return conf
23
+ ? displayTypeOf(getEnv(session).pluginManager, (isStateTreeNode(conf) ? getSnapshot(conf) : conf), modifiers, displayType)
24
+ : undefined;
25
+ }
26
+ const consoleWarn = (message) => {
27
+ console.warn(`Warning: ${message}`);
28
+ };
29
+ function modifierLaunch(displayType, modifiers, warn = consoleWarn) {
30
+ return {
31
+ displayType,
32
+ settings: buildDisplaySnapshot(displayType, modifiers, warn),
33
+ members: memberWrites(displayType, modifiers),
34
+ };
35
+ }
36
+ // the entries grouped by field, as `setFilterFields` takes them: a field whose
37
+ // entries are all bare is `[]`, which clears it, defaults included
38
+ function filterPatch(entries) {
39
+ const patch = {};
40
+ for (const p of entries) {
41
+ patch[p.field] = [
42
+ ...(patch[p.field] ?? []),
43
+ ...(testsSomething(p) ? [p] : []),
44
+ ];
45
+ }
46
+ return patch;
47
+ }
48
+ // the display snapshot a track opens with. `filter` stays out of it:
49
+ // `writeFilter` writes it once the track is open, since the list in a
50
+ // snapshot would replace the configured one
51
+ function launchSnapshot({ settings }) {
52
+ const { snap, displayType } = settings;
53
+ const { filter: _filter, ...unfiltered } = snap;
54
+ return {
55
+ ...unfiltered,
56
+ ...(displayType ? { type: displayType } : {}),
57
+ };
58
+ }
59
+ /**
60
+ * Write the `filter` of `trackId`'s `settings` onto its display open in
61
+ * `view`, by field over the configured filter, as the track menu would.
62
+ * Returns the display's filter as written, which reopens the track as drawn,
63
+ * or undefined when the settings write no filter.
64
+ */
65
+ export function writeFilter(view, trackId, settings) {
66
+ const { filter } = settings.snap;
67
+ const track = view.tracks.find(t => t.configuration.trackId === trackId);
68
+ if (!filter || !track) {
69
+ return undefined;
70
+ }
71
+ const patch = filterPatch(filter);
72
+ const display = track.displays[0];
73
+ if (display?.setFilterFields) {
74
+ display.setFilterFields(patch);
75
+ return display.writtenFilter;
76
+ }
77
+ const written = withFields(predicatesOf(track.activeDisplay.configuration.filter), patch);
78
+ view.showTrack(trackId, {}, { filter: written });
79
+ return written;
80
+ }
81
+ /**
82
+ * @deprecated `writeFilter`, which every display category takes. This one
83
+ * writes an alignments `display`'s filter only.
84
+ */
85
+ export function writeReadFilter(display, displayType, settings) {
86
+ const { filter } = settings.snap;
87
+ if (displayCategory(displayType) !== 'alignments' || !filter) {
88
+ return undefined;
89
+ }
90
+ const filterDisplay = display;
91
+ filterDisplay?.setFilterFields?.(filterPatch(filter));
92
+ return filterDisplay?.writtenFilter;
93
+ }
94
+ // the pivot is the base under the view's center, which a view has only once
95
+ // it shows a region
96
+ function centerSort(view, trackId, field, warn) {
97
+ const center = view.centerLineInfo;
98
+ if (center && !center.oob) {
99
+ return {
100
+ field,
101
+ pos: basePaintedAt(center, center.offset),
102
+ refName: center.refName ?? '',
103
+ };
104
+ }
105
+ else {
106
+ warn(`sort:${field} on "${trackId}" ignored — the view has no center position to sort at (pass --loc)`);
107
+ return undefined;
108
+ }
109
+ }
110
+ // The modifiers' settings written over the entry's own as a settings bag
111
+ // writes them (`mergeConfSettings`): a namespace such as `pileup` member by
112
+ // member, a bare value lifted first, and a channel such as `encoding.color`
113
+ // whole, so a `color:` modifier is not mixed into the entry's color by field.
114
+ // Plain JSON where no registered display type says which is which.
115
+ function overEntry(view, displayType, entry, over) {
116
+ const pluginManager = isStateTreeNode(view)
117
+ ? getEnv(view).pluginManager
118
+ : undefined;
119
+ return displayType && pluginManager?.displayTypes.has(displayType)
120
+ ? mergeConfSettings(pluginManager.getDisplayType(displayType).configSchema, entry, over)
121
+ : mergeSettings(structuredClone(entry), over);
122
+ }
123
+ /**
124
+ * Open `trackId` in `view` with its command-line modifiers applied, and return
125
+ * the session-spec track entry that reopens it as drawn, or undefined when the
126
+ * open fails, which `launchTrack` reports to the session.
127
+ */
128
+ export async function launchTrackWithModifiers(view, trackId, displayType, modifiers, { displaySnapshot = {}, trackSnapshot = {}, adjust, warn = consoleWarn, } = {}) {
129
+ const launch = modifierLaunch(displayType, modifiers, warn);
130
+ const { snap, sort } = launch.settings;
131
+ const at = sort && centerSort(view, trackId, sort.field, warn);
132
+ if (at) {
133
+ snap.sort = at;
134
+ }
135
+ adjust?.(launch.settings);
136
+ const launched = overEntry(view, displayType, displaySnapshot, launchSnapshot(launch));
137
+ const opened = await view.launchTrack(trackId, trackSnapshot, launched);
138
+ if (!opened) {
139
+ return undefined;
140
+ }
141
+ const drawnFilter = writeFilter(view, trackId, launch.settings);
142
+ const members = writeMembers(view, trackId, launch.members);
143
+ return {
144
+ trackId,
145
+ ...launched,
146
+ ...(drawnFilter ? { filter: drawnFilter } : {}),
147
+ ...members,
148
+ };
149
+ }
150
+ /**
151
+ * Open a session spec's track entry in a navigated `view`, its `modifiers`
152
+ * written over its display settings. When a modifier or the open fails, the
153
+ * session's snackbar says why and the track opens with its own settings.
154
+ */
155
+ export async function launchTrackInit(view, t) {
156
+ const { trackId, trackSnapshot, displaySnapshot, modifiers } = normalizeTrackInit(t);
157
+ if (modifiers.length === 0) {
158
+ await view.launchTrack(trackId, trackSnapshot, displaySnapshot);
159
+ return;
160
+ }
161
+ const session = getSession(view);
162
+ const { type } = displaySnapshot;
163
+ const notes = [];
164
+ try {
165
+ await launchTrackWithModifiers(view, trackId, openedDisplayType(session, trackId, modifiers, typeof type === 'string' ? type : undefined), modifiers, {
166
+ trackSnapshot,
167
+ displaySnapshot,
168
+ warn: message => notes.push(message),
169
+ });
170
+ }
171
+ catch (e) {
172
+ console.error(e);
173
+ session.notifyError(`"${trackId}" ${modifiers.join(' ')}: ${e}`, e);
174
+ }
175
+ // launchTrack reports its own failure, such as a display:
176
+ // the track cannot open as
177
+ if (!view.tracks.some(t => t.configuration.trackId === trackId)) {
178
+ await view.launchTrack(trackId, trackSnapshot, displaySnapshot);
179
+ }
180
+ if (notes.length) {
181
+ session.notify(notes.join('; '), 'warning');
182
+ }
183
+ }
184
+ /**
185
+ * Write a track's `memberWrites` onto what its display in `view` already has,
186
+ * after every other modifier, so a member write keeps the rest of the setting
187
+ * whatever order the command line gives them in. A display replaces a color
188
+ * or facet object whole, which is why these cannot ride in on the launch
189
+ * snapshot.
190
+ */
191
+ export function writeMembers(view, trackId, members) {
192
+ if (members.length === 0) {
193
+ return {};
194
+ }
195
+ const track = view.tracks.find(t => t.configuration.trackId === trackId);
196
+ if (!track) {
197
+ throw new Error(`"${trackId}" is not open, so ${members.join(' ')} has nothing to write to`);
198
+ }
199
+ const { configuration } = track.activeDisplay;
200
+ const configured = getSnapshot(configuration);
201
+ const patch = memberPatch(members, root => {
202
+ // a list at a default the snapshot strips, Manhattan's marks, still seeds
203
+ // the write, so `marks.0.mark=bar` edits the mark drawn
204
+ const member = configuration[root];
205
+ return (configured[root] ??
206
+ (isStateTreeNode(member) && isArrayType(getType(member))
207
+ ? getSnapshot(member)
208
+ : undefined));
209
+ });
210
+ view.showTrack(trackId, {}, patch);
211
+ return patch;
212
+ }
@@ -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.d.ts CHANGED
@@ -29,12 +29,14 @@ export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/compo
29
29
  export type { SearchBoxPrefs } from './LinearGenomeView/components/useSearchBoxPrefs.ts';
30
30
  export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
31
31
  export { applyInitHighlights } from './LinearGenomeView/afterAttach.ts';
32
+ export { launchTrackInit, launchTrackWithModifiers, openedDisplayType, writeFilter, writeMembers, writeReadFilter, } from './TrackModifiers/index.ts';
33
+ export type { ModifierView } from './TrackModifiers/index.ts';
32
34
  export { MultiLevelRubberband, multiLevelRowClickMenuItems, multiLevelRowMenuItems, rowLabels, } from './MultiLevelRubberband/index.ts';
33
35
  export { fetchResults, SearchResultsNotFoundError } from './searchUtils.ts';
34
36
  export type { LaunchLinearGenomeViewArgs } from './LaunchLinearGenomeView/index.ts';
35
37
  export type { BpOffset, ExportSvgOptions, HighlightInput, HighlightType, InitState, LinearGenomeViewLaunchProps, NavLocation, TrackLabelMode, ViewTrackLabelMode, VolatileGuide, } from './LinearGenomeView/types.ts';
36
38
  export type { ScalebarRefNameLabel } from './LinearGenomeView/util.ts';
37
- export type { HighlightRegion, SearchPickerRow, SearchPickerState, } from './LinearGenomeView/model.ts';
39
+ export type { HighlightRegion, SearchHits, SearchPickerRow, SearchPickerState, } from './LinearGenomeView/model.ts';
38
40
  export type { RegionsOrientation } from './LinearGenomeView/util.ts';
39
41
  export type { RegionEdit } from './LinearGenomeView/util.ts';
40
42
  export { makeTicks } from './LinearGenomeView/util.ts';
package/esm/index.js CHANGED
@@ -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);
@@ -71,6 +69,7 @@ export { default as HeaderSearchBoxRow } from './LinearGenomeView/components/Hea
71
69
  export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/components/useSearchBoxPrefs.js';
72
70
  export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
73
71
  export { applyInitHighlights } from './LinearGenomeView/afterAttach.js';
72
+ export { launchTrackInit, launchTrackWithModifiers, openedDisplayType, writeFilter, writeMembers, writeReadFilter, } from './TrackModifiers/index.js';
74
73
  export { MultiLevelRubberband, multiLevelRowClickMenuItems, multiLevelRowMenuItems, rowLabels, } from './MultiLevelRubberband/index.js';
75
74
  export { fetchResults, SearchResultsNotFoundError } from './searchUtils.js';
76
75
  // The scalebar's grid, published because it is a contract other views draw
@@ -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.17",
3
+ "version": "5.0.0-beta.19",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 linear genome view",
6
6
  "keywords": [
@@ -21,15 +21,16 @@
21
21
  "esm"
22
22
  ],
23
23
  "dependencies": {
24
- "@jbrowse/mobx-state-tree": "^6.9.0",
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.0.6",
28
- "mobx-react": "^10.0.2",
29
- "@jbrowse/core": "^5.0.0-beta.17",
30
- "@jbrowse/display-ui": "^5.0.0-beta.17",
31
- "@jbrowse/render-core": "^5.0.0-beta.17",
32
- "@jbrowse/display-kit": "^5.0.0-beta.17"
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"
33
34
  },
34
35
  "peerDependencies": {
35
36
  "react": ">=19.0.0",
@@ -40,7 +41,7 @@
40
41
  },
41
42
  "sideEffects": false,
42
43
  "devDependencies": {
43
- "@jbrowse/display-test-utils": "^5.0.0-beta.17"
44
+ "@jbrowse/display-test-utils": "^5.0.0-beta.19"
44
45
  },
45
46
  "scripts": {
46
47
  "prebuild": "pnpm clean",