@jbrowse/core 5.0.0-beta.10 → 5.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +210 -80
  2. package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
  3. package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.d.ts +3 -0
  4. package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.js +14 -1
  5. package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
  6. package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
  7. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.d.ts +2 -0
  8. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.js +5 -5
  9. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.js +1 -1
  10. package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.d.ts +3 -1
  11. package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.js +2 -2
  12. package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.d.ts +2 -1
  13. package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.js +7 -14
  14. package/esm/BaseFeatureWidget/SequenceFeatureDetails/seqtypes/CDNASequence.js +3 -3
  15. package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
  16. package/esm/BaseFeatureWidget/stateModelFactory.d.ts +7 -0
  17. package/esm/BaseFeatureWidget/stateModelFactory.js +11 -0
  18. package/esm/BaseFeatureWidget/types.d.ts +2 -0
  19. package/esm/CorePlugin.js +32 -1
  20. package/esm/PluginManager.d.ts +6 -3
  21. package/esm/PluginManager.js +6 -3
  22. package/esm/ReExports/coreModules.generated.js +291 -283
  23. package/esm/ReExports/coreWorkerModules.generated.js +288 -281
  24. package/esm/ReExports/list.js +10 -2
  25. package/esm/TextSearch/TextSearchManager.d.ts +3 -3
  26. package/esm/TextSearch/TextSearchManager.js +6 -13
  27. package/esm/configuration/configurationSchema.d.ts +33 -95
  28. package/esm/configuration/configurationSchema.js +50 -218
  29. package/esm/configuration/configurationSlot.d.ts +18 -64
  30. package/esm/configuration/configurationSlot.js +31 -97
  31. package/esm/configuration/formatDetailsConfigSchema.js +2 -0
  32. package/esm/configuration/getConf.d.ts +7 -36
  33. package/esm/configuration/getConf.js +7 -37
  34. package/esm/configuration/index.d.ts +5 -2
  35. package/esm/configuration/index.js +3 -1
  36. package/esm/configuration/partitionAdvanced.d.ts +12 -0
  37. package/esm/configuration/partitionAdvanced.js +42 -0
  38. package/esm/configuration/plot.d.ts +67 -0
  39. package/esm/configuration/plot.js +175 -0
  40. package/esm/configuration/readConfObject.d.ts +15 -17
  41. package/esm/configuration/readConfObject.js +19 -50
  42. package/esm/configuration/schemaTypes.js +5 -1
  43. package/esm/configuration/snapshotPreprocess.js +1 -3
  44. package/esm/configuration/types.d.ts +60 -159
  45. package/esm/pluggableElementTypes/DisplayType.d.ts +14 -0
  46. package/esm/pluggableElementTypes/DisplayType.js +13 -0
  47. package/esm/pluggableElementTypes/RpcMethodType.d.ts +7 -0
  48. package/esm/pluggableElementTypes/RpcMethodType.js +9 -0
  49. package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
  50. package/esm/pluggableElementTypes/WidgetType.js +6 -0
  51. package/esm/pluggableElementTypes/addMenuItems.d.ts +2 -1
  52. package/esm/pluggableElementTypes/addMenuItems.js +2 -1
  53. package/esm/pluggableElementTypes/index.js +1 -8
  54. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +58 -1
  55. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
  56. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +2 -4
  57. package/esm/pluggableElementTypes/models/BaseTrackModel.js +2 -4
  58. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
  59. package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
  60. package/esm/pluggableElementTypes/models/baseTrackConfig.js +3 -0
  61. package/esm/rpc/RpcRegistry.d.ts +2 -1
  62. package/esm/rpc/byteBudget.d.ts +7 -0
  63. package/esm/rpc/byteBudget.js +10 -0
  64. package/esm/rpc/coreRpcMethods.d.ts +1 -1
  65. package/esm/rpc/coreRpcMethods.js +1 -1
  66. package/esm/rpc/explainTransferError.js +3 -3
  67. package/esm/rpc/methods/CoreLoadTrackCode.d.ts +17 -0
  68. package/esm/rpc/methods/CoreLoadTrackCode.js +24 -0
  69. package/esm/ui/AppLogo.js +43 -3
  70. package/esm/ui/JexlFilterDialog.d.ts +1 -1
  71. package/esm/ui/JexlFilterDialog.js +2 -3
  72. package/esm/ui/buildExtraTrackMenuItems.d.ts +2 -2
  73. package/esm/ui/colors.d.ts +41 -30
  74. package/esm/ui/colors.js +81 -44
  75. package/esm/ui/multiTrackMenuItems.d.ts +2 -2
  76. package/esm/util/TimeTraveller.d.ts +3 -0
  77. package/esm/util/TimeTraveller.js +34 -2
  78. package/esm/util/alignedBytes.d.ts +19 -0
  79. package/esm/util/alignedBytes.js +37 -0
  80. package/esm/util/analytics.d.ts +5 -6
  81. package/esm/util/analytics.js +5 -3
  82. package/esm/util/baseDisplayConfig.d.ts +8 -0
  83. package/esm/util/baseDisplayConfig.js +27 -0
  84. package/esm/util/bezierConnector.js +5 -1
  85. package/esm/util/cachedSetup.d.ts +16 -6
  86. package/esm/util/cachedSetup.js +108 -38
  87. package/esm/util/categoricalField.d.ts +23 -3
  88. package/esm/util/categoricalField.js +36 -3
  89. package/esm/util/cellMatches.d.ts +23 -0
  90. package/esm/util/cellMatches.js +264 -0
  91. package/esm/util/cellsStep.d.ts +27 -0
  92. package/esm/util/cellsStep.js +439 -0
  93. package/esm/util/color-bits/parse.js +22 -11
  94. package/esm/util/colorRamp.js +6 -0
  95. package/esm/util/colorScale.d.ts +9 -2
  96. package/esm/util/colorScale.js +33 -2
  97. package/esm/util/colorSchemes.d.ts +4 -2
  98. package/esm/util/colorSchemes.js +4 -1
  99. package/esm/util/copyToClipboard.js +8 -1
  100. package/esm/util/createAbortRotation.js +3 -3
  101. package/esm/util/featureTransforms.d.ts +7 -6
  102. package/esm/util/featureTransforms.js +131 -479
  103. package/esm/util/fetchPhases.d.ts +1 -1
  104. package/esm/util/formatBytes.js +1 -1
  105. package/esm/util/formatGuessers.d.ts +25 -14
  106. package/esm/util/formatGuessers.js +42 -27
  107. package/esm/util/formatRelativeTime.js +1 -1
  108. package/esm/util/getLocationUri.d.ts +2 -1
  109. package/esm/util/getLocationUri.js +8 -2
  110. package/esm/util/groupKeys.d.ts +4 -5
  111. package/esm/util/groupKeys.js +7 -6
  112. package/esm/util/intervals.js +9 -4
  113. package/esm/util/io/index.js +3 -1
  114. package/esm/util/isoformRank.d.ts +39 -0
  115. package/esm/util/isoformRank.js +118 -0
  116. package/esm/util/jexlFilters.d.ts +31 -49
  117. package/esm/util/jexlFilters.js +29 -39
  118. package/esm/util/locString.js +5 -2
  119. package/esm/util/markEncoding.js +48 -8
  120. package/esm/util/markEncodingTypes.d.ts +11 -2
  121. package/esm/util/quantileExtent.d.ts +22 -0
  122. package/esm/util/quantileExtent.js +24 -1
  123. package/esm/util/springAnimate.js +1 -1
  124. package/esm/util/stepTables.d.ts +14 -0
  125. package/esm/util/stepTables.js +38 -4
  126. package/esm/util/systemColorScheme.d.ts +4 -0
  127. package/esm/util/systemColorScheme.js +21 -0
  128. package/esm/util/tabix.d.ts +7 -3
  129. package/esm/util/tabix.js +3 -3
  130. package/esm/util/temporaryAssembly.d.ts +2 -2
  131. package/esm/util/temporaryAssembly.js +2 -3
  132. package/esm/util/trackConfigDelta.d.ts +1 -1
  133. package/esm/util/tracks.d.ts +28 -45
  134. package/esm/util/tracks.js +71 -82
  135. package/esm/util/types/index.d.ts +15 -7
  136. package/esm/util/types/index.js +21 -2
  137. package/esm/util/unknownSnapshotKeys.d.ts +3 -1
  138. package/esm/util/unknownSnapshotKeys.js +5 -11
  139. package/esm/util/usePanZoom.js +10 -3
  140. package/esm/util/useRafCommit.js +1 -0
  141. package/esm/util/useRowVirtualScroll.d.ts +1 -0
  142. package/esm/util/withFeatureDetails.js +4 -0
  143. package/package.json +13 -9
  144. package/esm/rpc/methods/CoreLoadAdapterCode.d.ts +0 -13
  145. package/esm/rpc/methods/CoreLoadAdapterCode.js +0 -16
