@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/hub.d.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
export declare function hubUrl(hub: string): string;
|
|
2
|
-
interface
|
|
2
|
+
export interface Track {
|
|
3
3
|
trackId: string;
|
|
4
4
|
type?: string;
|
|
5
|
-
name?:
|
|
5
|
+
name?: unknown;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export declare const trackName: (track: Track) => string;
|
|
8
|
+
/** Tracks whose id or display name contains `filter`, ignoring case. */
|
|
9
|
+
export declare function tracksMatching(tracks: Track[], filter: string): Track[];
|
|
10
|
+
/** Fetch JSON with a timeout, throwing a message that names `what` and the URL. */
|
|
11
|
+
export declare function fetchJson(url: string, what: string): Promise<unknown>;
|
|
12
|
+
interface HostedAssembly {
|
|
13
|
+
name: string;
|
|
14
|
+
organism?: string;
|
|
15
|
+
description?: string;
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* before naming them in a session.
|
|
18
|
+
* The UCSC assemblies genomes.jbrowse.org hosts. Its thousands of GenArk
|
|
19
|
+
* accessions are not listed.
|
|
15
20
|
*/
|
|
16
|
-
export declare function
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
* id or display name contains `filter`. Case-insensitive, because a hub carries
|
|
20
|
-
* hundreds of tracks and an agent picking one from a name is the common case.
|
|
21
|
-
*/
|
|
22
|
-
export declare function listHubTracks(hub: string, filter?: string): Promise<HubTrack[]>;
|
|
21
|
+
export declare function listHubAssemblies(): Promise<HostedAssembly[]>;
|
|
22
|
+
/** The tracks a hosted assembly publishes, optionally narrowed by `filter`. */
|
|
23
|
+
export declare function listHubTracks(hub: string, filter?: string): Promise<Track[]>;
|
|
23
24
|
export {};
|
package/esm/hub.js
CHANGED
|
@@ -1,43 +1,50 @@
|
|
|
1
1
|
const HUB_HOST = 'https://jbrowse.org';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// Deliberately a copy of `hubUrl` in packages/core/src/util/fetchHub.ts rather
|
|
7
|
-
// than an import of it. This package is what an agent reaches for with a bare
|
|
8
|
-
// `npx`, and its only runtime dependency is puppeteer; pulling in @jbrowse/core
|
|
9
|
-
// (react, mobx, the whole state tree) to evaluate one regex would be the largest
|
|
10
|
-
// thing in the install by far. `hub.test.ts` imports both and asserts they agree
|
|
11
|
-
// on every shape, so the copy cannot drift silently.
|
|
2
|
+
const FETCH_TIMEOUT_MS = 30000;
|
|
3
|
+
const BROWSE_HINT = 'See https://genomes.jbrowse.org for the available assemblies.';
|
|
4
|
+
// A copy of hubUrl in packages/core/src/util/fetchHub.ts, so an npx install
|
|
5
|
+
// stays puppeteer-only; hub.test.ts holds the two together.
|
|
12
6
|
export function hubUrl(hub) {
|
|
13
7
|
const genark = /^(GC[AF])_(\d{3})(\d{3})(\d{3})/.exec(hub);
|
|
14
8
|
return genark
|
|
15
9
|
? `${HUB_HOST}/hubs/genark/${genark[1]}/${genark[2]}/${genark[3]}/${genark[4]}/${hub}/config.json`
|
|
16
10
|
: `${HUB_HOST}/ucsc/${hub}/config.json`;
|
|
17
11
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
12
|
+
export const trackName = (track) => typeof track.name === 'string' ? track.name : '';
|
|
13
|
+
/** Tracks whose id or display name contains `filter`, ignoring case. */
|
|
14
|
+
export function tracksMatching(tracks, filter) {
|
|
15
|
+
const needle = filter.toLowerCase();
|
|
16
|
+
return tracks.filter(t => `${t.trackId} ${trackName(t)}`.toLowerCase().includes(needle));
|
|
17
|
+
}
|
|
18
|
+
/** Fetch JSON with a timeout, throwing a message that names `what` and the URL. */
|
|
19
|
+
export async function fetchJson(url, what) {
|
|
20
|
+
const res = await fetch(url, {
|
|
21
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
22
|
+
}).catch((error) => {
|
|
23
|
+
const timedOut = error instanceof Error && error.name === 'TimeoutError';
|
|
24
|
+
throw new Error(`${what} could not be fetched from ${url} ` +
|
|
25
|
+
`(${error instanceof Error ? error.message : error}${timedOut ? `, after ${FETCH_TIMEOUT_MS}ms` : ''}). ${BROWSE_HINT}`);
|
|
26
|
+
});
|
|
26
27
|
if (!res.ok) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
'See https://genomes.jbrowse.org for the available assemblies.');
|
|
28
|
+
throw new Error(`${what} not found (HTTP ${res.status} from ${url}). ${BROWSE_HINT}`);
|
|
29
29
|
}
|
|
30
|
-
return
|
|
30
|
+
return res.json();
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
* The
|
|
34
|
-
*
|
|
35
|
-
* hundreds of tracks and an agent picking one from a name is the common case.
|
|
33
|
+
* The UCSC assemblies genomes.jbrowse.org hosts. Its thousands of GenArk
|
|
34
|
+
* accessions are not listed.
|
|
36
35
|
*/
|
|
36
|
+
export async function listHubAssemblies() {
|
|
37
|
+
const { ucscGenomes = {} } = (await fetchJson(`${HUB_HOST}/ucsc/list.json`, 'the hosted assembly list'));
|
|
38
|
+
return Object.entries(ucscGenomes)
|
|
39
|
+
.map(([name, { organism, description }]) => ({
|
|
40
|
+
name,
|
|
41
|
+
organism,
|
|
42
|
+
description,
|
|
43
|
+
}))
|
|
44
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
45
|
+
}
|
|
46
|
+
/** The tracks a hosted assembly publishes, optionally narrowed by `filter`. */
|
|
37
47
|
export async function listHubTracks(hub, filter) {
|
|
38
|
-
const { tracks = [] } = await
|
|
39
|
-
|
|
40
|
-
return needle
|
|
41
|
-
? tracks.filter(t => `${t.trackId} ${t.name ?? ''}`.toLowerCase().includes(needle))
|
|
42
|
-
: tracks;
|
|
48
|
+
const { tracks = [] } = (await fetchJson(hubUrl(hub), `hub "${hub}"`));
|
|
49
|
+
return filter ? tracksMatching(tracks, filter) : tracks;
|
|
43
50
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function assertImagePath(out: string): void;
|
package/esm/imagePath.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The types puppeteer infers from a path; it writes a PNG under any other name.
|
|
2
|
+
const IMAGE_EXTENSION = /\.(png|jpe?g|webp)$/i;
|
|
3
|
+
export function assertImagePath(out) {
|
|
4
|
+
if (!IMAGE_EXTENSION.test(out)) {
|
|
5
|
+
throw new Error(`--out needs a .png, .jpg, .jpeg or .webp path, got "${out}" ` +
|
|
6
|
+
'(for SVG, @jbrowse/img renders one without a browser)');
|
|
7
|
+
}
|
|
8
|
+
}
|
package/esm/index.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
export { captureJBrowse, openJBrowse
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export
|
|
9
|
-
export type {
|
|
1
|
+
export { captureJBrowse, openJBrowse } from './capture.ts';
|
|
2
|
+
export { delay } from './poll.ts';
|
|
3
|
+
export { waitForFrame, waitForJBrowseReady } from './ready.ts';
|
|
4
|
+
export { describeDisplays, displayCensusInPage, waitForSession, } from './sessionGate.ts';
|
|
5
|
+
export { jbrowseUrl } from './url.ts';
|
|
6
|
+
export { assemblyFromSession, encodeSessionSpec, sessionSpecQuery, trackIdsFromSession, } from './session.ts';
|
|
7
|
+
export { PENDING_DISPLAYS, displayPainted, displaySettled, waitForAppSettled, waitForSelectorAttributed, } from './waits.ts';
|
|
8
|
+
export { BASE_CHROME_ARGS, findChromeExecutable, isBrowserConsoleNoise, launchBrowser, } from './browser.ts';
|
|
9
|
+
export type { LaunchOptions } from './browser.ts';
|
|
10
|
+
export type { CaptureOptions, CaptureResult, OpenOptions, OpenResult, } from './capture.ts';
|
|
11
|
+
export type { ReadyOptions, ReadyReport } from './ready.ts';
|
|
12
|
+
export type { DisplayState, SessionExpectations } from './sessionGate.ts';
|
|
10
13
|
export type { JBrowseUrlOptions } from './url.ts';
|
package/esm/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { captureJBrowse, openJBrowse
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
1
|
+
export { captureJBrowse, openJBrowse } from './capture.js';
|
|
2
|
+
export { delay } from './poll.js';
|
|
3
|
+
export { waitForFrame, waitForJBrowseReady } from './ready.js';
|
|
4
|
+
export { describeDisplays, displayCensusInPage, waitForSession, } from './sessionGate.js';
|
|
5
|
+
export { jbrowseUrl } from './url.js';
|
|
6
|
+
export { assemblyFromSession, encodeSessionSpec, sessionSpecQuery, trackIdsFromSession, } from './session.js';
|
|
7
|
+
export { PENDING_DISPLAYS, displayPainted, displaySettled, waitForAppSettled, waitForSelectorAttributed, } from './waits.js';
|
|
8
|
+
export { BASE_CHROME_ARGS, findChromeExecutable, isBrowserConsoleNoise, launchBrowser, } from './browser.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { instanceUrl } from './url.js';
|
|
2
|
+
// the first release to publish `[data-app-phase]` and its census
|
|
3
|
+
const MIN_MAJOR = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Refuse a JBrowse Web instance older than v5, read off its `version.txt`. An
|
|
6
|
+
* instance that serves no version passes, and the session gate judges it.
|
|
7
|
+
*/
|
|
8
|
+
export async function assertSupportedInstance(instance) {
|
|
9
|
+
const text = await fetch(new URL('version.txt', instanceUrl(instance)), {
|
|
10
|
+
signal: AbortSignal.timeout(10000),
|
|
11
|
+
})
|
|
12
|
+
.then(res => (res.ok ? res.text() : ''))
|
|
13
|
+
.catch(() => '');
|
|
14
|
+
const version = text.trim();
|
|
15
|
+
const major = /^(\d+)\.\d+\.\d+/.exec(version)?.[1];
|
|
16
|
+
if (major !== undefined && Number(major) < MIN_MAJOR) {
|
|
17
|
+
throw new Error(`${instance} serves JBrowse ${version}, and @jbrowse/capture needs v${MIN_MAJOR} or later: ` +
|
|
18
|
+
'older builds publish no readiness marker to wait on. Point --instance at a ' +
|
|
19
|
+
`v${MIN_MAJOR} build.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
package/esm/poll.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** The budget each stage of the ready chain gets unless told otherwise. */
|
|
2
|
+
export declare const DEFAULT_TIMEOUT = 60000;
|
|
3
|
+
export declare const delay: (ms: number) => Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Poll `read` until it has answered true for `holdMs` without a break, or
|
|
6
|
+
* `timeout` runs out. A `read` that throws ends the wait with its error.
|
|
7
|
+
* Polled from Node because Chrome throttles in-page timers in a background
|
|
8
|
+
* tab, which is what a headless page is.
|
|
9
|
+
*/
|
|
10
|
+
export declare function holdTrue(read: () => Promise<boolean>, { holdMs, timeout, pollMs, }: {
|
|
11
|
+
holdMs: number;
|
|
12
|
+
timeout: number;
|
|
13
|
+
pollMs: number;
|
|
14
|
+
}): Promise<boolean>;
|
package/esm/poll.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The budget each stage of the ready chain gets unless told otherwise. */
|
|
2
|
+
export const DEFAULT_TIMEOUT = 60000;
|
|
3
|
+
export const delay = (ms) => new Promise(resolve => {
|
|
4
|
+
setTimeout(resolve, ms);
|
|
5
|
+
});
|
|
6
|
+
/**
|
|
7
|
+
* Poll `read` until it has answered true for `holdMs` without a break, or
|
|
8
|
+
* `timeout` runs out. A `read` that throws ends the wait with its error.
|
|
9
|
+
* Polled from Node because Chrome throttles in-page timers in a background
|
|
10
|
+
* tab, which is what a headless page is.
|
|
11
|
+
*/
|
|
12
|
+
export async function holdTrue(read, { holdMs, timeout, pollMs, }) {
|
|
13
|
+
const deadline = Date.now() + timeout;
|
|
14
|
+
let since;
|
|
15
|
+
while (Date.now() < deadline) {
|
|
16
|
+
const ok = await read();
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
if (ok) {
|
|
19
|
+
since ??= now;
|
|
20
|
+
if (now - since >= holdMs) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
since = undefined;
|
|
26
|
+
}
|
|
27
|
+
await delay(pollMs);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
package/esm/ready.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DisplayState, SessionExpectations } from './sessionGate.ts';
|
|
2
|
+
import type { Page } from 'puppeteer';
|
|
3
|
+
export interface ReadyOptions extends SessionExpectations {
|
|
4
|
+
/** Budget for each wait stage. */
|
|
5
|
+
timeout?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Return the report instead of throwing when a stage times out or a display
|
|
8
|
+
* is not showing its data.
|
|
9
|
+
*/
|
|
10
|
+
allowUnsettled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ReadyReport {
|
|
13
|
+
/** Displays unpainted, canceled or showing an error, each with its phase. */
|
|
14
|
+
pending: DisplayState[];
|
|
15
|
+
/** Displays showing "too much data" in place of their features. Not a failure. */
|
|
16
|
+
tooLarge: DisplayState[];
|
|
17
|
+
/** What did not settle. Empty unless `allowUnsettled`. */
|
|
18
|
+
unsettled: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wait until the session holds the assembly and tracks asked for, the app has
|
|
22
|
+
* held itself ready, and every display has painted.
|
|
23
|
+
*/
|
|
24
|
+
export declare function waitForJBrowseReady(page: Page, options?: ReadyOptions): Promise<ReadyReport>;
|
|
25
|
+
/**
|
|
26
|
+
* `waitForJBrowseReady` without the session gate, for a frame that changed
|
|
27
|
+
* while the session did not, such as after a viewport resize.
|
|
28
|
+
*/
|
|
29
|
+
export declare function waitForFrame(page: Page, { timeout, allowUnsettled, }?: Omit<ReadyOptions, keyof SessionExpectations>): Promise<ReadyReport>;
|
package/esm/ready.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DEFAULT_TIMEOUT } from './poll.js';
|
|
2
|
+
import { describeDisplays, displayCensusInPage, waitForSession, } from './sessionGate.js';
|
|
3
|
+
import { appSettledBlocker } from './waits.js';
|
|
4
|
+
/**
|
|
5
|
+
* Wait until the session holds the assembly and tracks asked for, the app has
|
|
6
|
+
* held itself ready, and every display has painted.
|
|
7
|
+
*/
|
|
8
|
+
export async function waitForJBrowseReady(page, options = {}) {
|
|
9
|
+
await waitForSession(page, options);
|
|
10
|
+
return waitForFrame(page, options);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `waitForJBrowseReady` without the session gate, for a frame that changed
|
|
14
|
+
* while the session did not, such as after a viewport resize.
|
|
15
|
+
*/
|
|
16
|
+
export async function waitForFrame(page, { timeout = DEFAULT_TIMEOUT, allowUnsettled = false, } = {}) {
|
|
17
|
+
const unsettled = [];
|
|
18
|
+
const blocker = await appSettledBlocker(page, { timeout });
|
|
19
|
+
if (blocker) {
|
|
20
|
+
unsettled.push(`the app never held itself ready (last blocked by ${blocker})`);
|
|
21
|
+
}
|
|
22
|
+
const timedOut = unsettled.length > 0;
|
|
23
|
+
const { pending, tooLarge } = await page.evaluate(displayCensusInPage);
|
|
24
|
+
const failed = pending.filter(d => d.phase === 'error' || d.phase === 'renderError');
|
|
25
|
+
const canceled = pending.filter(d => d.phase === 'canceled');
|
|
26
|
+
const unpainted = pending.filter(d => !failed.includes(d) && !canceled.includes(d));
|
|
27
|
+
if (failed.length > 0) {
|
|
28
|
+
unsettled.push(`display(s) showing an error: ${describeDisplays(failed)}`);
|
|
29
|
+
}
|
|
30
|
+
if (unpainted.length > 0) {
|
|
31
|
+
unsettled.push(`display(s) never painted: ${describeDisplays(unpainted)}`);
|
|
32
|
+
}
|
|
33
|
+
if (canceled.length > 0) {
|
|
34
|
+
unsettled.push(`display(s) canceled, which lasts until Retry: ${describeDisplays(canceled)}`);
|
|
35
|
+
}
|
|
36
|
+
if (!allowUnsettled && unsettled.length > 0) {
|
|
37
|
+
const stillLoading = pending.some(d => d.phase === undefined || d.phase === 'loading');
|
|
38
|
+
const advice = timedOut || stillLoading
|
|
39
|
+
? 'Raise the timeout, or pass'
|
|
40
|
+
: 'No timeout changes that; pass';
|
|
41
|
+
throw new Error(`${timedOut ? `gave up waiting after ${timeout}ms: ` : ''}${unsettled.join('; ')}. ${advice} allowUnsettled (--allowUnsettled) to capture the frame as it stands.`);
|
|
42
|
+
}
|
|
43
|
+
return { pending, tooLarge, unsettled };
|
|
44
|
+
}
|
package/esm/session.d.ts
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decoded `session=` value that opens a session spec. Hand it to
|
|
3
|
+
* `URLSearchParams`, which encodes it once itself.
|
|
4
|
+
*/
|
|
1
5
|
export declare function sessionSpecParam(session: object): string;
|
|
6
|
+
/**
|
|
7
|
+
* The `session=` value pre-encoded, for a query string built by hand. Passed
|
|
8
|
+
* through `URLSearchParams` as well, it is encoded twice and the app fails to
|
|
9
|
+
* parse it.
|
|
10
|
+
*/
|
|
2
11
|
export declare function encodeSessionSpec(session: object): string;
|
|
3
12
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
* The `session=` value that opens a session saved with "Export session...".
|
|
14
|
+
* Takes the exported `{ session: {...} }` document or the snapshot inside it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function savedSessionParam(session: object): string;
|
|
17
|
+
/** The snapshot inside an exported session document, or the snapshot itself. */
|
|
18
|
+
export declare function savedSnapshot(session: object): object;
|
|
19
|
+
/**
|
|
20
|
+
* The assembly the first view of a session spec or saved snapshot opens, if
|
|
21
|
+
* it names one.
|
|
10
22
|
*/
|
|
11
23
|
export declare function assemblyFromSession(session: object): string | undefined;
|
|
12
24
|
/**
|
|
13
|
-
* Every trackId a session spec
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Used as the session gate's expectation: it is what turns "the browser loaded"
|
|
17
|
-
* into "the tracks I named are open", and a spec is the one input where the
|
|
18
|
-
* caller has not spelled those ids out separately.
|
|
25
|
+
* Every trackId a session spec or saved snapshot opens, nested views
|
|
26
|
+
* included.
|
|
19
27
|
*/
|
|
20
28
|
export declare function trackIdsFromSession(session: object): string[];
|
|
29
|
+
/**
|
|
30
|
+
* `?config=…&session=…&sessionName=…`, for a caller that has an origin but no
|
|
31
|
+
* JBrowse instance URL to hand `jbrowseUrl`.
|
|
32
|
+
*/
|
|
21
33
|
export declare function sessionSpecQuery({ config, session, sessionName, }: {
|
|
22
34
|
config: string;
|
|
23
35
|
session: object;
|
package/esm/session.js
CHANGED
|
@@ -1,31 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// encoding.
|
|
1
|
+
/**
|
|
2
|
+
* The decoded `session=` value that opens a session spec. Hand it to
|
|
3
|
+
* `URLSearchParams`, which encodes it once itself.
|
|
4
|
+
*/
|
|
6
5
|
export function sessionSpecParam(session) {
|
|
7
6
|
return `spec-${JSON.stringify(session)}`;
|
|
8
7
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The `session=` value pre-encoded, for a query string built by hand. Passed
|
|
10
|
+
* through `URLSearchParams` as well, it is encoded twice and the app fails to
|
|
11
|
+
* parse it.
|
|
12
|
+
*/
|
|
14
13
|
export function encodeSessionSpec(session) {
|
|
15
14
|
return encodeURIComponent(sessionSpecParam(session));
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
* The
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
* The `session=` value that opens a session saved with "Export session...".
|
|
18
|
+
* Takes the exported `{ session: {...} }` document or the snapshot inside it.
|
|
19
|
+
*/
|
|
20
|
+
export function savedSessionParam(session) {
|
|
21
|
+
return `json-${JSON.stringify({ session: savedSnapshot(session) })}`;
|
|
22
|
+
}
|
|
23
|
+
/** The snapshot inside an exported session document, or the snapshot itself. */
|
|
24
|
+
export function savedSnapshot(session) {
|
|
25
|
+
const inner = session.session;
|
|
26
|
+
return typeof inner === 'object' && inner !== null ? inner : session;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The assembly the first view of a session spec or saved snapshot opens, if
|
|
30
|
+
* it names one.
|
|
24
31
|
*/
|
|
25
32
|
export function assemblyFromSession(session) {
|
|
26
33
|
const find = (views) => {
|
|
27
34
|
for (const view of views ?? []) {
|
|
28
|
-
const found = view.assembly ??
|
|
35
|
+
const found = [view.assembly ?? []].flat()[0] ??
|
|
36
|
+
view.displayedRegions?.[0]?.assemblyName ??
|
|
37
|
+
view.assemblyNames?.[0] ??
|
|
38
|
+
find(view.views);
|
|
29
39
|
if (found) {
|
|
30
40
|
return found;
|
|
31
41
|
}
|
|
@@ -35,27 +45,32 @@ export function assemblyFromSession(session) {
|
|
|
35
45
|
return find(session.views);
|
|
36
46
|
}
|
|
37
47
|
/**
|
|
38
|
-
* Every trackId a session spec
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Used as the session gate's expectation: it is what turns "the browser loaded"
|
|
42
|
-
* into "the tracks I named are open", and a spec is the one input where the
|
|
43
|
-
* caller has not spelled those ids out separately.
|
|
48
|
+
* Every trackId a session spec or saved snapshot opens, nested views
|
|
49
|
+
* included.
|
|
44
50
|
*/
|
|
45
51
|
export function trackIdsFromSession(session) {
|
|
52
|
+
const idOf = (t) => typeof t === 'string'
|
|
53
|
+
? t
|
|
54
|
+
: (t.trackId ??
|
|
55
|
+
(typeof t.configuration === 'string'
|
|
56
|
+
? t.configuration
|
|
57
|
+
: t.configuration?.trackId));
|
|
58
|
+
const idsOf = (tracks = []) => tracks.map(idOf).filter(id => id !== undefined);
|
|
46
59
|
const collect = (views) => (views ?? []).flatMap(view => [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// drops out here instead of gating on undefined
|
|
50
|
-
...(view.tracks ?? [])
|
|
51
|
-
.map(t => (typeof t === 'string' ? t : t.trackId))
|
|
52
|
-
.filter(id => id !== undefined),
|
|
60
|
+
...idsOf(view.tracks?.flat()),
|
|
61
|
+
...(view.levels ?? []).flatMap(l => idsOf(l.tracks)),
|
|
53
62
|
...collect(view.views),
|
|
54
63
|
]);
|
|
55
64
|
return collect(session.views);
|
|
56
65
|
}
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
/**
|
|
67
|
+
* `?config=…&session=…&sessionName=…`, for a caller that has an origin but no
|
|
68
|
+
* JBrowse instance URL to hand `jbrowseUrl`.
|
|
69
|
+
*/
|
|
59
70
|
export function sessionSpecQuery({ config, session, sessionName = 'Screenshot', }) {
|
|
60
|
-
return
|
|
71
|
+
return `?${new URLSearchParams({
|
|
72
|
+
config,
|
|
73
|
+
session: sessionSpecParam(session),
|
|
74
|
+
sessionName,
|
|
75
|
+
})}`;
|
|
61
76
|
}
|