@jbrowse/plugin-linear-genome-view 5.0.0-beta.8 → 5.0.0-beta.9

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 (76) hide show
  1. package/esm/FeatureTrack/configSchema.d.ts +140 -48
  2. package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
  3. package/esm/LinearGenomeView/afterAttach.js +18 -24
  4. package/esm/LinearGenomeView/closeUps.d.ts +83 -0
  5. package/esm/LinearGenomeView/closeUps.js +211 -0
  6. package/esm/LinearGenomeView/components/AddCloseUpDialog.d.ts +19 -0
  7. package/esm/LinearGenomeView/components/AddCloseUpDialog.js +29 -0
  8. package/esm/LinearGenomeView/components/CloseUps.d.ts +5 -0
  9. package/esm/LinearGenomeView/components/CloseUps.js +75 -0
  10. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +2 -1
  11. package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
  12. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  13. package/esm/LinearGenomeView/components/Header.js +6 -1
  14. package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
  15. package/esm/LinearGenomeView/components/Highlight.js +2 -4
  16. package/esm/LinearGenomeView/components/HighlightBand.js +2 -2
  17. package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
  18. package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
  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 -2
  22. package/esm/LinearGenomeView/components/OverviewHighlight.js +1 -1
  23. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
  24. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.d.ts +7 -1
  25. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +22 -7
  26. package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
  27. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +6 -21
  28. package/esm/LinearGenomeView/components/SearchBox.js +2 -2
  29. package/esm/LinearGenomeView/components/TrackContainer.js +12 -5
  30. package/esm/LinearGenomeView/components/TrackLabel.js +1 -1
  31. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +3 -1
  32. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +3 -7
  33. package/esm/LinearGenomeView/components/TracksContainer.js +7 -15
  34. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  35. package/esm/LinearGenomeView/components/useSideScroll.js +1 -1
  36. package/esm/LinearGenomeView/consts.d.ts +2 -0
  37. package/esm/LinearGenomeView/consts.js +5 -0
  38. package/esm/LinearGenomeView/headerFit.d.ts +2 -1
  39. package/esm/LinearGenomeView/headerFit.js +7 -2
  40. package/esm/LinearGenomeView/index.d.ts +1 -1
  41. package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
  42. package/esm/LinearGenomeView/keyboardHandler.js +11 -0
  43. package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
  44. package/esm/LinearGenomeView/launchKeys.js +15 -5
  45. package/esm/LinearGenomeView/lazyDialogs.d.ts +7 -0
  46. package/esm/LinearGenomeView/lazyDialogs.js +2 -0
  47. package/esm/LinearGenomeView/menuItems.d.ts +28 -16
  48. package/esm/LinearGenomeView/menuItems.js +213 -85
  49. package/esm/LinearGenomeView/model.d.ts +367 -199
  50. package/esm/LinearGenomeView/model.js +302 -143
  51. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  52. package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
  53. package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.d.ts +5 -0
  54. package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.js +8 -0
  55. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +3 -4
  56. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
  57. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +95 -24
  58. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +4 -5
  59. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +13 -4
  60. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +4 -4
  61. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
  62. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +52 -63
  63. package/esm/LinearGenomeView/svgcomponents/util.d.ts +8 -6
  64. package/esm/LinearGenomeView/svgcomponents/util.js +37 -16
  65. package/esm/LinearGenomeView/types.d.ts +5 -4
  66. package/esm/LinearGenomeView/util.d.ts +20 -37
  67. package/esm/LinearGenomeView/util.js +58 -141
  68. package/esm/displayKitTests/perRegionTestEnv.d.ts +64 -45
  69. package/esm/displayKitTests/perRegionTestEnv.js +6 -6
  70. package/esm/index.d.ts +9 -67
  71. package/esm/index.js +6 -22
  72. package/esm/searchUtils.d.ts +2 -3
  73. package/esm/searchUtils.js +2 -2
  74. package/package.json +7 -7
  75. package/esm/lazyPluginExports.d.ts +0 -23
  76. package/esm/lazyPluginExports.js +0 -36
@@ -40,85 +40,177 @@ import type PluginManager from '@jbrowse/core/PluginManager';
40
40
  * }
