@jbrowse/product-core 4.3.0 → 5.0.0-beta.1
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.d.ts +629 -304
- package/esm/RootModel/BaseRootModel.js +138 -14
- package/esm/RootModel/HierarchicalConfig.d.ts +57 -21
- package/esm/RootModel/HierarchicalConfig.js +36 -0
- package/esm/RootModel/InternetAccounts.d.ts +18 -1
- package/esm/RootModel/InternetAccounts.js +120 -22
- package/esm/RootModel/PreferencesConfig.d.ts +53 -0
- package/esm/RootModel/PreferencesConfig.js +61 -0
- package/esm/RootModel/createConfigModel.d.ts +232 -0
- package/esm/RootModel/createConfigModel.js +56 -0
- package/esm/RootModel/index.d.ts +10 -5
- package/esm/RootModel/index.js +10 -5
- package/esm/RootModel/menuItems.d.ts +24 -0
- package/esm/RootModel/menuItems.js +143 -0
- package/esm/Session/BaseSession.d.ts +310 -11
- package/esm/Session/BaseSession.js +448 -14
- package/esm/Session/Connections.d.ts +114 -8
- package/esm/Session/Connections.js +195 -74
- package/esm/Session/DrawerWidgets.d.ts +115 -12
- package/esm/Session/DrawerWidgets.js +161 -42
- package/esm/Session/MultipleViews.d.ts +1266 -1128
- package/esm/Session/MultipleViews.js +357 -66
- package/esm/Session/Preferences.d.ts +1665 -0
- package/esm/Session/Preferences.js +54 -0
- package/esm/Session/ReferenceManagement.d.ts +28 -3
- package/esm/Session/ReferenceManagement.js +56 -41
- package/esm/Session/SessionTracks.d.ts +1275 -1138
- package/esm/Session/SessionTracks.js +532 -35
- package/esm/Session/Themes.d.ts +87 -2
- package/esm/Session/Themes.js +156 -23
- package/esm/Session/TrackMenu.d.ts +112 -0
- package/esm/Session/TrackMenu.js +200 -0
- package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
- package/esm/Session/TrackMenuSessionMixin.js +41 -0
- package/esm/Session/Tracks.d.ts +1169 -1117
- package/esm/Session/Tracks.js +160 -20
- package/esm/Session/assertSessionModel.d.ts +21 -0
- package/esm/Session/assertSessionModel.js +1 -0
- package/esm/Session/finalizeSession.d.ts +16 -0
- package/esm/Session/finalizeSession.js +29 -0
- package/esm/Session/index.d.ts +22 -10
- package/esm/Session/index.js +13 -10
- package/esm/Session/shareableSnapshot.d.ts +77 -0
- package/esm/Session/shareableSnapshot.js +196 -0
- package/esm/Session/temporaryAssemblyTracks.d.ts +23 -0
- package/esm/Session/temporaryAssemblyTracks.js +38 -0
- package/esm/assertExtends.d.ts +55 -0
- package/esm/assertExtends.js +1 -0
- package/esm/controllerTracks.d.ts +56 -0
- package/esm/controllerTracks.js +77 -0
- package/esm/destroyViewState.d.ts +21 -0
- package/esm/destroyViewState.js +29 -0
- package/esm/index.d.ts +31 -4
- package/esm/index.js +31 -4
- package/esm/loadPlugins.d.ts +33 -0
- package/esm/loadPlugins.js +38 -0
- package/esm/localFiles.d.ts +74 -0
- package/esm/localFiles.js +156 -0
- package/esm/observeSession.d.ts +56 -0
- package/esm/observeSession.js +91 -0
- package/esm/pluginInput.d.ts +32 -0
- package/esm/pluginInput.js +19 -0
- package/esm/pruneUnbuildableNodes.d.ts +109 -0
- package/esm/pruneUnbuildableNodes.js +568 -0
- package/esm/resolveAssemblies.d.ts +44 -0
- package/esm/resolveAssemblies.js +55 -0
- package/esm/rpcWorker.d.ts +26 -0
- package/esm/rpcWorker.js +96 -15
- package/esm/scheduleDetachedDestroy.d.ts +18 -0
- package/esm/scheduleDetachedDestroy.js +24 -0
- package/esm/sessionMigrations/index.d.ts +53 -0
- package/esm/sessionMigrations/index.js +432 -0
- package/esm/sessionUrl.d.ts +41 -0
- package/esm/sessionUrl.js +53 -0
- package/esm/sessionUtils.d.ts +69 -0
- package/esm/sessionUtils.js +613 -0
- package/esm/siblingCast.d.ts +14 -0
- package/esm/siblingCast.js +16 -0
- package/esm/snapshotUtils.d.ts +8 -0
- package/esm/snapshotUtils.js +12 -0
- package/esm/ui/AboutDialog.d.ts +3 -6
- package/esm/ui/AboutDialog.js +4 -4
- package/esm/ui/AboutDialogContents.d.ts +2 -6
- package/esm/ui/AboutDialogContents.js +34 -13
- package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
- package/esm/ui/AssemblyInfoPanel.js +69 -0
- package/esm/ui/DisplayDefaultsSection.d.ts +36 -0
- package/esm/ui/DisplayDefaultsSection.js +65 -0
- package/esm/ui/Drawer.d.ts +21 -0
- package/esm/ui/Drawer.js +60 -0
- package/esm/ui/DrawerControls.d.ts +5 -0
- package/esm/ui/DrawerControls.js +28 -0
- package/esm/ui/DrawerHeader.d.ts +6 -0
- package/esm/ui/DrawerHeader.js +36 -0
- package/esm/ui/DrawerWidget.d.ts +5 -0
- package/esm/ui/DrawerWidget.js +12 -0
- package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
- package/esm/ui/DrawerWidgetSelector.js +40 -0
- package/esm/ui/FileInfoPanel.d.ts +3 -6
- package/esm/ui/FileInfoPanel.js +35 -26
- package/esm/ui/HeaderButtons.d.ts +7 -1
- package/esm/ui/HeaderButtons.js +23 -18
- package/esm/ui/ModalWidget.d.ts +12 -0
- package/esm/ui/ModalWidget.js +33 -0
- package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
- package/esm/ui/ModalWidgetAppBar.js +9 -0
- package/esm/ui/PreferencesDialog.d.ts +56 -0
- package/esm/ui/PreferencesDialog.js +145 -0
- package/esm/ui/PreferencesResetDialog.d.ts +8 -0
- package/esm/ui/PreferencesResetDialog.js +19 -0
- package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
- package/esm/ui/RefNameAliasesDialog.js +65 -0
- package/esm/ui/RefNameInfoDialog.d.ts +3 -6
- package/esm/ui/RefNameInfoDialog.js +57 -63
- package/esm/ui/WidgetBody.d.ts +18 -0
- package/esm/ui/WidgetBody.js +22 -0
- package/esm/ui/WidgetHeading.d.ts +7 -0
- package/esm/ui/WidgetHeading.js +7 -0
- package/esm/ui/drawerLayout.d.ts +18 -0
- package/esm/ui/drawerLayout.js +22 -0
- package/esm/ui/index.d.ts +13 -1
- package/esm/ui/index.js +12 -1
- package/esm/ui/util.d.ts +37 -4
- package/esm/ui/util.js +35 -38
- package/esm/useEngineLifecycle.d.ts +13 -0
- package/esm/useEngineLifecycle.js +22 -0
- package/package.json +11 -15
- package/esm/RootModel/FormatAbout.d.ts +0 -12
- package/esm/RootModel/FormatAbout.js +0 -15
- package/esm/RootModel/FormatDetails.d.ts +0 -24
- package/esm/RootModel/FormatDetails.js +0 -27
- package/esm/Session/DialogQueue.d.ts +0 -17
- package/esm/Session/DialogQueue.js +0 -31
|
@@ -1,47 +1,171 @@
|
|
|
1
1
|
import TextSearchManager from '@jbrowse/core/TextSearch/TextSearchManager';
|
|
2
2
|
import assemblyManagerFactory from '@jbrowse/core/assemblyManager';
|
|
3
3
|
import RpcManager from '@jbrowse/core/rpc/RpcManager';
|
|
4
|
-
import { cast,
|
|
5
|
-
|
|
4
|
+
import { cast, detach, getType, isStateTreeNode, types, } from '@jbrowse/mobx-state-tree';
|
|
5
|
+
import { describeUnbuildableNodes, pruneUnbuildableNodes, } from '../pruneUnbuildableNodes.js';
|
|
6
|
+
import { scheduleDetachedDestroy } from '../scheduleDetachedDestroy.js';
|
|
7
|
+
import { migrateSessionSnapshot } from '../sessionMigrations/index.js';
|
|
8
|
+
import { filterSessionInPlace } from '../sessionUtils.js';
|
|
9
|
+
// `session` is a `types.maybe(sessionModelType)` where `sessionModelType` is the
|
|
10
|
+
// erased `IAnyType` (product-core can't name the concrete per-product session
|
|
11
|
+
// type without a root↔session cycle), so `self.session` degrades to `any`. Every
|
|
12
|
+
// concrete session composes BaseSessionModel + SnackbarModel, so asserting the
|
|
13
|
+
// shared `BaseSession` contract restores real checking on the `setName`/`notify`
|
|
14
|
+
// members the base root reaches for — without a hand-maintained shadow.
|
|
15
|
+
/**
|
|
16
|
+
* #stateModel BaseRootModel
|
|
17
|
+
* #category root
|
|
18
|
+
* factory function for the Base-level root model shared by all products
|
|
19
|
+
*/
|
|
20
|
+
export function BaseRootModelFactory({ pluginManager, jbrowseModelType, sessionModelType, assemblyConfigSchema, rpcManagerOptions, }) {
|
|
6
21
|
return types
|
|
7
22
|
.model('BaseRootModel', {
|
|
23
|
+
/**
|
|
24
|
+
* #property
|
|
25
|
+
* `jbrowse` is a mapping of the config.json into the in-memory state
|
|
26
|
+
* tree
|
|
27
|
+
*/
|
|
8
28
|
jbrowse: jbrowseModelType,
|
|
29
|
+
/**
|
|
30
|
+
* #property
|
|
31
|
+
* `session` encompasses the currently active state of the app, including
|
|
32
|
+
* views open, tracks open in those views, etc.
|
|
33
|
+
*/
|
|
9
34
|
session: types.maybe(sessionModelType),
|
|
10
|
-
|
|
35
|
+
/**
|
|
36
|
+
* #property
|
|
37
|
+
*/
|
|
38
|
+
sessionPath: types.stripDefault(types.string, ''),
|
|
39
|
+
/**
|
|
40
|
+
* #property
|
|
41
|
+
*/
|
|
11
42
|
assemblyManager: types.optional(assemblyManagerFactory(assemblyConfigSchema, pluginManager), {}),
|
|
12
43
|
})
|
|
13
44
|
.volatile(self => ({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
45
|
+
/**
|
|
46
|
+
* #volatile
|
|
47
|
+
*/
|
|
48
|
+
rpcManager: new RpcManager(pluginManager, self.jbrowse.configuration.rpc, rpcManagerOptions),
|
|
49
|
+
/**
|
|
50
|
+
* #volatile
|
|
51
|
+
*/
|
|
17
52
|
adminMode: false,
|
|
53
|
+
/**
|
|
54
|
+
* #volatile
|
|
55
|
+
*/
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
18
57
|
error: undefined,
|
|
58
|
+
/**
|
|
59
|
+
* #volatile
|
|
60
|
+
*/
|
|
19
61
|
textSearchManager: new TextSearchManager(pluginManager),
|
|
62
|
+
/**
|
|
63
|
+
* #volatile
|
|
64
|
+
*/
|
|
20
65
|
pluginManager,
|
|
21
66
|
}))
|
|
22
67
|
.actions(self => ({
|
|
68
|
+
/**
|
|
69
|
+
* #action
|
|
70
|
+
*/
|
|
23
71
|
setError(error) {
|
|
24
72
|
self.error = error;
|
|
25
73
|
},
|
|
74
|
+
/**
|
|
75
|
+
* #action
|
|
76
|
+
* Sets the active session. Remaps any legacy display type names
|
|
77
|
+
* (e.g. LinearPileupDisplay → LinearAlignmentsDisplay), drops nodes whose
|
|
78
|
+
* pluggable type this build has no plugin for (see
|
|
79
|
+
* `pruneUnbuildableNodes`), then walks the resulting MST tree to drop open
|
|
80
|
+
* tracks whose config can't hydrate so shared sessions still load when
|
|
81
|
+
* referencing tracks that no longer exist. Both kinds of drop are surfaced
|
|
82
|
+
* to the user via a snackbar. If filtering throws, the previous session is
|
|
83
|
+
* restored.
|
|
84
|
+
*/
|
|
26
85
|
setSession(sessionSnapshot) {
|
|
27
|
-
self.session
|
|
86
|
+
const oldSession = self.session;
|
|
87
|
+
const unbuildable = [];
|
|
88
|
+
let migrated = sessionSnapshot;
|
|
89
|
+
if (sessionSnapshot && typeof sessionSnapshot === 'object') {
|
|
90
|
+
// before the cast, because a type this build has no plugin for is a
|
|
91
|
+
// union failure the try below never gets to see
|
|
92
|
+
const pruned = pruneUnbuildableNodes(migrateSessionSnapshot(sessionSnapshot), pluginManager, sessionModelType);
|
|
93
|
+
migrated = pruned.snapshot;
|
|
94
|
+
unbuildable.push(...pruned.dropped);
|
|
95
|
+
}
|
|
96
|
+
// Detach first: assigning over it would destroy it inside this action,
|
|
97
|
+
// and MobX runs the action's pending reactions afterwards, against the
|
|
98
|
+
// nodes it just killed. ADR-069.
|
|
99
|
+
if (oldSession) {
|
|
100
|
+
detach(oldSession);
|
|
101
|
+
}
|
|
102
|
+
// The assignment is inside the try because it typechecks: a registered
|
|
103
|
+
// type carrying a malformed prop is a throw `pruneUnbuildableNodes`
|
|
104
|
+
// does not and cannot pre-empt, and the detach above has already
|
|
105
|
+
// emptied `self.session`. MST does not roll an action back, so without
|
|
106
|
+
// this the root would be left with no session at all and the old tree
|
|
107
|
+
// detached-and-alive forever.
|
|
108
|
+
try {
|
|
109
|
+
self.session = cast(migrated);
|
|
110
|
+
if (self.session) {
|
|
111
|
+
const unbuildableMessage = describeUnbuildableNodes(unbuildable);
|
|
112
|
+
if (unbuildableMessage) {
|
|
113
|
+
;
|
|
114
|
+
self.session.notify(unbuildableMessage, 'warning');
|
|
115
|
+
}
|
|
116
|
+
const dropped = filterSessionInPlace(self.session, getType(self.session));
|
|
117
|
+
if (dropped.length > 0) {
|
|
118
|
+
const names = dropped
|
|
119
|
+
.map(d => d.configuration ?? d.type ?? 'unknown')
|
|
120
|
+
.join(', ');
|
|
121
|
+
const plural = dropped.length > 1;
|
|
122
|
+
self.session.notify(`Removed ${dropped.length} track${plural ? 's' : ''} that could not be loaded: ${names}`, 'warning');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
// put it back, and do not schedule the destroy below — this is the
|
|
128
|
+
// one path where the old session goes on being the live one
|
|
129
|
+
self.session = oldSession;
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
// and it does still get destroyed, once the reaction flush has
|
|
133
|
+
// unwound. `beforeDestroy` is a plugin-facing contract and leaving a
|
|
134
|
+
// detached session alive forever would skip it. ADR-069.
|
|
135
|
+
if (oldSession) {
|
|
136
|
+
scheduleDetachedDestroy(oldSession);
|
|
137
|
+
}
|
|
28
138
|
},
|
|
139
|
+
/**
|
|
140
|
+
* #action
|
|
141
|
+
*/
|
|
29
142
|
setDefaultSession() {
|
|
30
|
-
|
|
143
|
+
const { defaultSession } = self.jbrowse;
|
|
144
|
+
this.setSession({
|
|
145
|
+
...defaultSession,
|
|
146
|
+
// timestamp the name so repeated "new session" names don't collide
|
|
147
|
+
name: `${defaultSession.name || 'New session'} ${new Date().toLocaleString()}`,
|
|
148
|
+
});
|
|
31
149
|
},
|
|
150
|
+
/**
|
|
151
|
+
* #action
|
|
152
|
+
*/
|
|
32
153
|
setSessionPath(path) {
|
|
33
154
|
self.sessionPath = path;
|
|
34
155
|
},
|
|
156
|
+
/**
|
|
157
|
+
* #action
|
|
158
|
+
*/
|
|
35
159
|
renameCurrentSession(newName) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
160
|
+
// Every concrete session model is composed from BaseSessionModel, which
|
|
161
|
+
// provides setName — avoid a full setSession rebuild here since the
|
|
162
|
+
// only field changing is `name`.
|
|
163
|
+
;
|
|
164
|
+
self.session?.setName(newName);
|
|
42
165
|
},
|
|
43
166
|
}));
|
|
44
167
|
}
|
|
168
|
+
/** Type guard for checking if something is a JB root model */
|
|
45
169
|
export function isRootModel(thing) {
|
|
46
170
|
return (isStateTreeNode(thing) &&
|
|
47
171
|
'session' in thing &&
|
|
@@ -1,31 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #config HierarchicalConfigSchema
|
|
3
|
+
* #category root
|
|
4
|
+
* generally exists on the config.json or root config as configuration.hierarchical
|
|
5
|
+
*/
|
|
1
6
|
export declare function HierarchicalConfigSchemaFactory(): import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
2
|
-
sort: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
readonly sort: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
8
|
+
/**
|
|
9
|
+
* #slot configuration.hierarchical.sort.trackNames
|
|
10
|
+
* start the track selector with tracks sorted alphabetically inside each
|
|
11
|
+
* category rather than in config order. Only the initial state — the
|
|
12
|
+
* selector's own "Sort track names" toggle overrides it from then on
|
|
13
|
+
*/
|
|
14
|
+
readonly trackNames: {
|
|
15
|
+
readonly type: "boolean";
|
|
16
|
+
readonly defaultValue: false;
|
|
6
17
|
};
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
/**
|
|
19
|
+
* #slot configuration.hierarchical.sort.categories
|
|
20
|
+
* start the track selector with categories sorted alphabetically rather
|
|
21
|
+
* than in config order. Only the initial state — the selector's own
|
|
22
|
+
* "Sort categories" toggle overrides it from then on
|
|
23
|
+
*/
|
|
24
|
+
readonly categories: {
|
|
25
|
+
readonly type: "boolean";
|
|
26
|
+
readonly defaultValue: false;
|
|
10
27
|
};
|
|
11
28
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
/**
|
|
30
|
+
* #slot configuration.hierarchical.defaultFolderCategories
|
|
31
|
+
*/
|
|
32
|
+
readonly defaultFolderCategories: {
|
|
33
|
+
readonly type: "stringArray";
|
|
34
|
+
readonly description: "list of category names to display as folders by default";
|
|
35
|
+
readonly defaultValue: readonly [];
|
|
16
36
|
};
|
|
17
|
-
defaultCollapsed: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
37
|
+
readonly defaultCollapsed: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
38
|
+
/**
|
|
39
|
+
* #slot configuration.hierarchical.defaultCollapsed.categoryNames
|
|
40
|
+
* named top-level categories that start collapsed, e.g.
|
|
41
|
+
* `['ENCODE', 'RNA-seq']`. Once a user expands or collapses anything, the
|
|
42
|
+
* selector remembers their state instead
|
|
43
|
+
*/
|
|
44
|
+
readonly categoryNames: {
|
|
45
|
+
readonly type: "stringArray";
|
|
46
|
+
readonly defaultValue: readonly [];
|
|
21
47
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
48
|
+
/**
|
|
49
|
+
* #slot configuration.hierarchical.defaultCollapsed.topLevelCategories
|
|
50
|
+
* start every top-level category collapsed, so a config with many
|
|
51
|
+
* categories opens as a short list rather than a wall of tracks
|
|
52
|
+
*/
|
|
53
|
+
readonly topLevelCategories: {
|
|
54
|
+
readonly type: "boolean";
|
|
55
|
+
readonly defaultValue: false;
|
|
25
56
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
57
|
+
/**
|
|
58
|
+
* #slot configuration.hierarchical.defaultCollapsed.subCategories
|
|
59
|
+
* start every nested category collapsed while leaving the top level
|
|
60
|
+
* open, for deeply categorized track sets
|
|
61
|
+
*/
|
|
62
|
+
readonly subCategories: {
|
|
63
|
+
readonly type: "boolean";
|
|
64
|
+
readonly defaultValue: false;
|
|
29
65
|
};
|
|
30
66
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
31
67
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
@@ -1,30 +1,66 @@
|
|
|
1
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
+
/**
|
|
3
|
+
* #config HierarchicalConfigSchema
|
|
4
|
+
* #category root
|
|
5
|
+
* generally exists on the config.json or root config as configuration.hierarchical
|
|
6
|
+
*/
|
|
2
7
|
export function HierarchicalConfigSchemaFactory() {
|
|
3
8
|
return ConfigurationSchema('hierarchical', {
|
|
4
9
|
sort: ConfigurationSchema('hierarchicalSort', {
|
|
10
|
+
/**
|
|
11
|
+
* #slot configuration.hierarchical.sort.trackNames
|
|
12
|
+
* start the track selector with tracks sorted alphabetically inside each
|
|
13
|
+
* category rather than in config order. Only the initial state — the
|
|
14
|
+
* selector's own "Sort track names" toggle overrides it from then on
|
|
15
|
+
*/
|
|
5
16
|
trackNames: {
|
|
6
17
|
type: 'boolean',
|
|
7
18
|
defaultValue: false,
|
|
8
19
|
},
|
|
20
|
+
/**
|
|
21
|
+
* #slot configuration.hierarchical.sort.categories
|
|
22
|
+
* start the track selector with categories sorted alphabetically rather
|
|
23
|
+
* than in config order. Only the initial state — the selector's own
|
|
24
|
+
* "Sort categories" toggle overrides it from then on
|
|
25
|
+
*/
|
|
9
26
|
categories: {
|
|
10
27
|
type: 'boolean',
|
|
11
28
|
defaultValue: false,
|
|
12
29
|
},
|
|
13
30
|
}),
|
|
31
|
+
/**
|
|
32
|
+
* #slot configuration.hierarchical.defaultFolderCategories
|
|
33
|
+
*/
|
|
14
34
|
defaultFolderCategories: {
|
|
15
35
|
type: 'stringArray',
|
|
16
36
|
description: 'list of category names to display as folders by default',
|
|
17
37
|
defaultValue: [],
|
|
18
38
|
},
|
|
19
39
|
defaultCollapsed: ConfigurationSchema('defaultCollapsed', {
|
|
40
|
+
/**
|
|
41
|
+
* #slot configuration.hierarchical.defaultCollapsed.categoryNames
|
|
42
|
+
* named top-level categories that start collapsed, e.g.
|
|
43
|
+
* `['ENCODE', 'RNA-seq']`. Once a user expands or collapses anything, the
|
|
44
|
+
* selector remembers their state instead
|
|
45
|
+
*/
|
|
20
46
|
categoryNames: {
|
|
21
47
|
type: 'stringArray',
|
|
22
48
|
defaultValue: [],
|
|
23
49
|
},
|
|
50
|
+
/**
|
|
51
|
+
* #slot configuration.hierarchical.defaultCollapsed.topLevelCategories
|
|
52
|
+
* start every top-level category collapsed, so a config with many
|
|
53
|
+
* categories opens as a short list rather than a wall of tracks
|
|
54
|
+
*/
|
|
24
55
|
topLevelCategories: {
|
|
25
56
|
type: 'boolean',
|
|
26
57
|
defaultValue: false,
|
|
27
58
|
},
|
|
59
|
+
/**
|
|
60
|
+
* #slot configuration.hierarchical.defaultCollapsed.subCategories
|
|
61
|
+
* start every nested category collapsed while leaving the top level
|
|
62
|
+
* open, for deeply categorized track sets
|
|
63
|
+
*/
|
|
28
64
|
subCategories: {
|
|
29
65
|
type: 'boolean',
|
|
30
66
|
defaultValue: false,
|
|
@@ -2,11 +2,28 @@ import type PluginManager from '@jbrowse/core/PluginManager';
|
|
|
2
2
|
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
3
3
|
import type { UriLocation } from '@jbrowse/core/util';
|
|
4
4
|
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
5
|
+
/**
|
|
6
|
+
* #stateModel InternetAccountsMixin
|
|
7
|
+
* #category root
|
|
8
|
+
*/
|
|
5
9
|
export declare function InternetAccountsRootModelMixin(pluginManager: PluginManager): import("@jbrowse/mobx-state-tree").IModelType<{
|
|
10
|
+
/**
|
|
11
|
+
* #property
|
|
12
|
+
*/
|
|
6
13
|
internetAccounts: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IAnyType>;
|
|
7
14
|
}, {
|
|
8
|
-
|
|
15
|
+
/**
|
|
16
|
+
* #action
|
|
17
|
+
*/
|
|
18
|
+
initializeInternetAccount(internetAccountConfig: AnyConfigurationModel, initialSnapshot?: object): any;
|
|
19
|
+
/**
|
|
20
|
+
* #action
|
|
21
|
+
*/
|
|
9
22
|
createEphemeralInternetAccount(internetAccountId: string, initialSnapshot: Record<string, unknown>, url: string): any;
|
|
23
|
+
} & {
|
|
24
|
+
/**
|
|
25
|
+
* #action
|
|
26
|
+
*/
|
|
10
27
|
findAppropriateInternetAccount(location: UriLocation): any;
|
|
11
28
|
} & {
|
|
12
29
|
afterCreate(): void;
|
|
@@ -1,74 +1,172 @@
|
|
|
1
1
|
import { addDisposer, types } from '@jbrowse/mobx-state-tree';
|
|
2
2
|
import { autorun } from 'mobx';
|
|
3
|
+
import { asRoot } from '../siblingCast.js';
|
|
4
|
+
/**
|
|
5
|
+
* The ephemeral account ids RpcManager mints are `<TypeName>-<rest>` — the
|
|
6
|
+
* leading segment names the account type and the remainder is its display name.
|
|
7
|
+
* Read in one place so the two callers cannot disagree about which is which.
|
|
8
|
+
*/
|
|
9
|
+
function parseEphemeralId(internetAccountId) {
|
|
10
|
+
const [type = '', ...rest] = internetAccountId.split('-');
|
|
11
|
+
return { type, name: rest.join('-') };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Whether the fetch this location describes would go to the origin serving
|
|
15
|
+
* JBrowse itself.
|
|
16
|
+
*
|
|
17
|
+
* `domains` cannot name that host. A config shipped with relative data paths
|
|
18
|
+
* (`volvox_microarray.bw`) resolves against wherever the app is deployed, which
|
|
19
|
+
* whoever wrote the config does not know — so an account for it has nothing to
|
|
20
|
+
* put in `domains`, and naming the account on the location is the only way to
|
|
21
|
+
* reach it. Safe whatever `domains` says: a link can move a same-origin
|
|
22
|
+
* location nowhere except the server already serving the page and holding the
|
|
23
|
+
* session.
|
|
24
|
+
*
|
|
25
|
+
* Resolved the way the request will be, against `baseUri` when there is one, so
|
|
26
|
+
* a spec cannot pass a relative uri under an off-origin base and read as local.
|
|
27
|
+
*/
|
|
28
|
+
function resolvesToAppOrigin(location) {
|
|
29
|
+
// typed as always present, and absent in node and in some embedded hosts
|
|
30
|
+
const here = globalThis.location;
|
|
31
|
+
// `null` is what an opaque origin (file:, data:) stringifies to, and two of
|
|
32
|
+
// them are not the same origin
|
|
33
|
+
if (!here?.origin || here.origin === 'null') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return (new URL(location.uri, location.baseUri ?? here.href).origin ===
|
|
38
|
+
here.origin);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// An account named by a location it is not scoped for reads as a dead track,
|
|
45
|
+
// so say which half to fix. Once per pair: the check runs on every RPC
|
|
46
|
+
// serialization, and a track issues one per block.
|
|
47
|
+
const warnedScopeMismatches = new Set();
|
|
48
|
+
function warnScopeMismatch(internetAccountId, uri) {
|
|
49
|
+
const key = `${internetAccountId}|${uri}`;
|
|
50
|
+
if (!warnedScopeMismatches.has(key)) {
|
|
51
|
+
warnedScopeMismatches.add(key);
|
|
52
|
+
console.warn(`Internet account "${internetAccountId}" is not used for ${uri}: the URL is outside its "domains". Add the host to that account's domains if this is your data.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* #stateModel InternetAccountsMixin
|
|
57
|
+
* #category root
|
|
58
|
+
*/
|
|
3
59
|
export function InternetAccountsRootModelMixin(pluginManager) {
|
|
4
60
|
return types
|
|
5
61
|
.model({
|
|
62
|
+
/**
|
|
63
|
+
* #property
|
|
64
|
+
*/
|
|
6
65
|
internetAccounts: types.array(pluginManager.pluggableMstType('internet account', 'stateModel')),
|
|
7
66
|
})
|
|
8
67
|
.actions(self => ({
|
|
68
|
+
/**
|
|
69
|
+
* #action
|
|
70
|
+
*/
|
|
9
71
|
initializeInternetAccount(internetAccountConfig, initialSnapshot = {}) {
|
|
10
|
-
|
|
11
|
-
if (!internetAccountType) {
|
|
12
|
-
throw new Error(`unknown internet account type ${internetAccountConfig.type}`);
|
|
13
|
-
}
|
|
14
|
-
const length = self.internetAccounts.push({
|
|
72
|
+
self.internetAccounts.push({
|
|
15
73
|
...initialSnapshot,
|
|
16
74
|
type: internetAccountConfig.type,
|
|
17
75
|
configuration: internetAccountConfig,
|
|
18
76
|
});
|
|
19
|
-
return self.internetAccounts
|
|
77
|
+
return self.internetAccounts.at(-1);
|
|
20
78
|
},
|
|
79
|
+
/**
|
|
80
|
+
* #action
|
|
81
|
+
*/
|
|
21
82
|
createEphemeralInternetAccount(internetAccountId, initialSnapshot, url) {
|
|
22
83
|
let hostUri;
|
|
23
84
|
try {
|
|
24
|
-
|
|
85
|
+
const urlObj = new URL(url);
|
|
86
|
+
const pathname = urlObj.pathname;
|
|
87
|
+
const lastSlash = pathname.lastIndexOf('/');
|
|
88
|
+
const dirPath = lastSlash !== -1 ? pathname.slice(0, lastSlash + 1) : '/';
|
|
89
|
+
hostUri = `${urlObj.origin}${dirPath}`;
|
|
25
90
|
}
|
|
26
|
-
catch
|
|
91
|
+
catch {
|
|
92
|
+
// ignore
|
|
27
93
|
}
|
|
28
|
-
const
|
|
94
|
+
const { type, name } = parseEphemeralId(internetAccountId);
|
|
29
95
|
const configuration = {
|
|
30
|
-
type
|
|
31
|
-
internetAccountId
|
|
32
|
-
name
|
|
96
|
+
type,
|
|
97
|
+
internetAccountId,
|
|
98
|
+
name,
|
|
33
99
|
description: '',
|
|
34
100
|
domains: hostUri ? [hostUri] : [],
|
|
35
101
|
};
|
|
36
|
-
|
|
37
|
-
const internetAccount = type.stateModel.create({
|
|
102
|
+
self.internetAccounts.push({
|
|
38
103
|
...initialSnapshot,
|
|
39
104
|
type: configuration.type,
|
|
40
105
|
configuration,
|
|
41
106
|
});
|
|
42
|
-
self.internetAccounts.
|
|
43
|
-
return internetAccount;
|
|
107
|
+
return self.internetAccounts.at(-1);
|
|
44
108
|
},
|
|
109
|
+
}))
|
|
110
|
+
.actions(self => ({
|
|
111
|
+
/**
|
|
112
|
+
* #action
|
|
113
|
+
*/
|
|
45
114
|
findAppropriateInternetAccount(location) {
|
|
115
|
+
// find the existing account selected from menu
|
|
46
116
|
const selectedId = location.internetAccountId;
|
|
47
117
|
if (selectedId) {
|
|
48
118
|
const selectedAccount = self.internetAccounts.find(account => {
|
|
49
119
|
return account.internetAccountId === selectedId;
|
|
50
120
|
});
|
|
121
|
+
// The account still has to be scoped for the URL. Naming one used to
|
|
122
|
+
// be enough on its own, which made the field a way to redirect
|
|
123
|
+
// someone else's credential: jbrowse-web builds tracks out of
|
|
124
|
+
// `sessionTracks` in the URL, and a location there carrying
|
|
125
|
+
// `internetAccountId` sent that account's token to whatever host the
|
|
126
|
+
// link chose — first as validateToken's probe from the main thread,
|
|
127
|
+
// then as the worker's read. An OAuth account with a refresh token in
|
|
128
|
+
// localStorage needs no interaction at all to mint a fresh one for it.
|
|
51
129
|
if (selectedAccount) {
|
|
52
|
-
|
|
130
|
+
if (selectedAccount.handlesLocation(location) ||
|
|
131
|
+
resolvesToAppOrigin(location)) {
|
|
132
|
+
return selectedAccount;
|
|
133
|
+
}
|
|
134
|
+
warnScopeMismatch(selectedId, location.uri);
|
|
53
135
|
}
|
|
54
136
|
}
|
|
137
|
+
// if no existing account or not found, try to find working account
|
|
55
138
|
for (const account of self.internetAccounts) {
|
|
56
139
|
const handleResult = account.handlesLocation(location);
|
|
57
140
|
if (handleResult) {
|
|
58
141
|
return account;
|
|
59
142
|
}
|
|
60
143
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
144
|
+
// if still no existing account, create ephemeral config to use — but
|
|
145
|
+
// only for an id whose leading segment names an account type this
|
|
146
|
+
// session actually has. Anything else is a location pointing at an
|
|
147
|
+
// account that is simply gone (a track from a shared session whose
|
|
148
|
+
// config defined one, a config it was removed from, a host that never
|
|
149
|
+
// loaded the authentication plugin), and pushing an unknown type into
|
|
150
|
+
// the array threw a bare MST union error out of whatever was opening
|
|
151
|
+
// the file. No account instead lets the read go out unauthenticated and
|
|
152
|
+
// report the 401 it gets, which is the failure the user can act on.
|
|
153
|
+
if (selectedId &&
|
|
154
|
+
pluginManager
|
|
155
|
+
.getElementTypesInGroup('internet account')
|
|
156
|
+
.some(t => t.name === parseEphemeralId(selectedId).type)) {
|
|
157
|
+
return self.createEphemeralInternetAccount(selectedId, {}, location.uri);
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
64
160
|
},
|
|
65
161
|
}))
|
|
66
162
|
.actions(self => ({
|
|
67
163
|
afterCreate() {
|
|
68
164
|
addDisposer(self, autorun(function internetAccountsAutorun() {
|
|
69
|
-
const { jbrowse } = self;
|
|
165
|
+
const { jbrowse } = asRoot(self);
|
|
70
166
|
for (const internetAccount of jbrowse.internetAccounts) {
|
|
71
|
-
self.
|
|
167
|
+
if (!self.internetAccounts.some(a => a.internetAccountId === internetAccount.internetAccountId)) {
|
|
168
|
+
self.initializeInternetAccount(internetAccount);
|
|
169
|
+
}
|
|
72
170
|
}
|
|
73
171
|
}, { name: 'InternetAccounts' }));
|
|
74
172
|
},
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #config PreferencesConfigSchema
|
|
3
|
+
* #category root
|
|
4
|
+
* admin/embedder defaults for user-facing preferences, found on the root config
|
|
5
|
+
* as `configuration.preferences`. Individual users override these at runtime
|
|
6
|
+
* (persisted to localStorage) via the session `getPreference` reader; a runtime
|
|
7
|
+
* override map layered over config defaults, at app scope.
|
|
8
|
+
*/
|
|
9
|
+
export declare function PreferencesConfigSchemaFactory(): import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
10
|
+
/**
|
|
11
|
+
* #slot configuration.preferences.animationMode
|
|
12
|
+
* controls feature-layout animations: 'enabled' always animates (the
|
|
13
|
+
* default), 'system' respects the OS prefers-reduced-motion setting,
|
|
14
|
+
* 'disabled' never animates
|
|
15
|
+
*/
|
|
16
|
+
readonly animationMode: {
|
|
17
|
+
readonly model: import("@jbrowse/mobx-state-tree").ISimpleType<"disabled" | "enabled" | "system">;
|
|
18
|
+
readonly type: "stringEnum";
|
|
19
|
+
readonly defaultValue: "enabled";
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* #slot configuration.preferences.scrollZoom
|
|
23
|
+
* when true, scrolling the mouse wheel over a track zooms in and out
|
|
24
|
+
* without holding Ctrl. Applies globally to all wheel-zoom views.
|
|
25
|
+
*/
|
|
26
|
+
readonly scrollZoom: {
|
|
27
|
+
readonly type: "boolean";
|
|
28
|
+
readonly defaultValue: false;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* #slot configuration.preferences.numberGrouping
|
|
32
|
+
* when true (the default), numbers are displayed with thousand separators
|
|
33
|
+
* — `chr1:1,234,567`. Turn it off to render them bare, which is what you
|
|
34
|
+
* want if you copy coordinates out of JBrowse into tools that won't accept
|
|
35
|
+
* the commas. Applies to every displayed number, and takes effect on
|
|
36
|
+
* reload.
|
|
37
|
+
*/
|
|
38
|
+
readonly numberGrouping: {
|
|
39
|
+
readonly type: "boolean";
|
|
40
|
+
readonly defaultValue: true;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* #slot configuration.preferences.useWorkspaces
|
|
44
|
+
* when true, views open in the tabbed/tiled workspace layout rather than
|
|
45
|
+
* stacked vertically. Only the default: a session that names
|
|
46
|
+
* `useWorkspaces` itself (a shared snapshot, or a session spec carrying a
|
|
47
|
+
* `layout`) still wins, and a user's own toggle overrides it.
|
|
48
|
+
*/
|
|
49
|
+
readonly useWorkspaces: {
|
|
50
|
+
readonly type: "boolean";
|
|
51
|
+
readonly defaultValue: false;
|
|
52
|
+
};
|
|
53
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|