@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.
Files changed (29) hide show
  1. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +3 -1
  2. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
  3. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -12
  4. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js +1 -1
  5. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -1
  6. package/dist/LinearBasicDisplay/model.d.ts +8 -9
  7. package/dist/LinearGenomeView/components/GetSequenceDialog.js +42 -29
  8. package/dist/LinearGenomeView/components/GetSequenceDialog.js.map +1 -1
  9. package/dist/LinearGenomeView/components/RefNameAutocomplete.js +1 -1
  10. package/dist/LinearGenomeView/model.js +19 -23
  11. package/dist/LinearGenomeView/model.js.map +1 -1
  12. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +3 -1
  13. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
  14. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -12
  15. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js +2 -2
  16. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -1
  17. package/esm/LinearBasicDisplay/model.d.ts +8 -9
  18. package/esm/LinearGenomeView/components/GetSequenceDialog.js +44 -31
  19. package/esm/LinearGenomeView/components/GetSequenceDialog.js.map +1 -1
  20. package/esm/LinearGenomeView/components/RefNameAutocomplete.js +1 -1
  21. package/esm/LinearGenomeView/model.js +19 -23
  22. package/esm/LinearGenomeView/model.js.map +1 -1
  23. package/package.json +4 -2
  24. package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +33 -32
  25. package/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts +9 -2
  26. package/src/LinearGenomeView/components/GetSequenceDialog.tsx +84 -37
  27. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +1 -1
  28. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap +1 -1
  29. 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 { types, getParent, isAlive, cast, Instance } from 'mobx-state-tree'
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 { getSession, Feature, Region } from '@jbrowse/core/util'
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 [sequence, setSequence] = useState<string>()
84
- const loading = Boolean(sequence === undefined)
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
- setSequence(
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, setSequence])
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 ? <Typography color="error">{`${error}`}</Typography> : null}
162
- {loading && !error ? (
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
- session.notify('Copied to clipboard', 'success')
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
- const seqFastaFile = new Blob([sequence || ''], {
210
- type: 'text/x-fasta;charset=utf-8',
211
- })
212
- saveAs(seqFastaFile, 'jbrowse_ref_seq.fa')
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) + 45, minWidth),
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: 255.27500000000003px;"
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 reduce some
141
- * typescripting strictness, but you should pass the string 'LinearGenomeView' to
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 subsections,
161
- * or the entire set of chromosomes in the genome, but it not advised to use the
162
- * entire set of chromosomes if your assembly is very fragmented
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
- const setting = localStorageGetItem('lgv-showCenterLine')
213
- return setting !== undefined && setting !== null ? !!+setting : false
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
- const setting = localStorageGetItem('lgv-showCytobands')
222
- return setting !== undefined && setting !== null ? !!+setting : true
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 => ({