@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.
- package/README.md +210 -80
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
- package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.d.ts +3 -0
- package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.js +14 -1
- package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
- package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.d.ts +2 -0
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.js +5 -5
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.js +1 -1
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.d.ts +3 -1
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.js +2 -2
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.d.ts +2 -1
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.js +7 -14
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/seqtypes/CDNASequence.js +3 -3
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
- package/esm/BaseFeatureWidget/stateModelFactory.d.ts +7 -0
- package/esm/BaseFeatureWidget/stateModelFactory.js +11 -0
- package/esm/BaseFeatureWidget/types.d.ts +2 -0
- package/esm/CorePlugin.js +32 -1
- package/esm/PluginManager.d.ts +6 -3
- package/esm/PluginManager.js +6 -3
- package/esm/ReExports/coreModules.generated.js +291 -283
- package/esm/ReExports/coreWorkerModules.generated.js +288 -281
- package/esm/ReExports/list.js +10 -2
- package/esm/TextSearch/TextSearchManager.d.ts +3 -3
- package/esm/TextSearch/TextSearchManager.js +6 -13
- package/esm/configuration/configurationSchema.d.ts +33 -95
- package/esm/configuration/configurationSchema.js +50 -218
- package/esm/configuration/configurationSlot.d.ts +18 -64
- package/esm/configuration/configurationSlot.js +31 -97
- package/esm/configuration/formatDetailsConfigSchema.js +2 -0
- package/esm/configuration/getConf.d.ts +7 -36
- package/esm/configuration/getConf.js +7 -37
- package/esm/configuration/index.d.ts +5 -2
- package/esm/configuration/index.js +3 -1
- package/esm/configuration/partitionAdvanced.d.ts +12 -0
- package/esm/configuration/partitionAdvanced.js +42 -0
- package/esm/configuration/plot.d.ts +67 -0
- package/esm/configuration/plot.js +175 -0
- package/esm/configuration/readConfObject.d.ts +15 -17
- package/esm/configuration/readConfObject.js +19 -50
- package/esm/configuration/schemaTypes.js +5 -1
- package/esm/configuration/snapshotPreprocess.js +1 -3
- package/esm/configuration/types.d.ts +60 -159
- package/esm/pluggableElementTypes/DisplayType.d.ts +14 -0
- package/esm/pluggableElementTypes/DisplayType.js +13 -0
- package/esm/pluggableElementTypes/RpcMethodType.d.ts +7 -0
- package/esm/pluggableElementTypes/RpcMethodType.js +9 -0
- package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
- package/esm/pluggableElementTypes/WidgetType.js +6 -0
- package/esm/pluggableElementTypes/addMenuItems.d.ts +2 -1
- package/esm/pluggableElementTypes/addMenuItems.js +2 -1
- package/esm/pluggableElementTypes/index.js +1 -8
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +58 -1
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +2 -4
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +2 -4
- package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
- package/esm/pluggableElementTypes/models/baseTrackConfig.js +3 -0
- package/esm/rpc/RpcRegistry.d.ts +2 -1
- package/esm/rpc/byteBudget.d.ts +7 -0
- package/esm/rpc/byteBudget.js +10 -0
- package/esm/rpc/coreRpcMethods.d.ts +1 -1
- package/esm/rpc/coreRpcMethods.js +1 -1
- package/esm/rpc/explainTransferError.js +3 -3
- package/esm/rpc/methods/CoreLoadTrackCode.d.ts +17 -0
- package/esm/rpc/methods/CoreLoadTrackCode.js +24 -0
- package/esm/ui/AppLogo.js +43 -3
- package/esm/ui/JexlFilterDialog.d.ts +1 -1
- package/esm/ui/JexlFilterDialog.js +2 -3
- package/esm/ui/buildExtraTrackMenuItems.d.ts +2 -2
- package/esm/ui/colors.d.ts +41 -30
- package/esm/ui/colors.js +81 -44
- package/esm/ui/multiTrackMenuItems.d.ts +2 -2
- package/esm/util/TimeTraveller.d.ts +3 -0
- package/esm/util/TimeTraveller.js +34 -2
- package/esm/util/alignedBytes.d.ts +19 -0
- package/esm/util/alignedBytes.js +37 -0
- package/esm/util/analytics.d.ts +5 -6
- package/esm/util/analytics.js +5 -3
- package/esm/util/baseDisplayConfig.d.ts +8 -0
- package/esm/util/baseDisplayConfig.js +27 -0
- package/esm/util/bezierConnector.js +5 -1
- package/esm/util/cachedSetup.d.ts +16 -6
- package/esm/util/cachedSetup.js +108 -38
- package/esm/util/categoricalField.d.ts +23 -3
- package/esm/util/categoricalField.js +36 -3
- package/esm/util/cellMatches.d.ts +23 -0
- package/esm/util/cellMatches.js +264 -0
- package/esm/util/cellsStep.d.ts +27 -0
- package/esm/util/cellsStep.js +439 -0
- package/esm/util/color-bits/parse.js +22 -11
- package/esm/util/colorRamp.js +6 -0
- package/esm/util/colorScale.d.ts +9 -2
- package/esm/util/colorScale.js +33 -2
- package/esm/util/colorSchemes.d.ts +4 -2
- package/esm/util/colorSchemes.js +4 -1
- package/esm/util/copyToClipboard.js +8 -1
- package/esm/util/createAbortRotation.js +3 -3
- package/esm/util/featureTransforms.d.ts +7 -6
- package/esm/util/featureTransforms.js +131 -479
- package/esm/util/fetchPhases.d.ts +1 -1
- package/esm/util/formatBytes.js +1 -1
- package/esm/util/formatGuessers.d.ts +25 -14
- package/esm/util/formatGuessers.js +42 -27
- package/esm/util/formatRelativeTime.js +1 -1
- package/esm/util/getLocationUri.d.ts +2 -1
- package/esm/util/getLocationUri.js +8 -2
- package/esm/util/groupKeys.d.ts +4 -5
- package/esm/util/groupKeys.js +7 -6
- package/esm/util/intervals.js +9 -4
- package/esm/util/io/index.js +3 -1
- package/esm/util/isoformRank.d.ts +39 -0
- package/esm/util/isoformRank.js +118 -0
- package/esm/util/jexlFilters.d.ts +31 -49
- package/esm/util/jexlFilters.js +29 -39
- package/esm/util/locString.js +5 -2
- package/esm/util/markEncoding.js +48 -8
- package/esm/util/markEncodingTypes.d.ts +11 -2
- package/esm/util/quantileExtent.d.ts +22 -0
- package/esm/util/quantileExtent.js +24 -1
- package/esm/util/springAnimate.js +1 -1
- package/esm/util/stepTables.d.ts +14 -0
- package/esm/util/stepTables.js +38 -4
- package/esm/util/systemColorScheme.d.ts +4 -0
- package/esm/util/systemColorScheme.js +21 -0
- package/esm/util/tabix.d.ts +7 -3
- package/esm/util/tabix.js +3 -3
- package/esm/util/temporaryAssembly.d.ts +2 -2
- package/esm/util/temporaryAssembly.js +2 -3
- package/esm/util/trackConfigDelta.d.ts +1 -1
- package/esm/util/tracks.d.ts +28 -45
- package/esm/util/tracks.js +71 -82
- package/esm/util/types/index.d.ts +15 -7
- package/esm/util/types/index.js +21 -2
- package/esm/util/unknownSnapshotKeys.d.ts +3 -1
- package/esm/util/unknownSnapshotKeys.js +5 -11
- package/esm/util/usePanZoom.js +10 -3
- package/esm/util/useRafCommit.js +1 -0
- package/esm/util/useRowVirtualScroll.d.ts +1 -0
- package/esm/util/withFeatureDetails.js +4 -0
- package/package.json +13 -9
- package/esm/rpc/methods/CoreLoadAdapterCode.d.ts +0 -13
- 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
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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
|
|
118
|
-
*
|
|
119
|
-
*
|
|
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
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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.
|
|
163
|
-
* `domain`
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
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
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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 {
|
|
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:
|
|
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 &&
|
|
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
|
+
}
|
package/esm/util/analytics.d.ts
CHANGED
|
@@ -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:
|
|
9
|
+
tracks: TrackConfigEntry[];
|
|
13
10
|
assemblies: unknown[];
|
|
14
11
|
plugins?: {
|
|
15
12
|
name?: string;
|
|
16
13
|
}[];
|
|
17
14
|
};
|
|
18
15
|
session?: {
|
|
19
|
-
sessionTracks:
|
|
16
|
+
sessionTracks: {
|
|
17
|
+
type: string;
|
|
18
|
+
}[];
|
|
20
19
|
views: unknown[];
|
|
21
20
|
};
|
|
22
21
|
version: string;
|
package/esm/util/analytics.js
CHANGED
|
@@ -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 =>
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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>;
|