@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.
- package/README.md +113 -5
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
- package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
- package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
- package/esm/CorePlugin.js +32 -1
- package/esm/ReExports/coreModules.generated.js +248 -246
- package/esm/ReExports/coreWorkerModules.generated.js +246 -244
- package/esm/ReExports/list.js +3 -2
- package/esm/configuration/index.d.ts +2 -0
- package/esm/configuration/index.js +1 -0
- package/esm/configuration/plot.d.ts +61 -0
- package/esm/configuration/plot.js +142 -0
- package/esm/pluggableElementTypes/DisplayType.d.ts +7 -0
- package/esm/pluggableElementTypes/DisplayType.js +6 -0
- package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
- package/esm/pluggableElementTypes/WidgetType.js +6 -0
- package/esm/pluggableElementTypes/index.js +1 -8
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +57 -1
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
- package/esm/ui/JexlFilterDialog.d.ts +1 -1
- package/esm/ui/JexlFilterDialog.js +2 -3
- package/esm/util/TimeTraveller.d.ts +3 -0
- package/esm/util/TimeTraveller.js +34 -2
- package/esm/util/baseDisplayConfig.d.ts +8 -0
- package/esm/util/baseDisplayConfig.js +27 -0
- package/esm/util/cachedSetup.d.ts +16 -6
- package/esm/util/cachedSetup.js +108 -38
- package/esm/util/cellMatches.d.ts +23 -0
- package/esm/util/cellMatches.js +264 -0
- package/esm/util/cellsStep.d.ts +29 -0
- package/esm/util/cellsStep.js +437 -0
- 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/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 +8 -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/tabix.d.ts +6 -2
- package/esm/util/tabix.js +2 -2
- package/esm/util/tracks.d.ts +3 -30
- package/esm/util/tracks.js +4 -39
- 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/withFeatureDetails.js +4 -0
- 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
|
|
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 (
|
|
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 (
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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
|
|
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(
|
|
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 &&
|
|
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
|
+
}
|