@jbrowse/core 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 (199) hide show
  1. package/README.md +112 -43
  2. package/esm/BaseFeatureWidget/BaseFeatureDetail/FeatureDetails.d.ts +2 -3
  3. package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.d.ts +4 -0
  4. package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.js +17 -0
  5. package/esm/BaseFeatureWidget/BaseFeatureDetail/index.js +4 -3
  6. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.d.ts +2 -2
  7. package/esm/BaseFeatureWidget/SequenceFeatureDetails/test_data/NCDN.d.ts +2 -2
  8. package/esm/BaseFeatureWidget/index.d.ts +1 -1
  9. package/esm/BaseFeatureWidget/stateModelFactory.d.ts +8 -1
  10. package/esm/BaseFeatureWidget/stateModelFactory.js +6 -0
  11. package/esm/BaseFeatureWidget/types.d.ts +10 -2
  12. package/esm/PluginLoader.d.ts +1 -0
  13. package/esm/PluginLoader.js +52 -8
  14. package/esm/PluginManager.d.ts +99 -17
  15. package/esm/PluginManager.js +194 -26
  16. package/esm/ReExports/BaseFeatureDetails.js +1 -1
  17. package/esm/ReExports/MuiDataGridReExports.d.ts +1 -0
  18. package/esm/ReExports/MuiDataGridReExports.js +2 -2
  19. package/esm/ReExports/barrelOnlyNames.d.ts +5 -0
  20. package/esm/ReExports/barrelOnlyNames.js +63 -0
  21. package/esm/ReExports/modules.js +31 -109
  22. package/esm/ReExports/publicTracks.d.ts +1 -0
  23. package/esm/ReExports/publicTracks.js +14 -0
  24. package/esm/ReExports/publicUi.d.ts +4 -0
  25. package/esm/ReExports/publicUi.js +33 -0
  26. package/esm/ReExports/publicUtil.d.ts +1 -1
  27. package/esm/ReExports/publicUtil.js +8 -4
  28. package/esm/ReExports/sharedModules.d.ts +96 -0
  29. package/esm/ReExports/sharedModules.js +66 -0
  30. package/esm/ReExports/uiStub.d.ts +2 -0
  31. package/esm/ReExports/uiStub.js +11 -0
  32. package/esm/ReExports/workerModules.d.ts +2 -0
  33. package/esm/ReExports/workerModules.js +15 -0
  34. package/esm/ReExports/workerNamespaceNames.d.ts +11 -0
  35. package/esm/ReExports/workerNamespaceNames.js +281 -0
  36. package/esm/configuration/configurationSchema.d.ts +5 -38
  37. package/esm/configuration/configurationSchema.js +64 -40
  38. package/esm/configuration/index.d.ts +2 -2
  39. package/esm/configuration/index.js +1 -1
  40. package/esm/configuration/promotableDefaults.d.ts +54 -19
  41. package/esm/configuration/promotableDefaults.js +117 -48
  42. package/esm/configuration/promotablePin.d.ts +50 -40
  43. package/esm/configuration/promotableResolve.d.ts +1 -1
  44. package/esm/configuration/promotableResolve.js +1 -1
  45. package/esm/configuration/tabixIndexFields.d.ts +2 -2
  46. package/esm/configuration/tabixIndexFields.js +2 -2
  47. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.d.ts +12 -0
  48. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +35 -4
  49. package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +41 -0
  50. package/esm/data_adapters/BaseAdapter/featureDensity.js +32 -0
  51. package/esm/data_adapters/BaseAdapter/index.d.ts +2 -1
  52. package/esm/data_adapters/BaseAdapter/index.js +1 -0
  53. package/esm/data_adapters/BaseAdapter/stats.d.ts +33 -1
  54. package/esm/data_adapters/BaseAdapter/stats.js +52 -13
  55. package/esm/data_adapters/BaseAdapter/types.d.ts +3 -8
  56. package/esm/pluggableElementTypes/AdapterType.d.ts +2 -1
  57. package/esm/pluggableElementTypes/AdapterType.js +1 -1
  58. package/esm/pluggableElementTypes/DisplayType.d.ts +4 -12
  59. package/esm/pluggableElementTypes/DisplayType.js +3 -10
  60. package/esm/pluggableElementTypes/LazyStateModelElement.d.ts +27 -0
  61. package/esm/pluggableElementTypes/LazyStateModelElement.js +71 -0
  62. package/esm/pluggableElementTypes/ViewType.d.ts +20 -4
  63. package/esm/pluggableElementTypes/ViewType.js +32 -4
  64. package/esm/pluggableElementTypes/extendElementType.d.ts +1 -2
  65. package/esm/pluggableElementTypes/extendElementType.js +9 -5
  66. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +44 -3
  67. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +75 -2
  68. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +21 -0
  69. package/esm/pluggableElementTypes/models/BaseTrackModel.js +34 -2
  70. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +49 -0
  71. package/esm/pluggableElementTypes/models/BaseViewModel.js +28 -0
  72. package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +14 -10
  73. package/esm/pluggableElementTypes/models/assertDisplayContract.js +25 -32
  74. package/esm/pluggableElementTypes/models/baseTrackConfig.js +7 -1
  75. package/esm/rpc/RpcRegistry.d.ts +10 -0
  76. package/esm/rpc/byteBudget.d.ts +9 -0
  77. package/esm/rpc/coreRpcMethods.d.ts +1 -0
  78. package/esm/rpc/coreRpcMethods.js +1 -0
  79. package/esm/rpc/methods/CoreGetFeatureDensity.d.ts +11 -0
  80. package/esm/rpc/methods/CoreGetFeatureDensity.js +22 -0
  81. package/esm/svg/svgReady.d.ts +1 -0
  82. package/esm/ui/BaseExportSvgDialog.d.ts +1 -1
  83. package/esm/ui/CascadingMenu.js +30 -8
  84. package/esm/ui/ErrorMessageStackTraceDialog.js +5 -6
  85. package/esm/ui/ExportSvgDialog.d.ts +1 -1
  86. package/esm/ui/LoadingOverlay.d.ts +14 -0
  87. package/esm/ui/LoadingOverlay.js +1 -1
  88. package/esm/ui/MenuTypes.d.ts +14 -5
  89. package/esm/ui/MenuTypes.js +3 -0
  90. package/esm/ui/PaletteContext.d.ts +11 -4
  91. package/esm/ui/PaletteContext.js +34 -5
  92. package/esm/ui/PinAdornment.js +31 -43
  93. package/esm/ui/ScrollChrome.d.ts +16 -0
  94. package/esm/ui/ScrollChrome.js +12 -0
  95. package/esm/ui/ScrollZoomToggle.d.ts +4 -3
  96. package/esm/ui/ScrollZoomToggle.js +4 -3
  97. package/esm/ui/SizeSliderRow.js +1 -1
  98. package/esm/ui/index.d.ts +3 -3
  99. package/esm/ui/index.js +4 -4
  100. package/esm/ui/launchViewMenu.js +7 -1
  101. package/esm/ui/legendMenuItem.d.ts +10 -16
  102. package/esm/ui/legendMenuItem.js +9 -20
  103. package/esm/ui/makeSizeMenu.js +1 -1
  104. package/esm/ui/menuItemAdornment.d.ts +2 -3
  105. package/esm/ui/menuItemAdornment.js +2 -3
  106. package/esm/ui/menuItems.d.ts +2 -3
  107. package/esm/ui/menuItems.js +1 -2
  108. package/esm/ui/palette.d.ts +11 -11
  109. package/esm/ui/palette.js +11 -11
  110. package/esm/ui/promotablePinCoverage.d.ts +16 -2
  111. package/esm/ui/promotablePinCoverage.js +40 -3
  112. package/esm/ui/theme.js +5 -0
  113. package/esm/ui/toggleMenuItems.d.ts +14 -9
  114. package/esm/ui/toggleMenuItems.js +32 -15
  115. package/esm/util/Base1DUtils.d.ts +4 -2
  116. package/esm/util/Base1DUtils.js +12 -4
  117. package/esm/util/SvgCanvas.js +4 -3
  118. package/esm/util/adapterConfigKey.d.ts +12 -0
  119. package/esm/util/adapterConfigKey.js +14 -0
  120. package/esm/util/adapterMetadata.js +2 -1
  121. package/esm/util/addAndShowTrack.d.ts +1 -1
  122. package/esm/util/addAndShowTrack.js +5 -1
  123. package/esm/util/addTrackFromWidget.js +2 -2
  124. package/esm/util/analytics.js +4 -5
  125. package/esm/util/cacheBudgets.d.ts +22 -19
  126. package/esm/util/cacheBudgets.js +22 -19
  127. package/esm/util/clampToContig.d.ts +17 -0
  128. package/esm/util/clampToContig.js +19 -0
  129. package/esm/util/colorBits.d.ts +2 -1
  130. package/esm/util/colorBits.js +2 -5
  131. package/esm/util/createStopTokenRotation.d.ts +9 -7
  132. package/esm/util/createStopTokenRotation.js +7 -27
  133. package/esm/util/featureTypes.d.ts +24 -0
  134. package/esm/util/featureTypes.js +35 -0
  135. package/esm/util/fetchContext.d.ts +2 -2
  136. package/esm/util/fetchContext.js +2 -2
  137. package/esm/util/fetchPhases.d.ts +10 -10
  138. package/esm/util/hooks.d.ts +8 -0
  139. package/esm/util/hooks.js +27 -0
  140. package/esm/util/index.d.ts +6 -5
  141. package/esm/util/index.js +5 -4
  142. package/esm/util/installFetch.d.ts +71 -34
  143. package/esm/util/installFetch.js +57 -29
  144. package/esm/util/installInitAutorun.d.ts +14 -13
  145. package/esm/util/installInitAutorun.js +17 -17
  146. package/esm/util/installPrerequisiteFetch.d.ts +86 -0
  147. package/esm/util/installPrerequisiteFetch.js +73 -0
  148. package/esm/util/lazyAfterAttach.d.ts +13 -0
  149. package/esm/util/lazyAfterAttach.js +31 -0
  150. package/esm/util/legendCandidates.d.ts +50 -0
  151. package/esm/util/legendCandidates.js +87 -0
  152. package/esm/util/mstUtils.d.ts +12 -1
  153. package/esm/util/mstUtils.js +20 -2
  154. package/esm/util/openFeatureWidget.d.ts +5 -0
  155. package/esm/util/openFeatureWidget.js +1 -0
  156. package/esm/util/openViews.d.ts +16 -0
  157. package/esm/util/openViews.js +22 -0
  158. package/esm/util/parentWalk.d.ts +1 -1
  159. package/esm/util/parentWalk.js +1 -1
  160. package/esm/util/pluginStore.d.ts +1 -0
  161. package/esm/util/pluginStore.js +8 -1
  162. package/esm/util/range.d.ts +13 -9
  163. package/esm/util/range.js +17 -8
  164. package/esm/util/renderToStaticMarkup.js +10 -0
  165. package/esm/util/rxjs.d.ts +16 -0
  166. package/esm/util/rxjs.js +49 -0
  167. package/esm/util/scheduleDetachedDestroy.d.ts +18 -0
  168. package/esm/util/scheduleDetachedDestroy.js +24 -0
  169. package/esm/util/scrollLatch.d.ts +1 -0
  170. package/esm/util/scrollLatch.js +8 -0
  171. package/esm/util/stopToken.d.ts +6 -5
  172. package/esm/util/tabix.d.ts +10 -4
  173. package/esm/util/tabix.js +11 -5
  174. package/esm/util/trackConfigDelta.d.ts +6 -6
  175. package/esm/util/trackConfigDelta.js +3 -3
  176. package/esm/util/tracks.d.ts +48 -0
  177. package/esm/util/tracks.js +192 -66
  178. package/esm/util/types/index.d.ts +58 -10
  179. package/esm/util/types/index.js +3 -0
  180. package/esm/util/unknownSnapshotKeys.d.ts +36 -0
  181. package/esm/util/unknownSnapshotKeys.js +69 -0
  182. package/esm/util/usePanelVirtualScroll.d.ts +6 -2
  183. package/esm/util/usePanelVirtualScroll.js +6 -2
  184. package/esm/util/useRowVirtualScroll.d.ts +1 -1
  185. package/esm/util/useRowVirtualScroll.js +1 -1
  186. package/esm/util/useVirtualScrollWheel.js +11 -1
  187. package/esm/util/viewStatus.d.ts +23 -0
  188. package/esm/util/viewStatus.js +17 -0
  189. package/esm/util/wheelZoom.d.ts +3 -1
  190. package/esm/util/wheelZoom.js +39 -14
  191. package/esm/util/whenViewSettled.d.ts +17 -4
  192. package/esm/util/whenViewSettled.js +19 -6
  193. package/esm/util/withLaunchInput.d.ts +168 -0
  194. package/esm/util/withLaunchInput.js +273 -0
  195. package/package.json +19 -11
  196. package/esm/ui/promotableMenuItems.d.ts +0 -19
  197. package/esm/ui/promotableMenuItems.js +0 -89
  198. package/esm/util/applyInitSettings.d.ts +0 -79
  199. package/esm/util/applyInitSettings.js +0 -91
