@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
|
@@ -43,7 +43,7 @@ export interface FetchPhases<TArgs, TResult, TCtx> {
|
|
|
43
43
|
* gets a dead Retry button — see there.
|
|
44
44
|
*
|
|
45
45
|
* What it must **not** do is move a trigger read of its own under a bail-out.
|
|
46
|
-
* See reference/
|
|
46
|
+
* See reference/FETCH_KEYS.md §"The global-fetch trigger list must be read
|
|
47
47
|
* unconditionally".
|
|
48
48
|
*/
|
|
49
49
|
prepare: () => TArgs | undefined;
|
package/esm/util/formatBytes.js
CHANGED
|
@@ -10,7 +10,7 @@ const units = ['bytes', 'kB', 'MB', 'GB', 'TB'];
|
|
|
10
10
|
export function formatBytes(bytes) {
|
|
11
11
|
let n = bytes;
|
|
12
12
|
let i = 0;
|
|
13
|
-
while (n >= 1000 && i < units.length - 1) {
|
|
13
|
+
while (Number(n.toFixed(1)) >= 1000 && i < units.length - 1) {
|
|
14
14
|
n /= 1000;
|
|
15
15
|
i++;
|
|
16
16
|
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import type PluginManager from '../PluginManager.ts';
|
|
2
1
|
import type { AdapterConfig } from './tracks.ts';
|
|
3
2
|
import type { FileLocation } from './types/data.ts';
|
|
4
3
|
import type { AdapterSpec } from '@jbrowse/add-track-core';
|
|
4
|
+
/**
|
|
5
|
+
* creates a new location from the provided location including the appropriate
|
|
6
|
+
* suffix and location type
|
|
7
|
+
*
|
|
8
|
+
* @param location - the FileLocation
|
|
9
|
+
* @param suffix - the file suffix (e.g. .bam)
|
|
10
|
+
* @returns the constructed location object from the provided parameters
|
|
11
|
+
*/
|
|
12
|
+
export declare function makeIndex(location: FileLocation, suffix: string): import("./index.ts").BlobLocation | import("./index.ts").FileHandleLocation | {
|
|
13
|
+
uri: string;
|
|
14
|
+
locationType: string;
|
|
15
|
+
baseUri?: string | undefined;
|
|
16
|
+
localPath?: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
localPath: string;
|
|
19
|
+
locationType: string;
|
|
20
|
+
};
|
|
5
21
|
/**
|
|
6
22
|
* The adapter config one format-table entry describes: the data file under the
|
|
7
23
|
* field that format's adapter reads it from, plus wherever that adapter expects
|
|
@@ -14,17 +30,12 @@ import type { AdapterSpec } from '@jbrowse/add-track-core';
|
|
|
14
30
|
*/
|
|
15
31
|
export declare function adapterConfigFromSpec(spec: AdapterSpec, file: FileLocation, index?: FileLocation): AdapterConfig | undefined;
|
|
16
32
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* Whether a build can open a format is decided by `hasAdapterType`, not by the
|
|
22
|
-
* table: guessing `BamAdapter` in a build with no alignments plugin writes a
|
|
23
|
-
* track config that fails at render. A plugin therefore registers its adapters
|
|
24
|
-
* and nothing else — there is no format list to keep in step. ADR-077.
|
|
25
|
-
*
|
|
26
|
-
* `CorePlugin` installs this first, so any `addAdapterGuesser` a plugin
|
|
27
|
-
* registers is later in the chain and wins over the table. That is how a format
|
|
28
|
-
* the table cannot express is added, and how a third-party plugin claims one.
|
|
33
|
+
* The track the format table makes of a file: its adapter, the track type that
|
|
34
|
+
* draws it, and the file name as its name. `undefined` for a file no format
|
|
35
|
+
* claims, which is where `guessTrackConf` throws.
|
|
29
36
|
*/
|
|
30
|
-
export declare function
|
|
37
|
+
export declare function guessTrackConfFromTable(file: FileLocation, index?: FileLocation, adapterHint?: string): {
|
|
38
|
+
type: string;
|
|
39
|
+
name: string;
|
|
40
|
+
adapter: AdapterConfig;
|
|
41
|
+
} | undefined;
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { matchFormat, resolveIndexType, trackTypeForAdapter, } from '@jbrowse/add-track-core';
|
|
2
2
|
import { getFileName } from './getFileName.js';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* creates a new location from the provided location including the appropriate
|
|
5
|
+
* suffix and location type
|
|
6
|
+
*
|
|
7
|
+
* @param location - the FileLocation
|
|
8
|
+
* @param suffix - the file suffix (e.g. .bam)
|
|
9
|
+
* @returns the constructed location object from the provided parameters
|
|
10
|
+
*/
|
|
11
|
+
export function makeIndex(location, suffix) {
|
|
12
|
+
if ('uri' in location) {
|
|
13
|
+
return {
|
|
14
|
+
uri: location.uri + suffix,
|
|
15
|
+
locationType: 'UriLocation',
|
|
16
|
+
// carry the parent's baseUri so a derived sibling index resolves against
|
|
17
|
+
// the same config location as the file it indexes
|
|
18
|
+
...(location.baseUri ? { baseUri: location.baseUri } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
else if ('localPath' in location) {
|
|
22
|
+
return {
|
|
23
|
+
localPath: location.localPath + suffix,
|
|
24
|
+
locationType: 'LocalPathLocation',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return location;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
4
31
|
/**
|
|
5
32
|
* The adapter config one format-table entry describes: the data file under the
|
|
6
33
|
* field that format's adapter reads it from, plus wherever that adapter expects
|
|
@@ -39,31 +66,19 @@ export function adapterConfigFromSpec(spec, file, index) {
|
|
|
39
66
|
}
|
|
40
67
|
}
|
|
41
68
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Whether a build can open a format is decided by `hasAdapterType`, not by the
|
|
47
|
-
* table: guessing `BamAdapter` in a build with no alignments plugin writes a
|
|
48
|
-
* track config that fails at render. A plugin therefore registers its adapters
|
|
49
|
-
* and nothing else — there is no format list to keep in step. ADR-077.
|
|
50
|
-
*
|
|
51
|
-
* `CorePlugin` installs this first, so any `addAdapterGuesser` a plugin
|
|
52
|
-
* registers is later in the chain and wins over the table. That is how a format
|
|
53
|
-
* the table cannot express is added, and how a third-party plugin claims one.
|
|
69
|
+
* The track the format table makes of a file: its adapter, the track type that
|
|
70
|
+
* draws it, and the file name as its name. `undefined` for a file no format
|
|
71
|
+
* claims, which is where `guessTrackConf` throws.
|
|
54
72
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
? trackTypeForAdapter(adapterName, file && getFileName(file))
|
|
67
|
-
: undefined);
|
|
73
|
+
export function guessTrackConfFromTable(file, index, adapterHint) {
|
|
74
|
+
const name = getFileName(file);
|
|
75
|
+
const spec = matchFormat(name, adapterHint)?.spec;
|
|
76
|
+
const adapter = spec && adapterConfigFromSpec(spec, file, index);
|
|
77
|
+
return adapter
|
|
78
|
+
? {
|
|
79
|
+
type: trackTypeForAdapter(adapter.type, name) ?? 'FeatureTrack',
|
|
80
|
+
name,
|
|
81
|
+
adapter,
|
|
82
|
+
}
|
|
83
|
+
: undefined;
|
|
68
84
|
}
|
|
69
|
-
// #endregion
|
|
@@ -15,7 +15,7 @@ export function formatRelativeTime(date, now = Date.now()) {
|
|
|
15
15
|
let duration = (Number(date) - now) / 1000;
|
|
16
16
|
let result = rtf.format(Math.round(duration), 'years');
|
|
17
17
|
for (const { amount, unit } of DIVISIONS) {
|
|
18
|
-
if (Math.abs(duration) < amount) {
|
|
18
|
+
if (Math.round(Math.abs(duration)) < amount) {
|
|
19
19
|
result = rtf.format(Math.round(duration), unit);
|
|
20
20
|
break;
|
|
21
21
|
}
|
|
@@ -9,7 +9,8 @@ export declare function resolveUri({ uri, baseUri, }: {
|
|
|
9
9
|
baseUri?: string;
|
|
10
10
|
}): string;
|
|
11
11
|
/**
|
|
12
|
-
* An address with its query string dropped, which is
|
|
12
|
+
* An address with its query string and any username:password dropped, which is
|
|
13
|
+
* what makes it safe to put
|
|
13
14
|
* on screen. A presigned S3 or GCS link carries its credential in the query — a
|
|
14
15
|
* few hundred characters of `?X-Amz-Signature=…` — and a notice that renders one
|
|
15
16
|
* puts it in front of whoever is looking at the screen and into every screenshot
|
|
@@ -13,7 +13,8 @@ export function resolveUri({ uri, baseUri = '', }) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* An address with its query string dropped, which is
|
|
16
|
+
* An address with its query string and any username:password dropped, which is
|
|
17
|
+
* what makes it safe to put
|
|
17
18
|
* on screen. A presigned S3 or GCS link carries its credential in the query — a
|
|
18
19
|
* few hundred characters of `?X-Amz-Signature=…` — and a notice that renders one
|
|
19
20
|
* puts it in front of whoever is looking at the screen and into every screenshot
|
|
@@ -25,7 +26,12 @@ export function resolveUri({ uri, baseUri = '', }) {
|
|
|
25
26
|
* than a typed location.
|
|
26
27
|
*/
|
|
27
28
|
export function redactSource(source) {
|
|
28
|
-
|
|
29
|
+
if (!source.includes('://')) {
|
|
30
|
+
return source;
|
|
31
|
+
}
|
|
32
|
+
return source
|
|
33
|
+
.replace(/^([a-z][a-z\d+.-]*:\/\/)[^/?#]*@/i, '$1')
|
|
34
|
+
.split(/[?#]/)[0];
|
|
29
35
|
}
|
|
30
36
|
/**
|
|
31
37
|
* Where a location's bytes come from, as an address worth showing someone, or
|
package/esm/util/groupKeys.d.ts
CHANGED
|
@@ -46,14 +46,13 @@ export declare function carryGroupDomain<T extends {
|
|
|
46
46
|
export declare function overflowLabel(count: number): string;
|
|
47
47
|
/**
|
|
48
48
|
* Which section each key stacks into once the cap applies: the first
|
|
49
|
-
* `
|
|
50
|
-
* the
|
|
51
|
-
*
|
|
52
|
-
* answer users look for. A domain orders the
|
|
49
|
+
* `MAX_GROUPS` keys in natural order keep their own and the rest fold into
|
|
50
|
+
* the overflow section. The catch-all `''` is held out of the merge, since
|
|
51
|
+
* "lacking the value" is an answer users look for. A domain orders the
|
|
53
52
|
* sections and never decides which exist, so reordering them changes no
|
|
54
53
|
* key's section.
|
|
55
54
|
*/
|
|
56
|
-
export declare function capGroupKeys(keys: Iterable<string
|
|
55
|
+
export declare function capGroupKeys(keys: Iterable<string>): {
|
|
57
56
|
mergedCount: number;
|
|
58
57
|
sectionOf: (key: string) => string;
|
|
59
58
|
};
|
package/esm/util/groupKeys.js
CHANGED
|
@@ -125,18 +125,19 @@ export function overflowLabel(count) {
|
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
127
|
* Which section each key stacks into once the cap applies: the first
|
|
128
|
-
* `
|
|
129
|
-
* the
|
|
130
|
-
*
|
|
131
|
-
* answer users look for. A domain orders the
|
|
128
|
+
* `MAX_GROUPS` keys in natural order keep their own and the rest fold into
|
|
129
|
+
* the overflow section. The catch-all `''` is held out of the merge, since
|
|
130
|
+
* "lacking the value" is an answer users look for. A domain orders the
|
|
132
131
|
* sections and never decides which exist, so reordering them changes no
|
|
133
132
|
* key's section.
|
|
134
133
|
*/
|
|
135
|
-
export function capGroupKeys(keys
|
|
134
|
+
export function capGroupKeys(keys) {
|
|
136
135
|
const ordered = [...new Set(keys)].sort(compareGroupKeys);
|
|
137
136
|
const hasUntagged = ordered.includes('');
|
|
138
137
|
const named = ordered.filter(key => key !== '');
|
|
139
|
-
const merged = ordered.length >
|
|
138
|
+
const merged = ordered.length > MAX_GROUPS
|
|
139
|
+
? named.slice(MAX_GROUPS - (hasUntagged ? 2 : 1))
|
|
140
|
+
: [];
|
|
140
141
|
const mergedSet = new Set(merged);
|
|
141
142
|
return {
|
|
142
143
|
mergedCount: merged.length,
|
package/esm/util/intervals.js
CHANGED
|
@@ -28,10 +28,15 @@ export function mergeIntervals(intervals, padding = 5000) {
|
|
|
28
28
|
* features. `padding` carries the same per-side meaning.
|
|
29
29
|
*/
|
|
30
30
|
export function gatherOverlaps(regions, padding = 5000) {
|
|
31
|
-
const memo =
|
|
31
|
+
const memo = new Map();
|
|
32
32
|
for (const x of regions) {
|
|
33
|
-
memo
|
|
34
|
-
|
|
33
|
+
const group = memo.get(x.refName);
|
|
34
|
+
if (group) {
|
|
35
|
+
group.push(x);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
memo.set(x.refName, [x]);
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
|
-
return
|
|
41
|
+
return [...memo.values()].flatMap(group => mergeIntervals(group, padding));
|
|
37
42
|
}
|
package/esm/util/io/index.js
CHANGED
|
@@ -77,7 +77,9 @@ export function openLocation(location, pluginManager) {
|
|
|
77
77
|
if (!file) {
|
|
78
78
|
throw new Error(`file ("${location.name}") requires permission. Please reopen the file from track settings`);
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
// A File is a snapshot: the handle yields a new one once the file changes
|
|
81
|
+
// on disk, and its chunks must not be the old snapshot's
|
|
82
|
+
return new CachedFilehandle(new BlobFile(file), `filehandle://${location.handleId}@${file.lastModified}:${file.size}`);
|
|
81
83
|
}
|
|
82
84
|
if (isUriLocation(location)) {
|
|
83
85
|
// Check for empty string
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Feature, SimpleFeatureSerializedNoId } from './simpleFeature.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Which isoform of a gene a display shows first: the canvas gene glyph's
|
|
4
|
+
* `longestCoding` collapse and the sequence panel's default transcript both
|
|
5
|
+
* rank with this, so the panel opens on the isoform the track drew. Generic
|
|
6
|
+
* over the feature shape because the worker ranks live `Feature`s and the
|
|
7
|
+
* panel ranks their serialized form.
|
|
8
|
+
*/
|
|
9
|
+
export interface IsoformReader<T> {
|
|
10
|
+
type: (feature: T) => string | undefined;
|
|
11
|
+
start: (feature: T) => number;
|
|
12
|
+
end: (feature: T) => number;
|
|
13
|
+
subfeatures: (feature: T) => readonly T[];
|
|
14
|
+
attribute: (feature: T, field: string) => unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare const featureIsoformReader: IsoformReader<Feature>;
|
|
17
|
+
export declare const serializedIsoformReader: IsoformReader<SimpleFeatureSerializedNoId>;
|
|
18
|
+
/**
|
|
19
|
+
* The attribute naming a gene's representative isoform, and the values of it
|
|
20
|
+
* that do, best first. The canvas display's `canonicalTranscriptField` and
|
|
21
|
+
* `canonicalTranscriptTags` slots default to these.
|
|
22
|
+
*/
|
|
23
|
+
export interface CanonicalTranscripts {
|
|
24
|
+
field: string;
|
|
25
|
+
tags: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
export declare const DEFAULT_CANONICAL_TRANSCRIPTS: CanonicalTranscripts;
|
|
28
|
+
export interface IsoformScore {
|
|
29
|
+
canonical: number;
|
|
30
|
+
coding: boolean;
|
|
31
|
+
size: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function isCodingIsoform<T>(read: IsoformReader<T>, feature: T): boolean;
|
|
34
|
+
export declare function isoformScorer<T>(read: IsoformReader<T>, { field, tags }: CanonicalTranscripts): (feature: T) => IsoformScore;
|
|
35
|
+
export declare function rankIsoforms<T>(isoforms: readonly T[], scoreOf: (feature: T) => IsoformScore): T[];
|
|
36
|
+
export interface CanonicalTranscriptsHost {
|
|
37
|
+
canonicalTranscripts: CanonicalTranscripts;
|
|
38
|
+
}
|
|
39
|
+
export declare function isCanonicalTranscriptsHost(thing: unknown): thing is CanonicalTranscriptsHost;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export const featureIsoformReader = {
|
|
2
|
+
type: f => f.get('type'),
|
|
3
|
+
start: f => f.get('start'),
|
|
4
|
+
end: f => f.get('end'),
|
|
5
|
+
subfeatures: f => f.get('subfeatures') ?? [],
|
|
6
|
+
attribute: (f, field) => f.get(field),
|
|
7
|
+
};
|
|
8
|
+
export const serializedIsoformReader = {
|
|
9
|
+
type: f => f.type,
|
|
10
|
+
start: f => f.start,
|
|
11
|
+
end: f => f.end,
|
|
12
|
+
subfeatures: f => f.subfeatures ?? [],
|
|
13
|
+
attribute: (f, field) => f[field],
|
|
14
|
+
};
|
|
15
|
+
export const DEFAULT_CANONICAL_TRANSCRIPTS = {
|
|
16
|
+
field: 'tag',
|
|
17
|
+
tags: [
|
|
18
|
+
'MANE Select',
|
|
19
|
+
'MANE_Select',
|
|
20
|
+
'RefSeq Select',
|
|
21
|
+
'Ensembl_canonical',
|
|
22
|
+
'MANE Plus Clinical',
|
|
23
|
+
'MANE_Plus_Clinical',
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
function isCDSType(type) {
|
|
27
|
+
return type?.toLowerCase() === 'cds';
|
|
28
|
+
}
|
|
29
|
+
// The feature itself counts, because an isoform can BE the CDS: a viral
|
|
30
|
+
// polyprotein hangs its cleavage products off its CDS, not off further CDSs.
|
|
31
|
+
export function isCodingIsoform(read, feature) {
|
|
32
|
+
return (isCDSType(read.type(feature)) ||
|
|
33
|
+
read.subfeatures(feature).some(sub => isCodingIsoform(read, sub)));
|
|
34
|
+
}
|
|
35
|
+
// "Longest coding" is the longest protein — summed CDS length, not the widest
|
|
36
|
+
// genomic footprint an isoform with a large intron could win. A duplicated CDS
|
|
37
|
+
// row is a real GFF3 quirk that would otherwise inflate one isoform past a
|
|
38
|
+
// genuinely longer protein, so equal spans count once.
|
|
39
|
+
function codingLength(read, feature) {
|
|
40
|
+
const spans = [];
|
|
41
|
+
const walk = (f) => {
|
|
42
|
+
for (const sub of read.subfeatures(f)) {
|
|
43
|
+
if (isCDSType(read.type(sub))) {
|
|
44
|
+
spans.push([read.start(sub), read.end(sub)]);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
walk(sub);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
walk(feature);
|
|
52
|
+
if (spans.length === 0) {
|
|
53
|
+
return isCDSType(read.type(feature))
|
|
54
|
+
? read.end(feature) - read.start(feature)
|
|
55
|
+
: 0;
|
|
56
|
+
}
|
|
57
|
+
spans.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
58
|
+
let sum = 0;
|
|
59
|
+
for (const [i, span] of spans.entries()) {
|
|
60
|
+
const prev = spans[i - 1];
|
|
61
|
+
if (!prev || prev[0] !== span[0] || prev[1] !== span[1]) {
|
|
62
|
+
sum += span[1] - span[0];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return sum;
|
|
66
|
+
}
|
|
67
|
+
// A position in the tag list rather than a boolean, because the default list
|
|
68
|
+
// holds two tags one gene can carry at once and flattening them to "tagged"
|
|
69
|
+
// leaves the coding-length tiebreak to pick between them. A GFF3 attribute
|
|
70
|
+
// holding a comma list arrives as an array, hence both shapes.
|
|
71
|
+
function canonicalRank(value, wanted) {
|
|
72
|
+
const values = Array.isArray(value)
|
|
73
|
+
? value.map(v => String(v).toLowerCase())
|
|
74
|
+
: typeof value === 'string'
|
|
75
|
+
? [value.toLowerCase()]
|
|
76
|
+
: [];
|
|
77
|
+
let best = Infinity;
|
|
78
|
+
for (const v of values) {
|
|
79
|
+
const rank = wanted.indexOf(v);
|
|
80
|
+
if (rank !== -1 && rank < best) {
|
|
81
|
+
best = rank;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return best;
|
|
85
|
+
}
|
|
86
|
+
export function isoformScorer(read, { field, tags }) {
|
|
87
|
+
const wanted = tags.map(t => t.toLowerCase());
|
|
88
|
+
return (feature) => {
|
|
89
|
+
const coding = isCodingIsoform(read, feature);
|
|
90
|
+
return {
|
|
91
|
+
canonical: wanted.length
|
|
92
|
+
? canonicalRank(read.attribute(feature, field), wanted)
|
|
93
|
+
: Infinity,
|
|
94
|
+
coding,
|
|
95
|
+
size: coding
|
|
96
|
+
? codingLength(read, feature)
|
|
97
|
+
: read.end(feature) - read.start(feature),
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Best first. A curated tag outranks every measurement, because for a gene whose
|
|
102
|
+
// longest protein is a minor variant it is the only thing that picks the right
|
|
103
|
+
// isoform. A coding-length tie resolves to the LATER isoform, which a stable
|
|
104
|
+
// sort would break the other way — hence the explicit index term.
|
|
105
|
+
export function rankIsoforms(isoforms, scoreOf) {
|
|
106
|
+
return isoforms
|
|
107
|
+
.map((feature, index) => ({ feature, index, ...scoreOf(feature) }))
|
|
108
|
+
.sort((a, b) => a.canonical - b.canonical ||
|
|
109
|
+
Number(b.coding) - Number(a.coding) ||
|
|
110
|
+
b.size - a.size ||
|
|
111
|
+
b.index - a.index)
|
|
112
|
+
.map(s => s.feature);
|
|
113
|
+
}
|
|
114
|
+
export function isCanonicalTranscriptsHost(thing) {
|
|
115
|
+
return (typeof thing === 'object' &&
|
|
116
|
+
thing !== null &&
|
|
117
|
+
'canonicalTranscripts' in thing);
|
|
118
|
+
}
|
|
@@ -1,68 +1,50 @@
|
|
|
1
1
|
import { getConf } from '../configuration/getConf.ts';
|
|
2
|
+
import type { RetiredDisplayState } from '../pluggableElementTypes/DisplayType.ts';
|
|
2
3
|
import type { Reversible } from '../ui/filterMenuItems.ts';
|
|
3
4
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* *even to an empty list* — it replaces the config tier entirely, which is
|
|
13
|
-
* what makes "clear the filters an admin declared" expressible.
|
|
14
|
-
*
|
|
15
|
-
* Both hold `jexl:` expressions and refuse a bare string.
|
|
5
|
+
* The feature-filter contract, shared by every display offering a "Filter
|
|
6
|
+
* by..." row: the **`filter` config slot** holds the filters applied, as
|
|
7
|
+
* `jexl:` expressions (`jexlFilterConfigSchemaFields` in display-kit, spread
|
|
8
|
+
* by the canvas base display schema, the mark display schema and the shared
|
|
9
|
+
* multi-sample variant schema). The dialog, a feature's "Filter" actions and
|
|
10
|
+
* "Edit plot..." all write that slot, and the session keeps the write as it
|
|
11
|
+
* keeps any track setting, so "Reset track settings" returns it with the
|
|
12
|
+
* rest.
|
|
16
13
|
*/
|
|
17
14
|
export interface JexlFilterSource {
|
|
18
|
-
/**
|
|
19
|
-
* The dialog's override. `undefined` means "follow the config slot"; an
|
|
20
|
-
* empty array means "the user cleared them".
|
|
21
|
-
*/
|
|
22
|
-
filterSetting?: readonly string[];
|
|
23
|
-
/**
|
|
24
|
-
* The config tier — in practice `configuredJexlFilters(self)`. A member rather than a read this module does
|
|
25
|
-
* itself, so the two things that consume this contract off a **duck-typed**
|
|
26
|
-
* model — LD's structural menu builder and its shape test — need no live
|
|
27
|
-
* config node to answer a count with.
|
|
28
|
-
*/
|
|
15
|
+
/** The slot's filters, in practice `configuredJexlFilters(self)`. */
|
|
29
16
|
configuredFilters: () => string[];
|
|
30
17
|
}
|
|
31
18
|
/**
|
|
32
19
|
* The writing half, for the dialog and the menu's clear row. Separate from
|
|
33
|
-
* {@link JexlFilterSource} because a display declares
|
|
34
|
-
* earlier `.views()` block than the `.actions()` block declaring its setter
|
|
35
|
-
* requiring the setter to *read* the filters would put the two in an order MST
|
|
36
|
-
* cannot satisfy.
|
|
20
|
+
* {@link JexlFilterSource} because a display declares its readers in an
|
|
21
|
+
* earlier `.views()` block than the `.actions()` block declaring its setter.
|
|
37
22
|
*/
|
|
38
23
|
export interface JexlFilterModel extends JexlFilterSource {
|
|
24
|
+
/** What the track's config declares, which "Clear all filters" returns to. */
|
|
25
|
+
baseFilters: () => string[];
|
|
26
|
+
/** Write the slot; undefined returns it to {@link baseFilters}. */
|
|
39
27
|
setFilter: (filters?: string[]) => void;
|
|
40
28
|
}
|
|
41
|
-
/**
|
|
42
|
-
export declare const FilterSetting: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").ISimpleType<string>>>;
|
|
43
|
-
/**
|
|
44
|
-
* A snapshot's v4 `jexlFiltersSetting` as `filterSetting`, for the
|
|
45
|
-
* `preProcessSnapshot` of the model declaring {@link FilterSetting}.
|
|
46
|
-
*/
|
|
47
|
-
export declare function liftRetiredFilterSetting<T>(snap: T): T;
|
|
48
|
-
/** What the `filter` config slot alone declares: a display's `configuredFilters`. */
|
|
29
|
+
/** What the `filter` config slot holds: a display's `configuredFilters`. */
|
|
49
30
|
export declare function configuredJexlFilters(self: Parameters<typeof getConf>[0]): string[];
|
|
50
31
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
32
|
+
* What the track's config declares for this display's `filter`, before the
|
|
33
|
+
* session wrote any: a display's `baseFilters`. Empty in a session that keeps
|
|
34
|
+
* no base.
|
|
54
35
|
*/
|
|
55
|
-
export declare function
|
|
36
|
+
export declare function baseJexlFilters(self: object): string[];
|
|
56
37
|
/**
|
|
57
|
-
* The `Reversible` a display declares for its jexl filters.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* counts in both directions when they differ — narrowing further, and (emptied
|
|
63
|
-
* over a slot that declares filters) widening past what the config asked for.
|
|
64
|
-
*
|
|
65
|
-
* No per-item row: a list of jexl expressions has no recovery to name beyond
|
|
66
|
-
* restoring the config default, which the group clear already is.
|
|
38
|
+
* The `Reversible` a display declares for its jexl filters. The count is
|
|
39
|
+
* whether the filters differ from what the track's config declares, not the
|
|
40
|
+
* number of expressions: a filter an admin declared is not something the user
|
|
41
|
+
* narrowed. It counts in both directions, narrowing further and, emptied over
|
|
42
|
+
* a config that declares filters, widening past it.
|
|
67
43
|
*/
|
|
68
44
|
export declare function jexlFilterNarrowing(self: JexlFilterModel): Reversible;
|
|
45
|
+
/**
|
|
46
|
+
* A v4 session's display-instance `jexlFiltersSetting`, which is the `filter`
|
|
47
|
+
* slot now: the retired state the canvas and single-sample variant displays
|
|
48
|
+
* declare (ADR-168).
|
|
49
|
+
*/
|
|
50
|
+
export declare const retiredFilterState: RetiredDisplayState;
|
package/esm/util/jexlFilters.js
CHANGED
|
@@ -1,57 +1,36 @@
|
|
|
1
1
|
// own module, not the configuration barrel: that barrel re-exports modules
|
|
2
2
|
// which import this one, and a value import back into it is the cycle shape
|
|
3
3
|
// `openFeatureWidget` documents
|
|
4
|
-
import { types } from '@jbrowse/mobx-state-tree';
|
|
5
4
|
import { getConf } from '../configuration/getConf.js';
|
|
5
|
+
import { baseDisplayConfig } from './baseDisplayConfig.js';
|
|
6
6
|
import { ensureJexlPrefix } from './jexlStrings.js';
|
|
7
|
-
|
|
8
|
-
/** The `filterSetting` property, for a display model to declare. */
|
|
9
|
-
export const FilterSetting = types.maybe(types.array(JexlExpressionString));
|
|
10
|
-
/**
|
|
11
|
-
* A snapshot's v4 `jexlFiltersSetting` as `filterSetting`, for the
|
|
12
|
-
* `preProcessSnapshot` of the model declaring {@link FilterSetting}.
|
|
13
|
-
*/
|
|
14
|
-
export function liftRetiredFilterSetting(snap) {
|
|
15
|
-
if (!snap || typeof snap !== 'object' || !('jexlFiltersSetting' in snap)) {
|
|
16
|
-
return snap;
|
|
17
|
-
}
|
|
18
|
-
const { jexlFiltersSetting, ...rest } = snap;
|
|
19
|
-
return (Array.isArray(jexlFiltersSetting) && !('filterSetting' in rest)
|
|
20
|
-
? { ...rest, filterSetting: jexlFiltersSetting.map(ensureJexlPrefix) }
|
|
21
|
-
: rest);
|
|
22
|
-
}
|
|
23
|
-
/** What the `filter` config slot alone declares: a display's `configuredFilters`. */
|
|
7
|
+
/** What the `filter` config slot holds: a display's `configuredFilters`. */
|
|
24
8
|
export function configuredJexlFilters(self) {
|
|
25
9
|
return getConf(self, 'filter');
|
|
26
10
|
}
|
|
27
11
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
12
|
+
* What the track's config declares for this display's `filter`, before the
|
|
13
|
+
* session wrote any: a display's `baseFilters`. Empty in a session that keeps
|
|
14
|
+
* no base.
|
|
31
15
|
*/
|
|
32
|
-
export function
|
|
33
|
-
const
|
|
34
|
-
return
|
|
16
|
+
export function baseJexlFilters(self) {
|
|
17
|
+
const declared = baseDisplayConfig(self).filter;
|
|
18
|
+
return Array.isArray(declared)
|
|
19
|
+
? declared.map(f => ensureJexlPrefix(String(f)))
|
|
20
|
+
: [];
|
|
35
21
|
}
|
|
36
22
|
/**
|
|
37
|
-
* The `Reversible` a display declares for its jexl filters.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* counts in both directions when they differ — narrowing further, and (emptied
|
|
43
|
-
* over a slot that declares filters) widening past what the config asked for.
|
|
44
|
-
*
|
|
45
|
-
* No per-item row: a list of jexl expressions has no recovery to name beyond
|
|
46
|
-
* restoring the config default, which the group clear already is.
|
|
23
|
+
* The `Reversible` a display declares for its jexl filters. The count is
|
|
24
|
+
* whether the filters differ from what the track's config declares, not the
|
|
25
|
+
* number of expressions: a filter an admin declared is not something the user
|
|
26
|
+
* narrowed. It counts in both directions, narrowing further and, emptied over
|
|
27
|
+
* a config that declares filters, widening past it.
|
|
47
28
|
*/
|
|
48
29
|
export function jexlFilterNarrowing(self) {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
30
|
+
const current = self.configuredFilters();
|
|
31
|
+
const base = self.baseFilters();
|
|
51
32
|
return {
|
|
52
|
-
count:
|
|
53
|
-
(override.length !== configured.length ||
|
|
54
|
-
override.some((f, i) => f !== configured[i]))
|
|
33
|
+
count: current.length !== base.length || current.some((f, i) => f !== base[i])
|
|
55
34
|
? 1
|
|
56
35
|
: 0,
|
|
57
36
|
clear: () => {
|
|
@@ -59,3 +38,14 @@ export function jexlFilterNarrowing(self) {
|
|
|
59
38
|
},
|
|
60
39
|
};
|
|
61
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* A v4 session's display-instance `jexlFiltersSetting`, which is the `filter`
|
|
43
|
+
* slot now: the retired state the canvas and single-sample variant displays
|
|
44
|
+
* declare (ADR-168).
|
|
45
|
+
*/
|
|
46
|
+
export const retiredFilterState = {
|
|
47
|
+
keys: ['jexlFiltersSetting'],
|
|
48
|
+
lift: ({ jexlFiltersSetting }) => Array.isArray(jexlFiltersSetting)
|
|
49
|
+
? { filter: jexlFiltersSetting.map(f => ensureJexlPrefix(String(f))) }
|
|
50
|
+
: {},
|
|
51
|
+
};
|