@jbrowse/plugin-linear-genome-view 2.3.1 → 2.3.3
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/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +3 -1
- package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -12
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js +1 -1
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -1
- package/dist/LinearBasicDisplay/model.d.ts +8 -9
- package/dist/LinearGenomeView/components/GetSequenceDialog.js +42 -29
- package/dist/LinearGenomeView/components/GetSequenceDialog.js.map +1 -1
- package/dist/LinearGenomeView/components/RefNameAutocomplete.js +1 -1
- package/dist/LinearGenomeView/model.js +19 -23
- package/dist/LinearGenomeView/model.js.map +1 -1
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +3 -1
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
- package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -12
- package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js +2 -2
- package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -1
- package/esm/LinearBasicDisplay/model.d.ts +8 -9
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +44 -31
- package/esm/LinearGenomeView/components/GetSequenceDialog.js.map +1 -1
- package/esm/LinearGenomeView/components/RefNameAutocomplete.js +1 -1
- package/esm/LinearGenomeView/model.js +19 -23
- package/esm/LinearGenomeView/model.js.map +1 -1
- package/package.json +4 -2
- package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +33 -32
- package/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts +9 -2
- package/src/LinearGenomeView/components/GetSequenceDialog.tsx +84 -37
- package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +1 -1
- package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap +1 -1
- package/src/LinearGenomeView/model.ts +28 -24
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
types,
|
|
5
|
+
getParent,
|
|
6
|
+
isAlive,
|
|
7
|
+
cast,
|
|
8
|
+
Instance,
|
|
9
|
+
getSnapshot,
|
|
10
|
+
} from 'mobx-state-tree'
|
|
4
11
|
import { readConfObject } from '@jbrowse/core/configuration'
|
|
5
12
|
import {
|
|
6
13
|
assembleLocString,
|
|
@@ -238,7 +245,7 @@ export function renderBlockData(
|
|
|
238
245
|
}
|
|
239
246
|
},
|
|
240
247
|
assemblyName: self.region.assemblyName,
|
|
241
|
-
regions: [self.region],
|
|
248
|
+
regions: [getSnapshot(self.region)],
|
|
242
249
|
adapterConfig,
|
|
243
250
|
rendererType: rendererType.name,
|
|
244
251
|
sessionId,
|
|
@@ -2,10 +2,13 @@ import React, { useEffect, useMemo, useState } from 'react'
|
|
|
2
2
|
import { makeStyles } from 'tss-react/mui'
|
|
3
3
|
import {
|
|
4
4
|
Button,
|
|
5
|
+
Checkbox,
|
|
5
6
|
CircularProgress,
|
|
6
7
|
Container,
|
|
7
8
|
DialogActions,
|
|
8
9
|
DialogContent,
|
|
10
|
+
FormGroup,
|
|
11
|
+
FormControlLabel,
|
|
9
12
|
TextField,
|
|
10
13
|
Typography,
|
|
11
14
|
} from '@mui/material'
|
|
@@ -14,7 +17,13 @@ import { saveAs } from 'file-saver'
|
|
|
14
17
|
import { getConf } from '@jbrowse/core/configuration'
|
|
15
18
|
import copy from 'copy-to-clipboard'
|
|
16
19
|
import { Dialog } from '@jbrowse/core/ui'
|
|
17
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
getSession,
|
|
22
|
+
reverse,
|
|
23
|
+
complement,
|
|
24
|
+
Feature,
|
|
25
|
+
Region,
|
|
26
|
+
} from '@jbrowse/core/util'
|
|
18
27
|
import { formatSeqFasta } from '@jbrowse/core/util/formatFastaStrings'
|
|
19
28
|
|
|
20
29
|
// icons
|
|
@@ -80,9 +89,12 @@ function SequenceDialog({
|
|
|
80
89
|
const { classes } = useStyles()
|
|
81
90
|
const session = getSession(model)
|
|
82
91
|
const [error, setError] = useState<unknown>()
|
|
83
|
-
const [
|
|
84
|
-
const
|
|
92
|
+
const [sequenceChunks, setSequenceChunks] = useState<Feature[]>()
|
|
93
|
+
const [rev, setReverse] = useState(false)
|
|
94
|
+
const [copied, setCopied] = useState(false)
|
|
95
|
+
const [comp, setComplement] = useState(false)
|
|
85
96
|
const { leftOffset, rightOffset } = model
|
|
97
|
+
const loading = Boolean(sequenceChunks === undefined)
|
|
86
98
|
|
|
87
99
|
// avoid infinite looping of useEffect
|
|
88
100
|
// random note: the current selected region can't be a computed because it
|
|
@@ -106,27 +118,7 @@ function SequenceDialog({
|
|
|
106
118
|
controller.signal,
|
|
107
119
|
)
|
|
108
120
|
if (active) {
|
|
109
|
-
|
|
110
|
-
formatSeqFasta(
|
|
111
|
-
chunks
|
|
112
|
-
.filter(f => !!f)
|
|
113
|
-
.map(chunk => {
|
|
114
|
-
const chunkSeq = chunk.get('seq')
|
|
115
|
-
const chunkRefName = chunk.get('refName')
|
|
116
|
-
const chunkStart = chunk.get('start') + 1
|
|
117
|
-
const chunkEnd = chunk.get('end')
|
|
118
|
-
const chunkLocstring = `${chunkRefName}:${chunkStart}-${chunkEnd}`
|
|
119
|
-
if (chunkSeq?.length !== chunkEnd - chunkStart + 1) {
|
|
120
|
-
throw new Error(
|
|
121
|
-
`${chunkLocstring} returned ${chunkSeq.length.toLocaleString()} bases, but should have returned ${(
|
|
122
|
-
chunkEnd - chunkStart
|
|
123
|
-
).toLocaleString()}`,
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
return { header: chunkLocstring, seq: chunkSeq }
|
|
127
|
-
}),
|
|
128
|
-
),
|
|
129
|
-
)
|
|
121
|
+
setSequenceChunks(chunks)
|
|
130
122
|
}
|
|
131
123
|
} else {
|
|
132
124
|
throw new Error('Selected region is out of bounds')
|
|
@@ -143,7 +135,36 @@ function SequenceDialog({
|
|
|
143
135
|
controller.abort()
|
|
144
136
|
active = false
|
|
145
137
|
}
|
|
146
|
-
}, [model, session, regionsSelected
|
|
138
|
+
}, [model, session, regionsSelected])
|
|
139
|
+
|
|
140
|
+
const sequence = sequenceChunks
|
|
141
|
+
? formatSeqFasta(
|
|
142
|
+
sequenceChunks
|
|
143
|
+
.filter(f => !!f)
|
|
144
|
+
.map(chunk => {
|
|
145
|
+
let chunkSeq = chunk.get('seq')
|
|
146
|
+
const chunkRefName = chunk.get('refName')
|
|
147
|
+
const chunkStart = chunk.get('start') + 1
|
|
148
|
+
const chunkEnd = chunk.get('end')
|
|
149
|
+
const chunkLocstring = `${chunkRefName}:${chunkStart}-${chunkEnd}`
|
|
150
|
+
if (chunkSeq?.length !== chunkEnd - chunkStart + 1) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`${chunkLocstring} returned ${chunkSeq.length.toLocaleString()} bases, but should have returned ${(
|
|
153
|
+
chunkEnd - chunkStart
|
|
154
|
+
).toLocaleString()}`,
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (rev) {
|
|
159
|
+
chunkSeq = reverse(chunkSeq)
|
|
160
|
+
}
|
|
161
|
+
if (comp) {
|
|
162
|
+
chunkSeq = complement(chunkSeq)
|
|
163
|
+
}
|
|
164
|
+
return { header: chunkLocstring, seq: chunkSeq }
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
: ''
|
|
147
168
|
|
|
148
169
|
const sequenceTooLarge = sequence ? sequence.length > 1_000_000 : false
|
|
149
170
|
|
|
@@ -158,14 +179,13 @@ function SequenceDialog({
|
|
|
158
179
|
title="Reference sequence"
|
|
159
180
|
>
|
|
160
181
|
<DialogContent>
|
|
161
|
-
{error ?
|
|
162
|
-
|
|
182
|
+
{error ? (
|
|
183
|
+
<Typography color="error">{`${error}`}</Typography>
|
|
184
|
+
) : loading ? (
|
|
163
185
|
<Container>
|
|
164
186
|
Retrieving reference sequence...
|
|
165
187
|
<CircularProgress
|
|
166
|
-
style={{
|
|
167
|
-
marginLeft: 10,
|
|
168
|
-
}}
|
|
188
|
+
style={{ marginLeft: 10 }}
|
|
169
189
|
size={20}
|
|
170
190
|
disableShrink
|
|
171
191
|
/>
|
|
@@ -176,6 +196,7 @@ function SequenceDialog({
|
|
|
176
196
|
variant="outlined"
|
|
177
197
|
multiline
|
|
178
198
|
minRows={5}
|
|
199
|
+
maxRows={10}
|
|
179
200
|
disabled={sequenceTooLarge}
|
|
180
201
|
className={classes.dialogContent}
|
|
181
202
|
fullWidth
|
|
@@ -191,25 +212,51 @@ function SequenceDialog({
|
|
|
191
212
|
},
|
|
192
213
|
}}
|
|
193
214
|
/>
|
|
215
|
+
<FormGroup>
|
|
216
|
+
<FormControlLabel
|
|
217
|
+
control={
|
|
218
|
+
<Checkbox
|
|
219
|
+
value={rev}
|
|
220
|
+
onChange={event => setReverse(event.target.checked)}
|
|
221
|
+
/>
|
|
222
|
+
}
|
|
223
|
+
label="Reverse sequence"
|
|
224
|
+
/>
|
|
225
|
+
<FormControlLabel
|
|
226
|
+
control={
|
|
227
|
+
<Checkbox
|
|
228
|
+
value={comp}
|
|
229
|
+
onChange={event => setComplement(event.target.checked)}
|
|
230
|
+
/>
|
|
231
|
+
}
|
|
232
|
+
label="Complement sequence"
|
|
233
|
+
/>
|
|
234
|
+
</FormGroup>
|
|
235
|
+
<Typography style={{ margin: 10 }}>
|
|
236
|
+
Note: Check both boxes for the "reverse complement"
|
|
237
|
+
</Typography>
|
|
194
238
|
</DialogContent>
|
|
195
239
|
<DialogActions>
|
|
196
240
|
<Button
|
|
197
241
|
onClick={() => {
|
|
198
|
-
copy(sequence
|
|
199
|
-
|
|
242
|
+
copy(sequence)
|
|
243
|
+
setCopied(true)
|
|
244
|
+
setTimeout(() => setCopied(false), 500)
|
|
200
245
|
}}
|
|
201
246
|
disabled={loading || !!error || sequenceTooLarge}
|
|
202
247
|
color="primary"
|
|
203
248
|
startIcon={<ContentCopyIcon />}
|
|
204
249
|
>
|
|
205
|
-
Copy to clipboard
|
|
250
|
+
{copied ? 'Copied' : 'Copy to clipboard'}
|
|
206
251
|
</Button>
|
|
207
252
|
<Button
|
|
208
253
|
onClick={() => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
254
|
+
saveAs(
|
|
255
|
+
new Blob([sequence || ''], {
|
|
256
|
+
type: 'text/x-fasta;charset=utf-8',
|
|
257
|
+
}),
|
|
258
|
+
'jbrowse_ref_seq.fa',
|
|
259
|
+
)
|
|
213
260
|
}}
|
|
214
261
|
disabled={loading || !!error}
|
|
215
262
|
color="primary"
|
|
@@ -149,7 +149,7 @@ function RefNameAutocomplete({
|
|
|
149
149
|
// heuristic, text width + icon width
|
|
150
150
|
// + 45 accommodates help icon and search icon
|
|
151
151
|
const width = Math.min(
|
|
152
|
-
Math.max(measureText(inputBoxVal, 16) +
|
|
152
|
+
Math.max(measureText(inputBoxVal, 16) + 50, minWidth),
|
|
153
153
|
550,
|
|
154
154
|
)
|
|
155
155
|
|
|
@@ -718,7 +718,7 @@ exports[`renders two tracks, two regions 1`] = `
|
|
|
718
718
|
<div
|
|
719
719
|
class="MuiAutocomplete-root css-1qqsdnr-MuiAutocomplete-root"
|
|
720
720
|
data-testid="autocomplete"
|
|
721
|
-
style="width:
|
|
721
|
+
style="width: 260.27500000000003px;"
|
|
722
722
|
>
|
|
723
723
|
<div
|
|
724
724
|
class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root css-1efd6m0-MuiFormControl-root-MuiTextField-root-headerRefName"
|
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
getSession,
|
|
13
13
|
isViewContainer,
|
|
14
14
|
isSessionModelWithWidgets,
|
|
15
|
+
isSessionWithAddTracks,
|
|
16
|
+
localStorageGetItem,
|
|
15
17
|
measureText,
|
|
16
18
|
parseLocString,
|
|
17
19
|
springAnimate,
|
|
18
|
-
isSessionWithAddTracks,
|
|
19
20
|
} from '@jbrowse/core/util'
|
|
20
21
|
import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
|
|
21
22
|
import { BlockSet, BaseBlock } from '@jbrowse/core/util/blockTypes'
|
|
@@ -116,12 +117,6 @@ export const INTER_REGION_PADDING_WIDTH = 2
|
|
|
116
117
|
export const SPACING = 7
|
|
117
118
|
export const WIDGET_HEIGHT = 32
|
|
118
119
|
|
|
119
|
-
function localStorageGetItem(item: string) {
|
|
120
|
-
return typeof localStorage !== 'undefined'
|
|
121
|
-
? localStorage.getItem(item)
|
|
122
|
-
: undefined
|
|
123
|
-
}
|
|
124
|
-
|
|
125
120
|
/**
|
|
126
121
|
* #stateModel LinearGenomeView
|
|
127
122
|
*/
|
|
@@ -137,9 +132,9 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
137
132
|
|
|
138
133
|
/**
|
|
139
134
|
* #property
|
|
140
|
-
* this is a string instead of the const literal 'LinearGenomeView' to
|
|
141
|
-
* typescripting strictness, but you should pass the string
|
|
142
|
-
* the model explicitly
|
|
135
|
+
* this is a string instead of the const literal 'LinearGenomeView' to
|
|
136
|
+
* reduce some typescripting strictness, but you should pass the string
|
|
137
|
+
* 'LinearGenomeView' to the model explicitly
|
|
143
138
|
*/
|
|
144
139
|
type: types.literal('LinearGenomeView') as unknown as string,
|
|
145
140
|
|
|
@@ -157,9 +152,10 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
157
152
|
|
|
158
153
|
/**
|
|
159
154
|
* #property
|
|
160
|
-
* currently displayed regions, can be a single chromosome, arbitrary
|
|
161
|
-
* or the entire set of chromosomes in the genome, but it not
|
|
162
|
-
* entire set of chromosomes if your assembly is very
|
|
155
|
+
* currently displayed regions, can be a single chromosome, arbitrary
|
|
156
|
+
* subsections, or the entire set of chromosomes in the genome, but it not
|
|
157
|
+
* advised to use the entire set of chromosomes if your assembly is very
|
|
158
|
+
* fragmented
|
|
163
159
|
*/
|
|
164
160
|
displayedRegions: types.array(MUIRegion),
|
|
165
161
|
|
|
@@ -208,19 +204,17 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
208
204
|
* #property
|
|
209
205
|
* show the "center line"
|
|
210
206
|
*/
|
|
211
|
-
showCenterLine: types.optional(types.boolean, () =>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}),
|
|
207
|
+
showCenterLine: types.optional(types.boolean, () =>
|
|
208
|
+
JSON.parse(localStorageGetItem('lgv-showCenterLine') || 'false'),
|
|
209
|
+
),
|
|
215
210
|
|
|
216
211
|
/**
|
|
217
212
|
* #property
|
|
218
213
|
* show the "cytobands" in the overview scale bar
|
|
219
214
|
*/
|
|
220
|
-
showCytobandsSetting: types.optional(types.boolean, () =>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}),
|
|
215
|
+
showCytobandsSetting: types.optional(types.boolean, () =>
|
|
216
|
+
JSON.parse(localStorageGetItem('lgv-showCytobands') || 'true'),
|
|
217
|
+
),
|
|
224
218
|
|
|
225
219
|
/**
|
|
226
220
|
* #property
|
|
@@ -529,7 +523,6 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
529
523
|
*/
|
|
530
524
|
setShowCytobands(flag: boolean) {
|
|
531
525
|
self.showCytobandsSetting = flag
|
|
532
|
-
localStorage.setItem('lgv-showCytobands', `${+flag}`)
|
|
533
526
|
},
|
|
534
527
|
/**
|
|
535
528
|
* #action
|
|
@@ -757,7 +750,6 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
757
750
|
*/
|
|
758
751
|
setTrackLabels(setting: 'overlapping' | 'offset' | 'hidden') {
|
|
759
752
|
self.trackLabels = setting
|
|
760
|
-
localStorage.setItem('lgv-trackLabels', setting)
|
|
761
753
|
},
|
|
762
754
|
|
|
763
755
|
/**
|
|
@@ -765,7 +757,6 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
765
757
|
*/
|
|
766
758
|
toggleCenterLine() {
|
|
767
759
|
self.showCenterLine = !self.showCenterLine
|
|
768
|
-
localStorage.setItem('lgv-showCenterLine', `${+self.showCenterLine}`)
|
|
769
760
|
},
|
|
770
761
|
|
|
771
762
|
/**
|
|
@@ -1241,6 +1232,19 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
1241
1232
|
{ delay: 150 },
|
|
1242
1233
|
),
|
|
1243
1234
|
)
|
|
1235
|
+
|
|
1236
|
+
addDisposer(
|
|
1237
|
+
self,
|
|
1238
|
+
autorun(() => {
|
|
1239
|
+
const s = (s: unknown) => JSON.stringify(s)
|
|
1240
|
+
const { trackLabels, showCytobandsSetting, showCenterLine } = self
|
|
1241
|
+
if (typeof localStorage !== 'undefined') {
|
|
1242
|
+
localStorage.setItem('lgv-trackLabels', trackLabels)
|
|
1243
|
+
localStorage.setItem('lgv-showCytobands', s(showCytobandsSetting))
|
|
1244
|
+
localStorage.setItem('lgv-showCenterLine', s(showCenterLine))
|
|
1245
|
+
}
|
|
1246
|
+
}),
|
|
1247
|
+
)
|
|
1244
1248
|
},
|
|
1245
1249
|
}))
|
|
1246
1250
|
.actions(self => ({
|