@jbrowse/core 5.0.0-beta.10 → 5.0.0-beta.11

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.
Files changed (67) hide show
  1. package/README.md +113 -5
  2. package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
  3. package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
  4. package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
  5. package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
  6. package/esm/CorePlugin.js +32 -1
  7. package/esm/ReExports/coreModules.generated.js +248 -246
  8. package/esm/ReExports/coreWorkerModules.generated.js +246 -244
  9. package/esm/ReExports/list.js +3 -2
  10. package/esm/configuration/index.d.ts +2 -0
  11. package/esm/configuration/index.js +1 -0
  12. package/esm/configuration/plot.d.ts +61 -0
  13. package/esm/configuration/plot.js +142 -0
  14. package/esm/pluggableElementTypes/DisplayType.d.ts +7 -0
  15. package/esm/pluggableElementTypes/DisplayType.js +6 -0
  16. package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
  17. package/esm/pluggableElementTypes/WidgetType.js +6 -0
  18. package/esm/pluggableElementTypes/index.js +1 -8
  19. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +57 -1
  20. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
  21. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
  22. package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
  23. package/esm/ui/JexlFilterDialog.d.ts +1 -1
  24. package/esm/ui/JexlFilterDialog.js +2 -3
  25. package/esm/util/TimeTraveller.d.ts +3 -0
  26. package/esm/util/TimeTraveller.js +34 -2
  27. package/esm/util/baseDisplayConfig.d.ts +8 -0
  28. package/esm/util/baseDisplayConfig.js +27 -0
  29. package/esm/util/cachedSetup.d.ts +16 -6
  30. package/esm/util/cachedSetup.js +108 -38
  31. package/esm/util/cellMatches.d.ts +23 -0
  32. package/esm/util/cellMatches.js +264 -0
  33. package/esm/util/cellsStep.d.ts +29 -0
  34. package/esm/util/cellsStep.js +437 -0
  35. package/esm/util/copyToClipboard.js +8 -1
  36. package/esm/util/createAbortRotation.js +3 -3
  37. package/esm/util/featureTransforms.d.ts +7 -6
  38. package/esm/util/featureTransforms.js +131 -479
  39. package/esm/util/fetchPhases.d.ts +1 -1
  40. package/esm/util/formatBytes.js +1 -1
  41. package/esm/util/formatGuessers.d.ts +25 -14
  42. package/esm/util/formatGuessers.js +42 -27
  43. package/esm/util/formatRelativeTime.js +1 -1
  44. package/esm/util/getLocationUri.d.ts +2 -1
  45. package/esm/util/getLocationUri.js +8 -2
  46. package/esm/util/groupKeys.d.ts +4 -5
  47. package/esm/util/groupKeys.js +7 -6
  48. package/esm/util/intervals.js +9 -4
  49. package/esm/util/io/index.js +3 -1
  50. package/esm/util/jexlFilters.d.ts +31 -49
  51. package/esm/util/jexlFilters.js +29 -39
  52. package/esm/util/locString.js +5 -2
  53. package/esm/util/markEncoding.js +48 -8
  54. package/esm/util/markEncodingTypes.d.ts +8 -1
  55. package/esm/util/springAnimate.js +1 -1
  56. package/esm/util/stepTables.d.ts +14 -0
  57. package/esm/util/stepTables.js +38 -4
  58. package/esm/util/tabix.d.ts +6 -2
  59. package/esm/util/tabix.js +2 -2
  60. package/esm/util/tracks.d.ts +3 -30
  61. package/esm/util/tracks.js +4 -39
  62. package/esm/util/unknownSnapshotKeys.d.ts +3 -1
  63. package/esm/util/unknownSnapshotKeys.js +5 -11
  64. package/esm/util/usePanZoom.js +10 -3
  65. package/esm/util/useRafCommit.js +1 -0
  66. package/esm/util/withFeatureDetails.js +4 -0
  67. package/package.json +9 -8
