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

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 (45) hide show
  1. package/esm/LaunchLinearGenomeView/index.js +2 -13
  2. package/esm/LinearGenomeView/afterAttach.js +16 -18
  3. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +1 -1
  4. package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
  5. package/esm/LinearGenomeView/components/PaddingBlocks.js +11 -1
  6. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +37 -3
  7. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +1 -1
  8. package/esm/LinearGenomeView/components/TrackContainer.js +46 -4
  9. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  10. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -2
  11. package/esm/LinearGenomeView/components/TrackLabelMenu.js +3 -2
  12. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +2 -1
  13. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +5 -15
  14. package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
  15. package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
  16. package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
  17. package/esm/LinearGenomeView/components/searchModes.js +1 -1
  18. package/esm/LinearGenomeView/consts.js +1 -1
  19. package/esm/LinearGenomeView/containingLgv.d.ts +15 -0
  20. package/esm/LinearGenomeView/containingLgv.js +16 -0
  21. package/esm/LinearGenomeView/index.d.ts +1 -0
  22. package/esm/LinearGenomeView/index.js +8 -1
  23. package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
  24. package/esm/LinearGenomeView/launchKeys.js +23 -0
  25. package/esm/LinearGenomeView/lazyDialogs.d.ts +1 -1
  26. package/esm/LinearGenomeView/linkedViewSync.d.ts +1 -1
  27. package/esm/LinearGenomeView/linkedViewSync.js +6 -0
  28. package/esm/LinearGenomeView/model.d.ts +1779 -41
  29. package/esm/LinearGenomeView/model.js +192 -96
  30. package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
  31. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +2 -2
  32. package/esm/LinearGenomeView/types.d.ts +1 -1
  33. package/esm/LinearGenomeView/util.d.ts +31 -0
  34. package/esm/LinearGenomeView/util.js +48 -0
  35. package/esm/displayKitTests/perRegionTestEnv.d.ts +145 -55
  36. package/esm/displayKitTests/perRegionTestEnv.js +26 -25
  37. package/esm/index.d.ts +2 -9
  38. package/esm/index.js +2 -3
  39. package/esm/searchUtils.d.ts +6 -3
  40. package/esm/searchUtils.js +37 -11
  41. package/package.json +8 -8
  42. package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
  43. package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
  44. package/esm/LinearGenomeView/initKeys.d.ts +0 -13
  45. package/esm/LinearGenomeView/initKeys.js +0 -104
