@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
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { getSnapshot, getType, isArrayType, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import { compareStructural } from 'mobx';
|
|
3
|
+
import { colorProblems, colorSlotsOf, noticeLines, scaleEndProblems, scaleEndsOf, } from '../util/colorScale.js';
|
|
4
|
+
import { readConfObject } from './readConfObject.js';
|
|
5
|
+
import { getConfigurationSchemaMetadata } from './schemaRegistry.js';
|
|
6
|
+
import { bareFormOf, shorthandTargets } from './schemaTypes.js';
|
|
7
|
+
/**
|
|
8
|
+
* #api core/configuration
|
|
9
|
+
* The grammar's settings, by the slot name every display that has one gives
|
|
10
|
+
* it, and what each holds: what "Edit plot..." shows and an agent reads as a
|
|
11
|
+
* display's `plot`. A display's plot is the ones its config declares
|
|
12
|
+
* (`plotKeysOf`).
|
|
13
|
+
*/
|
|
14
|
+
export const PLOT_VOCABULARY = {
|
|
15
|
+
marks: 'the marks drawn in order, each a mark and an encoding',
|
|
16
|
+
transform: 'the steps run over the features before any mark',
|
|
17
|
+
unit: 'what one row stands for: a read or a chain, a sample or a haplotype',
|
|
18
|
+
facet: 'one section per value of a field',
|
|
19
|
+
rows: 'the rows: their field or order, labels, focus and tree',
|
|
20
|
+
rowColor: 'the colour of each row, by a row attribute',
|
|
21
|
+
color: 'the colour',
|
|
22
|
+
baseColor: 'the per-base layer over the reads',
|
|
23
|
+
arcColor: 'the colour of the arcs between mates',
|
|
24
|
+
ribbonColor: 'the colour of the ribbons between lanes',
|
|
25
|
+
laneLayers: 'the layers drawn over each lane',
|
|
26
|
+
scales: 'the axes, scales.y the value axis',
|
|
27
|
+
y: "what each row's bar height carries",
|
|
28
|
+
filter: 'the jexl: expressions a feature has to pass',
|
|
29
|
+
filterBy: 'the read flags and tags a read has to pass',
|
|
30
|
+
};
|
|
31
|
+
const LIFT_ID = 'plotLift';
|
|
32
|
+
/**
|
|
33
|
+
* #api core/configuration
|
|
34
|
+
* The plot keys a display config declares.
|
|
35
|
+
*/
|
|
36
|
+
export function plotKeysOf(conf) {
|
|
37
|
+
const definition = getConfigurationSchemaMetadata(conf)?.definition ?? {};
|
|
38
|
+
return Object.keys(PLOT_VOCABULARY).filter(key => Object.hasOwn(definition, key));
|
|
39
|
+
}
|
|
40
|
+
// A one-member object a shorthand would write as a bare value prints as that
|
|
41
|
+
// value, as a config file writes it: `color: "red"`, `facet: "strand"`.
|
|
42
|
+
function folded(member, value) {
|
|
43
|
+
if (!isStateTreeNode(member) ||
|
|
44
|
+
typeof value !== 'object' ||
|
|
45
|
+
value === null ||
|
|
46
|
+
Array.isArray(value)) {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
const meta = getConfigurationSchemaMetadata(member);
|
|
50
|
+
const entries = Object.entries(value);
|
|
51
|
+
if (!meta || meta.options.shorthandWith || entries.length !== 1) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
const [slot, bare] = entries[0];
|
|
55
|
+
const form = bareFormOf(bare);
|
|
56
|
+
return form && shorthandTargets(meta)[form] === slot ? bare : value;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* #api core/configuration
|
|
60
|
+
* A display's plot as declared, defaults left off. A list sitting at a
|
|
61
|
+
* default it shares with no other display, a default plot's marks, shows its
|
|
62
|
+
* entries, since they are what is drawn.
|
|
63
|
+
*/
|
|
64
|
+
export function plotOf(conf) {
|
|
65
|
+
const snapshot = getSnapshot(conf);
|
|
66
|
+
const plot = {};
|
|
67
|
+
for (const key of plotKeysOf(conf)) {
|
|
68
|
+
const member = conf[key];
|
|
69
|
+
const value = snapshot[key] ??
|
|
70
|
+
(isStateTreeNode(member) &&
|
|
71
|
+
isArrayType(getType(member)) &&
|
|
72
|
+
member.length > 0
|
|
73
|
+
? getSnapshot(member)
|
|
74
|
+
: undefined);
|
|
75
|
+
if (value !== undefined) {
|
|
76
|
+
plot[key] = folded(member, value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return plot;
|
|
80
|
+
}
|
|
81
|
+
function checkKeys(draft, keys) {
|
|
82
|
+
const unknown = Object.keys(draft).filter(key => !keys.includes(key));
|
|
83
|
+
if (unknown.length > 0) {
|
|
84
|
+
throw new Error(`This display's plot is ${keys.join(', ')}, not ${unknown.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* #api core/configuration
|
|
89
|
+
* The text as a plot, refusing a key the display's plot does not hold. The
|
|
90
|
+
* schema is the parser past this point.
|
|
91
|
+
*/
|
|
92
|
+
export function parsePlot(text, keys) {
|
|
93
|
+
const parsed = JSON.parse(text);
|
|
94
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
95
|
+
throw new Error(`A plot is one JSON object of ${keys.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
checkKeys(parsed, keys);
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
function merged(draft, current) {
|
|
101
|
+
const out = { ...current };
|
|
102
|
+
for (const [key, value] of Object.entries(draft)) {
|
|
103
|
+
if (value === null) {
|
|
104
|
+
delete out[key];
|
|
105
|
+
}
|
|
106
|
+
else if (value !== undefined) {
|
|
107
|
+
out[key] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* #api core/configuration
|
|
114
|
+
* A draft as the display's config would hold it, through the schema's own
|
|
115
|
+
* lift and checks: a shorthand becomes its object, a default falls off, and
|
|
116
|
+
* what a config file is refused for throws, a key outside the plot included.
|
|
117
|
+
* The node is never attached, so nothing on the display is touched; a display
|
|
118
|
+
* reads its typed members off it. The draft is copied first, since MST
|
|
119
|
+
* freezes what it creates from.
|
|
120
|
+
*/
|
|
121
|
+
export function liftPlot(conf, draft) {
|
|
122
|
+
checkKeys(draft, plotKeysOf(conf));
|
|
123
|
+
return getType(conf).create({
|
|
124
|
+
displayId: LIFT_ID,
|
|
125
|
+
...structuredClone(merged(draft, plotOf(conf))),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* #api core/configuration
|
|
130
|
+
* What applying a draft writes: each setting whose lifted value differs from
|
|
131
|
+
* the plot, as the lifted value, and `null` for one it resets. A value
|
|
132
|
+
* spelled another way but lifting to the same setting writes nothing.
|
|
133
|
+
* Throws a refusal before anything is written.
|
|
134
|
+
*/
|
|
135
|
+
export function plotWrites(conf, draft) {
|
|
136
|
+
const current = plotOf(conf);
|
|
137
|
+
const lifted = plotOf(liftPlot(conf, draft));
|
|
138
|
+
const writes = {};
|
|
139
|
+
for (const [key, value] of Object.entries(draft)) {
|
|
140
|
+
if (value === undefined || compareStructural(lifted[key], current[key])) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
writes[key] = lifted[key] ?? null;
|
|
144
|
+
}
|
|
145
|
+
return writes;
|
|
146
|
+
}
|
|
147
|
+
function subConfOf(conf, key) {
|
|
148
|
+
const member = conf[key];
|
|
149
|
+
return isStateTreeNode(member) ? member : undefined;
|
|
150
|
+
}
|
|
151
|
+
function membersOf(node) {
|
|
152
|
+
return (name) => readConfObject(node, name);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* What a lifted plot's colour objects and `scales.y` say together that the
|
|
156
|
+
* display cannot draw as written, as corner-notice lines: each colour object
|
|
157
|
+
* under its schema's `fieldPresets`, as `jbrowse validate` judges it.
|
|
158
|
+
*/
|
|
159
|
+
export function schemaPlotProblems(lifted) {
|
|
160
|
+
return plotKeysOf(lifted).flatMap(key => {
|
|
161
|
+
const node = subConfOf(lifted, key);
|
|
162
|
+
const presets = node
|
|
163
|
+
? getConfigurationSchemaMetadata(node)?.options.fieldPresets
|
|
164
|
+
: undefined;
|
|
165
|
+
const y = key === 'scales' && node ? subConfOf(node, 'y') : undefined;
|
|
166
|
+
return [
|
|
167
|
+
...(node && presets
|
|
168
|
+
? noticeLines(key, colorProblems(colorSlotsOf(membersOf(node)), presets))
|
|
169
|
+
: []),
|
|
170
|
+
...(y
|
|
171
|
+
? noticeLines('scales.y', scaleEndProblems(scaleEndsOf(membersOf(y))))
|
|
172
|
+
: []),
|
|
173
|
+
];
|
|
174
|
+
});
|
|
175
|
+
}
|
|
@@ -3,20 +3,13 @@ import type { JexlInstance } from '../util/jexlStrings.ts';
|
|
|
3
3
|
import type { AnyConfigurationModel, AnyConfigurationSnapshot, ConfigurationSchemaForModel, ConfigurationSlotName, ConfigurationSlotPath, ConfigurationSlotPathValue, ConfigurationSlotValue } from './types.ts';
|
|
4
4
|
/**
|
|
5
5
|
* #api core/configuration
|
|
6
|
-
*
|
|
7
|
-
* configuration
|
|
8
|
-
* configuration model directly, e.g. an entry from `session.tracks`.
|
|
6
|
+
* Read the value at a path of a live config node, such as a track's
|
|
7
|
+
* `configuration`, evaluating a `jexl:` callback with `args`.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* That is enforced in the types only, deliberately: it can't be a runtime check.
|
|
16
|
-
* `generateHierarchy` reads slots straight off the **un-hydrated frozen** entries
|
|
17
|
-
* of `jbrowse.tracks` on purpose, because hydrating every track to answer the
|
|
18
|
-
* track selector is what `types.frozen` exists to avoid — and those reads are
|
|
19
|
-
* indistinguishable at runtime from the broken spelling.
|
|
9
|
+
* A snapshot is refused by the types, since a slot at its default is absent
|
|
10
|
+
* from one. A `session.tracks` entry is one (`TrackConfigEntry`): read its
|
|
11
|
+
* `trackId` and `type` directly, and a slot through a helper that supplies the
|
|
12
|
+
* default, such as `getConfAssemblyNamesOrNone`.
|
|
20
13
|
*
|
|
21
14
|
* @param model - instance of ConfigurationSchema
|
|
22
15
|
* @param slotPaths - array of paths to read
|
|
@@ -26,9 +19,14 @@ import type { AnyConfigurationModel, AnyConfigurationSnapshot, ConfigurationSche
|
|
|
26
19
|
export declare function readConfObject(confObject: AnyConfigurationModel): AnyConfigurationSnapshot;
|
|
27
20
|
export declare function readConfObject<CONFMODEL extends AnyConfigurationModel, const SLOT extends ConfigurationSlotName<ConfigurationSchemaForModel<CONFMODEL>> | ConfigurationSlotPath<ConfigurationSchemaForModel<CONFMODEL>> = ConfigurationSlotName<ConfigurationSchemaForModel<CONFMODEL>>>(confObject: CONFMODEL, slotPath?: SLOT, args?: Record<string, unknown>): SLOT extends string ? ConfigurationSlotValue<ConfigurationSchemaForModel<CONFMODEL>, SLOT> : ConfigurationSlotPathValue<ConfigurationSchemaForModel<CONFMODEL>, SLOT>;
|
|
28
21
|
/**
|
|
29
|
-
* Read a value from a plain config snapshot
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* so plugin-registered functions (e.g. in a custom `mouseover` slot) resolve.
|
|
22
|
+
* Read a value from a plain config snapshot, evaluating a `jexl:` callback
|
|
23
|
+
* against `feature`. For rendering code and workers; pass the realm's
|
|
24
|
+
* `pluginManager.jexl` so plugin-registered functions resolve.
|
|
33
25
|
*/
|
|
34
26
|
export declare function readConfigValue<T>(config: Record<string, unknown>, key: string | string[], feature: Feature, jexl: JexlInstance): T;
|
|
27
|
+
/**
|
|
28
|
+
* #api core/configuration
|
|
29
|
+
* A config value as `feature` reads it: a `jexl:` callback evaluated against
|
|
30
|
+
* the feature, anything else as written.
|
|
31
|
+
*/
|
|
32
|
+
export declare function evaluateForFeature(value: unknown, feature: Feature, jexl: JexlInstance): unknown;
|
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module
|
|
3
|
-
* The config
|
|
4
|
-
* `readConfigValue` off a plain snapshot
|
|
5
|
-
*
|
|
6
|
-
* where the jexl instance comes from, the node's env or an explicit argument.
|
|
7
|
-
* A `featureField` slot's `jexl:` expression is the display's to evaluate per
|
|
8
|
-
* feature, so `readConfObject` hands it over as written.
|
|
3
|
+
* The config readers: `readConfObject` off a live config node, whose env
|
|
4
|
+
* supplies the jexl instance, and `readConfigValue` off a plain snapshot in a
|
|
5
|
+
* worker or renderer, which is handed one.
|
|
9
6
|
*/
|
|
10
7
|
import { getEnv, getSnapshot, getType, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
|
|
11
8
|
import { getConfigurationSchemaMetadata } from './schemaRegistry.js';
|
|
12
9
|
import { evaluateJexl, isCallbackValue } from './slotValueUtils.js';
|
|
13
|
-
// Evaluate a slot's `jexl:...` callback string against the realm's single jexl
|
|
14
|
-
// instance (carrying plugin-registered functions), read from the config node's
|
|
15
|
-
// env. readConfObject only ever operates on live MST configs — nested sub-config
|
|
16
|
-
// reads stay MST (env resolves to the root), and frozen track configs hydrate to
|
|
17
|
-
// MST before any callback is read — so the env instance is always present here.
|
|
18
10
|
function evalConfigCallback(expr, args, confObject) {
|
|
19
11
|
if (!isStateTreeNode(confObject)) {
|
|
20
|
-
// A jexl slot needs the realm's jexl instance, which is read from a live
|
|
21
|
-
// config node's env. A plain config snapshot (e.g. an un-hydrated
|
|
22
|
-
// session.tracks entry) carries no env — read it through a hydrated model,
|
|
23
|
-
// or use readConfigValue(config, key, feature, jexl) which takes jexl
|
|
24
|
-
// explicitly.
|
|
25
12
|
throw new Error(`cannot evaluate jexl config callback ${JSON.stringify(expr)}: config is a plain snapshot, not a live model (no env to resolve the jexl instance)`);
|
|
26
13
|
}
|
|
27
14
|
const jexl = getEnv(confObject)
|
|
@@ -35,11 +22,7 @@ function isFeatureField(confObject, slotName) {
|
|
|
35
22
|
return (isStateTreeNode(confObject) &&
|
|
36
23
|
!!getConfigurationSchemaMetadata(getType(confObject))?.featureFields.has(slotName));
|
|
37
24
|
}
|
|
38
|
-
// Read and resolve a single slot: raw value, jexl callback evaluation, then a
|
|
39
|
-
// referentially-stable snapshot for sub-config nodes.
|
|
40
25
|
function readSlot(confObject, slotName, args) {
|
|
41
|
-
// strict undefined check, not truthiness — a slot value can legitimately be
|
|
42
|
-
// falsy (0, '', false, null)
|
|
43
26
|
const value = confObject[slotName];
|
|
44
27
|
if (value === undefined) {
|
|
45
28
|
return undefined;
|
|
@@ -47,42 +30,25 @@ function readSlot(confObject, slotName, args) {
|
|
|
47
30
|
const val = isCallbackValue(value) && !isFeatureField(confObject, slotName)
|
|
48
31
|
? evalConfigCallback(value, args, confObject)
|
|
49
32
|
: value;
|
|
50
|
-
// Fast path for primitives (most common case)
|
|
51
33
|
if (val === null || typeof val !== 'object') {
|
|
52
34
|
return val;
|
|
53
35
|
}
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// the old structuredClone was both a hot-path allocation and a source of
|
|
57
|
-
// spurious recomputation. Treat as read-only.
|
|
36
|
+
// the live snapshot, stable across reads so downstream computeds memoize;
|
|
37
|
+
// read-only
|
|
58
38
|
return isStateTreeNode(val) ? getSnapshot(val) : val;
|
|
59
39
|
}
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// callers see. No looser overload admits the model: one that did was a
|
|
63
|
-
// catch-all a slot-name typo fell through to, compiling clean as `any`.
|
|
40
|
+
// No looser overload admits the model: a catch-all lets a slot-name typo
|
|
41
|
+
// compile as `any`.
|
|
64
42
|
export function readConfObject(confObject, slotPath, args = {}) {
|
|
65
|
-
// the single-slot read, first and allocation-free: it is the shape of nearly
|
|
66
|
-
// every one of the ~1300 call sites, so it doesn't get normalized into a
|
|
67
|
-
// one-element array on the way past
|
|
68
43
|
if (typeof slotPath === 'string') {
|
|
69
44
|
return readSlot(confObject, slotPath, args);
|
|
70
45
|
}
|
|
71
46
|
if (slotPath !== undefined && !Array.isArray(slotPath)) {
|
|
72
47
|
throw new TypeError('slotPath must be a string or array');
|
|
73
48
|
}
|
|
74
|
-
// No path, or an empty one — which means the same thing, and used not to: `[]`
|
|
75
|
-
// is truthy, so it reached the walk below and read `confObject[undefined]`.
|
|
76
|
-
//
|
|
77
|
-
// Returns the whole config as a plain object: the live, referentially-stable
|
|
78
|
-
// snapshot (frozen in dev), not a fresh clone — treat as read-only.
|
|
79
49
|
if (!slotPath?.length) {
|
|
80
50
|
return isStateTreeNode(confObject) ? getSnapshot(confObject) : confObject;
|
|
81
51
|
}
|
|
82
|
-
// every segment but the last names a sub-config, and only the last is a slot
|
|
83
|
-
// read — which is where jexl evaluation and snapshotting happen, and nowhere
|
|
84
|
-
// else. Iterative rather than a self-call per segment, which re-entered the
|
|
85
|
-
// whole argument-shape preamble above at each level.
|
|
86
52
|
let conf = confObject;
|
|
87
53
|
for (let i = 0; i < slotPath.length - 1; i++) {
|
|
88
54
|
const subConf = conf[slotPath[i]];
|
|
@@ -93,9 +59,6 @@ export function readConfObject(confObject, slotPath, args = {}) {
|
|
|
93
59
|
}
|
|
94
60
|
return readSlot(conf, slotPath[slotPath.length - 1], args);
|
|
95
61
|
}
|
|
96
|
-
// The plain-object half of the slot walk, for readConfigValue. The MST half is
|
|
97
|
-
// readSlot/rawSlotValue above. A plain object has no map entries and no
|
|
98
|
-
// sub-config nodes to snapshot, so it is just a path reduce.
|
|
99
62
|
function resolveConfigValue(config, key) {
|
|
100
63
|
if (Array.isArray(key)) {
|
|
101
64
|
let val = config;
|
|
@@ -107,12 +70,18 @@ function resolveConfigValue(config, key) {
|
|
|
107
70
|
return config[key];
|
|
108
71
|
}
|
|
109
72
|
/**
|
|
110
|
-
* Read a value from a plain config snapshot
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* so plugin-registered functions (e.g. in a custom `mouseover` slot) resolve.
|
|
73
|
+
* Read a value from a plain config snapshot, evaluating a `jexl:` callback
|
|
74
|
+
* against `feature`. For rendering code and workers; pass the realm's
|
|
75
|
+
* `pluginManager.jexl` so plugin-registered functions resolve.
|
|
114
76
|
*/
|
|
115
77
|
export function readConfigValue(config, key, feature, jexl) {
|
|
116
|
-
|
|
117
|
-
|
|
78
|
+
return evaluateForFeature(resolveConfigValue(config, key), feature, jexl);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* #api core/configuration
|
|
82
|
+
* A config value as `feature` reads it: a `jexl:` callback evaluated against
|
|
83
|
+
* the feature, anything else as written.
|
|
84
|
+
*/
|
|
85
|
+
export function evaluateForFeature(value, feature, jexl) {
|
|
86
|
+
return isCallbackValue(value) ? evaluateJexl(value, { feature }, jexl) : value;
|
|
118
87
|
}
|
|
@@ -67,7 +67,11 @@ export function isSlotDefinitionEntry(def) {
|
|
|
67
67
|
export function isConfigurationModel(thing) {
|
|
68
68
|
return isStateTreeNode(thing) && isConfigurationSchemaType(getType(thing));
|
|
69
69
|
}
|
|
70
|
-
const NUMBER_SLOT_TYPES = new Set([
|
|
70
|
+
const NUMBER_SLOT_TYPES = new Set([
|
|
71
|
+
'number',
|
|
72
|
+
'integer',
|
|
73
|
+
'maybeNumber',
|
|
74
|
+
]);
|
|
71
75
|
/**
|
|
72
76
|
* The slot a bare value of each form lifts into, off a `shorthand` option:
|
|
73
77
|
* each slot it names takes the form its own type holds, a number where it
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isJexl } from '../util/jexlStrings.js';
|
|
2
|
+
import { isPlainObject } from '../util/objectUtils.js';
|
|
2
3
|
import { slotWriteRefusal } from './configurationSlot.js';
|
|
3
4
|
import { getConfigurationSchemaMetadata } from './schemaRegistry.js';
|
|
4
5
|
import { bareFormOf, isConstantEntry, shorthandTargets } from './schemaTypes.js';
|
|
@@ -60,9 +61,6 @@ function isOwnSnapshot({ name, options }, snapshot) {
|
|
|
60
61
|
snapshot.type === undefined ||
|
|
61
62
|
snapshot.type === name);
|
|
62
63
|
}
|
|
63
|
-
function isPlainObject(value) {
|
|
64
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
65
|
-
}
|
|
66
64
|
// `lifted` under `written`: what the snapshot spells wins, member by member
|
|
67
65
|
// where both are objects.
|
|
68
66
|
function underWritten(written, lifted) {
|