41
41
  * ```
42
42
  */
43
- declare const configSchema: (pluginManager: PluginManager) => import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
43
+ declare const configSchema: (pluginManager: PluginManager) => import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
44
+ adapter: import("@jbrowse/mobx-state-tree").IAnyType;
45
+ assemblyNames: {
46
+ type: "stringArray";
47
+ } & {
48
+ defaultValue: unknown;
49
+ };
50
+ category: {
51
+ type: "stringArray";
52
+ } & {
53
+ defaultValue: unknown;
54
+ };
55
+ description: {
56
+ type: "string";
57
+ } & {
58
+ defaultValue: string;
59
+ };
60
+ displays: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IAnyType>;
61
+ formatAbout: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
62
+ readonly config: {
63
+ type: "frozen";
64
+ } & {
65
+ defaultValue: unknown;
66
+ };
67
+ readonly hideUris: {
68
+ type: "boolean";
69
+ } & {
70
+ defaultValue: boolean;
71
+ };
72
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>;
73
+ formatDetails: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
74
+ readonly feature: {
75
+ type: "frozen";
76
+ } & {
77
+ defaultValue: unknown;
78
+ };
79
+ readonly subfeatures: {
80
+ type: "frozen";
81
+ } & {
82
+ defaultValue: unknown;
83
+ };
84
+ readonly depth: {
85
+ type: "maybeNumber";
86
+ };
87
+ readonly maxDepth: {
88
+ type: "maybeNumber";
89
+ };
90
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>;
91
+ metadata: {
92
+ type: "frozen";
93
+ } & {
94
+ defaultValue: unknown;
95
+ };
96
+ name: {
97
+ type: "string";
98
+ } & {
99
+ defaultValue: string;
100
+ };
101
+ textSearching: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
102
+ readonly indexingAttributes: {
103
+ type: "stringArray";
104
+ } & {
105
+ defaultValue: unknown;
106
+ };
107
+ readonly indexingFeatureTypesToExclude: {
108
+ type: "stringArray";
109
+ } & {
110
+ defaultValue: unknown;
111
+ };
112
+ readonly indexingFeatureTypesToInclude: {
113
+ type: "stringArray";
114
+ } & {
115
+ defaultValue: unknown;
116
+ };
117
+ readonly textSearchAdapter: import("@jbrowse/mobx-state-tree").ITypeUnion<any, any, any>;
118
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>;
119
+ trackId: {
120
+ type: "string";
121
+ } & {
122
+ defaultValue: string;
123
+ };
124
+ type: {
125
+ type: "string";
126
+ } & {
127
+ defaultValue: string;
128
+ };
129
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
44
130
  readonly name: {
45
- readonly description: "descriptive name of the track, falls back to the trackId when unset";
46
- readonly type: "string";
47
- readonly defaultValue: "";
131
+ type: "string";
132
+ } & {
133
+ defaultValue: string;
48
134
  };
49
135
  readonly assemblyNames: {
50
- readonly description: "name of the assembly (or assemblies) track belongs to";
51
- readonly type: "stringArray";
52
- readonly defaultValue: readonly ["assemblyName"];
136
+ type: "stringArray";
137
+ } & {
138
+ defaultValue: unknown;
53
139
  };
54
140
  readonly description: {
55
- readonly description: "a description of the track";
56
- readonly type: "string";
57
- readonly defaultValue: "";
141
+ type: "string";
142
+ } & {
143
+ defaultValue: string;
58
144
  };
59
145
  readonly category: {
60
- readonly description: "the category and sub-categories of a track";
61
- readonly type: "stringArray";
62
- readonly defaultValue: readonly [];
146
+ type: "stringArray";
147
+ } & {
148
+ defaultValue: unknown;
63
149
  };
64
150
  readonly metadata: {
65
- readonly type: "frozen";
66
- readonly description: "anything to add about this track";
67
- readonly defaultValue: {};
151
+ type: "frozen";
152
+ } & {
153
+ defaultValue: unknown;
68
154
  };
69
155
  readonly adapter: import("@jbrowse/mobx-state-tree").IAnyType;
70
156
  readonly textSearching: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
71
157
  readonly indexingAttributes: {
72
- readonly type: "stringArray";
73
- readonly description: "list of which feature attributes to index for text searching";
74
- readonly defaultValue: readonly ["Name", "ID", "symbol"];
158
+ type: "stringArray";
159
+ } & {
160
+ defaultValue: unknown;
75
161
  };
76
162
  readonly indexingFeatureTypesToExclude: {
77
- readonly type: "stringArray";
78
- readonly description: "list of feature types to exclude in text search index";
79
- readonly defaultValue: readonly ["CDS", "exon"];
163
+ type: "stringArray";
164
+ } & {
165
+ defaultValue: unknown;
80
166
  };
81
167
  readonly indexingFeatureTypesToInclude: {
82
- readonly type: "stringArray";
83
- readonly description: "the only feature types to index; empty means index every type not excluded";
84
- readonly defaultValue: readonly [];
168
+ type: "stringArray";
169
+ } & {
170
+ defaultValue: unknown;
85
171
  };
86
- readonly textSearchAdapter: import("@jbrowse/mobx-state-tree").IAnyType;
87
- }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
172
+ readonly textSearchAdapter: import("@jbrowse/mobx-state-tree").ITypeUnion<any, any, any>;
173
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>;
88
174
  readonly displays: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IAnyType>;
89
175
  readonly formatDetails: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
90
176
  readonly feature: {
91
- readonly type: "frozen";
92
- readonly description: "adds extra fields to the feature details";
93
- readonly defaultValue: {};
94
- readonly contextVariable: ["feature"];
177
+ type: "frozen";
178
+ } & {
179
+ defaultValue: unknown;
95
180
  };
96
181
  readonly subfeatures: {
97
- readonly type: "frozen";
98
- readonly description: "adds extra fields to the subfeatures of a feature";
99
- readonly defaultValue: {};
100
- readonly contextVariable: ["feature"];
182
+ type: "frozen";
183
+ } & {
184
+ defaultValue: unknown;
101
185
  };
102
186
  readonly depth: {
103
- readonly type: "maybeNumber";
104
- readonly description: "levels of subfeature the formatDetails.subfeatures callback runs on, default 2";
187
+ type: "maybeNumber";
105
188
  };
106
189
  readonly maxDepth: {
107
- readonly type: "maybeNumber";
108
- readonly description: "hide subfeatures nested deeper than this, default no limit";
190
+ type: "maybeNumber";
109
191
  };
110
- }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
192
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>;
111
193
  readonly formatAbout: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
112
194
  readonly config: {
113
- readonly type: "frozen";
114
- readonly description: "formats configuration object in about dialog";
115
- readonly defaultValue: {};
116
- readonly contextVariable: ["config"];
195
+ type: "frozen";
196
+ } & {
197
+ defaultValue: unknown;
117
198
  };
118
199
  readonly hideUris: {
119
- readonly type: "boolean";
120
- readonly defaultValue: false;
200
+ type: "boolean";
201
+ } & {
202
+ defaultValue: boolean;
121
203
  };
122
- }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
123
- }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, "trackId">>, "trackId">>;
204
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>;
205
+ type: {
206
+ type: "string";
207
+ } & {
208
+ defaultValue: string;
209
+ };
210
+ trackId: {
211
+ type: "string";
212
+ } & {
213
+ defaultValue: string;
214
+ };
215
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, "trackId", true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>, "trackId", undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>>>;
124
216
  export default configSchema;
@@ -1,17 +1,17 @@
1
1
  import type { LinearGenomeViewModel } from './model.ts';
2
2
  import type { InitState } from './types.ts';
3
- import type { AssemblyHost, NotificationSink } from '@jbrowse/core/util';
3
+ import type { AbstractSessionModel, AssemblyHost, NotificationSink } from '@jbrowse/core/util';
4
4
  /**
5
- * Apply an init blob's `highlight` entries to a view, coercing each locstring
6
- * or wire-format object and reporting a bad one without taking out its
7
- * siblings.
5
+ * Add an init blob's `highlight` entries to the session, coercing each
6
+ * locstring or wire-format object and reporting a bad one without taking out
7
+ * its siblings. An entry without an assembly takes the launch's.
8
8
  *
9
9
  * Exported because a LinearSyntenyView row is a LinearGenomeView that does NOT
10
10
  * go through this file's init autorun — the synteny view builds each row's
11
11
  * snapshot and navigates it itself — so the row's own `highlight` had nowhere
12
12
  * to be applied and was dropped in silence.
13
13
  */
14
- export declare function applyInitHighlights(self: LinearGenomeViewModel, session: AssemblyHost & NotificationSink, init: Pick<InitState, 'highlight' | 'assembly'>): void;
14
+ export declare function applyInitHighlights(session: AssemblyHost & NotificationSink & Pick<AbstractSessionModel, 'addHighlight'>, init: Pick<InitState, 'highlight' | 'assembly'>): void;
15
15
  /**
16
16
  * Autorun that handles the init state - navigating to initial location,
17
17
  * showing tracks, etc.
@@ -5,6 +5,7 @@ import { normalizeTrackInit } from '@jbrowse/core/util/tracks';
5
5
  import { addDisposer } from '@jbrowse/mobx-state-tree';
6
6
  import { autorun, when } from 'mobx';
7
7
  import { SearchResultsNotFoundError } from '../searchUtils.js';
8
+ import { installCloseUps } from './closeUps.js';
8
9
  // `init` is a frozen blob that often comes from hand-authored JSON, where a
9
10
  // lone entry gets written bare (`tracks: 'genes'`). A string is iterable, so
10
11
  // looping over it directly walked its characters — one track opened per letter.
@@ -97,7 +98,7 @@ async function navigateInit(self, session, init) {
97
98
  // a spec that listed its tracks gets those tracks; one that only named
98
99
  // a gene gets the track the gene was found in, as the search box would
99
100
  await self.navToLocString(init.loc, init.assembly, init.grow, {
100
- showHitTrack: asArray(init.tracks).length === 0,
101
+ showHitTrack: init.showHitTrack ?? asArray(init.tracks).length === 0,
101
102
  });
102
103
  }
103
104
  else if (init.displayedRegionNames?.length) {
@@ -138,36 +139,24 @@ async function showInitTracks(self, init) {
138
139
  await self.launchTrack(trackId, trackSnapshot, displaySnapshot);
139
140
  }
140
141
  }
141
- // backfill assemblyName on any session-authored highlights that omitted it so
142
- // downstream code (bookmark widget grid, addBookmark, etc) doesn't have to keep
143
- // falling back
144
- function backfillHighlightAssemblies(self) {
145
- const fallback = self.assemblyNames[0];
146
- if (self.highlight.length && fallback) {
147
- const normalized = self.highlight.map(h => h.assemblyName ? h : { ...h, assemblyName: fallback });
148
- if (normalized.some((h, i) => h !== self.highlight[i])) {
149
- self.setHighlight(normalized);
150
- }
151
- }
152
- }
153
142
  /**
154
- * Apply an init blob's `highlight` entries to a view, coercing each locstring
155
- * or wire-format object and reporting a bad one without taking out its
156
- * siblings.
143
+ * Add an init blob's `highlight` entries to the session, coercing each
144
+ * locstring or wire-format object and reporting a bad one without taking out
145
+ * its siblings. An entry without an assembly takes the launch's.
157
146
  *
158
147
  * Exported because a LinearSyntenyView row is a LinearGenomeView that does NOT
159
148
  * go through this file's init autorun — the synteny view builds each row's
160
149
  * snapshot and navigates it itself — so the row's own `highlight` had nowhere
161
150
  * to be applied and was dropped in silence.
162
151
  */
163
- export function applyInitHighlights(self, session, init) {
152
+ export function applyInitHighlights(session, init) {
164
153
  for (const h of asArray(init.highlight)) {
165
154
  // parseLocString throws on an unknown refName or a malformed locstring, and
166
155
  // one bad entry must take out neither its siblings nor the rest of init
167
156
  try {
168
157
  const highlight = coerceHighlight(h, init.assembly, refName => session.assemblyManager.isValidRefName(refName, init.assembly));
169
158
  if (highlight) {
170
- self.addToHighlights(highlight);
159
+ session.addHighlight(highlight);
171
160
  }
172
161
  }
173
162
  catch (e) {
@@ -176,8 +165,14 @@ export function applyInitHighlights(self, session, init) {
176
165
  }
177
166
  }
178
167
  }
179
- async function applyInit(self, init, { superseded }) {
168
+ async function applyInit(self, pending, { superseded }) {
180
169
  const session = getSession(self);
170
+ // A re-launch of a view already showing an assembly need not restate it:
171
+ // jb.setSession writes `loc` and track entries beside the built state, and
172
+ // the readiness gate resolved the same default before letting this run.
173
+ const init = pending.assembly
174
+ ? pending
175
+ : { ...pending, assembly: self.launchAssemblyName ?? pending.assembly };
181
176
  if (init.tracklist) {
182
177
  await openTracklist(self, session, superseded);
183
178
  }
@@ -186,9 +181,8 @@ async function applyInit(self, init, { superseded }) {
186
181
  // reading or mutating a detached node throws) and also covers the case
187
182
  // isAlive misses — a newer setLaunch landed mid-apply, so the rest of *this*
188
183
  // blob is stale. Finishing it anyway appends its tracks and highlights under
189
- // the one that replaced it, and addToHighlights pushes, so a re-launch of the
190
- // same spec (a StrictMode remount) doubles the bands. The drain loop applies
191
- // the newer init next either way.
184
+ // the one that replaced it. The drain loop applies the newer init next either
185
+ // way.
192
186
  //
193
187
  // Checked after each await rather than once at the top, and as early returns
194
188
  // rather than nesting, so a step added later takes a guard beside it instead
@@ -207,8 +201,7 @@ async function applyInit(self, init, { superseded }) {
207
201
  if (init.nav !== undefined) {
208
202
  self.setHideHeader(!init.nav);
209
203
  }
210
- backfillHighlightAssemblies(self);
211
- applyInitHighlights(self, session, init);
204
+ applyInitHighlights(session, init);
212
205
  }
213
206
  /**
214
207
  * Autorun that handles the init state - navigating to initial location,
@@ -272,4 +265,5 @@ export function doAfterAttach(self) {
272
265
  setupInitAutorun(self);
273
266
  setupCoarseDynamicBlocksAutorun(self);
274
267
  setupLocalStorageAutorun(self);
268
+ installCloseUps(self);
275
269
  }
@@ -0,0 +1,83 @@
1
+ import type { LinearGenomeViewModel } from './model.ts';
2
+ import type PluginManager from '@jbrowse/core/PluginManager';
3
+ import type { Region } from '@jbrowse/core/util/types';
4
+ import type { IAnyModelType, IStateTreeNode } from '@jbrowse/mobx-state-tree';
5
+ import type { Theme } from '@mui/material';
6
+ /**
7
+ * A close-up is a LinearGenomeView nested under another one, drawn below the
8
+ * host's tracks and showing a narrower window of the same locus — the header
9
+ * overview's relationship to the view, carried on downward. Its only state of
10
+ * its own is `windowWidthBp` and `tracks`: the host writes its regions, width
11
+ * and left edge, so the close-up always shares the host's centre.
12
+ *
13
+ * Declared by hand rather than as the view model's own instance type: the
14
+ * close-up array is a `types.late` back onto the registered LinearGenomeView,
15
+ * and naming the model's instance type inside its own factory is a circular
16
+ * reference tsc refuses (TS7022). Components outside the factory cast a
17
+ * close-up to `LinearGenomeViewModel`.
18
+ */
19
+ export interface CloseUp extends IStateTreeNode {
20
+ id: string;
21
+ bpPerPx: number;
22
+ minBpPerPx: number;
23
+ windowWidthBp: number;
24
+ windowStartBp: number;
25
+ displayedRegions: Region[];
26
+ pendingLaunch?: unknown;
27
+ setWidth(width: number): void;
28
+ setDisplayedRegions(regions: Region[]): void;
29
+ setWindowFrame(windowWidthBp: number, windowStartBp: number): void;
30
+ showTrack(trackId: string): unknown;
31
+ }
32
+ /**
33
+ * The stack in page order, each close-up paired with the row above it — the
34
+ * previous close-up, or the host for the first. The array is widest first and
35
+ * every close-up is narrower than its host, so page order and array order are
36
+ * the same thing and each pair reads as one trapezoid: the span this row shows,
37
+ * marked on the row above it.
38
+ *
39
+ * One derivation for the screen and the SVG export, which otherwise agree on
40
+ * the order by having been written twice.
41
+ */
42
+ export declare function closeUpStackRows<T>(host: T, closeUps: T[]): {
43
+ closeUp: T;
44
+ context: T;
45
+ }[];
46
+ export declare const CLOSE_UP_FRAME_WIDTH = 1;
47
+ /**
48
+ * The outline a close-up and its connector share. Tertiary is the header
49
+ * overview's colour, but the dark themes set it near black, so those take its
50
+ * light shade.
51
+ */
52
+ export declare function closeUpColor(theme: {
53
+ palette: Pick<Theme['palette'], 'mode' | 'tertiary'>;
54
+ }): string;
55
+ /**
56
+ * The registered view, read at first instantiation rather than the factory's
57
+ * own return value, so a close-up is the LinearGenomeView every
58
+ * `extendViewType` caller has already extended. The name goes through a
59
+ * `string` so the typed `getViewType` overload, whose result names this model's
60
+ * own instance type, is not what tsc resolves here.
61
+ */
62
+ export declare function closeUpType(pluginManager: PluginManager): IAnyModelType;
63
+ /**
64
+ * What a close-up reaches on the view holding it. Not `LinearGenomeViewModel`:
65
+ * the model's own factory calls this, and a return type naming the model there
66
+ * is the same circular reference as above, which tsc resolves by quietly
67
+ * widening the whole model.
68
+ */
69
+ export interface CloseUpHost extends IStateTreeNode {
70
+ bpPerPx: number;
71
+ windowWidthBp: number;
72
+ closeUpViews: CloseUp[];
73
+ removeCloseUp(closeUp: CloseUp): void;
74
+ horizontalScroll(distance: number): number;
75
+ slide(viewWidths: number): void;
76
+ scrollToBp(startBp: number): number;
77
+ }
78
+ export declare function closeUpHost(node: IStateTreeNode): CloseUpHost | undefined;
79
+ export declare const CLOSE_UP_PANS: Set<string>;
80
+ export declare const CLOSE_UP_NAVIGATIONS: Set<string>;
81
+ export declare const CLOSE_UP_REPLAYS: Set<string>;
82
+ export declare const CLOSE_UP_OWN: Set<string>;
83
+ export declare function installCloseUps(self: LinearGenomeViewModel): void;
@@ -0,0 +1,211 @@
1
+ import { addDisposer, addMiddleware, getParent, hasParent, types, } from '@jbrowse/mobx-state-tree';
2
+ import { autorun, untracked } from 'mobx';
3
+ /**
4
+ * The stack in page order, each close-up paired with the row above it — the
5
+ * previous close-up, or the host for the first. The array is widest first and
6
+ * every close-up is narrower than its host, so page order and array order are
7
+ * the same thing and each pair reads as one trapezoid: the span this row shows,
8
+ * marked on the row above it.
9
+ *
10
+ * One derivation for the screen and the SVG export, which otherwise agree on
11
+ * the order by having been written twice.
12
+ */
13
+ export function closeUpStackRows(host, closeUps) {
14
+ return closeUps.map((closeUp, i) => ({
15
+ closeUp,
16
+ context: closeUps[i - 1] ?? host,
17
+ }));
18
+ }
19
+ export const CLOSE_UP_FRAME_WIDTH = 1;
20
+ /**
21
+ * The outline a close-up and its connector share. Tertiary is the header
22
+ * overview's colour, but the dark themes set it near black, so those take its
23
+ * light shade.
24
+ */
25
+ export function closeUpColor(theme) {
26
+ return theme.palette.mode === 'dark'
27
+ ? theme.palette.tertiary.light
28
+ : theme.palette.tertiary.main;
29
+ }
30
+ /**
31
+ * The registered view, read at first instantiation rather than the factory's
32
+ * own return value, so a close-up is the LinearGenomeView every
33
+ * `extendViewType` caller has already extended. The name goes through a
34
+ * `string` so the typed `getViewType` overload, whose result names this model's
35
+ * own instance type, is not what tsc resolves here.
36
+ */
37
+ export function closeUpType(pluginManager) {
38
+ const name = 'LinearGenomeView';
39
+ return types.late(() => pluginManager.getViewType(name).stateModel);
40
+ }
41
+ export function closeUpHost(node) {
42
+ const host = hasParent(node, 2)
43
+ ? getParent(node, 2)
44
+ : undefined;
45
+ return host && 'closeUps' in host
46
+ ? host
47
+ : undefined;
48
+ }
49
+ // The three sets below classify every navigation-shaped action of the view, and
50
+ // `closeUps.test.ts` holds them against the view's real action list: a
51
+ // navigation in none of them lands on the close-up and quietly walks it off the
52
+ // host's centre.
53
+ // A gesture on a close-up that means "move": replayed on the host in the host's
54
+ // own units, and swallowed on the close-up, whose left edge is derived.
55
+ export const CLOSE_UP_PANS = new Set(['horizontalScroll', 'scrollTo', 'slide']);
56
+ // A navigation on a close-up that names a place. The close-up and its host lay
57
+ // out the same regions, so the arguments mean the same thing on either, and the
58
+ // host is where they belong. `centerAt` is not an action but a method over
59
+ // `scrollTo`, so the pan above already lands it.
60
+ export const CLOSE_UP_NAVIGATIONS = new Set([
61
+ 'moveTo',
62
+ 'flyToCenter',
63
+ 'navTo',
64
+ 'navToLocation',
65
+ 'navToMultiple',
66
+ 'navToLocString',
67
+ 'navToLocations',
68
+ 'navigateNewestHighlight',
69
+ 'showRegions',
70
+ 'showAllRegionsInAssembly',
71
+ ]);
72
+ // Everything a close-up hands to its host untouched. The navigations above,
73
+ // plus the picture: what a reader sees is the stack, and `renderToSvg` draws
74
+ // every close-up above the host that owns them, so a close-up's own Export SVG
75
+ // is the host's. A close-up alone is a row out of a figure nobody asked for.
76
+ export const CLOSE_UP_REPLAYS = new Set([...CLOSE_UP_NAVIGATIONS, 'exportSvg']);
77
+ // What stays the close-up's own: its zoom, which is its one piece of state, and
78
+ // the primitives the sync writes through. `zoomTo` from a wheel anchors under
79
+ // the cursor, and the sync puts the centre back on the same frame.
80
+ export const CLOSE_UP_OWN = new Set([
81
+ 'zoomTo',
82
+ 'zoom',
83
+ 'flyTo',
84
+ 'flyToFit',
85
+ 'showAllRegions',
86
+ 'fitAllRegions',
87
+ 'clampZoomToCeiling',
88
+ 'setWindow',
89
+ 'setWindowFrame',
90
+ 'setNewView',
91
+ 'scrollToBp',
92
+ 'setDisplayedRegions',
93
+ 'horizontallyFlip',
94
+ ]);
95
+ /**
96
+ * Keep every close-up a derived view of its host: same regions, same width,
97
+ * same centre, and between base level and the row above it, so the stack reads
98
+ * the host's span at the top zooming in the whole way down. A close-up zoomed
99
+ * out past its ceiling is pulled back in; a host zoomed in past a close-up
100
+ * pulls the close-up in with it.
101
+ */
102
+ function syncCloseUps(self) {
103
+ // The stack first and alone, so a view with no close-ups — which is every
104
+ // view in the session but the one someone built a stack on — depends on this
105
+ // array and nothing else, and its own pans and zooms wake nothing here.
106
+ const closeUps = self.closeUpViews;
107
+ if (!closeUps.length) {
108
+ return;
109
+ }
110
+ const { volatileWidth, displayedRegions, windowStartBp, windowWidthBp } = self;
111
+ if (volatileWidth === undefined || !displayedRegions.length) {
112
+ return;
113
+ }
114
+ const centerBp = windowStartBp + windowWidthBp / 2;
115
+ let ceiling = windowWidthBp;
116
+ for (const closeUp of closeUps) {
117
+ closeUp.setWidth(volatileWidth);
118
+ if (closeUp.displayedRegions !== displayedRegions) {
119
+ closeUp.setDisplayedRegions(displayedRegions);
120
+ }
121
+ // BOTH of the close-up's own window numbers are read, not just its width,
122
+ // and that is what makes this the thing holding a close-up under its host
123
+ // rather than the gesture sets below. An action that moves a close-up
124
+ // without resizing it — one the sets do not cover, or one added later that
125
+ // nobody classified — otherwise never wakes this autorun, and the close-up
126
+ // sits off the centre for good with nothing to put it back. Read, it snaps
127
+ // back, so a miss costs that gesture rather than the stack.
128
+ const { windowWidthBp: closeUpWidth, windowStartBp: closeUpStart } = closeUp;
129
+ // The floor is base level, which `zoomTo` holds every gesture to but not
130
+ // the window a snapshot arrives carrying — and a stack ten times in each
131
+ // step reaches it in three or four rungs.
132
+ const floor = closeUp.minBpPerPx * volatileWidth;
133
+ const width = Math.min(Math.max(closeUpWidth, floor), ceiling);
134
+ const start = centerBp - width / 2;
135
+ if (width !== closeUpWidth || start !== closeUpStart) {
136
+ closeUp.setWindowFrame(width, start);
137
+ }
138
+ ceiling = closeUp.windowWidthBp;
139
+ }
140
+ }
141
+ /**
142
+ * Replay a gesture made on a close-up onto its host, or answer false to let it
143
+ * through. Every read here is of the live tree, which is why the caller runs it
144
+ * untracked.
145
+ */
146
+ function redirectCloseUpGesture(self, host, call, abort) {
147
+ // a close-up's own launch navigates it, and that is not a gesture: the sync
148
+ // above puts it back under the host once the regions land
149
+ if (self.pendingLaunch) {
150
+ return false;
151
+ }
152
+ if (CLOSE_UP_PANS.has(call.name)) {
153
+ const arg = call.args[0];
154
+ const ratio = self.bpPerPx / host.bpPerPx;
155
+ if (call.name === 'horizontalScroll') {
156
+ abort(host.horizontalScroll(arg * ratio) / ratio);
157
+ }
158
+ else if (call.name === 'slide') {
159
+ // a fraction of the close-up's window is that many of its bases, which is
160
+ // `ratio` widths of the host
161
+ host.slide(arg * ratio);
162
+ abort(undefined);
163
+ }
164
+ else {
165
+ const centerBp = arg * self.bpPerPx + self.windowWidthBp / 2;
166
+ host.scrollToBp(centerBp - host.windowWidthBp / 2);
167
+ abort(arg);
168
+ }
169
+ return true;
170
+ }
171
+ if (CLOSE_UP_REPLAYS.has(call.name)) {
172
+ const target = host;
173
+ abort(target[call.name](...call.args));
174
+ return true;
175
+ }
176
+ return false;
177
+ }
178
+ /**
179
+ * The gesture redirect, installed on the CLOSE-UP rather than on the view
180
+ * holding it. MST collects a call's middleware by walking from the action's own
181
+ * node up to the root, so one installed on the host runs for every action of
182
+ * every track and display beneath it — a tax on views that will never hold a
183
+ * close-up, which is nearly all of them — where one installed here sees this
184
+ * close-up's subtree and nothing else. It also makes the identity check an
185
+ * identity check rather than a search through the host's array.
186
+ */
187
+ function installCloseUpGestures(self, host) {
188
+ addDisposer(self, addMiddleware(self, (call, next, abort) => {
189
+ const mine = call.type === 'action' &&
190
+ call.id === call.rootId &&
191
+ call.context === self;
192
+ // A middleware handler runs in whatever context dispatched the action,
193
+ // and one of those is `syncCloseUps` — an autorun that dispatches
194
+ // close-up actions. A `pendingLaunch` or `bpPerPx` read registered there
195
+ // re-runs the whole sync whenever anything in the stack moves.
196
+ // eslint-disable-next-line no-restricted-syntax -- effect input: the two scales a gesture is replayed at, read where an autorun may be the caller
197
+ const handled = untracked(() => mine && redirectCloseUpGesture(self, host, call, abort));
198
+ if (!handled) {
199
+ next(call);
200
+ }
201
+ }));
202
+ }
203
+ export function installCloseUps(self) {
204
+ addDisposer(self, autorun(function closeUpsAutorun() {
205
+ syncCloseUps(self);
206
+ }, { name: 'LGVCloseUps' }));
207
+ const host = closeUpHost(self);
208
+ if (host) {
209
+ installCloseUpGestures(self, host);
210
+ }
211
+ }
@@ -0,0 +1,19 @@
1
+ import type { LinearGenomeViewModel } from '../model.ts';
2
+ import type { BpOffset } from '../types.ts';
3
+ /**
4
+ * The one thing a close-up still needs deciding once a drag has named its
5
+ * span: whether it opens showing what this view shows, or empty for a track
6
+ * chosen afterwards.
7
+ *
8
+ * The span is the selection's, named here the way the same menu's "Copy range"
9
+ * names it, so the dialog says what it is about to open rather than describing
10
+ * the feature. The model's action takes the same two offsets, for a stack an
11
+ * agent or a spec builds outright.
12
+ */
13
+ declare const AddCloseUpDialog: ({ model, leftOffset, rightOffset, handleClose, }: {
14
+ model: LinearGenomeViewModel;
15
+ leftOffset?: BpOffset;
16
+ rightOffset?: BpOffset;
17
+ handleClose: () => void;
18
+ }) => import("react").JSX.Element;
19
+ export default AddCloseUpDialog;