@@ -19,13 +19,16 @@ export function aggregateQuantitativeStats(stats) {
19
19
  ...(meanMaxs.length > 0 ? { scoreMeanMax: max(meanMaxs) } : {}),
20
20
  });
21
21
  }
22
- // A sample window of `interval` bp centered ~25% into the region, so the sample
23
- // avoids the region edges where coverage often tapers. Clamped to the region
24
- // bounds so it never samples features outside the region being measured (the
25
- // left edge clamps to the region start, not to the chromosome origin).
26
- function sampleWindow(region, interval) {
22
+ // Where the probe samples, as a fraction into the region. The primary point
23
+ // avoids the region edges, where coverage often tapers.
24
+ const DENSITY_SAMPLE_POINT = 0.25;
25
+ const DENSITY_CONFIRM_POINT = 0.7;
26
+ // A sample window of `interval` bp centered `at` into the region. Clamped to the
27
+ // region bounds so it never samples features outside the region being measured
28
+ // (the left edge clamps to the region start, not to the chromosome origin).
29
+ function sampleWindow(region, interval, at) {
27
30
  const { start, end } = region;
28
- const sampleCenter = start * 0.75 + end * 0.25;
31
+ const sampleCenter = start + (end - start) * at;
29
32
  return {
30
33
  start: Math.max(start, Math.round(sampleCenter - interval / 2)),
31
34
  end: Math.min(end, Math.round(sampleCenter + interval / 2)),
@@ -49,20 +52,56 @@ function sampleWindow(region, interval) {
49
52
  // count would make a selective filter double the window over and over until it
50
53
  // spanned the whole region — turning the cheap pre-fetch probe into a second
51
54
  // full download for exactly the views that need it most.
52
- export async function calculateFeatureDensityStats(region, getFeatures, opts, admit) {
55
+ export async function calculateFeatureDensityStats(region, getFeatures, { admit, gate, ...opts } = {}) {
53
56
  const refLen = region.end - region.start;
54
57
  const t0 = performance.now();
55
- let interval = DENSITY_SAMPLE_INITIAL_INTERVAL;
58
+ let interval = Math.max(DENSITY_SAMPLE_INITIAL_INTERVAL, gate?.initialInterval ?? 0);
59
+ const sampleAt = async (at) => {
60
+ const { start, end } = sampleWindow(region, interval, at);
61
+ const features = await firstValueFrom(getFeatures({ ...region, start, end }, opts).pipe(toArray()));
62
+ return {
63
+ sampledBp: end - start,
64
+ raw: features.length,
65
+ admitted: admit ? features.filter(admit).length : features.length,
66
+ };
67
+ };
56
68
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
57
69
  while (true) {
58
- const { start, end } = sampleWindow(region, interval);
59
- const sampledBp = end - start;
60
- const features = await firstValueFrom(getFeatures({ ...region, start, end }, opts).pipe(toArray()));
61
- const admitted = admit ? features.filter(admit).length : features.length;
70
+ const { sampledBp, raw, admitted } = await sampleAt(DENSITY_SAMPLE_POINT);
71
+ // The verdict is already decided by a margin, so a precise density would
72
+ // only make it more decided. Tested on the admitted count, which is the
73
+ // population the caller judges — the raw count below governs window growth
74
+ // for the reason in the note above, and the two must not be swapped.
75
+ //
76
+ // A second point has to agree before the probe answers a verdict the old
77
+ // ladder could not have reached — and `raw` is exactly what separates the
78
+ // two. A window already holding DENSITY_SAMPLE_MIN_FEATURES is one the exit
79
+ // below would have answered from anyway, so there is nothing new to confirm
80
+ // and this falls through to it for the same number. Under that count the
81
+ // ladder would have kept widening, diluting a local cluster, and answering
82
+ // here instead is what lets a sparse track with a cluster at the fixed
83
+ // sample point read as dense and banner at that zoom until the user
84
+ // force-loads.
85
+ //
86
+ // So the confirmation is owed only by the case that is new, which is also
87
+ // the case that is cheap: every dense annotation track measured clears
88
+ // DENSITY_SAMPLE_MIN_FEATURES in its first window and pays nothing. A
89
+ // disagreement is not a verdict — the ladder simply carries on.
90
+ if (gate?.settled(admitted, sampledBp) &&
91
+ raw < DENSITY_SAMPLE_MIN_FEATURES) {
92
+ const other = await sampleAt(DENSITY_CONFIRM_POINT);
93
+ if (gate.settled(other.admitted, other.sampledBp)) {
94
+ // the lower of two agreeing readings, so the count the banner quotes is
95
+ // never the high one of a pair
96
+ return {
97
+ featureDensity: Math.min(admitted / sampledBp, other.admitted / other.sampledBp),
98
+ };
99
+ }
100
+ }
62
101
  // Enough features to be meaningful, or the window already spans the whole
63
102
  // region (growing further can't sample more) — report density over the bp
64
103
  // actually sampled, not the nominal interval.
65
- if (features.length >= DENSITY_SAMPLE_MIN_FEATURES || sampledBp >= refLen) {
104
+ if (raw >= DENSITY_SAMPLE_MIN_FEATURES || sampledBp >= refLen) {
66
105
  return { featureDensity: admitted / sampledBp };
67
106
  }
68
107
  // Sparse region or slow adapter: sampling ran past the time budget without
@@ -1,21 +1,16 @@
1
1
  import type { StatusCallback } from '../../util/progress.ts';
2
2
  import type { StopToken } from '../../util/stopToken.ts';
3
- import type { Region } from '../../util/types/index.ts';
3
+ export type LodTier = 'fine' | 'coarse';
4
4
  export interface BaseOptions {
5
5
  stopToken?: StopToken;
6
6
  bpPerPx?: number;
7
7
  sessionId?: string;
8
- trackInstanceId?: string;
9
8
  signal?: AbortSignal;
10
9
  statusCallback?: StatusCallback;
11
10
  headers?: Record<string, string>;
12
- statsEstimationMode?: boolean;
13
11
  assemblyName?: string;
14
- targetAssemblyName?: string;
15
- lodMode?: 'fine' | 'coarse';
16
- }
17
- export interface BaseOptionsWithRegions extends BaseOptions {
18
- regions?: Region[];
12
+ lodMode?: LodTier;
13
+ topLevelOnly?: boolean;
19
14
  }
20
15
  export type SearchType = 'full' | 'prefix' | 'exact';
21
16
  export interface BaseTextSearchArgs {
@@ -42,7 +42,7 @@ export default class AdapterType extends PluggableElementBase {
42
42
  * declaring the shorthand once gets both. It used to be a second, separate
43
43
  * registration, and five in-tree adapters had only the schema half
44
44
  * (`MafTabixAdapter`, `BgzipMafAdapter`, `BgzipTaffyAdapter`,
45
- * `AllVsAllPAFAdapter`, `MCScanBlocksAdapter`) — which reads as working,
45
+ * `MultiGenomePAFAdapter`, `MCScanBlocksAdapter`) — which reads as working,
46
46
  * because loading such a config from a URL goes through the schema. Only
47
47
  * `normalizeAdapterSnapshots` consults *this*, so what broke was `localFiles`
48
48
  * in the embedded products: the shorthand stayed unexpanded, `uri` never
@@ -63,6 +63,7 @@ export default class AdapterType extends PluggableElementBase {
63
63
  name: string;
64
64
  configSchema: AnyConfigurationSchemaType;
65
65
  displayName?: string;
66
+ aliases?: string[];
66
67
  adapterCapabilities?: string[];
67
68
  adapterMetadata?: AdapterMetadata;
68
69
  normalizeSnapshot?: NormalizeSnapshot;
@@ -17,7 +17,7 @@ export default class AdapterType extends PluggableElementBase {
17
17
  * declaring the shorthand once gets both. It used to be a second, separate
18
18
  * registration, and five in-tree adapters had only the schema half
19
19
  * (`MafTabixAdapter`, `BgzipMafAdapter`, `BgzipTaffyAdapter`,
20
- * `AllVsAllPAFAdapter`, `MCScanBlocksAdapter`) — which reads as working,
20
+ * `MultiGenomePAFAdapter`, `MCScanBlocksAdapter`) — which reads as working,
21
21
  * because loading such a config from a URL goes through the schema. Only
22
22
  * `normalizeAdapterSnapshots` consults *this*, so what broke was `localFiles`
23
23
  * in the embedded products: the shorthand stayed unexpanded, `uri` never
@@ -1,19 +1,15 @@
1
- import PluggableElementBase from './PluggableElementBase.ts';
1
+ import LazyStateModelElement from './LazyStateModelElement.ts';
2
2
  import type { AnyConfigurationSchemaType } from '../configuration/index.ts';
3
3
  import type { AnyReactComponentType } from '../util/index.ts';
4
4
  import type { IAnyModelType } from '@jbrowse/mobx-state-tree';
5
- export default class DisplayType extends PluggableElementBase {
6
- stateModel: IAnyModelType;
5
+ export default class DisplayType extends LazyStateModelElement {
6
+ protected readonly group = "display";
7
7
  configSchema: AnyConfigurationSchemaType;
8
8
  ReactComponent: AnyReactComponentType;
9
9
  /**
10
10
  * The track type the display is associated with
11
11
  */
12
12
  trackType: string;
13
- subDisplay?: {
14
- type: string;
15
- [key: string]: unknown;
16
- };
17
13
  /**
18
14
  * The view type the display is associated with
19
15
  */
@@ -24,14 +20,10 @@ export default class DisplayType extends PluggableElementBase {
24
20
  helpText?: string;
25
21
  constructor(stuff: {
26
22
  name: string;
27
- stateModel: IAnyModelType;
23
+ stateModel: IAnyModelType | (() => Promise<IAnyModelType>);
28
24
  trackType: string;
29
25
  viewType: string;
30
26
  displayName?: string;
31
- subDisplay?: {
32
- type: string;
33
- [key: string]: unknown;
34
- };
35
27
  configSchema: AnyConfigurationSchemaType;
36
28
  ReactComponent: AnyReactComponentType;
37
29
  helpText?: string;
@@ -1,17 +1,12 @@
1
- import PluggableElementBase from './PluggableElementBase.js';
2
- export default class DisplayType extends PluggableElementBase {
3
- stateModel;
1
+ import LazyStateModelElement from './LazyStateModelElement.js';
2
+ export default class DisplayType extends LazyStateModelElement {
3
+ group = 'display';
4
4
  configSchema;
5
5
  ReactComponent;
6
6
  /**
7
7
  * The track type the display is associated with
8
8
  */
9
9
  trackType;
10
- /*
11
- * Indicates that this display type can be a "sub-display" of another type of
12
- * display, e.g. in AlignmentsDisplay, has Pileup and SNPCoverage subDisplays
13
- */
14
- subDisplay;
15
10
  /**
16
11
  * The view type the display is associated with
17
12
  */
@@ -22,8 +17,6 @@ export default class DisplayType extends PluggableElementBase {
22
17
  helpText;
23
18
  constructor(stuff) {
24
19
  super(stuff);
25
- this.stateModel = stuff.stateModel;
26
- this.subDisplay = stuff.subDisplay;
27
20
  this.configSchema = stuff.configSchema;
28
21
  this.ReactComponent = stuff.ReactComponent;
29
22
  this.trackType = stuff.trackType;
@@ -0,0 +1,27 @@
1
+ import PluggableElementBase from './PluggableElementBase.ts';
2
+ import type { IAnyModelType } from '@jbrowse/mobx-state-tree';
3
+ /**
4
+ * A pluggable element whose state model may arrive through a dynamic import.
5
+ * Code that can run before any instance exists — session preloading, pruning,
6
+ * union membership — checks `isStateModelLoaded` before reading `stateModel`.
7
+ */
8
+ export default abstract class LazyStateModelElement extends PluggableElementBase {
9
+ protected abstract readonly group: 'view' | 'display';
10
+ private loadedStateModel?;
11
+ private stateModelPromise?;
12
+ private pendingStateModelExtensions;
13
+ stateModelLoader?: () => Promise<IAnyModelType>;
14
+ onStateModelLoaded?: () => void;
15
+ constructor(stuff: {
16
+ name: string;
17
+ displayName?: string;
18
+ aliases?: string[];
19
+ stateModel: IAnyModelType | (() => Promise<IAnyModelType>);
20
+ });
21
+ get stateModel(): IAnyModelType;
22
+ set stateModel(stateModel: IAnyModelType);
23
+ get isStateModelLoaded(): boolean;
24
+ assertStateModelLoaded(): void;
25
+ loadStateModel(): Promise<IAnyModelType>;
26
+ extendStateModel(extend: (stateModel: IAnyModelType) => IAnyModelType): void;
27
+ }
@@ -0,0 +1,71 @@
1
+ import PluggableElementBase from './PluggableElementBase.js';
2
+ /**
3
+ * A pluggable element whose state model may arrive through a dynamic import.
4
+ * Code that can run before any instance exists — session preloading, pruning,
5
+ * union membership — checks `isStateModelLoaded` before reading `stateModel`.
6
+ */
7
+ export default class LazyStateModelElement extends PluggableElementBase {
8
+ loadedStateModel;
9
+ stateModelPromise;
10
+ pendingStateModelExtensions = [];
11
+ // named `stateModel` + `Loader` because pluggableMstType probes
12
+ // `${fieldName}Loader` generically
13
+ stateModelLoader;
14
+ // set by PluginManager.addElementType: runs Core-extendPluggableElement once
15
+ // the loader resolves, so a callback reading `stateModel` sees a model
16
+ onStateModelLoaded;
17
+ constructor(stuff) {
18
+ super(stuff);
19
+ if (typeof stuff.stateModel === 'function') {
20
+ this.stateModelLoader = stuff.stateModel;
21
+ }
22
+ else {
23
+ this.loadedStateModel = stuff.stateModel;
24
+ }
25
+ }
26
+ // Throws before the loader resolves, so a plugin composing an unloaded
27
+ // model fails here rather than as an opaque MST error.
28
+ get stateModel() {
29
+ this.assertStateModelLoaded();
30
+ return this.loadedStateModel;
31
+ }
32
+ set stateModel(stateModel) {
33
+ this.loadedStateModel = stateModel;
34
+ }
35
+ get isStateModelLoaded() {
36
+ return this.loadedStateModel !== undefined;
37
+ }
38
+ assertStateModelLoaded() {
39
+ if (this.loadedStateModel === undefined) {
40
+ const getter = this.group === 'view' ? 'getViewType' : 'getDisplayType';
41
+ throw new Error(`state model for ${this.group} type ${this.name} is not loaded yet — await ${getter}('${this.name}').loadStateModel() first`);
42
+ }
43
+ }
44
+ loadStateModel() {
45
+ if (this.loadedStateModel !== undefined) {
46
+ return Promise.resolve(this.loadedStateModel);
47
+ }
48
+ if (!this.stateModelLoader) {
49
+ throw new Error(`${this.group} type ${this.name} has neither a state model nor a loader`);
50
+ }
51
+ this.stateModelPromise ??= this.stateModelLoader().then(loaded => {
52
+ let stateModel = loaded;
53
+ for (const extend of this.pendingStateModelExtensions) {
54
+ stateModel = extend(stateModel);
55
+ }
56
+ this.pendingStateModelExtensions = [];
57
+ this.loadedStateModel = stateModel;
58
+ this.onStateModelLoaded?.();
59
+ return this.stateModel;
60
+ });
61
+ return this.stateModelPromise;
62
+ }
63
+ extendStateModel(extend) {
64
+ if (this.loadedStateModel !== undefined) {
65
+ this.loadedStateModel = extend(this.loadedStateModel);
66
+ }
67
+ else {
68
+ this.pendingStateModelExtensions.push(extend);
69
+ }
70
+ }
71
+ }
@@ -1,4 +1,5 @@
1
- import PluggableElementBase from './PluggableElementBase.ts';
1
+ import LazyStateModelElement from './LazyStateModelElement.ts';
2
+ import type { LaunchKeyRegistration } from '../util/withLaunchInput.ts';
2
3
  import type DisplayType from './DisplayType.ts';
3
4
  import type { IAnyModelType, IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
4
5
  import type React from 'react';
@@ -10,21 +11,36 @@ type ViewComponentType = React.LazyExoticComponent<BasicView> | BasicView;
10
11
  interface ViewMetadata {
11
12
  hiddenFromGUI?: boolean;
12
13
  }
13
- export default class ViewType extends PluggableElementBase {
14
+ export default class ViewType extends LazyStateModelElement {
15
+ protected readonly group = "view";
14
16
  ReactComponent: ViewComponentType;
15
- stateModel: IAnyModelType;
17
+ launchKeys?: LaunchKeyRegistration<unknown, string>;
16
18
  displayTypes: DisplayType[];
17
19
  viewMetadata: ViewMetadata;
18
20
  extendedName?: string;
19
21
  constructor(stuff: {
20
22
  name: string;
21
23
  displayName?: string;
22
- stateModel: IAnyModelType;
24
+ stateModel: IAnyModelType | (() => Promise<IAnyModelType>);
25
+ launchKeys?: LaunchKeyRegistration<unknown, string>;
23
26
  extendedName?: string;
24
27
  viewMetadata?: ViewMetadata;
25
28
  ReactComponent: ViewComponentType;
26
29
  aliases?: string[];
27
30
  });
31
+ /**
32
+ * Every key this view accepts written on the view object — its declared MST
33
+ * properties plus the launch keys above. `undefined` for a view that
34
+ * registers none: nothing there says which of its launcher's arguments are
35
+ * settings, so a caller must classify nothing rather than call them all typos.
36
+ * Also `undefined` while a lazily registered state model is not loaded yet,
37
+ * for the same reason: nothing is known, so nothing is a typo.
38
+ *
39
+ * This is the set `withLaunchInput`'s partition reads off the same two
40
+ * declarations, published for the surfaces that never build a view snapshot —
41
+ * a session spec launches straight through `LaunchView-<type>`.
42
+ */
43
+ get acceptedKeys(): string[] | undefined;
28
44
  addDisplayType(display: DisplayType): void;
29
45
  }
30
46
  export {};
@@ -1,7 +1,11 @@
1
- import PluggableElementBase from './PluggableElementBase.js';
2
- export default class ViewType extends PluggableElementBase {
1
+ import LazyStateModelElement from './LazyStateModelElement.js';
2
+ export default class ViewType extends LazyStateModelElement {
3
+ group = 'view';
3
4
  ReactComponent;
4
- stateModel;
5
+ // What `withLaunchInput` partitioned out of this view's snapshots, published
6
+ // beside the model so an out-of-tree plugin, the doc generator and the
7
+ // validator read one declaration rather than each keeping a list.
8
+ launchKeys;
5
9
  displayTypes = [];
6
10
  viewMetadata = {};
7
11
  // extendedName can be used for when you extend a given view type, and want
@@ -16,9 +20,33 @@ export default class ViewType extends PluggableElementBase {
16
20
  super(stuff);
17
21
  this.ReactComponent = stuff.ReactComponent;
18
22
  this.viewMetadata = stuff.viewMetadata ?? {};
19
- this.stateModel = stuff.stateModel;
23
+ this.launchKeys = stuff.launchKeys;
20
24
  this.extendedName = stuff.extendedName;
21
25
  }
26
+ /**
27
+ * Every key this view accepts written on the view object — its declared MST
28
+ * properties plus the launch keys above. `undefined` for a view that
29
+ * registers none: nothing there says which of its launcher's arguments are
30
+ * settings, so a caller must classify nothing rather than call them all typos.
31
+ * Also `undefined` while a lazily registered state model is not loaded yet,
32
+ * for the same reason: nothing is known, so nothing is a typo.
33
+ *
34
+ * This is the set `withLaunchInput`'s partition reads off the same two
35
+ * declarations, published for the surfaces that never build a view snapshot —
36
+ * a session spec launches straight through `LaunchView-<type>`.
37
+ */
38
+ get acceptedKeys() {
39
+ const properties = this.isStateModelLoaded
40
+ ? this.stateModel.properties
41
+ : undefined;
42
+ return this.launchKeys && properties
43
+ ? [
44
+ ...Object.keys(properties),
45
+ ...Object.keys(this.launchKeys.keys),
46
+ ...this.launchKeys.passThrough,
47
+ ]
48
+ : undefined;
49
+ }
22
50
  addDisplayType(display) {
23
51
  this.displayTypes.push(display);
24
52
  }
@@ -5,8 +5,7 @@ import type { IAnyModelType } from '@jbrowse/mobx-state-tree';
5
5
  * Replace a view type's state model with an extended one, e.g. to add a menu
6
6
  * item to `LinearGenomeView`. Pass an array to extend several at once, which is
7
7
  * how a contribution reaches a family: the tree spells a display family as a
8
- * shared mixin set rather than a chain, so there is no parent to name — see
9
- * `reference/REJECTED_IDEAS.md`, "Give a pluggable element an `extendsType`".
8
+ * shared mixin set rather than a chain, so there is no parent to name, which is why an `extendsType` was declined.
10
9
  *
11
10
  * `Core-extendPluggableElement` fires for every pluggable element there is —
12
11
  * adapters, widgets, RPC methods, all of them — so a callback written against
@@ -14,8 +14,7 @@ function namesOf(name) {
14
14
  * Replace a view type's state model with an extended one, e.g. to add a menu
15
15
  * item to `LinearGenomeView`. Pass an array to extend several at once, which is
16
16
  * how a contribution reaches a family: the tree spells a display family as a
17
- * shared mixin set rather than a chain, so there is no parent to name — see
18
- * `reference/REJECTED_IDEAS.md`, "Give a pluggable element an `extendsType`".
17
+ * shared mixin set rather than a chain, so there is no parent to name, which is why an `extendsType` was declined.
19
18
  *
20
19
  * `Core-extendPluggableElement` fires for every pluggable element there is —
21
20
  * adapters, widgets, RPC methods, all of them — so a callback written against
@@ -45,8 +44,12 @@ export function extendViewType(pluginManager, name, extend) {
45
44
  // point of the signature; widening it once here costs nothing, since
46
45
  // the only operation is handing the model to `extend` and storing what
47
46
  // comes back.
47
+ //
48
+ // Routed through extendStateModel rather than assigning `.stateModel`
49
+ // directly so a view registered with a lazy loader queues the
50
+ // extension and composes it when the loader resolves.
48
51
  const view = element;
49
- view.stateModel = widen(extend)(view.stateModel);
52
+ view.extendStateModel(widen(extend));
50
53
  }
51
54
  return element;
52
55
  });
@@ -59,9 +62,10 @@ export function extendDisplayType(pluginManager, name, extend) {
59
62
  const names = namesOf(name);
60
63
  pluginManager.addToExtensionPoint('Core-extendPluggableElement', (element, props) => {
61
64
  if (props.group === 'display' && names.has(element.name)) {
62
- // widened for the reason in extendViewType
65
+ // widened for the reason in extendViewType; routed through
66
+ // extendStateModel so a lazy registration queues the extension
63
67
  const display = element;
64
- display.stateModel = widen(extend)(display.stateModel);
68
+ display.extendStateModel(widen(extend));
65
69
  }
66
70
  return element;
67
71
  });
@@ -161,7 +161,7 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
161
161
  * pointing at" channel a plugin can subscribe to.
162
162
  *
163
163
  * Declared here because a cross-display consumer can only read a name the
164
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
164
+ * base declares — the same reason `FetchMixin.fetchInert` is a
165
165
  * hook rather than a getter each display invents. The container used to
166
166
  * read `featureUnderMouse`, which only the wiggle, alignments and
167
167
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -382,7 +382,7 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
382
382
  * pointing at" channel a plugin can subscribe to.
383
383
  *
384
384
  * Declared here because a cross-display consumer can only read a name the
385
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
385
+ * base declares — the same reason `FetchMixin.fetchInert` is a
386
386
  * hook rather than a getter each display invents. The container used to
387
387
  * read `featureUnderMouse`, which only the wiggle, alignments and
388
388
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -595,7 +595,7 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
595
595
  * pointing at" channel a plugin can subscribe to.
596
596
  *
597
597
  * Declared here because a cross-display consumer can only read a name the
598
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
598
+ * base declares — the same reason `FetchMixin.fetchInert` is a
599
599
  * hook rather than a getter each display invents. The container used to
600
600
  * read `featureUnderMouse`, which only the wiggle, alignments and
601
601
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -684,6 +684,47 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
684
684
  * base display reload does nothing, see specialized displays for details
685
685
  */
686
686
  reload(): void;
687
+ /**
688
+ * #action
689
+ * Apply a bag of display settings to the LIVE display, and report what
690
+ * landed. Each key runs through the display config schema's
691
+ * `preProcessSnapshot` (shorthand expansions, legacy-key migrations —
692
+ * the same lowering a session spec's inline track keys get in
693
+ * `showTrackGeneric`), then writes the matching config slot. Keys that
694
+ * are not slots come back in `unapplied` rather than vanishing: the
695
+ * settings vocabulary's historical failure mode is the silently dropped
696
+ * key.
697
+ *
698
+ * `allowSetters` additionally routes a non-slot key to a conventionally
699
+ * named single-argument `set<Key>` action. Opt-in, never the default:
700
+ * the declarative surfaces (session specs, share links, embeds) feed
701
+ * this whole bags of untyped JSON, and a blanket fallback would let
702
+ * them reach internal setters (`setError`, `setScrollTop`, ...) and
703
+ * call multi-argument setters with one argument. A caller that wants a
704
+ * specific action can also simply call it.
705
+ *
706
+ * A key whose write THREW is reported separately, in `failed` — it is the
707
+ * only one of the three that means the caller got something wrong, and
708
+ * the only one worth a notification. `unapplied` is not: at the
709
+ * `showTrackGeneric` call site it also collects keys that surface
710
+ * consumed itself (`type`) and MST display props the display snapshot
711
+ * already applied (`resolution`), so treating it as "dropped" would
712
+ * report a correct call as broken.
713
+ *
714
+ * Per-key errors do not abort the rest of the bag — a caller
715
+ * mid-`showTrack` has already pushed the track, and one rejected value
716
+ * must not strand a half-configured track.
717
+ */
718
+ applyDisplaySettings(settings: Record<string, unknown>, options?: {
719
+ allowSetters?: boolean;
720
+ }): {
721
+ applied: string[];
722
+ unapplied: string[];
723
+ failed: {
724
+ key: string;
725
+ error: string;
726
+ }[];
727
+ };
687
728
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
688
729
  export type BaseDisplayStateModel = typeof BaseDisplay;
689
730
  export type BaseDisplayModel = Instance<BaseDisplayStateModel>;
@@ -1,5 +1,5 @@
1
1
  import { hasParent, isAlive, types } from '@jbrowse/mobx-state-tree';
2
- import { getConf } from '../../configuration/index.js';
2
+ import { getConf, isConfigurationSlot, preProcessSlotValues, } from '../../configuration/index.js';
3
3
  import { getContainingTrack, getEnv, statusFraction, statusMessageText, } from '../../util/index.js';
4
4
  import { ElementId } from '../../util/types/mst.js';
5
5
  /**
@@ -94,7 +94,7 @@ function stateModelFactory() {
94
94
  * pointing at" channel a plugin can subscribe to.
95
95
  *
96
96
  * Declared here because a cross-display consumer can only read a name the
97
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
97
+ * base declares — the same reason `FetchMixin.fetchInert` is a
98
98
  * hook rather than a getter each display invents. The container used to
99
99
  * read `featureUnderMouse`, which only the wiggle, alignments and
100
100
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -210,6 +210,79 @@ function stateModelFactory() {
210
210
  * base display reload does nothing, see specialized displays for details
211
211
  */
212
212
  reload() { },
213
+ /**
214
+ * #action
215
+ * Apply a bag of display settings to the LIVE display, and report what
216
+ * landed. Each key runs through the display config schema's
217
+ * `preProcessSnapshot` (shorthand expansions, legacy-key migrations —
218
+ * the same lowering a session spec's inline track keys get in
219
+ * `showTrackGeneric`), then writes the matching config slot. Keys that
220
+ * are not slots come back in `unapplied` rather than vanishing: the
221
+ * settings vocabulary's historical failure mode is the silently dropped
222
+ * key.
223
+ *
224
+ * `allowSetters` additionally routes a non-slot key to a conventionally
225
+ * named single-argument `set<Key>` action. Opt-in, never the default:
226
+ * the declarative surfaces (session specs, share links, embeds) feed
227
+ * this whole bags of untyped JSON, and a blanket fallback would let
228
+ * them reach internal setters (`setError`, `setScrollTop`, ...) and
229
+ * call multi-argument setters with one argument. A caller that wants a
230
+ * specific action can also simply call it.
231
+ *
232
+ * A key whose write THREW is reported separately, in `failed` — it is the
233
+ * only one of the three that means the caller got something wrong, and
234
+ * the only one worth a notification. `unapplied` is not: at the
235
+ * `showTrackGeneric` call site it also collects keys that surface
236
+ * consumed itself (`type`) and MST display props the display snapshot
237
+ * already applied (`resolution`), so treating it as "dropped" would
238
+ * report a correct call as broken.
239
+ *
240
+ * Per-key errors do not abort the rest of the bag — a caller
241
+ * mid-`showTrack` has already pushed the track, and one rejected value
242
+ * must not strand a half-configured track.
243
+ */
244
+ applyDisplaySettings(settings, options) {
245
+ // configuration is the reference every concrete display adds at
246
+ // instantiation (see DisplayModel below); the base model composes
247
+ // before it exists, hence the cast rather than a prop
248
+ const { configuration } = self;
249
+ const applied = [];
250
+ const unapplied = [];
251
+ const failed = [];
252
+ const slots = preProcessSlotValues(configuration, settings);
253
+ for (const [key, value] of Object.entries(slots)) {
254
+ if (!key || key === 'type') {
255
+ unapplied.push(key ? 'type (switch the display type instead)' : '(empty key)');
256
+ continue;
257
+ }
258
+ try {
259
+ if (isConfigurationSlot(configuration, key)) {
260
+ // the key arrives from runtime JSON; setConf's slot name is a
261
+ // compile-time type
262
+ // eslint-disable-next-line no-restricted-syntax
263
+ configuration.setSlot(key, value);
264
+ applied.push(key);
265
+ continue;
266
+ }
267
+ const setter = self[`set${key[0].toUpperCase()}${key.slice(1)}`];
268
+ if (typeof setter !== 'function') {
269
+ unapplied.push(key);
270
+ }
271
+ else if (options?.allowSetters) {
272
+ ;
273
+ setter(value);
274
+ applied.push(`${key} (via setter)`);
275
+ }
276
+ else {
277
+ unapplied.push(`${key} (not a config slot; a set${key[0].toUpperCase()}${key.slice(1)} action exists — call it, or pass { allowSetters: true })`);
278
+ }
279
+ }
280
+ catch (e) {
281
+ failed.push({ key, error: `${e}` });
282
+ }
283
+ }
284
+ return { applied, unapplied, failed };
285
+ },
213
286
  }));
214
287
  }
215
288
  export const BaseDisplay = stateModelFactory();