@jbrowse/plugin-linear-genome-view 5.0.0-beta.1 → 5.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/LaunchLinearGenomeView/index.js +2 -13
- package/esm/LinearGenomeView/afterAttach.js +16 -18
- package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
- package/esm/LinearGenomeView/components/PaddingBlocks.js +11 -1
- package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +1 -1
- package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
- package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
- package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
- package/esm/LinearGenomeView/components/searchModes.js +1 -1
- package/esm/LinearGenomeView/index.d.ts +1 -0
- package/esm/LinearGenomeView/index.js +8 -1
- package/esm/LinearGenomeView/initKeys.d.ts +0 -1
- package/esm/LinearGenomeView/initKeys.js +9 -45
- package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
- package/esm/LinearGenomeView/launchKeys.js +23 -0
- package/esm/LinearGenomeView/lazyDialogs.d.ts +1 -1
- package/esm/LinearGenomeView/linkedViewSync.d.ts +1 -1
- package/esm/LinearGenomeView/linkedViewSync.js +6 -0
- package/esm/LinearGenomeView/model.d.ts +1734 -22
- package/esm/LinearGenomeView/model.js +121 -57
- package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +2 -2
- package/esm/LinearGenomeView/types.d.ts +1 -1
- package/esm/displayKitTests/perRegionTestEnv.d.ts +51 -28
- package/esm/displayKitTests/perRegionTestEnv.js +0 -3
- package/esm/index.d.ts +14 -2
- package/esm/index.js +1 -2
- package/esm/searchUtils.d.ts +6 -3
- package/esm/searchUtils.js +37 -11
- package/package.json +8 -8
- package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
- package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import { linearGenomeViewPropKeys, partitionLaunchKeys, warnUnknownLaunchKeys, } from '../LinearGenomeView/initKeys.js';
|
|
2
1
|
export default function LaunchLinearGenomeViewF(pluginManager) {
|
|
3
2
|
/** #extensionPoint LaunchView-LinearGenomeView | async | Programmatically launch a linear genome view */
|
|
4
3
|
pluginManager.addToExtensionPoint('LaunchView-LinearGenomeView', args => {
|
|
5
|
-
const { session,
|
|
4
|
+
const { session, ...spec } = args;
|
|
6
5
|
if (!spec.assembly) {
|
|
7
6
|
throw new Error('No assembly provided when launching linear genome view');
|
|
8
7
|
}
|
|
9
|
-
|
|
10
|
-
// discards; plain props go straight onto the view snapshot, where MST
|
|
11
|
-
// restores them natively (and, except for the purely localStorage-backed
|
|
12
|
-
// showCenterLine, persist on save). Anything in neither set is a typo — MST
|
|
13
|
-
// drops unknown snapshot keys and `init` is a frozen blob, so nothing
|
|
14
|
-
// downstream would notice.
|
|
15
|
-
const { init, viewProps, unknown } = partitionLaunchKeys(spec, linearGenomeViewPropKeys(pluginManager));
|
|
16
|
-
warnUnknownLaunchKeys('LaunchView-LinearGenomeView', unknown);
|
|
17
|
-
// A provided id is passed top-level so MST's optional identifier honors it
|
|
18
|
-
// (undefined falls back to an auto-generated id).
|
|
19
|
-
session.addView('LinearGenomeView', { id, ...viewProps, init });
|
|
8
|
+
session.addView('LinearGenomeView', spec);
|
|
20
9
|
return args;
|
|
21
10
|
});
|
|
22
11
|
}
|
|
@@ -2,18 +2,9 @@ import { getSession, isSessionModelWithWidgets, localStorageSetItem, resolveName
|
|
|
2
2
|
import { coerceHighlight } from '@jbrowse/core/util/highlights';
|
|
3
3
|
import { installInitAutorun } from '@jbrowse/core/util/installInitAutorun';
|
|
4
4
|
import { normalizeTrackInit } from '@jbrowse/core/util/tracks';
|
|
5
|
-
import { addDisposer
|
|
5
|
+
import { addDisposer } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
import { autorun, when } from 'mobx';
|
|
7
7
|
import { SearchResultsNotFoundError } from '../searchUtils.js';
|
|
8
|
-
import { partitionLaunchKeys, warnUnknownLaunchKeys } from './initKeys.js';
|
|
9
|
-
function warnInitKeyProblems(self, init) {
|
|
10
|
-
const { viewProps, unknown } = partitionLaunchKeys(init, new Set(Object.keys(getPropertyMembers(self).properties)));
|
|
11
|
-
const viewPropKeys = Object.keys(viewProps);
|
|
12
|
-
warnUnknownLaunchKeys('LinearGenomeView init', unknown);
|
|
13
|
-
if (viewPropKeys.length) {
|
|
14
|
-
console.warn(`LinearGenomeView init ignored view prop(s): ${viewPropKeys.join(', ')} — set these on the view alongside init, not inside it`);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
8
|
// `init` is a frozen blob that often comes from hand-authored JSON, where a
|
|
18
9
|
// lone entry gets written bare (`tracks: 'genes'`). A string is iterable, so
|
|
19
10
|
// looping over it directly walked its characters — one track opened per letter.
|
|
@@ -103,7 +94,11 @@ async function navigateInit(self, session, init) {
|
|
|
103
94
|
// navToLocString waits for the assembly itself, and this autorun only
|
|
104
95
|
// runs once `initialized` confirms init.assembly has loaded regions, so
|
|
105
96
|
// no explicit waitForAssembly is needed here
|
|
106
|
-
|
|
97
|
+
// a spec that listed its tracks gets those tracks; one that only named
|
|
98
|
+
// a gene gets the track the gene was found in, as the search box would
|
|
99
|
+
await self.navToLocString(init.loc, init.assembly, init.grow, {
|
|
100
|
+
showHitTrack: asArray(init.tracks).length === 0,
|
|
101
|
+
});
|
|
107
102
|
}
|
|
108
103
|
else if (init.displayedRegionNames?.length) {
|
|
109
104
|
// an explicit region list is a navigation request just like `loc`, so it
|
|
@@ -134,12 +129,13 @@ async function navigateInit(self, session, init) {
|
|
|
134
129
|
}
|
|
135
130
|
}
|
|
136
131
|
}
|
|
137
|
-
//
|
|
138
|
-
// (unresolved id, bad config, etc) as its own snackbar
|
|
139
|
-
|
|
132
|
+
// launchTrack funnels through showTrackGeneric, which surfaces any failure
|
|
133
|
+
// (unresolved id, bad config, etc) as its own snackbar, and loads the picked
|
|
134
|
+
// display's state model first when it is registered lazily
|
|
135
|
+
async function showInitTracks(self, init) {
|
|
140
136
|
for (const t of asArray(init.tracks)) {
|
|
141
137
|
const { trackId, trackSnapshot, displaySnapshot } = normalizeTrackInit(t);
|
|
142
|
-
self.
|
|
138
|
+
await self.launchTrack(trackId, trackSnapshot, displaySnapshot);
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
141
|
// backfill assemblyName on any session-authored highlights that omitted it so
|
|
@@ -182,14 +178,13 @@ export function applyInitHighlights(self, session, init) {
|
|
|
182
178
|
}
|
|
183
179
|
async function applyInit(self, init, { superseded }) {
|
|
184
180
|
const session = getSession(self);
|
|
185
|
-
warnInitKeyProblems(self, init);
|
|
186
181
|
if (init.tracklist) {
|
|
187
182
|
await openTracklist(self, session, superseded);
|
|
188
183
|
}
|
|
189
184
|
// `superseded`, not a bare isAlive: it subsumes the liveness check (the view
|
|
190
185
|
// may have been removed while the drawer or the navigation resolved, and
|
|
191
186
|
// reading or mutating a detached node throws) and also covers the case
|
|
192
|
-
// isAlive misses — a newer
|
|
187
|
+
// isAlive misses — a newer setLaunch landed mid-apply, so the rest of *this*
|
|
193
188
|
// blob is stale. Finishing it anyway appends its tracks and highlights under
|
|
194
189
|
// the one that replaced it, and addToHighlights pushes, so a re-launch of the
|
|
195
190
|
// same spec (a StrictMode remount) doubles the bands. The drain loop applies
|
|
@@ -205,7 +200,10 @@ async function applyInit(self, init, { superseded }) {
|
|
|
205
200
|
if (superseded()) {
|
|
206
201
|
return;
|
|
207
202
|
}
|
|
208
|
-
showInitTracks(self, init);
|
|
203
|
+
await showInitTracks(self, init);
|
|
204
|
+
if (superseded()) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
209
207
|
if (init.nav !== undefined) {
|
|
210
208
|
self.setHideHeader(!init.nav);
|
|
211
209
|
}
|
|
@@ -41,11 +41,21 @@ const PaddingBlocks = observer(function PaddingBlocks({ model, offset = 0, }) {
|
|
|
41
41
|
// the geometry is `model.paddingSpans` -- shared with any host drawing this
|
|
42
42
|
// itself, so a seam here and a seam in an embedder's own chrome can't be
|
|
43
43
|
// computed two different ways. This file owns only what the spans look like.
|
|
44
|
+
const spans = model.paddingSpans;
|
|
45
|
+
// Nothing to mask, so no positioned container to hold it — the common case,
|
|
46
|
+
// since a view sitting inside one contig has no seam, elision or boundary.
|
|
47
|
+
// The wrapper is not free: `ZoomTransform` reads `staticBlocks`, so an empty
|
|
48
|
+
// one still re-rendered and rewrote its transform on every gesture frame, per
|
|
49
|
+
// track. The view returns one shared array here, so this bails without even
|
|
50
|
+
// re-rendering.
|
|
51
|
+
if (spans.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
44
54
|
const kindClass = {
|
|
45
55
|
seam: classes.regionSeparator,
|
|
46
56
|
elided: classes.elided,
|
|
47
57
|
boundary: classes.boundaryBlock,
|
|
48
58
|
};
|
|
49
|
-
return (_jsx(ZoomTransform, { model: model, offset: offset, children: _jsx("div", { className: classes.absoluteFill, children:
|
|
59
|
+
return (_jsx(ZoomTransform, { model: model, offset: offset, children: _jsx("div", { className: classes.absoluteFill, children: spans.map(({ x, width, kind }, i) => (_jsx("div", { className: cx(classes.block, kindClass[kind]), style: { transform: `translateX(${x}px)`, width } }, i))) }) }));
|
|
50
60
|
});
|
|
51
61
|
export default PaddingBlocks;
|
|
@@ -15,7 +15,12 @@ const ZoomButton = observer(function ZoomButton({ model, direction, small, }) {
|
|
|
15
15
|
? coarseBpPerPx >= maxBpPerPx - EPSILON
|
|
16
16
|
: coarseBpPerPx <= minBpPerPx + EPSILON;
|
|
17
17
|
const fontSize = small ? 'small' : undefined;
|
|
18
|
-
return (_jsx(Tooltip, { title: out ? 'Zoom out 2x' : 'Zoom in 2x', children: _jsx("span", { children: _jsx(IconButton
|
|
18
|
+
return (_jsx(Tooltip, { title: out ? 'Zoom out 2x' : 'Zoom in 2x', children: _jsx("span", { children: _jsx(IconButton
|
|
19
|
+
// on the button, not left to the Tooltip: its title lands on the
|
|
20
|
+
// <span> the disabled case needs, so the button itself reached the
|
|
21
|
+
// accessibility tree unnamed and nothing driving by name could aim at
|
|
22
|
+
// it
|
|
23
|
+
, { "aria-label": out ? 'Zoom out' : 'Zoom in', "data-testid": out ? 'zoom_out' : 'zoom_in', size: small ? 'small' : undefined, disabled: disabled, onClick: () => {
|
|
19
24
|
model.zoom(out ? model.bpPerPx * 2 : model.bpPerPx / 2);
|
|
20
25
|
}, children: out ? (_jsx(ZoomOut, { fontSize: fontSize })) : (_jsx(ZoomIn, { fontSize: fontSize })) }) }) }));
|
|
21
26
|
});
|
|
@@ -13,6 +13,10 @@ const useStyles = makeStyles()({
|
|
|
13
13
|
position: 'absolute',
|
|
14
14
|
height: '100%',
|
|
15
15
|
pointerEvents: 'none',
|
|
16
|
+
// its own compositor layer: a pan then moves the layer instead of
|
|
17
|
+
// re-rasterizing the root layer's tiles, which paid for the gridline paths
|
|
18
|
+
// on every frame
|
|
19
|
+
willChange: 'transform',
|
|
16
20
|
},
|
|
17
21
|
});
|
|
18
22
|
// Shared zoom/scroll-transform wrapper for the gridline ticks and padding-block
|
|
@@ -14,7 +14,7 @@ export function addReferenceScanTrack(model, args) {
|
|
|
14
14
|
type: 'FeatureTrack',
|
|
15
15
|
adapter: args.adapter,
|
|
16
16
|
});
|
|
17
|
-
model.
|
|
17
|
+
void model.launchTrack(args.trackId);
|
|
18
18
|
}
|
|
19
19
|
else {
|
|
20
20
|
session.notify('This session does not support adding tracks', 'warning');
|
|
@@ -25,6 +25,7 @@ declare module '@jbrowse/core/PluginManager' {
|
|
|
25
25
|
}
|
|
26
26
|
export default function LinearGenomeViewF(pluginManager: PluginManager): void;
|
|
27
27
|
export { stateModelFactory } from './model.ts';
|
|
28
|
+
export { lgvLaunchKeys } from './launchKeys.ts';
|
|
28
29
|
export type { LinearGenomeViewModel, LinearGenomeViewStateModel, } from './model.ts';
|
|
29
30
|
export type { BpOffset, ExportSvgOptions, HighlightType, InitState, LinearGenomeViewLaunchProps, NavLocation, TrackLabelMode, VolatileGuide, } from './types.ts';
|
|
30
31
|
export { type SyncableViewAction, installLinkedViewSync, } from './linkedViewSync.ts';
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { lazy } from 'react';
|
|
2
2
|
import { ViewType } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
+
import { lgvLaunchKeys } from './launchKeys.js';
|
|
3
4
|
import { stateModelFactory } from './model.js';
|
|
4
5
|
export default function LinearGenomeViewF(pluginManager) {
|
|
5
6
|
pluginManager.addViewType(() => {
|
|
7
|
+
// annotated against the registry rather than inferred, which is what
|
|
8
|
+
// makes a hand-written augmentation earn what `getViewType` promises
|
|
9
|
+
// its callers — see `ViewTypeRegistry`
|
|
10
|
+
const stateModel = stateModelFactory(pluginManager);
|
|
6
11
|
return new ViewType({
|
|
7
12
|
name: 'LinearGenomeView',
|
|
8
13
|
displayName: 'Linear genome view',
|
|
9
|
-
stateModel
|
|
14
|
+
stateModel,
|
|
15
|
+
launchKeys: lgvLaunchKeys,
|
|
10
16
|
ReactComponent: lazy(() => import('./components/LinearGenomeView.js')),
|
|
11
17
|
});
|
|
12
18
|
});
|
|
13
19
|
}
|
|
14
20
|
export { stateModelFactory } from './model.js';
|
|
21
|
+
export { lgvLaunchKeys } from './launchKeys.js';
|
|
15
22
|
export { installLinkedViewSync, } from './linkedViewSync.js';
|
|
16
23
|
export { default as HighlightBand } from './components/HighlightBand.js';
|
|
17
24
|
export { default as HighlightChip } from './components/HighlightChip.js';
|
|
@@ -1,39 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
// they are whatever the state model declares, read off it by the caller and
|
|
7
|
-
// passed in as `viewPropKeys`. A hand-written eight is what left `hideHeader`,
|
|
8
|
-
// `showGridlines`, `showCytobands` and five others declared, menu-settable and
|
|
9
|
-
// silently unreachable from any spec — the same failure LinearSyntenyView's
|
|
10
|
-
// `drawLocationMarkers` had.
|
|
11
|
-
//
|
|
12
|
-
// #launchKeys LinearGenomeView — the URL parameters page renders this list
|
|
13
|
-
// rather than restating it; each of these keys has its own `¶m=` section
|
|
14
|
-
// there, which is why no description is attached here.
|
|
15
|
-
const knownInitKeyMap = {
|
|
16
|
-
loc: true,
|
|
17
|
-
grow: true,
|
|
18
|
-
assembly: true,
|
|
19
|
-
displayedRegionNames: true,
|
|
20
|
-
tracks: true,
|
|
21
|
-
tracklist: true,
|
|
22
|
-
nav: true,
|
|
23
|
-
highlight: true,
|
|
24
|
-
};
|
|
25
|
-
const knownInitKeys = new Set(Object.keys(knownInitKeyMap));
|
|
1
|
+
import { lgvLaunchKeys } from './launchKeys.js';
|
|
2
|
+
// The remaining v4 partition, still used by the synteny view to build its LGV
|
|
3
|
+
// rows. `lgvLaunchKeys` is the one declaration of what a launch key is, so the
|
|
4
|
+
// two cannot disagree while both exist.
|
|
5
|
+
const knownInitKeys = new Set(Object.keys(lgvLaunchKeys.keys));
|
|
26
6
|
// Never settable from a spec, whatever the model declares: `type` picks the
|
|
27
|
-
// view
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// and they would partition as typos — reported as unknown and dropped. They are
|
|
32
|
-
// not typos: `preProcessSnapshot` still converts them, so they belong in the
|
|
33
|
-
// snapshot bucket, and a URL or saved spec that names them goes on working.
|
|
34
|
-
//
|
|
35
|
-
// Deletable with `legacyBpPerPx` in the model, and for the same reason.
|
|
36
|
-
const LEGACY_VIEWPORT_PROPS = new Set(['bpPerPx', 'offsetPx']);
|
|
7
|
+
// view, `id` is passed top-level so MST's optional identifier honors it, and
|
|
8
|
+
// `launch` is the blob the partition fills rather than something anyone writes.
|
|
9
|
+
const RESERVED = new Set(['id', 'type', 'launch']);
|
|
10
|
+
const LEGACY_VIEWPORT_PROPS = new Set(lgvLaunchKeys.passThrough);
|
|
37
11
|
// Partition launch keys three ways, once, for every caller that needs to know
|
|
38
12
|
// which is which:
|
|
39
13
|
//
|
|
@@ -92,13 +66,3 @@ export function linearGenomeViewPropKeys(pluginManager) {
|
|
|
92
66
|
propKeyCache.set(pluginManager, keys);
|
|
93
67
|
return keys;
|
|
94
68
|
}
|
|
95
|
-
// Report the `unknown` bucket. Both callers say the same thing about the same
|
|
96
|
-
// mistake and differ only in which surface they name, so the wording lives next
|
|
97
|
-
// to the partition that produces the bucket rather than in two hand-written
|
|
98
|
-
// copies that drift.
|
|
99
|
-
export function warnUnknownLaunchKeys(surface, unknown) {
|
|
100
|
-
const keys = Object.keys(unknown);
|
|
101
|
-
if (keys.length) {
|
|
102
|
-
console.warn(`${surface} ignored unknown key(s): ${keys.join(', ')}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineLaunchKeys } from '@jbrowse/core/util/withLaunchInput';
|
|
2
|
+
// The argument is `Record<keyof InitState, LaunchKeySpec>`, so a command this
|
|
3
|
+
// view interprets and nobody registered here is a compile error rather than a
|
|
4
|
+
// key that partitions as a typo.
|
|
5
|
+
//
|
|
6
|
+
// `bpPerPx`/`offsetPx` are the viewport spelling from before it was stored as a
|
|
7
|
+
// window. They are no longer declared properties, so the partition would read
|
|
8
|
+
// them as typos; the model's own preProcessSnapshot still converts them, so a
|
|
9
|
+
// URL or saved spec naming them goes on working. Deletable with `legacyBpPerPx`.
|
|
10
|
+
//
|
|
11
|
+
// #launchKeys LinearGenomeView — the URL parameters page renders this list
|
|
12
|
+
// rather than restating it; each of these keys has its own `¶m=` section
|
|
13
|
+
// there, which is why no description is attached here.
|
|
14
|
+
export const lgvLaunchKeys = defineLaunchKeys()({
|
|
15
|
+
loc: { kind: 'launch' },
|
|
16
|
+
grow: { kind: 'launch' },
|
|
17
|
+
assembly: { kind: 'launch' },
|
|
18
|
+
displayedRegionNames: { kind: 'launch' },
|
|
19
|
+
tracklist: { kind: 'launch' },
|
|
20
|
+
nav: { kind: 'launch' },
|
|
21
|
+
tracks: { kind: 'trackEntries' },
|
|
22
|
+
highlight: { kind: 'highlightEntries' },
|
|
23
|
+
}, { passThrough: ['bpPerPx', 'offsetPx'] });
|
|
@@ -7,7 +7,7 @@ export declare const ReturnToImportFormDialog: import("react").LazyExoticCompone
|
|
|
7
7
|
export declare const SequenceSearchDialog: import("react").LazyExoticComponent<({ model, handleClose, }: {
|
|
8
8
|
model: {
|
|
9
9
|
assemblyNames: string[];
|
|
10
|
-
|
|
10
|
+
launchTrack: (trackId: string) => Promise<unknown>;
|
|
11
11
|
};
|
|
12
12
|
handleClose: () => void;
|
|
13
13
|
}) => import("react").JSX.Element>;
|
|
@@ -6,7 +6,7 @@ import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
|
6
6
|
* arrays in each parent view) means the dispatch below stays exhaustive and a
|
|
7
7
|
* typo can't silently produce a no-op sync.
|
|
8
8
|
*/
|
|
9
|
-
export type SyncableViewAction = 'horizontalScroll' | 'zoomTo' | 'showTrack' | 'toggleTrack' | 'hideTrack' | 'setTrackLabels' | 'setShowCenterLine';
|
|
9
|
+
export type SyncableViewAction = 'horizontalScroll' | 'zoomTo' | 'showTrack' | 'toggleTrack' | 'launchTrack' | 'launchToggleTrack' | 'hideTrack' | 'setTrackLabels' | 'setShowCenterLine';
|
|
10
10
|
interface LinkableViews {
|
|
11
11
|
linkViews: boolean;
|
|
12
12
|
views: LinearGenomeViewModel[];
|
|
@@ -13,6 +13,12 @@ function applyViewAction(view, name, args) {
|
|
|
13
13
|
case 'toggleTrack':
|
|
14
14
|
view.toggleTrack(args[0]);
|
|
15
15
|
break;
|
|
16
|
+
case 'launchTrack':
|
|
17
|
+
void view.launchTrack(args[0]);
|
|
18
|
+
break;
|
|
19
|
+
case 'launchToggleTrack':
|
|
20
|
+
void view.launchToggleTrack(args[0]);
|
|
21
|
+
break;
|
|
16
22
|
case 'hideTrack':
|
|
17
23
|
view.hideTrack(args[0]);
|
|
18
24
|
break;
|