jbrowse-plugin-msaview 4.0.0 → 5.0.0
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/dist/AddHighlightModel/index.js +2 -11
- package/dist/LaunchMsaView/codingFeature.d.ts +0 -12
- package/dist/LaunchMsaView/codingFeature.js +0 -30
- package/dist/LaunchMsaView/codingFeature.test.js +1 -48
- package/dist/LaunchMsaView/components/fetchSeq.d.ts +1 -1
- package/dist/LaunchMsaView/components/fetchSeq.js +3 -10
- package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +1 -1
- package/dist/LaunchMsaView/index.js +2 -12
- package/dist/LaunchMsaView/launchTarget.d.ts +7 -30
- package/dist/LaunchMsaView/launchTarget.js +10 -49
- package/dist/LaunchMsaView/launchTarget.test.js +17 -109
- package/dist/MsaViewPanel/model.d.ts +8 -8
- package/dist/MsaViewPanel/model.js +3 -10
- package/dist/MsaViewPanel/resolveConnectedTranscript.js +1 -7
- package/dist/chunks/{LaunchMsaViewDialog-54LBEZHK.js → LaunchMsaViewDialog-LMZOJ7T6.js} +2 -2
- package/dist/chunks/chunk-MHBTV7BZ.js +3 -0
- package/dist/chunks/chunk-MHBTV7BZ.js.map +7 -0
- package/dist/chunks/chunk-VOBS7IVV.js +2 -0
- package/dist/chunks/chunk-VOBS7IVV.js.map +7 -0
- package/dist/chunks/{model-KEZXRCK6.js → model-7VBTOIJX.js} +4 -4
- package/dist/chunks/model-7VBTOIJX.js.map +7 -0
- package/dist/index.js +1 -1
- package/dist/jbrowse-plugin-msaview.esm.js +2 -2
- package/dist/jbrowse-plugin-msaview.esm.js.map +3 -3
- package/dist/utils/workspaces.js +6 -25
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -2
- package/src/AddHighlightModel/index.tsx +5 -15
- package/src/LaunchMsaView/codingFeature.test.ts +1 -64
- package/src/LaunchMsaView/codingFeature.ts +0 -38
- package/src/LaunchMsaView/components/fetchSeq.ts +3 -20
- package/src/LaunchMsaView/index.ts +2 -12
- package/src/LaunchMsaView/launchTarget.test.ts +20 -133
- package/src/LaunchMsaView/launchTarget.ts +18 -70
- package/src/MsaViewPanel/model.ts +3 -10
- package/src/MsaViewPanel/resolveConnectedTranscript.ts +1 -7
- package/src/index.ts +1 -1
- package/src/utils/workspaces.ts +6 -25
- package/src/version.ts +1 -1
- package/dist/chunks/chunk-EO7URJUB.js +0 -2
- package/dist/chunks/chunk-EO7URJUB.js.map +0 -7
- package/dist/chunks/chunk-RKVHQK52.js +0 -3
- package/dist/chunks/chunk-RKVHQK52.js.map +0 -7
- package/dist/chunks/model-KEZXRCK6.js.map +0 -7
- /package/dist/chunks/{LaunchMsaViewDialog-54LBEZHK.js.map → LaunchMsaViewDialog-LMZOJ7T6.js.map} +0 -0
|
@@ -3,17 +3,8 @@ import { getSession } from '@jbrowse/core/util';
|
|
|
3
3
|
import { isMsaView } from '../MsaViewPanel/isMsaView';
|
|
4
4
|
import HighlightComponents from './HighlightComponents';
|
|
5
5
|
export default function AddHighlightComponentsModelF(pluginManager) {
|
|
6
|
-
pluginManager.
|
|
7
|
-
// @ts-expect-error v4 hosts have no contributeToExtensionPoint
|
|
8
|
-
'LinearGenomeView-TracksContainerComponent', (rest, { model }) => {
|
|
6
|
+
pluginManager.contributeToExtensionPoint('LinearGenomeView-TracksContainerComponent', ({ model }) => {
|
|
9
7
|
const { views } = getSession(model);
|
|
10
|
-
|
|
11
|
-
if (!hasMsaView) {
|
|
12
|
-
return rest;
|
|
13
|
-
}
|
|
14
|
-
return [
|
|
15
|
-
...rest,
|
|
16
|
-
React.createElement(HighlightComponents, { key: "highlight_protein_viewer_msaview", model: model }),
|
|
17
|
-
];
|
|
8
|
+
return views.some(v => isMsaView(v) && v.connectedViewId === model.id) ? (React.createElement(HighlightComponents, { key: "highlight_protein_viewer_msaview", model: model })) : undefined;
|
|
18
9
|
});
|
|
19
10
|
}
|
|
@@ -1,14 +1,2 @@
|
|
|
1
1
|
import type { Feature } from '@jbrowse/core/util';
|
|
2
|
-
export declare function isGeneLikeType(type: unknown): boolean;
|
|
3
2
|
export declare function isCodingFeature(feature: Feature): boolean;
|
|
4
|
-
/**
|
|
5
|
-
* Whether the feature is known not to code for anything.
|
|
6
|
-
*
|
|
7
|
-
* Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
|
|
8
|
-
* all says nothing either way, and a host is free to hand one over that way.
|
|
9
|
-
* Reading that as "no protein here" takes the menu item off a perfectly
|
|
10
|
-
* ordinary gene, silently, which is worse than opening a dialog that then has
|
|
11
|
-
* nothing to translate.
|
|
12
|
-
*/
|
|
13
|
-
export declare function isKnownNonCoding(feature: Feature): boolean;
|
|
14
|
-
export declare function geneLikeRoot(feature: Feature): Feature;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
// Copied from @jbrowse/core's featureTypes rather than imported: the barrel
|
|
2
|
-
// only exports it from v5, and a bundle resolving it on a v4 host reads
|
|
3
|
-
// undefined.
|
|
4
|
-
const GENE_LIKE_TYPE = /gene(_segment)?$|rna$|transcript/;
|
|
5
|
-
// `unknown` rather than `string | undefined`, because the type comes off a
|
|
6
|
-
// host's hit-test item and nothing here built it.
|
|
7
|
-
export function isGeneLikeType(type) {
|
|
8
|
-
return typeof type === 'string' && GENE_LIKE_TYPE.test(type.toLowerCase());
|
|
9
|
-
}
|
|
10
1
|
function isCDS(feature) {
|
|
11
2
|
return feature.get('type')?.toLowerCase() === 'cds';
|
|
12
3
|
}
|
|
@@ -15,24 +6,3 @@ function isCDS(feature) {
|
|
|
15
6
|
export function isCodingFeature(feature) {
|
|
16
7
|
return isCDS(feature) || !!feature.get('subfeatures')?.some(isCodingFeature);
|
|
17
8
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Whether the feature is known not to code for anything.
|
|
20
|
-
*
|
|
21
|
-
* Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
|
|
22
|
-
* all says nothing either way, and a host is free to hand one over that way.
|
|
23
|
-
* Reading that as "no protein here" takes the menu item off a perfectly
|
|
24
|
-
* ordinary gene, silently, which is worse than opening a dialog that then has
|
|
25
|
-
* nothing to translate.
|
|
26
|
-
*/
|
|
27
|
-
export function isKnownNonCoding(feature) {
|
|
28
|
-
return !!feature.get('subfeatures')?.length && !isCodingFeature(feature);
|
|
29
|
-
}
|
|
30
|
-
// The outermost gene-like ancestor, so a click on an isoform opens the dialog
|
|
31
|
-
// on the gene with every transcript to choose from, as the canvas host does.
|
|
32
|
-
export function geneLikeRoot(feature) {
|
|
33
|
-
let root = feature;
|
|
34
|
-
for (let parent = root.parent?.(); parent && isGeneLikeType(parent.get('type')); parent = parent.parent?.()) {
|
|
35
|
-
root = parent;
|
|
36
|
-
}
|
|
37
|
-
return root;
|
|
38
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleFeature } from '@jbrowse/core/util';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import { isCodingFeature } from './codingFeature';
|
|
4
4
|
function feature(type, subfeatures = []) {
|
|
5
5
|
return new SimpleFeature({
|
|
6
6
|
uniqueId: `${type}-${Math.random()}`,
|
|
@@ -11,29 +11,6 @@ function feature(type, subfeatures = []) {
|
|
|
11
11
|
subfeatures: subfeatures.map(s => s.toJSON()),
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
-
describe('isGeneLikeType', () => {
|
|
15
|
-
it('accepts the SO spellings of gene, transcript and RNA', () => {
|
|
16
|
-
for (const t of [
|
|
17
|
-
'gene',
|
|
18
|
-
'mRNA',
|
|
19
|
-
'transcript',
|
|
20
|
-
'ncRNA_gene',
|
|
21
|
-
'protein_coding_gene',
|
|
22
|
-
'V_gene_segment',
|
|
23
|
-
'primary_transcript',
|
|
24
|
-
'pseudogenic_transcript',
|
|
25
|
-
'lnc_RNA',
|
|
26
|
-
]) {
|
|
27
|
-
expect(isGeneLikeType(t)).toBe(true);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
it('rejects regions, matches and CDS', () => {
|
|
31
|
-
for (const t of ['intergenic_region', 'cDNA_match', 'CDS', 'exon']) {
|
|
32
|
-
expect(isGeneLikeType(t)).toBe(false);
|
|
33
|
-
}
|
|
34
|
-
expect(isGeneLikeType(undefined)).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
14
|
describe('isCodingFeature', () => {
|
|
38
15
|
it('finds a CDS anywhere below the feature', () => {
|
|
39
16
|
const gene = feature('gene', [
|
|
@@ -49,27 +26,3 @@ describe('isCodingFeature', () => {
|
|
|
49
26
|
expect(isCodingFeature(feature('CDS'))).toBe(true);
|
|
50
27
|
});
|
|
51
28
|
});
|
|
52
|
-
describe('isKnownNonCoding', () => {
|
|
53
|
-
it('is true for a transcript whose subfeatures are all exons', () => {
|
|
54
|
-
expect(isKnownNonCoding(feature('lnc_RNA', [feature('exon'), feature('exon')]))).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
// a host is free to hand over a bare record, and reading that as "no protein
|
|
57
|
-
// here" takes the menu item off an ordinary gene without a word
|
|
58
|
-
it('is false for a feature that arrived with no subfeatures at all', () => {
|
|
59
|
-
expect(isKnownNonCoding(feature('gene'))).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
it('is false when a CDS is somewhere below', () => {
|
|
62
|
-
expect(isKnownNonCoding(feature('gene', [feature('mRNA', [feature('CDS')])]))).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
describe('geneLikeRoot', () => {
|
|
66
|
-
it('climbs from an isoform to its gene', () => {
|
|
67
|
-
const gene = feature('gene', [feature('mRNA', [feature('CDS')])]);
|
|
68
|
-
const mrna = gene.get('subfeatures')[0];
|
|
69
|
-
expect(geneLikeRoot(mrna)).toBe(gene);
|
|
70
|
-
});
|
|
71
|
-
it('stays put with no gene-like parent', () => {
|
|
72
|
-
const mrna = feature('mRNA', [feature('CDS')]);
|
|
73
|
-
expect(geneLikeRoot(mrna)).toBe(mrna);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -5,11 +5,8 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
|
|
|
5
5
|
if (!assembly) {
|
|
6
6
|
throw new Error('assembly not found');
|
|
7
7
|
}
|
|
8
|
-
const
|
|
9
|
-
// a named object keeps sessionId, which v4 hosts read from the args
|
|
10
|
-
const args = {
|
|
8
|
+
const feats = await rpcManager.call('getSequence', 'CoreGetFeatures', {
|
|
11
9
|
adapterConfig: getConf(assembly, ['sequence', 'adapter']),
|
|
12
|
-
sessionId,
|
|
13
10
|
regions: [
|
|
14
11
|
{
|
|
15
12
|
start,
|
|
@@ -18,15 +15,11 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
|
|
|
18
15
|
assemblyName,
|
|
19
16
|
},
|
|
20
17
|
],
|
|
21
|
-
};
|
|
22
|
-
const feats = await rpcManager.call(sessionId, 'CoreGetFeatures', args);
|
|
18
|
+
});
|
|
23
19
|
return {
|
|
24
20
|
seq: feats[0]?.get('seq') ?? '',
|
|
25
21
|
// travels with the sequence because the caller needs both to translate, and
|
|
26
22
|
// resolving the assembly twice to get them is the shape that lost it
|
|
27
|
-
assemblyGeneticCodeId:
|
|
23
|
+
assemblyGeneticCodeId: assembly.getGeneticCodeId(refName),
|
|
28
24
|
};
|
|
29
25
|
}
|
|
30
|
-
function geneticCodeId(assembly, refName) {
|
|
31
|
-
return assembly.getGeneticCodeId?.(refName);
|
|
32
|
-
}
|
|
@@ -24,10 +24,6 @@ function openDialog(self, target) {
|
|
|
24
24
|
{ model: track, handleClose, feature, preferredTranscriptId },
|
|
25
25
|
]);
|
|
26
26
|
};
|
|
27
|
-
if ('feature' in target) {
|
|
28
|
-
open(target.feature);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
27
|
target
|
|
32
28
|
.fetchFeature()
|
|
33
29
|
.then(feature => {
|
|
@@ -55,14 +51,8 @@ export function extendStateModel(stateModel) {
|
|
|
55
51
|
contextMenuItems() {
|
|
56
52
|
const target = launchTarget(self);
|
|
57
53
|
return [
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// captured super is invoked detached. It throws, the ErrorBoundary the
|
|
61
|
-
// menu builds inside swallows it, and the user right-clicks a feature
|
|
62
|
-
// and gets no menu at all -- the host's own rows gone too, which is
|
|
63
|
-
// worse than this plugin contributing nothing. jbrowse-components hit
|
|
64
|
-
// exactly this with `this.isGeneLike` and fixed its side in
|
|
65
|
-
// 104bbfc581, but a plugin cannot choose which host build it runs on.
|
|
54
|
+
// a detached call leaves `this` undefined for a host method that
|
|
55
|
+
// reads it, and the throw takes the host's own rows with it
|
|
66
56
|
...superContextMenuItems.call(self),
|
|
67
57
|
...(target
|
|
68
58
|
? [
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isGeneLikeType } from './codingFeature';
|
|
2
1
|
import type { MenuItem } from '@jbrowse/core/ui';
|
|
3
2
|
import type { Feature } from '@jbrowse/core/util';
|
|
4
3
|
export interface ContextMenuInfo {
|
|
@@ -14,43 +13,21 @@ export interface ContextMenuInfo {
|
|
|
14
13
|
export interface DisplayModel {
|
|
15
14
|
contextMenuItems: () => MenuItem[];
|
|
16
15
|
contextMenuInfo?: ContextMenuInfo;
|
|
17
|
-
fetchFullFeature
|
|
18
|
-
contextMenuFeature?: Feature;
|
|
16
|
+
fetchFullFeature: (featureId: string, displayedRegionIndex: number) => Promise<Feature | undefined>;
|
|
19
17
|
}
|
|
20
18
|
/**
|
|
21
|
-
* What the menu item launches on.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* answered after the fetch, and the caller answers it there.
|
|
19
|
+
* What the menu item launches on. The hit test carries a type and an id, so
|
|
20
|
+
* whether the feature codes for anything can only be answered after the fetch,
|
|
21
|
+
* and the caller answers it there.
|
|
25
22
|
*/
|
|
26
|
-
interface
|
|
23
|
+
export interface MenuTarget {
|
|
24
|
+
fetchFeature: () => Promise<Feature | undefined>;
|
|
27
25
|
/**
|
|
28
26
|
* The isoform the click actually landed on, when the dialog opens on its
|
|
29
27
|
* gene. Climbing to the gene is what puts every isoform in the picker, and it
|
|
30
28
|
* threw away which one the user pointed at: the picker then opened on the
|
|
31
|
-
* longest transcript
|
|
32
|
-
* ever stated.
|
|
29
|
+
* longest transcript.
|
|
33
30
|
*/
|
|
34
31
|
preferredTranscriptId?: string;
|
|
35
32
|
}
|
|
36
|
-
export type MenuTarget = (ClickedTranscript & {
|
|
37
|
-
feature: Feature;
|
|
38
|
-
}) | (ClickedTranscript & {
|
|
39
|
-
fetchFeature: () => Promise<Feature | undefined>;
|
|
40
|
-
});
|
|
41
|
-
export { isGeneLikeType };
|
|
42
|
-
/**
|
|
43
|
-
* How to get the right-clicked feature, or nothing when there is nothing to
|
|
44
|
-
* launch on. Both host shapes reduce to one target, so the menu item is built
|
|
45
|
-
* and the dialog is opened by one code path — and the same gene test decides
|
|
46
|
-
* both. The strict three-name set the legacy branch used to carry disagreed
|
|
47
|
-
* with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
|
|
48
|
-
* and not on a 3.7 one.
|
|
49
|
-
*
|
|
50
|
-
* Gene-like is not enough on its own: an lncRNA has no protein to align, and
|
|
51
|
-
* accepting it opened a dialog whose Submit never left grey, with nothing
|
|
52
|
-
* saying why. Where the whole feature is in hand the CDS decides here -- but
|
|
53
|
-
* only where there are subfeatures to read it off, since a feature that came
|
|
54
|
-
* with none has not answered the question.
|
|
55
|
-
*/
|
|
56
33
|
export declare function launchTarget(self: DisplayModel): MenuTarget | undefined;
|
|
@@ -1,53 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
// Read off the clicked item rather than off the display
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `contextMenuItems`, and every host built after it returns `undefined` here --
|
|
7
|
-
// so the gate was never satisfied, `onClick` stayed undefined, and the item
|
|
8
|
-
// silently left the right-click menu on every gene track. Nothing failed loudly:
|
|
9
|
-
// the display still had contextMenuInfo and fetchFullFeature, and the menu still
|
|
10
|
-
// opened with its own items in it.
|
|
11
|
-
//
|
|
12
|
-
// A predicate over the type we were already given cannot go the same way, and it
|
|
13
|
-
// costs one comparison. Deliberately the same loose case-insensitive test the
|
|
14
|
-
// host applies (`isGeneLikeType` in collapseIntronsMenu.ts): real GFFs carry
|
|
15
|
-
// 'mRNA', 'lnc_RNA', 'protein_coding_gene', 'transcript'.
|
|
16
|
-
export { isGeneLikeType };
|
|
17
|
-
/**
|
|
18
|
-
* How to get the right-clicked feature, or nothing when there is nothing to
|
|
19
|
-
* launch on. Both host shapes reduce to one target, so the menu item is built
|
|
20
|
-
* and the dialog is opened by one code path — and the same gene test decides
|
|
21
|
-
* both. The strict three-name set the legacy branch used to carry disagreed
|
|
22
|
-
* with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
|
|
23
|
-
* and not on a 3.7 one.
|
|
24
|
-
*
|
|
25
|
-
* Gene-like is not enough on its own: an lncRNA has no protein to align, and
|
|
26
|
-
* accepting it opened a dialog whose Submit never left grey, with nothing
|
|
27
|
-
* saying why. Where the whole feature is in hand the CDS decides here -- but
|
|
28
|
-
* only where there are subfeatures to read it off, since a feature that came
|
|
29
|
-
* with none has not answered the question.
|
|
30
|
-
*/
|
|
1
|
+
import { isGeneLikeType } from '@jbrowse/core/util';
|
|
2
|
+
// Read off the clicked item rather than off the display: LinearBasicDisplay's
|
|
3
|
+
// `isGeneLike` getter was inlined into its own `contextMenuItems`
|
|
4
|
+
// (jbrowse-components 684142b3), and gating on it silently took the item off
|
|
5
|
+
// every gene track.
|
|
31
6
|
export function launchTarget(self) {
|
|
32
7
|
const info = self.contextMenuInfo;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return isGeneLikeType(info.item.type)
|
|
39
|
-
? {
|
|
40
|
-
fetchFeature: () => fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
|
|
41
|
-
preferredTranscriptId: info.subfeature?.featureId,
|
|
42
|
-
}
|
|
43
|
-
: undefined;
|
|
44
|
-
}
|
|
45
|
-
const legacy = self.contextMenuFeature;
|
|
46
|
-
if (!legacy) {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
const root = geneLikeRoot(legacy);
|
|
50
|
-
return isGeneLikeType(root.get('type')) && !isKnownNonCoding(root)
|
|
51
|
-
? { feature: root, preferredTranscriptId: legacy.id() }
|
|
8
|
+
return info && isGeneLikeType(info.item.type)
|
|
9
|
+
? {
|
|
10
|
+
fetchFeature: () => self.fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
|
|
11
|
+
preferredTranscriptId: info.subfeature?.featureId,
|
|
12
|
+
}
|
|
52
13
|
: undefined;
|
|
53
14
|
}
|
|
@@ -1,132 +1,40 @@
|
|
|
1
1
|
import { SimpleFeature } from '@jbrowse/core/util';
|
|
2
2
|
import { describe, expect, test } from 'vitest';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { launchTarget } from './launchTarget';
|
|
4
|
+
const host = (type) => ({
|
|
5
|
+
contextMenuItems: () => [],
|
|
6
|
+
contextMenuInfo: { item: { featureId: 'f1', type }, displayedRegionIndex: 0 },
|
|
7
|
+
fetchFullFeature: (featureId) => Promise.resolve(new SimpleFeature({
|
|
8
|
+
uniqueId: featureId,
|
|
7
9
|
refName: 'chr1',
|
|
8
10
|
start: 0,
|
|
9
11
|
end: 100,
|
|
10
|
-
type
|
|
11
|
-
|
|
12
|
-
uniqueId: `${type}-${i}`,
|
|
13
|
-
refName: 'chr1',
|
|
14
|
-
start: 0,
|
|
15
|
-
end: 100,
|
|
16
|
-
type: t,
|
|
17
|
-
})),
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
const modernHost = (type) => ({
|
|
21
|
-
contextMenuItems: () => [],
|
|
22
|
-
contextMenuInfo: { item: { featureId: 'f1', type }, displayedRegionIndex: 0 },
|
|
23
|
-
fetchFullFeature: (featureId) => Promise.resolve(feature(`fetched:${featureId}`)),
|
|
24
|
-
});
|
|
25
|
-
const legacyHost = (feat) => ({
|
|
26
|
-
contextMenuItems: () => [],
|
|
27
|
-
contextMenuFeature: feat,
|
|
28
|
-
});
|
|
29
|
-
async function fetched(target) {
|
|
30
|
-
return target && 'fetchFeature' in target
|
|
31
|
-
? await target.fetchFeature()
|
|
32
|
-
: undefined;
|
|
33
|
-
}
|
|
34
|
-
describe('isGeneLikeType', () => {
|
|
35
|
-
test.each(['gene', 'mRNA', 'transcript', 'lnc_RNA', 'protein_coding_gene'])('accepts %s', type => {
|
|
36
|
-
expect(isGeneLikeType(type)).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
test.each(['CDS', 'exon', 'match', 'SNV', undefined, null, 42])('rejects %s', type => {
|
|
39
|
-
expect(isGeneLikeType(type)).toBe(false);
|
|
40
|
-
});
|
|
12
|
+
type: `fetched:${featureId}`,
|
|
13
|
+
})),
|
|
41
14
|
});
|
|
42
15
|
describe('launchTarget', () => {
|
|
43
16
|
test('resolves the clicked feature through fetchFullFeature', async () => {
|
|
44
|
-
const
|
|
45
|
-
expect(
|
|
46
|
-
expect((await fetched(target))?.get('type')).toBe('fetched:f1');
|
|
17
|
+
const feature = await launchTarget(host('mRNA'))?.fetchFeature();
|
|
18
|
+
expect(feature?.get('type')).toBe('fetched:f1');
|
|
47
19
|
});
|
|
48
20
|
test('offers nothing for a non-gene click', () => {
|
|
49
|
-
expect(launchTarget(
|
|
50
|
-
expect(launchTarget(
|
|
21
|
+
expect(launchTarget(host('CDS'))).toBeUndefined();
|
|
22
|
+
expect(launchTarget(host(undefined))).toBeUndefined();
|
|
51
23
|
});
|
|
52
24
|
test('offers nothing when nothing was clicked', () => {
|
|
53
|
-
expect(launchTarget({
|
|
54
|
-
});
|
|
55
|
-
// v4.3.0 and earlier have contextMenuFeature and nothing else; dropping this
|
|
56
|
-
// fallback once took "Launch MSA view" off every host in the wild.
|
|
57
|
-
test('falls back to a synchronous contextMenuFeature', () => {
|
|
58
|
-
const target = launchTarget(legacyHost(feature('mRNA')));
|
|
59
|
-
expect(target && 'feature' in target && target.feature.get('type')).toBe('mRNA');
|
|
60
|
-
});
|
|
61
|
-
test('applies the same gene test on both host shapes', () => {
|
|
62
|
-
expect(launchTarget(legacyHost(feature('lnc_RNA')))).toBeDefined();
|
|
63
|
-
expect(launchTarget(legacyHost(feature('exon')))).toBeUndefined();
|
|
64
|
-
});
|
|
65
|
-
// gene-like is not enough: an lncRNA translates to nothing, and offering the
|
|
66
|
-
// item opened a dialog whose Submit never left grey
|
|
67
|
-
test('declines a gene-like feature with no CDS below it', () => {
|
|
68
|
-
expect(launchTarget(legacyHost(feature('lnc_RNA', ['exon'])))).toBeUndefined();
|
|
69
|
-
});
|
|
70
|
-
// a legacy host can hand over a bare record whose children are a separate
|
|
71
|
-
// query, and declining it takes the item off an ordinary gene with no word
|
|
72
|
-
test('offers the item for a gene that arrived with no subfeatures', () => {
|
|
73
|
-
expect(launchTarget(legacyHost(feature('gene', [])))).toBeDefined();
|
|
74
|
-
});
|
|
75
|
-
// a click on an isoform should open the dialog on the gene, so every
|
|
76
|
-
// transcript is there to pick from
|
|
77
|
-
test('climbs to the gene from a clicked isoform', () => {
|
|
78
|
-
const gene = new SimpleFeature({
|
|
79
|
-
uniqueId: 'gene',
|
|
80
|
-
refName: 'chr1',
|
|
81
|
-
start: 0,
|
|
82
|
-
end: 100,
|
|
83
|
-
type: 'gene',
|
|
84
|
-
subfeatures: [
|
|
85
|
-
{
|
|
86
|
-
uniqueId: 'mrna',
|
|
87
|
-
refName: 'chr1',
|
|
88
|
-
start: 0,
|
|
89
|
-
end: 100,
|
|
90
|
-
type: 'mRNA',
|
|
91
|
-
subfeatures: [
|
|
92
|
-
{
|
|
93
|
-
uniqueId: 'cds',
|
|
94
|
-
refName: 'chr1',
|
|
95
|
-
start: 0,
|
|
96
|
-
end: 100,
|
|
97
|
-
type: 'CDS',
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
});
|
|
103
|
-
const mrna = gene.get('subfeatures')[0];
|
|
104
|
-
const target = launchTarget(legacyHost(mrna));
|
|
105
|
-
expect(target && 'feature' in target && target.feature).toBe(gene);
|
|
106
|
-
// the gene is what the dialog opens on, but the picker still opens on the
|
|
107
|
-
// isoform the click landed on -- on a v4 host that click is the only place
|
|
108
|
-
// the choice is ever stated
|
|
109
|
-
expect(target?.preferredTranscriptId).toBe(mrna.id());
|
|
25
|
+
expect(launchTarget({ ...host('mRNA'), contextMenuInfo: undefined })).toBeUndefined();
|
|
110
26
|
});
|
|
111
27
|
// the canvas host names the clicked isoform beside the gene it fetches, as
|
|
112
28
|
// its own Collapse introns dialog reads it
|
|
113
|
-
test('carries the isoform a
|
|
114
|
-
const
|
|
29
|
+
test('carries the isoform a click landed on', () => {
|
|
30
|
+
const h = host('gene');
|
|
115
31
|
const target = launchTarget({
|
|
116
|
-
...
|
|
32
|
+
...h,
|
|
117
33
|
contextMenuInfo: {
|
|
118
|
-
...
|
|
34
|
+
...h.contextMenuInfo,
|
|
119
35
|
subfeature: { featureId: 'mrna-2' },
|
|
120
36
|
},
|
|
121
37
|
});
|
|
122
38
|
expect(target?.preferredTranscriptId).toBe('mrna-2');
|
|
123
39
|
});
|
|
124
|
-
// a host that has both shapes must not fall through to the legacy branch and
|
|
125
|
-
// launch on a stale feature when the click was not on a gene
|
|
126
|
-
test('a non-gene click on a host carrying both shapes offers nothing', () => {
|
|
127
|
-
expect(launchTarget({
|
|
128
|
-
...modernHost('CDS'),
|
|
129
|
-
...legacyHost(feature('mRNA')),
|
|
130
|
-
})).toBeUndefined();
|
|
131
|
-
});
|
|
132
40
|
});
|
|
@@ -498,16 +498,16 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
498
498
|
clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Clade, import("react-msaview").Clade, import("react-msaview").Clade>>, [undefined]>;
|
|
499
499
|
encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Encoding, import("react-msaview").Encoding, import("react-msaview").Encoding>>, [undefined]>;
|
|
500
500
|
rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec>>, [undefined]>;
|
|
501
|
-
}, "init" | "querySeqName" | "
|
|
501
|
+
}, "init" | "querySeqName" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion"> & {
|
|
502
502
|
connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
503
503
|
connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
|
|
504
504
|
connectedTranscript: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
505
505
|
blastParams: import("@jbrowse/mobx-state-tree").IType<BlastParams | undefined, BlastParams | undefined, BlastParams | undefined>;
|
|
506
506
|
orthologParams: import("@jbrowse/mobx-state-tree").IType<OrthologParams | undefined, OrthologParams | undefined, OrthologParams | undefined>;
|
|
507
|
-
querySeqName: import("@jbrowse/mobx-state-tree").
|
|
508
|
-
querySeqOffset: import("@jbrowse/mobx-state-tree").
|
|
507
|
+
querySeqName: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
508
|
+
querySeqOffset: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
|
|
509
509
|
uniprotId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
510
|
-
zoomToBaseLevel: import("@jbrowse/mobx-state-tree").
|
|
510
|
+
zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
|
|
511
511
|
init: import("@jbrowse/mobx-state-tree").IType<MsaViewInitState | undefined, MsaViewInitState | undefined, MsaViewInitState | undefined>;
|
|
512
512
|
dataStoreId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
513
513
|
launchCompleted: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
@@ -1509,16 +1509,16 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
1509
1509
|
clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Clade, import("react-msaview").Clade, import("react-msaview").Clade>>, [undefined]>;
|
|
1510
1510
|
encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Encoding, import("react-msaview").Encoding, import("react-msaview").Encoding>>, [undefined]>;
|
|
1511
1511
|
rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec>>, [undefined]>;
|
|
1512
|
-
}, "init" | "querySeqName" | "
|
|
1512
|
+
}, "init" | "querySeqName" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion"> & {
|
|
1513
1513
|
connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
1514
1514
|
connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
|
|
1515
1515
|
connectedTranscript: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
1516
1516
|
blastParams: import("@jbrowse/mobx-state-tree").IType<BlastParams | undefined, BlastParams | undefined, BlastParams | undefined>;
|
|
1517
1517
|
orthologParams: import("@jbrowse/mobx-state-tree").IType<OrthologParams | undefined, OrthologParams | undefined, OrthologParams | undefined>;
|
|
1518
|
-
querySeqName: import("@jbrowse/mobx-state-tree").
|
|
1519
|
-
querySeqOffset: import("@jbrowse/mobx-state-tree").
|
|
1518
|
+
querySeqName: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
1519
|
+
querySeqOffset: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
|
|
1520
1520
|
uniprotId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
1521
|
-
zoomToBaseLevel: import("@jbrowse/mobx-state-tree").
|
|
1521
|
+
zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
|
|
1522
1522
|
init: import("@jbrowse/mobx-state-tree").IType<MsaViewInitState | undefined, MsaViewInitState | undefined, MsaViewInitState | undefined>;
|
|
1523
1523
|
dataStoreId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
1524
1524
|
launchCompleted: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
@@ -51,12 +51,7 @@ export default function stateModelFactory() {
|
|
|
51
51
|
/**
|
|
52
52
|
* #property
|
|
53
53
|
*/
|
|
54
|
-
|
|
55
|
-
// mobx-state-tree every released core exposes (present only on main), and
|
|
56
|
-
// the missing function throws while this model is being built, which
|
|
57
|
-
// error-pages the whole app rather than just this view. Restore
|
|
58
|
-
// stripDefault once a release ships it.
|
|
59
|
-
querySeqName: 'QUERY',
|
|
54
|
+
querySeqName: types.stripDefault(types.string, 'QUERY'),
|
|
60
55
|
/**
|
|
61
56
|
* #property
|
|
62
57
|
* transcript residues before the query row's first residue, so a
|
|
@@ -66,8 +61,7 @@ export default function stateModelFactory() {
|
|
|
66
61
|
* transcript's first residue put every click off by the trimmed
|
|
67
62
|
* amount. Negative when the row runs past the transcript's start.
|
|
68
63
|
*/
|
|
69
|
-
|
|
70
|
-
querySeqOffset: 0,
|
|
64
|
+
querySeqOffset: types.stripDefault(types.number, 0),
|
|
71
65
|
/**
|
|
72
66
|
* #property
|
|
73
67
|
*/
|
|
@@ -75,8 +69,7 @@ export default function stateModelFactory() {
|
|
|
75
69
|
/**
|
|
76
70
|
* #property
|
|
77
71
|
*/
|
|
78
|
-
|
|
79
|
-
zoomToBaseLevel: false,
|
|
72
|
+
zoomToBaseLevel: types.stripDefault(types.boolean, false),
|
|
80
73
|
/**
|
|
81
74
|
* #property
|
|
82
75
|
*/
|
|
@@ -50,13 +50,7 @@ async function findTranscript(self, transcriptId) {
|
|
|
50
50
|
const tracks = transcriptTracks(view.tracks);
|
|
51
51
|
for (const regions of searchWindows(view)) {
|
|
52
52
|
for (const track of tracks) {
|
|
53
|
-
|
|
54
|
-
const args = {
|
|
55
|
-
adapterConfig: getConf(track, 'adapter'),
|
|
56
|
-
sessionId,
|
|
57
|
-
regions,
|
|
58
|
-
};
|
|
59
|
-
const feats = await session.rpcManager.call(sessionId, 'CoreGetFeatures', args);
|
|
53
|
+
const feats = await session.rpcManager.call(sessionId, 'CoreGetFeatures', { adapterConfig: getConf(track, 'adapter'), regions });
|
|
60
54
|
for (const feat of feats) {
|
|
61
55
|
const hit = getTranscriptFeatures(feat).find(t => transcriptMatches(t, transcriptId));
|
|
62
56
|
if (hit) {
|