@jbrowse/core 5.0.0-beta.3 → 5.0.0-beta.7
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 +278 -250
- package/esm/PluginManager.js +1 -1
- package/esm/ReExports/publicUi.d.ts +1 -1
- package/esm/ReExports/publicUi.js +1 -1
- package/esm/ReExports/publicUtil.d.ts +1 -1
- package/esm/ReExports/publicUtil.js +1 -1
- package/esm/ReExports/workerNamespaceNames.js +3 -9
- package/esm/assemblyManager/assemblyConfigSchema.d.ts +2 -1
- package/esm/assemblyManager/assemblyConfigSchema.js +2 -1
- package/esm/assemblyManager/expandAssemblyConfigShorthand.d.ts +15 -15
- package/esm/assemblyManager/expandAssemblyConfigShorthand.js +34 -18
- package/esm/configuration/configurationSchema.d.ts +1 -2
- package/esm/configuration/configurationSchema.js +3 -8
- package/esm/configuration/configurationSlot.d.ts +0 -47
- package/esm/configuration/configurationSlot.js +4 -54
- package/esm/configuration/fillLocations.d.ts +14 -0
- package/esm/configuration/fillLocations.js +14 -0
- package/esm/configuration/fullConfSnapshot.d.ts +0 -15
- package/esm/configuration/fullConfSnapshot.js +0 -28
- package/esm/configuration/getConf.d.ts +4 -34
- package/esm/configuration/getConf.js +3 -38
- package/esm/configuration/index.d.ts +3 -4
- package/esm/configuration/index.js +3 -2
- package/esm/configuration/schemaRegistry.d.ts +1 -2
- package/esm/configuration/schemaRegistry.js +1 -2
- package/esm/configuration/slotFacade.d.ts +1 -16
- package/esm/configuration/slotFacade.js +1 -2
- package/esm/configuration/tabixShorthand.js +3 -3
- package/esm/configuration/types.d.ts +4 -16
- package/esm/data_adapters/CytobandAdapter/configSchema.js +3 -4
- package/esm/pluggableElementTypes/DisplayType.d.ts +5 -3
- package/esm/pluggableElementTypes/DisplayType.js +3 -1
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +30 -0
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +45 -17
- package/esm/rpc/RpcRegistry.d.ts +7 -1
- package/esm/rpc/coreRpcMethods.d.ts +1 -0
- package/esm/rpc/coreRpcMethods.js +1 -0
- package/esm/rpc/methods/CoreEncodeFeatures.d.ts +15 -0
- package/esm/rpc/methods/CoreEncodeFeatures.js +62 -0
- package/esm/svg/svgId.d.ts +1 -1
- package/esm/svg/svgId.js +1 -1
- package/esm/ui/LegendSwatchGlyph.js +26 -21
- package/esm/ui/MenuTypes.d.ts +5 -52
- package/esm/ui/SizeSliderRow.d.ts +1 -3
- package/esm/ui/SizeSliderRow.js +4 -6
- package/esm/ui/SvgColorLegend.d.ts +9 -3
- package/esm/ui/SvgColorLegend.js +74 -33
- package/esm/ui/colorScale.d.ts +64 -0
- package/esm/ui/colorScale.js +41 -0
- package/esm/ui/colors.d.ts +11 -0
- package/esm/ui/colors.js +7 -0
- package/esm/ui/index.d.ts +5 -7
- package/esm/ui/index.js +3 -13
- package/esm/ui/legendMenuItem.d.ts +5 -16
- package/esm/ui/legendMenuItem.js +3 -14
- package/esm/ui/legendSpec.d.ts +11 -5
- package/esm/ui/legendSpec.js +8 -11
- package/esm/ui/makeSizeMenu.d.ts +0 -6
- package/esm/ui/makeSizeMenu.js +6 -30
- package/esm/ui/measureLegendText.js +1 -1
- package/esm/ui/menuItemAdornment.d.ts +9 -19
- package/esm/ui/menuItemAdornment.js +10 -31
- package/esm/ui/menuItems.d.ts +2 -6
- package/esm/ui/menuItems.js +0 -4
- package/esm/ui/palette.js +2 -1
- package/esm/ui/toggleMenuItems.d.ts +6 -13
- package/esm/ui/toggleMenuItems.js +13 -29
- package/esm/util/colorRamp.d.ts +3 -3
- package/esm/util/colorRamp.js +2 -2
- package/esm/util/featureTransforms.d.ts +15 -0
- package/esm/util/featureTransforms.js +342 -0
- package/esm/util/freezeDeep.d.ts +8 -10
- package/esm/util/freezeDeep.js +8 -10
- package/esm/util/glyphNames.d.ts +8 -0
- package/esm/util/glyphNames.js +12 -0
- package/esm/util/index.d.ts +0 -1
- package/esm/util/index.js +0 -1
- package/esm/util/markEncoding.d.ts +76 -0
- package/esm/util/markEncoding.js +436 -0
- package/esm/util/markEncodingTypes.d.ts +331 -0
- package/esm/util/markEncodingTypes.js +8 -0
- package/esm/util/renderToStaticMarkup.js +1 -1
- package/esm/util/starFusion.d.ts +26 -0
- package/esm/util/starFusion.js +37 -0
- package/esm/util/trackConfigDelta.d.ts +4 -8
- package/esm/util/trackConfigDelta.js +2 -4
- package/esm/util/tracks.d.ts +7 -1
- package/esm/util/tracks.js +72 -7
- package/esm/util/types/index.d.ts +12 -4
- package/esm/util/whenViewSettled.d.ts +40 -6
- package/esm/util/whenViewSettled.js +41 -5
- package/package.json +13 -3
- package/esm/configuration/promotableDefaults.d.ts +0 -234
- package/esm/configuration/promotableDefaults.js +0 -395
- package/esm/configuration/promotablePin.d.ts +0 -65
- package/esm/configuration/promotablePin.js +0 -1
- package/esm/configuration/promotableResolve.d.ts +0 -108
- package/esm/configuration/promotableResolve.js +0 -117
- package/esm/configuration/promotableSlots.d.ts +0 -12
- package/esm/configuration/promotableSlots.js +0 -45
- package/esm/configuration/slotShape.d.ts +0 -12
- package/esm/configuration/slotShape.js +0 -84
- package/esm/ui/PinAdornment.d.ts +0 -5
- package/esm/ui/PinAdornment.js +0 -67
- package/esm/ui/SvgGradientLegend.d.ts +0 -26
- package/esm/ui/SvgGradientLegend.js +0 -26
- package/esm/ui/promotablePinCoverage.d.ts +0 -58
- package/esm/ui/promotablePinCoverage.js +0 -124
- package/esm/util/openDisplays.d.ts +0 -23
- package/esm/util/openDisplays.js +0 -79
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module
|
|
3
|
-
* Session-wide "promoted defaults" for display-type config slots — the UI /
|
|
4
|
-
* control layer over the read-time cascade in `promotableResolve.ts`, whose
|
|
5
|
-
* `SlotResolution` every function here reads a field off. The session store
|
|
6
|
-
* (`get/setDisplayTypeDefault`) holds the promoted value.
|
|
7
|
-
*/
|
|
8
|
-
import { getSnapshot, isAlive } from '@jbrowse/mobx-state-tree';
|
|
9
|
-
import { deepEqual } from '../util/deepEqual.js';
|
|
10
|
-
import { getSession } from '../util/mstUtils.js';
|
|
11
|
-
import { isObject } from '../util/objectUtils.js';
|
|
12
|
-
import { openPromotableDisplays } from '../util/openDisplays.js';
|
|
13
|
-
import { pluralize } from '../util/stringUtils.js';
|
|
14
|
-
import { fullConfSnapshot } from './fullConfSnapshot.js';
|
|
15
|
-
import { setConf } from './getConf.js';
|
|
16
|
-
import { cascadeContextFor, isPromotableValue, resolveSlot, resolveSlotIn, storedSlotValue, } from './promotableResolve.js';
|
|
17
|
-
import { promotableSlotNames } from './promotableSlots.js';
|
|
18
|
-
import { isConfigurationModel } from './schemaTypes.js';
|
|
19
|
-
/**
|
|
20
|
-
* #api core/configuration
|
|
21
|
-
* Whether this track has customized the slot (holds a non-default value of its
|
|
22
|
-
* own) rather than following the display type's default. The correct "reset to
|
|
23
|
-
* default" predicate for a promotable slot: comparing the resolved value to the
|
|
24
|
-
* base instead reads as at-default for a track merely *following* a non-base
|
|
25
|
-
* promoted default, so the reset control lights up on a no-op.
|
|
26
|
-
*
|
|
27
|
-
* `SLOT` is constrained the way `getConf`'s is, and a widened `self` switches
|
|
28
|
-
* the check off (`HostChecksSlotNames`). A slot name the schema does not
|
|
29
|
-
* declare throws at the first read (`getSlotDefinition`), and a declared but
|
|
30
|
-
* non-promotable one throws in `resolveSlotIn`.
|
|
31
|
-
*/
|
|
32
|
-
export function isSlotCustomized(self, slot) {
|
|
33
|
-
return resolveSlot(self, slot).customized;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* #api core/configuration
|
|
37
|
-
* The display's full config snapshot with every `promotable`
|
|
38
|
-
* slot overwritten by its resolved value in place. For building a worker payload:
|
|
39
|
-
* a promotable slot serializes as its raw inherit sentinel (`undefined`, since
|
|
40
|
-
* they're all `maybe*` types), which the worker can't interpret — it has no
|
|
41
|
-
* session to resolve against. This hands it concrete values instead, with no per-slot
|
|
42
|
-
* bookkeeping, so adding a promotable worker-consumed slot needs no rpcProps
|
|
43
|
-
* change and can't silently ship a sentinel. Main-thread only (the cascade
|
|
44
|
-
* consults the session). Display-only promotable slots the worker never reads
|
|
45
|
-
* (e.g. displayMode) are still excluded by the caller — resolving them here is a
|
|
46
|
-
* harmless no-op since they're dropped anyway.
|
|
47
|
-
*
|
|
48
|
-
* The return type is branded (`ResolvedConfigSnapshot`) so a payload builder can
|
|
49
|
-
* demand a snapshot that has been through here. The assertion below is the one
|
|
50
|
-
* place the brand is applied, and it sits on the line after the resolve.
|
|
51
|
-
*/
|
|
52
|
-
export function getConfigSnapshotWithPromotables(self) {
|
|
53
|
-
// the unresolved walk: this is the one place allowed to snapshot a promotable
|
|
54
|
-
// config, because `resolvePromotablesInto` is what resolves every such slot
|
|
55
|
-
const snap = fullConfSnapshot(self.configuration);
|
|
56
|
-
resolvePromotablesInto(cascadeContextFor(self), snap);
|
|
57
|
-
return snap;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* The subsystem's one resolve loop: every promotable slot of `ctx.config`
|
|
61
|
-
* written into `snap` in place, returning the slots whose value came from a
|
|
62
|
-
* promoted default rather than from the config. Both serialization boundaries
|
|
63
|
-
* are this function over a different context — a display state node for the
|
|
64
|
-
* worker payload, a bare display config for the About dialog's copy.
|
|
65
|
-
*/
|
|
66
|
-
function resolvePromotablesInto(ctx, snap) {
|
|
67
|
-
const inherited = [];
|
|
68
|
-
for (const slot of promotableSlotNames(ctx.config)) {
|
|
69
|
-
const res = resolveSlotIn(ctx, slot);
|
|
70
|
-
snap[slot] = res.value;
|
|
71
|
-
if (res.inherited) {
|
|
72
|
-
inherited.push(slot);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return inherited;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* #api core/configuration
|
|
79
|
-
* See {@link TrackConfigWithPromotables}.
|
|
80
|
-
*/
|
|
81
|
-
export function getTrackConfigWithPromotables(session, trackConfig) {
|
|
82
|
-
const config = structuredClone(getSnapshot(trackConfig));
|
|
83
|
-
const fromDisplayTypeDefaults = [];
|
|
84
|
-
const displayConfigs = trackConfig.displays;
|
|
85
|
-
const displaySnaps = config.displays;
|
|
86
|
-
// a config with no `displays` (an assembly, a plain customized About config)
|
|
87
|
-
// has no promotable slot to resolve — every one of them is display-level
|
|
88
|
-
if (Array.isArray(displayConfigs) && Array.isArray(displaySnaps)) {
|
|
89
|
-
for (const [i, displayConfig] of displayConfigs.entries()) {
|
|
90
|
-
const snap = displaySnaps[i];
|
|
91
|
-
if (!isConfigurationModel(displayConfig) || !isObject(snap)) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// off the live config node, not off `snap` — the snapshot is what this
|
|
95
|
-
// function *writes*, so the key it resolves against shouldn't depend on
|
|
96
|
-
// what survived `stripDefault` on the way out. A display schema is
|
|
97
|
-
// `explicitlyTyped`, so the node always carries `type`; reading the
|
|
98
|
-
// snapshot's copy meant a display whose `type` ever stopped being emitted
|
|
99
|
-
// got skipped whole, and a skipped display is a copied config that
|
|
100
|
-
// silently isn't flattened. Same source `cascadeContextFor` reads.
|
|
101
|
-
const displayType = displayConfig.type;
|
|
102
|
-
if (typeof displayType === 'string') {
|
|
103
|
-
const ctx = { config: displayConfig, displayType, defaults: session };
|
|
104
|
-
for (const slot of resolvePromotablesInto(ctx, snap)) {
|
|
105
|
-
fromDisplayTypeDefaults.push(`${displayType}.${slot}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return { config, fromDisplayTypeDefaults };
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Open displays of this display's type, **one entry per track** — the set the
|
|
114
|
-
* pin's click writes. Module-internal (exercised by promotableDefaults.test.ts);
|
|
115
|
-
* not part of the public barrel.
|
|
116
|
-
*
|
|
117
|
-
* A track open in two views is two display *models* over one display *config* —
|
|
118
|
-
* `TrackConfigurationReference` resolves both through the hydration cache
|
|
119
|
-
* (ADR-031) — so the raw walk yields it twice. That is the ordinary case in a
|
|
120
|
-
* breakpoint-split view, which shows the same track in both halves and is one of
|
|
121
|
-
* the composite shapes `openPromotableDisplays` recurses into. The count reaches
|
|
122
|
-
* the user in the snackbar and the set gets written, and both
|
|
123
|
-
* go wrong on a duplicate: the toast offered to act on "2 tracks" over a single
|
|
124
|
-
* track, and the second write was a no-op against a node the first had already
|
|
125
|
-
* set.
|
|
126
|
-
*
|
|
127
|
-
* Keying on the config node rather than on `trackId` is what keeps this
|
|
128
|
-
* cast-free: the node is a member of `ResolvableDisplay`, and within one display
|
|
129
|
-
* type it is 1:1 with the track.
|
|
130
|
-
*/
|
|
131
|
-
export function openTracksOfType(self) {
|
|
132
|
-
// seeded with the clicked display so it is in the set by construction rather
|
|
133
|
-
// than by the walk happening to reach it. A display the walk misses used to
|
|
134
|
-
// cost nothing — the click wrote only the session default — and would now be
|
|
135
|
-
// the whole of the click. A duplicate from the walk lands on the same config
|
|
136
|
-
// key, so this adds no entry.
|
|
137
|
-
const byTrack = new Map([
|
|
138
|
-
[self.configuration, self],
|
|
139
|
-
]);
|
|
140
|
-
for (const display of openPromotableDisplays(getSession(self))) {
|
|
141
|
-
if (display.type === self.type) {
|
|
142
|
-
byTrack.set(display.configuration, display);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return [...byTrack.values()];
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Whether `value` is the current session default for `slot`. The live state the
|
|
149
|
-
* pin's filled/outline reflects — a session-wide fact, so it reads the raw
|
|
150
|
-
* promoted default rather than what this display resolves to (a customized track
|
|
151
|
-
* can be showing something else entirely). The named form of the comparison
|
|
152
|
-
* {@link makePin} inlines off a resolution it already holds. Module-internal
|
|
153
|
-
* (exercised by promotableDefaults.test.ts); not part of the public barrel.
|
|
154
|
-
*/
|
|
155
|
-
export function isPromotableDefault(self, slot, value) {
|
|
156
|
-
return deepEqual(resolveSlot(self, slot).promoted, value);
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Write `value` into each display's own config for `slot`, so the track *holds*
|
|
160
|
-
* it rather than resolving it through the cascade. The pin's click, over every
|
|
161
|
-
* open track of the display type.
|
|
162
|
-
*
|
|
163
|
-
* **A track already showing `value` still has to be written**, which is why this
|
|
164
|
-
* compares the *stored* value and not the resolved one: a follower stores
|
|
165
|
-
* nothing and is showing `value` only by way of some promoted default, so
|
|
166
|
-
* skipping it would leave it to move again the moment that default changed.
|
|
167
|
-
* Comparing the stored value is also what lets a `jexl:` value answer "is this
|
|
168
|
-
* already what we would write?" without being evaluated.
|
|
169
|
-
*
|
|
170
|
-
* Skips dead displays, since the caller supplies the list and MST throws on any
|
|
171
|
-
* read or write to a destroyed node.
|
|
172
|
-
*/
|
|
173
|
-
export function applySlotToOpenTracks(displays, slot, value) {
|
|
174
|
-
for (const display of displays.filter(display => isAlive(display))) {
|
|
175
|
-
if (!deepEqual(storedSlotValue(display.configuration, slot), value)) {
|
|
176
|
-
setConf(display, slot, value);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* The pin's click: write `value` into every open track of the type, and offer
|
|
182
|
-
* the promotion as a snackbar action. Applying to the tracks in front of the
|
|
183
|
-
* user is the click they mean far more often, so it is the one that needs no
|
|
184
|
-
* second click; a default outlives the tracks it was set for and governs every
|
|
185
|
-
* track of the type opened later, so it is the escalation (ADR-048).
|
|
186
|
-
*
|
|
187
|
-
* The apply is one operation over *every* open track, not a labeled pair over
|
|
188
|
-
* the tracks that differ and the tracks that follow. Overwriting a customized
|
|
189
|
-
* track is the same write as filling in a follower, and the distinction the two
|
|
190
|
-
* actions drew is not one the user has any reason to see.
|
|
191
|
-
*
|
|
192
|
-
* The snackbar outlives the click that raised it, so the promotion closes over
|
|
193
|
-
* the display TYPE — a string — rather than reaching back through `self`. A
|
|
194
|
-
* promoted default is keyed by display type and governs tracks opened later, so
|
|
195
|
-
* it has nothing to do with the clicked display still being open; guarding on
|
|
196
|
-
* `self` instead meant a user who applied a value, closed the track, then took
|
|
197
|
-
* the offer got a silent no-op. What is still guarded is the session, which owns
|
|
198
|
-
* the map the write lands in. ADR-048 has the decisions this does not change:
|
|
199
|
-
* no track set and no apply/promote decision may be closed over.
|
|
200
|
-
*
|
|
201
|
-
* **Promoting the slot's base value clears the default instead of storing it.**
|
|
202
|
-
* The cascade resolves the two identically, but a stored base was a third
|
|
203
|
-
* state the rest of the subsystem could see: the Preferences inventory listed a
|
|
204
|
-
* row reading "from X to X", and the base option's radio pin drew filled. Now
|
|
205
|
-
* "Set as the default" on the base row is the per-value way to undo a promoted
|
|
206
|
-
* default, and a filled pin only ever means a non-base value is promoted. The
|
|
207
|
-
* base is read here rather than at click time because it is a schema literal
|
|
208
|
-
* and cannot change.
|
|
209
|
-
*/
|
|
210
|
-
function applyAndOfferDefault(self, slot, value) {
|
|
211
|
-
const session = getSession(self);
|
|
212
|
-
const displayType = self.type;
|
|
213
|
-
const { base } = resolveSlot(self, slot);
|
|
214
|
-
const promoted = deepEqual(value, base) ? undefined : value;
|
|
215
|
-
const open = openTracksOfType(self);
|
|
216
|
-
applySlotToOpenTracks(open, slot, value);
|
|
217
|
-
session.notify(`Applied to ${open.length} open ${pluralize(open.length, 'track')}`, 'info', {
|
|
218
|
-
name: 'Set as the default',
|
|
219
|
-
onClick: () => {
|
|
220
|
-
if (isAlive(session)) {
|
|
221
|
-
session.setDisplayTypeDefault(displayType, slot, promoted);
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* A value pin's click when its value is already the promoted default: clear
|
|
228
|
-
* it, touching no track. The open tracks hold their values because the user
|
|
229
|
-
* applied them, so reverting them here would make a toggle into a bulk discard.
|
|
230
|
-
*/
|
|
231
|
-
function clearDefault(self, slot) {
|
|
232
|
-
const session = getSession(self);
|
|
233
|
-
session.setDisplayTypeDefault(self.type, slot, undefined);
|
|
234
|
-
session.notify('Cleared the default', 'info');
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* #api core/configuration
|
|
238
|
-
* The pin on a radio or slider row: "apply this value to every open track of
|
|
239
|
-
* this display type", and — via the snackbar it raises — "keep it as the
|
|
240
|
-
* default for the ones opened later".
|
|
241
|
-
*
|
|
242
|
-
* `value` chooses between the subsystem's two meanings, which are otherwise
|
|
243
|
-
* identical:
|
|
244
|
-
*
|
|
245
|
-
* - **Give it** for a per-value pin — "make *compact* the default" —
|
|
246
|
-
* independent of what the track currently shows. Use on an always-visible pin
|
|
247
|
-
* so it can never promote a meaningless value, and so two rows sharing one
|
|
248
|
-
* slot (sashimi `'down'` vs `'auto'`) stay independent.
|
|
249
|
-
* - **Omit it** for "whatever I'm showing", resolved through the cascade. Use for
|
|
250
|
-
* a continuous setting where no fixed on-value makes sense (wiggle point size,
|
|
251
|
-
* arc line width).
|
|
252
|
-
*
|
|
253
|
-
* A checkbox row takes neither: {@link makeTogglePin}.
|
|
254
|
-
*
|
|
255
|
-
* One function with an optional argument, rather than the two exported builders
|
|
256
|
-
* it replaces — a per-value one and a `…CurrentValue…` one, the second of which
|
|
257
|
-
* was exactly the first applied to `resolveSlot(self, slot).value`. The pair was
|
|
258
|
-
* one function plus a doc section explaining which name to reach for; omitting
|
|
259
|
-
* the argument now says what the longer name said.
|
|
260
|
-
*/
|
|
261
|
-
export function makePin(self, slot, ...value) {
|
|
262
|
-
// One walk of the cascade feeds both halves. The value-omitted form's
|
|
263
|
-
// on-value IS the settled value, and `active` compares against the raw
|
|
264
|
-
// promoted default of that same resolution — taking the two from separate
|
|
265
|
-
// `resolveSlot` calls read as though they could disagree, and cost a second
|
|
266
|
-
// walk per pin on a menu that builds a dozen of them.
|
|
267
|
-
const res = resolveSlot(self, slot);
|
|
268
|
-
// rest-tuple, not `value?: unknown`: the promote-current case has to stay
|
|
269
|
-
// distinguishable from an explicit `undefined` rather than collapsing into it.
|
|
270
|
-
const onValue = value.length ? value[0] : res.value;
|
|
271
|
-
// The value-omitted form can't fail this — the cascade only ever settles on a
|
|
272
|
-
// usable value — so this is entirely about a caller-supplied one. An on-value
|
|
273
|
-
// the cascade would refuse builds a pin that is inert *and* silent: clicking
|
|
274
|
-
// it stores a key `resolveSlotIn` then drops, so no track moves and the pin
|
|
275
|
-
// draws outline forever. The reachable mistakes are the inherit sentinel
|
|
276
|
-
// itself (`makePin(self, slot, undefined)`, which additionally reads as the
|
|
277
|
-
// default the moment nothing is promoted, so it draws *filled* and does
|
|
278
|
-
// nothing), a non-finite number, and a value outside a `maybeStringEnum`'s
|
|
279
|
-
// vocabulary. The `value` parameter's type catches those wherever the slot
|
|
280
|
-
// resolves to a real value type; what is left for runtime is the slot whose
|
|
281
|
-
// schema widened to `any` — a `frozen`/`maybeFrozen` one by design, or a
|
|
282
|
-
// display whose config model was not narrowed. Same bargain `ConfigSlot`
|
|
283
|
-
// strikes over `promotedBase`, which is this gate at the other end.
|
|
284
|
-
if (!isPromotableValue(self.configuration, slot, onValue)) {
|
|
285
|
-
throw new Error(`cannot pin ${JSON.stringify(onValue)} as the default for config slot "${slot}": the cascade refuses it, so the pin could never light up`);
|
|
286
|
-
}
|
|
287
|
-
// `isPromotableDefault` off the resolution already in hand — same comparison,
|
|
288
|
-
// same `deepEqual`, no second walk.
|
|
289
|
-
const active = deepEqual(res.promoted, onValue);
|
|
290
|
-
return {
|
|
291
|
-
kind: 'value',
|
|
292
|
-
slot,
|
|
293
|
-
onValue,
|
|
294
|
-
active,
|
|
295
|
-
toggle: () => {
|
|
296
|
-
if (active) {
|
|
297
|
-
clearDefault(self, slot);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
applyAndOfferDefault(self, slot, onValue);
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* #api core/configuration
|
|
307
|
-
* The pin on a checkbox row: the row's own checkbox, acting on every open track
|
|
308
|
-
* of the display type. `active` mirrors the row, so the pin draws filled
|
|
309
|
-
* exactly when the box is ticked; a click flips the row's state on every open
|
|
310
|
-
* track and offers the new state as the display type's default. It never
|
|
311
|
-
* clears a default the way {@link makePin}'s filled pin does — flipping back
|
|
312
|
-
* and taking the offer promotes the other value, and promoting the base value
|
|
313
|
-
* clears the default (`applyAndOfferDefault`).
|
|
314
|
-
*
|
|
315
|
-
* Replaces a symmetric `makePin(self, slot)` on these rows, which carried the
|
|
316
|
-
* row's current state: beside an unchecked box that applied *off* everywhere
|
|
317
|
-
* and visibly did nothing. It also replaces the per-value `makePin` a checkbox
|
|
318
|
-
* row over a shared enum slot used to carry, which gave two checkbox rows in
|
|
319
|
-
* one submenu two different pins: one filled when its value was promoted and
|
|
320
|
-
* clearing on a second click, the other filled when the box was ticked and
|
|
321
|
-
* flipping. With `states`, a checkbox row is always the toggle kind.
|
|
322
|
-
*/
|
|
323
|
-
export function makeTogglePin(self, slot,
|
|
324
|
-
// rest-tuple, like `makePin`'s value: a boolean slot may omit the states, and
|
|
325
|
-
// any other slot has to name them, so the type says which without a runtime
|
|
326
|
-
// check that could only fire on a schema widened to `any`
|
|
327
|
-
...states) {
|
|
328
|
-
const current = resolveSlot(self, slot).value;
|
|
329
|
-
const [given] = states;
|
|
330
|
-
if (!given && typeof current !== 'boolean') {
|
|
331
|
-
throw new Error(`cannot build a toggle pin over config slot "${slot}" without naming its on/off states: it resolves to ${JSON.stringify(current)}, not a boolean`);
|
|
332
|
-
}
|
|
333
|
-
const { on, off } = given ?? { on: true, off: false };
|
|
334
|
-
for (const state of [on, off]) {
|
|
335
|
-
if (!isPromotableValue(self.configuration, slot, state)) {
|
|
336
|
-
throw new Error(`cannot toggle config slot "${slot}" to ${JSON.stringify(state)}: the cascade refuses it, so the pin could never apply it`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
const active = deepEqual(current, on);
|
|
340
|
-
const onValue = active ? off : on;
|
|
341
|
-
return {
|
|
342
|
-
kind: 'toggle',
|
|
343
|
-
slot,
|
|
344
|
-
onValue,
|
|
345
|
-
active,
|
|
346
|
-
toggle: () => {
|
|
347
|
-
applyAndOfferDefault(self, slot, onValue);
|
|
348
|
-
},
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* #api core/configuration
|
|
353
|
-
* Effective differences a track following the default inherits from session-wide
|
|
354
|
-
* defaults, one per promotable slot whose inherited value differs from its schema
|
|
355
|
-
* default. Drives the track-selector "affected by a session default" badge.
|
|
356
|
-
*/
|
|
357
|
-
export function getDisplayTypeDefaultChanges(self) {
|
|
358
|
-
const changes = [];
|
|
359
|
-
for (const slot of promotableSlotNames(self.configuration)) {
|
|
360
|
-
const res = resolveSlot(self, slot);
|
|
361
|
-
if (res.inherited) {
|
|
362
|
-
changes.push({
|
|
363
|
-
path: [slot],
|
|
364
|
-
// a cascade value is `unknown` here but JSON by contract — it has to
|
|
365
|
-
// survive `postMessage` to a worker. Asserted per field rather than on
|
|
366
|
-
// the whole entry, so `path` stays type-checked.
|
|
367
|
-
from: res.base,
|
|
368
|
-
to: res.value,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return changes;
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* #api core/configuration
|
|
376
|
-
* Clear the named promoted defaults for this display type, so every track
|
|
377
|
-
* following one reverts to its own config value. Backs the badge's "clear
|
|
378
|
-
* session default" action, which passes the slots it actually listed
|
|
379
|
-
* (`getDisplayTypeDefaultChanges`).
|
|
380
|
-
*
|
|
381
|
-
* **`slots` is required, and an all-slots default is not the convenience it
|
|
382
|
-
* looks like.** It reaches further than any list a dialog can have shown: a
|
|
383
|
-
* promoted default the track *customized* over is `inherited: false` and so
|
|
384
|
-
* appears in no row, yet still governs sibling tracks — so clearing it from a
|
|
385
|
-
* dialog that never showed
|
|
386
|
-
* it moves tracks other than the one whose badge was clicked. Clearing every
|
|
387
|
-
* promoted default at once is a preferences-scope action, and Preferences →
|
|
388
|
-
* "Reset to defaults" is where it lives (`clearPreferenceOverrides`).
|
|
389
|
-
*/
|
|
390
|
-
export function clearPromotedDefaults(self, slots) {
|
|
391
|
-
const session = getSession(self);
|
|
392
|
-
for (const slot of slots) {
|
|
393
|
-
session.setDisplayTypeDefault(self.type, slot, undefined);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared by both pin kinds: which promotable slot the pin writes, whether it
|
|
3
|
-
* draws filled, and the one click it takes.
|
|
4
|
-
*
|
|
5
|
-
* `slot` is here so a *built menu* can be asked which promotable slots it offers
|
|
6
|
-
* a pin for, which is the only way that question has an answer: declaring
|
|
7
|
-
* `promotedBase` is a schema fact and the pin is a menu fact, and a display that
|
|
8
|
-
* inherits the slot but never builds a row has a slot nothing can ever promote,
|
|
9
|
-
* silently (`promotableSlotsWithoutPin`, guarded by
|
|
10
|
-
* `products/jbrowse-web/src/tests/PromotablePinCoverage.test.ts`).
|
|
11
|
-
*
|
|
12
|
-
* **`toggle` rather than an `apply`/`clear` pair**, which was tried and dropped:
|
|
13
|
-
* the sole renderer is a MUI `ToggleButton` whose `onChange` means exactly
|
|
14
|
-
* "flip", so splitting it adds a member *and* a branch at the one call site that
|
|
15
|
-
* never needed one. `active` is already public for a caller that wants to state
|
|
16
|
-
* a direction.
|
|
17
|
-
*
|
|
18
|
-
* Lives here, alone and with no imports, rather than beside `makePin` in
|
|
19
|
-
* `promotableDefaults.ts`: the menu types describe a pin without building one,
|
|
20
|
-
* and `MenuTypes.ts` taking this from that module gave a React-free type file a
|
|
21
|
-
* type closure of 374 files. See `agent-docs/ideas/barrels-block-extraction.md`
|
|
22
|
-
* and `scripts/moduleClosure.ts`.
|
|
23
|
-
*/
|
|
24
|
-
interface PinBase {
|
|
25
|
-
slot: string;
|
|
26
|
-
/** what `toggle` writes into every open track of the display type */
|
|
27
|
-
onValue: unknown;
|
|
28
|
-
active: boolean;
|
|
29
|
-
toggle: () => void;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* #api core/configuration
|
|
33
|
-
* The pin on a radio or slider row, built by {@link makePin}: `onValue` is the
|
|
34
|
-
* row's own value, `active` means that value is the display type's promoted
|
|
35
|
-
* default, and `toggle` on an outline pin applies the value to every open track
|
|
36
|
-
* and offers it as the default, while on a filled pin it clears that default
|
|
37
|
-
* and touches no track (`applyAndOfferDefault` / `clearDefault`).
|
|
38
|
-
*/
|
|
39
|
-
export interface ValuePin extends PinBase {
|
|
40
|
-
kind: 'value';
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* #api core/configuration
|
|
44
|
-
* The pin on a checkbox row, built by {@link makeTogglePin}: the row's own
|
|
45
|
-
* checkbox over every open track of the display type. `active` mirrors the row,
|
|
46
|
-
* `onValue` is the state a click applies (the row's opposite), and `toggle`
|
|
47
|
-
* applies it everywhere and offers it as the default. It never clears a default
|
|
48
|
-
* on its own — promoting the slot's base value is what clears one.
|
|
49
|
-
*/
|
|
50
|
-
export interface TogglePin extends PinBase {
|
|
51
|
-
kind: 'toggle';
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* #api core/configuration
|
|
55
|
-
* The "apply this to every open track of this type" affordance on a menu row —
|
|
56
|
-
* the trailing `PushPin`, bundled so the row consumes it as one prop.
|
|
57
|
-
*
|
|
58
|
-
* `kind` is what the adornment words itself from and what the row builders
|
|
59
|
-
* check: a checkbox row takes a {@link TogglePin}, a radio or slider row a
|
|
60
|
-
* {@link ValuePin}. The two used to be told apart by `typeof onValue ===
|
|
61
|
-
* 'boolean'`, which let a value pin over a boolean slot compile and read as a
|
|
62
|
-
* toggle while clearing on its second click.
|
|
63
|
-
*/
|
|
64
|
-
export type Pin = ValuePin | TogglePin;
|
|
65
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import type { AnyConfigurationModel } from './types.ts';
|
|
2
|
-
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
3
|
-
/**
|
|
4
|
-
* Everything the cascade needs to read a promotable slot: the display type it
|
|
5
|
-
* keys the session-wide default on, and the config holding the track's own
|
|
6
|
-
* value. Every entry point takes this — `resolveConf`, the control builders,
|
|
7
|
-
* the worker snapshot, the badge diff — so each can take a display state node
|
|
8
|
-
* directly rather than through a cast.
|
|
9
|
-
*
|
|
10
|
-
* Read-only, and there is no write-capable variant. A `PromotableDisplay` used
|
|
11
|
-
* to exist for the one member the subsystem wrote (`setIgnorePromotedDefaults`,
|
|
12
|
-
* the received-session opt-out); with that flag gone the subsystem writes
|
|
13
|
-
* nothing to a display but a config slot, which `setConf` reaches through
|
|
14
|
-
* `configuration`.
|
|
15
|
-
*
|
|
16
|
-
* **`CONF` is a parameter rather than always `AnyConfigurationModel` because
|
|
17
|
-
* intersecting is not narrowing.** A caller wanting the cascade's members *and*
|
|
18
|
-
* a concrete schema — a mixin casting to reach its host — writes
|
|
19
|
-
* `ResolvableDisplay<XConfigModel>`. Spelling it
|
|
20
|
-
* `ResolvableDisplay & { configuration: XConfigModel }` instead reads identical
|
|
21
|
-
* and silently does the opposite: the intersected `configuration` is
|
|
22
|
-
* `AnyConfigurationModel & XConfigModel`, `ConfigurationSchemaForModel` infers
|
|
23
|
-
* the widened brand off it, and every slot name typechecks again. Two mixins
|
|
24
|
-
* shipped that spelling and only a sabotage found it.
|
|
25
|
-
*/
|
|
26
|
-
export type ResolvableDisplay<CONF extends AnyConfigurationModel = AnyConfigurationModel> = IStateTreeNode & {
|
|
27
|
-
type: string;
|
|
28
|
-
configuration: CONF;
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Where the session-wide tier of the cascade is read from. Narrowed to the one
|
|
32
|
-
* method so the resolver doesn't depend on the whole session type — and exported
|
|
33
|
-
* for the same reason, so an entry point taking the store directly
|
|
34
|
-
* (`getTrackConfigWithPromotables`) can ask for what it uses instead of for a
|
|
35
|
-
* whole session a test then has to fake.
|
|
36
|
-
*/
|
|
37
|
-
export interface PromotedDefaultStore {
|
|
38
|
-
getDisplayTypeDefault: (displayType: string, slot: string) => unknown;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* The cascade's inputs, stated directly rather than read off a display state
|
|
42
|
-
* node. `ResolvableDisplay` is the usual way to supply them (see
|
|
43
|
-
* `cascadeContextFor`), but nothing here *needs* a state node: the About
|
|
44
|
-
* dialog's "Copy config" resolves a track's display configs directly, which is
|
|
45
|
-
* what lets it answer "what would this render as" for a track that was never
|
|
46
|
-
* opened. Reaching for the open display instead buys nothing — its
|
|
47
|
-
* `configuration` is the same node, and its `type` is that config's own.
|
|
48
|
-
*/
|
|
49
|
-
export interface CascadeContext {
|
|
50
|
-
config: AnyConfigurationModel;
|
|
51
|
-
displayType: string;
|
|
52
|
-
defaults: PromotedDefaultStore;
|
|
53
|
-
}
|
|
54
|
-
export declare function cascadeContextFor(self: ResolvableDisplay): CascadeContext;
|
|
55
|
-
/**
|
|
56
|
-
* What the slot literally holds, unevaluated — a stray `jexl:` string yields the
|
|
57
|
-
* raw string rather than running it. Both callers ask "is the slot set, and to
|
|
58
|
-
* what kind of thing?", which has to be answerable with no feature context.
|
|
59
|
-
*
|
|
60
|
-
* Deliberately not `readConfObject`: for a promotable slot this is the whole
|
|
61
|
-
* stored read, and both of that reader's extra behaviors are unwanted here — it
|
|
62
|
-
* evaluates a `jexl:` string (which `isUsableValue` refuses anyway) and it
|
|
63
|
-
* snapshots an MST-node value, which a `maybe*` slot never holds.
|
|
64
|
-
*/
|
|
65
|
-
export declare function storedSlotValue(config: AnyConfigurationModel, slot: string): unknown;
|
|
66
|
-
/**
|
|
67
|
-
* Whether `value` could actually become this slot's promoted default — the same
|
|
68
|
-
* `isUsableValue` gate the cascade puts both tiers through, asked ahead of time
|
|
69
|
-
* about a value a caller is about to offer as a pin's on-value.
|
|
70
|
-
*
|
|
71
|
-
* Exists because a pin built over a value the gate refuses is inert in a way
|
|
72
|
-
* nothing reports: `applyAndOfferDefault` writes it to the session store happily,
|
|
73
|
-
* `resolveSlotIn` then drops it and every track keeps resolving to the tier
|
|
74
|
-
* below, and `isPromotableDefault` compares the *raw* stored value, so the pin
|
|
75
|
-
* draws outline forever while a dead key sits in the user's localStorage. See
|
|
76
|
-
* `makePin`, which is the one caller.
|
|
77
|
-
*/
|
|
78
|
-
export declare function isPromotableValue(config: AnyConfigurationModel, slot: string, value: unknown): boolean;
|
|
79
|
-
/**
|
|
80
|
-
* The outcome of walking the cascade for one slot: the two tiers that fed it,
|
|
81
|
-
* whether the track customized it, and the settled value.
|
|
82
|
-
*
|
|
83
|
-
* There is deliberately **no callback case** — a `jexl:` value fails
|
|
84
|
-
* `isUsableValue` like any other unusable value and degrades to the inherited
|
|
85
|
-
* one, so `value` is always readable. See `isUsableValue`.
|
|
86
|
-
*/
|
|
87
|
-
export interface SlotResolution {
|
|
88
|
-
/** value a track following the default shows with nothing promoted (CSS `initial`) */
|
|
89
|
-
base: unknown;
|
|
90
|
-
/** the raw session-wide promoted default, if any */
|
|
91
|
-
promoted: unknown;
|
|
92
|
-
/** track holds its own value rather than following the default */
|
|
93
|
-
customized: boolean;
|
|
94
|
-
/**
|
|
95
|
-
* the value came from the session-wide tier — the track follows the default
|
|
96
|
-
* *and* that default moves it off `base`. The "this display picked something
|
|
97
|
-
* up from the session" predicate, for the badge diff and the About dialog's
|
|
98
|
-
* copy-config note. A field rather than a `!customized && !deepEqual(value,
|
|
99
|
-
* base)` at each call site, because getting either half wrong is silent:
|
|
100
|
-
* dropping `customized` reports a customized track as inheriting, dropping
|
|
101
|
-
* the `base` compare flags a promoted default that changes nothing.
|
|
102
|
-
*/
|
|
103
|
-
inherited: boolean;
|
|
104
|
-
/** the final cascaded value (never the `undefined` inherit sentinel) */
|
|
105
|
-
value: unknown;
|
|
106
|
-
}
|
|
107
|
-
export declare function resolveSlot(self: ResolvableDisplay, slot: string): SlotResolution;
|
|
108
|
-
export declare function resolveSlotIn(ctx: CascadeContext, slot: string): SlotResolution;
|