@@ -0,0 +1,142 @@
1
+ import { getSnapshot, getType, isArrayType, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
2
+ import { compareStructural } from 'mobx';
3
+ import { getConfigurationSchemaMetadata } from './schemaRegistry.js';
4
+ import { bareFormOf, shorthandTargets } from './schemaTypes.js';
5
+ /**
6
+ * #api core/configuration
7
+ * The grammar's settings, by the slot name every display that has one gives
8
+ * it, and what each holds: what "Edit plot..." shows and an agent reads as a
9
+ * display's `plot`. A display's plot is the ones its config declares
10
+ * (`plotKeysOf`).
11
+ */
12
+ export const PLOT_VOCABULARY = {
13
+ marks: 'the marks drawn in order, each a mark and an encoding',
14
+ transform: 'the steps run over the features before any mark',
15
+ facet: 'one section per value of a field',
16
+ rows: 'the rows: their field or order, labels, focus and tree',
17
+ rowColor: 'the colour of each row label',
18
+ color: 'the colour',
19
+ baseColor: 'the per-base layer over the reads',
20
+ arcColor: 'the colour of the arcs between mates',
21
+ ribbonColor: 'the colour of the ribbons between lanes',
22
+ laneLayers: 'the layers drawn over each lane',
23
+ scales: 'the axes, scales.y the value axis',
24
+ filter: 'the jexl: expressions a feature has to pass',
25
+ filterBy: 'the read flags and tags a read has to pass',
26
+ };
27
+ const LIFT_ID = 'plotLift';
28
+ /**
29
+ * #api core/configuration
30
+ * The plot keys a display config declares.
31
+ */
32
+ export function plotKeysOf(conf) {
33
+ const definition = getConfigurationSchemaMetadata(conf)?.definition ?? {};
34
+ return Object.keys(PLOT_VOCABULARY).filter(key => Object.hasOwn(definition, key));
35
+ }
36
+ // A one-member object a shorthand would write as a bare value prints as that
37
+ // value, as a config file writes it: `color: "red"`, `facet: "strand"`.
38
+ function folded(member, value) {
39
+ if (!isStateTreeNode(member) ||
40
+ typeof value !== 'object' ||
41
+ value === null ||
42
+ Array.isArray(value)) {
43
+ return value;
44
+ }
45
+ const meta = getConfigurationSchemaMetadata(member);
46
+ const entries = Object.entries(value);
47
+ if (!meta || meta.options.shorthandWith || entries.length !== 1) {
48
+ return value;
49
+ }
50
+ const [slot, bare] = entries[0];
51
+ const form = bareFormOf(bare);
52
+ return form && shorthandTargets(meta)[form] === slot ? bare : value;
53
+ }
54
+ /**
55
+ * #api core/configuration
56
+ * A display's plot as declared, defaults left off. A list sitting at a
57
+ * default it shares with no other display, a default plot's marks, shows its
58
+ * entries, since they are what is drawn.
59
+ */
60
+ export function plotOf(conf) {
61
+ const snapshot = getSnapshot(conf);
62
+ const plot = {};
63
+ for (const key of plotKeysOf(conf)) {
64
+ const member = conf[key];
65
+ const value = snapshot[key] ??
66
+ (isStateTreeNode(member) &&
67
+ isArrayType(getType(member)) &&
68
+ member.length > 0
69
+ ? getSnapshot(member)
70
+ : undefined);
71
+ if (value !== undefined) {
72
+ plot[key] = folded(member, value);
73
+ }
74
+ }
75
+ return plot;
76
+ }
77
+ function checkKeys(draft, keys) {
78
+ const unknown = Object.keys(draft).filter(key => !keys.includes(key));
79
+ if (unknown.length > 0) {
80
+ throw new Error(`This display's plot is ${keys.join(', ')}, not ${unknown.join(', ')}`);
81
+ }
82
+ }
83
+ /**
84
+ * #api core/configuration
85
+ * The text as a plot, refusing a key the display's plot does not hold. The
86
+ * schema is the parser past this point.
87
+ */
88
+ export function parsePlot(text, keys) {
89
+ const parsed = JSON.parse(text);
90
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
91
+ throw new Error(`A plot is one JSON object of ${keys.join(', ')}`);
92
+ }
93
+ checkKeys(parsed, keys);
94
+ return parsed;
95
+ }
96
+ function merged(draft, current) {
97
+ const out = { ...current };
98
+ for (const [key, value] of Object.entries(draft)) {
99
+ if (value === null) {
100
+ delete out[key];
101
+ }
102
+ else if (value !== undefined) {
103
+ out[key] = value;
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ /**
109
+ * #api core/configuration
110
+ * A draft as the display's config would hold it, through the schema's own
111
+ * lift and checks: a shorthand becomes its object, a default falls off, and
112
+ * what a config file is refused for throws, a key outside the plot included.
113
+ * The node is never attached, so nothing on the display is touched; a display
114
+ * reads its typed members off it. The draft is copied first, since MST
115
+ * freezes what it creates from.
116
+ */
117
+ export function liftPlot(conf, draft) {
118
+ checkKeys(draft, plotKeysOf(conf));
119
+ return getType(conf).create({
120
+ displayId: LIFT_ID,
121
+ ...structuredClone(merged(draft, plotOf(conf))),
122
+ });
123
+ }
124
+ /**
125
+ * #api core/configuration
126
+ * What applying a draft writes: each setting whose lifted value differs from
127
+ * the plot, as the lifted value, and `null` for one it resets. A value
128
+ * spelled another way but lifting to the same setting writes nothing.
129
+ * Throws a refusal before anything is written.
130
+ */
131
+ export function plotWrites(conf, draft) {
132
+ const current = plotOf(conf);
133
+ const lifted = plotOf(liftPlot(conf, draft));
134
+ const writes = {};
135
+ for (const [key, value] of Object.entries(draft)) {
136
+ if (value === undefined || compareStructural(lifted[key], current[key])) {
137
+ continue;
138
+ }
139
+ writes[key] = lifted[key] ?? null;
140
+ }
141
+ return writes;
142
+ }
@@ -1,5 +1,6 @@
1
1
  import LazyStateModelElement from './LazyStateModelElement.ts';
2
2
  import type { AnyConfigurationSchemaType } from '../configuration/index.ts';
3
+ import type { PlotExample } from '../configuration/plot.ts';
3
4
  import type { AnyReactComponentType } from '../util/index.ts';
4
5
  import type { IAnyModelType } from '@jbrowse/mobx-state-tree';
5
6
  export type DisplayEntry = Record<string, unknown>;
@@ -54,6 +55,11 @@ export default class DisplayType extends LazyStateModelElement {
54
55
  adapterCapabilities: readonly string[];
55
56
  retiredTypes: readonly RetiredDisplayType[];
56
57
  retiredState?: RetiredDisplayState;
58
+ /**
59
+ * Worked examples of this display's plot, which "Edit plot..." offers as
60
+ * buttons. Each has to pass the display's config schema.
61
+ */
62
+ plotExamples: readonly PlotExample[];
57
63
  constructor(stuff: {
58
64
  name: string;
59
65
  stateModel: IAnyModelType | (() => Promise<IAnyModelType>);
@@ -66,5 +72,6 @@ export default class DisplayType extends LazyStateModelElement {
66
72
  adapterCapabilities?: readonly string[];
67
73
  retiredTypes?: readonly RetiredDisplayType[];
68
74
  retiredState?: RetiredDisplayState;
75
+ plotExamples?: readonly PlotExample[];
69
76
  });
70
77
  }
@@ -26,6 +26,11 @@ export default class DisplayType extends LazyStateModelElement {
26
26
  adapterCapabilities;
27
27
  retiredTypes;
28
28
  retiredState;
29
+ /**
30
+ * Worked examples of this display's plot, which "Edit plot..." offers as
31
+ * buttons. Each has to pass the display's config schema.
32
+ */
33
+ plotExamples;
29
34
  constructor(stuff) {
30
35
  super({
31
36
  ...stuff,
@@ -39,5 +44,6 @@ export default class DisplayType extends LazyStateModelElement {
39
44
  this.adapterCapabilities = stuff.adapterCapabilities ?? [];
40
45
  this.retiredTypes = stuff.retiredTypes ?? [];
41
46
  this.retiredState = stuff.retiredState;
47
+ this.plotExamples = stuff.plotExamples ?? [];
42
48
  }
43
49
  }
@@ -14,6 +14,11 @@ export default class WidgetType extends PluggableElementBase {
14
14
  ReactComponent: WidgetComponentType;
15
15
  stateModel: IAnyModelType;
16
16
  helpText?: React.ReactNode;
17
+ /**
18
+ * Closing the widget removes it from the session rather than hiding it, so
19
+ * what it holds stops riding along in every saved and shared session (#3538)
20
+ */
21
+ discardOnClose: boolean;
17
22
  constructor(stuff: {
18
23
  name: string;
19
24
  heading?: string;
@@ -22,6 +27,7 @@ export default class WidgetType extends PluggableElementBase {
22
27
  stateModel: IAnyModelType;
23
28
  ReactComponent: WidgetComponentType;
24
29
  helpText?: React.ReactNode;
30
+ discardOnClose?: boolean;
25
31
  });
26
32
  }
27
33
  export {};
@@ -6,6 +6,11 @@ export default class WidgetType extends PluggableElementBase {
6
6
  ReactComponent;
7
7
  stateModel;
8
8
  helpText;
9
+ /**
10
+ * Closing the widget removes it from the session rather than hiding it, so
11
+ * what it holds stops riding along in every saved and shared session (#3538)
12
+ */
13
+ discardOnClose;
9
14
  constructor(stuff) {
10
15
  super(stuff);
11
16
  this.heading = stuff.heading;
@@ -14,5 +19,6 @@ export default class WidgetType extends PluggableElementBase {
14
19
  this.stateModel = stuff.stateModel;
15
20
  this.ReactComponent = stuff.ReactComponent;
16
21
  this.helpText = stuff.helpText;
22
+ this.discardOnClose = stuff.discardOnClose ?? false;
17
23
  }
18
24
  }
@@ -9,15 +9,8 @@ export { default as ViewType } from './ViewType.js';
9
9
  export { default as DisplayType } from './DisplayType.js';
10
10
  export { default as InternetAccountType } from './InternetAccountType.js';
11
11
  export { default as RpcMethodType } from './RpcMethodType.js';
12
- // The two region-renaming bases, which nearly every RPC method that takes a
13
- // viewport wants — without them an external plugin deep-imports the file and
14
- // bundles its own copy, which is only harmless because RPC methods are keyed by
15
- // name (PluginManager.rpcMethods) and nothing does an instanceof.
16
12
  // `RpcMethodTypeWithFiltersAndRenameRegions` is deliberately not here: it pulls
17
- // in `renderers/util/serializableFilterChain`, and RFC-001 §9 deletes
18
- // `pluggableElementTypes/renderers/*` after the legacy renderer migration. This
19
- // barrel is served as ABI, so exporting it would pin a directory we intend to
20
- // remove.
13
+ // in `renderers/util/serializableFilterChain`, and this barrel is served as ABI.
21
14
  export { default as RpcMethodTypeWithRenameRegion } from './RpcMethodTypeWithRenameRegion.js';
22
15
  export { default as RpcMethodTypeWithRenameRegions } from './RpcMethodTypeWithRenameRegions.js';
23
16
  export { default as AddTrackWorkflowType } from './AddTrackWorkflowType.js';
@@ -1,4 +1,5 @@
1
1
  import type { AnyConfigurationModel } from '../../configuration/index.ts';
2
+ import type { Plot, PlotExample } from '../../configuration/plot.ts';
2
3
  import type { MenuItem } from '../../ui/index.ts';
3
4
  import type { RpcStatus } from '../../util/progress.ts';
4
5
  import type { UnappliedSetting } from '../../util/unknownSnapshotKeys.ts';
@@ -670,7 +671,7 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
670
671
  * `installClearHoverOnViewportChange` calls.
671
672
  *
672
673
  * A display that STORES its hover owes an override; one that derives it
673
- * from the live pointer (MAF, Hi-C, LD) owes nothing, and the default
674
+ * from the live pointer (Hi-C, LD) owes nothing, and the default
674
675
  * costs it nothing. Declared here so the clear can be installed for every
675
676
  * display rather than remembered per display — forgetting it is the
676
677
  * failure ARCHITECTURE.md's stored-hover section is about, and it used to
@@ -722,6 +723,61 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
722
723
  error: string;
723
724
  }[];
724
725
  };
726
+ } & {
727
+ /**
728
+ * #getter
729
+ * The grammar settings this display's config declares, by slot name
730
+ * (`PLOT_VOCABULARY`): what "Edit plot..." edits. Empty on a display
731
+ * with none, which then offers no editor.
732
+ */
733
+ readonly plotKeys: string[];
734
+ /**
735
+ * #getter
736
+ * Those settings as declared, defaults left off and a shorthand written
737
+ * as a config file writes it. An agent edits a `structuredClone` of it,
738
+ * checks it with `plotProblems` and hands it to `applyPlot`.
739
+ */
740
+ readonly plot: Plot;
741
+ /**
742
+ * #getter
743
+ * The worked examples this display type declares, which "Edit plot..."
744
+ * offers as buttons.
745
+ */
746
+ readonly plotExamples: readonly PlotExample[];
747
+ /**
748
+ * #getter
749
+ * This display type's page in the config reference, which lists every
750
+ * setting it takes.
751
+ */
752
+ readonly configDocsUrl: string;
753
+ /**
754
+ * #method
755
+ * A draft as the config would hold it, merged over `plot`, throwing what
756
+ * a config file would be refused for. Nothing on the display changes.
757
+ */
758
+ liftPlot(draft: Plot): AnyConfigurationModel;
759
+ /**
760
+ * #method
761
+ * What a draft would report once applied, throwing a refusal as
762
+ * `liftPlot` does. A display with a rule list over its settings
763
+ * overrides it; the draft still applies, and draws what it can.
764
+ */
765
+ plotProblems(draft: Plot): string[];
766
+ /**
767
+ * #method
768
+ * What applying a draft would write, by setting: the lifted value where
769
+ * it differs from `plot`, `null` where it resets one. Throws a refusal.
770
+ */
771
+ plotWrites(draft: Plot): Record<string, unknown>;
772
+ } & {
773
+ /**
774
+ * #action
775
+ * Write a draft over `plot`: each setting it changes is replaced whole,
776
+ * so a member left out of an object goes back to its default, and `null`
777
+ * resets the setting. A draft the schema refuses, or naming a setting
778
+ * outside the plot, throws before anything is written.
779
+ */
780
+ applyPlot(draft: Plot): void;
725
781
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
726
782
  export type BaseDisplayStateModel = typeof BaseDisplay;
727
783
  export type BaseDisplayModel = Instance<BaseDisplayStateModel>;
@@ -1,5 +1,6 @@
1
1
  import { hasParent, isAlive, types } from '@jbrowse/mobx-state-tree';
2
- import { applyConfSettings, getConf } from '../../configuration/index.js';
2
+ import { applyConfSettings, getConfigurationSchemaMetadata, getConf, setConf, } from '../../configuration/index.js';
3
+ import { liftPlot as liftPlotDraft, plotKeysOf, plotOf, plotWrites as plotWritesOf, } from '../../configuration/plot.js';
3
4
  import { getContainingTrack, getEnv, statusFraction, statusMessageText, } from '../../util/index.js';
4
5
  import { ElementId } from '../../util/types/mst.js';
5
6
  import { displaySetterName } from '../../util/unknownSnapshotKeys.js';
@@ -198,7 +199,7 @@ function stateModelFactory() {
198
199
  * `installClearHoverOnViewportChange` calls.
199
200
  *
200
201
  * A display that STORES its hover owes an override; one that derives it
201
- * from the live pointer (MAF, Hi-C, LD) owes nothing, and the default
202
+ * from the live pointer (Hi-C, LD) owes nothing, and the default
202
203
  * costs it nothing. Declared here so the clear can be installed for every
203
204
  * display rather than remembered per display — forgetting it is the
204
205
  * failure ARCHITECTURE.md's stored-hover section is about, and it used to
@@ -272,6 +273,87 @@ function stateModelFactory() {
272
273
  }
273
274
  return { applied, unapplied, failed };
274
275
  },
276
+ }))
277
+ .views(self => ({
278
+ /**
279
+ * #getter
280
+ * The grammar settings this display's config declares, by slot name
281
+ * (`PLOT_VOCABULARY`): what "Edit plot..." edits. Empty on a display
282
+ * with none, which then offers no editor.
283
+ */
284
+ get plotKeys() {
285
+ return plotKeysOf(self.configuration);
286
+ },
287
+ /**
288
+ * #getter
289
+ * Those settings as declared, defaults left off and a shorthand written
290
+ * as a config file writes it. An agent edits a `structuredClone` of it,
291
+ * checks it with `plotProblems` and hands it to `applyPlot`.
292
+ */
293
+ get plot() {
294
+ return plotOf(self.configuration);
295
+ },
296
+ /**
297
+ * #getter
298
+ * The worked examples this display type declares, which "Edit plot..."
299
+ * offers as buttons.
300
+ */
301
+ get plotExamples() {
302
+ const { pluginManager } = getEnv(self);
303
+ return pluginManager.getDisplayType(self.type).plotExamples;
304
+ },
305
+ /**
306
+ * #getter
307
+ * This display type's page in the config reference, which lists every
308
+ * setting it takes.
309
+ */
310
+ get configDocsUrl() {
311
+ const name = getConfigurationSchemaMetadata(self.configuration)?.name;
312
+ return name
313
+ ? `https://jbrowse.org/jb2/docs/config/${name.toLowerCase()}/`
314
+ : 'https://jbrowse.org/jb2/docs/config_guide/';
315
+ },
316
+ /**
317
+ * #method
318
+ * A draft as the config would hold it, merged over `plot`, throwing what
319
+ * a config file would be refused for. Nothing on the display changes.
320
+ */
321
+ liftPlot(draft) {
322
+ return liftPlotDraft(self.configuration, draft);
323
+ },
324
+ /**
325
+ * #method
326
+ * What a draft would report once applied, throwing a refusal as
327
+ * `liftPlot` does. A display with a rule list over its settings
328
+ * overrides it; the draft still applies, and draws what it can.
329
+ */
330
+ plotProblems(draft) {
331
+ this.liftPlot(draft);
332
+ return [];
333
+ },
334
+ /**
335
+ * #method
336
+ * What applying a draft would write, by setting: the lifted value where
337
+ * it differs from `plot`, `null` where it resets one. Throws a refusal.
338
+ */
339
+ plotWrites(draft) {
340
+ return plotWritesOf(self.configuration, draft);
341
+ },
342
+ }))
343
+ .actions(self => ({
344
+ /**
345
+ * #action
346
+ * Write a draft over `plot`: each setting it changes is replaced whole,
347
+ * so a member left out of an object goes back to its default, and `null`
348
+ * resets the setting. A draft the schema refuses, or naming a setting
349
+ * outside the plot, throws before anything is written.
350
+ */
351
+ applyPlot(draft) {
352
+ const { configuration } = self;
353
+ for (const [key, value] of Object.entries(self.plotWrites(draft))) {
354
+ setConf(configuration, key, value);
355
+ }
356
+ },
275
357
  }));
276
358
  }
277
359
  export const BaseDisplay = stateModelFactory();
@@ -29,7 +29,7 @@ declare const BaseViewModel: import("@jbrowse/mobx-state-tree").IModelType<{
29
29
  *
30
30
  * `ViewContainer` mounts a view's body only while an IntersectionObserver
31
31
  * says it is on screen, to hold the app under the WebGL2 context ceiling
32
- * (`reference/GPU_CONTEXT_BUDGET.md`). A view below the fold therefore has
32
+ * (`reference/GPU_PORTABILITY.md`). A view below the fold therefore has
33
33
  * no canvas, so nothing ever calls `markCanvasDrawn` and the pre-first-paint
34
34
  * term of `displayPhase` pins every display in it at `loading` with nothing
35
35
  * left to resolve it — which parks `[data-app-phase="ready"]` for the whole
@@ -169,7 +169,7 @@ export declare const BaseViewModelWithDisplayedRegions: import("@jbrowse/mobx-st
169
169
  *
170
170
  * `ViewContainer` mounts a view's body only while an IntersectionObserver
171
171
  * says it is on screen, to hold the app under the WebGL2 context ceiling
172
- * (`reference/GPU_CONTEXT_BUDGET.md`). A view below the fold therefore has
172
+ * (`reference/GPU_PORTABILITY.md`). A view below the fold therefore has
173
173
  * no canvas, so nothing ever calls `markCanvasDrawn` and the pre-first-paint
174
174
  * term of `displayPhase` pins every display in it at `loading` with nothing
175
175
  * left to resolve it — which parks `[data-app-phase="ready"]` for the whole
@@ -54,7 +54,7 @@ const BaseViewModel = types
54
54
  *
55
55
  * `ViewContainer` mounts a view's body only while an IntersectionObserver
56
56
  * says it is on screen, to hold the app under the WebGL2 context ceiling
57
- * (`reference/GPU_CONTEXT_BUDGET.md`). A view below the fold therefore has
57
+ * (`reference/GPU_PORTABILITY.md`). A view below the fold therefore has
58
58
  * no canvas, so nothing ever calls `markCanvasDrawn` and the pre-first-paint
59
59
  * term of `displayPhase` pins every display in it at `loading` with nothing
60
60
  * left to resolve it — which parks `[data-app-phase="ready"]` for the whole
@@ -7,7 +7,7 @@ export interface JexlFilterExample {
7
7
  }
8
8
  /**
9
9
  * Editor for a display's jexl feature filters, for any display implementing
10
- * the two-tier {@link JexlFilterModel} contract. The Conditions tab shows each
10
+ * the {@link JexlFilterModel} contract. The Conditions tab shows each
11
11
  * line as rows of field, operator and value where it can, and as text where it
12
12
  * cannot; the Text tab is every line as text.
13
13
  *
@@ -6,7 +6,6 @@ import { Button, ListItemText, MenuItem, Tab, Tabs, TextField, } from '@mui/mate
6
6
  import { observer } from 'mobx-react';
7
7
  import { readConfObject } from '../configuration/readConfObject.js';
8
8
  import { addRow, describedColumns, readFilterRows, removeRow, replaceRow, resolveFields, stripJexlPrefix, writeFilterRows, } from '../util/jexlFilterRows.js';
9
- import { activeJexlFilters } from '../util/jexlFilters.js';
10
9
  import { ensureJexlPrefix } from '../util/jexlStrings.js';
11
10
  import { getContainingTrack } from '../util/mstUtils.js';
12
11
  import { makeStyles } from '../util/tss-react/index.js';
@@ -100,7 +99,7 @@ function withOneRow(state) {
100
99
  }
101
100
  /**
102
101
  * Editor for a display's jexl feature filters, for any display implementing
103
- * the two-tier {@link JexlFilterModel} contract. The Conditions tab shows each
102
+ * the {@link JexlFilterModel} contract. The Conditions tab shows each
104
103
  * line as rows of field, operator and value where it can, and as text where it
105
104
  * cannot; the Text tab is every line as text.
106
105
  *
@@ -116,7 +115,7 @@ const JexlFilterDialog = observer(function JexlFilterDialog({ model, handleClose
116
115
  const fieldList = fields instanceof Promise ? use(fields) : fields;
117
116
  const described = metadata && use(metadata);
118
117
  const [choices] = useState(() => resolveFields([...fieldList, ...describedColumns(described, fieldList)]));
119
- const [state, setState] = useState(() => withOneRow(readFilterRows(activeJexlFilters(model), jexl, choices)));
118
+ const [state, setState] = useState(() => withOneRow(readFilterRows(model.configuredFilters(), jexl, choices)));
120
119
  const [tab, setTab] = useState('conditions');
121
120
  const [text, setText] = useState({ value: '', shown: '' });
122
121
  const textEdited = text.value !== text.shown;
@@ -9,10 +9,13 @@ declare const TimeTraveller: import("@jbrowse/mobx-state-tree").IModelType<{
9
9
  }, {
10
10
  history: unknown[];
11
11
  notTrackingUndo: boolean;
12
+ hasPendingRecord: boolean;
12
13
  } & {
13
14
  readonly canUndo: boolean;
14
15
  readonly canRedo: boolean;
15
16
  } & {
17
+ setHasPendingRecord(value: boolean): void;
18
+ flushPendingRecord(): void;
16
19
  stopTrackingUndo(): void;
17
20
  resumeTrackingUndo(): void;
18
21
  addUndoState(snapshot: unknown): void;
@@ -14,13 +14,16 @@ const TimeTraveller = types
14
14
  .volatile(() => ({
15
15
  history: [],
16
16
  notTrackingUndo: false,
17
+ hasPendingRecord: false,
17
18
  }))
18
19
  .views(self => ({
19
20
  get canUndo() {
20
- return self.undoIdx > 0 && !self.notTrackingUndo;
21
+ return ((self.undoIdx > 0 || self.hasPendingRecord) && !self.notTrackingUndo);
21
22
  },
22
23
  get canRedo() {
23
- return self.undoIdx < self.history.length - 1 && !self.notTrackingUndo;
24
+ return (self.undoIdx < self.history.length - 1 &&
25
+ !self.hasPendingRecord &&
26
+ !self.notTrackingUndo);
24
27
  },
25
28
  }))
26
29
  .actions(self => {
@@ -93,7 +96,21 @@ const TimeTraveller = types
93
96
  skipNextUndoState = false;
94
97
  }
95
98
  }
99
+ let recordPending;
96
100
  return {
101
+ setHasPendingRecord(value) {
102
+ self.hasPendingRecord = value;
103
+ },
104
+ flushPendingRecord() {
105
+ if (debounceTimer) {
106
+ clearTimeout(debounceTimer);
107
+ debounceTimer = undefined;
108
+ }
109
+ if (self.hasPendingRecord) {
110
+ self.hasPendingRecord = false;
111
+ recordPending?.();
112
+ }
113
+ },
97
114
  stopTrackingUndo() {
98
115
  self.notTrackingUndo = true;
99
116
  },
@@ -141,14 +158,17 @@ const TimeTraveller = types
141
158
  skipNextUndoState = false;
142
159
  self.history = [];
143
160
  self.undoIdx = -1;
161
+ self.hasPendingRecord = false;
144
162
  // read the target's current state rather than a snapshot captured per
145
163
  // change: any change inside the debounce window re-times it, so when it
146
164
  // does fire the two agree
147
165
  const record = () => {
166
+ this.setHasPendingRecord(false);
148
167
  if (targetStore) {
149
168
  this.addUndoState(getSnapshot(targetStore));
150
169
  }
151
170
  };
171
+ recordPending = record;
152
172
  // onPatch, not onSnapshot: this only needs to know *that* the target
153
173
  // changed, and the snapshot is taken once per debounce window below.
154
174
  // onSnapshot would serialize the whole target on every single change to
@@ -178,17 +198,29 @@ const TimeTraveller = types
178
198
  clearTimeout(debounceTimer);
179
199
  debounceTimer = undefined;
180
200
  }
201
+ this.setHasPendingRecord(false);
181
202
  return;
182
203
  }
204
+ if (!self.hasPendingRecord) {
205
+ this.setHasPendingRecord(true);
206
+ }
183
207
  armRecording(record);
184
208
  });
185
209
  this.addUndoState(getSnapshot(targetStore));
186
210
  },
187
211
  undo() {
212
+ this.flushPendingRecord();
213
+ if (self.undoIdx <= 0) {
214
+ return;
215
+ }
188
216
  self.undoIdx--;
189
217
  applyHistoryState(self.undoIdx);
190
218
  },
191
219
  redo() {
220
+ this.flushPendingRecord();
221
+ if (self.undoIdx >= self.history.length - 1) {
222
+ return;
223
+ }
192
224
  self.undoIdx++;
193
225
  applyHistoryState(self.undoIdx);
194
226
  },
@@ -0,0 +1,8 @@
1
+ /**
2
+ * #api core/util
3
+ * The base's entry for this display, hydrated, which a reset returns to and
4
+ * "is this the reader's" compares against: the config.json's, or the one a
5
+ * track the session owns was added with; empty in a session that keeps no
6
+ * base.
7
+ */
8
+ export declare function baseDisplayConfig(self: object): Record<string, unknown>;
@@ -0,0 +1,27 @@
1
+ import { hasParent } from '@jbrowse/mobx-state-tree';
2
+ import { getConf } from '../configuration/getConf.js';
3
+ import { getContainingTrack, getSession } from './mstUtils.js';
4
+ import { isSessionWithBaseTrackConfig } from './types/index.js';
5
+ /**
6
+ * #api core/util
7
+ * The base's entry for this display, hydrated, which a reset returns to and
8
+ * "is this the reader's" compares against: the config.json's, or the one a
9
+ * track the session owns was added with; empty in a session that keeps no
10
+ * base.
11
+ */
12
+ export function baseDisplayConfig(self) {
13
+ if (!hasParent(self)) {
14
+ return {};
15
+ }
16
+ const session = getSession(self);
17
+ if (!isSessionWithBaseTrackConfig(session)) {
18
+ return {};
19
+ }
20
+ const base = session.baseTrackConfig(getConf(getContainingTrack(self), 'trackId'));
21
+ const { displayId } = self
22
+ .configuration;
23
+ const displays = base?.displays;
24
+ return ((Array.isArray(displays)
25
+ ? displays.find(d => d.displayId === displayId)
26
+ : undefined) ?? {});
27
+ }