@jbrowse/plugin-blat 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/esm/BlatDialog.js +9 -15
- package/esm/UcscResultsWidget/configSchema.d.ts +1 -1
- package/esm/UcscResultsWidget/index.js +3 -2
- package/esm/UcscResultsWidget/stateModel.d.ts +1 -1
- package/esm/blatQuery.d.ts +29 -4
- package/esm/blatQuery.js +87 -40
- package/esm/pslToSam.d.ts +3 -9
- package/esm/pslToSam.js +8 -29
- package/esm/ucscShared.js +18 -15
- package/package.json +8 -7
package/esm/BlatDialog.js
CHANGED
|
@@ -7,7 +7,7 @@ import MonospaceTextField from './MonospaceTextField.js';
|
|
|
7
7
|
import UcscQueryActions from './UcscQueryActions.js';
|
|
8
8
|
import UcscQueryFields from './UcscQueryFields.js';
|
|
9
9
|
import UcscQueryStatus from './UcscQueryStatus.js';
|
|
10
|
-
import { DEFAULT_BLAT_URL, UCSC_BLAT_URL,
|
|
10
|
+
import { DEFAULT_BLAT_URL, UCSC_BLAT_URL, MINIMUM_BLAT_LENGTH, blatQueryProblem, blatResidueCount, buildBlatBody, parseBlatResponse, parseFastaRecords, pslToFeatures, queryLabel, } from './blatQuery.js';
|
|
11
11
|
import { parseQuerySequences, pslToSam } from './pslToSam.js';
|
|
12
12
|
import { canRenderAlignments } from './ucscShared.js';
|
|
13
13
|
import { runUcscFetch, useUcscQuery } from './useUcscQuery.js';
|
|
@@ -20,20 +20,14 @@ const BlatDialog = observer(function BlatDialog({ session, handleClose, }) {
|
|
|
20
20
|
});
|
|
21
21
|
const { db, urlBase, fallbackUrl, apiKey } = query;
|
|
22
22
|
const [seq, setSeq] = useState('');
|
|
23
|
-
const
|
|
24
|
-
const queryCount =
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
? `Sequence is ${residues.length.toLocaleString()} bp; UCSC BLAT is limited to ${MAXIMUM_BLAT_LENGTH.toLocaleString()} bp`
|
|
30
|
-
: tooMany
|
|
31
|
-
? `${queryCount} sequences; UCSC BLAT is limited to ${MAXIMUM_BLAT_QUERIES} per query`
|
|
32
|
-
: tooShort && residues
|
|
33
|
-
? `Sequence must be at least ${MINIMUM_BLAT_LENGTH} bp`
|
|
34
|
-
: '';
|
|
23
|
+
const records = parseFastaRecords(seq);
|
|
24
|
+
const queryCount = records.length;
|
|
25
|
+
const seqError = blatQueryProblem(records);
|
|
26
|
+
// an empty box disables submit without turning the field red, so the minimum
|
|
27
|
+
// is checked here rather than reported above
|
|
28
|
+
const submittable = !seqError && blatResidueCount(records) >= MINIMUM_BLAT_LENGTH;
|
|
35
29
|
async function handleSubmit() {
|
|
36
|
-
const label = queryLabel(
|
|
30
|
+
const label = queryLabel(records);
|
|
37
31
|
await query.runQuery({
|
|
38
32
|
// PSL states each hit as aligned blocks in query and target coordinates,
|
|
39
33
|
// which is a CIGAR alignment: converting to SAM and showing it in an
|
|
@@ -86,7 +80,7 @@ const BlatDialog = observer(function BlatDialog({ session, handleClose, }) {
|
|
|
86
80
|
}, children: [_jsxs(DialogContent, { style: { display: 'flex', flexDirection: 'column', gap: 12 }, children: [_jsx(MonospaceTextField, { label: "Sequence", value: seq, onChange: event => {
|
|
87
81
|
setSeq(event.target.value);
|
|
88
82
|
}, multiline: true, minRows: 6, maxRows: 14, autoFocus: true, placeholder: "Paste a DNA sequence or FASTA to locate it in the genome", error: !!seqError, helperText: seqError ||
|
|
89
|
-
'DNA, or FASTA with up to 25 records, 25 kb total. Hits are added as a track, and listed best-first in a panel where each one is a link.' }), _jsx(UcscQueryFields, { session: session, query: query, urlLabel: "BLAT server URL" }), _jsx(UcscQueryStatus, { query: query })] }), _jsx(UcscQueryActions, { query: query, submitDisabled:
|
|
83
|
+
'DNA, or FASTA with up to 25 records, 25 kb each and 50 kb in total. Hits are added as a track, and listed best-first in a panel where each one is a link.' }), _jsx(UcscQueryFields, { session: session, query: query, urlLabel: "BLAT server URL" }), _jsx(UcscQueryStatus, { query: query })] }), _jsx(UcscQueryActions, { query: query, submitDisabled: !submittable || !db, onSubmit: () => void handleSubmit(), onCancel: () => {
|
|
90
84
|
handleClose();
|
|
91
85
|
} })] }));
|
|
92
86
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const configSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
1
|
+
export declare const configSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { lazy } from 'react';
|
|
2
1
|
import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType';
|
|
2
|
+
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
3
3
|
import { configSchema } from './configSchema.js';
|
|
4
4
|
import { stateModelFactory } from './stateModel.js';
|
|
5
5
|
export default function UcscResultsWidgetF(pluginManager) {
|
|
6
6
|
pluginManager.addWidgetType(() => new WidgetType({
|
|
7
7
|
name: 'UcscResultsWidget',
|
|
8
|
+
discardOnClose: true,
|
|
8
9
|
heading: 'Search results',
|
|
9
10
|
configSchema,
|
|
10
11
|
stateModel: stateModelFactory(),
|
|
11
|
-
ReactComponent:
|
|
12
|
+
ReactComponent: lazyWithPreload(() => import('./UcscResultsWidget.js')),
|
|
12
13
|
}));
|
|
13
14
|
}
|
|
@@ -36,7 +36,7 @@ export declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
36
36
|
* product, which is a band on a gel — the word a bench scientist reasons in,
|
|
37
37
|
* and "2 hits" understates what two of them mean for a PCR run
|
|
38
38
|
*/
|
|
39
|
-
resultNoun: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<
|
|
39
|
+
resultNoun: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<"hit" | "product">, [undefined]>;
|
|
40
40
|
}, {}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
|
|
41
41
|
export type UcscResultsWidgetStateModel = ReturnType<typeof stateModelFactory>;
|
|
42
42
|
export type UcscResultsWidgetModel = Instance<UcscResultsWidgetStateModel>;
|
package/esm/blatQuery.d.ts
CHANGED
|
@@ -37,11 +37,36 @@ export interface PslRow {
|
|
|
37
37
|
export declare function parsePslRows(data: BlatJsonResponse): PslRow[];
|
|
38
38
|
export declare function pslToFeatures(rows: PslRow[]): SimpleFeatureSerialized[];
|
|
39
39
|
export declare const MINIMUM_BLAT_LENGTH = 20;
|
|
40
|
-
export declare const
|
|
40
|
+
export declare const MAXIMUM_BLAT_SEQUENCE_LENGTH = 25000;
|
|
41
|
+
export declare const MAXIMUM_BLAT_TOTAL_LENGTH = 50000;
|
|
41
42
|
export declare const MAXIMUM_BLAT_QUERIES = 25;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
/**
|
|
44
|
+
* One submitted FASTA record. `name` is the header's first token and is absent
|
|
45
|
+
* for a bare sequence, which hgBlat labels `YourSeq`.
|
|
46
|
+
*
|
|
47
|
+
* `residues` keeps letters only, which is what kent's FASTA reader counts and
|
|
48
|
+
* therefore what both the limits and `qSize` are stated in: a sequence pasted
|
|
49
|
+
* with line numbers or alignment-gap dashes is fewer bases to BLAT than it has
|
|
50
|
+
* characters.
|
|
51
|
+
*/
|
|
52
|
+
export interface FastaRecord {
|
|
53
|
+
name?: string;
|
|
54
|
+
residues: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The submitted text split the way hgBlat splits it: each record is placed
|
|
58
|
+
* separately and its hits are labelled with its name. The length limits, the
|
|
59
|
+
* track label and the name-to-bases map the SAM conversion needs all come off
|
|
60
|
+
* this one reader, so none of them can disagree about where a record starts.
|
|
61
|
+
*
|
|
62
|
+
* The text itself still goes to the server verbatim — stripping the headers here
|
|
63
|
+
* would fuse a multi-record paste into one chimeric query and throw away the
|
|
64
|
+
* names that tell the hits apart.
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseFastaRecords(text: string): FastaRecord[];
|
|
67
|
+
export declare function blatResidueCount(records: FastaRecord[]): number;
|
|
68
|
+
export declare function blatQueryProblem(records: FastaRecord[]): string;
|
|
69
|
+
export declare function queryLabel(records: FastaRecord[]): string;
|
|
45
70
|
export declare function buildBlatBody({ db, seq, apiKey, }: {
|
|
46
71
|
db: string;
|
|
47
72
|
seq: string;
|
package/esm/blatQuery.js
CHANGED
|
@@ -113,43 +113,86 @@ function pslRowToFeature(row, featureIndex) {
|
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
export function pslToFeatures(rows) {
|
|
116
|
-
return rows.map(
|
|
116
|
+
return rows.map(pslRowToFeature);
|
|
117
117
|
}
|
|
118
118
|
export const MINIMUM_BLAT_LENGTH = 20;
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
//
|
|
119
|
+
// hgBlat's own three caps, which its submission page states: 25,000 bases in one
|
|
120
|
+
// DNA sequence, 50,000 across a multi-record submission, 25 records. The
|
|
121
|
+
// per-sequence and combined numbers are separate limits — holding the total to
|
|
122
|
+
// 25,000 refused a three-record FASTA the server would have taken.
|
|
123
|
+
export const MAXIMUM_BLAT_SEQUENCE_LENGTH = 25000;
|
|
124
|
+
export const MAXIMUM_BLAT_TOTAL_LENGTH = 50000;
|
|
122
125
|
export const MAXIMUM_BLAT_QUERIES = 25;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
export function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
126
|
+
/**
|
|
127
|
+
* The submitted text split the way hgBlat splits it: each record is placed
|
|
128
|
+
* separately and its hits are labelled with its name. The length limits, the
|
|
129
|
+
* track label and the name-to-bases map the SAM conversion needs all come off
|
|
130
|
+
* this one reader, so none of them can disagree about where a record starts.
|
|
131
|
+
*
|
|
132
|
+
* The text itself still goes to the server verbatim — stripping the headers here
|
|
133
|
+
* would fuse a multi-record paste into one chimeric query and throw away the
|
|
134
|
+
* names that tell the hits apart.
|
|
135
|
+
*/
|
|
136
|
+
export function parseFastaRecords(text) {
|
|
137
|
+
const records = [];
|
|
138
|
+
let name;
|
|
139
|
+
let residues = [];
|
|
140
|
+
const flush = () => {
|
|
141
|
+
const joined = residues.join('');
|
|
142
|
+
if (joined) {
|
|
143
|
+
records.push({ name, residues: joined });
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
for (const line of text.split('\n')) {
|
|
147
|
+
if (line.startsWith('>')) {
|
|
148
|
+
flush();
|
|
149
|
+
name = /^>\s*(\S+)/.exec(line)?.[1];
|
|
150
|
+
residues = [];
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
residues.push(line.replaceAll(/[^A-Za-z]/g, ''));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
flush();
|
|
157
|
+
return records;
|
|
158
|
+
}
|
|
159
|
+
export function blatResidueCount(records) {
|
|
160
|
+
return records.reduce((sum, record) => sum + record.residues.length, 0);
|
|
161
|
+
}
|
|
162
|
+
// Why this query cannot be sent, in the terms hgBlat states its limits in, or
|
|
163
|
+
// '' when it can. Checked here rather than left to the server so an over-long
|
|
164
|
+
// paste says which cap it broke instead of coming back as a kent error page.
|
|
165
|
+
export function blatQueryProblem(records) {
|
|
166
|
+
const total = blatResidueCount(records);
|
|
167
|
+
// reduced rather than spread into Math.max: the record cap is checked below
|
|
168
|
+
// this, so the list reaching here is whatever was pasted
|
|
169
|
+
const longest = records.reduce((max, record) => Math.max(max, record.residues.length), 0);
|
|
170
|
+
const subject = records.length > 1 ? 'Longest sequence' : 'Sequence';
|
|
171
|
+
if (longest > MAXIMUM_BLAT_SEQUENCE_LENGTH) {
|
|
172
|
+
return `${subject} is ${longest.toLocaleString()} bp; UCSC BLAT is limited to ${MAXIMUM_BLAT_SEQUENCE_LENGTH.toLocaleString()} bp per sequence`;
|
|
173
|
+
}
|
|
174
|
+
else if (total > MAXIMUM_BLAT_TOTAL_LENGTH) {
|
|
175
|
+
return `${total.toLocaleString()} bp in total; UCSC BLAT is limited to ${MAXIMUM_BLAT_TOTAL_LENGTH.toLocaleString()} bp per query`;
|
|
176
|
+
}
|
|
177
|
+
else if (records.length > MAXIMUM_BLAT_QUERIES) {
|
|
178
|
+
return `${records.length} sequences; UCSC BLAT is limited to ${MAXIMUM_BLAT_QUERIES} per query`;
|
|
179
|
+
}
|
|
180
|
+
else if (total > 0 && total < MINIMUM_BLAT_LENGTH) {
|
|
181
|
+
return `Sequence must be at least ${MINIMUM_BLAT_LENGTH} bp`;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// names the result track after what was searched: the first record's header, or
|
|
146
188
|
// the leading bases of a bare sequence
|
|
147
|
-
export function queryLabel(
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
189
|
+
export function queryLabel(records) {
|
|
190
|
+
const first = records[0];
|
|
191
|
+
if (!first) {
|
|
192
|
+
return '';
|
|
193
|
+
}
|
|
194
|
+
const { name, residues } = first;
|
|
195
|
+
return name ?? residues.slice(0, 12) + (residues.length > 12 ? '…' : '');
|
|
153
196
|
}
|
|
154
197
|
export function buildBlatBody({ db, seq, apiKey, }) {
|
|
155
198
|
const params = new URLSearchParams({
|
|
@@ -172,17 +215,21 @@ export function buildBlatBody({ db, seq, apiKey, }) {
|
|
|
172
215
|
export class BlatChallengeError extends Error {
|
|
173
216
|
name = 'BlatChallengeError';
|
|
174
217
|
}
|
|
175
|
-
// Markers on a Cloudflare Turnstile challenge page. Every marker has to
|
|
176
|
-
//
|
|
177
|
-
// matches an ordinary UCSC page, whose
|
|
178
|
-
// FAQdownloads.html#CAPTCHA and
|
|
179
|
-
//
|
|
180
|
-
//
|
|
218
|
+
// Markers on a Cloudflare Turnstile challenge page. Every marker has to name
|
|
219
|
+
// the widget being MOUNTED, never a page that merely mentions it: a bare `cf-`,
|
|
220
|
+
// `captcha` or `challenge` matches an ordinary UCSC page, whose nav links to
|
|
221
|
+
// FAQdownloads.html#CAPTCHA, and a bare `turnstile` matches one too, because
|
|
222
|
+
// UCSC's Content-Security-Policy whitelists
|
|
223
|
+
// `challenges.cloudflare.com/turnstile/v0/api.js` on every page it serves —
|
|
224
|
+
// including the honest "No matches" that hgPcr answers a primer pair with.
|
|
225
|
+
// Each of those words in turn sent a user off to solve a CAPTCHA that was not
|
|
226
|
+
// there. `turnstile.render(` is the call that puts one on screen, and the
|
|
227
|
+
// challenge page is the only page that makes it.
|
|
181
228
|
//
|
|
182
229
|
// Duplicated in `products/aws/blat-proxy/src/routes.ts`, which decides the same thing
|
|
183
230
|
// about the same pages server-side. The proxy is a standalone package and cannot
|
|
184
231
|
// import this one, so the copies are deliberate: narrow one and narrow both.
|
|
185
|
-
const CHALLENGE_MARKERS = /turnstile|cf[-_]chl/i;
|
|
232
|
+
const CHALLENGE_MARKERS = /turnstile\s*\.\s*render|cf[-_](?:chl|turnstile)/i;
|
|
186
233
|
export function isChallengePage(text) {
|
|
187
234
|
return CHALLENGE_MARKERS.test(text);
|
|
188
235
|
}
|
package/esm/pslToSam.d.ts
CHANGED
|
@@ -41,14 +41,8 @@ export declare function pslQuerySeq(row: PslRow, querySequences: Map<string, str
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function pslToSam(rows: PslRow[], querySequences: Map<string, string>): string;
|
|
43
43
|
/**
|
|
44
|
-
* The submitted query text as name -> residues
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* Keeps only letters, which is what Kent's FASTA reader counts: a pasted
|
|
49
|
-
* sequence carrying line numbers, or alignment-gap dashes, is those bases to
|
|
50
|
-
* BLAT and so `qSize` excludes them. Stripping only whitespace left our text
|
|
51
|
-
* longer than the hit it describes, which is exactly the off-by-N that puts
|
|
52
|
-
* every base out of register.
|
|
44
|
+
* The submitted query text as name -> residues, which is the map from a hit back
|
|
45
|
+
* to the bases it was made of. `YourSeq` is what hgBlat calls a record with no
|
|
46
|
+
* header, so that is the name its hits arrive under.
|
|
53
47
|
*/
|
|
54
48
|
export declare function parseQuerySequences(text: string): Map<string, string>;
|
package/esm/pslToSam.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { revcom } from '@jbrowse/core/util';
|
|
2
|
+
import { parseFastaRecords } from './blatQuery.js';
|
|
2
3
|
/**
|
|
3
4
|
* PSL states a hit as a list of aligned blocks in both query and target
|
|
4
5
|
* coordinates, which is exactly the information a CIGAR encodes: each block is
|
|
@@ -119,35 +120,13 @@ export function pslToSam(rows, querySequences) {
|
|
|
119
120
|
].join('\n');
|
|
120
121
|
}
|
|
121
122
|
/**
|
|
122
|
-
* The submitted query text as name -> residues
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* Keeps only letters, which is what Kent's FASTA reader counts: a pasted
|
|
127
|
-
* sequence carrying line numbers, or alignment-gap dashes, is those bases to
|
|
128
|
-
* BLAT and so `qSize` excludes them. Stripping only whitespace left our text
|
|
129
|
-
* longer than the hit it describes, which is exactly the off-by-N that puts
|
|
130
|
-
* every base out of register.
|
|
123
|
+
* The submitted query text as name -> residues, which is the map from a hit back
|
|
124
|
+
* to the bases it was made of. `YourSeq` is what hgBlat calls a record with no
|
|
125
|
+
* header, so that is the name its hits arrive under.
|
|
131
126
|
*/
|
|
132
127
|
export function parseQuerySequences(text) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (residues.length) {
|
|
138
|
-
sequences.set(name, residues.join(''));
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
for (const line of text.split('\n')) {
|
|
142
|
-
if (line.startsWith('>')) {
|
|
143
|
-
flush();
|
|
144
|
-
name = /^>\s*(\S+)/.exec(line)?.[1] ?? 'YourSeq';
|
|
145
|
-
residues = [];
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
residues.push(line.replaceAll(/[^A-Za-z]/g, ''));
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
flush();
|
|
152
|
-
return sequences;
|
|
128
|
+
return new Map(parseFastaRecords(text).map(({ name, residues }) => [
|
|
129
|
+
name ?? 'YourSeq',
|
|
130
|
+
residues,
|
|
131
|
+
]));
|
|
153
132
|
}
|
package/esm/ucscShared.js
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { getConf } from '@jbrowse/core/configuration';
|
|
2
2
|
import { getEnv, isSessionModelWithWidgets, isSessionWithAddSessionTrack, } from '@jbrowse/core/util';
|
|
3
|
+
import { isSameAssemblyName } from '@jbrowse/core/util/tracks';
|
|
3
4
|
import { assemblyToUcscDb } from './ucscDbMap.js';
|
|
4
5
|
function isNavigableView(view) {
|
|
5
|
-
return ('
|
|
6
|
-
typeof view.
|
|
6
|
+
return ('launchTrack' in view &&
|
|
7
|
+
typeof view.launchTrack === 'function' &&
|
|
7
8
|
'navToLocString' in view &&
|
|
8
9
|
typeof view.navToLocString === 'function');
|
|
9
10
|
}
|
|
10
11
|
// jb2hubs stamps the UCSC db on the assembly's sequence.metadata. Its presence
|
|
11
12
|
// is also the one positive proof that UCSC can search the assembly, so the
|
|
12
13
|
// dialogs read it directly to decide whether to warn.
|
|
14
|
+
// Annotated rather than inferred: the path runs past `metadata`, a `frozen`
|
|
15
|
+
// slot, so the read is into arbitrary JSON and the path type stops deriving
|
|
16
|
+
// anything at that segment.
|
|
13
17
|
export function ucscDbStamp(session, name) {
|
|
14
18
|
const assembly = session.assemblyManager.get(name);
|
|
15
|
-
|
|
19
|
+
return assembly
|
|
16
20
|
? getConf(assembly, ['sequence', 'metadata', 'blatDb'])
|
|
17
21
|
: undefined;
|
|
18
|
-
return stamped;
|
|
19
22
|
}
|
|
20
23
|
// Prefers the stamp, falling back to the static alias map for assemblies whose
|
|
21
24
|
// configs predate it. Shared by the BLAT and in-silico PCR dialogs.
|
|
22
25
|
export function resolveUcscDb(session, name) {
|
|
23
|
-
|
|
24
|
-
return stamped ? stamped : assemblyToUcscDb(name);
|
|
26
|
+
return ucscDbStamp(session, name) || assemblyToUcscDb(name);
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
27
29
|
* Whether this host can draw a hit as an alignment rather than as plain blocks.
|
|
@@ -45,7 +47,8 @@ export function featureLocString(feature) {
|
|
|
45
47
|
return `${feature.refName}:${feature.start + 1}-${feature.end}`;
|
|
46
48
|
}
|
|
47
49
|
function findNavigableView(session, assembly) {
|
|
48
|
-
const view = session.views.find(v => v.type === 'LinearGenomeView' &&
|
|
50
|
+
const view = session.views.find(v => v.type === 'LinearGenomeView' &&
|
|
51
|
+
!!v.assemblyNames?.some(n => isSameAssemblyName(n, assembly, session.assemblyManager)));
|
|
49
52
|
return view && isNavigableView(view) ? view : undefined;
|
|
50
53
|
}
|
|
51
54
|
// Navigates an open view to a hit, growing the window so it lands with flanking
|
|
@@ -93,24 +96,24 @@ export async function addResultTrack({ session, assembly, features, trackIdPrefi
|
|
|
93
96
|
});
|
|
94
97
|
const view = findNavigableView(session, assembly);
|
|
95
98
|
if (view) {
|
|
96
|
-
view.
|
|
99
|
+
await view.launchTrack(trackId);
|
|
97
100
|
}
|
|
98
101
|
else {
|
|
99
102
|
session.notify(`Added track "${trackId}" but no open view displays ${assembly}`, 'warning');
|
|
100
103
|
}
|
|
101
104
|
// #region showWidget
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
// properties UcscResultsWidget's state model declares. Not
|
|
105
|
-
// a drawer — an embedded component may be built without one
|
|
106
|
-
// not optional.
|
|
105
|
+
// openWidget constructs the widget and puts it in the drawer, which is
|
|
106
|
+
// addWidget followed by showWidget. The third argument is the initial state,
|
|
107
|
+
// so its keys are the properties UcscResultsWidget's state model declares. Not
|
|
108
|
+
// every session has a drawer — an embedded component may be built without one
|
|
109
|
+
// — so the guard is not optional.
|
|
107
110
|
if (isSessionModelWithWidgets(session)) {
|
|
108
|
-
session.
|
|
111
|
+
session.openWidget('UcscResultsWidget', 'ucscResults', {
|
|
109
112
|
features,
|
|
110
113
|
assembly,
|
|
111
114
|
trackName,
|
|
112
115
|
resultNoun,
|
|
113
|
-
})
|
|
116
|
+
});
|
|
114
117
|
}
|
|
115
118
|
// #endregion
|
|
116
119
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-blat",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 UCSC BLAT client",
|
|
6
6
|
"keywords": [
|
|
@@ -24,15 +24,16 @@
|
|
|
24
24
|
"esbuild": "^0.28.2"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jbrowse/mobx-state-tree": "^6.
|
|
28
|
-
"@mui/icons-material": "^9.
|
|
29
|
-
"@mui/material": "^9.
|
|
30
|
-
"mobx": "^7.0.
|
|
27
|
+
"@jbrowse/mobx-state-tree": "^6.7.0",
|
|
28
|
+
"@mui/icons-material": "^9.4.0",
|
|
29
|
+
"@mui/material": "^9.4.0",
|
|
30
|
+
"mobx": "^7.0.5",
|
|
31
31
|
"mobx-react": "^10.0.2",
|
|
32
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
32
|
+
"@jbrowse/core": "^5.0.0-beta.11"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"react": ">=19.0.0"
|
|
35
|
+
"react": ">=19.0.0",
|
|
36
|
+
"react-dom": ">=19.0.0"
|
|
36
37
|
},
|
|
37
38
|
"publishConfig": {
|
|
38
39
|
"access": "public"
|