@jbrowse/product-core 5.0.0-beta.1 → 5.0.0-beta.2
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 +1 -0
- 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 +2 -0
- package/esm/Session/BaseSession.js +11 -1
- package/esm/Session/MultipleViews.d.ts +17 -2
- package/esm/Session/MultipleViews.js +44 -3
- package/esm/Session/Preferences.d.ts +2 -0
- package/esm/Session/Preferences.js +5 -0
- package/esm/Session/SessionTracks.d.ts +2 -0
- package/esm/Session/SessionTracks.js +8 -0
- package/esm/Session/TrackMenu.js +2 -2
- package/esm/Session/Tracks.d.ts +2 -0
- package/esm/Session/Tracks.js +24 -1
- 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 +1 -1
- package/esm/index.js +1 -1
- package/esm/loadPlugins.d.ts +1 -1
- package/esm/loadPlugins.js +6 -3
- package/esm/rpcWorker.js +5 -4
- package/esm/ui/DisplayDefaultsSection.js +1 -1
- package/esm/useEngineLifecycle.d.ts +7 -1
- package/esm/useEngineLifecycle.js +24 -2
- package/package.json +5 -4
|
@@ -87,6 +87,7 @@ export function BaseRootModelFactory({ pluginManager, jbrowseModelType, sessionM
|
|
|
87
87
|
const unbuildable = [];
|
|
88
88
|
let migrated = sessionSnapshot;
|
|
89
89
|
if (sessionSnapshot && typeof sessionSnapshot === 'object') {
|
|
90
|
+
pluginManager.assertSessionTypesLoaded(sessionSnapshot);
|
|
90
91
|
// before the cast, because a type this build has no plugin for is a
|
|
91
92
|
// union failure the try below never gets to see
|
|
92
93
|
const pruned = pruneUnbuildableNodes(migrateSessionSnapshot(sessionSnapshot), pluginManager, sessionModelType);
|
|
@@ -50,4 +50,19 @@ export declare function PreferencesConfigSchemaFactory(): import("@jbrowse/core/
|
|
|
50
50
|
readonly type: "boolean";
|
|
51
51
|
readonly defaultValue: false;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* #slot configuration.preferences.developerMode
|
|
55
|
+
* when true, the ordering-contract checks a display or plugin can break
|
|
56
|
+
* report themselves in the app rather than only in a development build.
|
|
57
|
+
* Off by default and deliberately not in the Preferences dialog: the
|
|
58
|
+
* messages are about code, and a reader who cannot change the code cannot
|
|
59
|
+
* act on one. Turn it on for a site that is running a plugin under
|
|
60
|
+
* development; a plugin author needs nothing here, since a plugin served
|
|
61
|
+
* from localhost arms the same channel on its own, as does
|
|
62
|
+
* `localStorage.jbrowseDeveloperMode`. Takes effect on reload.
|
|
63
|
+
*/
|
|
64
|
+
readonly developerMode: {
|
|
65
|
+
readonly type: "boolean";
|
|
66
|
+
readonly defaultValue: false;
|
|
67
|
+
};
|
|
53
68
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
@@ -57,5 +57,20 @@ export function PreferencesConfigSchemaFactory() {
|
|
|
57
57
|
type: 'boolean',
|
|
58
58
|
defaultValue: false,
|
|
59
59
|
},
|
|
60
|
+
/**
|
|
61
|
+
* #slot configuration.preferences.developerMode
|
|
62
|
+
* when true, the ordering-contract checks a display or plugin can break
|
|
63
|
+
* report themselves in the app rather than only in a development build.
|
|
64
|
+
* Off by default and deliberately not in the Preferences dialog: the
|
|
65
|
+
* messages are about code, and a reader who cannot change the code cannot
|
|
66
|
+
* act on one. Turn it on for a site that is running a plugin under
|
|
67
|
+
* development; a plugin author needs nothing here, since a plugin served
|
|
68
|
+
* from localhost arms the same channel on its own, as does
|
|
69
|
+
* `localStorage.jbrowseDeveloperMode`. Takes effect on reload.
|
|
70
|
+
*/
|
|
71
|
+
developerMode: {
|
|
72
|
+
type: 'boolean',
|
|
73
|
+
defaultValue: false,
|
|
74
|
+
},
|
|
60
75
|
});
|
|
61
76
|
}
|
|
@@ -66,6 +66,10 @@ declare function rootConfigurationSchema(): import("@jbrowse/core/configuration/
|
|
|
66
66
|
readonly type: "boolean";
|
|
67
67
|
readonly defaultValue: false;
|
|
68
68
|
};
|
|
69
|
+
readonly developerMode: {
|
|
70
|
+
readonly type: "boolean";
|
|
71
|
+
readonly defaultValue: false;
|
|
72
|
+
};
|
|
69
73
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
70
74
|
readonly formatDetails: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
71
75
|
readonly feature: {
|
|
@@ -178,6 +182,10 @@ export declare function createConfigModel(pluginManager: PluginManager, assembly
|
|
|
178
182
|
readonly type: "boolean";
|
|
179
183
|
readonly defaultValue: false;
|
|
180
184
|
};
|
|
185
|
+
readonly developerMode: {
|
|
186
|
+
readonly type: "boolean";
|
|
187
|
+
readonly defaultValue: false;
|
|
188
|
+
};
|
|
181
189
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
182
190
|
readonly formatDetails: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
183
191
|
readonly feature: {
|
|
@@ -322,6 +322,8 @@ export declare function BaseSessionModel<ROOT_MODEL_TYPE extends BaseRootModelTy
|
|
|
322
322
|
pushSnackbarMessage(message: string, level?: import("@jbrowse/core/util").NotificationLevel, actions?: import("@jbrowse/core/util").SnackAction[]): void;
|
|
323
323
|
popSnackbarMessage(): import("@jbrowse/core/ui/SnackbarModel").SnackbarMessage | undefined;
|
|
324
324
|
removeSnackbarMessage(message: string): void;
|
|
325
|
+
} & {
|
|
326
|
+
afterAttach(): void;
|
|
325
327
|
}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
|
|
326
328
|
/** Session mixin MST type for the most basic session */
|
|
327
329
|
export type BaseSessionType = ReturnType<typeof BaseSessionModel>;
|
|
@@ -6,6 +6,7 @@ import { isFeature, unwrapFeature } from '@jbrowse/core/util/simpleFeature';
|
|
|
6
6
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
7
7
|
import { getParent, isStateTreeNode, types } from '@jbrowse/mobx-state-tree';
|
|
8
8
|
import { observable } from 'mobx';
|
|
9
|
+
import { applyDeveloperMode } from './developerMode.js';
|
|
9
10
|
function isAnimationMode(val) {
|
|
10
11
|
return val === 'system' || val === 'enabled' || val === 'disabled';
|
|
11
12
|
}
|
|
@@ -490,7 +491,16 @@ export function BaseSessionModel(_pluginManager) {
|
|
|
490
491
|
self.queueOfDialogs = [...self.queueOfDialogs, [component, props]];
|
|
491
492
|
},
|
|
492
493
|
}));
|
|
493
|
-
return types.compose(baseModel, SnackbarModel())
|
|
494
|
+
return types.compose(baseModel, SnackbarModel()).actions(self => ({
|
|
495
|
+
// after the compose rather than in the afterAttach above, because the
|
|
496
|
+
// notices land in the snackbar this line is the first place to hold both
|
|
497
|
+
// halves of. Web and desktop reach it a second time once the user's stored
|
|
498
|
+
// overrides are loaded (`PreferencesSessionMixin`), and our MST fork
|
|
499
|
+
// auto-chains both hooks.
|
|
500
|
+
afterAttach() {
|
|
501
|
+
applyDeveloperMode(self);
|
|
502
|
+
},
|
|
503
|
+
}));
|
|
494
504
|
}
|
|
495
505
|
/** Type guard for BaseSession */
|
|
496
506
|
export function isBaseSession(thing) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
+
import type { ViewSnapshotInput } from '@jbrowse/core/PluginManager';
|
|
2
3
|
import type { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes';
|
|
3
4
|
import type { IAnyStateTreeNode, Instance } from '@jbrowse/mobx-state-tree';
|
|
4
5
|
/**
|
|
@@ -1678,6 +1679,8 @@ export declare function MultipleViewsSessionMixin(pluginManager: PluginManager):
|
|
|
1678
1679
|
pushSnackbarMessage(message: string, level?: import("@jbrowse/core/util").NotificationLevel, actions?: import("@jbrowse/core/util").SnackAction[]): void;
|
|
1679
1680
|
popSnackbarMessage(): import("@jbrowse/core/ui/SnackbarModel").SnackbarMessage | undefined;
|
|
1680
1681
|
removeSnackbarMessage(message: string): void;
|
|
1682
|
+
} & {
|
|
1683
|
+
afterAttach(): void;
|
|
1681
1684
|
} & {
|
|
1682
1685
|
readonly visibleWidget: import("@jbrowse/core/util").Widget | undefined;
|
|
1683
1686
|
} & {
|
|
@@ -1752,8 +1755,10 @@ export declare function MultipleViewsSessionMixin(pluginManager: PluginManager):
|
|
|
1752
1755
|
orderViews(ids: string[]): void;
|
|
1753
1756
|
/**
|
|
1754
1757
|
* #action
|
|
1758
|
+
* Requires the view type's state model to be loaded; for a lazily
|
|
1759
|
+
* registered type use `launchView`.
|
|
1755
1760
|
*/
|
|
1756
|
-
addView(typeName:
|
|
1761
|
+
addView<N extends string>(typeName: N, initialState?: NoInfer<ViewSnapshotInput<N>>): any;
|
|
1757
1762
|
/**
|
|
1758
1763
|
* #action
|
|
1759
1764
|
* swap `view` for a new view of `typeName`, in the slot it occupied.
|
|
@@ -1783,7 +1788,7 @@ export declare function MultipleViewsSessionMixin(pluginManager: PluginManager):
|
|
|
1783
1788
|
* its panel, and the launch that offers a replace is a click on that
|
|
1784
1789
|
* view's own menu.
|
|
1785
1790
|
*/
|
|
1786
|
-
replaceView(view: IBaseViewModel, typeName:
|
|
1791
|
+
replaceView<N extends string>(view: IBaseViewModel, typeName: N, initialState?: NoInfer<ViewSnapshotInput<N>>): any;
|
|
1787
1792
|
/**
|
|
1788
1793
|
* #action
|
|
1789
1794
|
*/
|
|
@@ -1845,6 +1850,16 @@ export declare function MultipleViewsSessionMixin(pluginManager: PluginManager):
|
|
|
1845
1850
|
*/
|
|
1846
1851
|
resetUseWorkspaces(): void;
|
|
1847
1852
|
afterAttach(): void;
|
|
1853
|
+
} & {
|
|
1854
|
+
/**
|
|
1855
|
+
* #action
|
|
1856
|
+
* `addView` for view types whose state model may be lazily loaded. The
|
|
1857
|
+
* view is not in `session.views` until the promise resolves.
|
|
1858
|
+
* `getViewType` first so an unknown type name fails by that name; the
|
|
1859
|
+
* snapshot walk skips unregistered names and covers the displays of the
|
|
1860
|
+
* tracks a launch opens with.
|
|
1861
|
+
*/
|
|
1862
|
+
launchView<N extends string>(typeName: N, initialState?: NoInfer<ViewSnapshotInput<N>>): Promise<any>;
|
|
1848
1863
|
}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree").ModelSnapshotType<Omit<Omit<Omit<{
|
|
1849
1864
|
id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
1850
1865
|
name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
|
|
@@ -54,10 +54,24 @@ function dropRefsInto(holder, view) {
|
|
|
54
54
|
}
|
|
55
55
|
return found;
|
|
56
56
|
}
|
|
57
|
+
// A session snapshot's `views`, or undefined for anything that is not one
|
|
58
|
+
function viewsOfSessionSnapshot(snapshot) {
|
|
59
|
+
const views = typeof snapshot === 'object' && snapshot !== null && 'views' in snapshot
|
|
60
|
+
? snapshot.views
|
|
61
|
+
: undefined;
|
|
62
|
+
return Array.isArray(views) ? views : undefined;
|
|
63
|
+
}
|
|
57
64
|
/**
|
|
58
65
|
* #stateModel MultipleViewsSessionMixin
|
|
59
66
|
*/
|
|
60
67
|
export function MultipleViewsSessionMixin(pluginManager) {
|
|
68
|
+
// an unknown type name passes through to fail at the union as it always has
|
|
69
|
+
function assertViewStateModelLoaded(typeName) {
|
|
70
|
+
if (pluginManager.getElementTypeRecord('view').has(typeName) &&
|
|
71
|
+
!pluginManager.getViewType(typeName).isStateModelLoaded) {
|
|
72
|
+
throw new Error(`state model for view type ${typeName} is not loaded yet — use launchView(), or await getViewType('${typeName}').loadStateModel() first`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
61
75
|
return types
|
|
62
76
|
.compose(BaseSessionModel(pluginManager), DrawerWidgetSessionMixin(pluginManager), types.model({
|
|
63
77
|
/**
|
|
@@ -208,8 +222,11 @@ export function MultipleViewsSessionMixin(pluginManager) {
|
|
|
208
222
|
},
|
|
209
223
|
/**
|
|
210
224
|
* #action
|
|
225
|
+
* Requires the view type's state model to be loaded; for a lazily
|
|
226
|
+
* registered type use `launchView`.
|
|
211
227
|
*/
|
|
212
|
-
addView(typeName, initialState
|
|
228
|
+
addView(typeName, initialState) {
|
|
229
|
+
assertViewStateModelLoaded(typeName);
|
|
213
230
|
const length = self.views.push({
|
|
214
231
|
...initialState,
|
|
215
232
|
type: typeName,
|
|
@@ -245,7 +262,8 @@ export function MultipleViewsSessionMixin(pluginManager) {
|
|
|
245
262
|
* its panel, and the launch that offers a replace is a click on that
|
|
246
263
|
* view's own menu.
|
|
247
264
|
*/
|
|
248
|
-
replaceView(view, typeName, initialState
|
|
265
|
+
replaceView(view, typeName, initialState) {
|
|
266
|
+
assertViewStateModelLoaded(typeName);
|
|
249
267
|
// read before the removal, which is what makes both stale
|
|
250
268
|
const idx = self.views.indexOf(view);
|
|
251
269
|
// `idx` first, so a view already gone from the session short-circuits
|
|
@@ -305,7 +323,13 @@ export function MultipleViewsSessionMixin(pluginManager) {
|
|
|
305
323
|
* lands, which is why ADR-069's rule stops there.
|
|
306
324
|
*/
|
|
307
325
|
takeOutViewsMissingFrom(snapshot) {
|
|
308
|
-
const incoming = snapshot
|
|
326
|
+
const incoming = viewsOfSessionSnapshot(snapshot);
|
|
327
|
+
// an argument that is not a session snapshot used to read as "keep
|
|
328
|
+
// none" and close every view: an agent introspecting the actions
|
|
329
|
+
// called this with a tab id and a keep-list and lost its session
|
|
330
|
+
if (!incoming) {
|
|
331
|
+
throw new Error('takeOutViewsMissingFrom takes a session snapshot (it is what undo and redo call to close the views a snapshot no longer has); to close one view, session.removeView(view)');
|
|
332
|
+
}
|
|
309
333
|
const kept = new Map(incoming.map(v => [v.id, v.type]));
|
|
310
334
|
// a copy, because `takeOut` splices the array it is iterating
|
|
311
335
|
for (const view of [...self.views]) {
|
|
@@ -359,6 +383,23 @@ export function MultipleViewsSessionMixin(pluginManager) {
|
|
|
359
383
|
},
|
|
360
384
|
};
|
|
361
385
|
})
|
|
386
|
+
.actions(self => ({
|
|
387
|
+
/**
|
|
388
|
+
* #action
|
|
389
|
+
* `addView` for view types whose state model may be lazily loaded. The
|
|
390
|
+
* view is not in `session.views` until the promise resolves.
|
|
391
|
+
* `getViewType` first so an unknown type name fails by that name; the
|
|
392
|
+
* snapshot walk skips unregistered names and covers the displays of the
|
|
393
|
+
* tracks a launch opens with.
|
|
394
|
+
*/
|
|
395
|
+
async launchView(typeName, initialState) {
|
|
396
|
+
await pluginManager.getViewType(typeName).loadStateModel();
|
|
397
|
+
await pluginManager.preloadSessionTypes({
|
|
398
|
+
views: [{ type: typeName, ...initialState }],
|
|
399
|
+
});
|
|
400
|
+
return self.addView(typeName, initialState);
|
|
401
|
+
},
|
|
402
|
+
}))
|
|
362
403
|
.postProcessSnapshot(snap => {
|
|
363
404
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
364
405
|
if (!snap) {
|
|
@@ -1662,4 +1662,6 @@ export declare function PreferencesSessionMixin(pluginManager: PluginManager): i
|
|
|
1662
1662
|
removeSnackbarMessage(message: string): void;
|
|
1663
1663
|
} & {
|
|
1664
1664
|
afterAttach(): void;
|
|
1665
|
+
} & {
|
|
1666
|
+
afterAttach(): void;
|
|
1665
1667
|
}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
|
|
@@ -3,6 +3,7 @@ import { freezeDeep } from '@jbrowse/core/util/freezeDeep';
|
|
|
3
3
|
import { addDisposer, types } from '@jbrowse/mobx-state-tree';
|
|
4
4
|
import { autorun } from 'mobx';
|
|
5
5
|
import { BaseSessionModel } from './BaseSession.js';
|
|
6
|
+
import { applyDeveloperMode } from './developerMode.js';
|
|
6
7
|
const PREFS_KEY = 'jbrowsePreferences';
|
|
7
8
|
function loadStoredPreferences() {
|
|
8
9
|
const stored = localStorageGetItem(PREFS_KEY);
|
|
@@ -43,6 +44,10 @@ export function PreferencesSessionMixin(pluginManager) {
|
|
|
43
44
|
// main-thread update would leave the app formatted two ways at once.
|
|
44
45
|
// The dialog says a reload is needed; this is where the reload lands.
|
|
45
46
|
setNumberGrouping(self.numberGrouping);
|
|
47
|
+
// and the same second pass for the developer-notice channel, whose
|
|
48
|
+
// admin default BaseSession already read: a user's stored override is
|
|
49
|
+
// only loaded a line above this
|
|
50
|
+
applyDeveloperMode(self);
|
|
46
51
|
addDisposer(self, autorun(function persistPreferences() {
|
|
47
52
|
// observable.map doesn't JSON-serialize to an object on its own;
|
|
48
53
|
// fromEntries also reads every key so the autorun re-persists on
|
|
@@ -1689,6 +1689,8 @@ export declare function SessionTracksManagerSessionMixin(pluginManager: PluginMa
|
|
|
1689
1689
|
pushSnackbarMessage(message: string, level?: import("@jbrowse/core/util").NotificationLevel, actions?: import("@jbrowse/core/util").SnackAction[]): void;
|
|
1690
1690
|
popSnackbarMessage(): import("@jbrowse/core/ui/SnackbarModel").SnackbarMessage | undefined;
|
|
1691
1691
|
removeSnackbarMessage(message: string): void;
|
|
1692
|
+
} & {
|
|
1693
|
+
afterAttach(): void;
|
|
1692
1694
|
} & {
|
|
1693
1695
|
getReferringMultiple(trackIds: string[]): Map<string, import("./ReferenceManagement.ts").ReferringNode[]>;
|
|
1694
1696
|
} & {
|
|
@@ -4,6 +4,7 @@ import { expandLooseTrackConfig } from '@jbrowse/core/util/tracks';
|
|
|
4
4
|
import { applySnapshot, getSnapshot, isStateTreeNode, types, } from '@jbrowse/mobx-state-tree';
|
|
5
5
|
import { compareStructural } from 'mobx';
|
|
6
6
|
import { TracksManagerSessionMixin } from './Tracks.js';
|
|
7
|
+
import { assertNotReaddedDifferently } from './readdedTrackConf.js';
|
|
7
8
|
import { assertTrackConfOutlivesItsAssemblies } from './temporaryAssemblyTracks.js';
|
|
8
9
|
// jbrowse.tracks holds frozen plain objects (app-core, web/desktop) or MST
|
|
9
10
|
// config nodes (product-core, embedded react views); the delta math reads both
|
|
@@ -342,6 +343,13 @@ export function SessionTracksManagerSessionMixin(pluginManager) {
|
|
|
342
343
|
// and dropping its trackConfigDeltas override semantics.
|
|
343
344
|
const existing = self.getTrackById(trackId);
|
|
344
345
|
if (existing) {
|
|
346
|
+
if (self.sessionTracks.includes(existing)) {
|
|
347
|
+
assertNotReaddedDifferently(pluginManager, existing, {
|
|
348
|
+
...trackConf,
|
|
349
|
+
trackId,
|
|
350
|
+
type,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
345
353
|
return existing;
|
|
346
354
|
}
|
|
347
355
|
// sessionTracks is a typed MST array (unlike the frozen
|
package/esm/Session/TrackMenu.js
CHANGED
|
@@ -44,7 +44,7 @@ export function trackActionItems({ session, config, view, canEdit, isSessionOver
|
|
|
44
44
|
// visitor is served, one per click since the copy's stamped trackId defeats
|
|
45
45
|
// the dedupe, and a non-admin's lands as the dead `sessionTracks` entry
|
|
46
46
|
// ADR-084 removed the sweep for. Neither is worth offering, so the item says
|
|
47
|
-
// so instead of producing garbage; the
|
|
47
|
+
// so instead of producing garbage; the contract check on the adders
|
|
48
48
|
// is what found this.
|
|
49
49
|
//
|
|
50
50
|
// Greying a row tells nobody anything, and this reason is one nobody guesses:
|
|
@@ -86,7 +86,7 @@ export function trackActionItems({ session, config, view, canEdit, isSessionOver
|
|
|
86
86
|
onClick: () => {
|
|
87
87
|
const snap = makeCopy();
|
|
88
88
|
if (session.publishTrackConf(snap)) {
|
|
89
|
-
view.
|
|
89
|
+
void view.launchTrack(snap.trackId);
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
},
|
package/esm/Session/Tracks.d.ts
CHANGED
|
@@ -1657,6 +1657,8 @@ export declare function TracksManagerSessionMixin(pluginManager: PluginManager):
|
|
|
1657
1657
|
pushSnackbarMessage(message: string, level?: import("@jbrowse/core/util").NotificationLevel, actions?: import("@jbrowse/core/util").SnackAction[]): void;
|
|
1658
1658
|
popSnackbarMessage(): import("@jbrowse/core/ui/SnackbarModel").SnackbarMessage | undefined;
|
|
1659
1659
|
removeSnackbarMessage(message: string): void;
|
|
1660
|
+
} & {
|
|
1661
|
+
afterAttach(): void;
|
|
1660
1662
|
} & {
|
|
1661
1663
|
getReferringMultiple(trackIds: string[]): Map<string, import("./ReferenceManagement.ts").ReferringNode[]>;
|
|
1662
1664
|
} & {
|
package/esm/Session/Tracks.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { expandLooseTrackConfig } from '@jbrowse/core/util/tracks';
|
|
1
2
|
import { types } from '@jbrowse/mobx-state-tree';
|
|
2
3
|
import { 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
|
|
@@ -24,6 +26,14 @@ export function TracksManagerSessionMixin(pluginManager) {
|
|
|
24
26
|
// here; hydration to MST nodes happens lazily in TrackConfigurationReference
|
|
25
27
|
// on first access. Cached, so the N per-id lookups below share one rebuild
|
|
26
28
|
// per change rather than each re-scanning.
|
|
29
|
+
//
|
|
30
|
+
// `keepAlive` is what makes that last sentence true, and it is load
|
|
31
|
+
// bearing: an unobserved computed re-derives on EVERY read, and this one
|
|
32
|
+
// walks every track, assembly and connection the session can resolve. A
|
|
33
|
+
// reader inside a reaction or an MST action was already fine — MobX
|
|
34
|
+
// caches within a batch, which is why the resolvers and `showTrackGeneric`
|
|
35
|
+
// never showed it. Ranking search hits is the caller that is neither, and
|
|
36
|
+
// it cost 33ms per search on a 2000-track config against 0.07ms held.
|
|
27
37
|
const tracksByIdRecord = computed(() => {
|
|
28
38
|
const temporaryAssemblies = 'temporaryAssemblies' in self
|
|
29
39
|
? self.temporaryAssemblies
|
|
@@ -49,7 +59,7 @@ export function TracksManagerSessionMixin(pluginManager) {
|
|
|
49
59
|
e.config,
|
|
50
60
|
]),
|
|
51
61
|
]);
|
|
52
|
-
});
|
|
62
|
+
}, { keepAlive: true });
|
|
53
63
|
// Per-id computed cache backing getTrackById. Resolving one track's config
|
|
54
64
|
// subscribes only to that id's computed, so editing track A leaves track
|
|
55
65
|
// B's observers untouched: an unedited id's entry keeps its object identity
|
|
@@ -108,6 +118,19 @@ export function TracksManagerSessionMixin(pluginManager) {
|
|
|
108
118
|
*/
|
|
109
119
|
addSessionTrackConf(trackConf) {
|
|
110
120
|
assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
|
|
121
|
+
// the config's own adder appends without looking, so a re-add under a
|
|
122
|
+
// known id used to leave two entries with the first one winning
|
|
123
|
+
const expanded = expandLooseTrackConfig(trackConf, pluginManager);
|
|
124
|
+
const { trackId, type } = expanded;
|
|
125
|
+
const existing = self.getTrackById(trackId);
|
|
126
|
+
if (existing) {
|
|
127
|
+
assertNotReaddedDifferently(pluginManager, existing, {
|
|
128
|
+
...expanded,
|
|
129
|
+
trackId,
|
|
130
|
+
type,
|
|
131
|
+
});
|
|
132
|
+
return existing;
|
|
133
|
+
}
|
|
111
134
|
return self.jbrowse.addTrackConf(trackConf);
|
|
112
135
|
},
|
|
113
136
|
/**
|
|
@@ -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
|
@@ -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
|
@@ -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
|
}
|
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);
|
|
@@ -60,6 +60,6 @@ const DisplayDefaultsSection = observer(function DisplayDefaultsSection({ sessio
|
|
|
60
60
|
}));
|
|
61
61
|
return changes.length ? (_jsxs(_Fragment, { children: [_jsx(Typography, { children: "These apply to every track of a display type that hasn't set its own value. Clear one to put those tracks back on the setting's default." }), _jsx(SettingsChangesTable, { changes: changes, onResetRow: change => {
|
|
62
62
|
session.setDisplayTypeDefault(change.path[0], change.path[1], undefined);
|
|
63
|
-
} })] })) : (_jsx(Typography, { children: "None set. The pin beside a setting in a track menu
|
|
63
|
+
} })] })) : (_jsx(Typography, { children: "None set. The pin beside a setting in a track menu applies that value to every open track of the same display type, and then offers to keep it as the default for the ones you open later." }));
|
|
64
64
|
});
|
|
65
65
|
export default DisplayDefaultsSection;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { EmbeddedRoot } from './destroyViewState.ts';
|
|
2
|
-
export { useCreateOnce } from '@jbrowse/core/util/hooks';
|
|
2
|
+
export { useCreateOnce, useCreateOnceAsync } from '@jbrowse/core/util/hooks';
|
|
3
|
+
/**
|
|
4
|
+
* Async `useCreateOnce` + `useDestroyOnUnmount`: undefined until the engine is
|
|
5
|
+
* ready. An engine resolving after the final unmount is destroyed on arrival
|
|
6
|
+
* instead of orphaning its worker pool and autoruns.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useAsyncEngineLifecycle<T extends EmbeddedRoot>(create: () => Promise<T>): T | undefined;
|
|
3
9
|
/**
|
|
4
10
|
* Destroy an engine when its component really unmounts — terminating its RPC
|
|
5
11
|
* worker threads and stopping its autoruns.
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import { useCreateOnceAsync, useFinalUnmount } from '@jbrowse/core/util/hooks';
|
|
2
3
|
import { destroyViewState } from './destroyViewState.js';
|
|
3
4
|
// `useCreateOnce` is core's, and is re-exported rather than wrapped: an engine
|
|
4
5
|
// is only the most expensive thing StrictMode's double-invoked state
|
|
5
6
|
// initializer can build twice, not the only one. The faceted track selector's
|
|
6
7
|
// MST model hit the same trap in a plugin, which cannot reach this package.
|
|
7
|
-
export { useCreateOnce } from '@jbrowse/core/util/hooks';
|
|
8
|
+
export { useCreateOnce, useCreateOnceAsync } from '@jbrowse/core/util/hooks';
|
|
9
|
+
/**
|
|
10
|
+
* Async `useCreateOnce` + `useDestroyOnUnmount`: undefined until the engine is
|
|
11
|
+
* ready. An engine resolving after the final unmount is destroyed on arrival
|
|
12
|
+
* instead of orphaning its worker pool and autoruns.
|
|
13
|
+
*/
|
|
14
|
+
export function useAsyncEngineLifecycle(create) {
|
|
15
|
+
const unmounted = useRef(false);
|
|
16
|
+
const engine = useCreateOnceAsync(() => create().then(built => {
|
|
17
|
+
if (unmounted.current) {
|
|
18
|
+
destroyViewState(built);
|
|
19
|
+
}
|
|
20
|
+
return built;
|
|
21
|
+
}));
|
|
22
|
+
useFinalUnmount(() => {
|
|
23
|
+
unmounted.current = true;
|
|
24
|
+
if (engine) {
|
|
25
|
+
destroyViewState(engine);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return engine;
|
|
29
|
+
}
|
|
8
30
|
/**
|
|
9
31
|
* Destroy an engine when its component really unmounts — terminating its RPC
|
|
10
32
|
* worker threads and stopping its autoruns.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/product-core",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "JBrowse 2 code shared between products but not used by plugins",
|
|
@@ -26,11 +26,12 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@jbrowse/mobx-state-tree": "^6.5.1",
|
|
29
|
-
"@mui/icons-material": "^9.
|
|
30
|
-
"@mui/material": "^9.
|
|
29
|
+
"@mui/icons-material": "^9.4.0",
|
|
30
|
+
"@mui/material": "^9.4.0",
|
|
31
31
|
"mobx": "^7.0.3",
|
|
32
32
|
"mobx-react": "^10.0.2",
|
|
33
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
33
|
+
"@jbrowse/core": "^5.0.0-beta.2",
|
|
34
|
+
"@jbrowse/render-core": "^5.0.0-beta.2"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"react": ">=19.0.0",
|