package/esm/ui/colors.js CHANGED
@@ -98,11 +98,9 @@ export const paletteColors = {
98
98
  * The wide qualitative palette: every scheme above, in order, deduped and with
99
99
  * the neutrals dropped. ~40 entries.
100
100
  *
101
- * One list, because "give each row a distinct color" is one question this repo
102
- * answers in two places — the multi-row painter handing colors out by row
103
- * index, and the arrangement dialog's palette-by-attribute — and two lists
104
- * would mean a track's automatic colors and the colors it takes when a user
105
- * palettes it by hand were different palettes for no reason a reader could see.
101
+ * A feature's categorical colour deals from it; a row's deals from
102
+ * `rowPaletteColorAt`, which re-lights tableau10 rather than reaching for
103
+ * another scheme's hues.
106
104
  *
107
105
  * `tableau10` leads because its hues are the most evenly separated, so a track
108
106
  * with a handful of rows spends only that. The tail is what makes the 26
@@ -114,21 +112,16 @@ export const paletteColors = {
114
112
  * re-palettes), while a by-position painter has no value to hash and wraps.
115
113
  *
116
114
  * **It holds 14 pairs `similarColors` calls the same** — five schemes' blues,
117
- * oranges, greens, reds and pinks — and deduping it at construction is the
118
- * wrong reading of that, measured on the 38 GENCODE gene biotypes. Hashed,
119
- * which is what a track with no `colorDomain` does, 30 of them land on 22
120
- * colors and 38 on 26; the loss is the birthday collision on 40 slots, not the
121
- * near-twins, and only 3 of the pairs they land on read alike. Pruning to the
122
- * 30 distinct entries makes that 23 and 24, so it costs ten colors and buys
123
- * nothing.
115
+ * oranges, greens, reds and pinks. A dealt scale (`categoricalScale`'s `held`)
116
+ * steps over a twin of a color already dealt, so they cost a track only once
117
+ * it has more values than the 30 distinct entries.
124
118
  *
125
- * The near-twins bite the PINNED case instead, where a `domain` spends the
126
- * list in order: 30 listed values take 30 colors today and 7 of those pairs
127
- * read alike. Ordering the distinct 30 first and the twins behind them fixes
128
- * that outright (0 pairs at 30 listed values, 11 at 38 where the twins are
129
- * reached anyway) and costs no colors — but it moves every hashed color in
130
- * marks, GWAS, canvas, multi-row and the arrangement dialog, so it is a pass
131
- * with a figure refresh in it.
119
+ * The near-twins bite a `domain` instead, which spends the list in order: 30
120
+ * listed values take 30 colors and 7 of those pairs read alike. Ordering the
121
+ * distinct 30 first and the twins behind them fixes that outright (0 pairs at
122
+ * 30 listed values, 11 at 38 where the twins are reached anyway) and costs no
123
+ * colors — but it moves every hashed color in marks, GWAS, canvas, multi-row
124
+ * and the arrangement dialog, so it is a pass with a figure refresh in it.
132
125
  */
133
126
  export const categoricalPalette = [
134
127
  ...new Set([...tableau10, ...set1, ...dark2, ...set2, ...category10].filter(hex => !NEUTRALS.has(hex.toLowerCase()))),
@@ -159,19 +152,24 @@ function freeSlot(value, size, taken) {
159
152
  return slot;
160
153
  }
161
154
  /**
162
- * The categorical rule every scaled channel resolves through. With no
163
- * `domain` a value takes `categoricalValueColor`'s slot in `range`. With one,
164
- * the listed values take `range` in order, continuing into the `fallback`
165
- * entries `range` lacks past its end, and any other value takes a slot of the
166
- * whole list derived from itself that no listed value holds, nor one
167
- * `isNear` calls the same. A value's entry depends only on the value and the
168
- * declaration, so every region agrees on it, and adding a listed value moves
169
- * only the unlisted values on the slots it takes.
155
+ * The categorical rule every scaled channel resolves through. The values
156
+ * `domain` lists take `range` in order, continuing into the `fallback`
157
+ * entries `range` lacks past its end. Any other value takes the slot derived
158
+ * from itself (`categoricalValueColor`'s, with no `domain`) unless a listed
159
+ * value holds it or one `isNear` calls the same, and then another slot derived
160
+ * from itself.
161
+ *
162
+ * With `held`, a value is dealt once, on first sight: its slot avoids every
163
+ * slot `held` already records and their near twins as well, so values met
164
+ * together never share an entry while the list has room, and the slot is
165
+ * recorded so the value keeps it as others arrive. A value nothing else
166
+ * collides with keeps the entry it takes without `held`. With no `domain`, the
167
+ * deal spends `range` before spilling into the `fallback` entries it lacks.
170
168
  */
171
- export function categoricalScale(domain, range, { fallback = [], isNear, } = {}) {
169
+ export function categoricalScale(domain, range, { fallback = [], isNear, held, } = {}) {
172
170
  const base = range.length ? range : fallback;
173
171
  const listed = [...new Set((domain ?? []).map(String))].filter(v => v !== '');
174
- if (listed.length === 0) {
172
+ if (listed.length === 0 && !held) {
175
173
  return value => base[valueSlot(value, base.length)];
176
174
  }
177
175
  const seen = new Set(base.map(entryKey));
@@ -183,19 +181,44 @@ export function categoricalScale(domain, range, { fallback = [], isNear, } = {})
183
181
  }),
184
182
  ];
185
183
  const size = entries.length;
184
+ const twins = [];
185
+ const twinsOf = (slot) => (twins[slot] ??= entries.flatMap((entry, i) => i === slot || isNear?.(entries[slot], entry) ? [i] : []));
186
186
  const rank = new Map(listed.map((value, i) => [value, i]));
187
- const listedSlots = new Set(listed.map((_, i) => i % size));
188
- const taken = new Set(entries.flatMap((entry, i) => listedSlots.has(i) ||
189
- (isNear && [...listedSlots].some(j => isNear(entries[j], entry)))
190
- ? [i]
191
- : []));
187
+ const listedTaken = new Set(listed.flatMap((_, i) => twinsOf(i % size)));
188
+ const tiers = listed.length > 0
189
+ ? [[0, size]]
190
+ : [[0, base.length], [base.length, size]];
191
+ const deal = (value, taken) => {
192
+ for (const [from, to] of tiers) {
193
+ const inTier = from === 0 && to === size
194
+ ? taken
195
+ : new Set([...taken].filter(s => s >= from && s < to).map(s => s - from));
196
+ if (inTier.size < to - from) {
197
+ return from + freeSlot(value, to - from, inTier);
198
+ }
199
+ }
200
+ return valueSlot(value, tiers[0][1]);
201
+ };
192
202
  return value => {
193
203
  const i = rank.get(value);
194
- return entries[i !== undefined
195
- ? i % size
196
- : taken.size < size
197
- ? freeSlot(value, size, taken)
198
- : valueSlot(value, size)];
204
+ if (i !== undefined) {
205
+ return entries[i % size];
206
+ }
207
+ if (!held) {
208
+ return entries[deal(value, listedTaken)];
209
+ }
210
+ let slot = held.get(value);
211
+ if (slot === undefined) {
212
+ const taken = new Set(listedTaken);
213
+ for (const s of held.values()) {
214
+ for (const twin of twinsOf(s)) {
215
+ taken.add(twin);
216
+ }
217
+ }
218
+ slot = deal(value, taken);
219
+ held.set(value, slot);
220
+ }
221
+ return entries[slot];
199
222
  };
200
223
  }
201
224
  const oklabByColor = new Map();
@@ -219,12 +242,14 @@ export function similarColors(a, b) {
219
242
  /**
220
243
  * `categoricalScale` for a color channel: `palette` (the wide palette when
221
244
  * empty) spent in `domain` order and continued into the wide palette, and an
222
- * unlisted value never on a listed value's color or one that reads the same.
245
+ * unlisted value never on a listed value's color or one that reads the same,
246
+ * nor, with `held`, on the color of a value met before it.
223
247
  */
224
- export function categoricalColorScale(domain, palette = []) {
248
+ export function categoricalColorScale(domain, palette = [], held) {
225
249
  return categoricalScale(domain, palette, {
226
250
  fallback: categoricalPalette,
227
251
  isNear: similarColors,
252
+ held,
228
253
  });
229
254
  }
230
255
  const MAX_CACHED_SCALES = 16;
@@ -316,11 +341,23 @@ const PALETTE_LAP_TONES = [
316
341
  // and pinks all pin against the top of the sRGB gamut and converge there.
317
342
  { lightnessShift: 0.18, chromaScale: 0.8 },
318
343
  ];
344
+ // The colour at `position` in `hexes`, each lap past the end re-lit.
345
+ function lapColorAt(hexes, position) {
346
+ const hex = hexes[position % hexes.length];
347
+ const lap = PALETTE_LAP_TONES[Math.floor(position / hexes.length) % PALETTE_LAP_TONES.length];
348
+ return lap ? relight(hex, lap.lightnessShift, lap.chromaScale) : hex;
349
+ }
319
350
  /** The palette color for a chromosome at `position` in its assembly. */
320
351
  export function refNamePaletteColorAt(position) {
321
- const hex = refNameColorHexes[position % refNameColorHexes.length];
322
- const lap = PALETTE_LAP_TONES[Math.floor(position / refNameColorHexes.length) % PALETTE_LAP_TONES.length];
323
- return lap ? relight(hex, lap.lightnessShift, lap.chromaScale) : hex;
352
+ return lapColorAt(refNameColorHexes, position);
353
+ }
354
+ const rowColorHexes = tableau10.filter(hex => !NEUTRALS.has(hex));
355
+ /**
356
+ * The colour the row palette deals the row or value at `position`: tableau10
357
+ * less its grey, then re-lit laps of it, as a karyotype's are.
358
+ */
359
+ export function rowPaletteColorAt(position) {
360
+ return lapColorAt(rowColorHexes, position);
324
361
  }
325
362
  /**
326
363
  * The color a refName paints, BY POSITION IN THE ASSEMBLY where the caller
@@ -1,10 +1,10 @@
1
1
  import type PluginManager from '../PluginManager.ts';
2
- import type { AnyConfigurationModel } from '../configuration/index.ts';
2
+ import type { AnyTrackConfig } from '../configuration/index.ts';
3
3
  import type { AbstractSessionModel } from '../util/types/index.ts';
4
4
  import type { MenuItem } from './MenuTypes.ts';
5
5
  import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
6
6
  export interface MultiTrackSelection extends IStateTreeNode {
7
- selection: AnyConfigurationModel[];
7
+ selection: AnyTrackConfig[];
8
8
  }
9
9
  export interface MultiTrackMenuItemsProps {
10
10
  session: AbstractSessionModel;
@@ -9,10 +9,13 @@ declare const TimeTraveller: import("@jbrowse/mobx-state-tree").IModelType<{
9
9
  }, {
10
10
  history: unknown[];
11
11
  notTrackingUndo: boolean;
12
+ hasPendingRecord: boolean;
12
13
  } & {
13
14
  readonly canUndo: boolean;
14
15
  readonly canRedo: boolean;
15
16
  } & {
17
+ setHasPendingRecord(value: boolean): void;
18
+ flushPendingRecord(): void;
16
19
  stopTrackingUndo(): void;
17
20
  resumeTrackingUndo(): void;
18
21
  addUndoState(snapshot: unknown): void;
@@ -14,13 +14,16 @@ const TimeTraveller = types
14
14
  .volatile(() => ({
15
15
  history: [],
16
16
  notTrackingUndo: false,
17
+ hasPendingRecord: false,
17
18
  }))
18
19
  .views(self => ({
19
20
  get canUndo() {
20
- return self.undoIdx > 0 && !self.notTrackingUndo;
21
+ return ((self.undoIdx > 0 || self.hasPendingRecord) && !self.notTrackingUndo);
21
22
  },
22
23
  get canRedo() {
23
- return self.undoIdx < self.history.length - 1 && !self.notTrackingUndo;
24
+ return (self.undoIdx < self.history.length - 1 &&
25
+ !self.hasPendingRecord &&
26
+ !self.notTrackingUndo);
24
27
  },
25
28
  }))
26
29
  .actions(self => {
@@ -93,7 +96,21 @@ const TimeTraveller = types
93
96
  skipNextUndoState = false;
94
97
  }
95
98
  }
99
+ let recordPending;
96
100
  return {
101
+ setHasPendingRecord(value) {
102
+ self.hasPendingRecord = value;
103
+ },
104
+ flushPendingRecord() {
105
+ if (debounceTimer) {
106
+ clearTimeout(debounceTimer);
107
+ debounceTimer = undefined;
108
+ }
109
+ if (self.hasPendingRecord) {
110
+ self.hasPendingRecord = false;
111
+ recordPending?.();
112
+ }
113
+ },
97
114
  stopTrackingUndo() {
98
115
  self.notTrackingUndo = true;
99
116
  },
@@ -141,14 +158,17 @@ const TimeTraveller = types
141
158
  skipNextUndoState = false;
142
159
  self.history = [];
143
160
  self.undoIdx = -1;
161
+ self.hasPendingRecord = false;
144
162
  // read the target's current state rather than a snapshot captured per
145
163
  // change: any change inside the debounce window re-times it, so when it
146
164
  // does fire the two agree
147
165
  const record = () => {
166
+ this.setHasPendingRecord(false);
148
167
  if (targetStore) {
149
168
  this.addUndoState(getSnapshot(targetStore));
150
169
  }
151
170
  };
171
+ recordPending = record;
152
172
  // onPatch, not onSnapshot: this only needs to know *that* the target
153
173
  // changed, and the snapshot is taken once per debounce window below.
154
174
  // onSnapshot would serialize the whole target on every single change to
@@ -178,17 +198,29 @@ const TimeTraveller = types
178
198
  clearTimeout(debounceTimer);
179
199
  debounceTimer = undefined;
180
200
  }
201
+ this.setHasPendingRecord(false);
181
202
  return;
182
203
  }
204
+ if (!self.hasPendingRecord) {
205
+ this.setHasPendingRecord(true);
206
+ }
183
207
  armRecording(record);
184
208
  });
185
209
  this.addUndoState(getSnapshot(targetStore));
186
210
  },
187
211
  undo() {
212
+ this.flushPendingRecord();
213
+ if (self.undoIdx <= 0) {
214
+ return;
215
+ }
188
216
  self.undoIdx--;
189
217
  applyHistoryState(self.undoIdx);
190
218
  },
191
219
  redo() {
220
+ this.flushPendingRecord();
221
+ if (self.undoIdx >= self.history.length - 1) {
222
+ return;
223
+ }
192
224
  self.undoIdx++;
193
225
  applyHistoryState(self.undoIdx);
194
226
  },
@@ -0,0 +1,19 @@
1
+ export declare const DASH = 45;
2
+ export declare const SPACE = 32;
3
+ export declare const LOWER_BIT = 32;
4
+ export declare const N_UPPER = 78;
5
+ /** No base at this column of a row: a gap, or the padding of a short row. */
6
+ export declare function isGapByte(b: number): b is 32 | 45;
7
+ export declare function sameBase(a: number, b: number): boolean;
8
+ /**
9
+ * An `N` reference base, which a row neither matches nor mismatches, so
10
+ * identity leaves it uncounted.
11
+ */
12
+ export declare function isUnknownBase(b: number): boolean;
13
+ /**
14
+ * The first column carrying the row's own sequence. A gap run reaching either
15
+ * end of a row measures where the block was cut, not the alignment.
16
+ */
17
+ export declare function firstDrawn(bytes: Uint8Array, at: number, len: number): number;
18
+ /** The last column carrying the row's own sequence, from `firstDrawn`'s answer. */
19
+ export declare function lastDrawn(bytes: Uint8Array, at: number, len: number, first: number): number;
@@ -0,0 +1,37 @@
1
+ export const DASH = 45;
2
+ export const SPACE = 32;
3
+ export const LOWER_BIT = 0x20;
4
+ export const N_UPPER = 78;
5
+ /** No base at this column of a row: a gap, or the padding of a short row. */
6
+ export function isGapByte(b) {
7
+ return b === DASH || b === SPACE;
8
+ }
9
+ export function sameBase(a, b) {
10
+ return (a | LOWER_BIT) === (b | LOWER_BIT);
11
+ }
12
+ /**
13
+ * An `N` reference base, which a row neither matches nor mismatches, so
14
+ * identity leaves it uncounted.
15
+ */
16
+ export function isUnknownBase(b) {
17
+ return (b & ~LOWER_BIT) === N_UPPER;
18
+ }
19
+ /**
20
+ * The first column carrying the row's own sequence. A gap run reaching either
21
+ * end of a row measures where the block was cut, not the alignment.
22
+ */
23
+ export function firstDrawn(bytes, at, len) {
24
+ let first = 0;
25
+ while (first < len && isGapByte(bytes[at + first])) {
26
+ first++;
27
+ }
28
+ return first;
29
+ }
30
+ /** The last column carrying the row's own sequence, from `firstDrawn`'s answer. */
31
+ export function lastDrawn(bytes, at, len, first) {
32
+ let last = len - 1;
33
+ while (last > first && isGapByte(bytes[at + last])) {
34
+ last--;
35
+ }
36
+ return last;
37
+ }
@@ -1,22 +1,21 @@
1
- import type { AnyConfigurationModel } from '../configuration/index.ts';
1
+ import type { AnyConfigurationModel, TrackConfigEntry } from '../configuration/index.ts';
2
2
  declare global {
3
3
  interface Window {
4
4
  ga?: (...args: unknown[]) => void;
5
5
  }
6
6
  }
7
- type TrackConfig = AnyConfigurationModel & {
8
- type: string;
9
- };
10
7
  interface AnalyticsRootModel {
11
8
  jbrowse: {
12
- tracks: TrackConfig[];
9
+ tracks: TrackConfigEntry[];
13
10
  assemblies: unknown[];
14
11
  plugins?: {
15
12
  name?: string;
16
13
  }[];
17
14
  };
18
15
  session?: {
19
- sessionTracks: TrackConfig[];
16
+ sessionTracks: {
17
+ type: string;
18
+ }[];
20
19
  views: unknown[];
21
20
  };
22
21
  version: string;
@@ -1,6 +1,7 @@
1
1
  import { effectiveRenderer, getGraphicsCapabilities, } from '@jbrowse/render-core/graphicsCapabilities';
2
2
  import { readConfObject } from '../configuration/index.js';
3
3
  import { isElectron, rIC } from '../util/index.js';
4
+ import { getConfAssemblyNamesOrNone } from './tracks.js';
4
5
  // Both writers are fire-and-forget from several call sites, so they swallow
5
6
  // their own failures rather than rejecting: a floating rejected promise trips
6
7
  // the webpack-dev-server overlay (it listens for unhandledrejection), and an
@@ -8,7 +9,7 @@ import { isElectron, rIC } from '../util/index.js';
8
9
  export async function writeAWSAnalytics(rootModel, initialTimestamp, sessionQuery) {
9
10
  try {
10
11
  const url = 'https://analytics.jbrowse.org/api/v1';
11
- const multiAssemblyTracks = rootModel.jbrowse.tracks.filter(track => readConfObject(track, 'assemblyNames').length > 1).length;
12
+ const multiAssemblyTracks = rootModel.jbrowse.tracks.filter(track => getConfAssemblyNamesOrNone(track).length > 1).length;
12
13
  // The rung createGpuHal built where a display has built one by idle time,
13
14
  // else the ladder's prediction with any page-wide pin applied — so a
14
15
  // session that fell back to Canvas2D is counted as Canvas2D rather than as
@@ -138,7 +139,8 @@ export function doAnalytics(rootModel, initialTimestamp, initialSessionQuery) {
138
139
  // `[data-app-phase="ready"]`, or a timeout for a page that never shows one
139
140
  // (Desktop's start screen)
140
141
  function afterAppReady(callback, maxWaitMs = 10_000) {
141
- const isReady = () => document.querySelector('[data-app-phase="ready"]') !== null;
142
+ const doc = document;
143
+ const isReady = () => doc.querySelector('[data-app-phase="ready"]') !== null;
142
144
  if (isReady()) {
143
145
  callback();
144
146
  return;
@@ -158,7 +160,7 @@ function afterAppReady(callback, maxWaitMs = 10_000) {
158
160
  }
159
161
  });
160
162
  const timer = setTimeout(finish, maxWaitMs);
161
- observer.observe(document.body, {
163
+ observer.observe(doc.body, {
162
164
  subtree: true,
163
165
  childList: true,
164
166
  attributeFilter: ['data-app-phase'],
@@ -0,0 +1,8 @@
1
+ /**
2
+ * #api core/util
3
+ * The base's entry for this display, hydrated, which a reset returns to and
4
+ * "is this the reader's" compares against: the config.json's, or the one a
5
+ * track the session owns was added with; empty in a session that keeps no
6
+ * base.
7
+ */
8
+ export declare function baseDisplayConfig(self: object): Record<string, unknown>;
@@ -0,0 +1,27 @@
1
+ import { hasParent } from '@jbrowse/mobx-state-tree';
2
+ import { getConf } from '../configuration/getConf.js';
3
+ import { getContainingTrack, getSession } from './mstUtils.js';
4
+ import { isSessionWithBaseTrackConfig } from './types/index.js';
5
+ /**
6
+ * #api core/util
7
+ * The base's entry for this display, hydrated, which a reset returns to and
8
+ * "is this the reader's" compares against: the config.json's, or the one a
9
+ * track the session owns was added with; empty in a session that keeps no
10
+ * base.
11
+ */
12
+ export function baseDisplayConfig(self) {
13
+ if (!hasParent(self)) {
14
+ return {};
15
+ }
16
+ const session = getSession(self);
17
+ if (!isSessionWithBaseTrackConfig(session)) {
18
+ return {};
19
+ }
20
+ const base = session.baseTrackConfig(getConf(getContainingTrack(self), 'trackId'));
21
+ const { displayId } = self
22
+ .configuration;
23
+ const displays = base?.displays;
24
+ return ((Array.isArray(displays)
25
+ ? displays.find(d => d.displayId === displayId)
26
+ : undefined) ?? {});
27
+ }
@@ -19,6 +19,9 @@ const SPAN_FACTOR = 0.3;
19
19
  // its leading end: unbounded, that hook grows with the arc, and a full-width
20
20
  // inversion flings a ~500px curl back across unrelated reads.
21
21
  const MAX_HANDLE_PX = 200;
22
+ // The handles are horizontal, so a tall vertical gap (stacked split-view
23
+ // panels) only widens the sideways bulge; past this height it earns no more.
24
+ const MAX_VERTICAL_SPAN_PX = 150;
22
25
  // A symmetric cubic's extreme sits 3/4 of the way from its baseline to its
23
26
  // control points, so an arc asked to reach a given apex puts its controls 4/3
24
27
  // past it. Exported because the sashimi overlay solves the same equation in the
@@ -62,7 +65,8 @@ function tangentSign(strand, leading, reversed) {
62
65
  // How much shaping (handle length, bow height) this curve may spend: a fraction
63
66
  // of the distance it has to cover.
64
67
  function shapingBudget(p1, p2) {
65
- return Math.hypot(p2.x - p1.x, p2.y - p1.y) * SPAN_FACTOR;
68
+ const dy = Math.min(Math.abs(p2.y - p1.y), MAX_VERTICAL_SPAN_PX);
69
+ return Math.hypot(p2.x - p1.x, dy) * SPAN_FACTOR;
66
70
  }
67
71
  // Fades to zero as the endpoints' rows separate, since a cross-row curve
68
72
  // already has vertical extent — and via the budget as they close in
@@ -1,6 +1,12 @@
1
1
  import type { BaseOptions } from '../data_adapters/BaseAdapter/types.ts';
2
2
  /** byte-granularity progress reporter to hand an index reader's `onProgress` */
3
3
  type OnProgress = (current: number, total?: number) => void;
4
+ /**
5
+ * How long a setup with no live waiter keeps running before its signal aborts:
6
+ * `createAbortRotation.begin()` aborts the superseded call before it posts the
7
+ * replacement, which joins within this window instead of restarting from byte 0.
8
+ */
9
+ export declare const ABANDONED_SETUP_GRACE_MS = 100;
4
10
  /**
5
11
  * Memoize an adapter's one-time setup (open the file, read the header, build a
6
12
  * parser, download and parse a whole file) into a loader every method can
@@ -17,11 +23,13 @@ type OnProgress = (current: number, total?: number) => void;
17
23
  * moment that fetch was superseded — the display's latest-wins guard gates the
18
24
  * old callback off — so the fetch replacing it awaited a multi-GB parse behind
19
25
  * a blank loading overlay.
20
- * - **`signal` is withheld from the shared work.** Honoring one caller's
21
- * cancel would abort a parse the caller replacing it is already waiting on,
22
- * and reject them both. A superseded fetch just stops listening. Cancellation
23
- * stays with the per-call work (indexed range queries), which no one else is
24
- * waiting on.
26
+ * - **Cancellation is shared, not per caller.** The setup gets one signal that
27
+ * aborts once its last live waiter has aborted and nobody rejoins within
28
+ * {@link ABANDONED_SETUP_GRACE_MS}, so a superseded fetch cannot cancel a
29
+ * parse its replacement is waiting on, yet a download nobody awaits stops.
30
+ * Each caller's own await rejects the moment its own signal aborts. An
31
+ * abandoned setup leaves the memo first, so the next call starts afresh and
32
+ * an abandoned run's late statuses reach no one.
25
33
  * - **`label` is shown only while the *first* attempt is in flight.** Re-entry
26
34
  * on pan/zoom (every getFeatures and byte estimate awaits the loader) would
27
35
  * otherwise re-flash "Downloading index" over an index that is already
@@ -32,7 +40,9 @@ type OnProgress = (current: number, total?: number) => void;
32
40
  * a whole-file fetch driving its own download bar — since two labels for one
33
41
  * download is worse than one, and phases nest (see `openPhase` in progress.ts).
34
42
  * `onProgress` is for handing to an index reader that can upgrade the label to a
35
- * determinate bar.
43
+ * determinate bar; like the status, it fans out to every live waiter. It is
44
+ * undefined without a `label`, so a setup that narrates itself never opts its
45
+ * reads into streamed progress.
36
46
  */
37
47
  export declare function cachedSetup<T>({ setup, label, }: {
38
48
  setup: (opts: BaseOptions, onProgress?: OnProgress) => Promise<T>;