@jbrowse/app-core 5.0.0-beta.8 → 5.0.0-beta.9
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/AppSession/AppSessionMixin.d.ts +2 -8
- package/esm/Assemblies/AssembliesMixin.d.ts +170 -92
- package/esm/JBrowseConfig/RootConfiguration.d.ts +78 -126
- package/esm/JBrowseConfig/RootConfiguration.js +5 -11
- package/esm/JBrowseConfig/defaultShareUrl.d.ts +1 -0
- package/esm/JBrowseConfig/defaultShareUrl.js +5 -0
- package/esm/JBrowseConfig/index.d.ts +79 -79
- package/esm/JBrowseConfig/index.js +1 -1
- package/esm/JBrowseModel/index.d.ts +79 -82
- package/esm/JBrowseModel/index.js +19 -10
- package/esm/JbApi/jbApi.d.ts +1069 -33
- package/esm/JbApi/jbApi.js +508 -138
- package/esm/SessionSpec/index.d.ts +1 -1
- package/esm/SessionSpec/index.js +1 -1
- package/esm/SessionSpec/loadSessionSpec.d.ts +58 -2
- package/esm/SessionSpec/loadSessionSpec.js +173 -93
- package/esm/SessionSpec/types.d.ts +6 -0
- package/esm/WorkspaceLayout/model.d.ts +8 -8
- package/esm/WorkspaceLayout/model.js +8 -8
- package/esm/ui/App/App.js +1 -2
- package/esm/ui/App/ViewStack.js +7 -1
- package/esm/ui/App/index.d.ts +1 -1
- package/esm/ui/App/index.js +1 -1
- package/package.json +4 -4
- package/esm/ui/App/AppReadyMarker.d.ts +0 -43
- package/esm/ui/App/AppReadyMarker.js +0 -78
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { buildLgvInit, buildLgvInitFromParams, hubConnectionSpec, readHubUrlParam, readNavParam, readTracklistParam, shortHubLabel, splitHighlights, } from './lgvUrlInit.ts';
|
|
2
|
-
export { addSessionTracks, loadSessionSpec } from './loadSessionSpec.ts';
|
|
2
|
+
export { addSessionTracks, launchSpecView, launchableSpecView, loadSessionSpec, viewTypeProblem, } from './loadSessionSpec.ts';
|
|
3
3
|
export { parseSessionSpecUrl } from './parseSessionSpecUrl.ts';
|
|
4
4
|
export type { LgvUrlInit } from './lgvUrlInit.ts';
|
|
5
5
|
export type { ParsedSessionSpec } from './parseSessionSpecUrl.ts';
|
package/esm/SessionSpec/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { buildLgvInit, buildLgvInitFromParams, hubConnectionSpec, readHubUrlParam, readNavParam, readTracklistParam, shortHubLabel, splitHighlights, } from './lgvUrlInit.js';
|
|
2
|
-
export { addSessionTracks, loadSessionSpec } from './loadSessionSpec.js';
|
|
2
|
+
export { addSessionTracks, launchSpecView, launchableSpecView, loadSessionSpec, viewTypeProblem, } from './loadSessionSpec.js';
|
|
3
3
|
export { parseSessionSpecUrl } from './parseSessionSpecUrl.js';
|
|
@@ -11,11 +11,67 @@ import type { AbstractSessionModel } from '@jbrowse/core/util';
|
|
|
11
11
|
* layer down.
|
|
12
12
|
*/
|
|
13
13
|
export declare function addSessionTracks(session: AbstractSessionModel | undefined, tracks: Record<string, unknown>[]): void;
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function unknownViewTypesMessage(types: string[]): string;
|
|
15
|
+
export declare function noLauncherMessage(types: string[]): string;
|
|
16
|
+
/**
|
|
17
|
+
* Why one view type cannot launch from a spec here, or nothing. Two causes,
|
|
18
|
+
* two messages: the type is unknown at all (a typo, or a plugin that was not
|
|
19
|
+
* loaded), or it exists but nothing taught it how to launch from a spec, which
|
|
20
|
+
* only the view's plugin can fix.
|
|
21
|
+
*/
|
|
22
|
+
export declare function viewTypeProblem(pluginManager: PluginManager, type: string): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* A spec view entry as its launcher takes it, and what its type does not take.
|
|
25
|
+
*
|
|
26
|
+
* The same classification a view snapshot gets, run here because a spec never
|
|
27
|
+
* becomes one: `LaunchView-<type>` takes these keys as arguments, so
|
|
28
|
+
* `withLaunchInput`'s partition never sees them and nothing names a typo.
|
|
29
|
+
* `{type: 'LinearGenomeView', asembly: 'volvox'}` reported only the launcher's
|
|
30
|
+
* downstream "No assembly provided", on the one surface written by hand with no
|
|
31
|
+
* compiler and no editor behind it.
|
|
32
|
+
*
|
|
33
|
+
* A view type that registers no launch keys classifies nothing: its launcher's
|
|
34
|
+
* vocabulary is undeclared, so every argument would read as a typo. A lazily
|
|
35
|
+
* registered type declares its properties on a state model that is not loaded
|
|
36
|
+
* yet, so it is loaded first — the launch would have loaded it anyway.
|
|
37
|
+
*/
|
|
38
|
+
export declare function launchableSpecView(pluginManager: PluginManager, spec: ViewSpec): Promise<{
|
|
39
|
+
view: ViewSpec;
|
|
40
|
+
problem?: string;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Launch one spec view into the session through its `LaunchView-<type>`
|
|
44
|
+
* extension point, the same door `loadSessionSpec` uses for each of its views
|
|
45
|
+
* — so `jb.addView` and a spec agree on what a view entry means, a
|
|
46
|
+
* ProteinView's `connectedView` included.
|
|
47
|
+
*
|
|
48
|
+
* Strict so a launch handler that throws (missing/invalid assembly, unresolved
|
|
49
|
+
* track, ...) is returned as a failure instead of being swallowed by the plain
|
|
50
|
+
* extension-point runner, which would leave a silent empty session. A handler
|
|
51
|
+
* that legitimately no-ops returns normally and is unaffected.
|
|
52
|
+
*
|
|
53
|
+
* `created` is EVERY view the launch added, in creation order: a connected
|
|
54
|
+
* ProteinView creates its genome view and then itself. `view` is the entry's
|
|
55
|
+
* own, the one of the spec's type, which is where `displayName` goes — it used
|
|
56
|
+
* to land on the genome view a ProteinView opened first, and the structure came
|
|
57
|
+
* up "Untitled view". `type` is the dispatch key, not a setting: forwarding it
|
|
58
|
+
* would land in the view's launch blob and trip the "ignored unknown key(s):
|
|
59
|
+
* type" warning meant to catch typos.
|
|
60
|
+
*/
|
|
61
|
+
export declare function launchSpecView(session: AbstractSessionModel | undefined, pluginManager: PluginManager, spec: ViewSpec): Promise<{
|
|
62
|
+
created: import("@jbrowse/core/util").AbstractViewModel[];
|
|
63
|
+
view: import("@jbrowse/core/util").AbstractViewModel | undefined;
|
|
64
|
+
failure?: {
|
|
65
|
+
message: string;
|
|
66
|
+
cause: unknown;
|
|
67
|
+
} | undefined;
|
|
68
|
+
}>;
|
|
69
|
+
export interface SessionSpec {
|
|
15
70
|
views: ViewSpec[];
|
|
16
71
|
sessionAssemblies?: Record<string, unknown>[];
|
|
17
72
|
sessionConnections?: Record<string, unknown>[];
|
|
18
73
|
sessionTracks?: Record<string, unknown>[];
|
|
19
74
|
layout?: LayoutNode;
|
|
20
75
|
sessionName?: string;
|
|
21
|
-
}
|
|
76
|
+
}
|
|
77
|
+
export declare function loadSessionSpec(spec: SessionSpec, pluginManager: PluginManager): Promise<void>;
|
|
@@ -156,15 +156,159 @@ function flattenedTabsContainers(layout) {
|
|
|
156
156
|
const nestedTabsChild = layout.direction === 'tabs' && children.some(c => c.children !== undefined);
|
|
157
157
|
return nestedTabsChild || children.some(flattenedTabsContainers);
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
export function unknownViewTypesMessage(types) {
|
|
160
|
+
return `Unknown view type(s) in session spec: ${types.join(', ')}. The plugin providing the view may be missing, or the type may be misspelled.`;
|
|
161
|
+
}
|
|
162
|
+
export function noLauncherMessage(types) {
|
|
163
|
+
return `View type(s) ${types.join(', ')} cannot be launched from a session spec: no LaunchView extension point is registered for them.`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Why one view type cannot launch from a spec here, or nothing. Two causes,
|
|
167
|
+
* two messages: the type is unknown at all (a typo, or a plugin that was not
|
|
168
|
+
* loaded), or it exists but nothing taught it how to launch from a spec, which
|
|
169
|
+
* only the view's plugin can fix.
|
|
170
|
+
*/
|
|
171
|
+
export function viewTypeProblem(pluginManager, type) {
|
|
172
|
+
if (pluginManager.extensionPoints.has(`LaunchView-${type}`)) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
// the record's `has`, not getElementType, which throws on an unregistered
|
|
176
|
+
// name rather than returning undefined
|
|
177
|
+
return pluginManager.getElementTypeRecord('view').has(type)
|
|
178
|
+
? noLauncherMessage([type])
|
|
179
|
+
: unknownViewTypesMessage([type]);
|
|
180
|
+
}
|
|
181
|
+
// v4 nested a view's settings under `init`. A spec never becomes a snapshot, so
|
|
182
|
+
// `withLaunchInput`'s unwrap never runs on one and this surface does it itself,
|
|
183
|
+
// in the same order: the flat spelling wins.
|
|
184
|
+
function flattenSpecView(spec) {
|
|
185
|
+
const { init, ...view } = spec;
|
|
186
|
+
if (init) {
|
|
187
|
+
console.warn(legacyInitMessage(view.type));
|
|
188
|
+
}
|
|
189
|
+
return init ? { ...init, ...view } : view;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A spec view entry as its launcher takes it, and what its type does not take.
|
|
193
|
+
*
|
|
194
|
+
* The same classification a view snapshot gets, run here because a spec never
|
|
195
|
+
* becomes one: `LaunchView-<type>` takes these keys as arguments, so
|
|
196
|
+
* `withLaunchInput`'s partition never sees them and nothing names a typo.
|
|
197
|
+
* `{type: 'LinearGenomeView', asembly: 'volvox'}` reported only the launcher's
|
|
198
|
+
* downstream "No assembly provided", on the one surface written by hand with no
|
|
199
|
+
* compiler and no editor behind it.
|
|
200
|
+
*
|
|
201
|
+
* A view type that registers no launch keys classifies nothing: its launcher's
|
|
202
|
+
* vocabulary is undeclared, so every argument would read as a typo. A lazily
|
|
203
|
+
* registered type declares its properties on a state model that is not loaded
|
|
204
|
+
* yet, so it is loaded first — the launch would have loaded it anyway.
|
|
205
|
+
*/
|
|
206
|
+
export async function launchableSpecView(pluginManager, spec) {
|
|
207
|
+
const flat = flattenSpecView(spec);
|
|
208
|
+
if (!pluginManager.getElementTypeRecord('view').has(flat.type)) {
|
|
209
|
+
return { view: flat };
|
|
210
|
+
}
|
|
211
|
+
const viewType = pluginManager.getViewType(flat.type);
|
|
212
|
+
await viewType.loadStateModel();
|
|
213
|
+
const accepted = viewType.acceptedKeys;
|
|
214
|
+
if (!accepted) {
|
|
215
|
+
return { view: flat };
|
|
216
|
+
}
|
|
217
|
+
const { view, problem } = readAssemblyNames(flat, accepted);
|
|
218
|
+
const { type, ...keyed } = view;
|
|
219
|
+
const unknown = Object.keys(keyed).filter(key => !accepted.includes(key));
|
|
220
|
+
const problems = [
|
|
221
|
+
...(unknown.length ? [unknownKeysMessage(type, unknown)] : []),
|
|
222
|
+
...(problem ? [problem] : []),
|
|
223
|
+
];
|
|
224
|
+
return { view, problem: problems.join('; ') || undefined };
|
|
225
|
+
}
|
|
226
|
+
// An agent reads `assemblyNames` off the live view and writes it back into a
|
|
227
|
+
// spec, where the key the entry takes is `assembly`. One name is that assembly;
|
|
228
|
+
// several are a question one view cannot answer.
|
|
229
|
+
function readAssemblyNames(view, accepted) {
|
|
230
|
+
const { assemblyNames, ...rest } = view;
|
|
231
|
+
// Array.isArray, not a length check: a hand-written `assemblyNames: 'volvox'`
|
|
232
|
+
// has a length too, and reached .join below — a throw, which this one outer
|
|
233
|
+
// catch turns into every remaining view and the layout being lost
|
|
234
|
+
if (!Array.isArray(assemblyNames) ||
|
|
235
|
+
!assemblyNames.length ||
|
|
236
|
+
!accepted.includes('assembly') ||
|
|
237
|
+
accepted.includes('assemblyNames')) {
|
|
238
|
+
return { view };
|
|
239
|
+
}
|
|
240
|
+
return assemblyNames.length === 1
|
|
241
|
+
? { view: { ...rest, assembly: rest.assembly ?? assemblyNames[0] } }
|
|
242
|
+
: {
|
|
243
|
+
view: rest,
|
|
244
|
+
problem: `${view.type} takes one "assembly", and assemblyNames names ${assemblyNames.length} (${assemblyNames.join(', ')}) — pass assembly, one view per assembly`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Launch one spec view into the session through its `LaunchView-<type>`
|
|
249
|
+
* extension point, the same door `loadSessionSpec` uses for each of its views
|
|
250
|
+
* — so `jb.addView` and a spec agree on what a view entry means, a
|
|
251
|
+
* ProteinView's `connectedView` included.
|
|
252
|
+
*
|
|
253
|
+
* Strict so a launch handler that throws (missing/invalid assembly, unresolved
|
|
254
|
+
* track, ...) is returned as a failure instead of being swallowed by the plain
|
|
255
|
+
* extension-point runner, which would leave a silent empty session. A handler
|
|
256
|
+
* that legitimately no-ops returns normally and is unaffected.
|
|
257
|
+
*
|
|
258
|
+
* `created` is EVERY view the launch added, in creation order: a connected
|
|
259
|
+
* ProteinView creates its genome view and then itself. `view` is the entry's
|
|
260
|
+
* own, the one of the spec's type, which is where `displayName` goes — it used
|
|
261
|
+
* to land on the genome view a ProteinView opened first, and the structure came
|
|
262
|
+
* up "Untitled view". `type` is the dispatch key, not a setting: forwarding it
|
|
263
|
+
* would land in the view's launch blob and trip the "ignored unknown key(s):
|
|
264
|
+
* type" warning meant to catch typos.
|
|
265
|
+
*/
|
|
266
|
+
export async function launchSpecView(session, pluginManager, spec) {
|
|
267
|
+
const { type, displayName, ...view } = spec;
|
|
268
|
+
const before = new Set(session?.views.map(v => v.id));
|
|
269
|
+
let failure;
|
|
270
|
+
try {
|
|
271
|
+
await pluginManager.evaluateAsyncExtensionPointStrict(`LaunchView-${type}`, {
|
|
272
|
+
...view,
|
|
273
|
+
session,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
console.error(e);
|
|
278
|
+
failure = { message: `Failed to launch ${type} view: ${e}`, cause: e };
|
|
279
|
+
}
|
|
280
|
+
const created = session?.views.filter(v => !before.has(v.id)) ?? [];
|
|
281
|
+
const named = created.find(v => v.type === type) ?? created[0];
|
|
282
|
+
if (named && displayName) {
|
|
283
|
+
named.setDisplayName(displayName);
|
|
284
|
+
}
|
|
285
|
+
return { created, view: named, ...(failure ? { failure } : {}) };
|
|
286
|
+
}
|
|
287
|
+
const SPEC_KEYS = [
|
|
288
|
+
'views',
|
|
289
|
+
'sessionAssemblies',
|
|
290
|
+
'sessionConnections',
|
|
291
|
+
'sessionTracks',
|
|
292
|
+
'layout',
|
|
293
|
+
'sessionName',
|
|
294
|
+
];
|
|
295
|
+
export async function loadSessionSpec(spec, pluginManager) {
|
|
296
|
+
const { views, sessionAssemblies = [], sessionConnections = [], sessionTracks = [], layout, sessionName, } = spec;
|
|
162
297
|
const rootModel = pluginManager.rootModel;
|
|
163
298
|
try {
|
|
164
299
|
rootModel.setSession?.({
|
|
165
300
|
name: sessionName ?? `New session ${new Date().toLocaleString()}`,
|
|
166
301
|
});
|
|
167
302
|
const { session } = rootModel;
|
|
303
|
+
// `tracks` for sessionTracks, `assemblies` for sessionAssemblies, `name`
|
|
304
|
+
// for sessionName: a top-level key this spec does not define used to be
|
|
305
|
+
// dropped by the destructure above with nothing said, on the one door every
|
|
306
|
+
// agent-facing text names first. Reported like the misplaced keys below,
|
|
307
|
+
// rather than thrown, so the rest of the spec still loads.
|
|
308
|
+
const unknownKeys = Object.keys(spec).filter(key => !SPEC_KEYS.includes(key));
|
|
309
|
+
if (unknownKeys.length) {
|
|
310
|
+
session?.notifyError(`${unknownKeysMessage('Session spec', unknownKeys)} — it takes ${SPEC_KEYS.join(', ')}`);
|
|
311
|
+
}
|
|
168
312
|
// Assemblies first: sessionTracks and the views below reference them by
|
|
169
313
|
// name, so a self-contained spec (novel assemblies + their tracks, no
|
|
170
314
|
// hosted config) resolves only if the assemblies exist before either runs.
|
|
@@ -217,82 +361,21 @@ export async function loadSessionSpec({ views, sessionAssemblies = [], sessionCo
|
|
|
217
361
|
}
|
|
218
362
|
}
|
|
219
363
|
addSessionTracks(session, sessionTracks);
|
|
220
|
-
//
|
|
364
|
+
// A view type with no registered LaunchView-<type> extension point makes
|
|
221
365
|
// evaluateAsyncExtensionPoint a silent no-op, leaving an empty session with
|
|
222
|
-
// no diagnostic.
|
|
223
|
-
|
|
224
|
-
// but nothing taught it how to launch from a spec, which the spec author
|
|
225
|
-
// can't fix, only the view's plugin can.
|
|
366
|
+
// no diagnostic. Reported once per type, not per view.
|
|
367
|
+
const viewTypes = pluginManager.getElementTypeRecord('view');
|
|
226
368
|
const notLaunchable = [
|
|
227
|
-
...new Set(views.flatMap(view => pluginManager.
|
|
228
|
-
? []
|
|
229
|
-
: [view.type])),
|
|
369
|
+
...new Set(views.flatMap(view => viewTypeProblem(pluginManager, view.type) ? [view.type] : [])),
|
|
230
370
|
];
|
|
231
|
-
// the record's `has`, not getElementType, which throws on an unregistered
|
|
232
|
-
// name rather than returning undefined
|
|
233
|
-
const viewTypes = pluginManager.getElementTypeRecord('view');
|
|
234
371
|
const unknown = notLaunchable.filter(type => !viewTypes.has(type));
|
|
235
372
|
const noLauncher = notLaunchable.filter(type => viewTypes.has(type));
|
|
236
373
|
if (unknown.length) {
|
|
237
|
-
session?.notifyError(
|
|
374
|
+
session?.notifyError(unknownViewTypesMessage(unknown));
|
|
238
375
|
}
|
|
239
376
|
if (noLauncher.length) {
|
|
240
|
-
session?.notifyError(
|
|
241
|
-
}
|
|
242
|
-
// v4 nested a view's settings under `init`. A spec never becomes a
|
|
243
|
-
// snapshot, so `withLaunchInput`'s unwrap never runs on one and this
|
|
244
|
-
// surface does it itself, in the same order: the flat spelling wins.
|
|
245
|
-
const specViews = views.map(({ init, ...view }) => {
|
|
246
|
-
if (init) {
|
|
247
|
-
console.warn(legacyInitMessage(view.type));
|
|
248
|
-
}
|
|
249
|
-
return init ? { ...init, ...view } : view;
|
|
250
|
-
});
|
|
251
|
-
// The same classification a view snapshot gets, run here because a spec
|
|
252
|
-
// never becomes one: `LaunchView-<type>` takes these keys as arguments, so
|
|
253
|
-
// `withLaunchInput`'s partition never sees them and nothing names a typo.
|
|
254
|
-
// `{type: 'LinearGenomeView', asembly: 'volvox'}` reported only the
|
|
255
|
-
// launcher's downstream "No assembly provided", on the one surface written
|
|
256
|
-
// by hand with no compiler and no editor behind it.
|
|
257
|
-
//
|
|
258
|
-
// An ERROR rather than the snapshot path's warning: this surface's
|
|
259
|
-
// misplaced keys already report as errors, and the launcher's own failure
|
|
260
|
-
// lands as an error a line later — a warning under it reads as the lesser
|
|
261
|
-
// of the two, when it is the cause.
|
|
262
|
-
//
|
|
263
|
-
// A view type that registers no launch keys classifies nothing: its
|
|
264
|
-
// launcher's vocabulary is undeclared, so every argument would read as a
|
|
265
|
-
// typo.
|
|
266
|
-
//
|
|
267
|
-
// A lazily registered view type declares its properties on a state model
|
|
268
|
-
// that is not loaded yet, so it is loaded first — the launch below would
|
|
269
|
-
// have loaded it anyway.
|
|
270
|
-
await Promise.all(specViews
|
|
271
|
-
.filter(({ type }) => viewTypes.has(type))
|
|
272
|
-
.map(({ type }) => pluginManager.getViewType(type).loadStateModel()));
|
|
273
|
-
for (const { type, ...view } of specViews) {
|
|
274
|
-
const accepted = viewTypes.has(type)
|
|
275
|
-
? pluginManager.getViewType(type).acceptedKeys
|
|
276
|
-
: undefined;
|
|
277
|
-
const unknown = accepted
|
|
278
|
-
? Object.keys(view).filter(key => !accepted.includes(key))
|
|
279
|
-
: [];
|
|
280
|
-
if (unknown.length) {
|
|
281
|
-
session?.notifyError(unknownKeysMessage(type, unknown));
|
|
282
|
-
}
|
|
377
|
+
session?.notifyError(noLauncherMessage(noLauncher));
|
|
283
378
|
}
|
|
284
|
-
// Launch sequentially and record the id each spec view created, so the
|
|
285
|
-
// layout below can map its indices to real views. Reading session.views
|
|
286
|
-
// positionally afterwards only works while every handler happens to addView
|
|
287
|
-
// synchronously and in order; capturing the delta per launch instead is
|
|
288
|
-
// correct even if a handler awaits or adds an auxiliary view, and lets a
|
|
289
|
-
// later spec view (e.g. a connected MsaView) reference an earlier one that
|
|
290
|
-
// now already exists. `type` is the dispatch key, not a setting: forwarding
|
|
291
|
-
// it would land in the view's launch blob and trip the spurious
|
|
292
|
-
// "ignored unknown key(s): type" warning meant to catch typos.
|
|
293
|
-
// `displayName` is applied here rather than forwarded because it is a base
|
|
294
|
-
// view prop every view type has, so one path covers all of them (including
|
|
295
|
-
// plugin-provided types whose launcher never heard of it).
|
|
296
379
|
// Let any connection this spec registered finish before the views that
|
|
297
380
|
// reference what it supplies are launched (see whenConnectionsSettle). Gated
|
|
298
381
|
// on there being views: with none, nothing is waiting on the connection, and
|
|
@@ -306,31 +389,28 @@ export async function loadSessionSpec({ views, sessionAssemblies = [], sessionCo
|
|
|
306
389
|
!(await whenConnectionsSettle(session))) {
|
|
307
390
|
return;
|
|
308
391
|
}
|
|
392
|
+
// Launch sequentially and record the ids each spec view created, so the
|
|
393
|
+
// layout below can map its indices to real views. Reading session.views
|
|
394
|
+
// positionally afterwards only works while every handler happens to addView
|
|
395
|
+
// synchronously and in order; capturing the delta per launch instead is
|
|
396
|
+
// correct even if a handler awaits or adds an auxiliary view, and lets a
|
|
397
|
+
// later spec view (e.g. a connected MsaView) reference an earlier one that
|
|
398
|
+
// now already exists.
|
|
399
|
+
//
|
|
400
|
+
// An unknown key is an ERROR rather than the snapshot path's warning: this
|
|
401
|
+
// surface's misplaced keys already report as errors, and the launcher's own
|
|
402
|
+
// failure lands as an error a line later — a warning under it reads as the
|
|
403
|
+
// lesser of the two, when it is the cause. Per view, so one bad view
|
|
404
|
+
// doesn't cost the rest.
|
|
309
405
|
const createdViewIds = [];
|
|
310
|
-
for (const
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// by the plain extension-point runner, which would leave a silent empty
|
|
315
|
-
// session. A handler that legitimately no-ops returns normally and is
|
|
316
|
-
// unaffected. Per-view try/catch so one bad view doesn't abort the rest.
|
|
317
|
-
try {
|
|
318
|
-
await pluginManager.evaluateAsyncExtensionPointStrict(`LaunchView-${type}`, {
|
|
319
|
-
...view,
|
|
320
|
-
session,
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
catch (e) {
|
|
324
|
-
console.error(e);
|
|
325
|
-
session?.notifyError(`Failed to launch ${type} view: ${e}`, e);
|
|
406
|
+
for (const spec of views) {
|
|
407
|
+
const { view, problem } = await launchableSpecView(pluginManager, spec);
|
|
408
|
+
if (problem) {
|
|
409
|
+
session?.notifyError(problem);
|
|
326
410
|
}
|
|
327
|
-
const created = session
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// and the structure came up "Untitled view".
|
|
331
|
-
const named = created.find(v => v.type === type) ?? created[0];
|
|
332
|
-
if (named && displayName) {
|
|
333
|
-
named.setDisplayName(displayName);
|
|
411
|
+
const { created, failure } = await launchSpecView(session, pluginManager, view);
|
|
412
|
+
if (failure) {
|
|
413
|
+
session?.notifyError(failure.message, failure.cause);
|
|
334
414
|
}
|
|
335
415
|
createdViewIds.push(created.map(v => v.id));
|
|
336
416
|
}
|
|
@@ -6,6 +6,12 @@ export interface ViewSpec {
|
|
|
6
6
|
displayName?: string;
|
|
7
7
|
tracks?: TrackInit[];
|
|
8
8
|
assembly?: string;
|
|
9
|
+
/**
|
|
10
|
+
* The live view's spelling of the key above, accepted because that is where
|
|
11
|
+
* a spec gets written from: one name is the `assembly`, and several are
|
|
12
|
+
* refused naming it.
|
|
13
|
+
*/
|
|
14
|
+
assemblyNames?: string[];
|
|
9
15
|
loc?: string;
|
|
10
16
|
/**
|
|
11
17
|
* @deprecated v4's nesting. Write every setting directly on the view object;
|
|
@@ -243,14 +243,14 @@ export declare function WorkspaceLayoutMixin(): import("@jbrowse/mobx-state-tree
|
|
|
243
243
|
tileViews(mode: TileMode, allViewIds: string[]): void;
|
|
244
244
|
/**
|
|
245
245
|
* #action
|
|
246
|
-
* Arrange the session's views into panels
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* Leaves take view ids or indexes into `session.views`; a
|
|
252
|
-
* seats no view throws rather than leaving a blank tab.
|
|
253
|
-
* spec's `layout` and an agent's live re-layout both call.
|
|
246
|
+
* Arrange the session's views into panels. Calls `applyLayoutSpec`,
|
|
247
|
+
* turns workspaces mode on for this session (a layout renders nowhere
|
|
248
|
+
* else), and orders `session.views` to the spec's top-to-bottom
|
|
249
|
+
* order, which tabs read their order from. Without the last two steps
|
|
250
|
+
* the layout applies but does not display, or the tabs appear in the
|
|
251
|
+
* wrong order. Leaves take view ids or indexes into `session.views`; a
|
|
252
|
+
* layout that seats no view throws rather than leaving a blank tab. A
|
|
253
|
+
* session spec's `layout` and an agent's live re-layout both call it.
|
|
254
254
|
*/
|
|
255
255
|
layoutViews(spec: LayoutSpecNode): string[];
|
|
256
256
|
}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
|
|
@@ -560,14 +560,14 @@ export function WorkspaceLayoutMixin() {
|
|
|
560
560
|
},
|
|
561
561
|
/**
|
|
562
562
|
* #action
|
|
563
|
-
* Arrange the session's views into panels
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
* Leaves take view ids or indexes into `session.views`; a
|
|
569
|
-
* seats no view throws rather than leaving a blank tab.
|
|
570
|
-
* spec's `layout` and an agent's live re-layout both call.
|
|
563
|
+
* Arrange the session's views into panels. Calls `applyLayoutSpec`,
|
|
564
|
+
* turns workspaces mode on for this session (a layout renders nowhere
|
|
565
|
+
* else), and orders `session.views` to the spec's top-to-bottom
|
|
566
|
+
* order, which tabs read their order from. Without the last two steps
|
|
567
|
+
* the layout applies but does not display, or the tabs appear in the
|
|
568
|
+
* wrong order. Leaves take view ids or indexes into `session.views`; a
|
|
569
|
+
* layout that seats no view throws rather than leaving a blank tab. A
|
|
570
|
+
* session spec's `layout` and an agent's live re-layout both call it.
|
|
571
571
|
*/
|
|
572
572
|
layoutViews(spec) {
|
|
573
573
|
const host = layoutHost(self);
|
package/esm/ui/App/App.js
CHANGED
|
@@ -2,11 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Suspense, lazy } from 'react';
|
|
3
3
|
import Snackbar from '@jbrowse/core/ui/Snackbar';
|
|
4
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import { ModalWidget, drawerGridTemplateColumns } from '@jbrowse/product-core';
|
|
5
|
+
import { AppReadyMarker, ModalWidget, drawerGridTemplateColumns, } from '@jbrowse/product-core';
|
|
6
6
|
import { AppBar } from '@mui/material';
|
|
7
7
|
import { observer } from 'mobx-react';
|
|
8
8
|
import AppFab from './AppFab.js';
|
|
9
|
-
import AppReadyMarker from './AppReadyMarker.js';
|
|
10
9
|
import AppToolbar from './AppToolbar.js';
|
|
11
10
|
import DialogQueue from './DialogQueue.js';
|
|
12
11
|
import ViewsContainer from './ViewsContainer.js';
|
package/esm/ui/App/ViewStack.js
CHANGED
|
@@ -10,6 +10,12 @@ import ViewContainer from './ViewContainer.js';
|
|
|
10
10
|
// the amount that makes that gesture reach, and any more is room to scroll the
|
|
11
11
|
// content off its own surface.
|
|
12
12
|
//
|
|
13
|
+
// It is scrollable room rather than content, so anything measuring how tall the
|
|
14
|
+
// session is discounts it by its `view-stack-overscroll` testid — `overflow` in
|
|
15
|
+
// jbApi does, and a filmed take paid for that: counted as content, it told
|
|
16
|
+
// `jb.fitToWindow` the session was 300px taller than it was, and the dotplot
|
|
17
|
+
// absorbing the cut came out a third of the height that fit.
|
|
18
|
+
//
|
|
13
19
|
// Capped, because half a port stops being a courtesy on a tall screen. 300px is
|
|
14
20
|
// the cap the classic stack has always had and it stays the answer there — a
|
|
15
21
|
// full window is comfortably past twice it. What the cap was hiding is the
|
|
@@ -37,6 +43,6 @@ const ViewStack = observer(function ViewStack({ views, session, className, }) {
|
|
|
37
43
|
// restore / initial load), so they must not steal scroll when they mount. A
|
|
38
44
|
// view whose id isn't in this set was added later and auto-scrolls into view.
|
|
39
45
|
const [initialViewIds] = useState(() => new Set(views.map(v => v.id)));
|
|
40
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: viewsRef, className: className, children: views.map(view => (_jsx(ViewContainer, { view: view, session: session, scrollOnMount: !initialViewIds.has(view.id) }, view.id))) }), scrollable ? _jsx("div", { className: classes.spacer }) : null] }));
|
|
46
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: viewsRef, className: className, children: views.map(view => (_jsx(ViewContainer, { view: view, session: session, scrollOnMount: !initialViewIds.has(view.id) }, view.id))) }), scrollable ? (_jsx("div", { className: classes.spacer, "data-testid": "view-stack-overscroll" })) : null] }));
|
|
41
47
|
});
|
|
42
48
|
export default ViewStack;
|
package/esm/ui/App/index.d.ts
CHANGED
package/esm/ui/App/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/app-core",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 code shared between the 'full featured' apps e.g. jbrowse-web and jbrowse-desktop",
|
|
6
6
|
"keywords": [
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"esm"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jbrowse/mobx-state-tree": "^6.5.
|
|
27
|
+
"@jbrowse/mobx-state-tree": "^6.5.2",
|
|
28
28
|
"@mui/icons-material": "^9.4.0",
|
|
29
29
|
"@mui/material": "^9.4.0",
|
|
30
30
|
"mobx": "^7.0.3",
|
|
31
31
|
"mobx-react": "^10.0.2",
|
|
32
|
-
"@jbrowse/
|
|
33
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
32
|
+
"@jbrowse/core": "^5.0.0-beta.9",
|
|
33
|
+
"@jbrowse/product-core": "^5.0.0-beta.9"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": ">=19.0.0",
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { AppSession } from './types.ts';
|
|
2
|
-
/**
|
|
3
|
-
* One element that says whether the whole app has finished, and what is open.
|
|
4
|
-
*
|
|
5
|
-
* `[data-app-phase="ready"]` is the entire readiness contract for anything
|
|
6
|
-
* driving JBrowse from outside — a screenshot tool, a test, an agent. Wait for
|
|
7
|
-
* that selector and you are done; there is no chain to assemble and no
|
|
8
|
-
* per-display census to take.
|
|
9
|
-
*
|
|
10
|
-
* Why it exists when `data-view-phase` and `data-display-phase` already do:
|
|
11
|
-
* those are per-element and NEGATIVE. Asking them "is everything finished"
|
|
12
|
-
* means asserting that no element carries the loading value, which is equally
|
|
13
|
-
* true of an app that has not started — and the gap is real, measured at about
|
|
14
|
-
* a second on a two-track session. A positive attribute cannot be satisfied
|
|
15
|
-
* early, because something has to render it.
|
|
16
|
-
*
|
|
17
|
-
* The census attributes beside it — `data-app-views`, `data-app-assemblies`,
|
|
18
|
-
* `data-app-tracks` (the latter two JSON arrays) — publish WHAT is open the
|
|
19
|
-
* same way the phase publishes whether it is done, so an outside reader asking
|
|
20
|
-
* "is the track I requested actually open" reads one element instead of
|
|
21
|
-
* walking `window.JBrowseSession` with its own copy of the view nesting. The
|
|
22
|
-
* walk itself is `openViews`/`openTracks` over each view's declared
|
|
23
|
-
* `ownViews`/`ownTracks`, so this component knows nothing about which property
|
|
24
|
-
* a container view keeps its children on either.
|
|
25
|
-
*
|
|
26
|
-
* Its own component, and `hidden`, for two reasons: an observer here subscribes
|
|
27
|
-
* to every view's phase without re-rendering the app shell around it, and a
|
|
28
|
-
* marker that is never drawn cannot be mistaken for UI or shift a layout. It is
|
|
29
|
-
* still found by `querySelector`, which is what reads it.
|
|
30
|
-
*
|
|
31
|
-
* **What it is silent about**: a display in a TERMINAL state. `error` is a
|
|
32
|
-
* finished state, so this reads `ready` over a display whose fetch failed —
|
|
33
|
-
* correctly, since nothing is still working. A capture wants more than that: it
|
|
34
|
-
* wants a picture, and an error banner is not one. `waitForDisplaysDone` after
|
|
35
|
-
* this is what draws that line, keying on the `data-display-drawn` the two
|
|
36
|
-
* comparative canvases publish from their stricter `settled` gate (see
|
|
37
|
-
* `comparativeReadiness` in `@jbrowse/synteny-core`, which holds both answers
|
|
38
|
-
* and says why an error separates them). Both harnesses do it in that order.
|
|
39
|
-
*/
|
|
40
|
-
declare const AppReadyMarker: ({ session, }: {
|
|
41
|
-
session: AppSession;
|
|
42
|
-
}) => import("react").JSX.Element;
|
|
43
|
-
export default AppReadyMarker;
|