@jbrowse/core 5.0.0-beta.1 → 5.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -43
- package/esm/BaseFeatureWidget/BaseFeatureDetail/FeatureDetails.d.ts +2 -3
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.d.ts +4 -0
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.js +17 -0
- package/esm/BaseFeatureWidget/BaseFeatureDetail/index.js +4 -3
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.d.ts +2 -2
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/test_data/NCDN.d.ts +2 -2
- package/esm/BaseFeatureWidget/index.d.ts +1 -1
- package/esm/BaseFeatureWidget/stateModelFactory.d.ts +8 -1
- package/esm/BaseFeatureWidget/stateModelFactory.js +6 -0
- package/esm/BaseFeatureWidget/types.d.ts +10 -2
- package/esm/PluginLoader.d.ts +1 -0
- package/esm/PluginLoader.js +52 -8
- package/esm/PluginManager.d.ts +99 -17
- package/esm/PluginManager.js +194 -26
- package/esm/ReExports/BaseFeatureDetails.js +1 -1
- package/esm/ReExports/MuiDataGridReExports.d.ts +1 -0
- package/esm/ReExports/MuiDataGridReExports.js +2 -2
- package/esm/ReExports/barrelOnlyNames.d.ts +5 -0
- package/esm/ReExports/barrelOnlyNames.js +63 -0
- package/esm/ReExports/modules.js +31 -109
- package/esm/ReExports/publicTracks.d.ts +1 -0
- package/esm/ReExports/publicTracks.js +14 -0
- package/esm/ReExports/publicUi.d.ts +4 -0
- package/esm/ReExports/publicUi.js +33 -0
- package/esm/ReExports/publicUtil.d.ts +1 -1
- package/esm/ReExports/publicUtil.js +8 -4
- package/esm/ReExports/sharedModules.d.ts +96 -0
- package/esm/ReExports/sharedModules.js +66 -0
- package/esm/ReExports/uiStub.d.ts +2 -0
- package/esm/ReExports/uiStub.js +11 -0
- package/esm/ReExports/workerModules.d.ts +2 -0
- package/esm/ReExports/workerModules.js +15 -0
- package/esm/ReExports/workerNamespaceNames.d.ts +11 -0
- package/esm/ReExports/workerNamespaceNames.js +281 -0
- package/esm/configuration/configurationSchema.d.ts +5 -38
- package/esm/configuration/configurationSchema.js +64 -40
- package/esm/configuration/index.d.ts +2 -2
- package/esm/configuration/index.js +1 -1
- package/esm/configuration/promotableDefaults.d.ts +54 -19
- package/esm/configuration/promotableDefaults.js +117 -48
- package/esm/configuration/promotablePin.d.ts +50 -40
- package/esm/configuration/promotableResolve.d.ts +1 -1
- package/esm/configuration/promotableResolve.js +1 -1
- package/esm/configuration/tabixIndexFields.d.ts +2 -2
- package/esm/configuration/tabixIndexFields.js +2 -2
- package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.d.ts +12 -0
- package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +35 -4
- package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +41 -0
- package/esm/data_adapters/BaseAdapter/featureDensity.js +32 -0
- package/esm/data_adapters/BaseAdapter/index.d.ts +2 -1
- package/esm/data_adapters/BaseAdapter/index.js +1 -0
- package/esm/data_adapters/BaseAdapter/stats.d.ts +33 -1
- package/esm/data_adapters/BaseAdapter/stats.js +52 -13
- package/esm/data_adapters/BaseAdapter/types.d.ts +3 -8
- package/esm/pluggableElementTypes/AdapterType.d.ts +2 -1
- package/esm/pluggableElementTypes/AdapterType.js +1 -1
- package/esm/pluggableElementTypes/DisplayType.d.ts +4 -12
- package/esm/pluggableElementTypes/DisplayType.js +3 -10
- package/esm/pluggableElementTypes/LazyStateModelElement.d.ts +27 -0
- package/esm/pluggableElementTypes/LazyStateModelElement.js +71 -0
- package/esm/pluggableElementTypes/ViewType.d.ts +20 -4
- package/esm/pluggableElementTypes/ViewType.js +32 -4
- package/esm/pluggableElementTypes/extendElementType.d.ts +1 -2
- package/esm/pluggableElementTypes/extendElementType.js +9 -5
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +44 -3
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +75 -2
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +21 -0
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +34 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +49 -0
- package/esm/pluggableElementTypes/models/BaseViewModel.js +28 -0
- package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +14 -10
- package/esm/pluggableElementTypes/models/assertDisplayContract.js +25 -32
- package/esm/pluggableElementTypes/models/baseTrackConfig.js +7 -1
- package/esm/rpc/RpcRegistry.d.ts +10 -0
- package/esm/rpc/byteBudget.d.ts +9 -0
- package/esm/rpc/coreRpcMethods.d.ts +1 -0
- package/esm/rpc/coreRpcMethods.js +1 -0
- package/esm/rpc/methods/CoreGetFeatureDensity.d.ts +11 -0
- package/esm/rpc/methods/CoreGetFeatureDensity.js +22 -0
- package/esm/svg/svgReady.d.ts +1 -0
- package/esm/ui/BaseExportSvgDialog.d.ts +1 -1
- package/esm/ui/CascadingMenu.js +30 -8
- package/esm/ui/ErrorMessageStackTraceDialog.js +5 -6
- package/esm/ui/ExportSvgDialog.d.ts +1 -1
- package/esm/ui/LoadingOverlay.d.ts +14 -0
- package/esm/ui/LoadingOverlay.js +1 -1
- package/esm/ui/MenuTypes.d.ts +14 -5
- package/esm/ui/MenuTypes.js +3 -0
- package/esm/ui/PaletteContext.d.ts +11 -4
- package/esm/ui/PaletteContext.js +34 -5
- package/esm/ui/PinAdornment.js +31 -43
- package/esm/ui/ScrollChrome.d.ts +16 -0
- package/esm/ui/ScrollChrome.js +12 -0
- package/esm/ui/ScrollZoomToggle.d.ts +4 -3
- package/esm/ui/ScrollZoomToggle.js +4 -3
- package/esm/ui/SizeSliderRow.js +1 -1
- package/esm/ui/index.d.ts +3 -3
- package/esm/ui/index.js +4 -4
- package/esm/ui/launchViewMenu.js +7 -1
- package/esm/ui/legendMenuItem.d.ts +10 -16
- package/esm/ui/legendMenuItem.js +9 -20
- package/esm/ui/makeSizeMenu.js +1 -1
- package/esm/ui/menuItemAdornment.d.ts +2 -3
- package/esm/ui/menuItemAdornment.js +2 -3
- package/esm/ui/menuItems.d.ts +2 -3
- package/esm/ui/menuItems.js +1 -2
- package/esm/ui/palette.d.ts +11 -11
- package/esm/ui/palette.js +11 -11
- package/esm/ui/promotablePinCoverage.d.ts +16 -2
- package/esm/ui/promotablePinCoverage.js +40 -3
- package/esm/ui/theme.js +5 -0
- package/esm/ui/toggleMenuItems.d.ts +14 -9
- package/esm/ui/toggleMenuItems.js +32 -15
- package/esm/util/Base1DUtils.d.ts +4 -2
- package/esm/util/Base1DUtils.js +12 -4
- package/esm/util/SvgCanvas.js +4 -3
- package/esm/util/adapterConfigKey.d.ts +12 -0
- package/esm/util/adapterConfigKey.js +14 -0
- package/esm/util/adapterMetadata.js +2 -1
- package/esm/util/addAndShowTrack.d.ts +1 -1
- package/esm/util/addAndShowTrack.js +5 -1
- package/esm/util/addTrackFromWidget.js +2 -2
- package/esm/util/analytics.js +4 -5
- package/esm/util/cacheBudgets.d.ts +22 -19
- package/esm/util/cacheBudgets.js +22 -19
- package/esm/util/clampToContig.d.ts +17 -0
- package/esm/util/clampToContig.js +19 -0
- package/esm/util/colorBits.d.ts +2 -1
- package/esm/util/colorBits.js +2 -5
- package/esm/util/createStopTokenRotation.d.ts +9 -7
- package/esm/util/createStopTokenRotation.js +7 -27
- package/esm/util/featureTypes.d.ts +24 -0
- package/esm/util/featureTypes.js +35 -0
- package/esm/util/fetchContext.d.ts +2 -2
- package/esm/util/fetchContext.js +2 -2
- package/esm/util/fetchPhases.d.ts +10 -10
- package/esm/util/hooks.d.ts +8 -0
- package/esm/util/hooks.js +27 -0
- package/esm/util/index.d.ts +6 -5
- package/esm/util/index.js +5 -4
- package/esm/util/installFetch.d.ts +71 -34
- package/esm/util/installFetch.js +57 -29
- package/esm/util/installInitAutorun.d.ts +14 -13
- package/esm/util/installInitAutorun.js +17 -17
- package/esm/util/installPrerequisiteFetch.d.ts +86 -0
- package/esm/util/installPrerequisiteFetch.js +73 -0
- package/esm/util/lazyAfterAttach.d.ts +13 -0
- package/esm/util/lazyAfterAttach.js +31 -0
- package/esm/util/legendCandidates.d.ts +50 -0
- package/esm/util/legendCandidates.js +87 -0
- package/esm/util/mstUtils.d.ts +12 -1
- package/esm/util/mstUtils.js +20 -2
- package/esm/util/openFeatureWidget.d.ts +5 -0
- package/esm/util/openFeatureWidget.js +1 -0
- package/esm/util/openViews.d.ts +16 -0
- package/esm/util/openViews.js +22 -0
- package/esm/util/parentWalk.d.ts +1 -1
- package/esm/util/parentWalk.js +1 -1
- package/esm/util/pluginStore.d.ts +1 -0
- package/esm/util/pluginStore.js +8 -1
- package/esm/util/range.d.ts +13 -9
- package/esm/util/range.js +17 -8
- package/esm/util/renderToStaticMarkup.js +10 -0
- package/esm/util/rxjs.d.ts +16 -0
- package/esm/util/rxjs.js +49 -0
- package/esm/util/scheduleDetachedDestroy.d.ts +18 -0
- package/esm/util/scheduleDetachedDestroy.js +24 -0
- package/esm/util/scrollLatch.d.ts +1 -0
- package/esm/util/scrollLatch.js +8 -0
- package/esm/util/stopToken.d.ts +6 -5
- package/esm/util/tabix.d.ts +10 -4
- package/esm/util/tabix.js +11 -5
- package/esm/util/trackConfigDelta.d.ts +6 -6
- package/esm/util/trackConfigDelta.js +3 -3
- package/esm/util/tracks.d.ts +48 -0
- package/esm/util/tracks.js +192 -66
- package/esm/util/types/index.d.ts +58 -10
- package/esm/util/types/index.js +3 -0
- package/esm/util/unknownSnapshotKeys.d.ts +36 -0
- package/esm/util/unknownSnapshotKeys.js +69 -0
- package/esm/util/usePanelVirtualScroll.d.ts +6 -2
- package/esm/util/usePanelVirtualScroll.js +6 -2
- package/esm/util/useRowVirtualScroll.d.ts +1 -1
- package/esm/util/useRowVirtualScroll.js +1 -1
- package/esm/util/useVirtualScrollWheel.js +11 -1
- package/esm/util/viewStatus.d.ts +23 -0
- package/esm/util/viewStatus.js +17 -0
- package/esm/util/wheelZoom.d.ts +3 -1
- package/esm/util/wheelZoom.js +39 -14
- package/esm/util/whenViewSettled.d.ts +17 -4
- package/esm/util/whenViewSettled.js +19 -6
- package/esm/util/withLaunchInput.d.ts +168 -0
- package/esm/util/withLaunchInput.js +273 -0
- package/package.json +19 -11
- package/esm/ui/promotableMenuItems.d.ts +0 -19
- package/esm/ui/promotableMenuItems.js +0 -89
- package/esm/util/applyInitSettings.d.ts +0 -79
- package/esm/util/applyInitSettings.js +0 -91
package/esm/util/tracks.js
CHANGED
|
@@ -8,9 +8,9 @@ export { getFileName } from './getFileName.js';
|
|
|
8
8
|
// it. `fetchContext.ts` takes it from mstUtils instead, which is what keeps the
|
|
9
9
|
// configuration schemas this file pulls in out of the fetch harness's graph.
|
|
10
10
|
export { getRpcSessionId } from './parentWalk.js';
|
|
11
|
-
import { getParent, getSnapshot, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
|
|
11
|
+
import { getParent, getSnapshot, hasParent, isAlive, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
|
|
12
12
|
import { observable, runInAction, untracked } from 'mobx';
|
|
13
|
-
import {
|
|
13
|
+
import { readConfObject } from '../configuration/index.js';
|
|
14
14
|
import { getFileHandle, storeFileHandle, verifyPermission, } from './fileHandleStore.js';
|
|
15
15
|
import { getFileName } from './getFileName.js';
|
|
16
16
|
import { getContainingView, getEnv, getSession, objectHash, } from './mstUtils.js';
|
|
@@ -76,12 +76,22 @@ export function isSameAssemblyName(a, b, assemblyManager) {
|
|
|
76
76
|
* `ReferenceSequenceTrack` does not — its schema omits the slot deliberately
|
|
77
77
|
* (see `createReferenceSeqTrackConfig`), because the assembly config holding it
|
|
78
78
|
* IS its assembly, so the name is read off the parent instead.
|
|
79
|
+
*
|
|
80
|
+
* That makes a sequence track's assembly **positional**, so a copy of one that
|
|
81
|
+
* is not inside its assembly cannot answer at all — and a detached config is
|
|
82
|
+
* what every hydration path produces (`hydrateTrackConfig`, and the working
|
|
83
|
+
* copy a non-admin's edits would go to, ADR-032). `hasParent` is the guard:
|
|
84
|
+
* without it the walk raises MST's "Failed to find the parent" from a helper
|
|
85
|
+
* whose other two failure modes are a plain `undefined`.
|
|
79
86
|
*/
|
|
80
87
|
function confAssemblyNames(conf) {
|
|
81
88
|
const trackAssemblyNames = readConfObject(conf, 'assemblyNames');
|
|
82
89
|
if (trackAssemblyNames) {
|
|
83
90
|
return trackAssemblyNames;
|
|
84
91
|
}
|
|
92
|
+
if (!hasParent(conf)) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
85
95
|
const parent = getParent(conf);
|
|
86
96
|
return 'sequence' in parent
|
|
87
97
|
? [readConfObject(parent, 'name')]
|
|
@@ -368,8 +378,12 @@ export function guessAdapter(file, index, adapterHint, model) {
|
|
|
368
378
|
}
|
|
369
379
|
export function guessTrackType(adapterType, model, file) {
|
|
370
380
|
if (model) {
|
|
371
|
-
|
|
372
|
-
|
|
381
|
+
// getEnv(model), not getEnv(getSession(model)): the env is tree-wide, so
|
|
382
|
+
// the session hop only narrowed what may be passed here — a session itself
|
|
383
|
+
// has no session ancestor, so guessAdapter above accepted one and this
|
|
384
|
+
// threw on it
|
|
385
|
+
const { pluginManager } = getEnv(model);
|
|
386
|
+
const trackTypeGuesser = pluginManager.evaluateExtensionPoint(
|
|
373
387
|
/** #extensionPoint Core-guessTrackTypeForLocation | sync | Guess a track type from a file location */
|
|
374
388
|
'Core-guessTrackTypeForLocation', () => undefined);
|
|
375
389
|
const trackType = trackTypeGuesser(adapterType, file);
|
|
@@ -392,24 +406,35 @@ export function guessTrackType(adapterType, model, file) {
|
|
|
392
406
|
*/
|
|
393
407
|
export function guessTrackConf(input, pluginManager, assemblyName) {
|
|
394
408
|
const { uri, index, ...extra } = typeof input === 'string' ? { uri: input } : input;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
409
|
+
return {
|
|
410
|
+
...guessTrackConfForLocation({ uri, locationType: 'UriLocation' }, index ? { uri: index, locationType: 'UriLocation' } : undefined, pluginManager, assemblyName),
|
|
411
|
+
...extra,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* {@link guessTrackConf} over an already-built {@link FileLocation}, for a
|
|
416
|
+
* caller holding a location the loose `{ uri }` vocabulary cannot spell — a
|
|
417
|
+
* desktop local path. Kept separate so `LooseTrackInput` stays URI-only: it
|
|
418
|
+
* sits on the track-config union and the root config's `tracks`, where a
|
|
419
|
+
* local path would be a config a web deployment could not open.
|
|
420
|
+
*
|
|
421
|
+
* The `trackId` is a content hash of the adapter, not a timestamp, so adding
|
|
422
|
+
* the same file twice is idempotent — `addToSession` dedupes on it.
|
|
423
|
+
*/
|
|
424
|
+
export function guessTrackConfForLocation(file, index, pluginManager, assemblyName) {
|
|
399
425
|
const adapterGuesser = pluginManager.evaluateExtensionPoint('Core-guessAdapterForLocation', () => undefined);
|
|
400
|
-
const adapter = adapterGuesser(file,
|
|
426
|
+
const adapter = adapterGuesser(file, index, undefined);
|
|
427
|
+
const name = getFileName(file);
|
|
401
428
|
if (!adapter || adapter.type === UNKNOWN) {
|
|
402
|
-
throw new Error(`could not infer a track type from "${
|
|
429
|
+
throw new Error(`could not infer a track type from "${name}"; pass a full track config instead`);
|
|
403
430
|
}
|
|
404
431
|
const trackTypeGuesser = pluginManager.evaluateExtensionPoint('Core-guessTrackTypeForLocation', () => undefined);
|
|
405
|
-
const name = getFileName(file);
|
|
406
432
|
return {
|
|
407
433
|
trackId: `${name}-${objectHash(adapter).slice(0, 8)}`,
|
|
408
434
|
type: trackTypeGuesser(adapter.type, file) ?? 'FeatureTrack',
|
|
409
435
|
name,
|
|
410
436
|
assemblyNames: assemblyName ? [assemblyName] : [],
|
|
411
437
|
adapter,
|
|
412
|
-
...extra,
|
|
413
438
|
};
|
|
414
439
|
}
|
|
415
440
|
/**
|
|
@@ -572,6 +597,57 @@ export function normalizeTrackInit(t) {
|
|
|
572
597
|
};
|
|
573
598
|
}
|
|
574
599
|
}
|
|
600
|
+
function resolveTrackDisplayChoice(self, trackId, displayInitialSnapshot, inlineConf) {
|
|
601
|
+
const { pluginManager } = getEnv(self);
|
|
602
|
+
const session = getSession(self);
|
|
603
|
+
// `trackId` seeds the expansion because `guessTrackConf` otherwise
|
|
604
|
+
// synthesizes one from the filename, and the dedupe in the callers and
|
|
605
|
+
// `hideTrackGeneric` both look the track up by the id the caller passed.
|
|
606
|
+
// Only for the loose form: spreading a live MST node (SvInspector's
|
|
607
|
+
// circular track) would flatten it out of the `isStateTreeNode` branch.
|
|
608
|
+
const inlineRawConf = isLooseTrackConfig(inlineConf)
|
|
609
|
+
? expandLooseTrackConfig({ trackId, ...inlineConf }, pluginManager)
|
|
610
|
+
: inlineConf;
|
|
611
|
+
const rawConf = inlineRawConf ?? session.getTrackById(trackId);
|
|
612
|
+
if (!rawConf) {
|
|
613
|
+
throw new Error(`Could not resolve identifier "${trackId}"`);
|
|
614
|
+
}
|
|
615
|
+
const confSnapshot = structuredClone(isStateTreeNode(rawConf) ? getSnapshot(rawConf) : rawConf);
|
|
616
|
+
const conf = pluginManager.evaluateExtensionPoint('Core-preProcessTrackConfig', confSnapshot);
|
|
617
|
+
const trackType = pluginManager.getTrackType(conf.type);
|
|
618
|
+
// Validate only a conf that is not already in the tree. `configSchema.create`
|
|
619
|
+
// here builds an entire config node and throws it away purely to surface a
|
|
620
|
+
// nice error; its preProcessSnapshot also re-runs Core-preProcessTrackConfig
|
|
621
|
+
// on a snapshot this function just preprocessed. A conf that is a state tree
|
|
622
|
+
// node was validated when MST created it, so all of that is pure waste on
|
|
623
|
+
// the common showTrack path.
|
|
624
|
+
if (!isStateTreeNode(rawConf)) {
|
|
625
|
+
try {
|
|
626
|
+
trackType.configSchema.create(conf, getEnv(self));
|
|
627
|
+
}
|
|
628
|
+
catch (e) {
|
|
629
|
+
throw new Error(`Track "${trackId}" has an invalid configuration: ${e}`, {
|
|
630
|
+
cause: e,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// A track container that isn't itself a view — a synteny level, which owns
|
|
635
|
+
// a track list but has no width and no registered view type — takes the
|
|
636
|
+
// display choice from the view it sits in, which is the same node
|
|
637
|
+
// getContainingView already resolves to for everything else beneath it.
|
|
638
|
+
const view = isViewModel(self) ? self : getContainingView(self);
|
|
639
|
+
const viewType = pluginManager.getViewType(view.type);
|
|
640
|
+
const picked = pickDisplayForView({
|
|
641
|
+
declaredDisplays: conf.displays ?? [],
|
|
642
|
+
requestedType: displayInitialSnapshot.type,
|
|
643
|
+
trackDisplayTypes: trackType.displayTypes.map(d => d.name),
|
|
644
|
+
viewDisplayTypes: viewType.displayTypes.map(d => d.name),
|
|
645
|
+
});
|
|
646
|
+
if (!picked) {
|
|
647
|
+
throw new Error(`Could not find a compatible display for view type ${view.type}`);
|
|
648
|
+
}
|
|
649
|
+
return { rawConf, conf, trackType, picked };
|
|
650
|
+
}
|
|
575
651
|
export function showTrackGeneric(self, trackId,
|
|
576
652
|
// `object`, annotated, rather than inferred from the default. A bare `{}`
|
|
577
653
|
// accepts every non-nullish value, numbers included, which is how a synteny
|
|
@@ -597,49 +673,28 @@ inlineConf) {
|
|
|
597
673
|
// snackbars. Config is validated before the push so the open set never holds
|
|
598
674
|
// a broken track.
|
|
599
675
|
try {
|
|
600
|
-
const rawConf = (
|
|
601
|
-
session.getTrackById(trackId);
|
|
602
|
-
if (!rawConf) {
|
|
603
|
-
throw new Error(`Could not resolve identifier "${trackId}"`);
|
|
604
|
-
}
|
|
605
|
-
const confSnapshot = structuredClone(isStateTreeNode(rawConf) ? getSnapshot(rawConf) : rawConf);
|
|
606
|
-
const conf = pluginManager.evaluateExtensionPoint('Core-preProcessTrackConfig', confSnapshot);
|
|
607
|
-
const trackType = pluginManager.getTrackType(conf.type);
|
|
608
|
-
// Validate only a conf that is not already in the tree. `configSchema.create`
|
|
609
|
-
// here builds an entire config node and throws it away purely to surface a
|
|
610
|
-
// nice error; its preProcessSnapshot also re-runs Core-preProcessTrackConfig
|
|
611
|
-
// on a snapshot this function just preprocessed. A conf that is a state tree
|
|
612
|
-
// node was validated when MST created it, so all of that is pure waste on
|
|
613
|
-
// the common showTrack path.
|
|
614
|
-
if (!isStateTreeNode(rawConf)) {
|
|
615
|
-
try {
|
|
616
|
-
trackType.configSchema.create(conf, getEnv(self));
|
|
617
|
-
}
|
|
618
|
-
catch (e) {
|
|
619
|
-
throw new Error(`Track "${trackId}" has an invalid configuration: ${e}`, { cause: e });
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
// A track container that isn't itself a view — a synteny level, which owns
|
|
623
|
-
// a track list but has no width and no registered view type — takes the
|
|
624
|
-
// display choice from the view it sits in, which is the same node
|
|
625
|
-
// getContainingView already resolves to for everything else beneath it.
|
|
626
|
-
const view = isViewModel(self) ? self : getContainingView(self);
|
|
627
|
-
const viewType = pluginManager.getViewType(view.type);
|
|
628
|
-
const picked = pickDisplayForView({
|
|
629
|
-
declaredDisplays: conf.displays ?? [],
|
|
630
|
-
requestedType: displayInitialSnapshot.type,
|
|
631
|
-
trackDisplayTypes: trackType.displayTypes.map(d => d.name),
|
|
632
|
-
viewDisplayTypes: viewType.displayTypes.map(d => d.name),
|
|
633
|
-
});
|
|
634
|
-
if (!picked) {
|
|
635
|
-
throw new Error(`Could not find a compatible display for view type ${view.type}`);
|
|
636
|
-
}
|
|
676
|
+
const { rawConf, conf, trackType, picked } = resolveTrackDisplayChoice(self, trackId, displayInitialSnapshot, inlineConf);
|
|
637
677
|
const { type: displayType, conf: displayConf } = picked;
|
|
638
678
|
const displayId = displayConf?.displayId ?? `${trackId}-${displayType}`;
|
|
679
|
+
// a registered display whose model is not loaded yet: the load re-enters
|
|
680
|
+
// through the view's own showTrack action, so a synchronous caller gets the
|
|
681
|
+
// track a tick late; a caller that needs it back awaits launchTrack
|
|
682
|
+
const displayRecord = pluginManager.resolveDisplayTypeRecord(displayType);
|
|
683
|
+
if (displayRecord && !displayRecord.isStateModelLoaded) {
|
|
684
|
+
void launchTrackGeneric(self, trackId, initialSnapshot, displayInitialSnapshot, inlineConf);
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
639
687
|
const track = trackType.stateModel.create({
|
|
640
688
|
...initialSnapshot,
|
|
641
689
|
type: conf.type,
|
|
642
|
-
|
|
690
|
+
// `rawConf`, not the caller's `inlineConf`: the loose `{ trackId, uri }`
|
|
691
|
+
// form has no `type` for the config schema to dispatch on, and the
|
|
692
|
+
// concrete schema this lands on carries no loose preprocessor — that one
|
|
693
|
+
// is on `pluggableConfigSchemaType('track')`, which a track model's
|
|
694
|
+
// `ConfigurationReference` is not. Not `conf` either: that has already
|
|
695
|
+
// run Core-preProcessTrackConfig, which the schema's own
|
|
696
|
+
// preProcessSnapshot runs again.
|
|
697
|
+
configuration: inlineConf ? rawConf : trackId,
|
|
643
698
|
displays: [
|
|
644
699
|
{
|
|
645
700
|
...displayConf,
|
|
@@ -652,23 +707,30 @@ inlineConf) {
|
|
|
652
707
|
self.tracks.push(track);
|
|
653
708
|
// Display settings (height, color, …) are config slots now, not display
|
|
654
709
|
// instance props — passed in the display snapshot they'd be dropped as
|
|
655
|
-
// unknown MST keys.
|
|
656
|
-
// display config so they take effect and
|
|
657
|
-
//
|
|
710
|
+
// unknown MST keys. applyDisplaySettings routes the ones that are real
|
|
711
|
+
// slots onto the persistent display config so they take effect and
|
|
712
|
+
// survive hide/retick (#5591), running the schema's preProcessSnapshot
|
|
713
|
+
// first so shorthand expansions and legacy-key migrations reach this path
|
|
714
|
+
// too — a session spec, share link, or embed writes slots here rather
|
|
715
|
+
// than creating a config from a snapshot, and those surfaces are meant to
|
|
716
|
+
// speak the same vocabulary. Runs after the push so the display's config
|
|
717
|
+
// reference can resolve. Slots only, deliberately: this is the
|
|
718
|
+
// declarative surface, and `allowSetters` stays off so a spec key cannot
|
|
719
|
+
// reach internal display actions.
|
|
658
720
|
//
|
|
659
|
-
//
|
|
660
|
-
// and
|
|
661
|
-
//
|
|
662
|
-
// snapshot
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
721
|
+
// `type` is dropped first because this function already consumed it
|
|
722
|
+
// (pickDisplayForView above), and only `failed` — a key whose write threw —
|
|
723
|
+
// is notified: `unapplied` here also holds the MST display props the
|
|
724
|
+
// snapshot spread above already applied, so notifying on it would report a
|
|
725
|
+
// correct call as broken. A throwing slot value used to reach the outer
|
|
726
|
+
// catch and raise a snackbar; per-key catching kept the track intact but
|
|
727
|
+
// took the message away with it.
|
|
728
|
+
const displaySettings = Object.fromEntries(Object.entries(displayInitialSnapshot).filter(([key]) => key !== 'type'));
|
|
729
|
+
const report = track.applyDisplaySettings(displaySettings);
|
|
730
|
+
if (report.failed.length) {
|
|
731
|
+
session.notifyError(`Track "${trackId}" rejected ${report.failed
|
|
732
|
+
.map(f => `${f.key}: ${f.error}`)
|
|
733
|
+
.join('; ')}`);
|
|
672
734
|
}
|
|
673
735
|
// if this track came from a connection, persist its config so it survives
|
|
674
736
|
// reload without re-establishing the connection (no-op otherwise)
|
|
@@ -708,6 +770,32 @@ export function toggleTrackGeneric(self, trackId) {
|
|
|
708
770
|
? false
|
|
709
771
|
: !!showTrackGeneric(self, trackId);
|
|
710
772
|
}
|
|
773
|
+
export async function launchTrackGeneric(self, trackId, initialSnapshot = {}, displayInitialSnapshot = {}, inlineConf) {
|
|
774
|
+
const { pluginManager } = getEnv(self);
|
|
775
|
+
const session = getSession(self);
|
|
776
|
+
const found = self.tracks.find(t => t.configuration.trackId === trackId);
|
|
777
|
+
if (found) {
|
|
778
|
+
return found;
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
const { picked } = resolveTrackDisplayChoice(self, trackId, displayInitialSnapshot, inlineConf);
|
|
782
|
+
await pluginManager.resolveDisplayTypeRecord(picked.type)?.loadStateModel();
|
|
783
|
+
}
|
|
784
|
+
catch (e) {
|
|
785
|
+
session.notifyError(`${e}`, e);
|
|
786
|
+
return undefined;
|
|
787
|
+
}
|
|
788
|
+
if (!isAlive(self)) {
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
return self.showTrack?.(trackId, initialSnapshot, displayInitialSnapshot, inlineConf);
|
|
792
|
+
}
|
|
793
|
+
// the hide runs in the synchronous prologue, so it keeps action context
|
|
794
|
+
export async function launchToggleTrackGeneric(self, trackId) {
|
|
795
|
+
return hideTrackGeneric(self, trackId)
|
|
796
|
+
? false
|
|
797
|
+
: !!(await launchTrackGeneric(self, trackId));
|
|
798
|
+
}
|
|
711
799
|
/**
|
|
712
800
|
* Every track config the session can show, connection-supplied ones included.
|
|
713
801
|
*
|
|
@@ -726,3 +814,41 @@ export function allSessionTracks(session) {
|
|
|
726
814
|
? [...session.tracks, ...connectionTracks]
|
|
727
815
|
: session.tracks;
|
|
728
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* The session's annotation for `assemblyName`, as trackIds: what a view opened
|
|
819
|
+
* on another genome starts with. Only FeatureTracks, not everything configured
|
|
820
|
+
* for the assembly — an alignments track turned on behind the user's back
|
|
821
|
+
* fetches data nobody asked for, and a FeatureTrack over a few kb is one tabix
|
|
822
|
+
* range query.
|
|
823
|
+
*/
|
|
824
|
+
export function annotationTrackIds(session, assemblyName) {
|
|
825
|
+
return allSessionTracks(session).flatMap(track => readConfObject(track, 'type') === 'FeatureTrack' &&
|
|
826
|
+
readConfObject(track, 'assemblyNames').some(name => isSameAssemblyName(name, assemblyName, session.assemblyManager))
|
|
827
|
+
? [readConfObject(track, 'trackId')]
|
|
828
|
+
: []);
|
|
829
|
+
}
|
|
830
|
+
function isNavigableView(view) {
|
|
831
|
+
return 'navToLocString' in view;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* One assembly on its own in a linear genome view, at `loc`. Keyed on `id`:
|
|
835
|
+
* following several alignments to one genome re-navigates the view rather than
|
|
836
|
+
* stacking a new one each time.
|
|
837
|
+
*/
|
|
838
|
+
export async function openAssemblyInLinearView({ session, id, assemblyName, loc, tracks, }) {
|
|
839
|
+
const view = session.views.find(v => v.id === id);
|
|
840
|
+
if (view && isNavigableView(view)) {
|
|
841
|
+
await view.navToLocString(loc, assemblyName);
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
session.addView('LinearGenomeView', {
|
|
845
|
+
id,
|
|
846
|
+
assembly: assemblyName,
|
|
847
|
+
loc,
|
|
848
|
+
tracks,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
export function openMateLabel(assemblyName) {
|
|
853
|
+
return `Open ${assemblyName} at the matching region`;
|
|
854
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ViewSnapshotInput } from '../../PluginManager.ts';
|
|
1
2
|
import type TextSearchManager from '../../TextSearch/TextSearchManager.ts';
|
|
2
3
|
import type { AnyConfigurationModel, ResolvableDisplay } from '../../configuration/index.ts';
|
|
3
4
|
import type { BaseInternetAccountModel } from '../../pluggableElementTypes/models/index.ts';
|
|
@@ -22,7 +23,8 @@ export type { AugmentedRegion, BasePlugin, BlobLocation, FileHandleLocation, Fil
|
|
|
22
23
|
export interface AbstractViewContainer extends IStateTreeNode {
|
|
23
24
|
views: AbstractViewModel[];
|
|
24
25
|
removeView(view: AbstractViewModel): void;
|
|
25
|
-
addView(typeName:
|
|
26
|
+
addView<N extends string>(typeName: N, initialState?: NoInfer<ViewSnapshotInput<N>>): AbstractViewModel;
|
|
27
|
+
launchView(typeName: string, initialState?: Record<string, unknown>): Promise<AbstractViewModel>;
|
|
26
28
|
}
|
|
27
29
|
export declare function isViewContainer(thing: unknown): thing is AbstractViewContainer;
|
|
28
30
|
/**
|
|
@@ -32,6 +34,7 @@ export declare function isViewContainer(thing: unknown): thing is AbstractViewCo
|
|
|
32
34
|
*/
|
|
33
35
|
export interface TrackActionView {
|
|
34
36
|
showTrack: (id: string) => void;
|
|
37
|
+
launchTrack: (id: string) => Promise<unknown>;
|
|
35
38
|
getActiveDisplayId?: (trackId: string) => string | undefined;
|
|
36
39
|
}
|
|
37
40
|
/**
|
|
@@ -153,6 +156,21 @@ export interface SessionWithConfigEditing extends AbstractSessionModel {
|
|
|
153
156
|
}): void;
|
|
154
157
|
}
|
|
155
158
|
export declare function isSessionModelWithConfigEditing(t: unknown): t is SessionWithConfigEditing;
|
|
159
|
+
/**
|
|
160
|
+
* abstract interface for a session that keeps a list of views, so that `addView`
|
|
161
|
+
* appends and the view a launch came out of survives it.
|
|
162
|
+
*
|
|
163
|
+
* `AbstractViewContainer` alone does not say this. The single-view embedded
|
|
164
|
+
* products satisfy it with a `views` getter that wraps their one `view` in a
|
|
165
|
+
* fresh plain array and an `addView` that overwrites that view, so "Open in new
|
|
166
|
+
* view" there would destroy the view it was offered from. A view list that is
|
|
167
|
+
* itself part of the state tree is what tells the two apart: only a session
|
|
168
|
+
* that stores its views can hold one more.
|
|
169
|
+
*/
|
|
170
|
+
export interface SessionWithMultipleViews extends AbstractSessionModel {
|
|
171
|
+
views: AbstractViewModel[] & IStateTreeNode;
|
|
172
|
+
}
|
|
173
|
+
export declare function isSessionWithMultipleViews(t: unknown): t is SessionWithMultipleViews;
|
|
156
174
|
/**
|
|
157
175
|
* abstract interface for a session that can swap one of its views for a new one
|
|
158
176
|
* of another type, in the slot the old view occupied.
|
|
@@ -164,7 +182,7 @@ export declare function isSessionModelWithConfigEditing(t: unknown): t is Sessio
|
|
|
164
182
|
* and the guard is what a launcher asks before offering it.
|
|
165
183
|
*/
|
|
166
184
|
export interface SessionWithViewReplacement extends AbstractSessionModel {
|
|
167
|
-
replaceView(view: AbstractViewModel, typeName:
|
|
185
|
+
replaceView<N extends string>(view: AbstractViewModel, typeName: N, initialState?: NoInfer<ViewSnapshotInput<N>>): AbstractViewModel;
|
|
168
186
|
}
|
|
169
187
|
export declare function isSessionWithViewReplacement(t: unknown): t is SessionWithViewReplacement;
|
|
170
188
|
/**
|
|
@@ -188,10 +206,10 @@ export declare function canReplaceView(session: AbstractViewContainer, view: Abs
|
|
|
188
206
|
* that can't replace a view falls back to appending, so a caller passes the
|
|
189
207
|
* source view unconditionally and never has to ask twice.
|
|
190
208
|
*/
|
|
191
|
-
export declare function addOrReplaceView({ session, typeName, initialState, replacing, }: {
|
|
209
|
+
export declare function addOrReplaceView<N extends string>({ session, typeName, initialState, replacing, }: {
|
|
192
210
|
session: AbstractViewContainer;
|
|
193
|
-
typeName:
|
|
194
|
-
initialState?:
|
|
211
|
+
typeName: N;
|
|
212
|
+
initialState?: NoInfer<ViewSnapshotInput<N>>;
|
|
195
213
|
replacing?: AbstractViewModel;
|
|
196
214
|
}): AbstractViewModel;
|
|
197
215
|
/**
|
|
@@ -358,21 +376,30 @@ export interface SessionWithSessionPlugins extends AbstractSessionModel {
|
|
|
358
376
|
removeSessionPlugin: (plugin: PluginDefinition) => void;
|
|
359
377
|
}
|
|
360
378
|
export declare function isSessionWithSessionPlugins(thing: unknown): thing is SessionWithSessionPlugins;
|
|
379
|
+
export interface PermanentPluginsSafeMode {
|
|
380
|
+
reason: 'requested' | 'previousLaunchFailed';
|
|
381
|
+
/** what was loading when the launch that failed did not finish */
|
|
382
|
+
suspects: string[];
|
|
383
|
+
}
|
|
361
384
|
/**
|
|
362
385
|
* A session whose product keeps a plugin list of its own, outside both the
|
|
363
386
|
* config and the session — jbrowse-web's permanent plugins, stored in the
|
|
364
387
|
* browser against the config being viewed, so an install survives a new session
|
|
365
388
|
* and the next visit.
|
|
366
389
|
*
|
|
367
|
-
* The
|
|
368
|
-
*
|
|
369
|
-
* which
|
|
370
|
-
* app reload every plugin change needs anyway.
|
|
390
|
+
* The plugin store is the whole surface: the pin beside an installed plugin
|
|
391
|
+
* writes the list, and an entry the load skipped is a row there with a switch
|
|
392
|
+
* on it, which is what makes the safe mode below something a user can act on.
|
|
371
393
|
*/
|
|
372
394
|
export interface SessionWithPermanentPlugins extends AbstractSessionModel {
|
|
373
|
-
permanentPlugins: PluginDefinition
|
|
395
|
+
permanentPlugins: (PluginDefinition & {
|
|
396
|
+
disabled?: boolean;
|
|
397
|
+
})[];
|
|
398
|
+
permanentPluginsSafeMode: PermanentPluginsSafeMode | undefined;
|
|
374
399
|
addPermanentPlugin: (plugin: PluginDefinition) => void;
|
|
375
400
|
removePermanentPlugin: (plugin: PluginDefinition) => void;
|
|
401
|
+
setPermanentPluginDisabled: (plugin: PluginDefinition, disabled: boolean) => void;
|
|
402
|
+
reloadWithPermanentPlugins: () => void;
|
|
376
403
|
}
|
|
377
404
|
export declare function isSessionWithPermanentPlugins(thing: unknown): thing is SessionWithPermanentPlugins;
|
|
378
405
|
/** abstract interface for a session that manages a global selection */
|
|
@@ -410,6 +437,13 @@ export interface TrackContainer {
|
|
|
410
437
|
* silence.
|
|
411
438
|
*/
|
|
412
439
|
showTrack: (trackId: string, initialSnapshot?: object, displayInitialSnapshot?: Record<string, unknown>, inlineConf?: Record<string, unknown>) => unknown;
|
|
440
|
+
/**
|
|
441
|
+
* The sync pair still works on an unloaded display — it loads and shows a
|
|
442
|
+
* tick later, returning undefined — so these are for a caller that needs
|
|
443
|
+
* the track back.
|
|
444
|
+
*/
|
|
445
|
+
launchTrack: (trackId: string, initialSnapshot?: object, displayInitialSnapshot?: Record<string, unknown>, inlineConf?: Record<string, unknown>) => Promise<unknown>;
|
|
446
|
+
launchToggleTrack: (trackId: string) => Promise<unknown>;
|
|
413
447
|
hideTrack: (trackId: string) => unknown;
|
|
414
448
|
toggleTrack: (trackId: string) => unknown;
|
|
415
449
|
}
|
|
@@ -453,6 +487,20 @@ export interface AbstractViewModel {
|
|
|
453
487
|
* `trackContainerFor` cannot answer that — a walker has no id to ask with.
|
|
454
488
|
*/
|
|
455
489
|
trackContainers?: TrackContainer[];
|
|
490
|
+
/**
|
|
491
|
+
* what this view contributes to the census of what is open: the tracks it
|
|
492
|
+
* holds itself, and the views nested directly inside it. `BaseViewModel`
|
|
493
|
+
* answers both with nothing, so a view declares what it owns instead of the
|
|
494
|
+
* base reading properties whose meaning belongs to the view — see
|
|
495
|
+
* `util/openViews.ts`, where the recursion over them lives.
|
|
496
|
+
*
|
|
497
|
+
* Required, like `bodyMounted`, so a stand-in that means to be enumerated
|
|
498
|
+
* says so; the empty default means forgetting under-reports rather than
|
|
499
|
+
* putting something that is not a track in front of the readiness marker.
|
|
500
|
+
*/
|
|
501
|
+
ownTracks: AbstractTrackModel[];
|
|
502
|
+
/** the views nested directly inside this one — see `ownTracks` */
|
|
503
|
+
ownViews: AbstractViewModel[];
|
|
456
504
|
}
|
|
457
505
|
export declare function isViewModel(thing: unknown): thing is AbstractViewModel;
|
|
458
506
|
type Display = {
|
package/esm/util/types/index.js
CHANGED
|
@@ -18,6 +18,9 @@ export function isSessionModel(thing) {
|
|
|
18
18
|
export function isSessionModelWithConfigEditing(t) {
|
|
19
19
|
return isSessionModel(t) && 'editConfiguration' in t;
|
|
20
20
|
}
|
|
21
|
+
export function isSessionWithMultipleViews(t) {
|
|
22
|
+
return isSessionModel(t) && isStateTreeNode(t.views);
|
|
23
|
+
}
|
|
21
24
|
export function isSessionWithViewReplacement(t) {
|
|
22
25
|
return isSessionModel(t) && 'replaceView' in t;
|
|
23
26
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
/** what a diagnostic calls the view: its snapshot `type`, or the model name */
|
|
3
|
+
export declare function viewLabel(self: IStateTreeNode): string;
|
|
4
|
+
/**
|
|
5
|
+
* What every surface calls this mistake. A session spec launches a view without
|
|
6
|
+
* ever building a snapshot, so it classifies its own keys and reports them
|
|
7
|
+
* through its own session rather than through the sink below — one wording, so
|
|
8
|
+
* a typo reads the same whether it was written in a config or in a URL.
|
|
9
|
+
*/
|
|
10
|
+
export declare function unknownKeysMessage(label: string, keys: string[]): string;
|
|
11
|
+
/**
|
|
12
|
+
* Name the keys a view was handed and could not place, from the partition
|
|
13
|
+
* `withLaunchInput` runs on the snapshot.
|
|
14
|
+
*/
|
|
15
|
+
export declare function reportUnknownKeys(self: IStateTreeNode, keys: string[]): void;
|
|
16
|
+
/**
|
|
17
|
+
* Name the row lists a view refused whole. `withLaunchInput` splits an authored
|
|
18
|
+
* array into the recipes a launcher resolves and the built snapshots MST
|
|
19
|
+
* restores, per entry — but a row list is indexed against the view's `levels`
|
|
20
|
+
* and per-level tracks, so a list holding both kinds has no correct split and
|
|
21
|
+
* is refused rather than renumbered.
|
|
22
|
+
*/
|
|
23
|
+
export declare function reportMalformedRows(self: IStateTreeNode, keys: string[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* What every surface calls v4's nested `init`. v5 takes every setting directly
|
|
26
|
+
* on the view object and unwraps the nesting on the way in, so the settings
|
|
27
|
+
* still arrive — this names the spelling to fix while they do.
|
|
28
|
+
*/
|
|
29
|
+
export declare function legacyInitMessage(label: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Name a snapshot that still writes v4's nested `init`. The console alone,
|
|
32
|
+
* unlike the two reporters above: the settings were applied, so there is
|
|
33
|
+
* nothing for a visitor to act on and a warning toast over a working view is
|
|
34
|
+
* noise. `jbrowse validate` is where the author of the config hears about it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function reportLegacyInit(self: IStateTreeNode): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getType } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import { getNotificationSink } from './sessionServices.js';
|
|
3
|
+
/** what a diagnostic calls the view: its snapshot `type`, or the model name */
|
|
4
|
+
export function viewLabel(self) {
|
|
5
|
+
return self.type ?? getType(self).name;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* What every surface calls this mistake. A session spec launches a view without
|
|
9
|
+
* ever building a snapshot, so it classifies its own keys and reports them
|
|
10
|
+
* through its own session rather than through the sink below — one wording, so
|
|
11
|
+
* a typo reads the same whether it was written in a config or in a URL.
|
|
12
|
+
*/
|
|
13
|
+
export function unknownKeysMessage(label, keys) {
|
|
14
|
+
return `${label} ignored unknown key(s): ${keys.join(', ')}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Name the keys a view was handed and could not place, from the partition
|
|
18
|
+
* `withLaunchInput` runs on the snapshot.
|
|
19
|
+
*/
|
|
20
|
+
export function reportUnknownKeys(self, keys) {
|
|
21
|
+
if (!keys.length) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const message = unknownKeysMessage(viewLabel(self), keys);
|
|
25
|
+
console.warn(message);
|
|
26
|
+
try {
|
|
27
|
+
getNotificationSink(self).notify(message, 'warning');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// a view built outside a session has nowhere to put it
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Name the row lists a view refused whole. `withLaunchInput` splits an authored
|
|
35
|
+
* array into the recipes a launcher resolves and the built snapshots MST
|
|
36
|
+
* restores, per entry — but a row list is indexed against the view's `levels`
|
|
37
|
+
* and per-level tracks, so a list holding both kinds has no correct split and
|
|
38
|
+
* is refused rather than renumbered.
|
|
39
|
+
*/
|
|
40
|
+
export function reportMalformedRows(self, keys) {
|
|
41
|
+
if (!keys.length) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const message = `${viewLabel(self)} refused ${keys.join(', ')}: the list mixes built view snapshots with recipes to open one, and the rows index against the levels between them. Write all of them one way.`;
|
|
45
|
+
console.warn(message);
|
|
46
|
+
try {
|
|
47
|
+
getNotificationSink(self).notify(message, 'warning');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// a view built outside a session has nowhere to put it
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* What every surface calls v4's nested `init`. v5 takes every setting directly
|
|
55
|
+
* on the view object and unwraps the nesting on the way in, so the settings
|
|
56
|
+
* still arrive — this names the spelling to fix while they do.
|
|
57
|
+
*/
|
|
58
|
+
export function legacyInitMessage(label) {
|
|
59
|
+
return `${label} nests its settings under "init", which is deprecated: write every setting directly on the view object.`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Name a snapshot that still writes v4's nested `init`. The console alone,
|
|
63
|
+
* unlike the two reporters above: the settings were applied, so there is
|
|
64
|
+
* nothing for a visitor to act on and a warning toast over a working view is
|
|
65
|
+
* noise. `jbrowse validate` is where the author of the config hears about it.
|
|
66
|
+
*/
|
|
67
|
+
export function reportLegacyInit(self) {
|
|
68
|
+
console.warn(legacyInitMessage(viewLabel(self)));
|
|
69
|
+
}
|
|
@@ -8,11 +8,15 @@ interface PanelScrollTarget {
|
|
|
8
8
|
* the canvas basic display: a plain wheel scrolls it, `shift` keeps scrolling it
|
|
9
9
|
* once `scrollZoom` has claimed the plain wheel for the view, and ctrl/meta is
|
|
10
10
|
* left to the browser and the view's pinch-zoom. Nothing here resizes anything.
|
|
11
|
+
* A sideways-dominant wheel is the view's pan and is declined
|
|
12
|
+
* (`useVirtualScrollWheel`, which says what latching does with that).
|
|
11
13
|
*
|
|
12
14
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
13
15
|
* DOM overflow container, so nothing scrolls them without this. The latch (inside
|
|
14
|
-
* `applyScroll`) owns `preventDefault` but never `stopPropagation
|
|
15
|
-
*
|
|
16
|
+
* `applyScroll`) owns `preventDefault` but never `stopPropagation`: the event
|
|
17
|
+
* still reaches the LGV, which reads that flag and declines a gesture this panel
|
|
18
|
+
* has claimed, so a diagonal wheel scrolls the reads without also panning the
|
|
19
|
+
* genome sideways.
|
|
16
20
|
*
|
|
17
21
|
* ADR-027 keeps wheel-*intent* dispatch per handler, because the panels do not
|
|
18
22
|
* agree on what a gesture means. This is the second of the two rules that
|
|
@@ -4,11 +4,15 @@ import { useVirtualScrollWheel } from './useVirtualScrollWheel.js';
|
|
|
4
4
|
* the canvas basic display: a plain wheel scrolls it, `shift` keeps scrolling it
|
|
5
5
|
* once `scrollZoom` has claimed the plain wheel for the view, and ctrl/meta is
|
|
6
6
|
* left to the browser and the view's pinch-zoom. Nothing here resizes anything.
|
|
7
|
+
* A sideways-dominant wheel is the view's pan and is declined
|
|
8
|
+
* (`useVirtualScrollWheel`, which says what latching does with that).
|
|
7
9
|
*
|
|
8
10
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
9
11
|
* DOM overflow container, so nothing scrolls them without this. The latch (inside
|
|
10
|
-
* `applyScroll`) owns `preventDefault` but never `stopPropagation
|
|
11
|
-
*
|
|
12
|
+
* `applyScroll`) owns `preventDefault` but never `stopPropagation`: the event
|
|
13
|
+
* still reaches the LGV, which reads that flag and declines a gesture this panel
|
|
14
|
+
* has claimed, so a diagonal wheel scrolls the reads without also panning the
|
|
15
|
+
* genome sideways.
|
|
12
16
|
*
|
|
13
17
|
* ADR-027 keeps wheel-*intent* dispatch per handler, because the panels do not
|
|
14
18
|
* agree on what a gesture means. This is the second of the two rules that
|
|
@@ -10,7 +10,7 @@ interface RowScrollTarget {
|
|
|
10
10
|
* Wheel gestures over a **row-stack** panel — the multi-sample variant displays
|
|
11
11
|
* and MAF: `shift`+wheel resizes the rows (a vertical-zoom that keeps the row
|
|
12
12
|
* under the cursor put), a plain wheel scrolls them, and anything the panel
|
|
13
|
-
* can't consume is left to bubble to the view.
|
|
13
|
+
* can't consume — a sideways swipe included — is left to bubble to the view.
|
|
14
14
|
*
|
|
15
15
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
16
16
|
* DOM overflow container, so nothing scrolls them without this.
|
|
@@ -4,7 +4,7 @@ import { useVirtualScrollWheel } from './useVirtualScrollWheel.js';
|
|
|
4
4
|
* Wheel gestures over a **row-stack** panel — the multi-sample variant displays
|
|
5
5
|
* and MAF: `shift`+wheel resizes the rows (a vertical-zoom that keeps the row
|
|
6
6
|
* under the cursor put), a plain wheel scrolls them, and anything the panel
|
|
7
|
-
* can't consume is left to bubble to the view.
|
|
7
|
+
* can't consume — a sideways swipe included — is left to bubble to the view.
|
|
8
8
|
*
|
|
9
9
|
* These panels paint a fixed-size canvas at `-scrollTop` rather than living in a
|
|
10
10
|
* DOM overflow container, so nothing scrolls them without this.
|