@jbrowse/product-core 5.0.0-beta.1 → 5.0.0-beta.3
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/esm/RootModel/BaseRootModel.js +2 -1
- package/esm/RootModel/PreferencesConfig.d.ts +15 -0
- package/esm/RootModel/PreferencesConfig.js +15 -0
- package/esm/RootModel/createConfigModel.d.ts +8 -0
- package/esm/Session/BaseSession.d.ts +11 -26
- package/esm/Session/BaseSession.js +21 -60
- package/esm/Session/MultipleViews.d.ts +17 -3
- package/esm/Session/MultipleViews.js +45 -5
- package/esm/Session/Preferences.d.ts +2 -1
- package/esm/Session/Preferences.js +5 -0
- package/esm/Session/SessionTracks.d.ts +4 -1
- package/esm/Session/SessionTracks.js +8 -0
- package/esm/Session/TrackMenu.js +2 -2
- package/esm/Session/Tracks.d.ts +4 -5
- package/esm/Session/Tracks.js +115 -92
- package/esm/Session/developerMode.d.ts +7 -0
- package/esm/Session/developerMode.js +43 -0
- package/esm/Session/readdedTrackConf.d.ts +8 -0
- package/esm/Session/readdedTrackConf.js +25 -0
- package/esm/Session/temporaryAssemblyTracks.d.ts +4 -3
- package/esm/Session/temporaryAssemblyTracks.js +6 -7
- package/esm/controllerTracks.d.ts +3 -3
- package/esm/controllerTracks.js +17 -4
- package/esm/index.d.ts +2 -2
- package/esm/index.js +2 -2
- package/esm/loadPlugins.d.ts +1 -1
- package/esm/loadPlugins.js +6 -3
- package/esm/pruneUnbuildableNodes.js +5 -2
- package/esm/rpcWorker.js +5 -4
- package/esm/ui/AboutDialogContents.js +2 -1
- package/esm/ui/DescriptionPanel.d.ts +3 -0
- package/esm/ui/DescriptionPanel.js +24 -0
- package/esm/ui/DisplayDefaultsSection.d.ts +7 -20
- package/esm/ui/DisplayDefaultsSection.js +9 -47
- package/esm/ui/GeneralPreferencesTab.d.ts +14 -0
- package/esm/ui/GeneralPreferencesTab.js +27 -0
- package/esm/ui/PreferenceCheckbox.d.ts +6 -0
- package/esm/ui/PreferenceCheckbox.js +16 -0
- package/esm/ui/PreferencesDialog.d.ts +6 -27
- package/esm/ui/PreferencesDialog.js +38 -119
- package/esm/ui/ViewPreferencesTab.d.ts +12 -0
- package/esm/ui/ViewPreferencesTab.js +15 -0
- package/esm/ui/descriptionHtml.d.ts +26 -0
- package/esm/ui/descriptionHtml.js +66 -0
- package/esm/ui/displayDefaultChanges.d.ts +31 -0
- package/esm/ui/displayDefaultChanges.js +66 -0
- package/esm/ui/preferencesReset.d.ts +18 -0
- package/esm/ui/preferencesReset.js +64 -0
- package/esm/useEngineLifecycle.d.ts +7 -1
- package/esm/useEngineLifecycle.js +24 -2
- package/package.json +5 -4
- package/esm/scheduleDetachedDestroy.d.ts +0 -18
- package/esm/scheduleDetachedDestroy.js +0 -24
package/esm/Session/Tracks.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { expandLooseTrackConfig } from '@jbrowse/core/util/tracks';
|
|
2
|
+
import { addDisposer, types } from '@jbrowse/mobx-state-tree';
|
|
3
|
+
import { autorun, computed } from 'mobx';
|
|
3
4
|
import { BaseSessionModel, isBaseSession } from './BaseSession.js';
|
|
4
5
|
import { isSessionWithConnections } from './Connections.js';
|
|
5
6
|
import { ReferenceManagementSessionMixin } from './ReferenceManagement.js';
|
|
7
|
+
import { assertNotReaddedDifferently } from './readdedTrackConf.js';
|
|
6
8
|
import { assertTrackConfOutlivesItsAssemblies } from './temporaryAssemblyTracks.js';
|
|
7
9
|
/**
|
|
8
10
|
* #stateModel TracksManagerSessionMixin
|
|
@@ -22,8 +24,11 @@ export function TracksManagerSessionMixin(pluginManager) {
|
|
|
22
24
|
// One index trackId → config for all tracks, assembly sequences, and
|
|
23
25
|
// connection tracks. Frozen jbrowse.tracks entries stay plain objects
|
|
24
26
|
// here; hydration to MST nodes happens lazily in TrackConfigurationReference
|
|
25
|
-
// on first access.
|
|
26
|
-
// per
|
|
27
|
+
// on first access. Held hot by the autorun in afterAttach, so a reader
|
|
28
|
+
// outside any reaction (ranking search hits: 33ms per search unheld on a
|
|
29
|
+
// 2000-track config, 0.07ms held) gets the cache too. Not `keepAlive`: that
|
|
30
|
+
// subscription never ends, and it reaches jbrowse.tracks on the root, so
|
|
31
|
+
// it pinned every superseded session for the tab's life.
|
|
27
32
|
const tracksByIdRecord = computed(() => {
|
|
28
33
|
const temporaryAssemblies = 'temporaryAssemblies' in self
|
|
29
34
|
? self.temporaryAssemblies
|
|
@@ -49,7 +54,7 @@ export function TracksManagerSessionMixin(pluginManager) {
|
|
|
49
54
|
e.config,
|
|
50
55
|
]),
|
|
51
56
|
]);
|
|
52
|
-
});
|
|
57
|
+
}, { name: 'tracksByIdRecord' });
|
|
53
58
|
// Per-id computed cache backing getTrackById. Resolving one track's config
|
|
54
59
|
// subscribes only to that id's computed, so editing track A leaves track
|
|
55
60
|
// B's observers untouched: an unedited id's entry keeps its object identity
|
|
@@ -92,96 +97,114 @@ export function TracksManagerSessionMixin(pluginManager) {
|
|
|
92
97
|
};
|
|
93
98
|
})
|
|
94
99
|
.actions(self => ({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* This mixin's session has no separate session-track store, so the
|
|
100
|
-
* destination is the jbrowse config — which in the products that compose
|
|
101
|
-
* it (desktop) is the single user's own file rather than something a
|
|
102
|
-
* server hands other visitors, so the two scopes are the same place.
|
|
103
|
-
* Defined here anyway so that every session has it: a feature standing a
|
|
104
|
-
* track up on the user's behalf can then call one action everywhere
|
|
105
|
-
* instead of asking which mixin it landed on.
|
|
106
|
-
* `SessionTracksManagerSessionMixin` overrides it with the real
|
|
107
|
-
* session-scoped store.
|
|
108
|
-
*/
|
|
109
|
-
addSessionTrackConf(trackConf) {
|
|
110
|
-
assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
|
|
111
|
-
return self.jbrowse.addTrackConf(trackConf);
|
|
100
|
+
afterAttach() {
|
|
101
|
+
addDisposer(self, autorun(() => {
|
|
102
|
+
self.getTracksById();
|
|
103
|
+
}));
|
|
112
104
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* "Add track" workflows, where an admin adding a track means to add it
|
|
117
|
-
* for the whole site. `SessionTracksManagerSessionMixin` overrides it to
|
|
118
|
-
* send a non-admin's to the session instead; here there is only the one
|
|
119
|
-
* destination.
|
|
120
|
-
*
|
|
121
|
-
* Anything that is not an Add-track workflow wants
|
|
122
|
-
* `addSessionTrackConf`: a track a feature stands up on the user's behalf
|
|
123
|
-
* — a search result, a computed consensus, a reconstruction's labels — is
|
|
124
|
-
* not a catalog entry, and publishing one writes it into the config.json
|
|
125
|
-
* every visitor is served, once per click.
|
|
126
|
-
*/
|
|
127
|
-
publishTrackConf(trackConf) {
|
|
128
|
-
assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
|
|
129
|
-
return self.jbrowse.addTrackConf(trackConf);
|
|
130
|
-
},
|
|
131
|
-
/**
|
|
132
|
-
* #action
|
|
133
|
-
* Deprecated alias of `addSessionTrackConf`. Call that, or
|
|
134
|
-
* `publishTrackConf`, which say which destination they mean.
|
|
135
|
-
*
|
|
136
|
-
* @deprecated
|
|
137
|
-
*
|
|
138
|
-
* Kept because a prebuilt plugin bundle reaches this by name at runtime
|
|
139
|
-
* and cannot be recompiled — `jbrowse-plugin-protein3d` calls it, and a
|
|
140
|
-
* member lookup that stops resolving throws nothing at all
|
|
141
|
-
* (`pluginFacingSessionApi.test.ts` is the guard). It now means the
|
|
142
|
-
* session, which is the destination every such caller wanted: a track a
|
|
143
|
-
* plugin stands up for one user is not a catalog entry. Nothing in tree
|
|
144
|
-
* may call it — `no-restricted-syntax` says so.
|
|
145
|
-
*
|
|
146
|
-
* Spelled out rather than delegating through `this`, for the reason
|
|
147
|
-
* `SessionTracks.addToSession` is a plain closure: an action whose return
|
|
148
|
-
* type is inferred from a sibling's makes the pair circular.
|
|
149
|
-
*/
|
|
150
|
-
addTrackConf(trackConf) {
|
|
105
|
+
}))
|
|
106
|
+
.actions(self => {
|
|
107
|
+
function addToSession(trackConf) {
|
|
151
108
|
assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// jbrowse.tracks; persist its edit there (jbrowse.updateTrackConf would
|
|
163
|
-
// no-op, since the track isn't in the config). Desktop uses this base
|
|
164
|
-
// mixin, so without this branch a connection-track edit is lost on
|
|
165
|
-
// reload.
|
|
166
|
-
if (isSessionWithConnections(self) &&
|
|
167
|
-
trackConf.trackId in self.connectionTrackConfigs) {
|
|
168
|
-
self.updateConnectionTrackConfig(trackConf);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
self.jbrowse.updateTrackConf(trackConf);
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
/**
|
|
175
|
-
* #action
|
|
176
|
-
*/
|
|
177
|
-
deleteTrackConf(trackConf) {
|
|
178
|
-
const { trackId } = trackConf;
|
|
179
|
-
self.dereferenceTrack(trackId, self.getReferring(trackId));
|
|
180
|
-
if (self.adminMode) {
|
|
181
|
-
return self.jbrowse.deleteTrackConf(trackConf);
|
|
109
|
+
const expanded = expandLooseTrackConfig(trackConf, pluginManager);
|
|
110
|
+
const { trackId, type } = expanded;
|
|
111
|
+
const existing = self.getTrackById(trackId);
|
|
112
|
+
if (existing) {
|
|
113
|
+
assertNotReaddedDifferently(pluginManager, existing, {
|
|
114
|
+
...expanded,
|
|
115
|
+
trackId,
|
|
116
|
+
type,
|
|
117
|
+
});
|
|
118
|
+
return existing;
|
|
182
119
|
}
|
|
183
|
-
|
|
184
|
-
|
|
120
|
+
return self.jbrowse.addTrackConf(trackConf);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
/**
|
|
124
|
+
* #action
|
|
125
|
+
* Add a track config to *this session*.
|
|
126
|
+
*
|
|
127
|
+
* This mixin's session has no separate session-track store, so the
|
|
128
|
+
* destination is the jbrowse config — which in the products that compose
|
|
129
|
+
* it (desktop) is the single user's own file rather than something a
|
|
130
|
+
* server hands other visitors, so the two scopes are the same place.
|
|
131
|
+
* Defined here anyway so that every session has it: a feature standing a
|
|
132
|
+
* track up on the user's behalf can then call one action everywhere
|
|
133
|
+
* instead of asking which mixin it landed on.
|
|
134
|
+
* `SessionTracksManagerSessionMixin` overrides it with the real
|
|
135
|
+
* session-scoped store.
|
|
136
|
+
*/
|
|
137
|
+
addSessionTrackConf(trackConf) {
|
|
138
|
+
return addToSession(trackConf);
|
|
139
|
+
},
|
|
140
|
+
/**
|
|
141
|
+
* #action
|
|
142
|
+
* Add a track config wherever *this user's* catalog edits belong — the
|
|
143
|
+
* "Add track" workflows, where an admin adding a track means to add it
|
|
144
|
+
* for the whole site. `SessionTracksManagerSessionMixin` overrides it to
|
|
145
|
+
* send a non-admin's to the session instead; here there is only the one
|
|
146
|
+
* destination.
|
|
147
|
+
*
|
|
148
|
+
* Anything that is not an Add-track workflow wants
|
|
149
|
+
* `addSessionTrackConf`: a track a feature stands up on the user's behalf
|
|
150
|
+
* — a search result, a computed consensus, a reconstruction's labels — is
|
|
151
|
+
* not a catalog entry, and publishing one writes it into the config.json
|
|
152
|
+
* every visitor is served, once per click.
|
|
153
|
+
*/
|
|
154
|
+
publishTrackConf(trackConf) {
|
|
155
|
+
assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
|
|
156
|
+
return self.jbrowse.addTrackConf(trackConf);
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* #action
|
|
160
|
+
* Deprecated alias of `addSessionTrackConf`. Call that, or
|
|
161
|
+
* `publishTrackConf`, which say which destination they mean.
|
|
162
|
+
*
|
|
163
|
+
* @deprecated
|
|
164
|
+
*
|
|
165
|
+
* Kept because a prebuilt plugin bundle reaches this by name at runtime
|
|
166
|
+
* and cannot be recompiled — `jbrowse-plugin-protein3d` calls it, and a
|
|
167
|
+
* member lookup that stops resolving throws nothing at all
|
|
168
|
+
* (`pluginFacingSessionApi.test.ts` is the guard). It now means the
|
|
169
|
+
* session, which is the destination every such caller wanted: a track a
|
|
170
|
+
* plugin stands up for one user is not a catalog entry. Nothing in tree
|
|
171
|
+
* may call it — `no-restricted-syntax` says so.
|
|
172
|
+
*/
|
|
173
|
+
addTrackConf(trackConf) {
|
|
174
|
+
return addToSession(trackConf);
|
|
175
|
+
},
|
|
176
|
+
/**
|
|
177
|
+
* #action
|
|
178
|
+
* Persist edited track config back to the in-memory jbrowse config. The
|
|
179
|
+
* session-tracks mixin overrides this so a non-admin's edits become a
|
|
180
|
+
* shareable session-track override instead.
|
|
181
|
+
*/
|
|
182
|
+
updateTrackConfiguration(trackConf) {
|
|
183
|
+
// an opened connection track lives in connectionTrackConfigs, not
|
|
184
|
+
// jbrowse.tracks; persist its edit there (jbrowse.updateTrackConf would
|
|
185
|
+
// no-op, since the track isn't in the config). Desktop uses this base
|
|
186
|
+
// mixin, so without this branch a connection-track edit is lost on
|
|
187
|
+
// reload.
|
|
188
|
+
if (isSessionWithConnections(self) &&
|
|
189
|
+
trackConf.trackId in self.connectionTrackConfigs) {
|
|
190
|
+
self.updateConnectionTrackConfig(trackConf);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
self.jbrowse.updateTrackConf(trackConf);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
/**
|
|
197
|
+
* #action
|
|
198
|
+
*/
|
|
199
|
+
deleteTrackConf(trackConf) {
|
|
200
|
+
const { trackId } = trackConf;
|
|
201
|
+
self.dereferenceTrack(trackId, self.getReferring(trackId));
|
|
202
|
+
if (self.adminMode) {
|
|
203
|
+
return self.jbrowse.deleteTrackConf(trackConf);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
});
|
|
185
208
|
}
|
|
186
209
|
/** Type guard for SessionWithTracks */
|
|
187
210
|
export function isSessionWithTracks(thing) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NotificationSink } from '@jbrowse/core/util/types';
|
|
2
|
+
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
3
|
+
interface DeveloperModeHost extends IStateTreeNode, Pick<NotificationSink, 'notify'> {
|
|
4
|
+
getPreference: (key: string) => unknown;
|
|
5
|
+
}
|
|
6
|
+
export declare function applyDeveloperMode(self: DeveloperModeHost): void;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { addDisposer, isAlive } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import { enableContractReports, setContractReportSink, } from '@jbrowse/render-core/contractReports';
|
|
3
|
+
/**
|
|
4
|
+
* What a plugin author sees when their display breaks an ordering contract in
|
|
5
|
+
* somebody else's production build.
|
|
6
|
+
*
|
|
7
|
+
* The checks themselves have always run and always reported; what they had no
|
|
8
|
+
* way to reach was this population, which is the one least able to diagnose the
|
|
9
|
+
* symptom and the one nobody can write a test for. A `console.error` surviving
|
|
10
|
+
* the build would not have fixed that — nobody has a console open on a page
|
|
11
|
+
* they did not write — so the report has to land in the app, in the session the
|
|
12
|
+
* broken display is in, and it has to explain itself: the reader did not turn
|
|
13
|
+
* this on deliberately in the common case (a plugin served from their own
|
|
14
|
+
* machine did), and a message they cannot place reads as the app being broken.
|
|
15
|
+
*
|
|
16
|
+
* `warning` rather than `error`: an error snackbar in this app means the user's
|
|
17
|
+
* data or their request failed, and every word here is about code they are
|
|
18
|
+
* writing. Warnings persist until dismissed, which is what a message with a fix
|
|
19
|
+
* in it wants, and identical ones collapse in the snackbar — so a violation
|
|
20
|
+
* repeating across ten tracks of one broken type is still one notice.
|
|
21
|
+
*/
|
|
22
|
+
function notification({ family, message, armedBy }) {
|
|
23
|
+
return (`JBrowse ${family} contract broken — a bug in code, not in your data or ` +
|
|
24
|
+
`configuration.\n\n${message}\n\n` +
|
|
25
|
+
`Shown because ${armedBy}. These notices can be turned on anywhere with ` +
|
|
26
|
+
`localStorage.jbrowseDeveloperMode = 1, or site-wide with ` +
|
|
27
|
+
`configuration.preferences.developerMode.`);
|
|
28
|
+
}
|
|
29
|
+
export function applyDeveloperMode(self) {
|
|
30
|
+
if (self.getPreference('developerMode') === true) {
|
|
31
|
+
enableContractReports('this site sets configuration.preferences.developerMode');
|
|
32
|
+
}
|
|
33
|
+
addDisposer(self, setContractReportSink(report => {
|
|
34
|
+
// out of whatever reaction, fetch handler or upload found the violation:
|
|
35
|
+
// `notify` writes observables, and a diagnostic that throws where it is
|
|
36
|
+
// read is worse than the bug it describes
|
|
37
|
+
queueMicrotask(() => {
|
|
38
|
+
if (isAlive(self)) {
|
|
39
|
+
self.notify(notification(report), 'warning');
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
+
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
3
|
+
type TrackConfInput = {
|
|
4
|
+
trackId: string;
|
|
5
|
+
type: string;
|
|
6
|
+
} & Record<string, unknown>;
|
|
7
|
+
export declare function assertNotReaddedDifferently(pluginManager: PluginManager, existing: AnyConfigurationModel | TrackConfInput, trackConf: TrackConfInput): void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getSnapshot, isStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import { compareStructural } from 'mobx';
|
|
3
|
+
// What a config means once its defaults are resolved: a live config node
|
|
4
|
+
// answers with its snapshot, and a plain object (desktop's `jbrowse.tracks` is
|
|
5
|
+
// frozen, so its entries are the input as written) is hydrated through the
|
|
6
|
+
// same schema first. Two inputs that would build the same node compare equal.
|
|
7
|
+
function resolvedTrackConf(pluginManager, conf) {
|
|
8
|
+
return isStateTreeNode(conf)
|
|
9
|
+
? getSnapshot(conf)
|
|
10
|
+
: getSnapshot(pluginManager
|
|
11
|
+
.pluggableConfigSchemaType('track')
|
|
12
|
+
.create(conf, { pluginManager }));
|
|
13
|
+
}
|
|
14
|
+
// Re-adding a trackId the session already holds returns the existing config,
|
|
15
|
+
// which is what keeps a content-hashed trackId idempotent. It is also the trap
|
|
16
|
+
// a recomputed track walks into: the new features arrive under the old id, the
|
|
17
|
+
// old config is handed back, nothing is said, and the track keeps showing the
|
|
18
|
+
// first values it saw. Same id with different content is therefore refused,
|
|
19
|
+
// naming the two-step that works.
|
|
20
|
+
export function assertNotReaddedDifferently(pluginManager, existing, trackConf) {
|
|
21
|
+
const same = compareStructural(resolvedTrackConf(pluginManager, existing), resolvedTrackConf(pluginManager, trackConf));
|
|
22
|
+
if (!same) {
|
|
23
|
+
throw new Error(`Track "${trackConf.trackId}" is already in this session with a different configuration, and adding it again would keep the old one. Delete it first (session.deleteTrackConf(session.getTrackById("${trackConf.trackId}"))), then add — and give a recomputed FromConfigAdapter a new adapterId, since the adapter cache is keyed on it.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Checks that a track config being written into a session or config list is one
|
|
4
|
+
* something can still draw tomorrow.
|
|
5
5
|
*
|
|
6
6
|
* A config naming nothing but a temporary assembly is dead the moment its view
|
|
7
7
|
* closes, and no list outside that view has anyone to sweep it — which is the
|
|
@@ -18,6 +18,7 @@ import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
|
18
18
|
* `console.error` and never `throw`, matching `assertDisplayContract`: these
|
|
19
19
|
* writes happen inside launchers and menu handlers where an exception is caught
|
|
20
20
|
* and reported as a failed track, which would hide the violation. The jest gate
|
|
21
|
-
* (`config/jest/contractGate.js`) is what listens
|
|
21
|
+
* (`config/jest/contractGate.js`) is what listens in tree; out of tree
|
|
22
|
+
* `contractReports` does, once a host arms it.
|
|
22
23
|
*/
|
|
23
24
|
export declare function assertTrackConfOutlivesItsAssemblies(session: unknown, trackConf: AnyConfigurationModel | Record<string, unknown>, destination: string): void;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { namesTemporaryAssembly } from '@jbrowse/core/util';
|
|
2
|
+
import { reportContractViolation } from '@jbrowse/render-core/contractReports';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
4
|
+
* Checks that a track config being written into a session or config list is one
|
|
5
|
+
* something can still draw tomorrow.
|
|
5
6
|
*
|
|
6
7
|
* A config naming nothing but a temporary assembly is dead the moment its view
|
|
7
8
|
* closes, and no list outside that view has anyone to sweep it — which is the
|
|
@@ -18,17 +19,15 @@ import { namesTemporaryAssembly } from '@jbrowse/core/util';
|
|
|
18
19
|
* `console.error` and never `throw`, matching `assertDisplayContract`: these
|
|
19
20
|
* writes happen inside launchers and menu handlers where an exception is caught
|
|
20
21
|
* and reported as a failed track, which would hide the violation. The jest gate
|
|
21
|
-
* (`config/jest/contractGate.js`) is what listens
|
|
22
|
+
* (`config/jest/contractGate.js`) is what listens in tree; out of tree
|
|
23
|
+
* `contractReports` does, once a host arms it.
|
|
22
24
|
*/
|
|
23
25
|
export function assertTrackConfOutlivesItsAssemblies(session, trackConf, destination) {
|
|
24
|
-
if (process.env.NODE_ENV === 'production') {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
26
|
if (!namesTemporaryAssembly(session, trackConf)) {
|
|
28
27
|
return;
|
|
29
28
|
}
|
|
30
29
|
const { trackId } = trackConf;
|
|
31
|
-
|
|
30
|
+
reportContractViolation('session', `${destination} was given "${trackId}", ` +
|
|
32
31
|
`which names a temporary assembly — one a comparative view synthesized ` +
|
|
33
32
|
`and gives back when it closes, so this config outlives the only ` +
|
|
34
33
|
`assembly that could draw it and nothing sweeps the list it landed in. ` +
|
|
@@ -14,7 +14,7 @@ interface ControllerView {
|
|
|
14
14
|
trackId: string;
|
|
15
15
|
};
|
|
16
16
|
}[];
|
|
17
|
-
|
|
17
|
+
launchTrack: (trackId: string) => Promise<unknown>;
|
|
18
18
|
hideTrack: (trackId: string) => unknown;
|
|
19
19
|
}
|
|
20
20
|
export interface ControllerSession extends IStateTreeNode {
|
|
@@ -48,9 +48,9 @@ export declare function resolveTracks(tracks: TrackInput[], node: IStateTreeNode
|
|
|
48
48
|
* Session-scoped: these are the host's declared tracks for one embed, not a
|
|
49
49
|
* catalog an admin curates.
|
|
50
50
|
*/
|
|
51
|
-
export declare function openTracks(session: ControllerSession, tracks: TrackConf[]): void
|
|
51
|
+
export declare function openTracks(session: ControllerSession, tracks: TrackConf[]): Promise<void>;
|
|
52
52
|
/** Open every wanted track and close any others the view currently shows. */
|
|
53
|
-
export declare function reconcileTracks(session: ControllerSession, tracks: TrackConf[]): void
|
|
53
|
+
export declare function reconcileTracks(session: ControllerSession, tracks: TrackConf[]): Promise<void>;
|
|
54
54
|
/** Merge a hub's own search adapters with the ones the caller supplied. */
|
|
55
55
|
export declare function mergeSearchAdapters<T>(a: readonly T[] | undefined, b: readonly T[] | undefined): T[] | undefined;
|
|
56
56
|
export {};
|
package/esm/controllerTracks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getEnv } from '@jbrowse/core/util';
|
|
2
2
|
import { guessTrackConf } from '@jbrowse/core/util/tracks';
|
|
3
|
+
import { isAlive } from '@jbrowse/mobx-state-tree';
|
|
3
4
|
import { resolveLocalFileUris } from './localFiles.js';
|
|
4
5
|
export function isLooseTrack(track) {
|
|
5
6
|
return typeof track === 'string' || (!('adapter' in track) && 'uri' in track);
|
|
@@ -47,20 +48,32 @@ export function resolveTracks(tracks, node, assemblyName, localFiles) {
|
|
|
47
48
|
* Session-scoped: these are the host's declared tracks for one embed, not a
|
|
48
49
|
* catalog an admin curates.
|
|
49
50
|
*/
|
|
50
|
-
export function openTracks(session, tracks) {
|
|
51
|
+
export async function openTracks(session, tracks) {
|
|
52
|
+
// sequential, not Promise.all: each display's state model may be a separate
|
|
53
|
+
// dynamic import, and racing them would land the tracks on the view in
|
|
54
|
+
// whatever order the chunks happen to resolve rather than the caller's
|
|
51
55
|
for (const conf of tracks) {
|
|
56
|
+
// the engine can be destroyed while a chunk is in flight (an unmounted
|
|
57
|
+
// embed); reading the dead tree from this floating promise would throw
|
|
58
|
+
// uncatchably
|
|
59
|
+
if (!isAlive(session.view)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
52
62
|
const trackId = conf.trackId;
|
|
53
63
|
if (!session.getTrackById(trackId)) {
|
|
54
64
|
session.addSessionTrackConf(conf);
|
|
55
65
|
}
|
|
56
|
-
session.view.
|
|
66
|
+
await session.view.launchTrack(trackId);
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
69
|
/** Open every wanted track and close any others the view currently shows. */
|
|
60
|
-
export function reconcileTracks(session, tracks) {
|
|
70
|
+
export async function reconcileTracks(session, tracks) {
|
|
61
71
|
const { view } = session;
|
|
62
72
|
const wanted = new Set(tracks.map(t => t.trackId));
|
|
63
|
-
openTracks(session, tracks);
|
|
73
|
+
await openTracks(session, tracks);
|
|
74
|
+
if (!isAlive(view)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
64
77
|
// materialize the ids first: hideTrack splices view.tracks, so iterating it
|
|
65
78
|
// live would skip entries
|
|
66
79
|
const unwanted = view.tracks
|
package/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { AssertExtends, AssertNotAny } from './assertExtends.ts';
|
|
2
2
|
export { asRoot, asSession } from './siblingCast.ts';
|
|
3
|
-
export { scheduleDetachedDestroy } from '
|
|
3
|
+
export { scheduleDetachedDestroy } from '@jbrowse/core/util';
|
|
4
4
|
export { BaseRootModelFactory, FormatAboutConfigSchemaFactory, FormatDetailsConfigSchemaFactory, HierarchicalConfigSchemaFactory, InternetAccountsRootModelMixin, PreferencesConfigSchemaFactory, createConfigModel, exportSessionMenuItem, importSessionMenuItem, isRootModel, newSessionMenuItem, openConnectionMenuItem, openTrackMenuItem, pluginStoreMenuItem, preferencesMenuItem, redoMenuItem, undoMenuItem, workspacesMenuItem, } from './RootModel/index.ts';
|
|
5
5
|
export type { BaseRootModel, BaseRootModelType, RootConfigurationSnapshot, RootModelWithInternetAccounts, RootModelWithInternetAccountsType, } from './RootModel/index.ts';
|
|
6
6
|
export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.ts';
|
|
@@ -21,7 +21,7 @@ export type { ControllerSession, TrackConf, TrackInput, } from './controllerTrac
|
|
|
21
21
|
export { resolveAssemblies, resolveAssembly } from './resolveAssemblies.ts';
|
|
22
22
|
export type { AssemblyInput, ResolvedAssemblies } from './resolveAssemblies.ts';
|
|
23
23
|
export { destroyViewState } from './destroyViewState.ts';
|
|
24
|
-
export { useCreateOnce, useDestroyOnUnmount } from './useEngineLifecycle.ts';
|
|
24
|
+
export { useAsyncEngineLifecycle, useCreateOnce, useCreateOnceAsync, useDestroyOnUnmount, } from './useEngineLifecycle.ts';
|
|
25
25
|
export { getSessionSnapshot, observeSession } from './observeSession.ts';
|
|
26
26
|
export type { SessionObservers, ViewLocation } from './observeSession.ts';
|
|
27
27
|
export { MIGRATED_DISPLAY_INSTANCE_KEYS, migrateConfigSnapshot, migrateSessionSnapshot, } from './sessionMigrations/index.ts';
|
package/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { asRoot, asSession } from './siblingCast.js';
|
|
2
|
-
export { scheduleDetachedDestroy } from '
|
|
2
|
+
export { scheduleDetachedDestroy } from '@jbrowse/core/util';
|
|
3
3
|
export { BaseRootModelFactory, FormatAboutConfigSchemaFactory, FormatDetailsConfigSchemaFactory, HierarchicalConfigSchemaFactory, InternetAccountsRootModelMixin, PreferencesConfigSchemaFactory, createConfigModel, exportSessionMenuItem, importSessionMenuItem, isRootModel, newSessionMenuItem, openConnectionMenuItem, openTrackMenuItem, pluginStoreMenuItem, preferencesMenuItem, redoMenuItem, undoMenuItem, workspacesMenuItem, } from './RootModel/index.js';
|
|
4
4
|
export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.js';
|
|
5
5
|
export { AboutDialog, Drawer, DrawerControls, DrawerHeader, DrawerWidget, DrawerWidgetSelector, ModalWidget, ModalWidgetAppBar, PreferencesDialog, WidgetBody, WidgetHeading, drawerGridTemplateColumns, } from './ui/index.js';
|
|
@@ -22,7 +22,7 @@ export { resolveAssemblies, resolveAssembly } from './resolveAssemblies.js';
|
|
|
22
22
|
export { destroyViewState } from './destroyViewState.js';
|
|
23
23
|
// the two halves of "React owns this engine", both of which have a StrictMode
|
|
24
24
|
// trap in them that the obvious spelling walks straight into
|
|
25
|
-
export { useCreateOnce, useDestroyOnUnmount } from './useEngineLifecycle.js';
|
|
25
|
+
export { useAsyncEngineLifecycle, useCreateOnce, useCreateOnceAsync, useDestroyOnUnmount, } from './useEngineLifecycle.js';
|
|
26
26
|
// the JS -> host direction of an embedded controller: where each view is
|
|
27
27
|
// looking, what got selected, and the layout as plain JSON
|
|
28
28
|
export { getSessionSnapshot, observeSession } from './observeSession.js';
|
package/esm/loadPlugins.d.ts
CHANGED
|
@@ -30,4 +30,4 @@ export interface LoadPluginsArgs {
|
|
|
30
30
|
export declare function loadRuntimePlugins(pluginDefinitions: PluginDefinition[], { dropVendored, jbrowseVersion, ...args }: LoadPluginsArgs & {
|
|
31
31
|
dropVendored: boolean;
|
|
32
32
|
jbrowseVersion: string;
|
|
33
|
-
}): Promise<import("@jbrowse/core/PluginLoader").PluginRecord[]>;
|
|
33
|
+
}): Promise<never[] | import("@jbrowse/core/PluginLoader").PluginRecord[]>;
|
package/esm/loadPlugins.js
CHANGED
|
@@ -28,11 +28,14 @@ export async function loadRuntimePlugins(pluginDefinitions, { dropVendored, jbro
|
|
|
28
28
|
if (failures[0]) {
|
|
29
29
|
throw failures[0].error;
|
|
30
30
|
}
|
|
31
|
-
const
|
|
32
|
-
pluginLoader.installGlobalReExports(window);
|
|
31
|
+
const toLoad = dropVendored ? dropVendoredPlugins(definitions) : definitions;
|
|
33
32
|
// the default matters: `new URL('umd_plugin.js', undefined)` throws, so a
|
|
34
33
|
// relative plugin url used to fail outright instead of resolving against the
|
|
35
34
|
// page the way every other relative url does
|
|
36
35
|
const base = args.baseUri ?? args.baseUrl;
|
|
37
|
-
return
|
|
36
|
+
return toLoad.length
|
|
37
|
+
? new PluginLoader(toLoad, args)
|
|
38
|
+
.installGlobalReExports(window)
|
|
39
|
+
.load(base ?? window.location.href)
|
|
40
|
+
: [];
|
|
38
41
|
}
|
|
@@ -12,8 +12,11 @@ const HELD_NODES_KEY = 'heldForMissingPlugins';
|
|
|
12
12
|
// widen the plugin ABI for nothing.
|
|
13
13
|
function buildableElements(pluginManager, group) {
|
|
14
14
|
return pluginManager.getElementTypesInGroup(group).filter(t => {
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const element = t;
|
|
16
|
+
if (element.isStateModelLoaded === false) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return isType(element.stateModel) && isModelType(element.stateModel);
|
|
17
20
|
});
|
|
18
21
|
}
|
|
19
22
|
// optional/stripDefault/late/snapshotProcessor all report the type they wrap,
|
package/esm/rpcWorker.js
CHANGED
|
@@ -3,7 +3,6 @@ import PluginManager from '@jbrowse/core/PluginManager';
|
|
|
3
3
|
import { setNumberGrouping, throttleStatusEmits } from '@jbrowse/core/util';
|
|
4
4
|
import { RpcServer, serializeError } from '@jbrowse/core/util/librpc';
|
|
5
5
|
import { setStackTraceLimit } from '@jbrowse/core/util/setStackTraceLimit';
|
|
6
|
-
import { enableStaticRendering } from 'mobx-react';
|
|
7
6
|
// waits for a message from the main thread containing our configuration, which
|
|
8
7
|
// must be sent on boot
|
|
9
8
|
function receiveConfiguration() {
|
|
@@ -25,13 +24,16 @@ async function getPluginManager(corePlugins, opts) {
|
|
|
25
24
|
// tooltip against the full feature here rather than shipping it back — so it
|
|
26
25
|
// needs the main thread's display preference before any RPC method runs
|
|
27
26
|
setNumberGrouping(config.numberGrouping);
|
|
28
|
-
const pluginLoader = new PluginLoader(config.plugins, opts).installGlobalReExports(self);
|
|
29
27
|
// Keep each runtime plugin's `definition` on its load record (mirroring the
|
|
30
28
|
// main thread's createPluginManager) so PluginManager populates
|
|
31
29
|
// runtimePluginDefinitions. A plugin that resolves a sibling asset from its
|
|
32
30
|
// own bundle url in the worker — e.g. GraphGenomeView's Bandage layout
|
|
33
31
|
// engine — needs its definition here, not just its instance.
|
|
34
|
-
const runtimePlugins =
|
|
32
|
+
const runtimePlugins = config.plugins.length
|
|
33
|
+
? await new PluginLoader(config.plugins, opts)
|
|
34
|
+
.installGlobalReExports(self)
|
|
35
|
+
.load(config.windowHref)
|
|
36
|
+
: [];
|
|
35
37
|
return new PluginManager([
|
|
36
38
|
...corePlugins.map(P => ({ plugin: new P() })),
|
|
37
39
|
...runtimePlugins.map(({ plugin: P, definition }) => ({
|
|
@@ -92,7 +94,6 @@ export async function initializeWorker(corePlugins, opts) {
|
|
|
92
94
|
// copies meant a fifth product would silently ship without it — the plugins
|
|
93
95
|
// that could trip it are not loaded until getPluginManager below, so this is
|
|
94
96
|
// still ahead of anything that observes.
|
|
95
|
-
enableStaticRendering(true);
|
|
96
97
|
// Add global error handler to catch uncaught errors in the worker
|
|
97
98
|
self.addEventListener('error', event => {
|
|
98
99
|
console.error('[Worker uncaught error]', event.error || event.message);
|
|
@@ -9,6 +9,7 @@ import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
|
9
9
|
import { isStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
10
10
|
import { observer } from 'mobx-react';
|
|
11
11
|
import AssemblyInfoPanel from './AssemblyInfoPanel.js';
|
|
12
|
+
import DescriptionPanel from './DescriptionPanel.js';
|
|
12
13
|
import FileInfoPanel from './FileInfoPanel.js';
|
|
13
14
|
import HeaderButtons from './HeaderButtons.js';
|
|
14
15
|
import RefNameInfoDialog from './RefNameInfoDialog.js';
|
|
@@ -49,7 +50,7 @@ const AboutDialogContents = observer(function AboutDialogContents({ config, sess
|
|
|
49
50
|
session,
|
|
50
51
|
pluginManager,
|
|
51
52
|
});
|
|
52
|
-
return (_jsxs("div", { className: classes.content, children: [_jsxs(BaseCard, { title: "Configuration", children: [_jsx(HeaderButtons, { conf: conf, hideUris: hideUris, fromDisplayTypeDefaults: fromDisplayTypeDefaults, setShowRefNames: setShowRefNames }), _jsx(Attributes, { attributes: shown, omit: [...hideFields, 'metadata'], hideUris: hideUris })] }), shown.metadata ? (_jsx(BaseCard, { title: "Metadata", children: _jsx(Attributes, { attributes: shown.metadata, hideUris: hideUris }) })) : null, _jsx(AssemblyInfoPanel, { config: config, session: session, hideUris: hideUris }), _jsx(PluggableComponents, { pluginManager: pluginManager,
|
|
53
|
+
return (_jsxs("div", { className: classes.content, children: [_jsxs(BaseCard, { title: "Configuration", children: [_jsx(HeaderButtons, { conf: conf, hideUris: hideUris, fromDisplayTypeDefaults: fromDisplayTypeDefaults, setShowRefNames: setShowRefNames }), _jsx(Attributes, { attributes: shown, omit: [...hideFields, 'metadata'], hideUris: hideUris })] }), shown.metadata ? (_jsx(BaseCard, { title: "Metadata", children: _jsx(Attributes, { attributes: shown.metadata, hideUris: hideUris }) })) : null, _jsx(DescriptionPanel, { config: config, session: session }), _jsx(AssemblyInfoPanel, { config: config, session: session, hideUris: hideUris }), _jsx(PluggableComponents, { pluginManager: pluginManager,
|
|
53
54
|
/** #extensionPoint Core-extraAboutPanel | sync | Add extra panels to a track's About dialog */
|
|
54
55
|
name: "Core-extraAboutPanel", props: { session, config } }), hideUris ? null : _jsx(FileInfoPanel, { config: config, session: session }), showRefNames ? (_jsx(RefNameInfoDialog, { session: session, config: config, onClose: () => {
|
|
55
56
|
setShowRefNames(false);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import BaseCard from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard';
|
|
3
|
+
import { ErrorBanner, LoadingEllipses, SanitizedHTML } from '@jbrowse/core/ui';
|
|
4
|
+
import { openLocation } from '@jbrowse/core/util/io';
|
|
5
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
6
|
+
import { useFetch } from '@jbrowse/core/util/useFetch';
|
|
7
|
+
import { Link, Typography } from '@mui/material';
|
|
8
|
+
import { observer } from 'mobx-react';
|
|
9
|
+
import { getDescriptionHtmlUrl, rebaseDescriptionHtml, } from './descriptionHtml.js';
|
|
10
|
+
const useStyles = makeStyles()(theme => ({
|
|
11
|
+
provenance: {
|
|
12
|
+
marginBottom: theme.spacing(1),
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
const DescriptionCard = observer(function DescriptionCard({ url, }) {
|
|
16
|
+
const { classes } = useStyles();
|
|
17
|
+
const { data: html, error, isLoading, } = useFetch(['trackDescriptionHtml', url], async (_name, url) => rebaseDescriptionHtml(await openLocation({ uri: url, locationType: 'UriLocation' }).readFile('utf8'), url));
|
|
18
|
+
return (_jsxs(BaseCard, { title: "Description", children: [_jsxs(Typography, { variant: "caption", color: "textSecondary", component: "div", className: classes.provenance, children: ["From the track hub, written for the UCSC Genome Browser \u2014 some display options it describes differ in JBrowse.", ' ', _jsx(Link, { href: url, target: "_blank", rel: "noopener noreferrer", children: "View original" })] }), error ? (_jsx(ErrorBanner, { error: error })) : isLoading || html === undefined ? (_jsx(LoadingEllipses, { message: "Loading description" })) : (_jsx(SanitizedHTML, { html: html }))] }));
|
|
19
|
+
});
|
|
20
|
+
const DescriptionPanel = observer(function DescriptionPanel({ config, }) {
|
|
21
|
+
const url = getDescriptionHtmlUrl(config);
|
|
22
|
+
return url ? _jsx(DescriptionCard, { url: url }) : null;
|
|
23
|
+
});
|
|
24
|
+
export default DescriptionPanel;
|