@jbrowse/plugin-linear-genome-view 5.0.0-beta.1 → 5.0.0-beta.10

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 (145) hide show
  1. package/esm/FeatureTrack/configSchema.d.ts +142 -50
  2. package/esm/LaunchLinearGenomeView/index.js +2 -13
  3. package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
  4. package/esm/LinearGenomeView/afterAttach.js +73 -59
  5. package/esm/LinearGenomeView/components/CenterLine.js +5 -3
  6. package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
  7. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +3 -2
  8. package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
  9. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  10. package/esm/LinearGenomeView/components/Header.js +11 -1
  11. package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
  12. package/esm/LinearGenomeView/components/HeaderPanControls.js +2 -1
  13. package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
  14. package/esm/LinearGenomeView/components/Highlight.js +7 -7
  15. package/esm/LinearGenomeView/components/HighlightBand.js +2 -2
  16. package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
  17. package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
  18. package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
  19. package/esm/LinearGenomeView/components/ImportForm.js +2 -2
  20. package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
  21. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -4
  22. package/esm/LinearGenomeView/components/MiniControls.js +3 -2
  23. package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
  24. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
  25. package/esm/LinearGenomeView/components/OverviewRubberband.js +20 -45
  26. package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
  27. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
  28. package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
  29. package/esm/LinearGenomeView/components/PaddingBlocks.js +14 -9
  30. package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
  31. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
  32. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +71 -0
  33. package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
  34. package/esm/LinearGenomeView/components/Rubberband.js +3 -2
  35. package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
  36. package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
  37. package/esm/LinearGenomeView/components/Scalebar.js +8 -5
  38. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +49 -34
  39. package/esm/LinearGenomeView/components/SearchBox.js +8 -7
  40. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +2 -1
  41. package/esm/LinearGenomeView/components/SearchResultsDialog.js +2 -2
  42. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
  43. package/esm/LinearGenomeView/components/SearchResultsTable.js +12 -29
  44. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
  45. package/esm/LinearGenomeView/components/TrackContainer.js +63 -9
  46. package/esm/LinearGenomeView/components/TrackLabel.js +10 -3
  47. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  48. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -2
  49. package/esm/LinearGenomeView/components/TrackLabelMenu.js +3 -2
  50. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +4 -1
  51. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +11 -23
  52. package/esm/LinearGenomeView/components/TracksContainer.js +10 -22
  53. package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
  54. package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
  55. package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
  56. package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
  57. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  58. package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
  59. package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
  60. package/esm/LinearGenomeView/components/searchModes.js +1 -1
  61. package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
  62. package/esm/LinearGenomeView/components/useRangeSelect.js +20 -59
  63. package/esm/LinearGenomeView/components/useSideScroll.d.ts +16 -2
  64. package/esm/LinearGenomeView/components/useSideScroll.js +153 -103
  65. package/esm/LinearGenomeView/components/util.d.ts +4 -0
  66. package/esm/LinearGenomeView/components/util.js +4 -2
  67. package/esm/LinearGenomeView/consts.d.ts +2 -0
  68. package/esm/LinearGenomeView/consts.js +3 -1
  69. package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
  70. package/esm/LinearGenomeView/containingLgv.js +23 -0
  71. package/esm/LinearGenomeView/headerFit.d.ts +4 -2
  72. package/esm/LinearGenomeView/headerFit.js +20 -7
  73. package/esm/LinearGenomeView/index.d.ts +1 -5
  74. package/esm/LinearGenomeView/index.js +10 -7
  75. package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
  76. package/esm/LinearGenomeView/keyboardHandler.js +15 -1
  77. package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
  78. package/esm/LinearGenomeView/launchKeys.js +27 -0
  79. package/esm/LinearGenomeView/lazyDialogs.d.ts +2 -4
  80. package/esm/LinearGenomeView/lazyDialogs.js +1 -0
  81. package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
  82. package/esm/LinearGenomeView/linkedViewSync.js +27 -43
  83. package/esm/LinearGenomeView/menuItems.d.ts +16 -18
  84. package/esm/LinearGenomeView/menuItems.js +242 -151
  85. package/esm/LinearGenomeView/model.d.ts +2009 -200
  86. package/esm/LinearGenomeView/model.js +545 -395
  87. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  88. package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
  89. package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
  90. package/esm/LinearGenomeView/stickyChrome.js +5 -9
  91. package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
  92. package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
  93. package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
  94. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +8 -8
  95. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
  96. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +7 -11
  97. package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
  98. package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
  99. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
  100. package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
  101. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +27 -0
  102. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
  103. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +15 -6
  104. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -4
  105. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +6 -5
  106. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -1
  107. package/esm/LinearGenomeView/svgcomponents/SVGView.js +1 -1
  108. package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
  109. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
  110. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +50 -68
  111. package/esm/LinearGenomeView/svgcomponents/util.d.ts +9 -5
  112. package/esm/LinearGenomeView/svgcomponents/util.js +26 -21
  113. package/esm/LinearGenomeView/types.d.ts +8 -11
  114. package/esm/LinearGenomeView/util.d.ts +60 -45
  115. package/esm/LinearGenomeView/util.js +131 -160
  116. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +7 -4
  117. package/esm/MultiLevelRubberband/index.d.ts +2 -0
  118. package/esm/MultiLevelRubberband/index.js +2 -0
  119. package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
  120. package/esm/MultiLevelRubberband/menuItems.js +52 -0
  121. package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
  122. package/esm/MultiLevelRubberband/rowLabel.js +27 -0
  123. package/esm/MultiLevelRubberband/types.d.ts +1 -0
  124. package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
  125. package/esm/MultiLevelRubberband/useRangeSelect.js +31 -47
  126. package/esm/displayKitTests/perRegionTestEnv.d.ts +214 -86
  127. package/esm/displayKitTests/perRegionTestEnv.js +30 -29
  128. package/esm/index.d.ts +15 -85
  129. package/esm/index.js +13 -36
  130. package/esm/searchUtils.d.ts +18 -9
  131. package/esm/searchUtils.js +85 -69
  132. package/esm/shared/RubberbandSpan.js +2 -1
  133. package/esm/shared/coordLabels.d.ts +7 -0
  134. package/esm/shared/coordLabels.js +15 -1
  135. package/esm/shared/useWindowDrag.d.ts +18 -0
  136. package/esm/shared/useWindowDrag.js +45 -0
  137. package/package.json +10 -10
  138. package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
  139. package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
  140. package/esm/LinearGenomeView/initKeys.d.ts +0 -13
  141. package/esm/LinearGenomeView/initKeys.js +0 -104
  142. package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
  143. package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
  144. package/esm/lazyPluginExports.d.ts +0 -23
  145. package/esm/lazyPluginExports.js +0 -36
