@jbrowse/capture 5.0.0-beta.1 → 5.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -30
- package/esm/args.d.ts +8 -8
- package/esm/args.js +83 -71
- package/esm/bin.js +64 -56
- package/esm/browser.d.ts +13 -1
- package/esm/browser.js +17 -25
- package/esm/capture.d.ts +19 -87
- package/esm/capture.js +54 -237
- package/esm/catalog.d.ts +32 -0
- package/esm/catalog.js +84 -0
- package/esm/hub.d.ts +16 -15
- package/esm/hub.js +36 -29
- package/esm/imagePath.d.ts +1 -0
- package/esm/imagePath.js +8 -0
- package/esm/index.d.ts +12 -9
- package/esm/index.js +8 -7
- package/esm/instanceVersion.d.ts +5 -0
- package/esm/instanceVersion.js +21 -0
- package/esm/poll.d.ts +14 -0
- package/esm/poll.js +30 -0
- package/esm/ready.d.ts +29 -0
- package/esm/ready.js +44 -0
- package/esm/session.d.ts +24 -12
- package/esm/session.js +47 -32
- package/esm/sessionGate.d.ts +38 -105
- package/esm/sessionGate.js +82 -176
- package/esm/sessionOverflow.d.ts +6 -0
- package/esm/sessionOverflow.js +22 -0
- package/esm/url.d.ts +28 -17
- package/esm/url.js +51 -27
- package/esm/version.d.ts +1 -0
- package/esm/version.js +1 -0
- package/esm/waits.d.ts +23 -202
- package/esm/waits.js +77 -404
- package/package.json +3 -3
package/esm/sessionGate.d.ts
CHANGED
|
@@ -1,126 +1,59 @@
|
|
|
1
1
|
import type { Page } from 'puppeteer';
|
|
2
2
|
export interface SessionExpectations {
|
|
3
|
-
/** Assembly that must be open on some view.
|
|
3
|
+
/** Assembly that must be open on some view. */
|
|
4
4
|
assembly?: string;
|
|
5
5
|
/**
|
|
6
|
-
* trackIds that must all be open.
|
|
7
|
-
*
|
|
8
|
-
* already there and a count is satisfied before your track arrives — or by
|
|
9
|
-
* the default set alone when the id you passed does not exist at all.
|
|
6
|
+
* trackIds that must all be open. Exact ids rather than a count, since a
|
|
7
|
+
* config's default session may already hold others.
|
|
10
8
|
*/
|
|
11
9
|
trackIds?: string[];
|
|
10
|
+
/** The fewest open views that count. Default 1; 0 accepts none. */
|
|
11
|
+
views?: number;
|
|
12
12
|
}
|
|
13
|
-
export interface
|
|
13
|
+
export interface SessionCensus {
|
|
14
14
|
views: number;
|
|
15
15
|
assemblies: string[];
|
|
16
16
|
trackIds: string[];
|
|
17
17
|
}
|
|
18
|
-
/** What the page currently has open, or undefined if there is no session yet. */
|
|
19
|
-
export declare function readSessionSummary(page: Page): Promise<SessionSummary | undefined>;
|
|
20
|
-
export declare function readSessionSummaryInPage(): SessionSummary | undefined;
|
|
21
18
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Throws on timeout rather than proceeding. A config URL that 404s, a trackId
|
|
26
|
-
* that does not exist in the config, and an assembly name that does not match
|
|
27
|
-
* the one the config declares all fail here, which is the only place they fail
|
|
28
|
-
* at all — each of them otherwise produces a browser that loads, paints its
|
|
29
|
-
* chrome, and photographs beautifully with nothing in it.
|
|
19
|
+
* The load failure the app shows in place of itself, the census
|
|
20
|
+
* `AppReadyMarker` publishes beside its phase, and any snackbar up — which is
|
|
21
|
+
* where a location that did not resolve says so. Serialized into the page.
|
|
30
22
|
*/
|
|
31
|
-
export declare function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
export declare function readSessionInPage(): {
|
|
24
|
+
failure?: string;
|
|
25
|
+
census?: SessionCensus;
|
|
26
|
+
notices: string[];
|
|
27
|
+
};
|
|
35
28
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* jbrowse.org/code/jb2/main publishes `data-view-phase`, `data-display-phase`
|
|
40
|
-
* and `data-display-drawn`; jbrowse.org/code/jb2/latest — the released build,
|
|
41
|
-
* which is what every genomes.jbrowse.org link and every docs figure link opens
|
|
42
|
-
* — publishes none of them and exposes only the loading overlay. Against that
|
|
43
|
-
* instance the display-level waits are unfalsifiable rather than satisfied, so a
|
|
44
|
-
* caller has to be told which of the two it got instead of reading "0 displays
|
|
45
|
-
* pending" as good news.
|
|
46
|
-
*
|
|
47
|
-
* The absence of the attribute is NOT on its own the answer, which is the trap
|
|
48
|
-
* here: a page with no tracks open — an import form, a menu shot, a bare view —
|
|
49
|
-
* publishes none of it either, and reporting that as "this build cannot measure
|
|
50
|
-
* paint" is both wrong and alarming. So this asks the question only when there
|
|
51
|
-
* is something to measure, and answers true when there is not.
|
|
52
|
-
*
|
|
53
|
-
* Call it AFTER the session gate, so "no tracks open" means the page genuinely
|
|
54
|
-
* has none rather than not having got there yet.
|
|
29
|
+
* Wait until the census shows the assembly and tracks that were asked for.
|
|
30
|
+
* Throws at once with the app's message if it could not load, and on timeout
|
|
31
|
+
* with what the census held instead.
|
|
55
32
|
*/
|
|
56
|
-
export declare function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* chain can adapt to the build BEFORE it starts waiting rather than reporting
|
|
63
|
-
* afterwards what it could not see.
|
|
64
|
-
*
|
|
65
|
-
* Every wait keyed on one of these is NEGATIVE — it passes when the selector is
|
|
66
|
-
* absent — so on a build that publishes none of them all three are satisfied by
|
|
67
|
-
* a page that has not begun to draw. Read this after the session gate and treat
|
|
68
|
-
* `false` as "this signal is unavailable", never as "this signal says done".
|
|
69
|
-
*/
|
|
70
|
-
export interface Instrumentation {
|
|
71
|
-
viewPhase: boolean;
|
|
72
|
-
displayPhase: boolean;
|
|
73
|
-
displayDrawn: boolean;
|
|
74
|
-
}
|
|
75
|
-
/** Serialized into the page; exported so a test can call the real function. */
|
|
76
|
-
export declare function readInstrumentationInPage(): Instrumentation;
|
|
77
|
-
export declare function readInstrumentation(page: Page): Promise<Instrumentation>;
|
|
78
|
-
/**
|
|
79
|
-
* Displays that were still reporting unpainted at the moment of the call.
|
|
80
|
-
*
|
|
81
|
-
* Distinct from `unsettled`, which says a wait ran out of time. This says what
|
|
82
|
-
* the page looked like when the shutter fired, and the two do not imply each
|
|
83
|
-
* other: a display can go back to pending after its stage passed. An empty
|
|
84
|
-
* result means nothing at all on a build with no paint contract — check
|
|
85
|
-
* `paintContract` before reading it as good news.
|
|
86
|
-
*/
|
|
87
|
-
export declare function pendingDisplays(page: Page): Promise<string[]>;
|
|
88
|
-
/** One unpainted display and what it says about itself. */
|
|
89
|
-
export interface PendingDisplay {
|
|
90
|
-
/** the display TYPE, shared by every instance of it */
|
|
33
|
+
export declare function waitForSession(page: Page, { assembly, trackIds, views, timeout, }?: SessionExpectations & {
|
|
34
|
+
timeout?: number;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/** One display and the phase it publishes. */
|
|
37
|
+
export interface DisplayState {
|
|
38
|
+
/** The display type, or its id where a banner has replaced the display. */
|
|
91
39
|
name: string;
|
|
92
|
-
/** which instance, where the display publishes `data-display-id` */
|
|
93
40
|
id?: string;
|
|
94
|
-
/**
|
|
95
|
-
* Its own phase, or undefined on a build too old to publish one. `loading` is
|
|
96
|
-
* a display still fetching; `ready` is the one that earns this census, since
|
|
97
|
-
* a display that says it has finished while reporting no paint is a bug in
|
|
98
|
-
* the display rather than a slow page.
|
|
99
|
-
*/
|
|
100
41
|
phase?: string;
|
|
101
42
|
}
|
|
102
43
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* `
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* bug, not the wait's, and it is the one a longer timeout will
|
|
113
|
-
* never fix.
|
|
114
|
-
* absent the element publishes no phase: a build older than the
|
|
115
|
-
* attribute, or a surface that never grew one.
|
|
116
|
-
*
|
|
117
|
-
* Serialized into the page, so a test can call the real function. Read fresh
|
|
118
|
-
* from the DOM at report time rather than from handles the waits held. A handle to an element that has since re-rendered throws
|
|
119
|
-
* `Node is detached from document`, which is how the previous attempt at this
|
|
120
|
-
* turned four diagnosable timeouts into nine opaque puppeteer errors (reverted
|
|
121
|
-
* in 28c6ee6d90).
|
|
44
|
+
* Displays that are not showing their data. `pending`: unpainted, canceled,
|
|
45
|
+
* or showing an error, each failing a capture — `loading` is still fetching,
|
|
46
|
+
* `error` and `renderError` show a banner, `canceled` lasts until Retry, and
|
|
47
|
+
* `ready` is a display that says it finished without painting. `tooLarge`:
|
|
48
|
+
* showing "too much data", which is the app working as designed. Serialized
|
|
49
|
+
* into the page.
|
|
50
|
+
*
|
|
51
|
+
* `error` is selected by phase as well as by paint: an LGV display that failed
|
|
52
|
+
* its fetch counts as painted, so its banner publishes `drawn="true"`.
|
|
122
53
|
*/
|
|
123
|
-
export declare function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
54
|
+
export declare function displayCensusInPage(): {
|
|
55
|
+
pending: DisplayState[];
|
|
56
|
+
tooLarge: DisplayState[];
|
|
57
|
+
};
|
|
58
|
+
/** Displays as one clause for a message. */
|
|
59
|
+
export declare function describeDisplays(displays: DisplayState[]): string;
|
package/esm/sessionGate.js
CHANGED
|
@@ -1,196 +1,102 @@
|
|
|
1
|
-
|
|
2
|
-
export function readSessionSummary(page) {
|
|
3
|
-
return page.evaluate(readSessionSummaryInPage);
|
|
4
|
-
}
|
|
5
|
-
// Serialized into the page, so it can only call what it declares — hence the
|
|
6
|
-
// inlined copy of readViews rather than a shared import.
|
|
7
|
-
//
|
|
8
|
-
// Exported for its test: it reads `globalThis.JBrowseSession` and nothing else,
|
|
9
|
-
// so calling it in node against a stubbed global exercises the very function
|
|
10
|
-
// puppeteer serializes, rather than a copy of it that can drift.
|
|
11
|
-
export function readSessionSummaryInPage() {
|
|
12
|
-
const session = globalThis.JBrowseSession;
|
|
13
|
-
const views = session?.views;
|
|
14
|
-
if (!views) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
// Inlined rather than shared with the gate below for the same reason as the
|
|
18
|
-
// readViews copy: both are serialized into the page.
|
|
19
|
-
function collect(v) {
|
|
20
|
-
return [
|
|
21
|
-
...(v.tracks ?? []),
|
|
22
|
-
...(v.levels ?? []).flatMap(l => l.tracks ?? []),
|
|
23
|
-
...(v.views ?? []).flatMap(collect),
|
|
24
|
-
];
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
views: views.length,
|
|
28
|
-
assemblies: [
|
|
29
|
-
...new Set(views.flatMap(function asm(v) {
|
|
30
|
-
return [...(v.assemblyNames ?? []), ...(v.views ?? []).flatMap(asm)];
|
|
31
|
-
})),
|
|
32
|
-
],
|
|
33
|
-
trackIds: views.flatMap(v => collect(v).map(t => t.configuration?.trackId ?? '(unnamed)')),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
1
|
+
import { DEFAULT_TIMEOUT, holdTrue } from './poll.js';
|
|
36
2
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* Throws on timeout rather than proceeding. A config URL that 404s, a trackId
|
|
41
|
-
* that does not exist in the config, and an assembly name that does not match
|
|
42
|
-
* the one the config declares all fail here, which is the only place they fail
|
|
43
|
-
* at all — each of them otherwise produces a browser that loads, paints its
|
|
44
|
-
* chrome, and photographs beautifully with nothing in it.
|
|
3
|
+
* The load failure the app shows in place of itself, the census
|
|
4
|
+
* `AppReadyMarker` publishes beside its phase, and any snackbar up — which is
|
|
5
|
+
* where a location that did not resolve says so. Serialized into the page.
|
|
45
6
|
*/
|
|
46
|
-
export
|
|
7
|
+
export function readSessionInPage() {
|
|
8
|
+
const failure = document.querySelector('[data-app-error]')?.dataset.appError;
|
|
9
|
+
const notices = [
|
|
10
|
+
...document.querySelectorAll('[data-testid^="snackbar-"]'),
|
|
11
|
+
].map(el => el.textContent.trim());
|
|
12
|
+
const marker = document.querySelector('[data-app-tracks]');
|
|
13
|
+
if (!marker) {
|
|
14
|
+
return { failure, notices };
|
|
15
|
+
}
|
|
47
16
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// only an explicit false is pending.
|
|
58
|
-
if (views.some(v => v.initialized === false)) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
// A container view (synteny, dotplot) keeps its assemblies on the rows
|
|
62
|
-
// and its tracks on the levels, so both walks descend into sub-views
|
|
63
|
-
// and levels rather than reading the top view only.
|
|
64
|
-
const asmOf = (v) => [
|
|
65
|
-
...(v.assemblyNames ?? []),
|
|
66
|
-
...(v.views ?? []).flatMap(asmOf),
|
|
67
|
-
];
|
|
68
|
-
if (wantAssembly !== null &&
|
|
69
|
-
!views.some(v => asmOf(v).includes(wantAssembly))) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const tracksOf = (v) => [
|
|
73
|
-
...(v.tracks ?? []),
|
|
74
|
-
...(v.levels ?? []).flatMap(l => l.tracks ?? []),
|
|
75
|
-
...(v.views ?? []).flatMap(tracksOf),
|
|
76
|
-
];
|
|
77
|
-
const open = new Set(views.flatMap(v => tracksOf(v).map(t => t.configuration?.trackId)));
|
|
78
|
-
return wantTracks.every(id => open.has(id));
|
|
79
|
-
}, { timeout, polling: 250 }, assembly ?? null, trackIds);
|
|
80
|
-
// #endregion session-gate
|
|
17
|
+
return {
|
|
18
|
+
failure,
|
|
19
|
+
notices,
|
|
20
|
+
census: {
|
|
21
|
+
views: Number(marker.dataset.appViews) || 0,
|
|
22
|
+
assemblies: JSON.parse(marker.dataset.appAssemblies ?? '[]'),
|
|
23
|
+
trackIds: JSON.parse(marker.dataset.appTracks ?? '[]'),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
81
26
|
}
|
|
82
27
|
catch {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
28
|
+
return { failure, notices };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wait until the census shows the assembly and tracks that were asked for.
|
|
33
|
+
* Throws at once with the app's message if it could not load, and on timeout
|
|
34
|
+
* with what the census held instead.
|
|
35
|
+
*/
|
|
36
|
+
export async function waitForSession(page, { assembly, trackIds = [], views = 1, timeout = DEFAULT_TIMEOUT, } = {}) {
|
|
37
|
+
let last;
|
|
38
|
+
const notices = new Set();
|
|
39
|
+
const reached = await holdTrue(async () => {
|
|
40
|
+
const read = await page.evaluate(readSessionInPage).catch(() => undefined);
|
|
41
|
+
if (read?.failure) {
|
|
42
|
+
throw new Error(`JBrowse could not load: ${read.failure}`);
|
|
43
|
+
}
|
|
44
|
+
for (const notice of read?.notices ?? []) {
|
|
45
|
+
notices.add(notice);
|
|
46
|
+
}
|
|
47
|
+
const census = read?.census;
|
|
48
|
+
last = census;
|
|
49
|
+
return (!!census &&
|
|
50
|
+
census.views >= views &&
|
|
51
|
+
(assembly === undefined || census.assemblies.includes(assembly)) &&
|
|
52
|
+
trackIds.every(id => census.trackIds.includes(id)));
|
|
53
|
+
}, { holdMs: 0, timeout, pollMs: 250 });
|
|
54
|
+
if (!reached) {
|
|
55
|
+
const census = last;
|
|
56
|
+
const found = census
|
|
57
|
+
? `${census.views} view(s), assemblies [${census.assemblies.join(', ')}], tracks [${census.trackIds.join(', ')}]`
|
|
58
|
+
: 'no census on the page at all, so either this is not a JBrowse app or ' +
|
|
59
|
+
'it is older than v5, the first release to publish one';
|
|
60
|
+
const missing = trackIds.filter(id => !census?.trackIds.includes(id));
|
|
90
61
|
const wanted = [
|
|
91
62
|
assembly ? `assembly "${assembly}"` : undefined,
|
|
92
63
|
missing.length ? `track(s) [${missing.join(', ')}]` : undefined,
|
|
93
64
|
]
|
|
94
65
|
.filter(Boolean)
|
|
95
66
|
.join(' and ');
|
|
96
|
-
|
|
97
|
-
`
|
|
98
|
-
'A config
|
|
99
|
-
|
|
67
|
+
const said = notices.size
|
|
68
|
+
? `The app said: ${[...notices].join(' | ')}.`
|
|
69
|
+
: 'A trackId the config does not define, or an assembly name that does not match the config, looks like this.';
|
|
70
|
+
throw new Error(`the session never reached the requested state after ${timeout}ms. Wanted ${wanted || (views > 0 ? 'an open view' : 'a session')}; found ${found}. ${said}`);
|
|
100
71
|
}
|
|
101
72
|
}
|
|
102
|
-
export const PAINT_CONTRACT_NOTE = 'this JBrowse build publishes no data-display-drawn attributes, so "every ' +
|
|
103
|
-
'display has painted" cannot be checked — only that nothing is still ' +
|
|
104
|
-
'loading. Raise --settle if the image looks half-drawn.';
|
|
105
73
|
/**
|
|
106
|
-
*
|
|
74
|
+
* Displays that are not showing their data. `pending`: unpainted, canceled,
|
|
75
|
+
* or showing an error, each failing a capture — `loading` is still fetching,
|
|
76
|
+
* `error` and `renderError` show a banner, `canceled` lasts until Retry, and
|
|
77
|
+
* `ready` is a display that says it finished without painting. `tooLarge`:
|
|
78
|
+
* showing "too much data", which is the app working as designed. Serialized
|
|
79
|
+
* into the page.
|
|
107
80
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* and `data-display-drawn`; jbrowse.org/code/jb2/latest — the released build,
|
|
111
|
-
* which is what every genomes.jbrowse.org link and every docs figure link opens
|
|
112
|
-
* — publishes none of them and exposes only the loading overlay. Against that
|
|
113
|
-
* instance the display-level waits are unfalsifiable rather than satisfied, so a
|
|
114
|
-
* caller has to be told which of the two it got instead of reading "0 displays
|
|
115
|
-
* pending" as good news.
|
|
116
|
-
*
|
|
117
|
-
* The absence of the attribute is NOT on its own the answer, which is the trap
|
|
118
|
-
* here: a page with no tracks open — an import form, a menu shot, a bare view —
|
|
119
|
-
* publishes none of it either, and reporting that as "this build cannot measure
|
|
120
|
-
* paint" is both wrong and alarming. So this asks the question only when there
|
|
121
|
-
* is something to measure, and answers true when there is not.
|
|
122
|
-
*
|
|
123
|
-
* Call it AFTER the session gate, so "no tracks open" means the page genuinely
|
|
124
|
-
* has none rather than not having got there yet.
|
|
81
|
+
* `error` is selected by phase as well as by paint: an LGV display that failed
|
|
82
|
+
* its fetch counts as painted, so its banner publishes `drawn="true"`.
|
|
125
83
|
*/
|
|
126
|
-
export
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
84
|
+
export function displayCensusInPage() {
|
|
85
|
+
const pending = [];
|
|
86
|
+
const tooLarge = [];
|
|
87
|
+
for (const el of document.querySelectorAll('[data-display-drawn="false"], [data-display-phase="error"], [data-display-phase="canceled"], [data-display-phase="renderError"], [data-display-phase="tooLarge"]')) {
|
|
88
|
+
const phase = el.dataset.displayPhase;
|
|
89
|
+
(phase === 'tooLarge' ? tooLarge : pending).push({
|
|
90
|
+
name: el.dataset.testid ?? el.dataset.displayId ?? 'unnamed display',
|
|
91
|
+
id: el.dataset.displayId,
|
|
92
|
+
phase,
|
|
93
|
+
});
|
|
130
94
|
}
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
/** Serialized into the page; exported so a test can call the real function. */
|
|
134
|
-
export function readInstrumentationInPage() {
|
|
135
|
-
return {
|
|
136
|
-
viewPhase: document.querySelector('[data-view-phase]') !== null,
|
|
137
|
-
displayPhase: document.querySelector('[data-display-phase]') !== null,
|
|
138
|
-
displayDrawn: document.querySelector('[data-display-drawn]') !== null,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
export function readInstrumentation(page) {
|
|
142
|
-
return page.evaluate(readInstrumentationInPage);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Displays that were still reporting unpainted at the moment of the call.
|
|
146
|
-
*
|
|
147
|
-
* Distinct from `unsettled`, which says a wait ran out of time. This says what
|
|
148
|
-
* the page looked like when the shutter fired, and the two do not imply each
|
|
149
|
-
* other: a display can go back to pending after its stage passed. An empty
|
|
150
|
-
* result means nothing at all on a build with no paint contract — check
|
|
151
|
-
* `paintContract` before reading it as good news.
|
|
152
|
-
*/
|
|
153
|
-
export function pendingDisplays(page) {
|
|
154
|
-
return page.evaluate(() => [
|
|
155
|
-
...document.querySelectorAll('[data-display-drawn="false"]'),
|
|
156
|
-
].map(el => el.dataset.testid ?? (el.id || 'unnamed display')));
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* The same census, with each display's own account of itself.
|
|
160
|
-
*
|
|
161
|
-
* `pendingDisplays` above answers WHICH, and that was the whole report: every
|
|
162
|
-
* timeout read the same however it was caused. The phase separates the cases a
|
|
163
|
-
* bare name runs together —
|
|
164
|
-
*
|
|
165
|
-
* `loading` still fetching. A slow page or a fetch that never returns.
|
|
166
|
-
* `error` finished, badly. Nothing is coming; the picture is a banner.
|
|
167
|
-
* `ready` it says it is done and reports no paint. That is the display's
|
|
168
|
-
* bug, not the wait's, and it is the one a longer timeout will
|
|
169
|
-
* never fix.
|
|
170
|
-
* absent the element publishes no phase: a build older than the
|
|
171
|
-
* attribute, or a surface that never grew one.
|
|
172
|
-
*
|
|
173
|
-
* Serialized into the page, so a test can call the real function. Read fresh
|
|
174
|
-
* from the DOM at report time rather than from handles the waits held. A handle to an element that has since re-rendered throws
|
|
175
|
-
* `Node is detached from document`, which is how the previous attempt at this
|
|
176
|
-
* turned four diagnosable timeouts into nine opaque puppeteer errors (reverted
|
|
177
|
-
* in 28c6ee6d90).
|
|
178
|
-
*/
|
|
179
|
-
export function pendingDisplayStatesInPage() {
|
|
180
|
-
return [
|
|
181
|
-
...document.querySelectorAll('[data-display-drawn="false"]'),
|
|
182
|
-
].map(el => ({
|
|
183
|
-
name: el.dataset.testid ?? (el.id || 'unnamed display'),
|
|
184
|
-
id: el.dataset.displayId,
|
|
185
|
-
phase: el.dataset.displayPhase,
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
export function pendingDisplayStates(page) {
|
|
189
|
-
return page.evaluate(pendingDisplayStatesInPage);
|
|
95
|
+
return { pending, tooLarge };
|
|
190
96
|
}
|
|
191
|
-
/**
|
|
192
|
-
export function
|
|
193
|
-
return
|
|
194
|
-
.map(d => `${d.name}${d.id ? ` (${d.id})` : ''} is ${d.phase ?? 'in an unpublished phase'}`)
|
|
97
|
+
/** Displays as one clause for a message. */
|
|
98
|
+
export function describeDisplays(displays) {
|
|
99
|
+
return displays
|
|
100
|
+
.map(d => `${d.name}${d.id && d.id !== d.name ? ` (${d.id})` : ''} is ${d.phase ?? 'in an unpublished phase'}`)
|
|
195
101
|
.join('; ');
|
|
196
102
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many CSS pixels of the session lie below the window: the most any
|
|
3
|
+
* scrolling column holding a view overflows, less the empty room `ViewStack`
|
|
4
|
+
* leaves below the last view. Serialized into the page.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sessionOverflowInPage(): number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many CSS pixels of the session lie below the window: the most any
|
|
3
|
+
* scrolling column holding a view overflows, less the empty room `ViewStack`
|
|
4
|
+
* leaves below the last view. Serialized into the page.
|
|
5
|
+
*/
|
|
6
|
+
export function sessionOverflowInPage() {
|
|
7
|
+
let most = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
|
|
8
|
+
for (const container of document.querySelectorAll('[data-testid^="view-container-"]')) {
|
|
9
|
+
for (let el = container.parentElement; el; el = el.parentElement) {
|
|
10
|
+
const { overflowY } = getComputedStyle(el);
|
|
11
|
+
if ((overflowY === 'auto' || overflowY === 'scroll') &&
|
|
12
|
+
el.scrollHeight > el.clientHeight) {
|
|
13
|
+
const room = el
|
|
14
|
+
.querySelector('[data-testid="view-stack-overscroll"]')
|
|
15
|
+
?.getBoundingClientRect().height ?? 0;
|
|
16
|
+
most = Math.max(most, Math.ceil(el.scrollHeight - room - el.clientHeight));
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return most;
|
|
22
|
+
}
|
package/esm/url.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export declare const PUBLIC_INSTANCE = "https://jbrowse.org/code/jb2/
|
|
1
|
+
export declare const PUBLIC_INSTANCE = "https://jbrowse.org/code/jb2/v5.0.0-beta.11/";
|
|
2
|
+
/**
|
|
3
|
+
* The instance as the page that loads it sees it: a directory given without
|
|
4
|
+
* its slash, which the host redirects, resolves relative URLs one level up.
|
|
5
|
+
*/
|
|
6
|
+
export declare function instanceUrl(instance?: string): URL;
|
|
2
7
|
export interface JBrowseUrlOptions {
|
|
3
8
|
/** A genomes.jbrowse.org assembly: a UCSC db name (`hg38`) or GenArk accession. */
|
|
4
9
|
hub?: string;
|
|
@@ -7,14 +12,19 @@ export interface JBrowseUrlOptions {
|
|
|
7
12
|
/** Assembly to open. Defaults to `hub`, whose config names its assembly after it. */
|
|
8
13
|
assembly?: string;
|
|
9
14
|
/**
|
|
10
|
-
* Where to open
|
|
11
|
-
* a discontinuous view), or
|
|
12
|
-
* UCSC assembly on genomes.jbrowse.org
|
|
15
|
+
* Where to open: a locstring (`chr1:1,000-2,000`, several space-separated
|
|
16
|
+
* for a discontinuous view), or a feature name such as `BRCA1` on a config
|
|
17
|
+
* with a text index, which every UCSC assembly on genomes.jbrowse.org has.
|
|
13
18
|
*/
|
|
14
19
|
loc?: string;
|
|
15
20
|
/** trackIds from the config to open. */
|
|
16
21
|
tracks?: string[];
|
|
17
|
-
/**
|
|
22
|
+
/** A session spec, for anything the three fields above cannot say. */
|
|
23
|
+
spec?: object;
|
|
24
|
+
/**
|
|
25
|
+
* A session saved with "Export session...": the `{ session: {...} }`
|
|
26
|
+
* document, or the snapshot inside it.
|
|
27
|
+
*/
|
|
18
28
|
session?: object;
|
|
19
29
|
/** Name the session carries once opened. */
|
|
20
30
|
sessionName?: string;
|
|
@@ -22,18 +32,19 @@ export interface JBrowseUrlOptions {
|
|
|
22
32
|
instance?: string;
|
|
23
33
|
}
|
|
24
34
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
35
|
+
* A spec or a saved session says which assembly, locations and tracks to
|
|
36
|
+
* open, so the URL carries one of the three ways of saying so, never two.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertSessionStandsAlone({ spec, session, assembly, loc, tracks, }: JBrowseUrlOptions): void;
|
|
39
|
+
/**
|
|
40
|
+
* The URL that opens a JBrowse Web instance onto a described view, for a
|
|
41
|
+
* person to follow without launching a browser here.
|
|
28
42
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* works there. `session` becomes a
|
|
43
|
+
* `assembly`/`loc`/`tracks` become the
|
|
44
|
+
* [URL parameters](https://jbrowse.org/jb2/docs/urlparams/), whose `loc`
|
|
45
|
+
* takes a gene name. `spec` becomes a
|
|
33
46
|
* [session spec](https://jbrowse.org/jb2/docs/urlparams/#session-spec), which
|
|
34
|
-
* can describe several views and per-display settings but whose `init.loc`
|
|
35
|
-
*
|
|
36
|
-
* `session` wins, and the URL params are dropped rather than silently starting
|
|
37
|
-
* the fresh session that `&loc=` otherwise forces.
|
|
47
|
+
* can describe several views and per-display settings but whose `init.loc`
|
|
48
|
+
* takes a locstring only. `session` opens a saved session as exported.
|
|
38
49
|
*/
|
|
39
|
-
export declare function jbrowseUrl({ hub, config, assembly, loc, tracks, session, sessionName, instance, }: JBrowseUrlOptions): string;
|
|
50
|
+
export declare function jbrowseUrl({ hub, config, assembly, loc, tracks, spec, session, sessionName, instance, }: JBrowseUrlOptions): string;
|
package/esm/url.js
CHANGED
|
@@ -1,44 +1,68 @@
|
|
|
1
1
|
import { hubUrl } from './hub.js';
|
|
2
|
-
import { sessionSpecParam } from './session.js';
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
import { savedSessionParam, sessionSpecParam } from './session.js';
|
|
3
|
+
import { version } from './version.js';
|
|
4
|
+
// the release this package shipped with; `latest/` trails a major behind
|
|
5
|
+
// during a beta
|
|
6
|
+
export const PUBLIC_INSTANCE = `https://jbrowse.org/code/jb2/v${version}/`;
|
|
6
7
|
/**
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
|
|
8
|
+
* The instance as the page that loads it sees it: a directory given without
|
|
9
|
+
* its slash, which the host redirects, resolves relative URLs one level up.
|
|
10
|
+
*/
|
|
11
|
+
export function instanceUrl(instance = PUBLIC_INSTANCE) {
|
|
12
|
+
const url = new URL(instance);
|
|
13
|
+
if (!url.pathname.endsWith('/') && !/\.html?$/.test(url.pathname)) {
|
|
14
|
+
url.pathname += '/';
|
|
15
|
+
}
|
|
16
|
+
return url;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A spec or a saved session says which assembly, locations and tracks to
|
|
20
|
+
* open, so the URL carries one of the three ways of saying so, never two.
|
|
21
|
+
*/
|
|
22
|
+
export function assertSessionStandsAlone({ spec, session, assembly, loc, tracks, }) {
|
|
23
|
+
if (spec && session) {
|
|
24
|
+
throw new Error('pass a session spec or a saved session, not both');
|
|
25
|
+
}
|
|
26
|
+
if ((spec || session) && (assembly || loc || tracks?.length)) {
|
|
27
|
+
throw new Error('a session spec or saved session says which assembly, locations and ' +
|
|
28
|
+
'tracks to open, so it cannot be combined with assembly, loc or tracks ' +
|
|
29
|
+
'(--assembly, --loc, --track): put them in the session');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The URL that opens a JBrowse Web instance onto a described view, for a
|
|
34
|
+
* person to follow without launching a browser here.
|
|
10
35
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* works there. `session` becomes a
|
|
36
|
+
* `assembly`/`loc`/`tracks` become the
|
|
37
|
+
* [URL parameters](https://jbrowse.org/jb2/docs/urlparams/), whose `loc`
|
|
38
|
+
* takes a gene name. `spec` becomes a
|
|
15
39
|
* [session spec](https://jbrowse.org/jb2/docs/urlparams/#session-spec), which
|
|
16
|
-
* can describe several views and per-display settings but whose `init.loc`
|
|
17
|
-
*
|
|
18
|
-
* `session` wins, and the URL params are dropped rather than silently starting
|
|
19
|
-
* the fresh session that `&loc=` otherwise forces.
|
|
40
|
+
* can describe several views and per-display settings but whose `init.loc`
|
|
41
|
+
* takes a locstring only. `session` opens a saved session as exported.
|
|
20
42
|
*/
|
|
21
|
-
export function jbrowseUrl({ hub, config, assembly
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
// URL's own encoder, not a hand-built query string: a config URL contains
|
|
25
|
-
// `://` and `?`, and a session spec is a whole JSON document, so appending
|
|
26
|
-
// either raw produces a link that loads a truncated config and reports
|
|
27
|
-
// nothing.
|
|
43
|
+
export function jbrowseUrl({ hub, config, assembly, loc, tracks, spec, session, sessionName, instance, }) {
|
|
44
|
+
assertSessionStandsAlone({ spec, session, assembly, loc, tracks });
|
|
45
|
+
const params = new URLSearchParams();
|
|
28
46
|
const set = (key, value) => {
|
|
29
47
|
if (value) {
|
|
30
|
-
|
|
48
|
+
params.set(key, value);
|
|
31
49
|
}
|
|
32
50
|
};
|
|
33
|
-
set('config',
|
|
34
|
-
if (
|
|
35
|
-
set('session', sessionSpecParam(
|
|
51
|
+
set('config', config ?? (hub ? hubUrl(hub) : undefined));
|
|
52
|
+
if (spec) {
|
|
53
|
+
set('session', sessionSpecParam(spec));
|
|
54
|
+
}
|
|
55
|
+
else if (session) {
|
|
56
|
+
set('session', savedSessionParam(session));
|
|
36
57
|
}
|
|
37
58
|
else {
|
|
38
|
-
set('assembly', assembly);
|
|
59
|
+
set('assembly', assembly ?? hub);
|
|
39
60
|
set('loc', loc);
|
|
40
61
|
set('tracks', tracks?.join(','));
|
|
41
62
|
}
|
|
42
63
|
set('sessionName', sessionName);
|
|
64
|
+
const url = instanceUrl(instance);
|
|
65
|
+
// the fragment never reaches a server, so a large spec cannot hit HTTP 414
|
|
66
|
+
url.hash = params.toString();
|
|
43
67
|
return url.href;
|
|
44
68
|
}
|