@@ -28,47 +28,49 @@ function makeStateModel(control, gate, configSchema) {
28
28
  .volatile(() => ({
29
29
  /** every `fetchNeeded` call, as the region list it was handed */
30
30
  fetchLog: [],
31
- loadedData: new Map(),
32
31
  /**
33
- * What `regionFetchKey` answers. A volatile, not a `control` knob, and
32
+ * What `zoomFetchKey` answers. A volatile, not a `control` knob, and
34
33
  * that is the point: a real display's key reads view state, so the
35
34
  * autorun re-runs when it moves. A plain closure value would be memoized
36
35
  * by the computed and never invalidate.
37
36
  */
38
37
  fetchKey: '',
39
38
  }))
39
+ .views(self => {
40
+ // chained, not replaced: `staleAnswers` forces a false answer for a
41
+ // bounded number of calls, and everything else is the foundation's own
42
+ // store-derived default
43
+ const superRegionHasData = self.regionHasData;
44
+ return {
45
+ regionHasData(displayedRegionIndex) {
46
+ control.cacheValidCalls += 1;
47
+ if (control.staleAnswers > 0) {
48
+ control.staleAnswers -= 1;
49
+ return false;
50
+ }
51
+ return superRegionHasData.call(self, displayedRegionIndex);
52
+ },
53
+ };
54
+ })
40
55
  .views(self => ({
56
+ /** the fetched payloads, off the foundation's store */
57
+ get loadedData() {
58
+ return self.regionPayloads;
59
+ },
41
60
  get gateEnabled() {
42
61
  return gate.gateEnabled ?? false;
43
62
  },
44
- get densityGateEnabled() {
45
- return gate.densityGateEnabled ?? false;
46
- },
47
63
  get densityTooLarge() {
48
64
  return control.densityTooLarge;
49
65
  },
50
- get regionFetchKey() {
66
+ get zoomFetchKey() {
51
67
  return self.fetchKey;
52
68
  },
53
- regionHasData(_displayedRegionIndex) {
54
- control.cacheValidCalls += 1;
55
- if (control.staleAnswers > 0) {
56
- control.staleAnswers -= 1;
57
- return false;
58
- }
59
- return true;
60
- },
61
69
  }))
62
70
  .actions(self => ({
63
71
  setFetchKey(key) {
64
72
  self.fetchKey = key;
65
73
  },
66
- setLoaded(displayedRegionIndex, value) {
67
- self.loadedData.set(displayedRegionIndex, value);
68
- },
69
- clearDisplaySpecificData() {
70
- self.loadedData.clear();
71
- },
72
74
  }))
73
75
  .actions(self => ({
74
76
  fetchNeeded(needed) {
@@ -93,17 +95,15 @@ function makeStateModel(control, gate, configSchema) {
93
95
  // budget means no measurement at all, which is why `estimateCalls`
94
96
  // stays at zero for an ungated display.
95
97
  if (byteLimit === undefined) {
96
- return { value: 'data' };
98
+ return { value: control.payloadTag };
97
99
  }
98
100
  control.estimateCalls += 1;
99
101
  const bytes = control.estimateBytes;
100
102
  return bytes > byteLimit
101
103
  ? { regionTooLarge: true, bytes }
102
- : { value: 'data', bytes };
103
- },
104
- onResult: (idx, result) => {
105
- self.setLoaded(idx, result.value);
104
+ : { value: control.payloadTag, bytes };
106
105
  },
106
+ onResult: (_idx, result) => result.value,
107
107
  });
108
108
  },
109
109
  }));