@@ -1,5 +1,6 @@
1
1
  import { RefSequenceResult } from '@jbrowse/core/TextSearch/BaseResults';
2
- import { assembleLocString, dedupe, getEnv, getNotificationSink, getSession, matchRefNames, MAX_GLOB_REGIONS, parseLocString, UnknownRefNameError, } from '@jbrowse/core/util';
2
+ import { groupByPlace } from '@jbrowse/core/TextSearch/places';
3
+ import { dedupe, getEnv, getNotificationSink, getSession, matchRefNames, MAX_GLOB_REGIONS, UnknownRefNameError, } from '@jbrowse/core/util';
3
4
  import { isAlive } from '@jbrowse/mobx-state-tree';
4
5
  import { parseLocStrings } from './LinearGenomeView/util.js';
5
6
  // Budget for refName suggestions from a query. Deliberately far below the
@@ -36,37 +37,6 @@ export function isOpenInView(result, model) {
36
37
  const trackId = result.getTrackId();
37
38
  return trackId !== undefined && !!model.getTrack(trackId);
38
39
  }
39
- // One spelling of a locstring, so two indexes that answer `chr1:1-100` and
40
- // `1:1..100` are recognised as one place. An unparseable string is compared
41
- // raw, which can only ever split a group that would otherwise have merged —
42
- // the safe direction, since the picker is what an unprovable match falls back
43
- // to.
44
- function canonicalLocString(locString, assembly) {
45
- try {
46
- const loc = parseLocString(locString, refName => assembly.isValidRefName(refName));
47
- return assembleLocString({
48
- ...loc,
49
- refName: assembly.getCanonicalRefName2(loc.refName),
50
- });
51
- }
52
- catch (e) {
53
- console.warn('failed to parse location string', locString, e);
54
- return locString;
55
- }
56
- }
57
- // What a hit means as a destination: the name it shows and the place it goes.
58
- // A hit with no location has no destination and can never join a group.
59
- function destination(result, assembly) {
60
- const locString = result.getLocation();
61
- return locString
62
- ? [
63
- result.getDisplayString(),
64
- assembly?.initialized
65
- ? canonicalLocString(locString, assembly)
66
- : locString,
67
- ].join('\u0000')
68
- : undefined;
69
- }
70
40
  // Which hit of an agreeing group to travel through, best rung first: a track
71
41
  // the view already has on screen, then one the session could open if asked,
72
42
  // then whatever the ranking put first. The middle rung is not hypothetical —
@@ -86,40 +56,48 @@ function trackRank(result, model, session) {
86
56
  return session.getTrackById(trackId) ? 1 : 3;
87
57
  }
88
58
  }
