jbrowse-plugin-msaview 3.7.0 → 3.8.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/README.md +44 -30
- package/dist/LaunchMsaView/launchTarget.d.ts +3 -0
- package/dist/LaunchMsaView/launchTarget.js +1 -0
- package/dist/LaunchMsaView/launchTarget.test.js +13 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js +25 -2
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +45 -0
- package/dist/MsaViewPanel/model.d.ts +10 -19
- package/dist/MsaViewPanel/model.js +0 -29
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +35 -33
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/unirefHomologs.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
- package/src/LaunchMsaView/launchTarget.test.ts +14 -0
- package/src/LaunchMsaView/launchTarget.ts +8 -4
- package/src/LaunchMsaViewExtensionPoint/index.test.ts +48 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +44 -8
- package/src/MsaViewPanel/model.ts +0 -34
- package/src/utils/unirefHomologs.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/MsaViewPanel/applyRegion.d.ts +0 -23
- package/dist/MsaViewPanel/applyRegion.js +0 -19
- package/dist/MsaViewPanel/applyRegion.test.d.ts +0 -1
- package/dist/MsaViewPanel/applyRegion.test.js +0 -45
- package/src/MsaViewPanel/applyRegion.test.ts +0 -55
- package/src/MsaViewPanel/applyRegion.ts +0 -33
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// pantherOrthologs.ts produce, so the launch, the labels and the aligner do not
|
|
20
20
|
// know which source ran. `protein` is the UniProt accession, which is what the
|
|
21
21
|
// CDD overlay reads through efetch (Swiss-Prot rows only; see the PANTHER note
|
|
22
|
-
// in
|
|
22
|
+
// in docs/alignments-from-a-gene.md).
|
|
23
23
|
import { handleFetch, jsonfetch } from './fetch';
|
|
24
24
|
import { cleanGeneCandidate, dedupeLabels, defaultMaxSpecies, } from './ncbiOrthologs';
|
|
25
25
|
const UNIPROT = 'https://rest.uniprot.org';
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.
|
|
1
|
+
export declare const version = "3.8.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '3.
|
|
1
|
+
export const version = '3.8.0';
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.
|
|
2
|
+
"version": "3.8.0",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "jbrowse-plugin-msaview",
|
|
5
5
|
"repository": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"jsdom": "^30.1.0",
|
|
47
47
|
"mobx": "^7.0.3",
|
|
48
48
|
"mobx-react": "^10.0.2",
|
|
49
|
-
"msa-parsers": "^8.
|
|
49
|
+
"msa-parsers": "^8.2.0",
|
|
50
50
|
"oxfmt": "^0.68.0",
|
|
51
51
|
"oxlint": "^1.83.0",
|
|
52
52
|
"oxlint-tsgolint": "^7.0.2001",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"puppeteer": "^25.11.0",
|
|
57
57
|
"react": "^19.3.0",
|
|
58
58
|
"react-dom": "^19.3.0",
|
|
59
|
-
"react-msaview": "^8.
|
|
59
|
+
"react-msaview": "^8.2.0",
|
|
60
60
|
"rimraf": "^6.1.3",
|
|
61
61
|
"rxjs": "^7.8.2",
|
|
62
62
|
"serve": "^14.2.6",
|
|
@@ -87,10 +87,11 @@
|
|
|
87
87
|
"test:setup:version": "node scripts/test-versions.mjs setup",
|
|
88
88
|
"test:versions": "node scripts/test-versions.mjs run",
|
|
89
89
|
"test:version": "node scripts/test-versions.mjs run",
|
|
90
|
+
"readme-figure": "node scripts/readme-figure.mjs",
|
|
90
91
|
"host-compat": "node scripts/host-compat-probe.mjs --bundle dist/jbrowse-plugin-msaview.umd.production.min.js",
|
|
91
92
|
"check-ci": "node scripts/require-green-ci.mjs",
|
|
92
93
|
"preversion": "pnpm check-ci && pnpm check-host-dep-pins && pnpm lint && pnpm build && pnpm check-host-externals && pnpm host-compat",
|
|
93
|
-
"version": "node -e \"console.log('export const version = \\'' + require('./package.json').version + '\\'')\" > src/version.ts && git-cliff --tag v$npm_package_version --unreleased --prepend CHANGELOG.md && git add src/version.ts CHANGELOG.md",
|
|
94
|
+
"version": "node -e \"console.log('export const version = \\'' + require('./package.json').version + '\\'')\" > src/version.ts && git-cliff --tag v$npm_package_version --unreleased --prepend CHANGELOG.md && (pnpm readme-figure || echo 'WARNING: README figure not refreshed, see above') && git add src/version.ts CHANGELOG.md img/1.png",
|
|
94
95
|
"postversion": "git push --follow-tags"
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -137,6 +137,20 @@ describe('launchTarget', () => {
|
|
|
137
137
|
expect(target?.preferredTranscriptId).toBe(mrna.id())
|
|
138
138
|
})
|
|
139
139
|
|
|
140
|
+
// the canvas host names the clicked isoform beside the gene it fetches, as
|
|
141
|
+
// its own Collapse introns dialog reads it
|
|
142
|
+
test('carries the isoform a canvas click landed on', () => {
|
|
143
|
+
const host = modernHost('gene')
|
|
144
|
+
const target = launchTarget({
|
|
145
|
+
...host,
|
|
146
|
+
contextMenuInfo: {
|
|
147
|
+
...host.contextMenuInfo,
|
|
148
|
+
subfeature: { featureId: 'mrna-2' },
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
expect(target?.preferredTranscriptId).toBe('mrna-2')
|
|
152
|
+
})
|
|
153
|
+
|
|
140
154
|
// a host that has both shapes must not fall through to the legacy branch and
|
|
141
155
|
// launch on a stale feature when the click was not on a gene
|
|
142
156
|
test('a non-gene click on a host carrying both shapes offers nothing', () => {
|
|
@@ -3,12 +3,15 @@ import { geneLikeRoot, isGeneLikeType, isKnownNonCoding } from './codingFeature'
|
|
|
3
3
|
import type { MenuItem } from '@jbrowse/core/ui'
|
|
4
4
|
import type { Feature } from '@jbrowse/core/util'
|
|
5
5
|
|
|
6
|
-
// The canvas LinearBasicDisplay (JBrowse
|
|
7
|
-
// feature via contextMenuInfo + async
|
|
8
|
-
// the v3.7.0 in the wild
|
|
9
|
-
//
|
|
6
|
+
// The canvas LinearBasicDisplay (JBrowse 5; no release through v4.3.0 has it)
|
|
7
|
+
// exposes the right-clicked feature via contextMenuInfo + async
|
|
8
|
+
// fetchFullFeature. Hosts before that -- v4.3.0, and the v3.7.0 in the wild
|
|
9
|
+
// that shipped configs still name -- expose it synchronously as
|
|
10
|
+
// contextMenuFeature, and only have that one.
|
|
10
11
|
export interface ContextMenuInfo {
|
|
11
12
|
item: { featureId: string; type?: string }
|
|
13
|
+
// the isoform under the pointer when the click landed on a gene's child
|
|
14
|
+
subfeature?: { featureId: string }
|
|
12
15
|
displayedRegionIndex: number
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -86,6 +89,7 @@ export function launchTarget(self: DisplayModel): MenuTarget | undefined {
|
|
|
86
89
|
? {
|
|
87
90
|
fetchFeature: () =>
|
|
88
91
|
fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
|
|
92
|
+
preferredTranscriptId: info.subfeature?.featureId,
|
|
89
93
|
}
|
|
90
94
|
: undefined
|
|
91
95
|
}
|
|
@@ -88,3 +88,51 @@ test('one field set is enough to need init', () => {
|
|
|
88
88
|
querySeqName: 'QUERY',
|
|
89
89
|
})
|
|
90
90
|
})
|
|
91
|
+
|
|
92
|
+
test('msa and tree urls reach the plugin sources, query the query row', () => {
|
|
93
|
+
const snapshot = launch({
|
|
94
|
+
msa: 'https://example.com/p53.afa',
|
|
95
|
+
tree: 'https://example.com/p53.nh',
|
|
96
|
+
query: 'Human',
|
|
97
|
+
})
|
|
98
|
+
expect(snapshot.init).toMatchObject({
|
|
99
|
+
msaUrl: 'https://example.com/p53.afa',
|
|
100
|
+
querySeqName: 'Human',
|
|
101
|
+
})
|
|
102
|
+
expect(snapshot.treeFilehandle).toEqual({
|
|
103
|
+
uri: 'https://example.com/p53.nh',
|
|
104
|
+
locationType: 'UriLocation',
|
|
105
|
+
})
|
|
106
|
+
expect(snapshot.relativeTo).toBe('Human')
|
|
107
|
+
expect('msa' in snapshot || 'query' in snapshot).toBe(false)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('inline msa and newick text become inline data', () => {
|
|
111
|
+
const snapshot = launch({ msa: '>a\nMEEP\n>b\nMEEP', tree: '(a,b);' })
|
|
112
|
+
expect(snapshot.data).toEqual({ msa: '>a\nMEEP\n>b\nMEEP', tree: '(a,b);' })
|
|
113
|
+
expect('init' in snapshot).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('highlights, region and column tracks expand onto the query row', () => {
|
|
117
|
+
const snapshot = launch({
|
|
118
|
+
msa: 'https://example.com/p53.afa',
|
|
119
|
+
query: 'Human',
|
|
120
|
+
highlights: ['102-292 DNA-binding', 175],
|
|
121
|
+
region: '170-290',
|
|
122
|
+
columnTracks: [{ name: 'ClinVar', start: 3, values: [2, 1] }],
|
|
123
|
+
})
|
|
124
|
+
expect(snapshot.highlights).toEqual([
|
|
125
|
+
{ row: 'Human', start: 102, end: 292, label: 'DNA-binding' },
|
|
126
|
+
{ row: 'Human', start: 175, end: 175, label: '{residue}{position}' },
|
|
127
|
+
])
|
|
128
|
+
expect(snapshot.region).toEqual({ row: 'Human', start: 170, end: 290 })
|
|
129
|
+
expect(snapshot.columnTracks).toEqual([
|
|
130
|
+
{
|
|
131
|
+
id: 'clinvar',
|
|
132
|
+
name: 'ClinVar',
|
|
133
|
+
kind: 'bar',
|
|
134
|
+
row: 'Human',
|
|
135
|
+
values: [0, 0, 2, 1],
|
|
136
|
+
},
|
|
137
|
+
])
|
|
138
|
+
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { expandSpec } from 'react-msaview'
|
|
2
|
+
|
|
1
3
|
import { launchMsaView } from '../utils/launchMsaView'
|
|
2
4
|
|
|
3
5
|
import type { BlastParams, OrthologParams } from '../MsaViewPanel/model'
|
|
@@ -6,16 +8,25 @@ import type PluginManager from '@jbrowse/core/PluginManager'
|
|
|
6
8
|
import type { AbstractSessionModel } from '@jbrowse/core/util'
|
|
7
9
|
import type {
|
|
8
10
|
Clade,
|
|
9
|
-
|
|
11
|
+
ColumnTrackShorthand,
|
|
10
12
|
Encoding,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
HighlightShorthand,
|
|
14
|
+
RegionShorthand,
|
|
13
15
|
ResidueMapping,
|
|
14
16
|
RowPanelSpec,
|
|
15
17
|
} from 'react-msaview'
|
|
16
18
|
|
|
17
19
|
interface LaunchMsaViewArgs {
|
|
18
20
|
session: AbstractSessionModel
|
|
21
|
+
/**
|
|
22
|
+
* Shorthand, see react-msaview's docs/layers.md: `msa` and `tree` take a
|
|
23
|
+
* url or the text itself, and `query` names the row the spec is about, the
|
|
24
|
+
* `querySeqName` and the `row` of every highlight, region and column track
|
|
25
|
+
* that names none
|
|
26
|
+
*/
|
|
27
|
+
msa?: string
|
|
28
|
+
tree?: string
|
|
29
|
+
query?: string
|
|
19
30
|
data?: { msa: string; tree?: string }
|
|
20
31
|
msaFileLocation?: { uri: string }
|
|
21
32
|
msaIndexedLocation?: { uri: string }
|
|
@@ -54,15 +65,15 @@ interface LaunchMsaViewArgs {
|
|
|
54
65
|
* for whole rows. Prefer this over `highlightColumns`: a residue range stays
|
|
55
66
|
* put when `allowedGappyness` changes the visible columns.
|
|
56
67
|
*/
|
|
57
|
-
highlights?:
|
|
68
|
+
highlights?: HighlightShorthand[]
|
|
58
69
|
/** per-column tracks supplied as data, see react-msaview's docs/layers.md */
|
|
59
|
-
columnTracks?:
|
|
70
|
+
columnTracks?: ColumnTrackShorthand[]
|
|
60
71
|
/**
|
|
61
72
|
* Where the view opens, in `highlights` coordinates: `{row, start, end}`
|
|
62
73
|
* zooms onto residues of that row, `{start, end}` onto alignment columns.
|
|
63
74
|
* Applied once the alignment has loaded, then dropped from the session.
|
|
64
75
|
*/
|
|
65
|
-
region?:
|
|
76
|
+
region?: RegionShorthand
|
|
66
77
|
/**
|
|
67
78
|
* Passed through untouched as react-msaview snapshot properties; see its
|
|
68
79
|
* docs/layers.md. A bare `{ uri }` is enough for the two filehandles.
|
|
@@ -121,6 +132,30 @@ interface LaunchMsaViewArgs {
|
|
|
121
132
|
placement?: MsaViewPlacement
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
/**
|
|
136
|
+
* `msa` and `tree` onto the plugin's own sources, so a url still goes through
|
|
137
|
+
* `init.msaUrl` and its format sniffing, and `query` onto `querySeqName`
|
|
138
|
+
*/
|
|
139
|
+
function withLongSources({ msa, tree, ...args }: LaunchMsaViewArgs) {
|
|
140
|
+
const inlineMsa = msa?.includes('\n') ? msa : undefined
|
|
141
|
+
const inlineTree = tree?.trimStart().startsWith('(') ? tree : undefined
|
|
142
|
+
const data =
|
|
143
|
+
inlineMsa || inlineTree
|
|
144
|
+
? {
|
|
145
|
+
...args.data,
|
|
146
|
+
...(inlineMsa ? { msa: inlineMsa } : {}),
|
|
147
|
+
...(inlineTree ? { tree: inlineTree } : {}),
|
|
148
|
+
}
|
|
149
|
+
: args.data
|
|
150
|
+
return {
|
|
151
|
+
...args,
|
|
152
|
+
data: data as LaunchMsaViewArgs['data'],
|
|
153
|
+
...(msa && !inlineMsa ? { msaFileLocation: { uri: msa } } : {}),
|
|
154
|
+
...(tree && !inlineTree ? { treeFileLocation: { uri: tree } } : {}),
|
|
155
|
+
querySeqName: args.querySeqName ?? args.query,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
124
159
|
export default function LaunchMsaViewExtensionPointF(
|
|
125
160
|
pluginManager: PluginManager,
|
|
126
161
|
) {
|
|
@@ -129,6 +164,7 @@ export default function LaunchMsaViewExtensionPointF(
|
|
|
129
164
|
(args: LaunchMsaViewArgs) => {
|
|
130
165
|
const {
|
|
131
166
|
session,
|
|
167
|
+
query,
|
|
132
168
|
data,
|
|
133
169
|
msaFileLocation,
|
|
134
170
|
msaIndexedLocation,
|
|
@@ -137,7 +173,7 @@ export default function LaunchMsaViewExtensionPointF(
|
|
|
137
173
|
querySeqName,
|
|
138
174
|
searchParams,
|
|
139
175
|
...rest
|
|
140
|
-
} = args
|
|
176
|
+
} = withLongSources(args)
|
|
141
177
|
|
|
142
178
|
// `orthologParams` and `searchParams` name no alignment at all — the
|
|
143
179
|
// view builds one at launch, which is the dialog's Orthologs and BLAST
|
|
@@ -169,7 +205,7 @@ export default function LaunchMsaViewExtensionPointF(
|
|
|
169
205
|
querySeqName,
|
|
170
206
|
}
|
|
171
207
|
launchMsaView(session, {
|
|
172
|
-
...rest,
|
|
208
|
+
...expandSpec({ ...rest, query }),
|
|
173
209
|
...(searchParams ? { blastParams: searchParams } : {}),
|
|
174
210
|
data,
|
|
175
211
|
...(treeFileLocation
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
storeDataToIndexedDB,
|
|
21
21
|
syncGenomeHoverToMsaColumn,
|
|
22
22
|
} from './afterCreateAutoruns'
|
|
23
|
-
import { applyRegion } from './applyRegion'
|
|
24
23
|
import {
|
|
25
24
|
msaCoordToGenomeCoord,
|
|
26
25
|
msaCoordToGenomeRegions,
|
|
@@ -40,7 +39,6 @@ import type { TranscriptRef } from './util'
|
|
|
40
39
|
import type { MenuItem } from '@jbrowse/core/ui'
|
|
41
40
|
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
42
41
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
43
|
-
import type { Region } from 'react-msaview'
|
|
44
42
|
|
|
45
43
|
type LGV = LinearGenomeViewModel
|
|
46
44
|
|
|
@@ -246,14 +244,6 @@ export default function stateModelFactory() {
|
|
|
246
244
|
* #property
|
|
247
245
|
*/
|
|
248
246
|
mafRegion: types.frozen<MafRegion | undefined>(),
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* #property
|
|
252
|
-
* where the view opens: 1-based residues of `row`, or alignment
|
|
253
|
-
* columns without one. Zoomed onto once and then cleared, so a
|
|
254
|
-
* reloaded session keeps the reader's own scroll.
|
|
255
|
-
*/
|
|
256
|
-
region: types.frozen<Region | undefined>(),
|
|
257
247
|
}),
|
|
258
248
|
)
|
|
259
249
|
|
|
@@ -433,12 +423,6 @@ export default function stateModelFactory() {
|
|
|
433
423
|
setQuerySeqName(arg: string) {
|
|
434
424
|
self.querySeqName = arg
|
|
435
425
|
},
|
|
436
|
-
/**
|
|
437
|
-
* #action
|
|
438
|
-
*/
|
|
439
|
-
setQuerySeqOffset(arg: number) {
|
|
440
|
-
self.querySeqOffset = arg
|
|
441
|
-
},
|
|
442
426
|
/**
|
|
443
427
|
* #action
|
|
444
428
|
*/
|
|
@@ -457,18 +441,6 @@ export default function stateModelFactory() {
|
|
|
457
441
|
setLaunchCompleted(arg: boolean) {
|
|
458
442
|
self.launchCompleted = arg
|
|
459
443
|
},
|
|
460
|
-
/**
|
|
461
|
-
* #action
|
|
462
|
-
*/
|
|
463
|
-
setMafRegion(arg?: MafRegion) {
|
|
464
|
-
self.mafRegion = arg
|
|
465
|
-
},
|
|
466
|
-
/**
|
|
467
|
-
* #action
|
|
468
|
-
*/
|
|
469
|
-
setRegion(arg?: Region) {
|
|
470
|
-
self.region = arg
|
|
471
|
-
},
|
|
472
444
|
/**
|
|
473
445
|
* #action
|
|
474
446
|
*/
|
|
@@ -671,12 +643,6 @@ export default function stateModelFactory() {
|
|
|
671
643
|
// so they're factories returning the autorun body rather than plain fns
|
|
672
644
|
addDisposer(self, autorun(syncGenomeHoverToMsaColumn(self)))
|
|
673
645
|
addDisposer(self, autorun(observeProteinHighlights(self)))
|
|
674
|
-
addDisposer(
|
|
675
|
-
self,
|
|
676
|
-
autorun(() => {
|
|
677
|
-
applyRegion(self)
|
|
678
|
-
}),
|
|
679
|
-
)
|
|
680
646
|
},
|
|
681
647
|
}))
|
|
682
648
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// pantherOrthologs.ts produce, so the launch, the labels and the aligner do not
|
|
20
20
|
// know which source ran. `protein` is the UniProt accession, which is what the
|
|
21
21
|
// CDD overlay reads through efetch (Swiss-Prot rows only; see the PANTHER note
|
|
22
|
-
// in
|
|
22
|
+
// in docs/alignments-from-a-gene.md).
|
|
23
23
|
|
|
24
24
|
import { handleFetch, jsonfetch } from './fetch'
|
|
25
25
|
import {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '3.
|
|
1
|
+
export const version = '3.8.0'
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Region } from 'react-msaview';
|
|
2
|
-
interface RegionModel {
|
|
3
|
-
region?: Region;
|
|
4
|
-
viewInitialized: boolean;
|
|
5
|
-
numColumns: number;
|
|
6
|
-
treeFilehandle?: unknown;
|
|
7
|
-
data: {
|
|
8
|
-
tree?: string;
|
|
9
|
-
};
|
|
10
|
-
zoomToRegion: (region: Region) => void;
|
|
11
|
-
setRegion: (region?: Region) => void;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Zoom onto the launch's `region` once, then forget it, so a reloaded session
|
|
15
|
-
* opens where the reader left it rather than back on the region.
|
|
16
|
-
*
|
|
17
|
-
* zoomToRegion is a no-op until the view has a width, and it resolves residues
|
|
18
|
-
* through the visible columns, so it waits for the alignment and for a tree
|
|
19
|
-
* file too: a clade the tree collapses hides columns and would move the
|
|
20
|
-
* target after the zoom.
|
|
21
|
-
*/
|
|
22
|
-
export declare function applyRegion(self: RegionModel): void;
|
|
23
|
-
export {};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zoom onto the launch's `region` once, then forget it, so a reloaded session
|
|
3
|
-
* opens where the reader left it rather than back on the region.
|
|
4
|
-
*
|
|
5
|
-
* zoomToRegion is a no-op until the view has a width, and it resolves residues
|
|
6
|
-
* through the visible columns, so it waits for the alignment and for a tree
|
|
7
|
-
* file too: a clade the tree collapses hides columns and would move the
|
|
8
|
-
* target after the zoom.
|
|
9
|
-
*/
|
|
10
|
-
export function applyRegion(self) {
|
|
11
|
-
const { region } = self;
|
|
12
|
-
if (region &&
|
|
13
|
-
self.viewInitialized &&
|
|
14
|
-
self.numColumns > 0 &&
|
|
15
|
-
!(self.treeFilehandle && !self.data.tree)) {
|
|
16
|
-
self.zoomToRegion(region);
|
|
17
|
-
self.setRegion(undefined);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { expect, test, vi } from 'vitest';
|
|
2
|
-
import { applyRegion } from './applyRegion';
|
|
3
|
-
const REGION = { row: 'Human', start: 245, end: 249 };
|
|
4
|
-
function makeModel(over = {}) {
|
|
5
|
-
const model = {
|
|
6
|
-
region: REGION,
|
|
7
|
-
viewInitialized: true,
|
|
8
|
-
numColumns: 400,
|
|
9
|
-
treeFilehandle: undefined,
|
|
10
|
-
data: {},
|
|
11
|
-
zoomToRegion: vi.fn(),
|
|
12
|
-
setRegion: vi.fn((arg) => {
|
|
13
|
-
model.region = arg;
|
|
14
|
-
}),
|
|
15
|
-
...over,
|
|
16
|
-
};
|
|
17
|
-
return model;
|
|
18
|
-
}
|
|
19
|
-
test('zooms onto the region once, then forgets it', () => {
|
|
20
|
-
const model = makeModel();
|
|
21
|
-
applyRegion(model);
|
|
22
|
-
applyRegion(model);
|
|
23
|
-
expect(model.zoomToRegion).toHaveBeenCalledTimes(1);
|
|
24
|
-
expect(model.zoomToRegion).toHaveBeenCalledWith(REGION);
|
|
25
|
-
expect(model.region).toBeUndefined();
|
|
26
|
-
});
|
|
27
|
-
test('waits for the view to have a width', () => {
|
|
28
|
-
const model = makeModel({ viewInitialized: false });
|
|
29
|
-
applyRegion(model);
|
|
30
|
-
expect(model.zoomToRegion).not.toHaveBeenCalled();
|
|
31
|
-
expect(model.region).toEqual(REGION);
|
|
32
|
-
});
|
|
33
|
-
test('waits for the alignment', () => {
|
|
34
|
-
const model = makeModel({ numColumns: 0 });
|
|
35
|
-
applyRegion(model);
|
|
36
|
-
expect(model.zoomToRegion).not.toHaveBeenCalled();
|
|
37
|
-
});
|
|
38
|
-
test('waits for a tree file, whose collapsed clades move the columns', () => {
|
|
39
|
-
const model = makeModel({ treeFilehandle: { uri: 'x.nh' } });
|
|
40
|
-
applyRegion(model);
|
|
41
|
-
expect(model.zoomToRegion).not.toHaveBeenCalled();
|
|
42
|
-
model.data.tree = '(a,b);';
|
|
43
|
-
applyRegion(model);
|
|
44
|
-
expect(model.zoomToRegion).toHaveBeenCalledTimes(1);
|
|
45
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { expect, test, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { applyRegion } from './applyRegion'
|
|
4
|
-
|
|
5
|
-
import type { Region } from 'react-msaview'
|
|
6
|
-
|
|
7
|
-
const REGION: Region = { row: 'Human', start: 245, end: 249 }
|
|
8
|
-
|
|
9
|
-
function makeModel(over: Record<string, unknown> = {}) {
|
|
10
|
-
const model = {
|
|
11
|
-
region: REGION as Region | undefined,
|
|
12
|
-
viewInitialized: true,
|
|
13
|
-
numColumns: 400,
|
|
14
|
-
treeFilehandle: undefined as unknown,
|
|
15
|
-
data: {} as { tree?: string },
|
|
16
|
-
zoomToRegion: vi.fn(),
|
|
17
|
-
setRegion: vi.fn((arg?: Region) => {
|
|
18
|
-
model.region = arg
|
|
19
|
-
}),
|
|
20
|
-
...over,
|
|
21
|
-
}
|
|
22
|
-
return model
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
test('zooms onto the region once, then forgets it', () => {
|
|
26
|
-
const model = makeModel()
|
|
27
|
-
applyRegion(model)
|
|
28
|
-
applyRegion(model)
|
|
29
|
-
expect(model.zoomToRegion).toHaveBeenCalledTimes(1)
|
|
30
|
-
expect(model.zoomToRegion).toHaveBeenCalledWith(REGION)
|
|
31
|
-
expect(model.region).toBeUndefined()
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('waits for the view to have a width', () => {
|
|
35
|
-
const model = makeModel({ viewInitialized: false })
|
|
36
|
-
applyRegion(model)
|
|
37
|
-
expect(model.zoomToRegion).not.toHaveBeenCalled()
|
|
38
|
-
expect(model.region).toEqual(REGION)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('waits for the alignment', () => {
|
|
42
|
-
const model = makeModel({ numColumns: 0 })
|
|
43
|
-
applyRegion(model)
|
|
44
|
-
expect(model.zoomToRegion).not.toHaveBeenCalled()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('waits for a tree file, whose collapsed clades move the columns', () => {
|
|
48
|
-
const model = makeModel({ treeFilehandle: { uri: 'x.nh' } })
|
|
49
|
-
applyRegion(model)
|
|
50
|
-
expect(model.zoomToRegion).not.toHaveBeenCalled()
|
|
51
|
-
|
|
52
|
-
model.data.tree = '(a,b);'
|
|
53
|
-
applyRegion(model)
|
|
54
|
-
expect(model.zoomToRegion).toHaveBeenCalledTimes(1)
|
|
55
|
-
})
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Region } from 'react-msaview'
|
|
2
|
-
|
|
3
|
-
interface RegionModel {
|
|
4
|
-
region?: Region
|
|
5
|
-
viewInitialized: boolean
|
|
6
|
-
numColumns: number
|
|
7
|
-
treeFilehandle?: unknown
|
|
8
|
-
data: { tree?: string }
|
|
9
|
-
zoomToRegion: (region: Region) => void
|
|
10
|
-
setRegion: (region?: Region) => void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Zoom onto the launch's `region` once, then forget it, so a reloaded session
|
|
15
|
-
* opens where the reader left it rather than back on the region.
|
|
16
|
-
*
|
|
17
|
-
* zoomToRegion is a no-op until the view has a width, and it resolves residues
|
|
18
|
-
* through the visible columns, so it waits for the alignment and for a tree
|
|
19
|
-
* file too: a clade the tree collapses hides columns and would move the
|
|
20
|
-
* target after the zoom.
|
|
21
|
-
*/
|
|
22
|
-
export function applyRegion(self: RegionModel) {
|
|
23
|
-
const { region } = self
|
|
24
|
-
if (
|
|
25
|
-
region &&
|
|
26
|
-
self.viewInitialized &&
|
|
27
|
-
self.numColumns > 0 &&
|
|
28
|
-
!(self.treeFilehandle && !self.data.tree)
|
|
29
|
-
) {
|
|
30
|
-
self.zoomToRegion(region)
|
|
31
|
-
self.setRegion(undefined)
|
|
32
|
-
}
|
|
33
|
-
}
|