@@ -121,6 +121,7 @@ export function createPerRegionTestEnvironment({ measuresBytes = false, gate, es
121
121
  estimateBytes,
122
122
  estimateCalls: 0,
123
123
  densityTooLarge: false,
124
+ payloadTag: 'data',
124
125
  };
125
126
  const optIns = gate ?? { gateEnabled: measuresBytes };
126
127
  const env = createDisplayTestEnvironment({
package/esm/index.d.ts CHANGED
@@ -16,12 +16,6 @@ export default class LinearGenomeViewPlugin extends Plugin {
16
16
  readonly description: 'Declarative equivalent of the "Force load" button on the "too much data" banner: when true the display always renders, however large the region or dense the features. Off by default (the gate guards against huge downloads). Set it on a view no one can interact with — an embedded / notebook view, or a screenshot — where the region is known and you want it drawn without a click.';
17
17
  readonly advanced: true;
18
18
  };
19
- readonly maxFeatureScreenDensity: {
20
- readonly type: "number";
21
- readonly description: "maximum features per pixel before showing a \"too many features\" message";
22
- readonly defaultValue: 1;
23
- readonly advanced: true;
24
- };
25
19
  readonly height: {
26
20
  readonly type: "number";
27
21
  readonly defaultValue: 100;
@@ -92,7 +86,6 @@ export { default as HeaderSearchBoxRow } from './LinearGenomeView/components/Hea
92
86
  export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/components/useSearchBoxPrefs.ts';
93
87
  export type { SearchBoxPrefs } from './LinearGenomeView/components/useSearchBoxPrefs.ts';
94
88
  export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
95
- export { linearGenomeViewPropKeys, partitionLaunchKeys, } from './LinearGenomeView/initKeys.ts';
96
89
  export { applyInitHighlights } from './LinearGenomeView/afterAttach.ts';
97
90
  export { MultiLevelRubberband } from './MultiLevelRubberband/index.ts';
98
91
  export { fetchResults, SearchResultsNotFoundError } from './searchUtils.ts';
@@ -102,6 +95,7 @@ export type { ScalebarRefNameLabel } from './LinearGenomeView/util.ts';
102
95
  export type { RegionsOrientation } from './LinearGenomeView/util.ts';
103
96
  export { makeTicks } from './LinearGenomeView/util.ts';
104
97
  export type { Tick } from './LinearGenomeView/util.ts';
98
+ export { containingLgv } from './LinearGenomeView/containingLgv.ts';
105
99
  export { showRegionsWithUndo } from './LinearGenomeView/showRegionsWithUndo.ts';
106
100
  export type { SharedFit } from './LinearGenomeView/sharedScaleContainer.ts';
107
101
  export { fitAllRegionsWindow } from './LinearGenomeView/util.ts';
@@ -117,8 +111,7 @@ export { default as ExportSvgDialog } from './LinearGenomeView/components/Export
117
111
  export { GetSequenceDialog } from './LinearGenomeView/lazyDialogs.ts';
118
112
  export { default as ConnectedHoverHighlight } from './LinearGenomeView/components/ConnectedHoverHighlight.tsx';
119
113
  export { default as HoverPositionHighlight } from './LinearGenomeView/components/HoverPositionHighlight.tsx';
120
- export { TrackOverlayContext, TrackOverlayPortal, TrackOverlaySlot, } from '@jbrowse/display-ui';
121
- export { FloatingSvgOverlay } from './LinearGenomeView/FloatingSvgOverlay.tsx';
114
+ export { FloatingSvgOverlay, TrackOverlayContext, TrackOverlayPortal, TrackOverlaySlot, } from '@jbrowse/display-ui';
122
115
  export type { HoverHighlightPosition } from './LinearGenomeView/components/HoverPositionHighlight.tsx';
123
116
  export { SvgChrome, SvgClipRect } from '@jbrowse/core/svg/SvgExport';
124
117
  export { svgSafeId } from '@jbrowse/core/svg/svgId';
package/esm/index.js CHANGED
@@ -85,7 +85,6 @@ export { default as HeaderSearchBoxes } from './LinearGenomeView/components/Head
85
85
  export { default as HeaderSearchBoxRow } from './LinearGenomeView/components/HeaderSearchBoxRow.js';
86
86
  export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/components/useSearchBoxPrefs.js';
87
87
  export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
88
- export { linearGenomeViewPropKeys, partitionLaunchKeys, } from './LinearGenomeView/initKeys.js';
89
88
  export { applyInitHighlights } from './LinearGenomeView/afterAttach.js';
90
89
  export { MultiLevelRubberband } from './MultiLevelRubberband/index.js';
91
90
  export { fetchResults, SearchResultsNotFoundError } from './searchUtils.js';
@@ -99,6 +98,7 @@ export { makeTicks } from './LinearGenomeView/util.js';
99
98
  // The two halves of "show these regions instead": navigate the view you are
100
99
  // looking at, or seed a snapshot for a new one. A caller deriving either itself
101
100
  // lands on a viewport property the model no longer reads — see each for how.
101
+ export { containingLgv } from './LinearGenomeView/containingLgv.js';
102
102
  export { showRegionsWithUndo } from './LinearGenomeView/showRegionsWithUndo.js';
103
103
  export { fitAllRegionsWindow } from './LinearGenomeView/util.js';
104
104
  export { renderToSvg } from './LinearGenomeView/svgcomponents/SVGLinearGenomeView.js';
@@ -125,8 +125,7 @@ export { default as HoverPositionHighlight } from './LinearGenomeView/components
125
125
  // mounts it, and so does an embedder mounting `RenderingComponent` directly —
126
126
  // who otherwise supplies no node at all and gets display chrome buried under
127
127
  // their own region masks.
128
- export { TrackOverlayContext, TrackOverlayPortal, TrackOverlaySlot, } from '@jbrowse/display-ui';
129
- export { FloatingSvgOverlay } from './LinearGenomeView/FloatingSvgOverlay.js';
128
+ export { FloatingSvgOverlay, TrackOverlayContext, TrackOverlayPortal, TrackOverlaySlot, } from '@jbrowse/display-ui';
130
129
  export { SvgChrome, SvgClipRect } from '@jbrowse/core/svg/SvgExport';
131
130
  // for a display that hand-rolls a <clipPath> instead of using SvgClipRect (an
132
131
  // inset or non-rect clip); ids built from a trackId/refName need it
@@ -32,28 +32,31 @@ export declare function unanimousResult({ results, model, session, assembly, }:
32
32
  session: TrackCatalog;
33
33
  assembly?: Assembly;
34
34
  }): BaseResult | undefined;
35
- export declare function showSearchResults({ results, query, model, assemblyName, grow, }: {
35
+ export declare function showSearchResults({ results, query, model, assemblyName, grow, showHitTrack, }: {
36
36
  results: BaseResult[];
37
37
  query: string;
38
38
  model: LinearGenomeViewModel;
39
39
  assemblyName: string;
40
40
  grow?: number;
41
+ showHitTrack?: boolean;
41
42
  }): Promise<boolean>;
42
- export declare function navToOption({ option, model, assemblyName, grow, }: {
43
+ export declare function navToOption({ option, model, assemblyName, grow, showHitTrack, }: {
43
44
  model: LinearGenomeViewModel;
44
45
  option: BaseResult;
45
46
  assemblyName: string;
46
47
  grow?: number;
48
+ showHitTrack?: boolean;
47
49
  }): Promise<void>;
48
50
  export declare class SearchResultsNotFoundError extends Error {
49
51
  name: string;
50
52
  }
51
53
  export declare function notifySearchFailure(session: NotificationSink, e: unknown): void;
52
- export declare function handleSelectedRegion({ input, model, assemblyName, grow, }: {
54
+ export declare function handleSelectedRegion({ input, model, assemblyName, grow, showHitTrack, }: {
53
55
  input: string;
54
56
  model: LinearGenomeViewModel;
55
57
  assemblyName: string;
56
58
  grow?: number;
59
+ showHitTrack?: boolean;
57
60
  }): Promise<boolean>;
58
61
  export declare function checkRef(str: string, isRef: (name: string) => boolean): boolean;
59
62
  export declare function fetchResults({ queryString, searchType, assemblyName, textSearchManager, assembly, stopToken, }: {
@@ -86,6 +86,24 @@ function trackRank(result, model, session) {
86
86
  return session.getTrackById(trackId) ? 1 : 3;
87
87
  }
88
88
  }
89
+ // The pick is a stable minimum, so a linear scan rather than a sort: only the
90
+ // best rung is taken, and keeping the first strictly-lowest one is what leaves
91
+ // an agreeing group with nothing to choose between in the order the ranking
92
+ // handed it. Ranked once per hit for the reason `destinations` above it is:
93
+ // `trackRank` reaches `session.getTrackById`, and a comparator asks O(n log n)
94
+ // times for an answer that does not vary.
95
+ function bestRanked(results, model, session) {
96
+ let best = results[0];
97
+ let bestRank = trackRank(best, model, session);
98
+ for (let i = 1; i < results.length; i++) {
99
+ const rank = trackRank(results[i], model, session);
100
+ if (rank < bestRank) {
101
+ best = results[i];
102
+ bestRank = rank;
103
+ }
104
+ }
105
+ return best;
106
+ }
89
107
  // The picker exists to disambiguate, and hits that name one feature at one
90
108
  // place are not ambiguous — they are several indexes having found it. An
91
109
  // instance carrying a handful of gene tracks turns every gene search into a
@@ -97,16 +115,12 @@ export function unanimousResult({ results, model, session, assembly, }) {
97
115
  const destinations = results.map(r => destination(r, assembly));
98
116
  const agree = destinations[0] !== undefined &&
99
117
  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;
118
+ return agree ? bestRanked(results, model, session) : undefined;
105
119
  }
106
120
  // Every multi-hit result set reaches the picker through here, so the two
107
121
  // search surfaces cannot disagree about when one is worth raising. Returns
108
122
  // whether the view moved.
109
- export async function showSearchResults({ results, query, model, assemblyName, grow, }) {
123
+ export async function showSearchResults({ results, query, model, assemblyName, grow, showHitTrack, }) {
110
124
  const session = getSession(model);
111
125
  const unanimous = unanimousResult({
112
126
  results,
@@ -115,7 +129,13 @@ export async function showSearchResults({ results, query, model, assemblyName, g
115
129
  assembly: session.assemblyManager.get(assemblyName),
116
130
  });
117
131
  if (unanimous) {
118
- await navToOption({ option: unanimous, model, assemblyName, grow });
132
+ await navToOption({
133
+ option: unanimous,
134
+ model,
135
+ assemblyName,
136
+ grow,
137
+ showHitTrack,
138
+ });
119
139
  return true;
120
140
  }
121
141
  else {
@@ -127,7 +147,11 @@ export async function showSearchResults({ results, query, model, assemblyName, g
127
147
  // own edges, which is what this default is; a caller that asked for a specific
128
148
  // padding gets that instead.
129
149
  const SEARCH_HIT_GROW = 0.2;
130
- export async function navToOption({ option, model, assemblyName, grow, }) {
150
+ // A hit lands with the track its index came from shown, which is what a name
151
+ // typed into the search box wants. A session spec that named its own tracks
152
+ // passes false: it asked for those tracks, and a hosted config's full RefSeq
153
+ // appearing beside the curated one it listed read as a bug.
154
+ export async function navToOption({ option, model, assemblyName, grow, showHitTrack = true, }) {
131
155
  // getLocation() can be an empty string when a result reports hasLocation()
132
156
  // but carries no coordinates; treat that as "no location" and fall back to
133
157
  // the label rather than forwarding '' into an empty, view-blanking parse
@@ -136,8 +160,8 @@ export async function navToOption({ option, model, assemblyName, grow, }) {
136
160
  const session = getSession(model);
137
161
  const { assemblyManager } = session;
138
162
  await model.navToLocations(parseLocStrings(location, assemblyName, (ref, asm) => assemblyManager.isValidRefName(ref, asm)), assemblyName, grow ?? SEARCH_HIT_GROW);
139
- if (trackId && isAlive(model)) {
140
- model.showTrack(trackId);
163
+ if (showHitTrack && trackId && isAlive(model)) {
164
+ await model.launchTrack(trackId);
141
165
  }
142
166
  // same detach hazard as showTrack above: navToLocations awaits, and handlers
143
167
  // read off the view (the canvas one calls getSession(model) and model.tracks),
@@ -174,7 +198,7 @@ export function notifySearchFailure(session, e) {
174
198
  // about where to go and so raise the picker, and a view detached while the
175
199
  // search RPC ran — and each of them resolves, so a caller awaiting this cannot
176
200
  // otherwise tell "showing it" from "asked you which one".
177
- export async function handleSelectedRegion({ input, model, assemblyName, grow, }) {
201
+ export async function handleSelectedRegion({ input, model, assemblyName, grow, showHitTrack, }) {
178
202
  const { assemblyManager, textSearchManager } = getSession(model);
179
203
  // resolves only once regions/aliases are loaded, which isValidRefName needs
180
204
  // (it throws otherwise). A load failure is not this function's to report:
@@ -260,6 +284,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
260
284
  model,
261
285
  assemblyName,
262
286
  grow,
287
+ showHitTrack,
263
288
  });
264
289
  }
265
290
  else if (results.length === 1) {
@@ -272,6 +297,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
272
297
  model,
273
298
  assemblyName,
274
299
  grow,
300
+ showHitTrack,
275
301
  });
276
302
  return true;
277
303
  }
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.3",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 linear genome view",
6
6
  "keywords": [
@@ -22,14 +22,14 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@jbrowse/mobx-state-tree": "^6.5.1",
25
- "@mui/icons-material": "^9.3.1",
26
- "@mui/material": "^9.3.1",
25
+ "@mui/icons-material": "^9.4.0",
26
+ "@mui/material": "^9.4.0",
27
27
  "mobx": "^7.0.3",
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.3",
30
+ "@jbrowse/display-kit": "^5.0.0-beta.3",
31
+ "@jbrowse/display-ui": "^5.0.0-beta.3",
32
+ "@jbrowse/render-core": "^5.0.0-beta.3"
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.3"
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
- }