89
- // The picker exists to disambiguate, and hits that name one feature at one
90
- // place are not ambiguous — they are several indexes having found it. An
91
- // instance carrying a handful of gene tracks turns every gene search into a
92
- // table whose only varying column is Track, which is issues #4302 and #5068.
93
- // When the hits agree there is nothing to ask, so navigate instead.
94
- export function unanimousResult({ results, model, session, assembly, }) {
95
- // computed once per hit rather than once per comparison: a broad query can
96
- // carry a hundred results, and each one costs a locstring parse
97
- const destinations = results.map(r => destination(r, assembly));
98
- const agree = destinations[0] !== undefined &&
99
- destinations.every(d => d === destinations[0]);
100
- // stable, so an agreeing group with nothing to choose between keeps the
101
- // order the ranking handed it
102
- return agree
103
- ? [...results].sort((a, b) => trackRank(a, model, session) - trackRank(b, model, session))[0]
104
- : undefined;
59
+ // The pick is a stable minimum, so a linear scan rather than a sort: keeping
60
+ // the first strictly-lowest rung leaves a group with nothing to choose between
61
+ // in the order the ranking handed it. A comparator would ask `trackRank`, and
62
+ // through it `session.getTrackById`, O(n log n) times.
63
+ function bestRanked(results, model, session) {
64
+ let best = results[0];
65
+ let bestRank = trackRank(best, model, session);
66
+ for (let i = 1; i < results.length; i++) {
67
+ const rank = trackRank(results[i], model, session);
68
+ if (rank < bestRank) {
69
+ best = results[i];
70
+ bestRank = rank;
71
+ }
72
+ }
73
+ return best;
74
+ }
75
+ // One hit per place, in the order the ranking first reached each. Hits that
76
+ // name one feature at one place are several indexes having found it, not a
77
+ // choice to put to the user: a handful of gene tracks turned every gene search
78
+ // into a picker whose only varying column was Track (issues #4302 and #5068),
79
+ // and two whose gene models end a few bases apart still did.
80
+ export function distinctDestinations({ results, model, session, assembly, }) {
81
+ return groupByPlace(results, assembly).map(group => bestRanked(group, model, session));
105
82
  }
106
83
  // Every multi-hit result set reaches the picker through here, so the two
107
- // search surfaces cannot disagree about when one is worth raising. Returns
108
- // whether the view moved.
109
- export async function showSearchResults({ results, query, model, assemblyName, grow, }) {
84
+ // search surfaces cannot disagree about when one is worth raising: only when
85
+ // the hits name more than one place. Returns whether the view moved.
86
+ export async function showSearchResults({ results, query, model, assemblyName, grow, showHitTrack, }) {
110
87
  const session = getSession(model);
111
- const unanimous = unanimousResult({
88
+ const places = distinctDestinations({
112
89
  results,
113
90
  model,
114
91
  session,
115
92
  assembly: session.assemblyManager.get(assemblyName),
116
93
  });
117
- if (unanimous) {
118
- await navToOption({ option: unanimous, model, assemblyName, grow });
94
+ const land = (option) => navToOption({ option, model, assemblyName, grow, showHitTrack });
95
+ if (places.length === 1) {
96
+ await land(places[0]);
119
97
  return true;
120
98
  }
121
99
  else {
122
- model.setSearchResults(results, query, assemblyName);
100
+ model.setSearchResults(places, query, assemblyName, land);
123
101
  return false;
124
102
  }
125
103
  }
@@ -127,17 +105,33 @@ export async function showSearchResults({ results, query, model, assemblyName, g
127
105
  // own edges, which is what this default is; a caller that asked for a specific
128
106
  // padding gets that instead.
129
107
  const SEARCH_HIT_GROW = 0.2;
130
- export async function navToOption({ option, model, assemblyName, grow, }) {
108
+ // The track to show beside a hit: the one its index came from, when this
109
+ // session has it. A shared index names tracks of whichever config it was built
110
+ // for — the UCSC hg38 names index says `hg38-ncbiRefSeqGff`, which the HPRC
111
+ // pangenome config reuses without carrying — and launching a track the session
112
+ // lacks turned every gene search there into a "Could not resolve identifier"
113
+ // error over a view that had navigated fine.
114
+ export function hitTrackToShow(result, session) {
115
+ const trackId = result.getTrackId();
116
+ return trackId !== undefined && session.getTrackById(trackId)
117
+ ? trackId
118
+ : undefined;
119
+ }
120
+ // A hit lands with the track its index came from shown, which is what a name
121
+ // typed into the search box wants. A session spec that named its own tracks
122
+ // passes false: it asked for those tracks, and a hosted config's full RefSeq
123
+ // appearing beside the curated one it listed read as a bug.
124
+ export async function navToOption({ option, model, assemblyName, grow, showHitTrack = true, }) {
131
125
  // getLocation() can be an empty string when a result reports hasLocation()
132
126
  // but carries no coordinates; treat that as "no location" and fall back to
133
127
  // the label rather than forwarding '' into an empty, view-blanking parse
134
128
  const location = option.getLocation() || option.getLabel();
135
- const trackId = option.getTrackId();
136
129
  const session = getSession(model);
137
130
  const { assemblyManager } = session;
138
131
  await model.navToLocations(parseLocStrings(location, assemblyName, (ref, asm) => assemblyManager.isValidRefName(ref, asm)), assemblyName, grow ?? SEARCH_HIT_GROW);
132
+ const trackId = showHitTrack ? hitTrackToShow(option, session) : undefined;
139
133
  if (trackId && isAlive(model)) {
140
- model.showTrack(trackId);
134
+ await model.launchTrack(trackId);
141
135
  }
142
136
  // same detach hazard as showTrack above: navToLocations awaits, and handlers
143
137
  // read off the view (the canvas one calls getSession(model) and model.tracks),
@@ -174,7 +168,7 @@ export function notifySearchFailure(session, e) {
174
168
  // about where to go and so raise the picker, and a view detached while the
175
169
  // search RPC ran — and each of them resolves, so a caller awaiting this cannot
176
170
  // otherwise tell "showing it" from "asked you which one".
177
- export async function handleSelectedRegion({ input, model, assemblyName, grow, }) {
171
+ export async function handleSelectedRegion({ input, model, assemblyName, grow, showHitTrack, }) {
178
172
  const { assemblyManager, textSearchManager } = getSession(model);
179
173
  // resolves only once regions/aliases are loaded, which isValidRefName needs
180
174
  // (it throws otherwise). A load failure is not this function's to report:
@@ -241,14 +235,14 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
241
235
  // adapters answer 'exact' by filtering exactly this list. An adapter that
242
236
  // tags nothing simply never wins the exact pass, which is the behaviour it
243
237
  // had when it returned nothing for searchType: 'exact'.
244
- const allResults = await fetchResults({
238
+ const found = await searchNames({
245
239
  queryString: input,
246
240
  assemblyName,
247
241
  textSearchManager,
248
242
  assembly,
249
243
  });
250
- const exactResults = allResults.filter(r => r.isExact());
251
- const results = exactResults.length ? exactResults : allResults;
244
+ const exactResults = found.results.filter(r => r.isExact());
245
+ const results = exactResults.length ? exactResults : found.results;
252
246
  // the view may have been closed/detached while the text-search RPC ran
253
247
  if (!isAlive(model)) {
254
248
  return false;
@@ -260,6 +254,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
260
254
  model,
261
255
  assemblyName,
262
256
  grow,
257
+ showHitTrack,
263
258
  });
264
259
  }
265
260
  else if (results.length === 1) {
@@ -272,6 +267,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
272
267
  model,
273
268
  assemblyName,
274
269
  grow,
270
+ showHitTrack,
275
271
  });
276
272
  return true;
277
273
  }
@@ -279,8 +275,8 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
279
275
  // no search hits: still try to resolve the input as a locstring (bare
280
276
  // refname, "ref start end" triplet, etc). if that also can't find a
281
277
  // refname AND the input is a single bare token (a plausible gene name),
282
- // reframe the unknown-ref error as a clean "no results" message; keep the
283
- // specific ref error for coordinate/multi-part queries
278
+ // reframe the unknown-ref error as a name miss; keep the specific ref
279
+ // error for coordinate/multi-part queries
284
280
  try {
285
281
  await navToLocstrings();
286
282
  return true;
@@ -288,7 +284,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
288
284
  catch (e) {
289
285
  const isPlainName = !input.includes(':') && !input.includes(' ');
290
286
  if (e instanceof UnknownRefNameError && isPlainName) {
291
- throw new SearchResultsNotFoundError(`No results found for "${input}"`);
287
+ throw nameNotFound(input, assemblyName, found);
292
288
  }
293
289
  else {
294
290
  throw e;
@@ -301,11 +297,11 @@ export function checkRef(str, isRef) {
301
297
  const [ref, rest] = splitLast(str, ':');
302
298
  return isRef(str) || (isRef(ref) && /^\d/.test(rest));
303
299
  }
304
- export async function fetchResults({ queryString, searchType, assemblyName, textSearchManager, assembly, stopToken, }) {
305
- const textSearchResults = await textSearchManager?.search({
300
+ async function searchNames({ queryString, searchType, assemblyName, textSearchManager, assembly, signal, }) {
301
+ const report = await textSearchManager?.searchIndexes({
306
302
  queryString,
307
303
  searchType,
308
- stopToken,
304
+ signal,
309
305
  }, assemblyName);
310
306
  // ensure aliases are loaded: allRefNames is a pure getter, so reading it does
311
307
  // not itself kick off the lazy load. A load failure just leaves allRefNames
@@ -314,7 +310,27 @@ export async function fetchResults({ queryString, searchType, assemblyName, text
314
310
  const refNameResults = assembly
315
311
  ? searchRefNames(assembly, queryString, searchType)
316
312
  : [];
317
- return dedupe([...refNameResults, ...(textSearchResults ?? [])], elt => elt.getId());
313
+ return {
314
+ results: dedupe([...refNameResults, ...(report?.results ?? [])], elt => elt.getId()),
315
+ indexCount: report?.indexCount ?? 0,
316
+ failures: report?.failures ?? [],
317
+ };
318
+ }
319
+ export async function fetchResults(args) {
320
+ return (await searchNames(args)).results;
321
+ }
322
+ // A miss says when an index could not answer: with none answering it is a
323
+ // failure, and with some answering it is still a miss, naming what failed
324
+ export function nameNotFound(input, assemblyName, { indexCount, failures }) {
325
+ const [failure] = failures;
326
+ const miss = `No results found for "${input}"`;
327
+ return failure === undefined
328
+ ? new SearchResultsNotFoundError(indexCount ? miss : `${miss}: ${assemblyName} has no text search index`)
329
+ : failures.length === indexCount
330
+ ? new Error(`Searching for "${input}" failed: ${failure}`, {
331
+ cause: failure,
332
+ })
333
+ : new SearchResultsNotFoundError(`${miss} (a search index failed: ${failure})`, { cause: failure });
318
334
  }
319
335
  // Scan assembly refnames for query matches, resolving aliases (e.g. 'contigB')
320
336
  // to the canonical refname ('ctgB') so the dropdown shows the name that matches
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { alpha } from '@jbrowse/core/ui/palette';
2
3
  import { makeStyles } from '@jbrowse/core/util/tss-react';
3
- import { Typography, alpha } from '@mui/material';
4
+ import { Typography } from '@mui/material';
4
5
  import { LABEL_WIDTH, SpanEdgeLabels } from './coordLabels.js';
5
6
  const useStyles = makeStyles()(theme => ({
6
7
  rubberband: {
@@ -1,4 +1,11 @@
1
1
  export declare const LABEL_WIDTH = 100;
2
+ /** A red vertical line at `coordX` with `children` labelled above it. */
3
+ export declare function VerticalGuideLine({ coordX, viewWidth, stickyTop, children, }: {
4
+ coordX: number;
5
+ viewWidth: number;
6
+ stickyTop: string | undefined;
7
+ children: React.ReactNode;
8
+ }): import("react").JSX.Element;
2
9
  /**
3
10
  * The bp coordinate under a vertical guide, drawn just above the top of the
4
11
  * guide line so it covers neither the line nor the cursor hovering the scalebar
@@ -1,8 +1,8 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from 'react';
3
+ import { alpha } from '@jbrowse/core/ui/palette';
3
4
  import { MUI_TOOLTIP_Z_INDEX } from '@jbrowse/core/ui/zIndexes';
4
5
  import { makeStyles } from '@jbrowse/core/util/tss-react';
5
- import { alpha } from '@mui/material';
6
6
  import { createPortal } from 'react-dom';
7
7
  // roughly how wide a coordinate label is. the guide clamps itself half of this
8
8
  // from either border, and a rubberband label that would hang off a border goes
@@ -51,7 +51,21 @@ const useStyles = makeStyles()(theme => ({
51
51
  insideRightEdge: {
52
52
  right: 0,
53
53
  },
54
+ guideLine: {
55
+ pointerEvents: 'none',
56
+ height: '100%',
57
+ width: 1,
58
+ position: 'absolute',
59
+ left: 0,
60
+ background: 'red',
61
+ zIndex: 1001,
62
+ },
54
63
  }));
64
+ /** A red vertical line at `coordX` with `children` labelled above it. */
65
+ export function VerticalGuideLine({ coordX, viewWidth, stickyTop, children, }) {
66
+ const { classes } = useStyles();
67
+ return (_jsxs(_Fragment, { children: [_jsx(GuideLabel, { coordX: coordX, viewWidth: viewWidth, stickyTop: stickyTop, children: children }), _jsx("div", { className: classes.guideLine, style: { transform: `translateX(${coordX}px)` } })] }));
68
+ }
55
69
  function anchorStyle(stickyTop) {
56
70
  return stickyTop === undefined
57
71
  ? { position: 'relative' }
@@ -0,0 +1,18 @@
1
+ import type React from 'react';
2
+ export interface DragEnd {
3
+ startX: number;
4
+ endX: number;
5
+ isClick: boolean;
6
+ clientX: number;
7
+ clientY: number;
8
+ }
9
+ /**
10
+ * Follow a press that began at `startX` (px from `ref`'s left edge) through
11
+ * window events until it ends, so the pointer can leave the element mid-drag.
12
+ * A release within 3px of the press is a click. Undefined `startX` is no drag.
13
+ */
14
+ export declare function useWindowDrag(ref: React.RefObject<HTMLElement | null>, startX: number | undefined, handlers: {
15
+ onMove: (x: number) => void;
16
+ onEnd: (end: DragEnd) => void;
17
+ onCancel: () => void;
18
+ }): void;
@@ -0,0 +1,45 @@
1
+ import { useEffect } from 'react';
2
+ import { useEventCallback } from '@jbrowse/core/util/useEventCallback';
3
+ /**
4
+ * Follow a press that began at `startX` (px from `ref`'s left edge) through
5
+ * window events until it ends, so the pointer can leave the element mid-drag.
6
+ * A release within 3px of the press is a click. Undefined `startX` is no drag.
7
+ */
8
+ export function useWindowDrag(ref, startX, handlers) {
9
+ const onMove = useEventCallback(handlers.onMove);
10
+ const onEnd = useEventCallback(handlers.onEnd);
11
+ const onCancel = useEventCallback(handlers.onCancel);
12
+ useEffect(() => {
13
+ const el = ref.current;
14
+ if (startX === undefined || !el) {
15
+ return;
16
+ }
17
+ const { left } = el.getBoundingClientRect();
18
+ const mouseMove = (event) => {
19
+ onMove(event.clientX - left);
20
+ };
21
+ const mouseUp = ({ clientX, clientY }) => {
22
+ const endX = clientX - left;
23
+ onEnd({
24
+ startX,
25
+ endX,
26
+ isClick: Math.abs(endX - startX) <= 3,
27
+ clientX,
28
+ clientY,
29
+ });
30
+ };
31
+ const keyDown = (event) => {
32
+ if (event.key === 'Escape') {
33
+ onCancel();
34
+ }
35
+ };
36
+ window.addEventListener('mousemove', mouseMove);
37
+ window.addEventListener('mouseup', mouseUp);
38
+ window.addEventListener('keydown', keyDown);
39
+ return () => {
40
+ window.removeEventListener('mousemove', mouseMove);
41
+ window.removeEventListener('mouseup', mouseUp);
42
+ window.removeEventListener('keydown', keyDown);
43
+ };
44
+ }, [ref, startX, onMove, onEnd, onCancel]);
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-linear-genome-view",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.10",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 linear genome view",
6
6
  "keywords": [
@@ -21,15 +21,15 @@
21
21
  "esm"
22
22
  ],
23
23
  "dependencies": {
24
- "@jbrowse/mobx-state-tree": "^6.5.1",
25
- "@mui/icons-material": "^9.3.1",
26
- "@mui/material": "^9.3.1",
27
- "mobx": "^7.0.3",
24
+ "@jbrowse/mobx-state-tree": "^6.6.1",
25
+ "@mui/icons-material": "^9.4.0",
26
+ "@mui/material": "^9.4.0",
27
+ "mobx": "^7.0.5",
28
28
  "mobx-react": "^10.0.2",
29
- "@jbrowse/core": "^5.0.0-beta.1",
30
- "@jbrowse/render-core": "^5.0.0-beta.1",
31
- "@jbrowse/display-ui": "^5.0.0-beta.1",
32
- "@jbrowse/display-kit": "^5.0.0-beta.1"
29
+ "@jbrowse/core": "^5.0.0-beta.10",
30
+ "@jbrowse/display-kit": "^5.0.0-beta.10",
31
+ "@jbrowse/render-core": "^5.0.0-beta.10",
32
+ "@jbrowse/display-ui": "^5.0.0-beta.10"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": ">=19.0.0",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "sideEffects": false,
42
42
  "devDependencies": {
43
- "@jbrowse/display-test-utils": "^5.0.0-beta.1"
43
+ "@jbrowse/display-test-utils": "^5.0.0-beta.10"
44
44
  },
45
45
  "scripts": {
46
46
  "prebuild": "pnpm clean",
@@ -1,5 +0,0 @@
1
- export declare const FloatingSvgOverlay: ({ width, height, children, }: {
2
- width: number;
3
- height: number;
4
- children: React.ReactNode;
5
- }) => import("react").JSX.Element;
@@ -1,25 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { TrackOverlayPortal } from '@jbrowse/display-ui';
3
- import { observer } from 'mobx-react';
4
- // Lifts an SVG legend / chrome above the inter-region padding masks: a
5
- // full-track, pointer-events:none <svg> portaled into the TrackContainer overlay
6
- // node (see TrackOverlayPortal). The SVG counterpart to the HTML overlays
7
- // (FloatingLegend, HicOverlayPanel) — used where the same legend
8
- // markup is also drawn by an SVG-export path, so it must stay <svg>.
9
- //
10
- // Children draw in <svg> user space; the wrapping <svg> stays pointer-events:none
11
- // so it doesn't eat canvas events, so any interactive sub-content (a dismiss
12
- // "×", clickable swatches) must set pointer-events:auto on its own element.
13
- // fallbackInline={false}: outside a TrackContainer it renders nothing, matching
14
- // callers whose export path draws its own inline legend.
15
- export const FloatingSvgOverlay = observer(function FloatingSvgOverlay({ width, height, children, }) {
16
- return (_jsx(TrackOverlayPortal, { fallbackInline: false, children: _jsx("svg", { style: {
17
- position: 'absolute',
18
- top: 0,
19
- left: 0,
20
- width,
21
- height,
22
- pointerEvents: 'none',
23
- overflow: 'hidden',
24
- }, children: children }) }));
25
- });
@@ -1,13 +0,0 @@
1
- export declare function partitionLaunchKeys(spec: object, viewPropKeys: ReadonlySet<string>): {
2
- init: Record<string, unknown>;
3
- viewProps: Record<string, unknown>;
4
- unknown: Record<string, unknown>;
5
- };
6
- export declare function linearGenomeViewPropKeys(pluginManager: {
7
- getViewType: (name: string) => {
8
- stateModel: {
9
- properties: object;
10
- };
11
- };
12
- }): ReadonlySet<string>;
13
- export declare function warnUnknownLaunchKeys(surface: string, unknown: object): void;
@@ -1,104 +0,0 @@
1
- // The init keys are derived from their interface so they can't drift: the
2
- // Record requires exactly one entry per key, so adding a field without listing
3
- // it here is a compile error rather than a key that silently warns as "unknown".
4
- //
5
- // There is no matching list for the plain view props, and there must not be:
6
- // they are whatever the state model declares, read off it by the caller and
7
- // passed in as `viewPropKeys`. A hand-written eight is what left `hideHeader`,
8
- // `showGridlines`, `showCytobands` and five others declared, menu-settable and
9
- // silently unreachable from any spec — the same failure LinearSyntenyView's
10
- // `drawLocationMarkers` had.
11
- //
12
- // #launchKeys LinearGenomeView — the URL parameters page renders this list
13
- // rather than restating it; each of these keys has its own `&param=` section
14
- // there, which is why no description is attached here.
15
- const knownInitKeyMap = {
16
- loc: true,
17
- grow: true,
18
- assembly: true,
19
- displayedRegionNames: true,
20
- tracks: true,
21
- tracklist: true,
22
- nav: true,
23
- highlight: true,
24
- };
25
- const knownInitKeys = new Set(Object.keys(knownInitKeyMap));
26
- // Never settable from a spec, whatever the model declares: `type` picks the
27
- // view and `id` is passed top-level so MST's optional identifier honors it.
28
- const RESERVED = new Set(['id', 'type', 'init']);
29
- // The viewport keys from before it was stored as a window. They are no longer
30
- // declared properties, so the model's own property list does not contain them
31
- // and they would partition as typos — reported as unknown and dropped. They are
32
- // not typos: `preProcessSnapshot` still converts them, so they belong in the
33
- // snapshot bucket, and a URL or saved spec that names them goes on working.
34
- //
35
- // Deletable with `legacyBpPerPx` in the model, and for the same reason.
36
- const LEGACY_VIEWPORT_PROPS = new Set(['bpPerPx', 'offsetPx']);
37
- // Partition launch keys three ways, once, for every caller that needs to know
38
- // which is which:
39
- //
40
- // - `init`: resolution keys (loc, tracks, highlight, …) with no direct MST
41
- // representation, which afterAttach applies on attach and then discards
42
- // - `viewProps`: plain persisted props MST restores natively, so they belong on
43
- // the view snapshot rather than in the blob
44
- // - `unknown`: neither, i.e. a typo. MST silently drops unknown snapshot keys and
45
- // `init` is a frozen blob, so nothing else would notice
46
- //
47
- // The two callers differ only in what they do with each bucket: the launcher
48
- // spreads a flat spec across the new view's snapshot and warns about the typos,
49
- // while afterAttach — which only ever sees an already-built blob — warns about
50
- // both a typo and a view prop that ended up inside it.
51
- export function partitionLaunchKeys(spec,
52
- // The view's declared property names. Callers with a live node read it off
53
- // `getPropertyMembers(self).properties`; callers building a snapshot read it
54
- // off the registered view type's `stateModel.properties` — see
55
- // `linearGenomeViewPropKeys`.
56
- viewPropKeys) {
57
- const init = {};
58
- const viewProps = {};
59
- const unknown = {};
60
- for (const [key, value] of Object.entries(spec)) {
61
- const bucket = knownInitKeys.has(key)
62
- ? init
63
- : (viewPropKeys.has(key) || LEGACY_VIEWPORT_PROPS.has(key)) &&
64
- !RESERVED.has(key)
65
- ? viewProps
66
- : unknown;
67
- bucket[key] = value;
68
- }
69
- return { init, viewProps, unknown };
70
- }
71
- // The registered LinearGenomeView's declared property names, for a caller that
72
- // has a PluginManager but no view yet (a launcher, or a synteny row being built).
73
- // A caller holding a live node reads the same thing off
74
- // `getPropertyMembers(self).properties` instead.
75
- //
76
- // `getViewType` THROWS on an unregistered name, and that is left alone: this is
77
- // only ever called from code that is about to add a LinearGenomeView, so an
78
- // absent view type is a broken plugin manager rather than a case to degrade
79
- // through — and degrading would mean silently dropping every property the
80
- // caller asked for, which is the failure this whole mechanism exists to end.
81
- //
82
- // Cached: the set is fixed for the life of the manager, and a launch that opens
83
- // a stack of rows would otherwise rebuild it per row.
84
- const propKeyCache = new WeakMap();
85
- export function linearGenomeViewPropKeys(pluginManager) {
86
- const cached = propKeyCache.get(pluginManager);
87
- if (cached) {
88
- return cached;
89
- }
90
- const { stateModel } = pluginManager.getViewType('LinearGenomeView');
91
- const keys = new Set(Object.keys(stateModel.properties));
92
- propKeyCache.set(pluginManager, keys);
93
- return keys;
94
- }
95
- // Report the `unknown` bucket. Both callers say the same thing about the same
96
- // mistake and differ only in which surface they name, so the wording lives next
97
- // to the partition that produces the bucket rather than in two hand-written
98
- // copies that drift.
99
- export function warnUnknownLaunchKeys(surface, unknown) {
100
- const keys = Object.keys(unknown);
101
- if (keys.length) {
102
- console.warn(`${surface} ignored unknown key(s): ${keys.join(', ')}`);
103
- }
104
- }
@@ -1,6 +0,0 @@
1
- import type { MultiLevelRubberbandModel } from './types.ts';
2
- declare const VerticalGuide: ({ model, coordX, }: {
3
- model: MultiLevelRubberbandModel;
4
- coordX: number;
5
- }) => import("react").JSX.Element;
6
- export default VerticalGuide;
@@ -1,21 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { stringify } from '@jbrowse/core/util';
3
- import { makeStyles } from '@jbrowse/core/util/tss-react';
4
- import { observer } from 'mobx-react';
5
- import { GuideLabel } from '../shared/coordLabels.js';
6
- const useStyles = makeStyles()({
7
- guide: {
8
- pointerEvents: 'none',
9
- height: '100%',
10
- width: 1,
11
- position: 'absolute',
12
- background: 'red',
13
- zIndex: 1001,
14
- },
15
- });
16
- const VerticalGuide = observer(function VerticalGuide({ model, coordX, }) {
17
- const { classes } = useStyles();
18
- const { views } = model;
19
- return (_jsxs(_Fragment, { children: [_jsx(GuideLabel, { coordX: coordX, viewWidth: Math.min(...views.map(view => view.width)), stickyTop: undefined, children: views.map(view => (_jsx("div", { children: stringify(view.pxToBp(coordX), true) }, view.id))) }), _jsx("div", { className: classes.guide, style: { left: coordX } })] }));
20
- });
21
- export default VerticalGuide;
@@ -1,23 +0,0 @@
1
- import type { LinearGenomeViewModel } from './LinearGenomeView/index.ts';
2
- export declare const LinearGenomeView: {
3
- (props: {
4
- model: LinearGenomeViewModel;
5
- }): import("react").JSX.Element;
6
- displayName: string;
7
- };
8
- export declare const SearchBox: {
9
- (props: {
10
- model: LinearGenomeViewModel;
11
- showHelp?: boolean;
12
- minWidth?: number;
13
- maxWidth?: number;
14
- style?: React.CSSProperties;
15
- }): import("react").JSX.Element;
16
- displayName: string;
17
- };
18
- export declare const ZoomControls: {
19
- (props: {
20
- model: LinearGenomeViewModel;
21
- }): import("react").JSX.Element;
22
- displayName: string;
23
- };